fix(editor): handle Oracle line comments in DDL viewer
This commit is contained in:
parent
29b351691a
commit
cc9b802b49
|
|
@ -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(() => {
|
|||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<DdlViewDialog v-if="queryEditorDdlTarget" v-model:open="showQueryEditorDdlDialog" :connection-id="queryEditorDdlTarget.connectionId" :database="queryEditorDdlTarget.database" :schema="queryEditorDdlTarget.schema" :table-name="queryEditorDdlTarget.tableName" :dialect="queryEditorDdlDialect" />
|
||||
<DdlViewDialog
|
||||
v-if="queryEditorDdlTarget"
|
||||
v-model:open="showQueryEditorDdlDialog"
|
||||
:connection-id="queryEditorDdlTarget.connectionId"
|
||||
:database="queryEditorDdlTarget.database"
|
||||
:schema="queryEditorDdlTarget.schema"
|
||||
:table-name="queryEditorDdlTarget.tableName"
|
||||
:database-type="queryEditorDdlDatabaseType"
|
||||
:dialect="queryEditorDdlDialect"
|
||||
/>
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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: [
|
||||
|
|
|
|||
|
|
@ -1808,13 +1808,15 @@ const pasteTableDataCopySupported = computed(() => supportsWholeRowTableDataCopy
|
|||
|
||||
const ddlTarget = ref<TreeNode | null>(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"
|
||||
|
|
|
|||
|
|
@ -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<typeof import("@codemirror/lang-sql"), "MSSQL" | "MySQL" | "PostgreSQL" | "SQLDialect">;
|
||||
type CodeMirrorSqlLanguageModule = Pick<typeof import("@codemirror/lang-sql"), "Cassandra" | "MSSQL" | "MySQL" | "PLSQL" | "PostgreSQL" | "SQLite" | "SQLDialect" | "StandardSQL">;
|
||||
|
||||
const MYSQL_CODEMIRROR_DATABASE_TYPES = new Set<DatabaseType>(["mysql", "doris", "starrocks", "manticoresearch", "goldendb", "gbase"]);
|
||||
const POSTGRES_CODEMIRROR_DATABASE_TYPES = new Set<DatabaseType>(["postgres", "redshift", "gaussdb", "kwdb", "kingbase", "highgo", "vastbase", "opengauss", "questdb"]);
|
||||
const ORACLE_CODEMIRROR_DATABASE_TYPES = new Set<DatabaseType>(["oracle", "dameng", "yashandb", "oscar", "oceanbase-oracle"]);
|
||||
const SQLITE_CODEMIRROR_DATABASE_TYPES = new Set<DatabaseType>(["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({
|
||||
|
|
|
|||
|
|
@ -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, /<DdlViewDialog[\s\S]*?:database-type="ddlDatabaseType"[\s\S]*?v-model:open="showDdlDialog"/);
|
||||
assert.match(app, /<DdlViewDialog[^>]*:database-type="queryEditorDdlDatabaseType"[^>]*\/>/);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue