mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-21 05:20:02 +00:00
fix(completion): trigger quoted table suggestions
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import * as langSql from "@codemirror/lang-sql";
|
||||
import { createDbxCodeMirrorSqlDialect, postgresKeywordSyntaxTerms } from "@/lib/editor/codemirrorSqlDialect";
|
||||
import type { DatabaseType } from "@/types/database";
|
||||
|
||||
describe("codemirrorSqlDialect", () => {
|
||||
it("keeps common PostgreSQL identifier names out of keyword highlighting", () => {
|
||||
@@ -23,4 +24,12 @@ describe("codemirrorSqlDialect", () => {
|
||||
expect(doltBuiltins.has("dolt_merge")).toBe(true);
|
||||
expect(mysqlBuiltins.has("dolt_branch")).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps double quotes as identifier delimiters for Oracle-family dialects", () => {
|
||||
const databaseTypes: DatabaseType[] = ["oracle", "dameng", "yashandb", "oscar", "oceanbase-oracle"];
|
||||
|
||||
for (const databaseType of databaseTypes) {
|
||||
expect(createDbxCodeMirrorSqlDialect(langSql, "mysql", databaseType).spec.doubleQuotedStrings, databaseType).toBe(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,15 +3,16 @@ import { ensureSyntaxTree } from "@codemirror/language";
|
||||
import { EditorState } from "@codemirror/state";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createDbxCodeMirrorSqlDialect } from "@/lib/editor/codemirrorSqlDialect";
|
||||
import { isSqlCompletionSuppressedContext, isSqlLikeCompletionStatement } from "@/lib/sql/sqlCompletion";
|
||||
import { isSqlCompletionSuppressedContext, isSqlLikeCompletionStatement, shouldAutoOpenSqlCompletion } from "@/lib/sql/sqlCompletion";
|
||||
import { buildSqlSemanticModel } from "@/lib/sql/semantic/model";
|
||||
import type { DatabaseType } from "@/types/database";
|
||||
|
||||
// Forces CM6's background parse to completion so tests are deterministic. Production code never
|
||||
// does this -- see sqlSyntaxTreeWindow.ts's doc comment.
|
||||
function stateFor(doc: string): EditorState {
|
||||
function stateFor(doc: string, dialectName: "mysql" | "postgres" | "sqlserver" = "postgres", databaseType: DatabaseType = "postgres"): EditorState {
|
||||
const state = EditorState.create({
|
||||
doc,
|
||||
extensions: [langSql.sql({ dialect: createDbxCodeMirrorSqlDialect(langSql, "postgres", "postgres") })],
|
||||
extensions: [langSql.sql({ dialect: createDbxCodeMirrorSqlDialect(langSql, dialectName, databaseType) })],
|
||||
});
|
||||
ensureSyntaxTree(state, doc.length, 5_000);
|
||||
return state;
|
||||
@@ -46,6 +47,21 @@ describe("isSqlCompletionSuppressedContext with a live EditorState", () => {
|
||||
|
||||
expect(isSqlCompletionSuppressedContext(sql, cursor, { databaseType: "postgres", editorState: state })).toBe(false);
|
||||
});
|
||||
|
||||
it("auto-opens table completion while editing double-quoted Oracle-family names", () => {
|
||||
const databaseTypes: DatabaseType[] = ["oracle", "dameng", "yashandb", "oscar", "oceanbase-oracle"];
|
||||
|
||||
for (const databaseType of databaseTypes) {
|
||||
for (const sql of ['SELECT * FROM "fo"', 'SELECT * FROM "fo']) {
|
||||
const cursor = sql.endsWith('"') ? sql.length - 1 : sql.length;
|
||||
const state = stateFor(sql, "mysql", databaseType);
|
||||
const options = { databaseType, dialect: "mysql" as const, editorState: state };
|
||||
|
||||
expect(isSqlCompletionSuppressedContext(sql, cursor, options), `${databaseType}: ${sql}`).toBe(false);
|
||||
expect(shouldAutoOpenSqlCompletion(sql, cursor, options), `${databaseType}: ${sql}`).toBe(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("isSqlLikeCompletionStatement with a live EditorState", () => {
|
||||
|
||||
@@ -4,14 +4,15 @@ import { EditorState } from "@codemirror/state";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { createDbxCodeMirrorSqlDialect } from "@/lib/editor/codemirrorSqlDialect";
|
||||
import { resolveLexicalLeafFromSyntaxTree, resolveStatementWindowFromSyntaxTree } from "@/lib/sql/sqlSyntaxTreeWindow";
|
||||
import type { DatabaseType } from "@/types/database";
|
||||
|
||||
// Forces CM6's background parse to completion so tests are deterministic instead of racing the
|
||||
// idle-time parse scheduler. Production code never does this (see sqlSyntaxTreeWindow.ts's doc
|
||||
// comment on why it only ever consults syntaxTree/syntaxTreeAvailable, never ensureSyntaxTree).
|
||||
function stateFor(doc: string, dialectName: "mysql" | "postgres" | "sqlserver" = "postgres", databaseType?: string): EditorState {
|
||||
function stateFor(doc: string, dialectName: "mysql" | "postgres" | "sqlserver" = "postgres", databaseType?: DatabaseType): EditorState {
|
||||
const state = EditorState.create({
|
||||
doc,
|
||||
extensions: [langSql.sql({ dialect: createDbxCodeMirrorSqlDialect(langSql, dialectName, databaseType as never) })],
|
||||
extensions: [langSql.sql({ dialect: createDbxCodeMirrorSqlDialect(langSql, dialectName, databaseType) })],
|
||||
});
|
||||
ensureSyntaxTree(state, doc.length, 5_000);
|
||||
return state;
|
||||
@@ -129,6 +130,22 @@ describe("resolveLexicalLeafFromSyntaxTree", () => {
|
||||
expect(resolveLexicalLeafFromSyntaxTree(backtickState, backtickQuoted.indexOf("user_name"))?.inStringLiteral).toBe(false);
|
||||
});
|
||||
|
||||
it("does not treat edited Oracle-family table identifiers as string literals", () => {
|
||||
const databaseTypes: DatabaseType[] = ["oracle", "dameng", "yashandb", "oscar", "oceanbase-oracle"];
|
||||
|
||||
for (const databaseType of databaseTypes) {
|
||||
const paired = 'SELECT * FROM "fo"';
|
||||
const pairedCursor = paired.length - 1;
|
||||
expect(resolveLexicalLeafFromSyntaxTree(stateFor(paired, "mysql", databaseType), pairedCursor)?.inStringLiteral, `${databaseType} paired`).toBe(false);
|
||||
|
||||
const unclosed = 'SELECT * FROM "fo';
|
||||
expect(resolveLexicalLeafFromSyntaxTree(stateFor(unclosed, "mysql", databaseType), unclosed.length)?.inStringLiteral, `${databaseType} unclosed`).toBe(false);
|
||||
|
||||
const stringLiteral = "SELECT * FROM users WHERE name = 'fo'";
|
||||
expect(resolveLexicalLeafFromSyntaxTree(stateFor(stringLiteral, "mysql", databaseType), stringLiteral.indexOf("fo"))?.inStringLiteral, `${databaseType} string`).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("classifies plain code as neither a comment nor a string literal", () => {
|
||||
const sql = "select id from users";
|
||||
const cursor = sql.indexOf("id");
|
||||
|
||||
@@ -180,6 +180,7 @@ export function createDbxCodeMirrorSqlDialect(langSql: CodeMirrorSqlLanguageModu
|
||||
const baseDialect = codeMirrorBaseDialect(langSql, dialectName, databaseType);
|
||||
const isPostgres = baseDialect === langSql.PostgreSQL;
|
||||
const isSqlServer = baseDialect === langSql.MSSQL;
|
||||
const isPlsql = baseDialect === langSql.PLSQL;
|
||||
const isClickHouse = databaseType === "clickhouse" || dialectName === "clickhouse";
|
||||
const baseKeywords = isClickHouse ? standardSqlKeywordSyntaxTerms(langSql) : isPostgres ? postgresKeywordSyntaxTerms(baseDialect.spec.keywords || "") : baseDialect.spec.keywords || "";
|
||||
const baseTypes = isClickHouse ? STANDARD_SQL_TYPES : baseDialect.spec.types || "";
|
||||
@@ -197,6 +198,7 @@ export function createDbxCodeMirrorSqlDialect(langSql: CodeMirrorSqlLanguageModu
|
||||
spaceAfterDashes: false,
|
||||
}
|
||||
: {}),
|
||||
...(isPlsql ? { doubleQuotedStrings: false } : {}),
|
||||
doubleDollarQuotedStrings: false,
|
||||
});
|
||||
}
|
||||
|
||||
+32
@@ -948,6 +948,38 @@ test("replaces partially typed quoted identifiers without duplicating quotes", (
|
||||
}
|
||||
});
|
||||
|
||||
test("suggests same-prefix tables while editing double-quoted Oracle-family identifiers", () => {
|
||||
for (const databaseType of ["oracle", "dameng"] as const) {
|
||||
const markedSql = 'SELECT * FROM "Fo|"';
|
||||
const cursor = markedSql.indexOf("|");
|
||||
const sql = markedSql.replace("|", "");
|
||||
const options = { databaseType, dialect: "mysql" as const };
|
||||
const context = sqlCompletionContextFromSemantic(buildSqlSemanticModel(sql, cursor, options), getSqlCompletionContext(sql, cursor, options));
|
||||
const items = buildSqlCompletionItemsFromContext(context, {
|
||||
tables: [
|
||||
{ name: "FormAlpha", schema: "APP", type: "table" },
|
||||
{ name: "FormArchive", schema: "APP", type: "table" },
|
||||
],
|
||||
columnsByTable: new Map(),
|
||||
...options,
|
||||
});
|
||||
const replacement = prepareSqlCompletionReplacement(sql, cursor, context, items);
|
||||
const tableItems = replacement.items.filter((item) => item.type === "table");
|
||||
|
||||
assert.deepEqual(
|
||||
tableItems.map((item) => item.label),
|
||||
["FormAlpha", "FormArchive"],
|
||||
databaseType,
|
||||
);
|
||||
assert.equal(replacement.from, sql.indexOf('"'), databaseType);
|
||||
assert.equal(
|
||||
tableItems.every((item) => item.replaceClosingQuote === '"'),
|
||||
true,
|
||||
databaseType,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("uses PostgreSQL-style double quotes for PostgreSQL-family completion identifiers", () => {
|
||||
const databaseTypes: DatabaseType[] = ["kingbase", "vastbase", "highgo", "uxdb"];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user