feat(oracle): improve multi-schema completion
This commit is contained in:
9 files changed
+934
-70
No files matched your search
@@ -1233,7 +1233,7 @@ async function findExactSemanticDiagnosticTable(table: SqlTableReference): Promi
|
||||
const localExact = localMatches.find((item) => completionTablesMatch(item, table));
|
||||
if (localExact) return localExact;
|
||||
|
||||
const remoteMatches = await connectionStore.listCompletionTables(props.connectionId, target.database, table.name, MAX_COMPLETION_TABLES, target.schema);
|
||||
const remoteMatches = await connectionStore.listCompletionTables(props.connectionId, target.database, table.name, MAX_COMPLETION_TABLES, target.schema, false, props.schema);
|
||||
cachedTables = mergeCompletionTables(cachedTables, remoteMatches);
|
||||
return remoteMatches.find((item) => completionTablesMatch(item, table)) ?? null;
|
||||
}
|
||||
@@ -1362,12 +1362,12 @@ async function resolveSqlHoverTooltip(currentView: EditorViewType, pos: number)
|
||||
if (cachedTables.length === 0) {
|
||||
cachedTables = usesLocalOnlyCompletionMetadata()
|
||||
? connectionStore.lookupLocalCompletionTables(props.connectionId, props.database, tableLookupName, MAX_COMPLETION_TABLES, props.schema)
|
||||
: await connectionStore.listCompletionTables(props.connectionId, props.database, tableLookupName, MAX_COMPLETION_TABLES, props.schema);
|
||||
: await connectionStore.listCompletionTables(props.connectionId, props.database, tableLookupName, MAX_COMPLETION_TABLES, props.schema, false, props.schema);
|
||||
}
|
||||
|
||||
let table = matchTable(qualifiedTableLookup, cachedTables) ?? matchTable(tableLookupName, cachedTables) ?? matchTable(identifier, cachedTables) ?? matchTable(name, cachedTables);
|
||||
if (!table && !usesLocalOnlyCompletionMetadata()) {
|
||||
const hoverTables = await connectionStore.listCompletionTables(props.connectionId, props.database, tableLookupName, MAX_COMPLETION_TABLES, semanticTarget?.schema ?? props.schema);
|
||||
const hoverTables = await connectionStore.listCompletionTables(props.connectionId, props.database, tableLookupName, MAX_COMPLETION_TABLES, semanticTarget?.schema ?? props.schema, false, props.schema);
|
||||
cachedTables = [...cachedTables, ...hoverTables];
|
||||
table = matchTable(qualifiedTableLookup, hoverTables) ?? matchTable(tableLookupName, hoverTables) ?? matchTable(identifier, hoverTables) ?? matchTable(name, hoverTables);
|
||||
}
|
||||
@@ -2019,6 +2019,7 @@ async function provideSqlCompletions(context: CompletionContext) {
|
||||
snippets: settingsStore.editorSettings.snippets,
|
||||
dialect: props.dialect,
|
||||
databaseType: props.databaseType,
|
||||
currentSchema: props.schema,
|
||||
keywordCase: settingsStore.editorSettings.sqlFormatter.keywordCase,
|
||||
autoAliasTables: settingsStore.editorSettings.autoAliasTables,
|
||||
});
|
||||
@@ -2038,6 +2039,7 @@ async function provideSqlCompletions(context: CompletionContext) {
|
||||
snippets: settingsStore.editorSettings.snippets,
|
||||
dialect: props.dialect,
|
||||
databaseType: props.databaseType,
|
||||
currentSchema: props.schema,
|
||||
keywordCase: settingsStore.editorSettings.sqlFormatter.keywordCase,
|
||||
autoAliasTables: settingsStore.editorSettings.autoAliasTables,
|
||||
});
|
||||
@@ -2150,12 +2152,11 @@ function buildLocalSqlCompletionResult(completionContext: ReturnType<typeof getS
|
||||
completionContext,
|
||||
knownDatabases: databaseNames,
|
||||
});
|
||||
const tables = shouldLoadTables ? connectionStore.lookupLocalCompletionTables(props.connectionId, tableLookupTarget.database, tableLookupTarget.filter, MAX_COMPLETION_TABLES, tableLookupTarget.schema) : cachedTables;
|
||||
const globalOracleTableSearch = props.databaseType === "oracle" && completionContext.suggestTables && !completionContext.qualifier;
|
||||
const tables = shouldLoadTables ? connectionStore.lookupLocalCompletionTables(props.connectionId, tableLookupTarget.database, tableLookupTarget.filter, MAX_COMPLETION_TABLES, globalOracleTableSearch ? undefined : tableLookupTarget.schema) : cachedTables;
|
||||
|
||||
const shouldLoadObjects = completionContext.suggestRoutines || completionContext.exclusiveRoutineSuggestions || (!!completionContext.qualifier && !completionContext.exclusiveColumnSuggestions);
|
||||
const completionObjects = shouldLoadObjects
|
||||
? connectionStore.lookupLocalCompletionObjects(props.connectionId, props.database, completionContext.qualifier || completionContext.prefix, MAX_COMPLETION_TABLES, completionContext.qualifier && !completionContext.exclusiveColumnSuggestions ? completionContext.qualifier : props.schema)
|
||||
: cachedCompletionObjects;
|
||||
const shouldLoadObjects = shouldLoadCompletionObjects(completionContext);
|
||||
const completionObjects = shouldLoadObjects ? lookupLocalCompletionObjectsForContext(completionContext) : cachedCompletionObjects;
|
||||
|
||||
const schemaNames =
|
||||
completionContext.suggestTables && !completionContext.qualifier && !completionContext.insertTable ? mergeCompletionQualifierNames(connectionStore.lookupLocalCompletionSchemas(props.connectionId, props.database, completionContext.prefix, MAX_COMPLETION_TABLES), databaseNames) : [];
|
||||
@@ -2225,6 +2226,7 @@ function buildLocalSqlCompletionResult(completionContext: ReturnType<typeof getS
|
||||
snippets: settingsStore.editorSettings.snippets,
|
||||
dialect: props.dialect,
|
||||
databaseType: props.databaseType,
|
||||
currentSchema: props.schema,
|
||||
keywordCase: settingsStore.editorSettings.sqlFormatter.keywordCase,
|
||||
autoAliasTables: settingsStore.editorSettings.autoAliasTables,
|
||||
});
|
||||
@@ -2247,8 +2249,9 @@ function scheduleCompletionMetadataRefresh(completionContext: ReturnType<typeof
|
||||
knownDatabases: localCompletionDatabaseNames(completionContext),
|
||||
});
|
||||
if (!localOnlyMetadata && (completionContext.suggestTables || (!!completionContext.qualifier && !isReferencedTableQualifier(completionContext)))) {
|
||||
const globalOracleTableSearch = props.databaseType === "oracle" && completionContext.suggestTables && !completionContext.qualifier;
|
||||
void connectionStore
|
||||
.refreshCompletionTables(connectionId, tableLookupTarget.database, tableLookupTarget.filter, MAX_COMPLETION_TABLES, tableLookupTarget.schema)
|
||||
.refreshCompletionTables(connectionId, tableLookupTarget.database, tableLookupTarget.filter, MAX_COMPLETION_TABLES, tableLookupTarget.schema, globalOracleTableSearch, props.schema)
|
||||
.then((tables) => {
|
||||
cachedTables = mergeCompletionTables(cachedTables, tables);
|
||||
if (completionContext.suggestTables && completionContext.referencedTables.length > 0) {
|
||||
@@ -2257,9 +2260,8 @@ function scheduleCompletionMetadataRefresh(completionContext: ReturnType<typeof
|
||||
})
|
||||
.catch(() => {});
|
||||
}
|
||||
if (!localOnlyMetadata && (completionContext.suggestRoutines || completionContext.exclusiveRoutineSuggestions || (!!completionContext.qualifier && !completionContext.exclusiveColumnSuggestions))) {
|
||||
void connectionStore
|
||||
.refreshCompletionObjects(connectionId, database, completionContext.prefix, MAX_COMPLETION_TABLES, props.schema)
|
||||
if (!localOnlyMetadata && shouldLoadCompletionObjects(completionContext)) {
|
||||
void listCompletionObjectsForContext(completionContext)
|
||||
.then((objects) => {
|
||||
cachedCompletionObjects = mergeCompletionObjects(cachedCompletionObjects, objects);
|
||||
})
|
||||
@@ -2315,14 +2317,18 @@ function scheduleCompletionMetadataRefresh(completionContext: ReturnType<typeof
|
||||
}
|
||||
}
|
||||
|
||||
function mergeCompletionTables(existing: Array<{ name: string; schema?: string; type?: "table" | "view" }>, incoming: Array<{ name: string; schema?: string; type?: "table" | "view" }>) {
|
||||
function mergeCompletionTables(existing: SqlCompletionTable[], incoming: SqlCompletionTable[]): SqlCompletionTable[] {
|
||||
const merged = [...existing];
|
||||
const seen = new Set(existing.map((table) => `${table.schema ?? ""}.${table.name}`.toLowerCase()));
|
||||
const indexes = new Map(existing.map((table, index) => [`${table.schema ?? ""}.${table.name}`.toLowerCase(), index]));
|
||||
for (const table of incoming) {
|
||||
const key = `${table.schema ?? ""}.${table.name}`.toLowerCase();
|
||||
if (seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
merged.push(table);
|
||||
const index = indexes.get(key);
|
||||
if (index == null) {
|
||||
indexes.set(key, merged.length);
|
||||
merged.push(table);
|
||||
} else {
|
||||
merged[index] = { ...merged[index], ...table };
|
||||
}
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
@@ -2331,9 +2337,9 @@ function withCompletionLatencyBudget<T>(remote: Promise<T>, local: T): Promise<T
|
||||
return Promise.race([remote, new Promise<T>((resolve) => setTimeout(() => resolve(local), COMPLETION_REMOTE_LATENCY_BUDGET_MS))]);
|
||||
}
|
||||
|
||||
function listCompletionTablesWithLatencyBudget(connectionId: string, database: string, filter: string, limit: number, schema?: string): Promise<SqlCompletionTable[]> {
|
||||
const local = connectionStore.lookupLocalCompletionTables(connectionId, database, filter, limit, schema);
|
||||
const remote = connectionStore.listCompletionTables(connectionId, database, filter, limit, schema).then((tables) => {
|
||||
function listCompletionTablesWithLatencyBudget(connectionId: string, database: string, filter: string, limit: number, schema?: string, globalSearch = false): Promise<SqlCompletionTable[]> {
|
||||
const local = connectionStore.lookupLocalCompletionTables(connectionId, database, filter, limit, globalSearch ? undefined : schema);
|
||||
const remote = connectionStore.listCompletionTables(connectionId, database, filter, limit, schema, globalSearch, props.schema).then((tables) => {
|
||||
cachedTables = mergeCompletionTables(cachedTables, tables);
|
||||
return tables;
|
||||
});
|
||||
@@ -2341,6 +2347,43 @@ function listCompletionTablesWithLatencyBudget(connectionId: string, database: s
|
||||
return withCompletionLatencyBudget(remote, local);
|
||||
}
|
||||
|
||||
interface RoutineCompletionTarget {
|
||||
schema?: string;
|
||||
parentName?: string;
|
||||
globalSearch?: boolean;
|
||||
}
|
||||
|
||||
function shouldLoadCompletionObjects(completionContext: ReturnType<typeof getSqlCompletionContext>): boolean {
|
||||
const routineContext = completionContext.suggestRoutines || completionContext.exclusiveRoutineSuggestions || (!!completionContext.qualifier && !completionContext.exclusiveColumnSuggestions);
|
||||
return routineContext && !isReferencedTableQualifier(completionContext);
|
||||
}
|
||||
|
||||
function oracleRoutineCompletionTargets(completionContext: ReturnType<typeof getSqlCompletionContext>): RoutineCompletionTarget[] {
|
||||
const parts = (completionContext.qualifierParts?.length ? completionContext.qualifierParts : completionContext.qualifier?.split("."))?.filter(Boolean) ?? [];
|
||||
if (parts.length === 0) return [{ schema: props.schema, globalSearch: true }];
|
||||
if (parts.length === 1) {
|
||||
return [{ schema: props.schema, parentName: parts[0] }, { schema: parts[0] }];
|
||||
}
|
||||
return [{ schema: parts[parts.length - 2], parentName: parts[parts.length - 1] }];
|
||||
}
|
||||
|
||||
function lookupLocalCompletionObjectsForContext(completionContext: ReturnType<typeof getSqlCompletionContext>): SqlCompletionObject[] {
|
||||
if (!props.connectionId || props.database == null) return [];
|
||||
if (props.databaseType === "oracle") {
|
||||
return connectionStore.lookupLocalCompletionObjects(props.connectionId, props.database, completionContext.prefix, MAX_COMPLETION_TABLES);
|
||||
}
|
||||
return connectionStore.lookupLocalCompletionObjects(props.connectionId, props.database, completionContext.qualifier || completionContext.prefix, MAX_COMPLETION_TABLES, completionContext.qualifier && !completionContext.exclusiveColumnSuggestions ? completionContext.qualifier : props.schema);
|
||||
}
|
||||
|
||||
async function listCompletionObjectsForContext(completionContext: ReturnType<typeof getSqlCompletionContext>): Promise<SqlCompletionObject[]> {
|
||||
if (!props.connectionId || props.database == null) return [];
|
||||
if (props.databaseType !== "oracle") {
|
||||
return connectionStore.listCompletionObjects(props.connectionId, props.database, completionContext.qualifier || completionContext.prefix, MAX_COMPLETION_TABLES, props.schema);
|
||||
}
|
||||
const groups = await Promise.all(oracleRoutineCompletionTargets(completionContext).map((target) => connectionStore.listCompletionObjects(props.connectionId!, props.database!, completionContext.prefix, MAX_COMPLETION_TABLES, target.schema, target.parentName, target.globalSearch, props.schema)));
|
||||
return groups.reduce((objects, group) => mergeCompletionObjects(objects, group), [] as SqlCompletionObject[]);
|
||||
}
|
||||
|
||||
async function performAsyncCompletionWithResult(epoch: number, completionContext: ReturnType<typeof getSqlCompletionContext>, fullDoc: string, position: number) {
|
||||
const localOnlyMetadata = usesLocalOnlyCompletionMetadata();
|
||||
const onDemandOnlyColumns = usesOnDemandOnlyCompletionColumns();
|
||||
@@ -2377,22 +2420,19 @@ async function performAsyncCompletionWithResult(epoch: number, completionContext
|
||||
completionContext,
|
||||
knownDatabases: databaseNames,
|
||||
});
|
||||
const globalOracleTableSearch = props.databaseType === "oracle" && completionContext.suggestTables && !completionContext.qualifier;
|
||||
let tables = shouldLoadTables
|
||||
? localOnlyMetadata
|
||||
? connectionStore.lookupLocalCompletionTables(props.connectionId!, tableLookupTarget.database, tableLookupTarget.filter, MAX_COMPLETION_TABLES, tableLookupTarget.schema)
|
||||
: await listCompletionTablesWithLatencyBudget(props.connectionId!, tableLookupTarget.database, tableLookupTarget.filter, MAX_COMPLETION_TABLES, tableLookupTarget.schema)
|
||||
? connectionStore.lookupLocalCompletionTables(props.connectionId!, tableLookupTarget.database, tableLookupTarget.filter, MAX_COMPLETION_TABLES, globalOracleTableSearch ? undefined : tableLookupTarget.schema)
|
||||
: await listCompletionTablesWithLatencyBudget(props.connectionId!, tableLookupTarget.database, tableLookupTarget.filter, MAX_COMPLETION_TABLES, tableLookupTarget.schema, globalOracleTableSearch)
|
||||
: cachedTables;
|
||||
if (epoch !== completionEpoch) return null;
|
||||
|
||||
const shouldLoadObjects = completionContext.suggestRoutines || completionContext.exclusiveRoutineSuggestions || (!!completionContext.qualifier && !completionContext.exclusiveColumnSuggestions);
|
||||
let completionObjects = shouldLoadObjects
|
||||
? localOnlyMetadata
|
||||
? connectionStore.lookupLocalCompletionObjects(props.connectionId!, props.database!, completionContext.qualifier || completionContext.prefix, MAX_COMPLETION_TABLES, props.schema)
|
||||
: await connectionStore.listCompletionObjects(props.connectionId!, props.database!, completionContext.qualifier || completionContext.prefix, MAX_COMPLETION_TABLES, props.schema)
|
||||
: cachedCompletionObjects;
|
||||
const shouldLoadObjects = shouldLoadCompletionObjects(completionContext);
|
||||
let completionObjects = shouldLoadObjects ? (localOnlyMetadata ? lookupLocalCompletionObjectsForContext(completionContext) : await listCompletionObjectsForContext(completionContext)) : cachedCompletionObjects;
|
||||
if (epoch !== completionEpoch) return null;
|
||||
|
||||
if (!localOnlyMetadata && completionContext.qualifier && completionObjects.length === 0) {
|
||||
if (props.databaseType !== "oracle" && !localOnlyMetadata && completionContext.qualifier && completionObjects.length === 0) {
|
||||
const schemaObjects = await connectionStore.listCompletionObjects(props.connectionId!, props.database!, completionContext.prefix, MAX_COMPLETION_TABLES, completionContext.qualifier);
|
||||
if (schemaObjects.length > 0) {
|
||||
completionObjects = schemaObjects;
|
||||
@@ -2445,7 +2485,7 @@ async function performAsyncCompletionWithResult(epoch: number, completionContext
|
||||
});
|
||||
const unresolvedRefs = refs.filter((rt) => !rt.schema && !rt.columns);
|
||||
if (!localOnlyMetadata && unresolvedRefs.length > 0) {
|
||||
const lookupGroups = await Promise.all(unresolvedRefs.map((rt) => connectionStore.listCompletionTables(props.connectionId!, props.database!, rt.name, 20, props.schema)));
|
||||
const lookupGroups = await Promise.all(unresolvedRefs.map((rt) => connectionStore.listCompletionTables(props.connectionId!, props.database!, rt.name, 20, props.schema, false, props.schema)));
|
||||
if (epoch !== completionEpoch) return null;
|
||||
const lookupTables = lookupGroups.flat();
|
||||
refs = refs.map((rt) => {
|
||||
@@ -2564,6 +2604,7 @@ async function performAsyncCompletionWithResult(epoch: number, completionContext
|
||||
snippets: settingsStore.editorSettings.snippets,
|
||||
dialect: props.dialect,
|
||||
databaseType: props.databaseType,
|
||||
currentSchema: props.schema,
|
||||
keywordCase: settingsStore.editorSettings.sqlFormatter.keywordCase,
|
||||
autoAliasTables: settingsStore.editorSettings.autoAliasTables,
|
||||
});
|
||||
@@ -2584,12 +2625,16 @@ function isTableNameCompletionContext(completionContext: ReturnType<typeof getSq
|
||||
|
||||
function mergeCompletionObjects(existing: SqlCompletionObject[], incoming: SqlCompletionObject[]) {
|
||||
const merged = [...existing];
|
||||
const seen = new Set(existing.map((object) => `${object.type}:${object.schema ?? ""}:${object.name}:${object.parentName ?? ""}`.toLowerCase()));
|
||||
const indexes = new Map(existing.map((object, index) => [`${object.type}:${object.schema ?? ""}:${object.name}:${object.parentName ?? ""}`.toLowerCase(), index]));
|
||||
for (const object of incoming) {
|
||||
const key = `${object.type}:${object.schema ?? ""}:${object.name}:${object.parentName ?? ""}`.toLowerCase();
|
||||
if (seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
merged.push(object);
|
||||
const index = indexes.get(key);
|
||||
if (index == null) {
|
||||
indexes.set(key, merged.length);
|
||||
merged.push(object);
|
||||
} else {
|
||||
merged[index] = { ...merged[index], ...object };
|
||||
}
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
@@ -3098,7 +3143,7 @@ onMounted(async () => {
|
||||
// Some metadata providers only accept table-name masks, not schema.table masks.
|
||||
cachedTables = usesLocalOnlyCompletionMetadata()
|
||||
? connectionStore.lookupLocalCompletionTables(props.connectionId!, props.database!, tableLookupFilter, MAX_COMPLETION_TABLES, props.schema)
|
||||
: await connectionStore.listCompletionTables(props.connectionId!, props.database!, tableLookupFilter, MAX_COMPLETION_TABLES, props.schema);
|
||||
: await connectionStore.listCompletionTables(props.connectionId!, props.database!, tableLookupFilter, MAX_COMPLETION_TABLES, props.schema, false, props.schema);
|
||||
}
|
||||
|
||||
// 1. Check if it's a table name
|
||||
|
||||
Reference in new issue
Block a user