feat: add PrestoSQL connector

This commit is contained in:
luoianun committed 2026-06-23 01:27:08 +08:00
1 parent 7910a2f68e
commit 8cc7a35c1a
40 files changed
+1323 -123

No files matched your search

@@ -19,6 +19,7 @@ import * as api from "@/lib/api";
import type { AgentDriverInfo, DriverRuntimeInfo, DriverRuntimeSummary, DriverStoreUsage, JavaRuntimeConfig } from "@/lib/api";
import { formatRuntimeBytes, formatRuntimeCpu, formatRuntimeUptime, runtimeHealthClass, runtimeStatusClass, runtimeStatusDotClass } from "@/lib/driverRuntimePresentation";
import { addDriverInstallQueue, driverInstallProgressPercent, isDriverInstallProgressTarget, removeDriverInstallQueue, takeNextDriverInstallQueue, type DriverInstallProgress } from "@/lib/driverInstallProgressUi";
import { PRESTOSQL_DRIVER_DB_TYPE, prestoSqlBuiltinDriverRow, prestoSqlMavenBundle } from "@/lib/prestoSqlBuiltinDriver";
const { t } = useI18n();
const { toast } = useToast();
@@ -261,12 +262,22 @@ function progressTitle(fallback: string): string {
return progressText.value || fallback;
}
function isPrestoSqlBuiltinDriver(dbType: string): boolean {
return dbType === PRESTOSQL_DRIVER_DB_TYPE;
}
const builtinDriverRows = computed<AgentDriverInfo[]>(() => [...drivers.value, prestoSqlBuiltinDriverRow(jdbcMavenBundles.value)]);
function driverLabel(dbType: string): string {
return builtinDriverRows.value.find((d) => d.db_type === dbType)?.label ?? dbType;
}
function isDriverQueued(dbType: string): boolean {
return queuedDriverInstalls.value.includes(dbType);
}
function canInstallOrUpdateDriver(dbType: string): boolean {
const driver = drivers.value.find((d) => d.db_type === dbType);
const driver = builtinDriverRows.value.find((d) => d.db_type === dbType);
return Boolean(driver && (!driver.installed || driver.update_available));
}
@@ -353,10 +364,21 @@ async function installDriver(dbType: string) {
}
async function runDriverInstall(dbType: string) {
const label = drivers.value.find((d) => d.db_type === dbType)?.label ?? dbType;
const label = driverLabel(dbType);
installing.value = dbType;
resetInstallProgress();
try {
if (isPrestoSqlBuiltinDriver(dbType)) {
if (!jdbcPluginStatus.value?.installed || !jdbcPluginStatus.value.compatible) {
jdbcPluginStatus.value = await api.installJdbcPlugin();
emitDriverUpdateCount();
}
jdbcDrivers.value = await api.installPrestoSqlJdbcDriver();
jdbcMavenBundles.value = await api.listJdbcMavenBundles();
void loadDriverStoreUsage();
toast(t("driverStore.driverInstallSuccess", { label }));
return;
}
const blockers = await api.checkAgentUpdateBlockers([dbType]);
if (blockers.length > 0) {
toast(t("driverStore.driverUpdateBlocked", { labels: blockers.map((blocker) => blocker.label).join(", ") }));
@@ -415,8 +437,17 @@ async function upgradeAll() {
}
async function uninstallDriver(dbType: string) {
const label = drivers.value.find((d) => d.db_type === dbType)?.label ?? dbType;
const label = driverLabel(dbType);
try {
if (isPrestoSqlBuiltinDriver(dbType)) {
const bundle = prestoSqlMavenBundle(jdbcMavenBundles.value);
if (!bundle) return;
jdbcDrivers.value = await api.deleteJdbcMavenBundle(bundle.id);
jdbcMavenBundles.value = await api.listJdbcMavenBundles();
void loadDriverStoreUsage();
toast(t("driverStore.driverUninstallSuccess", { label }));
return;
}
await api.uninstallAgent(dbType);
await refreshAgents();
toast(t("driverStore.driverUninstallSuccess", { label }));
@@ -495,7 +526,11 @@ async function importOfflineZip() {
}
async function importDriverJar(dbType: string) {
const label = drivers.value.find((d) => d.db_type === dbType)?.label ?? dbType;
if (isPrestoSqlBuiltinDriver(dbType)) {
await importJdbcDrivers();
return;
}
const label = driverLabel(dbType);
if (isWeb) {
const file = await chooseWebFile(".jar");
if (!file) return;
@@ -600,8 +635,8 @@ type JdbcDriverListItem =
const filteredAgentDrivers = computed(() => {
const query = agentDriverSearch.value.trim().toLowerCase();
if (!query) return drivers.value;
return drivers.value.filter((driver) => [driver.label, driver.db_type, driver.version, driver.installed_version, driver.jre].filter(Boolean).join(" ").toLowerCase().includes(query));
if (!query) return builtinDriverRows.value;
return builtinDriverRows.value.filter((driver) => [driver.label, driver.db_type, driver.version, driver.installed_version, driver.jre].filter(Boolean).join(" ").toLowerCase().includes(query));
});
const jdbcDriverListItems = computed<JdbcDriverListItem[]>(() => {
@@ -1102,7 +1137,7 @@ watch(driverStoreTab, (tab) => {
{{ t("driverStore.install") }}
</Button>
<Button
v-if="!driver.installed && !isDriverProgressActive(driver.db_type) && !isDriverQueued(driver.db_type)"
v-if="!driver.installed && !isPrestoSqlBuiltinDriver(driver.db_type) && !isDriverProgressActive(driver.db_type) && !isDriverQueued(driver.db_type)"
size="sm"
variant="ghost"
class="h-7 w-7 rounded-full text-xs text-muted-foreground"
@@ -484,6 +484,7 @@ const driverProfiles: Record<
h2: { type: "h2", port: 9092, user: "sa", label: "H2", icon: "h2" },
snowflake: { type: "snowflake", port: 443, user: "", label: "Snowflake", icon: "snowflake" },
trino: { type: "trino", port: 8080, user: "", label: "Trino", icon: "trino" },
prestosql: { type: "prestosql", port: 8080, user: "", label: "PrestoSQL", icon: "presto" },
hive: { type: "hive", port: 10000, user: "", label: "Apache Hive", icon: "hive" },
db2: { type: "db2", port: 50000, user: "db2inst1", label: "IBM DB2", icon: "db2" },
informix: { type: "informix", port: 9088, user: "informix", label: "Informix", icon: "informix" },
@@ -674,6 +675,13 @@ function applyProfile(val: string, preserveConnectionFields = false) {
form.value.jdbc_driver_paths = [];
jdbcDriverPathsInput.value = "";
}
if (profile.type === "prestosql") {
form.value.connection_string = undefined;
form.value.jdbc_driver_class = "io.prestosql.jdbc.PrestoDriver";
form.value.jdbc_driver_paths = [];
jdbcDriverPathsInput.value = "";
jdbcManualClasspathOpen.value = true;
}
if (profile.type === "mq") {
resetMqFields();
form.value.database = undefined;
@@ -763,6 +771,7 @@ watch(
}
mongoUseUrl.value = !!config.connection_string;
jdbcDriverPathsInput.value = (config.jdbc_driver_paths || []).join("\n");
jdbcManualClasspathOpen.value = config.db_type === "prestosql" || (config.jdbc_driver_paths || []).length > 0;
customDriverName.value = isCustomCompatibleProfile() ? config.driver_label || "" : "";
dialogStep.value = "config";
configTab.value = "connection";
@@ -913,6 +922,7 @@ const iconTypeMap: Record<string, string> = {
h2: "h2",
snowflake: "snowflake",
trino: "trino",
prestosql: "prestosql",
hive: "hive",
db2: "db2",
informix: "informix",
@@ -977,6 +987,7 @@ const dbOptions: DbOption[] = [
{ value: "h2", label: "H2" },
{ value: "snowflake", label: "Snowflake" },
{ value: "trino", label: "Trino" },
{ value: "prestosql", label: "PrestoSQL" },
{ value: "hive", label: "Hive" },
{ value: "db2", label: "DB2" },
{ value: "informix", label: "Informix" },
@@ -1022,7 +1033,9 @@ const filteredDbCategories = computed<DbCategory[]>(() => {
const hasDbPickerResults = computed(() => filteredDbCategories.value.some((category) => category.options.length > 0));
const selectedDbIcon = computed(() => iconTypeMap[selectedType.value] || selectedProfile().icon || selectedType.value);
const jdbcBackedDatabaseTypes = new Set<DatabaseType>(["jdbc", "prestosql"]);
const isJdbcConnection = computed(() => form.value.db_type === "jdbc");
const isPrestoSqlConnection = computed(() => form.value.db_type === "prestosql");
const isH2FileMode = computed(() => form.value.db_type === "h2" && h2ConnectionMode.value === "file");
const usesLocalFilePathInput = computed(() => isLocalFileTypeDb(form.value.db_type) && (form.value.db_type !== "h2" || isH2FileMode.value));
@@ -1369,10 +1382,15 @@ function connectionConfigForSubmit(id: string): ConnectionConfig {
} else {
config.ca_cert_path = config.ca_cert_path?.trim() || "";
}
if (config.db_type === "jdbc") {
config.host = "";
config.port = 0;
config.connection_string = config.connection_string?.trim() || "";
if (jdbcBackedDatabaseTypes.has(config.db_type)) {
if (config.db_type === "jdbc") {
config.host = "";
config.port = 0;
config.connection_string = config.connection_string?.trim() || "";
} else if (config.db_type === "prestosql") {
config.connection_string = undefined;
config.jdbc_driver_class = config.jdbc_driver_class?.trim() || "io.prestosql.jdbc.PrestoDriver";
}
config.jdbc_driver_class = config.jdbc_driver_class?.trim() || undefined;
config.jdbc_driver_paths = jdbcDriverPathsInput.value
.split(/\r?\n/)
@@ -2497,7 +2515,7 @@ function openExternalUrl(url: string) {
</div>
<!-- JDBC: optional external plugin -->
<template v-if="form.db_type === 'jdbc'">
<template v-if="isJdbcConnection">
<div class="grid grid-cols-4 items-center gap-4">
<Label class="text-right">{{ t("connection.jdbcUrl") }}</Label>
<Input v-model="form.connection_string" class="col-span-3" :placeholder="t('connection.jdbcUrlPlaceholder')" />
@@ -2560,6 +2578,10 @@ function openExternalUrl(url: string) {
{{ t("connection.jdbcPluginHint") }}
</p>
<div class="flex flex-wrap gap-2">
<Button type="button" variant="outline" size="sm" @click="emit('openDriverStore')">
<FolderOpen class="h-3.5 w-3.5" />
{{ t("toolbar.driverManager") }}
</Button>
<Button type="button" variant="outline" size="sm" @click="openExternalUrl('https://dbxio.com')">
<ExternalLink class="h-3.5 w-3.5" />
{{ t("connection.jdbcDocs") }}
@@ -3071,6 +3093,70 @@ function openExternalUrl(url: string) {
"
/>
</div>
<template v-if="isPrestoSqlConnection">
<div class="grid grid-cols-4 items-start gap-4">
<Label class="text-right mt-2">{{ t("connection.jdbcDriverPaths") }}</Label>
<div class="col-span-3 space-y-2">
<Select v-if="jdbcDriverSelectItems.length > 0" :model-value="selectedJdbcDriverPath" @update:model-value="onJdbcDriverSelect">
<SelectTrigger>
<SelectValue :placeholder="t('connection.jdbcDriverSelectPlaceholder')" />
</SelectTrigger>
<SelectContent>
<SelectItem v-for="driver in jdbcDriverSelectItems" :key="driver.id" :value="driver.id">
{{ driver.label }}
</SelectItem>
</SelectContent>
</Select>
<div class="flex items-center justify-between gap-3 rounded-md border bg-muted/20 px-3 py-2">
<div class="flex min-w-0 items-center gap-2">
<div class="truncate text-xs font-medium">{{ t("connection.jdbcManualClasspath") }}</div>
<Badge variant="outline" class="h-5 shrink-0 rounded-full px-2 text-[10px] font-medium">
{{ t("connection.jdbcManualClasspathCount", { count: jdbcManualClasspathCount }) }}
</Badge>
</div>
<Switch v-model="jdbcManualClasspathOpen" />
</div>
<div v-if="jdbcManualClasspathOpen" class="flex items-start gap-1">
<textarea
v-model="jdbcDriverPathsInput"
class="flex min-h-12 w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring"
:placeholder="t('connection.jdbcDriverPathsPlaceholder')"
/>
<Tooltip v-if="isDesktop">
<TooltipTrigger as-child>
<Button type="button" variant="outline" size="icon" class="h-9 w-9 shrink-0" @click="browseJdbcDriverPaths">
<FolderOpen class="h-4 w-4" />
</Button>
</TooltipTrigger>
<TooltipContent>{{ t("connection.jdbcDriverBrowse") }}</TooltipContent>
</Tooltip>
</div>
</div>
</div>
<div class="grid grid-cols-4 items-center gap-4">
<Label class="text-right">{{ t("connection.jdbcDriverClass") }}</Label>
<Input v-model="form.jdbc_driver_class" class="col-span-3" :placeholder="t('connection.jdbcDriverClassPlaceholder')" />
</div>
<div class="grid grid-cols-4 items-start gap-4">
<span />
<div class="col-span-3 space-y-2">
<p class="text-xs text-muted-foreground">
{{ t("connection.jdbcPluginHint") }}
</p>
<div class="flex flex-wrap gap-2">
<Button type="button" variant="outline" size="sm" @click="emit('openDriverStore')">
<FolderOpen class="h-3.5 w-3.5" />
{{ t("toolbar.driverManager") }}
</Button>
<Button type="button" variant="outline" size="sm" @click="openExternalUrl('https://dbxio.com')">
<ExternalLink class="h-3.5 w-3.5" />
{{ t("connection.jdbcDocs") }}
</Button>
</div>
</div>
</div>
</template>
</template>
</div>
</TabsContent>
@@ -40,6 +40,7 @@ import { trimmedSelectionLayer } from "@/lib/codemirrorTrimmedSelectionLayer";
import { selectionMatchOccurrences } from "@/lib/codemirrorSelectionMatches";
import { createDbxCodeMirrorSqlDialect } from "@/lib/codemirrorSqlDialect";
import { isSchemaAware, isSingleDatabase } from "@/lib/databaseFeatureSupport";
import { usesLocalOnlyEditorCompletionMetadata, usesOnDemandOnlyEditorColumnMetadata } from "@/lib/completionMetadataPolicy";
import { qualifiedTableNameAtSqlPosition } from "@/lib/queryCursorTableTarget";
import * as api from "@/lib/api";
import { areSqlSemanticDiagnosticsEqual, buildSqlParserErrorDiagnostic, buildSqlSemanticDiagnostics, shouldRunSqlSemanticDiagnostics, type SqlSemanticDiagnostic } from "@/lib/sqlSemanticDiagnostics";
@@ -579,6 +580,14 @@ function supportsDatabaseQualifierCompletion(): boolean {
return !!props.databaseType && !isSchemaAware(props.databaseType) && !isSingleDatabase(props.databaseType);
}
function usesLocalOnlyCompletionMetadata(): boolean {
return usesLocalOnlyEditorCompletionMetadata(props.databaseType);
}
function usesOnDemandOnlyCompletionColumns(): boolean {
return usesOnDemandOnlyEditorColumnMetadata(props.databaseType);
}
function completionMetadataTarget(table: { name: string; schema?: string | null }): { database: string; schema?: string } | null {
if (props.database == null) return null;
if (supportsDatabaseQualifierCompletion() && table.schema) {
@@ -711,11 +720,13 @@ async function resolveSqlHoverTooltip(currentView: EditorViewType, pos: number)
try {
if (cachedTables.length === 0) {
cachedTables = await connectionStore.listCompletionTables(props.connectionId, props.database, name, MAX_COMPLETION_TABLES, props.schema);
cachedTables = usesLocalOnlyCompletionMetadata()
? connectionStore.lookupLocalCompletionTables(props.connectionId, props.database, name, MAX_COMPLETION_TABLES, props.schema)
: await connectionStore.listCompletionTables(props.connectionId, props.database, name, MAX_COMPLETION_TABLES, props.schema);
}
let table = matchTable(identifier, cachedTables) ?? matchTable(name, cachedTables);
if (!table) {
if (!table && !usesLocalOnlyCompletionMetadata()) {
const hoverTables = await connectionStore.listCompletionTables(props.connectionId, props.database, name, MAX_COMPLETION_TABLES, props.schema);
cachedTables = [...cachedTables, ...hoverTables];
table = matchTable(identifier, hoverTables) ?? matchTable(name, hoverTables);
@@ -824,6 +835,12 @@ async function enrichSemanticDiagnosticTables(tables: SqlTableReference[]) {
continue;
}
try {
if (usesLocalOnlyCompletionMetadata()) {
const matches = connectionStore.lookupLocalCompletionTables(props.connectionId, props.database, table.name, MAX_COMPLETION_TABLES, props.schema);
const match = matches.find((item) => item.name.toLowerCase() === table.name.toLowerCase());
enriched.push(match?.schema ? { ...table, schema: match.schema } : table);
continue;
}
const matches = await connectionStore.listCompletionTables(props.connectionId, props.database, table.name, MAX_COMPLETION_TABLES, props.schema);
cachedTables = [...cachedTables, ...matches];
const match = matches.find((item) => item.name.toLowerCase() === table.name.toLowerCase());
@@ -875,7 +892,9 @@ async function refreshSemanticDiagnostics() {
if (runId !== semanticDiagnosticRunId) return;
const tables = await enrichSemanticDiagnosticTables(analysis.tables);
await Promise.all(tables.map((table) => ensureColumnsForTable(table)));
if (!usesOnDemandOnlyCompletionColumns()) {
await Promise.all(tables.map((table) => ensureColumnsForTable(table)));
}
if (runId !== semanticDiagnosticRunId) return;
const enrichedAnalysis: SqlReferenceAnalysis = { ...analysis, tables };
@@ -1359,10 +1378,12 @@ function buildLocalSqlCompletionResult(completionContext: ReturnType<typeof getS
function scheduleCompletionMetadataRefresh(completionContext: ReturnType<typeof getSqlCompletionContext>) {
if (!props.connectionId || props.database == null) return;
const localOnlyMetadata = usesLocalOnlyCompletionMetadata();
const onDemandOnlyColumns = usesOnDemandOnlyCompletionColumns();
const connectionId = props.connectionId;
const database = props.database;
const schema = completionContext.qualifier && completionContext.suggestTables ? completionContext.qualifier : props.schema;
if (completionContext.suggestTables || (!!completionContext.qualifier && !isReferencedTableQualifier(completionContext))) {
if (!localOnlyMetadata && (completionContext.suggestTables || (!!completionContext.qualifier && !isReferencedTableQualifier(completionContext)))) {
void connectionStore
.refreshCompletionTables(connectionId, database, completionContext.qualifier && !schema ? completionContext.qualifier : completionContext.prefix, MAX_COMPLETION_TABLES, schema)
.then((tables) => {
@@ -1373,7 +1394,7 @@ function scheduleCompletionMetadataRefresh(completionContext: ReturnType<typeof
})
.catch(() => {});
}
if (completionContext.suggestRoutines || completionContext.exclusiveRoutineSuggestions || (!!completionContext.qualifier && !completionContext.exclusiveColumnSuggestions)) {
if (!localOnlyMetadata && (completionContext.suggestRoutines || completionContext.exclusiveRoutineSuggestions || (!!completionContext.qualifier && !completionContext.exclusiveColumnSuggestions))) {
void connectionStore
.refreshCompletionObjects(connectionId, database, completionContext.prefix, MAX_COMPLETION_TABLES, props.schema)
.then((objects) => {
@@ -1381,13 +1402,13 @@ function scheduleCompletionMetadataRefresh(completionContext: ReturnType<typeof
})
.catch(() => {});
}
if (completionContext.suggestTables && !completionContext.qualifier && !completionContext.insertTable) {
if (!localOnlyMetadata && completionContext.suggestTables && !completionContext.qualifier && !completionContext.insertTable) {
void connectionStore.refreshCompletionSchemas(connectionId, database).catch(() => {});
if (supportsDatabaseQualifierCompletion()) {
void connectionStore.refreshCompletionDatabases(connectionId).catch(() => {});
}
}
if (completionContext.insertTable) {
if (!onDemandOnlyColumns && completionContext.insertTable) {
const insertTable = completionContext.insertTable;
void connectionStore
.refreshCompletionColumns(connectionId, database, insertTable, completionContext.insertSchema ?? props.schema)
@@ -1399,7 +1420,7 @@ function scheduleCompletionMetadataRefresh(completionContext: ReturnType<typeof
}
const qualifiedColumnTarget = completionQualifiedTableTarget(completionContext);
const qualifiedColumnCacheKey = qualifiedColumnTarget ? completionCacheKey(qualifiedColumnTarget) : undefined;
if (qualifiedColumnTarget && qualifiedColumnCacheKey && !cachedColumnsByTable.has(qualifiedColumnCacheKey)) {
if (!onDemandOnlyColumns && qualifiedColumnTarget && qualifiedColumnCacheKey && !cachedColumnsByTable.has(qualifiedColumnCacheKey)) {
const target = completionMetadataTarget(qualifiedColumnTarget);
if (target) {
void connectionStore
@@ -1410,19 +1431,21 @@ function scheduleCompletionMetadataRefresh(completionContext: ReturnType<typeof
.catch(() => {});
}
}
for (const refTable of completionContext.referencedTables) {
if (refTable.columns && refTable.columns.length > 0) continue;
const cacheKey = refTable.schema ? `${refTable.schema}.${refTable.name}` : refTable.name;
if (cacheKey === qualifiedColumnCacheKey) continue;
if (cachedColumnsByTable.has(cacheKey)) continue;
const target = completionMetadataTarget(refTable);
if (!target) continue;
void connectionStore
.refreshCompletionColumns(connectionId, target.database, refTable.name, target.schema)
.then((columns) => {
if (columns.length > 0) cachedColumnsByTable.set(cacheKey, columns);
})
.catch(() => {});
if (!onDemandOnlyColumns) {
for (const refTable of completionContext.referencedTables) {
if (refTable.columns && refTable.columns.length > 0) continue;
const cacheKey = refTable.schema ? `${refTable.schema}.${refTable.name}` : refTable.name;
if (cacheKey === qualifiedColumnCacheKey) continue;
if (cachedColumnsByTable.has(cacheKey)) continue;
const target = completionMetadataTarget(refTable);
if (!target) continue;
void connectionStore
.refreshCompletionColumns(connectionId, target.database, refTable.name, target.schema)
.then((columns) => {
if (columns.length > 0) cachedColumnsByTable.set(cacheKey, columns);
})
.catch(() => {});
}
}
if ((completionContext.suggestTables || completionContext.suggestJoinConditions) && completionContext.referencedTables.length > 0) {
void ensureForeignKeysForTables(completionContext.referencedTables);
@@ -1442,6 +1465,8 @@ function mergeCompletionTables(existing: Array<{ name: string; schema?: string;
}
async function performAsyncCompletionWithResult(epoch: number, completionContext: ReturnType<typeof getSqlCompletionContext>, fullDoc: string, position: number) {
const localOnlyMetadata = usesLocalOnlyCompletionMetadata();
const onDemandOnlyColumns = usesOnDemandOnlyCompletionColumns();
// Handle INSERT column list: fetch columns for the target table
let insertColumnsByTable = new Map<string, SqlCompletionColumn[]>();
if (completionContext.insertTable) {
@@ -1460,7 +1485,7 @@ async function performAsyncCompletionWithResult(epoch: number, completionContext
const shouldLoadTables = completionContext.suggestTables || (!!completionContext.qualifier && !isReferencedTableQualifier(completionContext));
let databaseNames: string[] = [];
if (supportsDatabaseQualifierCompletion() && completionContext.suggestTables && !completionContext.insertTable) {
if (!localOnlyMetadata && supportsDatabaseQualifierCompletion() && completionContext.suggestTables && !completionContext.insertTable) {
try {
databaseNames = await connectionStore.listCompletionDatabases(props.connectionId!);
if (epoch !== completionEpoch) return null;
@@ -1472,14 +1497,22 @@ async function performAsyncCompletionWithResult(epoch: number, completionContext
const tableLookupDatabase = qualifierDatabase ?? props.database!;
const tableLookupSchema = qualifierDatabase ? undefined : props.schema;
const tableLookupFilter = completionContext.qualifier && !qualifierDatabase ? completionContext.qualifier : completionContext.prefix;
let tables = shouldLoadTables ? await connectionStore.listCompletionTables(props.connectionId!, tableLookupDatabase, tableLookupFilter, MAX_COMPLETION_TABLES, tableLookupSchema) : cachedTables;
let tables = shouldLoadTables
? localOnlyMetadata
? connectionStore.lookupLocalCompletionTables(props.connectionId!, tableLookupDatabase, tableLookupFilter, MAX_COMPLETION_TABLES, tableLookupSchema)
: await connectionStore.listCompletionTables(props.connectionId!, tableLookupDatabase, tableLookupFilter, MAX_COMPLETION_TABLES, tableLookupSchema)
: cachedTables;
if (epoch !== completionEpoch) return null;
const shouldLoadObjects = completionContext.suggestRoutines || completionContext.exclusiveRoutineSuggestions || (!!completionContext.qualifier && !completionContext.exclusiveColumnSuggestions);
let completionObjects = shouldLoadObjects ? await connectionStore.listCompletionObjects(props.connectionId!, props.database!, completionContext.qualifier || completionContext.prefix, MAX_COMPLETION_TABLES, props.schema) : cachedCompletionObjects;
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;
if (epoch !== completionEpoch) return null;
if (completionContext.qualifier && completionObjects.length === 0) {
if (!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;
@@ -1491,19 +1524,25 @@ async function performAsyncCompletionWithResult(epoch: number, completionContext
// Fetch schemas for schema completion
let schemaNames: string[] = [];
if (completionContext.suggestTables && !completionContext.qualifier && !completionContext.insertTable) {
try {
const schemas = await connectionStore.listCompletionSchemas(props.connectionId!, props.database!);
schemaNames = mergeCompletionQualifierNames(schemas, databaseNames);
if (epoch !== completionEpoch) return null;
} catch {
schemaNames = databaseNames;
if (localOnlyMetadata) {
schemaNames = mergeCompletionQualifierNames(connectionStore.lookupLocalCompletionSchemas(props.connectionId!, props.database!, completionContext.prefix, MAX_COMPLETION_TABLES), databaseNames);
} else {
try {
const schemas = await connectionStore.listCompletionSchemas(props.connectionId!, props.database!);
schemaNames = mergeCompletionQualifierNames(schemas, databaseNames);
if (epoch !== completionEpoch) return null;
} catch {
schemaNames = databaseNames;
}
}
}
// If qualifier didn't match any table names, try it as a schema name
let qualifierIsSchema = false;
if (completionContext.qualifier && !qualifierDatabase && !isReferencedTableQualifier(completionContext) && tables.length === 0 && (completionContext.suggestTables || completionContext.exclusiveColumnSuggestions)) {
const schemaTables = await connectionStore.listCompletionTables(props.connectionId!, props.database!, completionContext.prefix, MAX_COMPLETION_TABLES, completionContext.qualifier);
const schemaTables = localOnlyMetadata
? connectionStore.lookupLocalCompletionTables(props.connectionId!, props.database!, completionContext.prefix, MAX_COMPLETION_TABLES, completionContext.qualifier)
: await connectionStore.listCompletionTables(props.connectionId!, props.database!, completionContext.prefix, MAX_COMPLETION_TABLES, completionContext.qualifier);
if (schemaTables.length > 0) {
tables = schemaTables;
qualifierIsSchema = true;
@@ -1522,7 +1561,7 @@ async function performAsyncCompletionWithResult(epoch: number, completionContext
return rt;
});
const unresolvedRefs = refs.filter((rt) => !rt.schema && !rt.columns);
if (unresolvedRefs.length > 0) {
if (!localOnlyMetadata && unresolvedRefs.length > 0) {
const lookupGroups = await Promise.all(unresolvedRefs.map((rt) => connectionStore.listCompletionTables(props.connectionId!, props.database!, rt.name, 20, props.schema)));
if (epoch !== completionEpoch) return null;
const lookupTables = lookupGroups.flat();
@@ -1555,23 +1594,26 @@ async function performAsyncCompletionWithResult(epoch: number, completionContext
}
}
await Promise.all(
refs.map(async (refTable) => {
if (refTable.columns && refTable.columns.length > 0) return;
const cacheKey = refTable.schema ? `${refTable.schema}.${refTable.name}` : refTable.name;
if (cachedColumnsByTable.has(cacheKey)) return;
try {
const target = completionMetadataTarget(refTable);
if (!target) return;
const columns = await connectionStore.listCompletionColumns(props.connectionId!, target.database, refTable.name, target.schema);
if (epoch !== completionEpoch) return;
if (columns.length === 0) return;
cachedColumnsByTable.set(cacheKey, columns);
} catch (e) {
console.error(`[DBX] Failed to load columns for ${cacheKey}:`, e);
}
}),
);
const shouldFetchColumnsForCompletion = !onDemandOnlyColumns || completionContext.suggestColumns || completionContext.exclusiveColumnSuggestions || !!completionContext.insertTable;
if (shouldFetchColumnsForCompletion) {
await Promise.all(
refs.map(async (refTable) => {
if (refTable.columns && refTable.columns.length > 0) return;
const cacheKey = refTable.schema ? `${refTable.schema}.${refTable.name}` : refTable.name;
if (cachedColumnsByTable.has(cacheKey)) return;
try {
const target = completionMetadataTarget(refTable);
if (!target) return;
const columns = await connectionStore.listCompletionColumns(props.connectionId!, target.database, refTable.name, target.schema);
if (epoch !== completionEpoch) return;
if (columns.length === 0) return;
cachedColumnsByTable.set(cacheKey, columns);
} catch (e) {
console.error(`[DBX] Failed to load columns for ${cacheKey}:`, e);
}
}),
);
}
if (epoch !== completionEpoch) return null;
if ((completionContext.suggestTables || completionContext.suggestJoinConditions) && refs.length > 0) {
@@ -2004,7 +2046,9 @@ onMounted(async () => {
try {
// Ensure table cache is populated
if (cachedTables.length === 0) {
cachedTables = await connectionStore.listCompletionTables(props.connectionId!, props.database!, identifier, MAX_COMPLETION_TABLES, props.schema);
cachedTables = usesLocalOnlyCompletionMetadata()
? connectionStore.lookupLocalCompletionTables(props.connectionId!, props.database!, identifier, MAX_COMPLETION_TABLES, props.schema)
: await connectionStore.listCompletionTables(props.connectionId!, props.database!, identifier, MAX_COMPLETION_TABLES, props.schema);
}
// 1. Check if it's a table name
@@ -44,10 +44,11 @@ const assetIcons: Record<string, string> = {
dm: "dm",
dameng: "dm",
presto: "presto",
prestosql: "presto",
hive: "hive",
apache_kylin: "apache_kylin",
sundb: "sundb",
trino: "presto",
trino: "trino",
kylin: "apache_kylin",
cockroachdb: "cockroachdb",
db2: "db2",