diff --git a/frontend/src/components/TableDesigner.metadata-load.test.ts b/frontend/src/components/TableDesigner.metadata-load.test.ts new file mode 100644 index 00000000..29f5ede1 --- /dev/null +++ b/frontend/src/components/TableDesigner.metadata-load.test.ts @@ -0,0 +1,19 @@ +import { readFileSync } from 'node:fs'; +import { describe, expect, it } from 'vitest'; + +const source = readFileSync(new URL('./TableDesigner.tsx', import.meta.url), 'utf8'); + +describe('TableDesigner metadata loading', () => { + it('renders columns before slower auxiliary metadata requests complete', () => { + expect(source).toContain('const [columnsLoading, setColumnsLoading] = useState(false);'); + expect(source).toContain('const [ddlLoading, setDdlLoading] = useState(false);'); + expect(source).toContain('const loadColumns = DBGetColumns(rpcConfig, dbName, tableName)'); + expect(source).toContain('await loadColumns;'); + expect(source).toContain('await Promise.allSettled([loadIndexes, loadForeignKeys, loadTriggers, loadDdl]);'); + expect(source).toContain('loading={columnsLoading}'); + expect(source).toContain('loading={indexesLoading}'); + expect(source).toContain('loading={foreignKeysLoading}'); + expect(source).toContain('loading={triggersLoading}'); + expect(source).not.toContain('const results = await Promise.all(promises);'); + }); +}); diff --git a/frontend/src/components/TableDesigner.tsx b/frontend/src/components/TableDesigner.tsx index 8394e9ac..33bf0c6b 100644 --- a/frontend/src/components/TableDesigner.tsx +++ b/frontend/src/components/TableDesigner.tsx @@ -429,7 +429,11 @@ const TableDesigner: React.FC<{ tab: TabData; embedded?: boolean }> = ({ tab, em const [starRocksExternalEngine, setStarRocksExternalEngine] = useState('hive'); const [starRocksExternalProperties, setStarRocksExternalProperties] = useState('"resource" = "hive0"\n"database" = "raw_db"\n"table" = "raw_table"'); - const [loading, setLoading] = useState(false); + const [columnsLoading, setColumnsLoading] = useState(false); + const [indexesLoading, setIndexesLoading] = useState(false); + const [foreignKeysLoading, setForeignKeysLoading] = useState(false); + const [triggersLoading, setTriggersLoading] = useState(false); + const [ddlLoading, setDdlLoading] = useState(false); const [previewSql, setPreviewSql] = useState(''); const [isPreviewOpen, setIsPreviewOpen] = useState(false); const [activeKey, setActiveKey] = useState(tab.initialTab || "columns"); @@ -488,6 +492,7 @@ const TableDesigner: React.FC<{ tab: TabData; embedded?: boolean }> = ({ tab, em const designerTableTitle = tab.tableName || newTableName || t('table_designer.title.untitled_table', undefined, i18nLanguage); const designerDbTitle = tab.dbName || t('table_designer.title.default_database', undefined, i18nLanguage); const designerColumnSummary = t('table_designer.summary.columns', { count: columns.length }, i18nLanguage); + const metadataLoading = columnsLoading || indexesLoading || foreignKeysLoading || triggersLoading || ddlLoading; const charsetOptions = useMemo(() => getCharsetOptions(i18nLanguage), [i18nLanguage]); const collationOptions = useMemo(() => getCollationOptions(i18nLanguage), [i18nLanguage]); const panelRadius = 10; @@ -502,6 +507,7 @@ const TableDesigner: React.FC<{ tab: TabData; embedded?: boolean }> = ({ tab, em const shellRef = useRef(null); const pendingFocusColumnKeyRef = useRef(null); const focusHighlightTimerRef = useRef(null); + const metadataLoadSeqRef = useRef(0); const [focusColumnKey, setFocusColumnKey] = useState(''); const openCommentEditor = useCallback((record: EditableColumn) => { @@ -869,14 +875,33 @@ const TableDesigner: React.FC<{ tab: TabData; embedded?: boolean }> = ({ tab, em }; }, [cleanupResizeState, detachResizeListeners]); - const fetchData = async () => { - if (isNewTable) return; // Don't fetch for new table + const clearMetadataLoading = () => { + setColumnsLoading(false); + setIndexesLoading(false); + setForeignKeysLoading(false); + setTriggersLoading(false); + setDdlLoading(false); + }; + + const formatLoadError = (error: unknown): string => { + if (error instanceof Error) return error.message; + return String(error || ''); + }; + + const fetchData = async () => { + const requestSeq = metadataLoadSeqRef.current + 1; + metadataLoadSeqRef.current = requestSeq; + const isCurrentRequest = () => metadataLoadSeqRef.current === requestSeq; + + if (isNewTable) { + clearMetadataLoading(); + return; + } - setLoading(true); const conn = connections.find(c => c.id === tab.connectionId); if (!conn) { message.error(t('table_designer.message.connection_not_found', undefined, i18nLanguage)); - setLoading(false); + clearMetadataLoading(); return; } @@ -889,63 +914,96 @@ const TableDesigner: React.FC<{ tab: TabData; embedded?: boolean }> = ({ tab, em ssh: conn.config.ssh || { host: "", port: 22, user: "", password: "", keyPath: "" } }; - const promises: Promise[] = [ - DBGetColumns(buildRpcConnectionConfig(config) as any, tab.dbName || '', tab.tableName || ''), - DBGetIndexes(buildRpcConnectionConfig(config) as any, tab.dbName || '', tab.tableName || ''), - DBGetForeignKeys(buildRpcConnectionConfig(config) as any, tab.dbName || '', tab.tableName || ''), - DBGetTriggers(buildRpcConnectionConfig(config) as any, tab.dbName || '', tab.tableName || '') - ]; + const rpcConfig = buildRpcConnectionConfig(config) as any; + const dbName = tab.dbName || ''; + const tableName = tab.tableName || ''; - if (!isNewTable) { - promises.push(DBShowCreateTable(buildRpcConnectionConfig(config) as any, tab.dbName || '', tab.tableName || '')); - } + setColumnsLoading(true); + setIndexesLoading(true); + setForeignKeysLoading(true); + setTriggersLoading(true); + setDdlLoading(true); - const results = await Promise.all(promises); - const colsRes = results[0]; - const idxRes = results[1]; - const fkRes = results[2]; - const trigRes = results[3]; - const ddlRes = !isNewTable ? results[4] : null; + const loadColumns = DBGetColumns(rpcConfig, dbName, tableName) + .then((colsRes) => { + if (!isCurrentRequest()) return; + if (colsRes.success) { + const colsWithKey = (colsRes.data as ColumnDefinition[]).map((c, index) => ({ + ...normalizeColumnDefinition(c), + _key: `col-${index}-${Date.now()}`, + isAutoIncrement: getColumnDefinitionExtra(c).toLowerCase().includes('auto_increment') + })); + setColumns(JSON.parse(JSON.stringify(colsWithKey))); + setOriginalColumns(JSON.parse(JSON.stringify(colsWithKey))); + setSelectedColumnRowKeys([]); + } else { + message.error(t('table_designer.message.load_columns_failed', { detail: colsRes.message }, i18nLanguage)); + } + }) + .catch((error: unknown) => { + if (!isCurrentRequest()) return; + message.error(t('table_designer.message.load_columns_failed', { detail: formatLoadError(error) }, i18nLanguage)); + }) + .finally(() => { + if (isCurrentRequest()) setColumnsLoading(false); + }); - if (colsRes.success) { - const colsWithKey = (colsRes.data as ColumnDefinition[]).map((c, index) => ({ - ...normalizeColumnDefinition(c), - _key: `col-${index}-${Date.now()}`, - isAutoIncrement: getColumnDefinitionExtra(c).toLowerCase().includes('auto_increment') - })); - setColumns(JSON.parse(JSON.stringify(colsWithKey))); - setOriginalColumns(JSON.parse(JSON.stringify(colsWithKey))); - setSelectedColumnRowKeys([]); - } else { - message.error(t('table_designer.message.load_columns_failed', { detail: colsRes.message }, i18nLanguage)); - } + await loadColumns; + if (!isCurrentRequest()) return; - if (idxRes.success) { - setIndexes(Array.isArray(idxRes.data) ? idxRes.data : []); - } else { - setIndexes([]); - } - if (fkRes.success) { - setFks(Array.isArray(fkRes.data) ? fkRes.data : []); - } else { - setFks([]); - } - if (trigRes.success) { - setTriggers(Array.isArray(trigRes.data) ? trigRes.data : []); - } else { - setTriggers([]); - } - if (ddlRes && ddlRes.success) { - const ddlText = String(ddlRes.data || ''); - setDdl(ddlText); - const parsedTableComment = parseTableCommentFromDDL(ddlText); - setTableComment(parsedTableComment); - if (!isTableCommentModalOpen) { - setTableCommentDraft(parsedTableComment); - } - } - - setLoading(false); + const loadIndexes = DBGetIndexes(rpcConfig, dbName, tableName) + .then((idxRes) => { + if (!isCurrentRequest()) return; + setIndexes(idxRes.success && Array.isArray(idxRes.data) ? idxRes.data : []); + }) + .catch(() => { + if (isCurrentRequest()) setIndexes([]); + }) + .finally(() => { + if (isCurrentRequest()) setIndexesLoading(false); + }); + + const loadForeignKeys = DBGetForeignKeys(rpcConfig, dbName, tableName) + .then((fkRes) => { + if (!isCurrentRequest()) return; + setFks(fkRes.success && Array.isArray(fkRes.data) ? fkRes.data : []); + }) + .catch(() => { + if (isCurrentRequest()) setFks([]); + }) + .finally(() => { + if (isCurrentRequest()) setForeignKeysLoading(false); + }); + + const loadTriggers = DBGetTriggers(rpcConfig, dbName, tableName) + .then((trigRes) => { + if (!isCurrentRequest()) return; + setTriggers(trigRes.success && Array.isArray(trigRes.data) ? trigRes.data : []); + }) + .catch(() => { + if (isCurrentRequest()) setTriggers([]); + }) + .finally(() => { + if (isCurrentRequest()) setTriggersLoading(false); + }); + + const loadDdl = DBShowCreateTable(rpcConfig, dbName, tableName) + .then((ddlRes) => { + if (!isCurrentRequest() || !ddlRes.success) return; + const ddlText = String(ddlRes.data || ''); + setDdl(ddlText); + const parsedTableComment = parseTableCommentFromDDL(ddlText); + setTableComment(parsedTableComment); + if (!isTableCommentModalOpen) { + setTableCommentDraft(parsedTableComment); + } + }) + .catch(() => undefined) + .finally(() => { + if (isCurrentRequest()) setDdlLoading(false); + }); + + await Promise.allSettled([loadIndexes, loadForeignKeys, loadTriggers, loadDdl]); }; useEffect(() => { @@ -2676,7 +2734,7 @@ END;`; rowClassName={(record: EditableColumn) => record._key === focusColumnKey ? 'table-designer-focus-row' : ''} size="small" pagination={false} - loading={loading} + loading={columnsLoading} scroll={{ y: tableHeight }} bordered={false} components={{ @@ -2695,7 +2753,7 @@ END;`; rowClassName={(record: EditableColumn) => record._key === focusColumnKey ? 'table-designer-focus-row' : ''} size="small" pagination={false} - loading={loading} + loading={columnsLoading} scroll={{ y: tableHeight }} bordered={false} components={{ @@ -3073,7 +3131,7 @@ END;`; )} {!readOnly && } - {!isNewTable && } + {!isNewTable && } {!isNewTable && !readOnly && supportsTableCommentOps() && ( )} @@ -3160,7 +3218,7 @@ END;`; rowKey="key" size="small" pagination={false} - loading={loading} + loading={indexesLoading} scroll={{ x: 960, y: indexTableHeight }} components={{ header: { cell: ResizableTitle }, @@ -3226,7 +3284,7 @@ END;`; rowKey="key" size="small" pagination={false} - loading={loading} + loading={foreignKeysLoading} scroll={{ x: 980, y: tableHeight }} rowSelection={{ type: 'radio', @@ -3284,7 +3342,7 @@ END;`; rowKey="name" size="small" pagination={false} - loading={loading} + loading={triggersLoading} scroll={{ y: tableHeight }} locale={{ emptyText: }} rowSelection={{ diff --git a/internal/db/dameng_columns_test.go b/internal/db/dameng_columns_test.go index 104ac6b7..477b524e 100644 --- a/internal/db/dameng_columns_test.go +++ b/internal/db/dameng_columns_test.go @@ -23,6 +23,16 @@ func TestBuildDamengColumnsQuery_IncludesPrimaryKeyMetadata(t *testing.T) { if !strings.Contains(ownerQuery, "WHERE c.owner = 'BIZ' AND c.table_name = 'ORDERS'") { t.Fatalf("owner query 应按 owner/table 过滤, got=%s", ownerQuery) } + for _, want := range []string{ + "AND cons.owner = 'BIZ'", + "AND cons.table_name = 'ORDERS'", + "AND cols.owner = 'BIZ'", + "AND cols.table_name = 'ORDERS'", + } { + if !strings.Contains(ownerQuery, want) { + t.Fatalf("owner query 主键子查询应按 owner/table 预过滤 %q, got=%s", want, ownerQuery) + } + } userQuery := buildDamengColumnsQuery("", "orders") if !strings.Contains(userQuery, "FROM user_tab_columns c") { @@ -31,6 +41,41 @@ func TestBuildDamengColumnsQuery_IncludesPrimaryKeyMetadata(t *testing.T) { if !strings.Contains(userQuery, "JOIN user_cons_columns cols") { t.Fatalf("user query 应关联 user_cons_columns, got=%s", userQuery) } + for _, want := range []string{ + "AND cons.table_name = 'ORDERS'", + "AND cols.table_name = 'ORDERS'", + } { + if !strings.Contains(userQuery, want) { + t.Fatalf("user query 主键子查询应按 table 预过滤 %q, got=%s", want, userQuery) + } + } +} + +func TestBuildDamengForeignKeysQuery_PreFiltersLocalColumnsByTargetTable(t *testing.T) { + t.Parallel() + + ownerQuery := buildDamengForeignKeysQuery("biz", "orders") + for _, want := range []string{ + "FROM (", + "FROM all_cons_columns", + "WHERE owner = 'BIZ' AND table_name = 'ORDERS'", + "WHERE c.constraint_type = 'R' AND c.owner = 'BIZ' AND c.table_name = 'ORDERS'", + } { + if !strings.Contains(ownerQuery, want) { + t.Fatalf("owner foreign-key query 应按 owner/table 预过滤 %q, got=%s", want, ownerQuery) + } + } + + userQuery := buildDamengForeignKeysQuery("", "orders") + for _, want := range []string{ + "FROM user_cons_columns", + "WHERE table_name = 'ORDERS'", + "WHERE c.constraint_type = 'R' AND c.table_name = 'ORDERS'", + } { + if !strings.Contains(userQuery, want) { + t.Fatalf("user foreign-key query 应按 table 预过滤 %q, got=%s", want, userQuery) + } + } } func TestBuildDamengColumnDefinitions_MarksPrimaryKeyColumns(t *testing.T) { diff --git a/internal/db/dameng_impl.go b/internal/db/dameng_impl.go index 5c64959d..e68fecc7 100644 --- a/internal/db/dameng_impl.go +++ b/internal/db/dameng_impl.go @@ -333,15 +333,7 @@ func (d *DamengDB) GetIndexes(dbName, tableName string) ([]connection.IndexDefin func (d *DamengDB) GetForeignKeys(dbName, tableName string) ([]connection.ForeignKeyDefinition, error) { // Reusing Oracle style query as DM is highly compatible - query := fmt.Sprintf(`SELECT a.constraint_name, a.column_name, c_pk.table_name r_table_name, b.column_name r_column_name - FROM all_cons_columns a - JOIN all_constraints c ON a.owner = c.owner AND a.constraint_name = c.constraint_name - JOIN all_constraints c_pk ON c.r_owner = c_pk.owner AND c.r_constraint_name = c_pk.constraint_name - JOIN all_cons_columns b ON c_pk.owner = b.owner AND c_pk.constraint_name = b.constraint_name AND a.position = b.position - WHERE c.constraint_type = 'R' AND a.owner = '%s' AND a.table_name = '%s'`, - strings.ToUpper(dbName), strings.ToUpper(tableName)) - - data, _, err := d.Query(query) + data, _, err := d.Query(buildDamengForeignKeysQuery(dbName, tableName)) if err != nil { return nil, err } diff --git a/internal/db/dameng_metadata.go b/internal/db/dameng_metadata.go index f06ab456..394560dd 100644 --- a/internal/db/dameng_metadata.go +++ b/internal/db/dameng_metadata.go @@ -118,9 +118,11 @@ func buildDamengColumnsQuery(dbName, tableName string) string { FROM user_constraints cons JOIN user_cons_columns cols USING (constraint_name) WHERE cons.constraint_type = 'P' + AND cons.table_name = '%s' + AND cols.table_name = '%s' ) pk ON c.table_name = pk.table_name AND c.column_name = pk.column_name WHERE c.table_name = '%s' - ORDER BY c.column_id`, upperTableName) + ORDER BY c.column_id`, upperTableName, upperTableName, upperTableName) } return fmt.Sprintf(`SELECT c.column_name, c.data_type, c.data_length, c.char_length, c.data_precision, c.data_scale, c.nullable, c.data_default, @@ -132,9 +134,41 @@ func buildDamengColumnsQuery(dbName, tableName string) string { JOIN all_cons_columns cols ON cons.owner = cols.owner AND cons.constraint_name = cols.constraint_name WHERE cons.constraint_type = 'P' + AND cons.owner = '%s' + AND cons.table_name = '%s' + AND cols.owner = '%s' + AND cols.table_name = '%s' ) pk ON c.owner = pk.owner AND c.table_name = pk.table_name AND c.column_name = pk.column_name WHERE c.owner = '%s' AND c.table_name = '%s' - ORDER BY c.column_id`, upperDBName, upperTableName) + ORDER BY c.column_id`, upperDBName, upperTableName, upperDBName, upperTableName, upperDBName, upperTableName) +} + +func buildDamengForeignKeysQuery(dbName, tableName string) string { + upperDBName := strings.ToUpper(strings.TrimSpace(dbName)) + upperTableName := strings.ToUpper(strings.TrimSpace(tableName)) + if upperDBName == "" { + return fmt.Sprintf(`SELECT a.constraint_name, a.column_name, c_pk.table_name r_table_name, b.column_name r_column_name + FROM ( + SELECT constraint_name, table_name, column_name, position + FROM user_cons_columns + WHERE table_name = '%s' + ) a + JOIN user_constraints c ON a.constraint_name = c.constraint_name + JOIN user_constraints c_pk ON c.r_constraint_name = c_pk.constraint_name + JOIN user_cons_columns b ON c_pk.constraint_name = b.constraint_name AND a.position = b.position + WHERE c.constraint_type = 'R' AND c.table_name = '%s'`, upperTableName, upperTableName) + } + return fmt.Sprintf(`SELECT a.constraint_name, a.column_name, c_pk.table_name r_table_name, b.column_name r_column_name + FROM ( + SELECT owner, constraint_name, table_name, column_name, position + FROM all_cons_columns + WHERE owner = '%s' AND table_name = '%s' + ) a + JOIN all_constraints c ON a.owner = c.owner AND a.constraint_name = c.constraint_name + JOIN all_constraints c_pk ON c.r_owner = c_pk.owner AND c.r_constraint_name = c_pk.constraint_name + JOIN all_cons_columns b ON c_pk.owner = b.owner AND c_pk.constraint_name = b.constraint_name AND a.position = b.position + WHERE c.constraint_type = 'R' AND c.owner = '%s' AND c.table_name = '%s'`, + upperDBName, upperTableName, upperDBName, upperTableName) } func getDamengRowInt(row map[string]interface{}, keys ...string) (int, bool) { diff --git a/internal/db/oracle_get_tables_test.go b/internal/db/oracle_get_tables_test.go index 8e6307d3..f6fdff7c 100644 --- a/internal/db/oracle_get_tables_test.go +++ b/internal/db/oracle_get_tables_test.go @@ -112,6 +112,39 @@ func TestOracleGetColumnsIncludesColumnComments(t *testing.T) { } } +func TestOracleColumnsQueryFiltersPrimaryKeyLookupByTargetTable(t *testing.T) { + t.Parallel() + + query := buildOracleColumnsQuery("MYCIMLED", "EDC_LOG") + for _, want := range []string{ + `AND cons.owner = 'MYCIMLED'`, + `AND cons.table_name = 'EDC_LOG'`, + `AND cols.owner = 'MYCIMLED'`, + `AND cols.table_name = 'EDC_LOG'`, + `WHERE c.owner = 'MYCIMLED' AND c.table_name = 'EDC_LOG'`, + } { + if !strings.Contains(query, want) { + t.Fatalf("expected Oracle columns query to contain %q, got: %s", want, query) + } + } +} + +func TestOracleForeignKeysQueryPreFiltersLocalColumnsByTargetTable(t *testing.T) { + t.Parallel() + + query := buildOracleForeignKeysQuery("MYCIMLED", "EDC_LOG") + for _, want := range []string{ + `FROM (`, + `FROM all_cons_columns`, + `WHERE owner = 'MYCIMLED' AND table_name = 'EDC_LOG'`, + `WHERE c.constraint_type = 'R' AND c.owner = 'MYCIMLED' AND c.table_name = 'EDC_LOG'`, + } { + if !strings.Contains(query, want) { + t.Fatalf("expected Oracle foreign-key query to contain %q, got: %s", want, query) + } + } +} + func TestOracleGetColumnsPreservesMetadataNameCaseBeforeUppercaseFallback(t *testing.T) { t.Parallel() diff --git a/internal/db/oracle_impl.go b/internal/db/oracle_impl.go index 37e98f89..58631137 100644 --- a/internal/db/oracle_impl.go +++ b/internal/db/oracle_impl.go @@ -733,9 +733,11 @@ func buildOracleColumnsQuery(schema string, table string) string { FROM user_constraints cons JOIN user_cons_columns cols USING (constraint_name) WHERE cons.constraint_type = 'P' + AND cons.table_name = '%s' + AND cols.table_name = '%s' ) pk ON c.table_name = pk.table_name AND c.column_name = pk.column_name WHERE c.table_name = '%s' - ORDER BY c.column_id`, metadataTableName) + ORDER BY c.column_id`, metadataTableName, metadataTableName, metadataTableName) } return fmt.Sprintf(`SELECT c.column_name AS "COLUMN_NAME", c.data_type AS "DATA_TYPE", c.data_length AS "DATA_LENGTH", c.char_length AS "CHAR_LENGTH", c.data_precision AS "DATA_PRECISION", c.data_scale AS "DATA_SCALE", c.nullable AS "NULLABLE", c.data_default AS "DATA_DEFAULT", @@ -750,9 +752,13 @@ func buildOracleColumnsQuery(schema string, table string) string { JOIN all_cons_columns cols ON cons.owner = cols.owner AND cons.constraint_name = cols.constraint_name WHERE cons.constraint_type = 'P' + AND cons.owner = '%s' + AND cons.table_name = '%s' + AND cols.owner = '%s' + AND cols.table_name = '%s' ) pk ON c.owner = pk.owner AND c.table_name = pk.table_name AND c.column_name = pk.column_name WHERE c.owner = '%s' AND c.table_name = '%s' - ORDER BY c.column_id`, metadataSchemaName, metadataTableName) + ORDER BY c.column_id`, metadataSchemaName, metadataTableName, metadataSchemaName, metadataTableName, metadataSchemaName, metadataTableName) } func parseOracleColumns(data []map[string]interface{}) []connection.ColumnDefinition { @@ -884,19 +890,27 @@ func buildOracleForeignKeysQuery(schema string, table string) string { metadataSchemaName := escapeOracleMetadataLiteralExact(schema) if strings.TrimSpace(schema) == "" { return fmt.Sprintf(`SELECT a.constraint_name, a.column_name, c_pk.table_name r_table_name, b.column_name r_column_name - FROM user_cons_columns a + FROM ( + SELECT constraint_name, table_name, column_name, position + FROM user_cons_columns + WHERE table_name = '%s' + ) a JOIN user_constraints c ON a.constraint_name = c.constraint_name JOIN user_constraints c_pk ON c.r_constraint_name = c_pk.constraint_name JOIN user_cons_columns b ON c_pk.constraint_name = b.constraint_name AND a.position = b.position - WHERE c.constraint_type = 'R' AND a.table_name = '%s'`, metadataTableName) + WHERE c.constraint_type = 'R' AND c.table_name = '%s'`, metadataTableName, metadataTableName) } return fmt.Sprintf(`SELECT a.constraint_name, a.column_name, c_pk.table_name r_table_name, b.column_name r_column_name - FROM all_cons_columns a + FROM ( + SELECT owner, constraint_name, table_name, column_name, position + FROM all_cons_columns + WHERE owner = '%s' AND table_name = '%s' + ) a JOIN all_constraints c ON a.owner = c.owner AND a.constraint_name = c.constraint_name JOIN all_constraints c_pk ON c.r_owner = c_pk.owner AND c.r_constraint_name = c_pk.constraint_name JOIN all_cons_columns b ON c_pk.owner = b.owner AND c_pk.constraint_name = b.constraint_name AND a.position = b.position - WHERE c.constraint_type = 'R' AND a.owner = '%s' AND a.table_name = '%s'`, - metadataSchemaName, metadataTableName) + WHERE c.constraint_type = 'R' AND c.owner = '%s' AND c.table_name = '%s'`, + metadataSchemaName, metadataTableName, metadataSchemaName, metadataTableName) } func parseOracleForeignKeys(data []map[string]interface{}) []connection.ForeignKeyDefinition {