diff --git a/apps/desktop/src/App.vue b/apps/desktop/src/App.vue
index 269eef2434..a4e010f7ad 100644
--- a/apps/desktop/src/App.vue
+++ b/apps/desktop/src/App.vue
@@ -329,9 +329,12 @@ function closeDriverStorePage() {
const toolbarAgentDriverUpdateCount = computed(() => (updateNotificationsEnabled.value ? agentDriverUpdateCount.value : 0));
const toolbarHasUpdateAvailable = computed(() => updateNotificationsEnabled.value && hasUpdateAvailable.value);
const hasSqlFileConnections = computed(() => connectionStore.connections.some((c) => supportsSqlFileExecution(c.db_type)));
+const queryEditorDdlDatabaseType = computed(() => {
+ if (!queryEditorDdlTarget.value?.connectionId) return undefined;
+ return effectiveDatabaseTypeForConnection(connectionStore.getConfig(queryEditorDdlTarget.value.connectionId));
+});
const queryEditorDdlDialect = computed(() => {
- if (!queryEditorDdlTarget.value?.connectionId) return "mysql";
- return codeMirrorSqlDialect(effectiveDatabaseTypeForConnection(connectionStore.getConfig(queryEditorDdlTarget.value.connectionId)));
+ return codeMirrorSqlDialect(queryEditorDdlDatabaseType.value);
});
const connectionStats = computed(() => ({
total: connectionStore.connections.length,
@@ -2145,7 +2148,16 @@ onUnmounted(() => {
-
+
diff --git a/apps/desktop/src/components/editor/QueryEditor.vue b/apps/desktop/src/components/editor/QueryEditor.vue
index dd33aa9dd6..a3da6b68e2 100644
--- a/apps/desktop/src/components/editor/QueryEditor.vue
+++ b/apps/desktop/src/components/editor/QueryEditor.vue
@@ -209,6 +209,7 @@ let codeMirrorTheme: import("@codemirror/state").Compartment | null = null;
let wordWrapComp: import("@codemirror/state").Compartment | null = null;
let vimModeComp: import("@codemirror/state").Compartment | null = null;
let closeBracketsComp: import("@codemirror/state").Compartment | null = null;
+let sqlLanguageComp: import("@codemirror/state").Compartment | null = null;
let codeMirrorCloseBrackets: typeof import("@codemirror/autocomplete").closeBrackets | null = null;
let codeMirrorCloseBracketsKeymap: readonly import("@codemirror/view").KeyBinding[] | null = null;
let readOnlyComp: import("@codemirror/state").Compartment | null = null;
@@ -224,6 +225,7 @@ let dbxVimCommandsConfigured = false;
let buildSqlDiagnosticExtension: (() => import("@codemirror/state").Extension) | null = null;
let buildSqlSignatureExtension: (() => import("@codemirror/state").Extension) | null = null;
let buildSqlCompletionExtension: (() => import("@codemirror/state").Extension) | null = null;
+let buildSqlLanguageExtension: (() => import("@codemirror/state").Extension) | null = null;
let codeMirrorSnippetCompletion: typeof import("@codemirror/autocomplete").snippetCompletion;
let codeMirrorCompletionStatus: typeof import("@codemirror/autocomplete").completionStatus | null = null;
let codeMirrorAcceptCompletion: typeof import("@codemirror/autocomplete").acceptCompletion | null = null;
@@ -2586,6 +2588,7 @@ onMounted(async () => {
wordWrapComp = new Compartment();
vimModeComp = new Compartment();
closeBracketsComp = new Compartment();
+ sqlLanguageComp = new Compartment();
codeMirrorCloseBrackets = closeBrackets;
codeMirrorCloseBracketsKeymap = closeBracketsKeymap;
readOnlyComp = new Compartment();
@@ -2700,7 +2703,7 @@ onMounted(async () => {
override: [async (context: CompletionContext) => provideSqlCompletions(context)],
});
- const dialect = createDbxCodeMirrorSqlDialect(langSql, props.dialect);
+ buildSqlLanguageExtension = () => langSql.sql({ dialect: createDbxCodeMirrorSqlDialect(langSql, props.dialect, props.databaseType) });
const initialSettings = settingsStore.editorSettings;
const theme = await loadEditorTheme(initialSettings.theme, editorThemeAppearance(), getCurrentCustomThemeColors(), themePalette.value);
@@ -2856,7 +2859,7 @@ onMounted(async () => {
// Vim must be mounted before DBX/default keymaps so normal-mode keys are handled first.
vimModeComp.of(vimModeExtension(initialSettings.vimModeEnabled)),
keymap.of([...defaultKeymap, ...searchKeymap, ...historyKeymap, ...foldKeymap, ...completionKeymap]),
- langSql.sql({ dialect }),
+ sqlLanguageComp.of(buildSqlLanguageExtension()),
tooltips({ parent: document.body }),
completionComp.of(buildSqlCompletionExtension()),
sqlCompletionTheme(EditorView),
@@ -3183,13 +3186,11 @@ watch(
},
);
-watch(
- () => props.databaseType,
- () => {
- executableStatementRangeCache = null;
- view.value?.dispatch({});
- },
-);
+watch([() => props.databaseType, () => props.dialect], () => {
+ executableStatementRangeCache = null;
+ if (!view.value || !sqlLanguageComp || !buildSqlLanguageExtension) return;
+ view.value.dispatch({ effects: sqlLanguageComp.reconfigure(buildSqlLanguageExtension()) });
+});
watch(
() => props.forceWordWrap,
diff --git a/apps/desktop/src/components/objects/DdlViewDialog.vue b/apps/desktop/src/components/objects/DdlViewDialog.vue
index ac25a17c99..e1ba067e70 100644
--- a/apps/desktop/src/components/objects/DdlViewDialog.vue
+++ b/apps/desktop/src/components/objects/DdlViewDialog.vue
@@ -14,7 +14,7 @@ import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import EditorSearchPanel from "@/components/editor/EditorSearchPanel.vue";
import type { EditorView } from "@codemirror/view";
-import type { ObjectSourceKind } from "@/types/database";
+import type { DatabaseType, ObjectSourceKind } from "@/types/database";
const props = withDefaults(
defineProps<{
@@ -24,7 +24,9 @@ const props = withDefaults(
schema?: string;
tableName: string;
objectType?: ObjectSourceKind;
- /** SQL dialect for syntax highlighting. Non-PG/non-MSSQL databases fall back to MySQL (same as QueryEditor's source viewer). */
+ /** Effective database type selects database-specific syntax rules; older callers can still rely on the dialect fallback. */
+ databaseType?: DatabaseType;
+ /** SQL dialect fallback for syntax highlighting when the effective database type is unavailable. */
dialect: "mysql" | "postgres" | "sqlserver";
/** SQL formatter dialect. Kept separate from the syntax-highlighting dialect because several PG-compatible DBs highlight as MySQL. */
formatDialect?: SqlFormatDialect;
@@ -88,7 +90,7 @@ async function initDdlEditor(content: string) {
const fontFamily = settingsStore.editorSettings.fontFamily;
const themeExt = await loadEditorTheme(editorTheme, appAppearance, undefined, themePalette.value);
const fontExt = editorFontTheme(EditorView, fontSize, fontFamily, { fixedHeight: true, scrollable: true });
- const dialect = createDbxCodeMirrorSqlDialect(langSql, props.dialect);
+ const dialect = createDbxCodeMirrorSqlDialect(langSql, props.dialect, props.databaseType);
const state = EditorState.create({
doc: content,
extensions: [
diff --git a/apps/desktop/src/components/sidebar/TreeItem.vue b/apps/desktop/src/components/sidebar/TreeItem.vue
index 9d4cfd75dc..a93bf9ef81 100644
--- a/apps/desktop/src/components/sidebar/TreeItem.vue
+++ b/apps/desktop/src/components/sidebar/TreeItem.vue
@@ -1808,13 +1808,15 @@ const pasteTableDataCopySupported = computed(() => supportsWholeRowTableDataCopy
const ddlTarget = ref(null);
const showDdlDialog = ref(false);
+const ddlDatabaseType = computed(() => {
+ if (!ddlTarget.value?.connectionId) return undefined;
+ return effectiveDatabaseTypeForConnection(connectionStore.getConfig(ddlTarget.value.connectionId));
+});
const ddlDialect = computed(() => {
- if (!ddlTarget.value?.connectionId) return "mysql";
- return codeMirrorSqlDialect(effectiveDatabaseTypeForConnection(connectionStore.getConfig(ddlTarget.value.connectionId)));
+ return codeMirrorSqlDialect(ddlDatabaseType.value);
});
const ddlFormatDialect = computed(() => {
- if (!ddlTarget.value?.connectionId) return "generic";
- return sqlFormatDialectForDbType(effectiveDatabaseTypeForConnection(connectionStore.getConfig(ddlTarget.value.connectionId)));
+ return sqlFormatDialectForDbType(ddlDatabaseType.value);
});
const objectSourceTarget = ref<{ node: TreeNode; initialEditing: boolean } | null>(null);
const showObjectSourceDialog = ref(false);
@@ -5930,6 +5932,7 @@ function treeItemMenuItems(): ContextMenuItem[] {
:schema="ddlTarget.schema"
:table-name="ddlTarget.label"
:object-type="tableDdlObjectTypeForNode(ddlTarget.type)"
+ :database-type="ddlDatabaseType"
:dialect="ddlDialect"
:format-dialect="ddlFormatDialect"
v-model:open="showDdlDialog"
diff --git a/apps/desktop/src/lib/editor/codemirrorSqlDialect.ts b/apps/desktop/src/lib/editor/codemirrorSqlDialect.ts
index 265db5739e..19db8b898e 100644
--- a/apps/desktop/src/lib/editor/codemirrorSqlDialect.ts
+++ b/apps/desktop/src/lib/editor/codemirrorSqlDialect.ts
@@ -1,8 +1,14 @@
import type { SQLDialect } from "@codemirror/lang-sql";
+import type { DatabaseType } from "@/types/database";
export type CodeMirrorSqlDialectName = "mysql" | "postgres" | "sqlserver";
-type CodeMirrorSqlLanguageModule = Pick;
+type CodeMirrorSqlLanguageModule = Pick;
+
+const MYSQL_CODEMIRROR_DATABASE_TYPES = new Set(["mysql", "doris", "starrocks", "manticoresearch", "goldendb", "gbase"]);
+const POSTGRES_CODEMIRROR_DATABASE_TYPES = new Set(["postgres", "redshift", "gaussdb", "kwdb", "kingbase", "highgo", "vastbase", "opengauss", "questdb"]);
+const ORACLE_CODEMIRROR_DATABASE_TYPES = new Set(["oracle", "dameng", "yashandb", "oscar", "oceanbase-oracle"]);
+const SQLITE_CODEMIRROR_DATABASE_TYPES = new Set(["sqlite", "rqlite", "turso", "cloudflare-d1"]);
const DBX_COMMON_SQL_KEYWORDS = [
"PIVOT",
@@ -52,10 +58,23 @@ export function postgresKeywordSyntaxTerms(keywords: string): string {
.join(" ");
}
-export function createDbxCodeMirrorSqlDialect(langSql: CodeMirrorSqlLanguageModule, dialectName: CodeMirrorSqlDialectName = "mysql"): SQLDialect {
- const baseDialect = dialectName === "postgres" ? langSql.PostgreSQL : dialectName === "sqlserver" ? langSql.MSSQL : langSql.MySQL;
- const isPostgres = dialectName === "postgres";
- const isSqlServer = dialectName === "sqlserver";
+function codeMirrorBaseDialect(langSql: CodeMirrorSqlLanguageModule, dialectName: CodeMirrorSqlDialectName, databaseType?: DatabaseType): SQLDialect {
+ if (databaseType) {
+ if (MYSQL_CODEMIRROR_DATABASE_TYPES.has(databaseType)) return langSql.MySQL;
+ if (POSTGRES_CODEMIRROR_DATABASE_TYPES.has(databaseType)) return langSql.PostgreSQL;
+ if (ORACLE_CODEMIRROR_DATABASE_TYPES.has(databaseType)) return langSql.PLSQL;
+ if (SQLITE_CODEMIRROR_DATABASE_TYPES.has(databaseType)) return langSql.SQLite;
+ if (databaseType === "sqlserver") return langSql.MSSQL;
+ if (databaseType === "cassandra") return langSql.Cassandra;
+ return langSql.StandardSQL;
+ }
+ return dialectName === "postgres" ? langSql.PostgreSQL : dialectName === "sqlserver" ? langSql.MSSQL : langSql.MySQL;
+}
+
+export function createDbxCodeMirrorSqlDialect(langSql: CodeMirrorSqlLanguageModule, dialectName: CodeMirrorSqlDialectName = "mysql", databaseType?: DatabaseType): SQLDialect {
+ const baseDialect = codeMirrorBaseDialect(langSql, dialectName, databaseType);
+ const isPostgres = baseDialect === langSql.PostgreSQL;
+ const isSqlServer = baseDialect === langSql.MSSQL;
const baseKeywords = isPostgres ? postgresKeywordSyntaxTerms(baseDialect.spec.keywords || "") : baseDialect.spec.keywords || "";
return langSql.SQLDialect.define({
diff --git a/packages/app-tests/codemirrorSqlDialect.test.ts b/packages/app-tests/codemirrorSqlDialect.test.ts
index 11fabddc00..f14a387f39 100644
--- a/packages/app-tests/codemirrorSqlDialect.test.ts
+++ b/packages/app-tests/codemirrorSqlDialect.test.ts
@@ -1,7 +1,10 @@
import { strict as assert } from "node:assert";
+import { readFileSync } from "node:fs";
import { test } from "vitest";
import * as langSql from "@codemirror/lang-sql";
import { createDbxCodeMirrorSqlDialect } from "../../apps/desktop/src/lib/editor/codemirrorSqlDialect.ts";
+import { codeMirrorSqlDialect } from "../../apps/desktop/src/lib/database/jdbcDialect.ts";
+import type { DatabaseType } from "../../apps/desktop/src/types/database.ts";
function hasKeyword(keywords: string | undefined, keyword: string): boolean {
return new RegExp(`(?:^|\\s)${keyword}(?:\\s|$)`, "i").test(keywords || "");
@@ -31,3 +34,61 @@ test("keeps DBX PostgreSQL procedural dialect extensions", () => {
assert.equal(hasKeyword(dialect.spec.types, "JSONB"), true);
assert.equal(hasKeyword(dialect.spec.builtin, "TG_NAME"), true);
});
+
+test("treats compact double-dash comments as comments in non-MySQL SQL dialects", () => {
+ const databaseTypes: DatabaseType[] = [
+ "oracle",
+ "dameng",
+ "yashandb",
+ "oscar",
+ "oceanbase-oracle",
+ "sqlite",
+ "rqlite",
+ "turso",
+ "cloudflare-d1",
+ "postgres",
+ "redshift",
+ "gaussdb",
+ "kwdb",
+ "kingbase",
+ "highgo",
+ "vastbase",
+ "opengauss",
+ "questdb",
+ "sqlserver",
+ "cassandra",
+ "clickhouse",
+ "duckdb",
+ "databend",
+ "db2",
+ "hive",
+ "spark",
+ ];
+
+ for (const databaseType of databaseTypes) {
+ const dialect = createDbxCodeMirrorSqlDialect(langSql, codeMirrorSqlDialect(databaseType), databaseType);
+ assert.equal(countParsedNodes(dialect, "--SELECT 1", "LineComment", "--SELECT 1"), 1, databaseType);
+ assert.equal(countParsedNodes(dialect, "--SELECT 1", "Keyword", "SELECT"), 0, databaseType);
+ }
+});
+
+test("keeps MySQL-compatible double-dash whitespace rules", () => {
+ const databaseTypes: DatabaseType[] = ["mysql", "doris", "starrocks", "manticoresearch", "goldendb", "gbase"];
+
+ for (const databaseType of databaseTypes) {
+ const dialect = createDbxCodeMirrorSqlDialect(langSql, codeMirrorSqlDialect(databaseType), databaseType);
+ assert.equal(countParsedNodes(dialect, "--SELECT 1", "LineComment", "--SELECT 1"), 0, databaseType);
+ assert.equal(countParsedNodes(dialect, "--SELECT 1", "Keyword", "SELECT"), 1, databaseType);
+ assert.equal(countParsedNodes(dialect, "-- SELECT 1", "LineComment", "-- SELECT 1"), 1, databaseType);
+ }
+});
+
+test("propagates database type to every DDL viewer entrypoint", () => {
+ const ddlViewDialog = readFileSync("apps/desktop/src/components/objects/DdlViewDialog.vue", "utf8");
+ const treeItem = readFileSync("apps/desktop/src/components/sidebar/TreeItem.vue", "utf8");
+ const app = readFileSync("apps/desktop/src/App.vue", "utf8");
+
+ assert.match(ddlViewDialog, /createDbxCodeMirrorSqlDialect\(langSql, props\.dialect, props\.databaseType\)/);
+ assert.match(treeItem, /]*:database-type="queryEditorDdlDatabaseType"[^>]*\/>/);
+});