diff --git a/apps/desktop/src/lib/__tests__/sql/editableQueryHiddenKeys.spec.ts b/apps/desktop/src/lib/__tests__/sql/editableQueryHiddenKeys.spec.ts index 8b08b3fe56..e3358f2121 100644 --- a/apps/desktop/src/lib/__tests__/sql/editableQueryHiddenKeys.spec.ts +++ b/apps/desktop/src/lib/__tests__/sql/editableQueryHiddenKeys.spec.ts @@ -64,6 +64,18 @@ describe("editable query hidden primary keys", () => { }); }); + it("preserves a WHERE subquery when adding an Oracle ROWID", () => { + expect( + buildQueryWithHiddenPrimaryKeys({ + sql: "SELECT t.* FROM APP.PLATFORM_CARS t WHERE t.CUSTOMER_NO IN (SELECT c.CUSTOMER_NO FROM APP.CUSTOMERS c WHERE c.ENABLED = 1)", + databaseType: "oracle", + primaryKeys: ["__DBX_ROWID"], + existingResultNames: ["ID", "CUSTOMER_NO"], + sourceExpressions: { __DBX_ROWID: "ROWIDTOCHAR(ROWID)" }, + })?.sql, + ).toBe('SELECT t.*, ROWIDTOCHAR(ROWID) AS "__DBX_PK_0" FROM APP.PLATFORM_CARS t WHERE t.CUSTOMER_NO IN (SELECT c.CUSTOMER_NO FROM APP.CUSTOMERS c WHERE c.ENABLED = 1)'); + }); + it("inserts hidden keys before a trailing line comment", () => { expect( buildQueryWithHiddenPrimaryKeys({ diff --git a/apps/desktop/src/stores/__tests__/queryStore.hiddenPrimaryKey.spec.ts b/apps/desktop/src/stores/__tests__/queryStore.hiddenPrimaryKey.spec.ts index fd3181c462..26132e3810 100644 --- a/apps/desktop/src/stores/__tests__/queryStore.hiddenPrimaryKey.spec.ts +++ b/apps/desktop/src/stores/__tests__/queryStore.hiddenPrimaryKey.spec.ts @@ -173,6 +173,63 @@ describe("queryStore hidden primary key editing", () => { expect(tab.queryEditabilityReason).toBeUndefined(); }); + it("keeps a keyless Oracle query editable when its WHERE clause reads another table", async () => { + getConnectionConfig.mockReturnValue({ id: "oracle-1", name: "Oracle", db_type: "oracle", database: "ORCL", query_timeout_secs: 30 }); + getColumns.mockResolvedValue([ + { name: "ID", data_type: "NUMBER", is_nullable: false, column_default: null, is_primary_key: false, extra: null }, + { name: "CUSTOMER_NO", data_type: "NUMBER", is_nullable: true, column_default: null, is_primary_key: false, extra: null }, + ]); + listObjects.mockResolvedValue([{ name: "PLATFORM_CARS", object_type: "TABLE", schema: "APP" }]); + analyzeEditableQueryEditability.mockImplementation(async (sql: string) => { + const hidden = sql.includes("__DBX_PK_0"); + return { + editable: true, + analysis: { + schema: "APP", + tableName: "PLATFORM_CARS", + tableAlias: "t", + selectStar: !hidden, + columns: hidden + ? [ + { star: true, sourceQualifier: "t", sourceKey: "t:0", resultName: "*", expression: "t.*" }, + { resultName: "__DBX_PK_0", expression: "ROWIDTOCHAR(ROWID)" }, + ] + : [], + }, + }; + }); + executeMulti.mockResolvedValue([ + { + columns: ["ID", "CUSTOMER_NO", "__DBX_PK_0"], + rows: [[72, 2100196, "AAAPr9AAEAAAACXAAA"]], + affected_rows: 0, + execution_time_ms: 1, + }, + ]); + + const sql = "SELECT t.* FROM APP.PLATFORM_CARS t WHERE t.CUSTOMER_NO IN (SELECT c.CUSTOMER_NO FROM APP.CUSTOMERS c WHERE c.ENABLED = 1)"; + const { useQueryStore } = await import("@/stores/queryStore"); + const store = useQueryStore(); + const tabId = store.createTab("oracle-1", "ORCL", "Query"); + + await store.executeTabSql(tabId, sql); + + expect(executeMulti).toHaveBeenCalledWith( + "oracle-1", + "ORCL", + 'SELECT t.*, ROWIDTOCHAR(ROWID) AS "__DBX_PK_0" FROM APP.PLATFORM_CARS t WHERE t.CUSTOMER_NO IN (SELECT c.CUSTOMER_NO FROM APP.CUSTOMERS c WHERE c.ENABLED = 1)', + undefined, + expect.any(String), + expect.objectContaining({ timeoutSecs: 30 }), + ); + const tab = store.tabs.find((item) => item.id === tabId)!; + expect(tab.result?.hidden_column_indexes).toEqual([2]); + await vi.waitFor(() => expect(tab.querySourceColumns).toEqual(["ID", "CUSTOMER_NO", "__DBX_ROWID"])); + expect(tab.queryAnalysis).toBeDefined(); + expect(tab.queryAnalysis?.allowInsertDelete).not.toBe(false); + expect(tab.queryEditabilityReason).toBeUndefined(); + }); + it("does not inject Oracle ROWID into keyless view queries", async () => { getConnectionConfig.mockReturnValue({ id: "oracle-1", name: "Oracle", db_type: "oracle", database: "ORCL", query_timeout_secs: 30 }); getColumns.mockResolvedValue([ diff --git a/crates/dbx-core/src/sql_editability.rs b/crates/dbx-core/src/sql_editability.rs index b984bf570a..06b9b11014 100644 --- a/crates/dbx-core/src/sql_editability.rs +++ b/crates/dbx-core/src/sql_editability.rs @@ -888,6 +888,22 @@ mod tests { assert!(analysis.columns.is_empty()); } + #[test] + fn ignores_where_subquery_sources_for_editable_target() { + let result = analyze_editable_query_editability( + "SELECT t.* FROM app.platform_cars t WHERE t.customer_no IN (SELECT c.customer_no FROM app.customers c WHERE c.enabled = 1)", + ); + + assert!(result.editable); + let analysis = result.analysis.unwrap(); + assert_eq!(analysis.schema.as_deref(), Some("app")); + assert_eq!(analysis.table_name, "platform_cars"); + assert_eq!(analysis.table_alias.as_deref(), Some("t")); + assert!(analysis.select_star); + assert!(!analysis.multi_source); + assert_eq!(analysis.allow_insert_delete, None); + } + #[test] fn maps_single_table_explicit_column_with_alias_star() { let result = analyze_editable_query_editability(