mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-07-13 00:13:33 +08:00
🐛 fix(query-editor): 修复限定名超链接并支持跨库/schema 解析
- 两段名兼容 MySQL 库.表与 PG/SQL Server 的 schema.表 - 三段名支持 database.schema.object,常见 schema 不当成库拉取 - SQL 变更时按引用库防抖补拉元数据并刷新装饰 - 修复元数据 key 提前占死导致取消后不再拉取、超链接全灭 - 补充限定名解析与收集逻辑单测
This commit is contained in:
@@ -3013,6 +3013,28 @@ describe('QueryEditor external SQL save', () => {
|
||||
tableName: 'dbo.orders',
|
||||
schemaName: 'dbo',
|
||||
});
|
||||
// MySQL 跨库手写 db.table:库不在可见列表时,只要元数据已加载也应可跳转
|
||||
expect(resolveQueryEditorNavigationTarget(
|
||||
'select * from front_end_sys_new.fs_mkefu_regist_record',
|
||||
'select * from front_end_sys_new.fs_mkefu_regist_record'.length,
|
||||
'mkefu_test_new',
|
||||
['mkefu_test_new'],
|
||||
[
|
||||
{ dbName: 'mkefu_test_new', tableName: 'uk_back_corp' },
|
||||
{ dbName: 'front_end_sys_new', tableName: 'fs_mkefu_regist_record' },
|
||||
],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
)).toEqual({
|
||||
type: 'table',
|
||||
dbName: 'front_end_sys_new',
|
||||
tableName: 'fs_mkefu_regist_record',
|
||||
schemaName: undefined,
|
||||
});
|
||||
expect(resolveQueryEditorNavigationTarget('use analytics', 6, 'main', ['main', 'analytics'], tables, views, materializedViews, triggers, routines, sequences, packages)).toEqual({
|
||||
type: 'database',
|
||||
dbName: 'analytics',
|
||||
@@ -8143,7 +8165,8 @@ describe('QueryEditor external SQL save', () => {
|
||||
readOnly: false,
|
||||
});
|
||||
expect(dataGridState.latestProps?.readOnly).toBe(false);
|
||||
expect(dataGridState.latestProps?.showRowNumberColumn).toBe(true);
|
||||
// 行号改由 appearance.showDataTableRowNumber 控制,不再按数据源硬编码写入结果集
|
||||
expect(dataGridState.latestProps?.showRowNumberColumn).toBeUndefined();
|
||||
expect(storeState.addSqlLog).toHaveBeenCalledWith(expect.objectContaining({
|
||||
sql: 'SELECT * FROM EDC_LOG',
|
||||
status: 'success',
|
||||
|
||||
@@ -10,7 +10,7 @@ import { DBQuery, DBQueryWithCancel, DBQueryMulti, DBQueryMultiInTransaction, DB
|
||||
import { GONAVI_ROW_KEY } from './DataGrid';
|
||||
import { EventsOn } from '../../wailsjs/runtime';
|
||||
import { findConnectionMutatingStatements } from '../utils/connectionReadOnly';
|
||||
import { getDataSourceCapabilities, shouldShowOceanBaseRowNumberColumn } from '../utils/dataSourceCapabilities';
|
||||
import { getDataSourceCapabilities } from '../utils/dataSourceCapabilities';
|
||||
import { applyMongoQueryAutoLimit, convertMongoShellToJsonCommand } from "../utils/mongodb";
|
||||
import { getShortcutDisplayLabel, getShortcutPlatform, getShortcutPrimaryModifierDisplayLabel, isEditableElement, isImeComposingKeyEvent, isShortcutMatch, comboToMonacoKeyBinding, normalizeShortcutCombo, resolveShortcutBinding } from "../utils/shortcuts";
|
||||
import { useAutoFetchVisibility } from '../utils/autoFetchVisibility';
|
||||
@@ -1209,6 +1209,10 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
const visibleDbsRef = useRef<string[]>([]); // Store visible databases for cross-db intellisense
|
||||
const metadataFetchKeyRef = useRef<string>('');
|
||||
const metadataContextKeyRef = useRef<string>('');
|
||||
/** SQL 中引用到的库集合变化时触发跨库元数据补拉(供超链接/补全) */
|
||||
const [sqlReferencedMetadataKey, setSqlReferencedMetadataKey] = useState('');
|
||||
const sqlReferencedMetadataTimerRef = useRef<number | null>(null);
|
||||
const lastSqlReferencedMetadataKeyRef = useRef('');
|
||||
|
||||
const connections = useStore(state => state.connections);
|
||||
const queryCapableConnections = useMemo(
|
||||
@@ -2656,6 +2660,8 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
// 仅在本次 effect 成功完成后写入;中途 cancel 不得留下 key,否则同 key 永远不再拉取 → 超链接全灭
|
||||
let activeFetchKey = '';
|
||||
const fetchMetadata = async () => {
|
||||
const conn = connections.find(c => c.id === currentConnectionId);
|
||||
if (!conn) return;
|
||||
@@ -2683,10 +2689,18 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
);
|
||||
const metadataFetchKey = [
|
||||
currentConnectionId,
|
||||
...metadataDbNames.map((dbName) => String(dbName || '').toLowerCase()),
|
||||
...metadataDbNames.map((dbName) => String(dbName || '').toLowerCase()).sort(),
|
||||
].join('\u0000');
|
||||
if (metadataFetchKeyRef.current === metadataFetchKey) return;
|
||||
metadataFetchKeyRef.current = metadataFetchKey;
|
||||
const hasCurrentDbTables = tablesRef.current.some(
|
||||
(table) => String(table.dbName || '').trim().toLowerCase() === metadataDbName.toLowerCase(),
|
||||
);
|
||||
if (metadataFetchKeyRef.current === metadataFetchKey && hasCurrentDbTables) {
|
||||
// 已成功拉过同一批库且当前库表仍在:只刷新装饰
|
||||
refreshObjectDecorations();
|
||||
return;
|
||||
}
|
||||
// key 相同但表为空(中途 cancel / 异常):允许重拉
|
||||
activeFetchKey = metadataFetchKey;
|
||||
|
||||
const allTables: CompletionTableMeta[] = [];
|
||||
const allColumns: CompletionColumnMeta[] = [];
|
||||
@@ -2935,13 +2949,25 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
}
|
||||
|
||||
if (!syncMetadataSnapshot()) return;
|
||||
// 成功完成后才固化 key,避免 cancel 后同 key 被误判为「已完成」
|
||||
metadataFetchKeyRef.current = activeFetchKey;
|
||||
lastSqlReferencedMetadataKeyRef.current = activeFetchKey;
|
||||
refreshObjectDecorations();
|
||||
};
|
||||
void fetchMetadata();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [autoFetchVisible, currentConnectionId, currentDb, connections, isActive, isObjectEditQueryTab, refreshObjectDecorations]);
|
||||
}, [
|
||||
autoFetchVisible,
|
||||
currentConnectionId,
|
||||
currentDb,
|
||||
connections,
|
||||
isActive,
|
||||
isObjectEditQueryTab,
|
||||
refreshObjectDecorations,
|
||||
sqlReferencedMetadataKey,
|
||||
]);
|
||||
|
||||
// Query ID management helpers
|
||||
const setQueryId = (id: string) => {
|
||||
@@ -4180,6 +4206,33 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
if (hasSlashCommandMarker) {
|
||||
refreshObjectDecorations(QUERY_EDITOR_LIVE_DECORATION_MAX_TEXT_LENGTH);
|
||||
}
|
||||
// SQL 文本变更后,按引用库集合防抖触发跨库元数据拉取(db.table / schema.table / db.schema.table)
|
||||
if (sqlReferencedMetadataTimerRef.current !== null) {
|
||||
window.clearTimeout(sqlReferencedMetadataTimerRef.current);
|
||||
}
|
||||
sqlReferencedMetadataTimerRef.current = window.setTimeout(() => {
|
||||
sqlReferencedMetadataTimerRef.current = null;
|
||||
if (editorRef.current !== editor) {
|
||||
return;
|
||||
}
|
||||
const modelText = String(editor.getModel?.()?.getValue?.() || '');
|
||||
const referencedDbs = collectQueryEditorReferencedDatabaseNames(
|
||||
modelText,
|
||||
currentDbRef.current || '',
|
||||
visibleDbsRef.current,
|
||||
);
|
||||
const nextKey = [
|
||||
String(currentConnectionIdRef.current || '').trim(),
|
||||
...referencedDbs.map((dbName) => String(dbName || '').toLowerCase()).sort(),
|
||||
].join('\u0000');
|
||||
if (nextKey === lastSqlReferencedMetadataKeyRef.current) {
|
||||
// 库集合未变:仍刷新装饰(可能表列表已异步到位)
|
||||
refreshObjectDecorations(QUERY_EDITOR_LIVE_DECORATION_MAX_TEXT_LENGTH);
|
||||
return;
|
||||
}
|
||||
lastSqlReferencedMetadataKeyRef.current = nextKey;
|
||||
setSqlReferencedMetadataKey(nextKey);
|
||||
}, 450);
|
||||
const acceptedCurrentAiGhost = !aiInlineGhostAcceptingRef.current
|
||||
&& didModelContentAcceptCurrentAiInlineGhost(event);
|
||||
if (acceptedCurrentAiGhost) {
|
||||
@@ -6186,7 +6239,6 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
.length || sourceStatements.length;
|
||||
|
||||
const forceReadOnlyResult = connCaps.forceReadOnlyQueryResult;
|
||||
const showRowNumberColumn = shouldShowOceanBaseRowNumberColumn(config);
|
||||
const defaultOracleSchema = isOracleLikeDialect(normalizedDbType)
|
||||
? resolveOracleLikeDefaultSchemaName(config)
|
||||
: '';
|
||||
@@ -6500,7 +6552,6 @@ const QueryEditor: React.FC<{ tab: TabData; isActive?: boolean }> = ({ tab, isAc
|
||||
pkColumns: plan?.pkColumns || [],
|
||||
editLocator,
|
||||
readOnly: forceReadOnlyResult || !editLocator || editLocator.readOnly,
|
||||
showRowNumberColumn,
|
||||
truncated,
|
||||
page,
|
||||
});
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import {
|
||||
collectQueryEditorReferencedDatabaseNames,
|
||||
resolveOracleLikeDefaultSchemaName,
|
||||
resolveOracleLikeExecutionSchemaName,
|
||||
resolveOracleLikeLookupSchemaCandidates,
|
||||
resolveQueryEditorNavigationTarget,
|
||||
} from './QueryEditorHelpers';
|
||||
|
||||
describe('QueryEditorHelpers Oracle-like execution schema', () => {
|
||||
@@ -31,3 +33,125 @@ describe('QueryEditorHelpers Oracle-like execution schema', () => {
|
||||
expect(resolveOracleLikeLookupSchemaCandidates(config, 'APP_OWNER')).toEqual(['APP_OWNER']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('QueryEditorHelpers qualified navigation (MySQL db.table + PG schema.table)', () => {
|
||||
it('collects cross-db names from SQL without requiring an empty visible list', () => {
|
||||
const sql = `
|
||||
SELECT * FROM uk_back_corp;
|
||||
SELECT * FROM front_end_sys_new.fs_mkefu_regist_record WHERE mobile = '1';
|
||||
DELETE FROM front_end_sys_new.fs_mkefu_regist_record WHERE mobile = '1';
|
||||
SELECT * FROM public.users;
|
||||
SELECT * FROM analytics.public.events;
|
||||
`;
|
||||
const names = collectQueryEditorReferencedDatabaseNames(
|
||||
sql,
|
||||
'mkefu_test_new',
|
||||
['mkefu_test_new', 'front_end_sys_new', 'analytics'],
|
||||
);
|
||||
expect(names).toEqual(expect.arrayContaining([
|
||||
'mkefu_test_new',
|
||||
'front_end_sys_new',
|
||||
'analytics',
|
||||
]));
|
||||
// public 是常见 schema,两段时不应当成库去拉取
|
||||
expect(names.map((name) => name.toLowerCase())).not.toContain('public');
|
||||
});
|
||||
|
||||
it('infers MySQL-style db.table even when the db is not yet in visibleDbs', () => {
|
||||
const names = collectQueryEditorReferencedDatabaseNames(
|
||||
"SELECT * FROM front_end_sys_new.fs_mkefu_regist_record",
|
||||
'mkefu_test_new',
|
||||
['mkefu_test_new'],
|
||||
);
|
||||
expect(names).toEqual(expect.arrayContaining(['mkefu_test_new', 'front_end_sys_new']));
|
||||
});
|
||||
|
||||
it('resolves MySQL db.table when the database is visible', () => {
|
||||
const tables = [
|
||||
{ dbName: 'mkefu_test_new', tableName: 'uk_back_corp' },
|
||||
{ dbName: 'front_end_sys_new', tableName: 'fs_mkefu_regist_record' },
|
||||
];
|
||||
const sql = 'SELECT * FROM front_end_sys_new.fs_mkefu_regist_record';
|
||||
expect(resolveQueryEditorNavigationTarget(
|
||||
sql,
|
||||
sql.length,
|
||||
'mkefu_test_new',
|
||||
['mkefu_test_new', 'front_end_sys_new'],
|
||||
tables,
|
||||
)).toEqual({
|
||||
type: 'table',
|
||||
dbName: 'front_end_sys_new',
|
||||
tableName: 'fs_mkefu_regist_record',
|
||||
schemaName: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('resolves PostgreSQL schema.table under the current database', () => {
|
||||
const tables = [
|
||||
{ dbName: 'appdb', tableName: 'public.users' },
|
||||
{ dbName: 'appdb', tableName: 'billing.orders' },
|
||||
];
|
||||
expect(resolveQueryEditorNavigationTarget(
|
||||
'select * from public.users',
|
||||
'select * from public.users'.length,
|
||||
'appdb',
|
||||
['appdb', 'otherdb'],
|
||||
tables,
|
||||
)).toEqual({
|
||||
type: 'table',
|
||||
dbName: 'appdb',
|
||||
tableName: 'public.users',
|
||||
schemaName: 'public',
|
||||
});
|
||||
expect(resolveQueryEditorNavigationTarget(
|
||||
'select * from billing.orders',
|
||||
'select * from billing.orders'.length,
|
||||
'appdb',
|
||||
['appdb'],
|
||||
tables,
|
||||
)).toEqual({
|
||||
type: 'table',
|
||||
dbName: 'appdb',
|
||||
tableName: 'billing.orders',
|
||||
schemaName: 'billing',
|
||||
});
|
||||
});
|
||||
|
||||
it('resolves database.schema.table three-part names for PostgreSQL-style metadata', () => {
|
||||
const tables = [
|
||||
{ dbName: 'analytics', tableName: 'public.events' },
|
||||
{ dbName: 'analytics', tableName: 'events' },
|
||||
];
|
||||
expect(resolveQueryEditorNavigationTarget(
|
||||
'select * from analytics.public.events',
|
||||
'select * from analytics.public.events'.length,
|
||||
'appdb',
|
||||
['appdb', 'analytics'],
|
||||
tables,
|
||||
)).toEqual({
|
||||
type: 'table',
|
||||
dbName: 'analytics',
|
||||
tableName: 'public.events',
|
||||
schemaName: 'public',
|
||||
});
|
||||
});
|
||||
|
||||
it('still resolves schema-qualified objects when the first segment is also a visible database name but no table exists there', () => {
|
||||
// 边界:可见库列表里碰巧有 "billing" 这个库,但当前库下才有 billing.orders
|
||||
const tables = [
|
||||
{ dbName: 'main', tableName: 'billing.orders' },
|
||||
];
|
||||
expect(resolveQueryEditorNavigationTarget(
|
||||
'select * from billing.orders',
|
||||
'select * from billing.orders'.length,
|
||||
'main',
|
||||
['main', 'billing'],
|
||||
tables,
|
||||
)).toEqual({
|
||||
type: 'table',
|
||||
dbName: 'main',
|
||||
tableName: 'billing.orders',
|
||||
schemaName: 'billing',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1605,6 +1605,23 @@ export const buildQueryEditorAliasMap = (
|
||||
return aliasMap;
|
||||
};
|
||||
|
||||
/**
|
||||
* 常见「schema」名:两段限定时优先当作 schema.table(PG/SQL Server),
|
||||
* 不把它们推断成需要跨库拉取的 database。
|
||||
*/
|
||||
export const QUERY_EDITOR_COMMON_SCHEMA_NAME_SET = new Set([
|
||||
'public',
|
||||
'dbo',
|
||||
'sys',
|
||||
'information_schema',
|
||||
'pg_catalog',
|
||||
'pg_toast',
|
||||
'mysql',
|
||||
'performance_schema',
|
||||
'sysdb',
|
||||
'guest',
|
||||
]);
|
||||
|
||||
export const collectQueryEditorReferencedDatabaseNames = (
|
||||
fullText: string,
|
||||
currentDb: string,
|
||||
@@ -1629,24 +1646,43 @@ export const collectQueryEditorReferencedDatabaseNames = (
|
||||
.filter(Boolean)
|
||||
.map((db) => [db.toLowerCase(), db] as const),
|
||||
);
|
||||
const commonSchemaNames = new Set(['public', 'dbo', 'sys', 'information_schema', 'pg_catalog', 'mysql', 'performance_schema']);
|
||||
const currentDbKey = String(currentDb || '').trim().toLowerCase();
|
||||
const tableRegex = QUERY_EDITOR_SQL_TABLE_REFERENCE_REGEX;
|
||||
tableRegex.lastIndex = 0;
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = tableRegex.exec(String(fullText || ''))) !== null) {
|
||||
const tableIdent = normalizeCompletionQualifiedName(match[1] || '');
|
||||
if (!tableIdent) continue;
|
||||
const parts = tableIdent.split('.');
|
||||
const parts = tableIdent.split('.').map((part) => String(part || '').trim()).filter(Boolean);
|
||||
if (parts.length < 2) continue;
|
||||
const candidate = visibleDbByLower.get(String(parts[0] || '').toLowerCase());
|
||||
if (candidate) {
|
||||
addDb(candidate);
|
||||
} else if (visibleDbByLower.size === 0) {
|
||||
const inferredDb = String(parts[0] || '').trim();
|
||||
const inferredKey = inferredDb.toLowerCase();
|
||||
if (inferredDb && inferredKey !== String(currentDb || '').trim().toLowerCase() && !commonSchemaNames.has(inferredKey)) {
|
||||
addDb(inferredDb);
|
||||
|
||||
const firstPart = parts[0];
|
||||
const firstKey = firstPart.toLowerCase();
|
||||
const asVisibleDb = visibleDbByLower.get(firstKey);
|
||||
if (asVisibleDb) {
|
||||
// MySQL: db.table;PG 三段 db.schema.table 的首段也是库
|
||||
addDb(asVisibleDb);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 三段及以上:首段通常是 database(PG/SQL Server 跨库限定)
|
||||
if (parts.length >= 3) {
|
||||
if (firstKey && firstKey !== currentDbKey && !QUERY_EDITOR_COMMON_SCHEMA_NAME_SET.has(firstKey)) {
|
||||
addDb(firstPart);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// 两段:可能是 MySQL 的 db.table,也可能是 PG 的 schema.table。
|
||||
// - 常见 schema 名不当库拉(避免 public/dbo 误请求)
|
||||
// - 其它未知前缀:若在可见库中已处理;若不在可见库但仍不像 schema,
|
||||
// 也作为候选库名拉取(覆盖 includeDatabases 过滤后手写跨库、或列表尚未刷新)
|
||||
if (
|
||||
firstKey
|
||||
&& firstKey !== currentDbKey
|
||||
&& !QUERY_EDITOR_COMMON_SCHEMA_NAME_SET.has(firstKey)
|
||||
) {
|
||||
addDb(firstPart);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
@@ -1897,14 +1933,54 @@ export const resolveQueryEditorNavigationTarget = (
|
||||
|
||||
if (parts.length === 2) {
|
||||
const [firstPart, secondPart] = parts;
|
||||
if (visibleDbSet.has(firstPart.toLowerCase())) {
|
||||
return findObjectInPriorityOrder(firstPart, secondPart);
|
||||
const firstKey = firstPart.toLowerCase();
|
||||
const firstIsVisibleDb = visibleDbSet.has(firstKey);
|
||||
const firstLooksLikeSchema = QUERY_EDITOR_COMMON_SCHEMA_NAME_SET.has(firstKey);
|
||||
|
||||
// 1) 首段是可见库 → MySQL/ClickHouse 风格 db.table(或跨库)
|
||||
if (firstIsVisibleDb) {
|
||||
const asDatabaseObject = findObjectInPriorityOrder(firstPart, secondPart);
|
||||
if (asDatabaseObject) {
|
||||
return asDatabaseObject;
|
||||
}
|
||||
}
|
||||
return findObjectInPriorityOrder(currentDbName, secondPart, firstPart);
|
||||
|
||||
// 2) 当前库下的 schema.table(PostgreSQL / SQL Server / Oracle owner)
|
||||
// 元数据里 tableName 可能是 "public.users" 或裸名 + schemaName
|
||||
const asSchemaObject = findObjectInPriorityOrder(currentDbName, secondPart, firstPart);
|
||||
if (asSchemaObject) {
|
||||
return asSchemaObject;
|
||||
}
|
||||
const asRawQualifiedUnderCurrent = findObjectInPriorityOrder(
|
||||
currentDbName,
|
||||
`${firstPart}.${secondPart}`,
|
||||
);
|
||||
if (asRawQualifiedUnderCurrent) {
|
||||
return asRawQualifiedUnderCurrent;
|
||||
}
|
||||
|
||||
// 3) 首段不在可见库列表,但元数据里已有该库(或拉取中的跨库结果)
|
||||
// 跳过明显 schema 名,避免 public.xxx 误当成库
|
||||
if (!firstIsVisibleDb && !firstLooksLikeSchema) {
|
||||
const asInferredDatabaseObject = findObjectInPriorityOrder(firstPart, secondPart);
|
||||
if (asInferredDatabaseObject) {
|
||||
return asInferredDatabaseObject;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// 三段:database.schema.object(PG/SQL Server 跨库限定)
|
||||
const [dbName, schemaName, tableName] = parts;
|
||||
if (!visibleDbSet.has(dbName.toLowerCase())) {
|
||||
const dbKey = dbName.toLowerCase();
|
||||
const dbIsKnown = visibleDbSet.has(dbKey)
|
||||
|| tableMetas.some((meta) => meta.normalizedDbName === dbKey)
|
||||
|| viewMetas.some((meta) => meta.normalizedDbName === dbKey)
|
||||
|| materializedViewMetas.some((meta) => meta.normalizedDbName === dbKey);
|
||||
|
||||
if (!dbIsKnown) {
|
||||
// Oracle 风格:schema.package.member / schema.sequence.nextval(库仍是 currentDb)
|
||||
const schemaQualifiedSequence = findSequence(currentDbName, schemaName, dbName);
|
||||
if (schemaQualifiedSequence && ['nextval', 'currval'].includes(tableName.toLowerCase())) {
|
||||
return schemaQualifiedSequence;
|
||||
@@ -1913,9 +1989,12 @@ export const resolveQueryEditorNavigationTarget = (
|
||||
if (schemaQualifiedPackage) {
|
||||
return schemaQualifiedPackage;
|
||||
}
|
||||
return null;
|
||||
// 仍尝试把首段当库解析(元数据可能已按 SQL 引用拉取)
|
||||
return findObjectInPriorityOrder(dbName, tableName, schemaName)
|
||||
|| findObjectInPriorityOrder(dbName, `${schemaName}.${tableName}`);
|
||||
}
|
||||
return findObjectInPriorityOrder(dbName, tableName, schemaName);
|
||||
return findObjectInPriorityOrder(dbName, tableName, schemaName)
|
||||
|| findObjectInPriorityOrder(dbName, `${schemaName}.${tableName}`);
|
||||
};
|
||||
|
||||
export const resolveQueryEditorHoverTarget = (
|
||||
|
||||
Reference in New Issue
Block a user