feat(dolt): improve system tables and SQL completion
This commit is contained in:
1 parent
c5eea21821
commit
a574459f8b
30 files changed
+789
-70
No files matched your search
@@ -48,6 +48,7 @@ import { mongodbAuthFailureHint, mongoUrlParam, mongoUrlParamIsTrue, normalizeMo
|
||||
import { isMongoLegacyDriverProfile } from "@/lib/mongo/mongoCapabilities";
|
||||
import { mysqlCleartextPasswordAuthEnabled, setMysqlCleartextPasswordAuthEnabled } from "@/lib/database/mysqlConnectionOptions";
|
||||
import { applyDamengSslUrlParams, damengSslFormConfig } from "@/lib/database/damengSslOptions";
|
||||
import { doltSystemTablesVisible, isDoltDriverProfile, setDoltSystemTablesVisible } from "@/lib/database/doltProfile";
|
||||
import { DamengJvmSystemPropertyError, damengJvmSystemPropertiesText, parseDamengJvmSystemProperties } from "@/lib/database/damengJvmOptions";
|
||||
import { copyToClipboard } from "@/lib/common/clipboard";
|
||||
import { configuredDatabaseProductName, connectionConfigFingerprint, databaseInfoCopyText, databaseInfoRows, normalizeDatabaseConnectionInfo, type DatabaseInfoField } from "@/lib/connection/connectionDatabaseInfo";
|
||||
@@ -3065,6 +3066,11 @@ const showGenericUrlParamsHint = computed(() => form.value.db_type === "mysql" |
|
||||
const bareMysqlProfiles = new Set(["doris", "selectdb", "oceanbase"]);
|
||||
const supportsMysqlTlsOptions = computed(() => form.value.db_type === "starrocks" || (form.value.db_type === "mysql" && !bareMysqlProfiles.has(selectedType.value)));
|
||||
const supportsMysqlCleartextPasswordAuth = computed(() => form.value.db_type === "mysql" && !bareMysqlProfiles.has(selectedType.value));
|
||||
const supportsDoltSystemTables = computed(() => isDoltDriverProfile(form.value.driver_profile));
|
||||
const showDoltSystemTables = computed({
|
||||
get: () => doltSystemTablesVisible(form.value),
|
||||
set: (visible: boolean) => setDoltSystemTablesVisible(form.value, visible),
|
||||
});
|
||||
const mysqlCleartextPasswordAuth = computed({
|
||||
get: () => mysqlCleartextPasswordAuthEnabled(form.value.url_params),
|
||||
set: (value: boolean) => {
|
||||
@@ -3862,7 +3868,7 @@ function connectionConfigForSubmit(id: string, generatedName = ""): ConnectionCo
|
||||
const style = gaussdbIdentifierQuoteStyle(config);
|
||||
config.external_config = undefined;
|
||||
setGaussdbIdentifierQuoteStyle(config, style);
|
||||
} else {
|
||||
} else if (!isDoltDriverProfile(config.driver_profile)) {
|
||||
config.external_config = undefined;
|
||||
}
|
||||
if (config.db_type === "mongodb" && !mongoUseUrl.value) {
|
||||
@@ -7869,6 +7875,13 @@ function openExternalUrl(url: string) {
|
||||
<Input v-model.number="form.keepalive_interval_secs" type="number" min="1" max="3600" step="1" class="flex-1" :disabled="!keepaliveEnabled" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="supportsDoltSystemTables" class="grid grid-cols-4 items-center gap-4">
|
||||
<Label :class="connectionLabelSmallClass">{{ t("connection.doltShowSystemTables") }}</Label>
|
||||
<label class="col-span-3 flex items-center gap-2 cursor-pointer">
|
||||
<input v-model="showDoltSystemTables" type="checkbox" class="mr-0" />
|
||||
<span class="text-xs text-muted-foreground">{{ t("connection.doltShowSystemTablesHint") }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 items-center gap-4">
|
||||
<Label :class="connectionLabelSmallClass">{{ t("connection.readOnly") }}</Label>
|
||||
<label class="col-span-3 flex items-center gap-2 cursor-pointer">
|
||||
|
||||
@@ -50,6 +50,7 @@ import {
|
||||
extractCteDefinitions,
|
||||
} from "@/lib/sql/sqlCompletion";
|
||||
import { originForSqlCompletionProvider, originForTypedSqlCompletionStart, shouldAllowSqlCompletionTrigger, type SqlCompletionTriggerFacts, type SqlCompletionTriggerOrigin } from "@/lib/sql/sqlCompletionTriggerPolicy";
|
||||
import { driverProfileHasCompletionCandidates } from "@/lib/database/driverProfileExtensions";
|
||||
import { sqlCompletionContextFromSemantic, sqlSemanticSelectStarIsOnlyProjection, sqlSemanticSelectStarQualifierSql, sqlSemanticSelectStarTableSources } from "@/lib/sql/semantic/completion";
|
||||
import { buildSqlSemanticModel } from "@/lib/sql/semantic/model";
|
||||
import { mergeSqlSemanticReferenceAnalysis, resolveSqlSemanticNavigationTarget } from "@/lib/sql/semantic/references";
|
||||
@@ -3765,7 +3766,14 @@ function buildLocalSqlCompletionResult(completionContext: ReturnType<typeof getS
|
||||
}
|
||||
}
|
||||
|
||||
if (tables.length === 0 && completionObjects.length === 0 && schemaNames.length === 0 && columnsByTable.size === 0 && (completionContext.exclusiveTableSuggestions || completionContext.exclusiveColumnSuggestions || completionContext.exclusiveRoutineSuggestions)) {
|
||||
if (
|
||||
tables.length === 0 &&
|
||||
completionObjects.length === 0 &&
|
||||
schemaNames.length === 0 &&
|
||||
columnsByTable.size === 0 &&
|
||||
!driverProfileHasCompletionCandidates(sqlDriverProfile.value, completionContext) &&
|
||||
(completionContext.exclusiveTableSuggestions || completionContext.exclusiveColumnSuggestions || completionContext.exclusiveRoutineSuggestions)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -292,7 +292,7 @@ watch([deferredSearchQuery, regexMode], ([newQuery, isRegexMode], [oldQuery, was
|
||||
.catch(() => {});
|
||||
});
|
||||
|
||||
const searchableObjectGroupTypes = new Set<TreeNodeType>(["group-tables", "group-views", "group-materialized-views", "group-procedures", "group-functions", "group-triggers", "group-sequences", "group-synonyms", "group-packages", "group-types"]);
|
||||
const searchableObjectGroupTypes = new Set<TreeNodeType>(["group-tables", "group-dolt-system-tables", "group-views", "group-materialized-views", "group-procedures", "group-functions", "group-triggers", "group-sequences", "group-synonyms", "group-packages", "group-types"]);
|
||||
const simpleObjectParentTypes = new Set<TreeNodeType>(["database", "schema", "linked-server-schema"]);
|
||||
const simpleObjectChildTypes = new Set<TreeNodeType>(["table", "view", "materialized_view", "procedure", "function", "trigger", "sequence", "synonym", "package", "package-body", "type", "type-body", "load-more"]);
|
||||
|
||||
@@ -1392,7 +1392,12 @@ async function ensureTableObjectGroupsLoaded(target: Extract<ActiveTabSidebarTar
|
||||
function findTableObjectGroupNodes(nodes: TreeNode[], target: Extract<ActiveTabSidebarTarget, { type: "table" }>): TreeNode[] {
|
||||
const matches: TreeNode[] = [];
|
||||
for (const node of nodes) {
|
||||
if ((node.type === "group-tables" || node.type === "group-views" || node.type === "group-materialized-views") && node.connectionId === target.connectionId && sameTreeName(node.database, target.database) && (!target.schema || sameTreeName(node.schema, target.schema))) {
|
||||
if (
|
||||
(node.type === "group-tables" || node.type === "group-dolt-system-tables" || node.type === "group-views" || node.type === "group-materialized-views") &&
|
||||
node.connectionId === target.connectionId &&
|
||||
sameTreeName(node.database, target.database) &&
|
||||
(!target.schema || sameTreeName(node.schema, target.schema))
|
||||
) {
|
||||
matches.push(node);
|
||||
}
|
||||
if (node.children) {
|
||||
|
||||
@@ -5219,7 +5219,7 @@ function buildObjectGroupSidebarMenu(context: SidebarMenuFactoryContext): boolea
|
||||
const hasMongoCreateIndexAction = node.type === "group-indexes" && canCreateMongoIndex.value;
|
||||
const hasMongoDropAllIndexesAction = node.type === "group-indexes" && canDropAllMongoIndexes.value;
|
||||
const hasGroupAction = (node.type === "group-tables" && canCreateTable.value) || (node.type === "group-views" && !!node.connectionId && !!node.database) || !!mysqlObjectTemplate || hasMongoCreateIndexAction || hasMongoDropAllIndexesAction;
|
||||
const canLoadAllObjectGroup = node.type === "group-tables" || node.type === "group-views" || node.type === "group-materialized-views";
|
||||
const canLoadAllObjectGroup = node.type === "group-tables" || node.type === "group-dolt-system-tables" || node.type === "group-views" || node.type === "group-materialized-views";
|
||||
if (node.type === "group-tables" && canCreateTable.value) {
|
||||
items.push({ label: t("contextMenu.createTable"), action: createTable, icon: Plus });
|
||||
if (canOpenTableImport.value) {
|
||||
|
||||
@@ -326,6 +326,8 @@ function getIconInfo(node: TreeNode): { icon: any; colorClass: string } | null {
|
||||
return { icon: Columns3, colorClass: "text-muted-foreground" };
|
||||
case "group-tables":
|
||||
return { icon: Table, colorClass: "text-green-500" };
|
||||
case "group-dolt-system-tables":
|
||||
return { icon: Table, colorClass: "text-slate-500" };
|
||||
case "group-views":
|
||||
return { icon: Eye, colorClass: "text-purple-500" };
|
||||
case "group-materialized-views":
|
||||
@@ -1323,6 +1325,7 @@ function onKeydown(event: KeyboardEvent) {
|
||||
<span
|
||||
v-if="
|
||||
(node.type === 'group-tables' ||
|
||||
node.type === 'group-dolt-system-tables' ||
|
||||
node.type === 'group-views' ||
|
||||
node.type === 'group-materialized-views' ||
|
||||
node.type === 'group-procedures' ||
|
||||
|
||||
@@ -1051,6 +1051,8 @@ export default {
|
||||
keepaliveInterval: "Keepalive Interval (seconds)",
|
||||
readOnly: "Read Only",
|
||||
readOnlyHint: "Block all write operations (INSERT, UPDATE, DELETE, etc.)",
|
||||
doltShowSystemTables: "Show Dolt System Tables",
|
||||
doltShowSystemTablesHint: "Show Dolt's built-in version-control tables in table lists for this connection.",
|
||||
showSystemSchemas: "Show System Schemas",
|
||||
showSystemSchemasHint: "Show built-in and metadata schemas in the sidebar and schema pickers for this connection.",
|
||||
docsNotesPath: "Notes file",
|
||||
@@ -2972,6 +2974,7 @@ export default {
|
||||
triggers: "Triggers",
|
||||
constraints: "Constraints",
|
||||
tables: "Tables",
|
||||
doltSystemTables: "Dolt System Tables",
|
||||
tableNameFilterActive: "filtered",
|
||||
views: "Views",
|
||||
materializedViews: "Materialized Views",
|
||||
|
||||
@@ -1111,6 +1111,8 @@ export default withEnglishFallback({
|
||||
mqttClientCertPath: "Ruta del certificado de cliente",
|
||||
mqttClientKeyPath: "Ruta de la clave privada del cliente",
|
||||
mqttNoLocal: "Prohibir reenvío local",
|
||||
doltShowSystemTables: "Mostrar tablas integradas de Dolt",
|
||||
doltShowSystemTablesHint: "Muestra las tablas integradas de control de versiones de Dolt en la lista de tablas de la conexión actual.",
|
||||
},
|
||||
editor: {
|
||||
duckdbDraining: "La consulta anterior de DuckDB aún se está deteniendo. Vuelva a intentarlo en breve.",
|
||||
@@ -2917,6 +2919,7 @@ export default withEnglishFallback({
|
||||
types: "tipo",
|
||||
gridfs: "GridFS",
|
||||
buckets: "Buckets",
|
||||
doltSystemTables: "Tablas del sistema de Dolt",
|
||||
},
|
||||
userAdmin: {
|
||||
title: "Usuarios y Privilegios",
|
||||
|
||||
@@ -1109,6 +1109,8 @@ export default withEnglishFallback({
|
||||
mqttClientCertPath: "Percorso del certificato client",
|
||||
mqttClientKeyPath: "Percorso della chiave privata client",
|
||||
mqttNoLocal: "Disabilita inoltro locale",
|
||||
doltShowSystemTables: "Mostra le tabelle integrate di Dolt",
|
||||
doltShowSystemTablesHint: "Mostra le tabelle di controllo versione integrate di Dolt nell'elenco delle tabelle della connessione corrente.",
|
||||
},
|
||||
editor: {
|
||||
duckdbDraining: "La query DuckDB precedente è ancora in fase di arresto. Riprova a breve.",
|
||||
@@ -2915,6 +2917,7 @@ export default withEnglishFallback({
|
||||
types: "Tipi",
|
||||
gridfs: "GridFS",
|
||||
buckets: "Buckets",
|
||||
doltSystemTables: "Tabelle di sistema di Dolt",
|
||||
},
|
||||
userAdmin: {
|
||||
title: "Utenti e Privilegi",
|
||||
|
||||
@@ -1130,6 +1130,8 @@ export default withEnglishFallback({
|
||||
mqttClientCertPath: "クライアント証明書パス",
|
||||
mqttClientKeyPath: "クライアント秘密鍵パス",
|
||||
mqttNoLocal: "ローカル転送禁止",
|
||||
doltShowSystemTables: "Dolt 組み込みテーブルを表示",
|
||||
doltShowSystemTablesHint: "現在の接続のテーブルリストに Dolt 組み込みのバージョン管理テーブルを表示します。",
|
||||
},
|
||||
editor: {
|
||||
duckdbDraining: "前回の DuckDB クエリはまだ停止処理中です。しばらくしてから再試行してください。",
|
||||
@@ -2942,6 +2944,7 @@ export default withEnglishFallback({
|
||||
types: "タイプ",
|
||||
gridfs: "GridFS",
|
||||
buckets: "バケット",
|
||||
doltSystemTables: "Dolt システムテーブル",
|
||||
},
|
||||
consul,
|
||||
sqlServerTrace,
|
||||
|
||||
@@ -1110,6 +1110,8 @@ export default withEnglishFallback({
|
||||
mqttClientCertPath: "Caminho do certificado do cliente",
|
||||
mqttClientKeyPath: "Caminho da chave privada do cliente",
|
||||
mqttNoLocal: "Proibir encaminhamento local",
|
||||
doltShowSystemTables: "Exibir tabelas internas do Dolt",
|
||||
doltShowSystemTablesHint: "Exibir as tabelas de controle de versão internas do Dolt na lista de tabelas da conexão atual.",
|
||||
},
|
||||
editor: {
|
||||
duckdbDraining: "A consulta anterior do DuckDB ainda está sendo interrompida. Tente novamente em breve.",
|
||||
@@ -2917,6 +2919,7 @@ export default withEnglishFallback({
|
||||
types: "Tipos",
|
||||
gridfs: "GridFS",
|
||||
buckets: "Buckets",
|
||||
doltSystemTables: "Tabelas de sistema do Dolt",
|
||||
},
|
||||
userAdmin: {
|
||||
title: "Usuários e Privilégios",
|
||||
|
||||
@@ -978,6 +978,8 @@ export default withEnglishFallback({
|
||||
keepaliveInterval: "保持连接间隔(秒)",
|
||||
readOnly: "只读模式",
|
||||
readOnlyHint: "阻止所有写操作(INSERT、UPDATE、DELETE 等)",
|
||||
doltShowSystemTables: "显示 Dolt 内置表",
|
||||
doltShowSystemTablesHint: "在当前连接的表列表中显示 Dolt 内置的版本控制表。",
|
||||
showSystemSchemas: "显示系统 Schema",
|
||||
showSystemSchemasHint: "为当前连接在侧边栏和 Schema 选择器中显示内置/元数据 Schema。",
|
||||
docsNotesPath: "笔记文件",
|
||||
@@ -2896,6 +2898,7 @@ export default withEnglishFallback({
|
||||
triggers: "触发器",
|
||||
constraints: "约束",
|
||||
tables: "表",
|
||||
doltSystemTables: "Dolt 系统表",
|
||||
tableNameFilterActive: "存在过滤",
|
||||
views: "视图",
|
||||
materializedViews: "物化视图",
|
||||
|
||||
@@ -1007,6 +1007,8 @@ export default withEnglishFallback({
|
||||
rocketmqSecretKey: "Secret Key",
|
||||
rocketmqAclAuth: "ACL",
|
||||
jdbcMissingRuntimeDependencyHint: "目前 JDBC 驅動缺少執行依賴。請在「驅動管理」中使用 Maven 座標安裝,或一次匯入驅動及全部依賴 JAR。",
|
||||
doltShowSystemTables: "顯示 Dolt 內建資料表",
|
||||
doltShowSystemTablesHint: "在目前連線的資料表清單中顯示 Dolt 內建的版本控制資料表。",
|
||||
showSystemSchemas: "顯示系統 Schema",
|
||||
showSystemSchemasHint: "為目前連線在側邊欄和 Schema 選擇器中顯示內建/中繼資料 Schema。",
|
||||
docsNotesPath: "筆記檔案",
|
||||
@@ -2900,6 +2902,7 @@ export default withEnglishFallback({
|
||||
triggers: "觸發器",
|
||||
constraints: "約束",
|
||||
tables: "資料表",
|
||||
doltSystemTables: "Dolt 系統資料表",
|
||||
tableNameFilterActive: "存在篩選",
|
||||
views: "檢視",
|
||||
procedures: "預存程序",
|
||||
|
||||
+111
-1
@@ -1,5 +1,66 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { DOLT_SQL_ROUTINES, doltSqlBuiltinTerms, doltSqlRoutineSignatures, isDoltDriverProfile } from "@/lib/database/doltProfile";
|
||||
import { DOLT_SQL_ROUTINES, doltObjectTreeProfileForConnection, doltSqlBuiltinTerms, doltSqlRoutineSignatures, doltSystemTablesVisible, isDoltDriverProfile, setDoltSystemTablesVisible } from "@/lib/database/doltProfile";
|
||||
import type { ConnectionConfig } from "@/types/database";
|
||||
|
||||
function doltConfig(showDoltSystemTables = false): ConnectionConfig {
|
||||
return {
|
||||
id: "dolt-1",
|
||||
name: "Dolt",
|
||||
db_type: "mysql",
|
||||
driver_profile: "dolt",
|
||||
host: "127.0.0.1",
|
||||
port: 3306,
|
||||
username: "root",
|
||||
password: "",
|
||||
database: "app",
|
||||
external_config: showDoltSystemTables ? { doltShowSystemTables: true } : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
const documentedDoltProcedures = [
|
||||
"DOLT_ADD",
|
||||
"DOLT_BACKUP",
|
||||
"DOLT_BRANCH",
|
||||
"DOLT_CHECKOUT",
|
||||
"DOLT_CHERRY_PICK",
|
||||
"DOLT_CLEAN",
|
||||
"DOLT_CLONE",
|
||||
"DOLT_COMMIT",
|
||||
"DOLT_COMMIT_HASH_OUT",
|
||||
"DOLT_CONFLICTS_RESOLVE",
|
||||
"DOLT_COUNT_COMMITS",
|
||||
"DOLT_FETCH",
|
||||
"DOLT_GC",
|
||||
"DOLT_MERGE",
|
||||
"DOLT_PULL",
|
||||
"DOLT_PURGE_DROPPED_DATABASES",
|
||||
"DOLT_PUSH",
|
||||
"DOLT_REBASE",
|
||||
"DOLT_REMOTE",
|
||||
"DOLT_RESET",
|
||||
"DOLT_REVERT",
|
||||
"DOLT_RM",
|
||||
"DOLT_SQUASH_HISTORY",
|
||||
"DOLT_STASH",
|
||||
"DOLT_TAG",
|
||||
"DOLT_THREAD_DUMP",
|
||||
"DOLT_UNDROP",
|
||||
"DOLT_UPDATE_COLUMN_TAG",
|
||||
"DOLT_VERIFY_CONSTRAINTS",
|
||||
"DOLT_STATS_RESTART",
|
||||
"DOLT_STATS_STOP",
|
||||
"DOLT_STATS_PURGE",
|
||||
"DOLT_STATS_ONCE",
|
||||
"DOLT_STATS_WAIT",
|
||||
"DOLT_STATS_FLUSH",
|
||||
"DOLT_STATS_GC",
|
||||
"DOLT_STATS_INFO",
|
||||
"DOLT_STATS_TIMERS",
|
||||
];
|
||||
|
||||
const documentedDoltScalarFunctions = ["ACTIVE_BRANCH", "DOLT_MERGE_BASE", "DOLT_HASHOF", "DOLT_HASHOF_DB", "DOLT_HASHOF_TABLE", "DOLT_STORAGE_FORMAT", "DOLT_VERSION", "HAS_ANCESTOR", "DOLT_JOIN_COST"];
|
||||
|
||||
const documentedDoltTableFunctions = ["DOLT_DIFF", "DOLT_DIFF_STAT", "DOLT_DIFF_SUMMARY", "DOLT_JSON_DIFF", "DOLT_LOG", "DOLT_PATCH", "DOLT_PREVIEW_MERGE_CONFLICTS_SUMMARY", "DOLT_PREVIEW_MERGE_CONFLICTS", "DOLT_REFLOG", "DOLT_SCHEMA_DIFF", "DOLT_QUERY_DIFF", "DOLT_BRANCH_STATUS", "DOLT_TEST_RUN"];
|
||||
|
||||
describe("doltProfile", () => {
|
||||
it("matches only the dedicated Dolt driver profile", () => {
|
||||
@@ -21,4 +82,53 @@ describe("doltProfile", () => {
|
||||
expect(signatures.get("DOLT_MERGE_BASE")).toEqual(["revision_a", "revision_b"]);
|
||||
expect(signatures.size).toBe(DOLT_SQL_ROUTINES.length);
|
||||
});
|
||||
|
||||
it("keeps the documented Dolt routine catalog separated by SQL usage", () => {
|
||||
expect(DOLT_SQL_ROUTINES.filter((routine) => routine.type === "procedure").map((routine) => routine.name)).toEqual(documentedDoltProcedures);
|
||||
expect(DOLT_SQL_ROUTINES.filter((routine) => routine.type === "scalar-function").map((routine) => routine.name)).toEqual(documentedDoltScalarFunctions);
|
||||
expect(DOLT_SQL_ROUTINES.filter((routine) => routine.type === "table-function").map((routine) => routine.name)).toEqual(documentedDoltTableFunctions);
|
||||
});
|
||||
|
||||
it("stores system-table visibility in the existing profile config slot", () => {
|
||||
const config = { ...doltConfig(), external_config: { retained: true } };
|
||||
setDoltSystemTablesVisible(config, true);
|
||||
expect(config.external_config).toEqual({ retained: true, doltShowSystemTables: true });
|
||||
expect(doltSystemTablesVisible(config)).toBe(true);
|
||||
|
||||
setDoltSystemTablesVisible(config, false);
|
||||
expect(config.external_config).toEqual({ retained: true });
|
||||
expect(doltSystemTablesVisible(config)).toBe(false);
|
||||
});
|
||||
|
||||
it("defines independent table ranges while Dolt system tables are visible", () => {
|
||||
expect(doltObjectTreeProfileForConnection(doltConfig(true))).toEqual({
|
||||
cacheKey: "dolt-system-tables-v1:shown",
|
||||
groupOverrides: [
|
||||
{
|
||||
nodeType: "group-tables",
|
||||
tableNameFilter: { includePatterns: [], excludePatterns: ["dolt%"] },
|
||||
},
|
||||
{
|
||||
nodeType: "group-dolt-system-tables",
|
||||
label: "tree.doltSystemTables",
|
||||
tableNameFilter: { includePatterns: ["dolt%"], excludePatterns: [] },
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
it("uses a separate hidden cache scope without changing ordinary MySQL", () => {
|
||||
expect(doltObjectTreeProfileForConnection(doltConfig())).toEqual({ cacheKey: "dolt-system-tables-v1:hidden", groupOverrides: [] });
|
||||
expect(doltObjectTreeProfileForConnection({ ...doltConfig(), driver_profile: "mysql" })).toBeUndefined();
|
||||
});
|
||||
|
||||
it("tracks the current Dolt procedure and scalar-function registries", () => {
|
||||
const routines = new Map(DOLT_SQL_ROUTINES.map((routine) => [routine.name, routine.type]));
|
||||
|
||||
expect(routines.get("DOLT_COUNT_COMMITS")).toBe("procedure");
|
||||
expect(routines.get("DOLT_THREAD_DUMP")).toBe("procedure");
|
||||
expect(routines.get("DOLT_STATS_TIMERS")).toBe("procedure");
|
||||
expect(routines.get("DOLT_STORAGE_FORMAT")).toBe("scalar-function");
|
||||
expect(routines.has("LAST_INSERT_UUID")).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,45 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { driverProfileCompletionObjects, driverProfileCompletionTableMetadata, driverProfileCompletionTables, driverProfileExtension, driverProfileObjectTreeProfileForConnection } from "@/lib/database/driverProfileExtensions";
|
||||
import type { ConnectionConfig } from "@/types/database";
|
||||
|
||||
const completionContext = {
|
||||
statementKind: "select" as const,
|
||||
suggestTables: false,
|
||||
suggestRoutines: true,
|
||||
exclusiveRoutineSuggestions: false,
|
||||
prefix: "DOLT_",
|
||||
openingParenAfterCursor: false,
|
||||
};
|
||||
|
||||
function connection(driverProfile: string): ConnectionConfig {
|
||||
return {
|
||||
id: driverProfile,
|
||||
name: driverProfile,
|
||||
db_type: "mysql",
|
||||
driver_profile: driverProfile,
|
||||
host: "127.0.0.1",
|
||||
port: 3306,
|
||||
username: "root",
|
||||
password: "",
|
||||
database: "app",
|
||||
external_config: { doltShowSystemTables: true },
|
||||
};
|
||||
}
|
||||
|
||||
describe("driverProfileExtensions", () => {
|
||||
it("resolves Dolt capabilities through the generic registry", () => {
|
||||
expect(driverProfileExtension("DOLT")?.id).toBe("dolt");
|
||||
expect(driverProfileObjectTreeProfileForConnection(connection("dolt"))?.groupOverrides.map((group) => group.nodeType)).toEqual(["group-tables", "group-dolt-system-tables"]);
|
||||
expect(driverProfileCompletionObjects("dolt", completionContext).map((object) => object.name)).toContain("DOLT_HASHOF");
|
||||
expect(driverProfileCompletionTables("dolt", { ...completionContext, suggestTables: true }).map((table) => table.name)).toContain("DOLT_LOG");
|
||||
expect(driverProfileCompletionTableMetadata("dolt", "dolt_log")).toEqual({ detail: "Dolt system table", boost: -1200 });
|
||||
});
|
||||
|
||||
it("leaves ordinary MySQL without profile capabilities", () => {
|
||||
expect(driverProfileExtension("mysql")).toBeUndefined();
|
||||
expect(driverProfileObjectTreeProfileForConnection(connection("mysql"))).toBeUndefined();
|
||||
expect(driverProfileCompletionObjects("mysql", completionContext)).toEqual([]);
|
||||
expect(driverProfileCompletionTables("mysql", { ...completionContext, suggestTables: true })).toEqual([]);
|
||||
expect(driverProfileCompletionTableMetadata("mysql", "dolt_log")).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -1,24 +1,73 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { buildSqlCompletionItems } from "@/lib/sql/sqlCompletion";
|
||||
import { buildSqlCompletionItems, shouldAutoOpenSqlCompletion, type SqlCompletionObject, type SqlCompletionTable } from "@/lib/sql/sqlCompletion";
|
||||
|
||||
function completionLabels(sql: string, driverProfile: string): string[] {
|
||||
function completionItems(sql: string, driverProfile: string, tables: SqlCompletionTable[] = [], objects: SqlCompletionObject[] = []) {
|
||||
return buildSqlCompletionItems(sql, sql.length, {
|
||||
tables: [],
|
||||
objects: [],
|
||||
tables,
|
||||
objects,
|
||||
columnsByTable: new Map(),
|
||||
databaseType: "mysql",
|
||||
driverProfile,
|
||||
}).map((item) => item.label);
|
||||
});
|
||||
}
|
||||
|
||||
function completionLabels(sql: string, driverProfile: string): string[] {
|
||||
return completionItems(sql, driverProfile).map((item) => item.label);
|
||||
}
|
||||
|
||||
describe("Dolt SQL completion", () => {
|
||||
it("suggests Dolt routines for the Dolt profile", () => {
|
||||
expect(completionLabels("DOLT_BR", "dolt")).toContain("DOLT_BRANCH");
|
||||
expect(completionLabels("CALL DOLT_CH", "dolt")).toContain("DOLT_CHECKOUT");
|
||||
expect(completionLabels("CALL DOLT_STASH", "dolt")).toContain("DOLT_STASH");
|
||||
expect(completionLabels("HAS_ANC", "dolt")).toContain("HAS_ANCESTOR");
|
||||
expect(completionLabels("DOLT_PREVIEW_MERGE_C", "dolt")).toContain("DOLT_PREVIEW_MERGE_CONFLICTS");
|
||||
});
|
||||
|
||||
it("does not expose Dolt routines to standard MySQL", () => {
|
||||
expect(completionLabels("DOLT_BR", "mysql")).not.toContain("DOLT_BRANCH");
|
||||
expect(completionLabels("CALL DOLT_CH", "mysql")).not.toContain("DOLT_CHECKOUT");
|
||||
});
|
||||
|
||||
it("filters Dolt routines by CALL, SELECT, and table contexts", () => {
|
||||
expect(shouldAutoOpenSqlCompletion("CALL ", "CALL ".length, { databaseType: "mysql" })).toBe(true);
|
||||
expect(completionLabels("CALL ", "dolt")).toContain("DOLT_CHECKOUT");
|
||||
|
||||
const callLabels = completionLabels("CALL DOLT_", "dolt");
|
||||
expect(callLabels).toContain("DOLT_CHECKOUT");
|
||||
expect(callLabels).not.toContain("DOLT_HASHOF");
|
||||
expect(callLabels).not.toContain("DOLT_LOG");
|
||||
|
||||
const selectLabels = completionLabels("SELECT DOLT_", "dolt");
|
||||
expect(selectLabels).toContain("DOLT_HASHOF");
|
||||
expect(selectLabels).not.toContain("DOLT_CHECKOUT");
|
||||
expect(selectLabels).not.toContain("DOLT_LOG");
|
||||
|
||||
const fromItems = completionItems("SELECT * FROM DOLT_", "dolt");
|
||||
expect(fromItems.find((item) => item.label === "DOLT_LOG")).toMatchObject({ detail: "Dolt table function", type: "table" });
|
||||
expect(fromItems.map((item) => item.label)).not.toContain("DOLT_CHECKOUT");
|
||||
expect(fromItems.map((item) => item.label)).not.toContain("DOLT_HASHOF");
|
||||
});
|
||||
|
||||
it("marks Dolt system tables and keeps user tables ahead of them", () => {
|
||||
const items = completionItems("SELECT * FROM ", "dolt", [{ name: "dolt_log" }, { name: "orders" }]).filter((item) => item.type === "table");
|
||||
const userTableIndex = items.findIndex((item) => item.label === "orders");
|
||||
const systemTableIndex = items.findIndex((item) => item.label === "dolt_log");
|
||||
|
||||
expect(items[systemTableIndex]).toMatchObject({ detail: "Dolt system table" });
|
||||
expect(userTableIndex).toBeGreaterThanOrEqual(0);
|
||||
expect(systemTableIndex).toBeGreaterThan(userTableIndex);
|
||||
expect(completionItems("SELECT * FROM dolt_", "mysql", [{ name: "dolt_log" }]).find((item) => item.label === "dolt_log")?.detail).not.toBe("Dolt system table");
|
||||
});
|
||||
|
||||
it("does not change package completion for other database families", () => {
|
||||
const labels = buildSqlCompletionItems("SELECT DBMS_", "SELECT DBMS_".length, {
|
||||
tables: [],
|
||||
objects: [{ name: "DBMS_OUTPUT", type: "package" }],
|
||||
columnsByTable: new Map(),
|
||||
databaseType: "oracle",
|
||||
}).map((item) => item.label);
|
||||
|
||||
expect(labels).toContain("DBMS_OUTPUT");
|
||||
});
|
||||
});
|
||||
@@ -34,5 +34,8 @@ describe("ClickHouse signature help", () => {
|
||||
expect(help?.name).toBe("DOLT_MERGE_BASE");
|
||||
expect(help?.parameters).toEqual(["revision_a", "revision_b"]);
|
||||
expect(help?.activeParameter).toBe(1);
|
||||
|
||||
const procedureSql = "CALL DOLT_COMMIT_HASH_OUT(";
|
||||
expect(getSqlFunctionSignatureHelp(procedureSql, procedureSql.length, "mysql", "dolt")?.parameters).toEqual(["out_hash", "arguments"]);
|
||||
});
|
||||
});
|
||||
@@ -1,46 +1,167 @@
|
||||
import type { ConnectionConfig } from "@/types/database";
|
||||
import type { DriverProfileExtensionDefinition, DriverProfileObjectTreeProfile, DriverProfileSqlCompletionContext } from "@/lib/database/driverProfileExtensions";
|
||||
|
||||
export const DOLT_DRIVER_PROFILE = "dolt";
|
||||
const DOLT_SHOW_SYSTEM_TABLES_CONFIG_KEY = "doltShowSystemTables";
|
||||
// Mirrors Dolt's IsSystemTable / HasDoltPrefix implementation. The reserved
|
||||
// namespace comparison is case-insensitive and currently checks the "dolt" prefix.
|
||||
export const DOLT_SYSTEM_TABLE_NAME_PATTERN = "dolt%";
|
||||
|
||||
export type DoltSqlRoutineDefinition = {
|
||||
name: string;
|
||||
type: "procedure" | "function";
|
||||
type: "procedure" | "scalar-function" | "table-function";
|
||||
signature: string;
|
||||
};
|
||||
|
||||
// Keep this list aligned with Dolt's documented SQL procedures and functions:
|
||||
// https://dolthub.com/docs/sql-reference/version-control/sql-extensions
|
||||
export const DOLT_SQL_ROUTINES: readonly DoltSqlRoutineDefinition[] = [
|
||||
{ name: "ACTIVE_BRANCH", type: "function", signature: "" },
|
||||
{ name: "DOLT_ADD", type: "procedure", signature: "arguments" },
|
||||
{ name: "DOLT_BACKUP", type: "procedure", signature: "arguments" },
|
||||
{ name: "DOLT_BRANCH", type: "procedure", signature: "arguments" },
|
||||
{ name: "DOLT_CHECKOUT", type: "procedure", signature: "arguments" },
|
||||
{ name: "DOLT_CHERRY_PICK", type: "procedure", signature: "arguments" },
|
||||
{ name: "DOLT_CLEAN", type: "procedure", signature: "arguments" },
|
||||
{ name: "DOLT_CLONE", type: "procedure", signature: "arguments" },
|
||||
{ name: "DOLT_COMMIT", type: "procedure", signature: "arguments" },
|
||||
{ name: "DOLT_COMMIT_HASH_OUT", type: "procedure", signature: "out_hash, arguments" },
|
||||
{ name: "DOLT_CONFLICTS_RESOLVE", type: "procedure", signature: "arguments" },
|
||||
{ name: "DOLT_COUNT_COMMITS", type: "procedure", signature: "revision_a, revision_b" },
|
||||
{ name: "DOLT_FETCH", type: "procedure", signature: "arguments" },
|
||||
{ name: "DOLT_GC", type: "procedure", signature: "arguments" },
|
||||
{ name: "DOLT_MERGE", type: "procedure", signature: "arguments" },
|
||||
{ name: "DOLT_PULL", type: "procedure", signature: "arguments" },
|
||||
{ name: "DOLT_PURGE_DROPPED_DATABASES", type: "procedure", signature: "arguments" },
|
||||
{ name: "DOLT_PUSH", type: "procedure", signature: "arguments" },
|
||||
{ name: "DOLT_REBASE", type: "procedure", signature: "arguments" },
|
||||
{ name: "DOLT_REMOTE", type: "procedure", signature: "arguments" },
|
||||
{ name: "DOLT_RESET", type: "procedure", signature: "arguments" },
|
||||
{ name: "DOLT_REVERT", type: "procedure", signature: "arguments" },
|
||||
{ name: "DOLT_RM", type: "procedure", signature: "arguments" },
|
||||
{ name: "DOLT_SQUASH_HISTORY", type: "procedure", signature: "arguments" },
|
||||
{ name: "DOLT_STASH", type: "procedure", signature: "arguments" },
|
||||
{ name: "DOLT_TAG", type: "procedure", signature: "arguments" },
|
||||
{ name: "DOLT_DIFF", type: "function", signature: "arguments" },
|
||||
{ name: "DOLT_DIFF_STAT", type: "function", signature: "arguments" },
|
||||
{ name: "DOLT_DIFF_SUMMARY", type: "function", signature: "arguments" },
|
||||
{ name: "DOLT_HASHOF", type: "function", signature: "revision" },
|
||||
{ name: "DOLT_HASHOF_DB", type: "function", signature: "revision" },
|
||||
{ name: "DOLT_HASHOF_TABLE", type: "function", signature: "table" },
|
||||
{ name: "DOLT_LOG", type: "function", signature: "arguments" },
|
||||
{ name: "DOLT_MERGE_BASE", type: "function", signature: "revision_a, revision_b" },
|
||||
{ name: "DOLT_PATCH", type: "function", signature: "arguments" },
|
||||
{ name: "DOLT_QUERY_DIFF", type: "function", signature: "query_a, query_b" },
|
||||
{ name: "DOLT_REFLOG", type: "function", signature: "arguments" },
|
||||
{ name: "DOLT_SCHEMA_DIFF", type: "function", signature: "arguments" },
|
||||
{ name: "DOLT_VERSION", type: "function", signature: "" },
|
||||
{ name: "DOLT_THREAD_DUMP", type: "procedure", signature: "" },
|
||||
{ name: "DOLT_UNDROP", type: "procedure", signature: "arguments" },
|
||||
{ name: "DOLT_UPDATE_COLUMN_TAG", type: "procedure", signature: "arguments" },
|
||||
{ name: "DOLT_VERIFY_CONSTRAINTS", type: "procedure", signature: "arguments" },
|
||||
{ name: "DOLT_STATS_RESTART", type: "procedure", signature: "" },
|
||||
{ name: "DOLT_STATS_STOP", type: "procedure", signature: "" },
|
||||
{ name: "DOLT_STATS_PURGE", type: "procedure", signature: "" },
|
||||
{ name: "DOLT_STATS_ONCE", type: "procedure", signature: "" },
|
||||
{ name: "DOLT_STATS_WAIT", type: "procedure", signature: "" },
|
||||
{ name: "DOLT_STATS_FLUSH", type: "procedure", signature: "" },
|
||||
{ name: "DOLT_STATS_GC", type: "procedure", signature: "" },
|
||||
{ name: "DOLT_STATS_INFO", type: "procedure", signature: "arguments" },
|
||||
{ name: "DOLT_STATS_TIMERS", type: "procedure", signature: "" },
|
||||
{ name: "ACTIVE_BRANCH", type: "scalar-function", signature: "" },
|
||||
{ name: "DOLT_MERGE_BASE", type: "scalar-function", signature: "revision_a, revision_b" },
|
||||
{ name: "DOLT_HASHOF", type: "scalar-function", signature: "revision" },
|
||||
{ name: "DOLT_HASHOF_DB", type: "scalar-function", signature: "revision" },
|
||||
{ name: "DOLT_HASHOF_TABLE", type: "scalar-function", signature: "table" },
|
||||
{ name: "DOLT_STORAGE_FORMAT", type: "scalar-function", signature: "" },
|
||||
{ name: "DOLT_VERSION", type: "scalar-function", signature: "" },
|
||||
{ name: "HAS_ANCESTOR", type: "scalar-function", signature: "target, ancestor" },
|
||||
{ name: "DOLT_JOIN_COST", type: "scalar-function", signature: "query" },
|
||||
{ name: "DOLT_DIFF", type: "table-function", signature: "from_revision, to_revision, table" },
|
||||
{ name: "DOLT_DIFF_STAT", type: "table-function", signature: "from_revision, to_revision, table" },
|
||||
{ name: "DOLT_DIFF_SUMMARY", type: "table-function", signature: "from_revision, to_revision, table" },
|
||||
{ name: "DOLT_JSON_DIFF", type: "table-function", signature: "from_document, to_document" },
|
||||
{ name: "DOLT_LOG", type: "table-function", signature: "arguments" },
|
||||
{ name: "DOLT_PATCH", type: "table-function", signature: "from_revision, to_revision, table" },
|
||||
{ name: "DOLT_PREVIEW_MERGE_CONFLICTS_SUMMARY", type: "table-function", signature: "base_branch, merge_branch" },
|
||||
{ name: "DOLT_PREVIEW_MERGE_CONFLICTS", type: "table-function", signature: "base_branch, merge_branch, table" },
|
||||
{ name: "DOLT_REFLOG", type: "table-function", signature: "arguments" },
|
||||
{ name: "DOLT_SCHEMA_DIFF", type: "table-function", signature: "from_revision, to_revision, table" },
|
||||
{ name: "DOLT_QUERY_DIFF", type: "table-function", signature: "query_a, query_b" },
|
||||
{ name: "DOLT_BRANCH_STATUS", type: "table-function", signature: "base_refspec, target_refspecs" },
|
||||
{ name: "DOLT_TEST_RUN", type: "table-function", signature: "arguments" },
|
||||
];
|
||||
|
||||
function doltRoutineParameters(signature: string): string[] {
|
||||
return signature
|
||||
.split(",")
|
||||
.map((parameter) => parameter.trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function doltRoutineApplyName(routine: DoltSqlRoutineDefinition, openingParenAfterCursor: boolean): string {
|
||||
if (openingParenAfterCursor) return routine.name;
|
||||
const parameters = doltRoutineParameters(routine.signature);
|
||||
return `${routine.name}(${parameters.map((parameter, index) => `\${${index + 1}:${parameter}}`).join(", ")})`;
|
||||
}
|
||||
|
||||
function doltCompletionObjects(context: DriverProfileSqlCompletionContext) {
|
||||
const routineTypes = context.exclusiveRoutineSuggestions
|
||||
? new Set<DoltSqlRoutineDefinition["type"]>(["procedure"])
|
||||
: context.statementKind === "select"
|
||||
? new Set<DoltSqlRoutineDefinition["type"]>(["scalar-function"])
|
||||
: context.suggestTables
|
||||
? new Set<DoltSqlRoutineDefinition["type"]>()
|
||||
: new Set<DoltSqlRoutineDefinition["type"]>(["procedure", "scalar-function", "table-function"]);
|
||||
|
||||
return DOLT_SQL_ROUTINES.filter((routine) => routineTypes.has(routine.type)).map((routine) => ({
|
||||
name: routine.name,
|
||||
type: routine.type === "procedure" ? ("procedure" as const) : ("function" as const),
|
||||
signature: routine.signature,
|
||||
boost: routine.type === "table-function" ? -200 : 0,
|
||||
}));
|
||||
}
|
||||
|
||||
function doltCompletionTables(context: DriverProfileSqlCompletionContext) {
|
||||
if (!context.suggestTables) return [];
|
||||
return DOLT_SQL_ROUTINES.filter((routine) => routine.type === "table-function").map((routine) => ({
|
||||
name: routine.name,
|
||||
detail: "Dolt table function",
|
||||
applyName: doltRoutineApplyName(routine, context.openingParenAfterCursor),
|
||||
boost: 200,
|
||||
}));
|
||||
}
|
||||
|
||||
export function isDoltDriverProfile(driverProfile?: string): boolean {
|
||||
return driverProfile?.toLowerCase() === DOLT_DRIVER_PROFILE;
|
||||
}
|
||||
|
||||
type DoltConnectionConfig = Pick<ConnectionConfig, "driver_profile"> & Partial<Pick<ConnectionConfig, "external_config">>;
|
||||
|
||||
function externalConfigRecord(value: unknown): Record<string, unknown> {
|
||||
return value && typeof value === "object" && !Array.isArray(value) ? { ...(value as Record<string, unknown>) } : {};
|
||||
}
|
||||
|
||||
export function doltSystemTablesVisible(config?: DoltConnectionConfig): boolean {
|
||||
if (!config || !isDoltDriverProfile(config.driver_profile)) return false;
|
||||
return externalConfigRecord(config.external_config)[DOLT_SHOW_SYSTEM_TABLES_CONFIG_KEY] === true;
|
||||
}
|
||||
|
||||
export function setDoltSystemTablesVisible(config: DoltConnectionConfig, visible: boolean): void {
|
||||
if (!isDoltDriverProfile(config.driver_profile)) return;
|
||||
const external = externalConfigRecord(config.external_config);
|
||||
if (visible) external[DOLT_SHOW_SYSTEM_TABLES_CONFIG_KEY] = true;
|
||||
else delete external[DOLT_SHOW_SYSTEM_TABLES_CONFIG_KEY];
|
||||
config.external_config = Object.keys(external).length > 0 ? external : undefined;
|
||||
}
|
||||
|
||||
export function doltObjectTreeProfileForConnection(config?: ConnectionConfig): DriverProfileObjectTreeProfile | undefined {
|
||||
if (!config || !isDoltDriverProfile(config.driver_profile)) return undefined;
|
||||
const systemTablesVisible = doltSystemTablesVisible(config);
|
||||
return {
|
||||
cacheKey: `dolt-system-tables-v1:${systemTablesVisible ? "shown" : "hidden"}`,
|
||||
groupOverrides: systemTablesVisible
|
||||
? [
|
||||
{
|
||||
nodeType: "group-tables",
|
||||
tableNameFilter: { includePatterns: [], excludePatterns: [DOLT_SYSTEM_TABLE_NAME_PATTERN] },
|
||||
},
|
||||
{
|
||||
nodeType: "group-dolt-system-tables",
|
||||
label: "tree.doltSystemTables",
|
||||
tableNameFilter: { includePatterns: [DOLT_SYSTEM_TABLE_NAME_PATTERN], excludePatterns: [] },
|
||||
},
|
||||
]
|
||||
: [],
|
||||
};
|
||||
}
|
||||
|
||||
export function doltSqlBuiltinTerms(driverProfile?: string): string {
|
||||
if (!isDoltDriverProfile(driverProfile)) return "";
|
||||
return DOLT_SQL_ROUTINES.map((routine) => routine.name.toLowerCase()).join(" ");
|
||||
@@ -48,13 +169,21 @@ export function doltSqlBuiltinTerms(driverProfile?: string): string {
|
||||
|
||||
export function doltSqlRoutineSignatures(driverProfile?: string): Map<string, string[]> {
|
||||
if (!isDoltDriverProfile(driverProfile)) return new Map();
|
||||
return new Map(
|
||||
DOLT_SQL_ROUTINES.map((routine) => [
|
||||
routine.name,
|
||||
routine.signature
|
||||
.split(",")
|
||||
.map((parameter) => parameter.trim())
|
||||
.filter(Boolean),
|
||||
]),
|
||||
);
|
||||
return new Map(DOLT_SQL_ROUTINES.map((routine) => [routine.name, doltRoutineParameters(routine.signature)]));
|
||||
}
|
||||
|
||||
export const DOLT_DRIVER_PROFILE_EXTENSION: DriverProfileExtensionDefinition = {
|
||||
id: DOLT_DRIVER_PROFILE,
|
||||
objectTreeProfile: doltObjectTreeProfileForConnection,
|
||||
completionTableMetadata: (tableName) =>
|
||||
tableName.toLowerCase().startsWith("dolt")
|
||||
? {
|
||||
detail: "Dolt system table",
|
||||
boost: -1200,
|
||||
}
|
||||
: undefined,
|
||||
completionObjects: doltCompletionObjects,
|
||||
completionTables: doltCompletionTables,
|
||||
sqlBuiltinTerms: () => DOLT_SQL_ROUTINES.map((routine) => routine.name.toLowerCase()).join(" "),
|
||||
routineSignatures: () => new Map(DOLT_SQL_ROUTINES.map((routine) => [routine.name, doltRoutineParameters(routine.signature)])),
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
import { DOLT_DRIVER_PROFILE_EXTENSION } from "@/lib/database/doltProfile";
|
||||
import type { SqlCompletionObject, SqlCompletionTable, SqlStatementKind } from "@/lib/sql/sqlCompletion";
|
||||
import type { ConnectionConfig, TableNameFilter, TreeNodeType } from "@/types/database";
|
||||
|
||||
export type DriverProfileObjectTreeProfile = {
|
||||
cacheKey: string;
|
||||
groupOverrides: Array<{
|
||||
nodeType: TreeNodeType;
|
||||
label?: string;
|
||||
tableNameFilter: TableNameFilter;
|
||||
}>;
|
||||
};
|
||||
|
||||
export type DriverProfileSqlCompletionContext = {
|
||||
statementKind: SqlStatementKind;
|
||||
suggestTables: boolean;
|
||||
suggestRoutines: boolean;
|
||||
exclusiveRoutineSuggestions: boolean;
|
||||
prefix: string;
|
||||
openingParenAfterCursor: boolean;
|
||||
};
|
||||
|
||||
export type DriverProfileCompletionTableMetadata = Pick<SqlCompletionTable, "detail" | "boost">;
|
||||
|
||||
export type DriverProfileExtensionDefinition = {
|
||||
id: string;
|
||||
objectTreeProfile?: (config: ConnectionConfig) => DriverProfileObjectTreeProfile | undefined;
|
||||
completionTableMetadata?: (tableName: string) => DriverProfileCompletionTableMetadata | undefined;
|
||||
completionObjects?: (context: DriverProfileSqlCompletionContext) => SqlCompletionObject[];
|
||||
completionTables?: (context: DriverProfileSqlCompletionContext) => SqlCompletionTable[];
|
||||
sqlBuiltinTerms?: () => string;
|
||||
routineSignatures?: () => Map<string, string[]>;
|
||||
};
|
||||
|
||||
export const DRIVER_PROFILE_EXTENSIONS = [DOLT_DRIVER_PROFILE_EXTENSION] as const satisfies readonly DriverProfileExtensionDefinition[];
|
||||
|
||||
export function driverProfileExtension(driverProfile?: string): DriverProfileExtensionDefinition | undefined {
|
||||
const normalized = driverProfile?.trim().toLowerCase();
|
||||
if (!normalized) return undefined;
|
||||
return DRIVER_PROFILE_EXTENSIONS.find((extension) => extension.id.toLowerCase() === normalized);
|
||||
}
|
||||
|
||||
export function driverProfileObjectTreeProfileForConnection(config?: ConnectionConfig): DriverProfileObjectTreeProfile | undefined {
|
||||
if (!config) return undefined;
|
||||
return driverProfileExtension(config.driver_profile)?.objectTreeProfile?.(config);
|
||||
}
|
||||
|
||||
export function driverProfileCompletionTableMetadata(driverProfile: string | undefined, tableName: string): DriverProfileCompletionTableMetadata | undefined {
|
||||
return driverProfileExtension(driverProfile)?.completionTableMetadata?.(tableName);
|
||||
}
|
||||
|
||||
export function driverProfileCompletionObjects(driverProfile: string | undefined, context: DriverProfileSqlCompletionContext): SqlCompletionObject[] {
|
||||
return driverProfileExtension(driverProfile)?.completionObjects?.(context) ?? [];
|
||||
}
|
||||
|
||||
export function driverProfileCompletionTables(driverProfile: string | undefined, context: DriverProfileSqlCompletionContext): SqlCompletionTable[] {
|
||||
return driverProfileExtension(driverProfile)?.completionTables?.(context) ?? [];
|
||||
}
|
||||
|
||||
export function driverProfileHasCompletionCandidates(driverProfile: string | undefined, context: DriverProfileSqlCompletionContext): boolean {
|
||||
const extension = driverProfileExtension(driverProfile);
|
||||
return Boolean(extension?.completionObjects?.(context).length || extension?.completionTables?.(context).length);
|
||||
}
|
||||
|
||||
export function driverProfileSqlBuiltinTerms(driverProfile?: string): string {
|
||||
return driverProfileExtension(driverProfile)?.sqlBuiltinTerms?.() ?? "";
|
||||
}
|
||||
|
||||
export function driverProfileRoutineSignatures(driverProfile?: string): Map<string, string[]> {
|
||||
return driverProfileExtension(driverProfile)?.routineSignatures?.() ?? new Map();
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { SQLDialect } from "@codemirror/lang-sql";
|
||||
import type { DatabaseType } from "@/types/database";
|
||||
import { doltSqlBuiltinTerms } from "@/lib/database/doltProfile";
|
||||
import { driverProfileSqlBuiltinTerms } from "@/lib/database/driverProfileExtensions";
|
||||
|
||||
export type CodeMirrorSqlDialectName = "mysql" | "postgres" | "sqlserver" | "clickhouse";
|
||||
|
||||
@@ -189,7 +189,7 @@ export function createDbxCodeMirrorSqlDialect(langSql: CodeMirrorSqlLanguageModu
|
||||
...baseDialect.spec,
|
||||
keywords: [baseKeywords, commonKeywords, isClickHouse ? CLICKHOUSE_KEYWORDS : "", isPostgres ? POSTGRES_PLPGSQL_KEYWORDS : "", isSqlServer ? SQLSERVER_KEYWORDS : ""].filter(Boolean).join(" "),
|
||||
types: [baseTypes, isClickHouse ? CLICKHOUSE_TYPES : "", isPostgres ? POSTGRES_PLPGSQL_TYPES : ""].filter(Boolean).join(" ") || undefined,
|
||||
builtin: [baseDialect.spec.builtin || "", isClickHouse ? CLICKHOUSE_BUILTINS : "", isPostgres ? POSTGRES_PLPGSQL_BUILTIN : "", doltSqlBuiltinTerms(driverProfile)].filter(Boolean).join(" ") || undefined,
|
||||
builtin: [baseDialect.spec.builtin || "", isClickHouse ? CLICKHOUSE_BUILTINS : "", isPostgres ? POSTGRES_PLPGSQL_BUILTIN : "", driverProfileSqlBuiltinTerms(driverProfile)].filter(Boolean).join(" ") || undefined,
|
||||
...(isClickHouse
|
||||
? {
|
||||
identifierQuotes: '"`',
|
||||
|
||||
@@ -49,7 +49,7 @@ export function shouldOpenObjectBrowserOnSingleClick(type: TreeNodeType, enabled
|
||||
const sourceNodeTypes = new Set<TreeNodeType>(["materialized_view", "procedure", "function", "trigger", "sequence", "synonym", "package", "package-body", "type", "type-body"]);
|
||||
const savedSqlNodeTypes = new Set<TreeNodeType>(["saved-sql-file"]);
|
||||
const tableChildGroupNodeTypes = new Set<TreeNodeType>(["group-columns", "group-indexes", "group-fkeys", "group-triggers", "group-constraints", "group-partitions", "group-table-partitions", "group-table-subpartitions"]);
|
||||
const databaseChildGroupNodeTypes = new Set<TreeNodeType>(["group-tables", "group-views", "group-materialized-views", "group-procedures", "group-functions", "group-triggers", "group-sequences", "group-synonyms", "group-packages", "group-types"]);
|
||||
const databaseChildGroupNodeTypes = new Set<TreeNodeType>(["group-tables", "group-dolt-system-tables", "group-views", "group-materialized-views", "group-procedures", "group-functions", "group-triggers", "group-sequences", "group-synonyms", "group-packages", "group-types"]);
|
||||
const displayPathObjectNodeTypes = new Set<TreeNodeType>(["table", "view", "materialized_view", "procedure", "function", "trigger"]);
|
||||
|
||||
export function objectSourceKindForTreeNode(type: TreeNodeType): ObjectSourceKind | null {
|
||||
|
||||
@@ -9,6 +9,7 @@ const treeGroupNodeTypes = new Set<TreeNodeType>([
|
||||
"group-table-partitions",
|
||||
"group-table-subpartitions",
|
||||
"group-tables",
|
||||
"group-dolt-system-tables",
|
||||
"group-views",
|
||||
"group-materialized-views",
|
||||
"group-procedures",
|
||||
|
||||
@@ -111,6 +111,8 @@ export function getTreeNodeIconInfo(node: TreeNode): TreeNodeIconInfo | null {
|
||||
return { icon: FileCode, colorClass: "text-cyan-400" };
|
||||
case "group-tables":
|
||||
return { icon: Table, colorClass: "text-green-500" };
|
||||
case "group-dolt-system-tables":
|
||||
return { icon: Table, colorClass: "text-slate-500" };
|
||||
case "group-views":
|
||||
return { icon: Eye, colorClass: "text-purple-500" };
|
||||
case "group-materialized-views":
|
||||
|
||||
@@ -13,7 +13,7 @@ import { requiresMysqlIdentifierQuote, requiresPostgresIdentifierQuote } from "@
|
||||
import { identifierMatchScore, matchesIdentifierSearch } from "@/lib/sql/identifierSearch";
|
||||
import { containsHan, orderedSubsequenceSpan, pinyinFirstLetters } from "@/lib/common/pinyin";
|
||||
import { quoteTableIdentifier } from "@/lib/table/tableSelectSql";
|
||||
import { DOLT_SQL_ROUTINES, doltSqlRoutineSignatures, isDoltDriverProfile } from "@/lib/database/doltProfile";
|
||||
import { driverProfileCompletionObjects, driverProfileCompletionTableMetadata, driverProfileCompletionTables, driverProfileRoutineSignatures } from "@/lib/database/driverProfileExtensions";
|
||||
|
||||
export { DEFAULT_SQL_SNIPPETS, resolveSqlSnippetBodyForDatabase } from "@/lib/sql/sqlSnippetTemplates";
|
||||
|
||||
@@ -1373,6 +1373,18 @@ class SqlCompletionProvider {
|
||||
build(): SqlCompletionItem[] {
|
||||
const { context } = this;
|
||||
const pendingJoinKeyword = isPendingJoinKeywordContext(context);
|
||||
const completionTables = [
|
||||
...this.input.tables.map((table) => {
|
||||
const metadata = driverProfileCompletionTableMetadata(this.input.driverProfile, table.name);
|
||||
if (!metadata) return table;
|
||||
return {
|
||||
...table,
|
||||
detail: table.detail ?? metadata.detail,
|
||||
boost: (table.boost ?? 0) + (metadata.boost ?? 0),
|
||||
};
|
||||
}),
|
||||
...driverProfileCompletionTables(this.input.driverProfile, context),
|
||||
];
|
||||
|
||||
if (this.databaseType === "mongodb") {
|
||||
return dedupeAndSort(buildMongoCompletionItemsFromContext({ mode: "root", prefix: context.prefix, from: 0 }).map(mongoCompletionItemToSqlCompletionItem));
|
||||
@@ -1385,7 +1397,7 @@ class SqlCompletionProvider {
|
||||
this.items.push(...buildSnippetItems(context.prefix, snippets, this.input.keywordCase, this.databaseType));
|
||||
}
|
||||
if (!preferReferencedColumns || context.suggestRoutines) {
|
||||
const functionItems = context.dataTypeContext ? [] : buildFunctionSnippetItems(context.prefix, getFunctionDescriptions(this.t), this.databaseType, this.input.driverProfile, context.openingParenAfterCursor, this.input.keywordCase, this.input.functionCase);
|
||||
const functionItems = context.dataTypeContext ? [] : buildFunctionSnippetItems(context.prefix, getFunctionDescriptions(this.t), this.databaseType, context.openingParenAfterCursor, this.input.keywordCase, this.input.functionCase);
|
||||
this.items.push(...(preferReferencedColumns ? functionItems.filter((item) => item.label.toLowerCase().startsWith(context.prefix.toLowerCase())) : functionItems));
|
||||
if (isOracleLikeDatabase(this.databaseType)) {
|
||||
this.items.push(...buildOracleSystemValueItems(context.prefix, this.input.keywordCase));
|
||||
@@ -1443,8 +1455,8 @@ class SqlCompletionProvider {
|
||||
}
|
||||
|
||||
if (!context.exclusiveColumnSuggestions && context.suggestTables) {
|
||||
this.items.push(...buildForeignKeyRelatedTableItems(context, this.input.tables, this.input.foreignKeysByTable, this.dialect, !!this.input.autoAliasTables && context.autoAliasTableCompletions, this.databaseType, this.input.keywordCase, this.input.currentSchema));
|
||||
this.items.push(...buildTableItems(context, this.input.tables, this.dialect, !!this.input.autoAliasTables && context.autoAliasTableCompletions, context.referencedTables, this.databaseType, this.input.currentSchema, this.input.keywordCase));
|
||||
this.items.push(...buildForeignKeyRelatedTableItems(context, completionTables, this.input.foreignKeysByTable, this.dialect, !!this.input.autoAliasTables && context.autoAliasTableCompletions, this.databaseType, this.input.keywordCase, this.input.currentSchema));
|
||||
this.items.push(...buildTableItems(context, completionTables, this.dialect, !!this.input.autoAliasTables && context.autoAliasTableCompletions, context.referencedTables, this.databaseType, this.input.currentSchema, this.input.keywordCase));
|
||||
if (this.databaseType === "clickhouse") {
|
||||
this.items.push(...buildClickHouseFunctionItems(context.prefix, context.openingParenAfterCursor, "table"));
|
||||
}
|
||||
@@ -1457,7 +1469,7 @@ class SqlCompletionProvider {
|
||||
}
|
||||
|
||||
if (context.suggestRoutines || context.exclusiveRoutineSuggestions || context.oracleTableFunctionContext) {
|
||||
const profileObjects = isDoltDriverProfile(this.input.driverProfile) ? DOLT_SQL_ROUTINES : [];
|
||||
const profileObjects = driverProfileCompletionObjects(this.input.driverProfile, context);
|
||||
this.items.push(...buildObjectItems(context, [...(this.input.objects ?? []), ...profileObjects], this.dialect, this.databaseType, this.input.currentSchema));
|
||||
}
|
||||
|
||||
@@ -1889,7 +1901,7 @@ export function getSqlFunctionSignatureHelp(sql: string, cursor: number, databas
|
||||
const observedParameter = countTopLevelCommas(call.groupText);
|
||||
if (databaseType !== "clickhouse") {
|
||||
const lookupName = call.name.toUpperCase();
|
||||
const parameters = activeFunctionSignatures(databaseType, driverProfile).get(lookupName);
|
||||
const parameters = activeFunctionSignatures(databaseType).get(lookupName) ?? driverProfileRoutineSignatures(driverProfile).get(lookupName);
|
||||
if (!parameters) return null;
|
||||
const activeParameter = Math.min(observedParameter, Math.max(0, parameters.length - 1));
|
||||
const signature = `${lookupName}(${parameters.join(", ")})`;
|
||||
@@ -4455,14 +4467,13 @@ function buildSnippetItems(prefix: string, snippets: SqlSnippet[], keywordCase?:
|
||||
});
|
||||
}
|
||||
|
||||
function activeFunctionSignatures(databaseType?: DatabaseType, driverProfile?: string): Map<string, string[]> {
|
||||
function activeFunctionSignatures(databaseType?: DatabaseType): Map<string, string[]> {
|
||||
const commonFunctionNames = databaseType === "cloudflare-d1" ? CLOUDFLARE_D1_COMMON_FUNCTION_NAMES : COMMON_SQL_FUNCTION_NAMES;
|
||||
const signatures = databaseType ? new Map(Array.from(SQL_FUNCTION_SIGNATURES.entries()).filter(([name]) => commonFunctionNames.has(name))) : new Map(SQL_FUNCTION_SIGNATURES);
|
||||
const databaseSignatures = databaseType ? DATABASE_FUNCTION_SIGNATURES[databaseType] : undefined;
|
||||
if (databaseSignatures) {
|
||||
for (const [name, parameters] of databaseSignatures) signatures.set(name, parameters);
|
||||
}
|
||||
for (const [name, parameters] of doltSqlRoutineSignatures(driverProfile)) signatures.set(name, parameters);
|
||||
return signatures;
|
||||
}
|
||||
|
||||
@@ -4504,11 +4515,11 @@ function buildClickHouseFunctionItems(prefix: string, omitOpeningParen: boolean,
|
||||
});
|
||||
}
|
||||
|
||||
function buildFunctionSnippetItems(prefix: string, functionDescriptions: Map<string, string>, databaseType?: DatabaseType, driverProfile?: string, omitOpeningParen = false, keywordCase?: SqlKeywordCase, functionCase?: SqlKeywordCase): SqlCompletionItem[] {
|
||||
function buildFunctionSnippetItems(prefix: string, functionDescriptions: Map<string, string>, databaseType?: DatabaseType, omitOpeningParen = false, keywordCase?: SqlKeywordCase, functionCase?: SqlKeywordCase): SqlCompletionItem[] {
|
||||
if (databaseType === "clickhouse") return buildClickHouseFunctionItems(prefix, omitOpeningParen);
|
||||
const items: SqlCompletionItem[] = [];
|
||||
|
||||
for (const [name, parameters] of activeFunctionSignatures(databaseType, driverProfile).entries()) {
|
||||
for (const [name, parameters] of activeFunctionSignatures(databaseType).entries()) {
|
||||
if (!matchesPrefix(name, prefix)) continue;
|
||||
const functionName = applySqlFunctionCase(name, functionCase);
|
||||
const paramStr = parameters.length > 0 ? parameters.map((p) => `\${${applyGeneratedSqlTemplateKeywordCase(p, keywordCase)}}`).join(", ") : "";
|
||||
@@ -4516,7 +4527,7 @@ function buildFunctionSnippetItems(prefix: string, functionDescriptions: Map<str
|
||||
items.push({
|
||||
label: functionName,
|
||||
type: "function" as const,
|
||||
detail: functionDescriptions.get(name) ?? (isDoltDriverProfile(driverProfile) && name.startsWith("DOLT_") ? "Dolt routine" : "function"),
|
||||
detail: functionDescriptions.get(name) ?? "function",
|
||||
apply: mysqlApply ? `${functionName}${applyGeneratedSqlTemplateKeywordCase(mysqlApply.slice(name.length), keywordCase)}` : `${functionName}(${paramStr})`,
|
||||
boost: computeBoost(name, prefix) + 300,
|
||||
});
|
||||
|
||||
@@ -707,8 +707,10 @@ const groupDefs: Array<{
|
||||
objectTypes: DatabaseObjectTreeKind[];
|
||||
nodeType: TreeNodeType;
|
||||
childType: TreeNodeType | ((objectType: DatabaseObjectTreeKind) => TreeNodeType);
|
||||
profileOnly?: boolean;
|
||||
}> = [
|
||||
{ key: "__tables", label: "tree.tables", objectTypes: ["TABLE"], nodeType: "group-tables", childType: "table" },
|
||||
{ key: "__dolt_system_tables", label: "tree.tables", objectTypes: ["TABLE"], nodeType: "group-dolt-system-tables", childType: "table", profileOnly: true },
|
||||
{ key: "__views", label: "tree.views", objectTypes: ["VIEW"], nodeType: "group-views", childType: "view" },
|
||||
{
|
||||
key: "__materialized_views",
|
||||
@@ -768,22 +770,40 @@ const groupDefs: Array<{
|
||||
},
|
||||
];
|
||||
|
||||
const objectGroupNodeTypes = new Set<TreeNodeType>(["group-tables", "group-views", "group-materialized-views", "group-procedures", "group-functions", "group-triggers", "group-sequences", "group-synonyms", "group-packages", "group-types"]);
|
||||
const objectGroupNodeTypes = new Set<TreeNodeType>(["group-tables", "group-dolt-system-tables", "group-views", "group-materialized-views", "group-procedures", "group-functions", "group-triggers", "group-sequences", "group-synonyms", "group-packages", "group-types"]);
|
||||
|
||||
export function buildObjectGroupPlaceholderNodes({ nodeId, connectionId, database, schema, objectTypes }: { nodeId: string; connectionId: string; database: string; schema?: string; objectTypes: DatabaseObjectTreeKind[] }): TreeNode[] {
|
||||
export function buildObjectGroupPlaceholderNodes({
|
||||
nodeId,
|
||||
connectionId,
|
||||
database,
|
||||
schema,
|
||||
objectTypes,
|
||||
groupOverrides = [],
|
||||
}: {
|
||||
nodeId: string;
|
||||
connectionId: string;
|
||||
database: string;
|
||||
schema?: string;
|
||||
objectTypes: DatabaseObjectTreeKind[];
|
||||
groupOverrides?: Array<{ nodeType: TreeNodeType; label?: string }>;
|
||||
}): TreeNode[] {
|
||||
const supported = new Set(objectTypes);
|
||||
const overrides = new Map(groupOverrides.map((override) => [override.nodeType, override]));
|
||||
return groupDefs
|
||||
.filter((def) => def.objectTypes.some((objectType) => supported.has(objectType)))
|
||||
.map((def) => ({
|
||||
id: `${nodeId}:${def.key}`,
|
||||
label: def.label,
|
||||
type: def.nodeType,
|
||||
connectionId,
|
||||
database,
|
||||
schema,
|
||||
isExpanded: false,
|
||||
children: [],
|
||||
}));
|
||||
.filter((def) => def.objectTypes.some((objectType) => supported.has(objectType)) && (!def.profileOnly || overrides.has(def.nodeType)))
|
||||
.map((def) => {
|
||||
const override = overrides.get(def.nodeType);
|
||||
return {
|
||||
id: `${nodeId}:${def.key}`,
|
||||
label: override?.label ?? def.label,
|
||||
type: def.nodeType,
|
||||
connectionId,
|
||||
database,
|
||||
schema,
|
||||
isExpanded: false,
|
||||
children: [],
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function objectGroupRefreshParentId(node: TreeNode): string | null {
|
||||
@@ -797,7 +817,7 @@ export function objectTypesForGroupNode(type: TreeNodeType): DatabaseObjectTreeK
|
||||
return groupDefs.find((def) => def.nodeType === type)?.objectTypes ?? null;
|
||||
}
|
||||
|
||||
export function buildGroupedObjectTreeNodes({ nodeId, connectionId, database, schema, objects, databaseType }: { nodeId: string; connectionId: string; database: string; schema?: string; objects: ObjectInfo[]; databaseType?: DatabaseType }): TreeNode[] {
|
||||
export function buildGroupedObjectTreeNodes({ nodeId, connectionId, database, schema, objects, databaseType, groupNodeType }: { nodeId: string; connectionId: string; database: string; schema?: string; objects: ObjectInfo[]; databaseType?: DatabaseType; groupNodeType?: TreeNodeType }): TreeNode[] {
|
||||
const buckets = new Map<string, ObjectInfo[]>();
|
||||
const seen = new Set<string>();
|
||||
for (const obj of coalesceXuguPackageObjects(objects, databaseType)) {
|
||||
@@ -816,9 +836,10 @@ export function buildGroupedObjectTreeNodes({ nodeId, connectionId, database, sc
|
||||
|
||||
const groups: TreeNode[] = [];
|
||||
for (const def of groupDefs) {
|
||||
if (groupNodeType ? def.nodeType !== groupNodeType : def.profileOnly) continue;
|
||||
const items = def.objectTypes.flatMap((objectType) => buckets.get(objectType) ?? []);
|
||||
if (!items?.length) continue;
|
||||
const isExpandable = def.nodeType === "group-tables" || def.nodeType === "group-views" || def.nodeType === "group-materialized-views";
|
||||
const isExpandable = def.nodeType === "group-tables" || def.nodeType === "group-dolt-system-tables" || def.nodeType === "group-views" || def.nodeType === "group-materialized-views";
|
||||
const children = isExpandable
|
||||
? buildObjectTreeEntries({
|
||||
nodeId: `${nodeId}:${def.key}`,
|
||||
|
||||
@@ -38,6 +38,16 @@ function mysqlConnection(): ConnectionConfig {
|
||||
} as ConnectionConfig;
|
||||
}
|
||||
|
||||
function doltConnection(): ConnectionConfig {
|
||||
return {
|
||||
...mysqlConnection(),
|
||||
id: "dolt-1",
|
||||
name: "Dolt",
|
||||
driver_profile: "dolt",
|
||||
external_config: { doltShowSystemTables: true },
|
||||
} as ConnectionConfig;
|
||||
}
|
||||
|
||||
function dorisConnection(): ConnectionConfig {
|
||||
return {
|
||||
...mysqlConnection(),
|
||||
@@ -1086,6 +1096,67 @@ describe("connectionStore metadata loading", () => {
|
||||
expect(tableGroup.children?.map((node) => node.label)).toEqual(["ads_pgc_report"]);
|
||||
});
|
||||
|
||||
it("loads Dolt user and system table groups with independent pre-pagination filters", async () => {
|
||||
const listTables = vi.fn(async (...args: unknown[]) => {
|
||||
const tableNameFilter = args[8] as { includePatterns: string[]; excludePatterns: string[] } | undefined;
|
||||
if (tableNameFilter?.includePatterns.includes("dolt%")) {
|
||||
return [{ name: "dolt_log", table_type: "BASE TABLE", comment: null }];
|
||||
}
|
||||
return [{ name: "orders", table_type: "BASE TABLE", comment: null }];
|
||||
});
|
||||
|
||||
vi.doMock("@/lib/backend/tauriRuntime", () => ({ isTauriRuntime: () => false }));
|
||||
vi.doMock("@/lib/backend/api", () => ({
|
||||
checkConnectionHealth: vi.fn().mockResolvedValue(undefined),
|
||||
deleteSchemaCachePrefix: vi.fn().mockResolvedValue(undefined),
|
||||
listTables,
|
||||
loadSchemaCache: vi.fn().mockResolvedValue(null),
|
||||
saveSchemaCache: vi.fn().mockResolvedValue(undefined),
|
||||
saveConnections: vi.fn().mockResolvedValue(undefined),
|
||||
saveSidebarLayout: vi.fn().mockResolvedValue(undefined),
|
||||
}));
|
||||
|
||||
const { useConnectionStore } = await import("@/stores/connectionStore");
|
||||
const { useSettingsStore } = await import("@/stores/settingsStore");
|
||||
const store = useConnectionStore();
|
||||
useSettingsStore().editorSettings.sidebarObjectDisplay = "grouped";
|
||||
const connection = doltConnection();
|
||||
const databaseNode: TreeNode = {
|
||||
id: `${connection.id}:app`,
|
||||
label: "app",
|
||||
type: "database",
|
||||
connectionId: connection.id,
|
||||
database: "app",
|
||||
isExpanded: false,
|
||||
children: [],
|
||||
};
|
||||
store.connections = [connection];
|
||||
store.connectedIds.add(connection.id);
|
||||
store.treeNodes = [
|
||||
{
|
||||
id: connection.id,
|
||||
label: connection.name,
|
||||
type: "connection",
|
||||
connectionId: connection.id,
|
||||
isExpanded: true,
|
||||
children: [databaseNode],
|
||||
},
|
||||
];
|
||||
|
||||
await store.loadTables(connection.id, "app", undefined, { force: true });
|
||||
const userTables = databaseNode.children?.find((node) => node.type === "group-tables");
|
||||
const systemTables = databaseNode.children?.find((node) => node.type === "group-dolt-system-tables");
|
||||
expect(systemTables?.label).toBe("tree.doltSystemTables");
|
||||
|
||||
await store.loadObjectGroupChildren(userTables!, { force: true });
|
||||
await store.loadObjectGroupChildren(systemTables!, { force: true });
|
||||
|
||||
expect(userTables?.children?.map((node) => node.label)).toEqual(["orders"]);
|
||||
expect(systemTables?.children?.map((node) => node.label)).toEqual(["dolt_log"]);
|
||||
expect(listTables).toHaveBeenCalledWith(connection.id, "app", "app", undefined, 1001, 0, ["TABLE"], undefined, { includePatterns: [], excludePatterns: ["dolt%"] });
|
||||
expect(listTables).toHaveBeenCalledWith(connection.id, "app", "app", undefined, 1001, 0, ["TABLE"], undefined, { includePatterns: ["dolt%"], excludePatterns: [] });
|
||||
});
|
||||
|
||||
it("clears a stale connection error after a schema metadata retry succeeds", async () => {
|
||||
const listSchemaInfos = vi
|
||||
.fn()
|
||||
|
||||
@@ -123,6 +123,7 @@ import { isXuguPublicSynonymScope, xuguSchemaDisplayName, XUGU_PUBLIC_SYNONYM_SC
|
||||
import { filterNacosNamespacesForSidebar, normalizeNacosNamespacesForDisplay } from "@/lib/nacos/nacosNamespaceVisibility";
|
||||
import { buildPackageMemberNodes, markPackageNodesExpandable } from "@/lib/sidebar/packageMembers";
|
||||
import { configuredDatabaseProductName, connectionConfigFingerprint, normalizeDatabaseConnectionInfo } from "@/lib/connection/connectionDatabaseInfo";
|
||||
import { driverProfileObjectTreeProfileForConnection } from "@/lib/database/driverProfileExtensions";
|
||||
import { createMetadataLoadTrace, logMetadataLoadTrace, MetadataLoadCoordinator, type MetadataLoadTraceLogger } from "@/lib/metadata/metadataLoadCoordinator";
|
||||
import type { MetadataScopeInput } from "@/lib/metadata/metadataLoadScope";
|
||||
import { MetadataResultCache, type MetadataCacheInvalidation } from "@/lib/metadata/metadataResultCache";
|
||||
@@ -1527,9 +1528,11 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
|
||||
function objectGroupCacheKey(node: TreeNode): string {
|
||||
const config = node.connectionId ? getConfig(node.connectionId) : undefined;
|
||||
const objectTreeProfileCacheKey = driverProfileObjectTreeProfileForConnection(config)?.cacheKey;
|
||||
// objects-v8: object-group listing SQL gained a pg_type branch for
|
||||
// PostgreSQL-family user-defined types; older cached lists miss TYPE nodes.
|
||||
const cacheVersion = ownerAwareMetadataCacheVersion(config, config?.db_type === "oracle" ? "objects-v7" : "objects-v8");
|
||||
const baseCacheVersion = ownerAwareMetadataCacheVersion(config, config?.db_type === "oracle" ? "objects-v7" : "objects-v8");
|
||||
const cacheVersion = objectTreeProfileCacheKey ? `${baseCacheVersion}:${objectTreeProfileCacheKey}` : baseCacheVersion;
|
||||
return schemaCacheKey(node.connectionId || "", node.database || "", node.schema || "", node.type, cacheVersion);
|
||||
}
|
||||
|
||||
@@ -1546,6 +1549,19 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
return tableNameFilterIsEmpty(filter) ? undefined : filter;
|
||||
}
|
||||
|
||||
function effectiveTableNameFilterForNode(node: TreeNode, userFilter?: TableNameFilter): TableNameFilter | undefined {
|
||||
const config = node.connectionId ? getConfig(node.connectionId) : undefined;
|
||||
const profileFilter = driverProfileObjectTreeProfileForConnection(config)?.groupOverrides.find((group) => group.nodeType === node.type)?.tableNameFilter;
|
||||
if (!profileFilter) return userFilter;
|
||||
if (!userFilter) return profileFilter;
|
||||
return normalizeTableNameFilter({
|
||||
// Dedicated groups own their include range. User-defined include patterns
|
||||
// continue to apply to the default tables group, whose profile rule is an exclusion.
|
||||
includePatterns: profileFilter.includePatterns.length > 0 ? profileFilter.includePatterns : userFilter.includePatterns,
|
||||
excludePatterns: [...profileFilter.excludePatterns, ...userFilter.excludePatterns],
|
||||
});
|
||||
}
|
||||
|
||||
function tableNameFilterMetadataExtra(filter: TableNameFilter | undefined): MetadataScopeInput["extra"] {
|
||||
return filter
|
||||
? {
|
||||
@@ -1698,6 +1714,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
schema: options.effectiveSchema,
|
||||
objects: options.objects.filter((object) => options.objectTypes.includes(normalizedObjectTreeKind(object.object_type))),
|
||||
databaseType,
|
||||
groupNodeType: options.node.type,
|
||||
});
|
||||
const refreshedGroup = grouped.find((group) => group.type === options.node.type);
|
||||
const children = refreshedGroup?.children ?? [];
|
||||
@@ -1779,13 +1796,14 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
return { children: [], objectCount: 0, hasMore: false, nextOffset: options.offset };
|
||||
}
|
||||
const searchFilter = (options.searchFilter ?? sidebarSearchQuery.value) || undefined;
|
||||
const tableNameFilter = activeTableNameFilterForScope({
|
||||
const userTableNameFilter = activeTableNameFilterForScope({
|
||||
connectionId: options.node.connectionId,
|
||||
database: options.node.database,
|
||||
schema: options.node.schema,
|
||||
nodeKind: options.node.type,
|
||||
catalog: options.node.catalog,
|
||||
});
|
||||
const tableNameFilter = effectiveTableNameFilterForNode(options.node, userTableNameFilter);
|
||||
const fetchLimit = searchFilter ? options.pageSize : options.pageSize + 1;
|
||||
const fetchOffset = searchFilter ? undefined : options.offset;
|
||||
const tables = await loadCachedMetadataListPage<TableInfo[]>(
|
||||
@@ -4703,7 +4721,9 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
const simpleObjectDisplay = useSettingsStore().editorSettings.sidebarObjectDisplay === "simple";
|
||||
const searchFilter = activeTreeLoadSearchFilter(options);
|
||||
const config = getConfig(connectionId);
|
||||
const cacheVersion = ownerAwareMetadataCacheVersion(config, simpleObjectDisplay ? "objects-simple-v8" : "objects-grouped-v8");
|
||||
const objectTreeProfile = driverProfileObjectTreeProfileForConnection(config);
|
||||
const baseCacheVersion = ownerAwareMetadataCacheVersion(config, simpleObjectDisplay ? "objects-simple-v8" : "objects-grouped-v8");
|
||||
const cacheVersion = !simpleObjectDisplay && objectTreeProfile?.cacheKey ? `${baseCacheVersion}:${objectTreeProfile.cacheKey}` : baseCacheVersion;
|
||||
const cacheKey = schemaCacheKey(connectionId, database, schema || "", cacheVersion);
|
||||
const querySchema = connectionObjectTreeQuerySchema(config, database, schema);
|
||||
const effectiveSchema = connectionObjectTreeNodeSchema(config, database, schema);
|
||||
@@ -4748,6 +4768,7 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
database,
|
||||
schema: effectiveSchema,
|
||||
objectTypes: supportedSidebarObjectTypes(config),
|
||||
groupOverrides: objectTreeProfile?.groupOverrides,
|
||||
});
|
||||
if (!schema && isPostgresLikeForExtensions(config?.db_type)) {
|
||||
children.push(buildExtensionManagementNode(connectionId, database));
|
||||
@@ -5165,7 +5186,12 @@ export const useConnectionStore = defineStore("connection", () => {
|
||||
}
|
||||
|
||||
const matchingGroups = findTreeNodes(treeNodes.value, (node) => {
|
||||
return (node.type === "group-tables" || node.type === "group-views" || node.type === "group-materialized-views") && node.connectionId === target.connectionId && sameSidebarObjectName(node.database, target.database) && (!target.schema || sameSidebarObjectName(node.schema, target.schema));
|
||||
return (
|
||||
(node.type === "group-tables" || node.type === "group-dolt-system-tables" || node.type === "group-views" || node.type === "group-materialized-views") &&
|
||||
node.connectionId === target.connectionId &&
|
||||
sameSidebarObjectName(node.database, target.database) &&
|
||||
(!target.schema || sameSidebarObjectName(node.schema, target.schema))
|
||||
);
|
||||
});
|
||||
|
||||
for (const group of matchingGroups) {
|
||||
|
||||
@@ -902,6 +902,7 @@ export type TreeNodeType =
|
||||
| "group-table-partitions"
|
||||
| "group-table-subpartitions"
|
||||
| "group-tables"
|
||||
| "group-dolt-system-tables"
|
||||
| "group-views"
|
||||
| "group-materialized-views"
|
||||
| "group-procedures"
|
||||
|
||||
Reference in new issue
Block a user