feat: add PrestoSQL connector
This commit is contained in:
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",
|
||||
|
||||
Reference in new issue
Block a user