mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-21 05:20:02 +00:00
fix(completion): quote SQL Server identifiers
This commit is contained in:
@@ -3498,7 +3498,15 @@ function splitQualifiedNameRawParts(input: string): string[] {
|
||||
continue;
|
||||
}
|
||||
if (ch === "[" && !inDoubleQuote && !inBacktick) inBracket = true;
|
||||
if (ch === "]" && inBracket) inBracket = false;
|
||||
if (ch === "]" && inBracket) {
|
||||
current += ch;
|
||||
if (input[i + 1] === "]") {
|
||||
current += input[++i];
|
||||
} else {
|
||||
inBracket = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (ch === "." && !inDoubleQuote && !inBacktick && !inBracket) {
|
||||
parts.push(current.trim());
|
||||
current = "";
|
||||
@@ -3537,6 +3545,8 @@ export function quoteSqlIdentifier(identifier: string, dialect?: SqlCompletionAp
|
||||
}
|
||||
|
||||
const POSTGRES_IDENTIFIER_KEYWORDS = new Set(SQL_KEYWORDS.map((keyword) => keyword.toLowerCase()));
|
||||
const SQLSERVER_IDENTIFIER_KEYWORDS = new Set(sqlDialectCompletionWords(MSSQL.spec.keywords).map((keyword) => keyword.toLowerCase()));
|
||||
const SQLSERVER_REGULAR_IDENTIFIER = /^[\p{L}_][\p{L}\p{Nd}_@$#]*$/u;
|
||||
|
||||
// Unlike quoteSqlIdentifier (also used by the data grid condition editor, which
|
||||
// intentionally leaves MySQL identifiers unquoted and applies backticks itself
|
||||
@@ -3547,14 +3557,19 @@ function quoteCompletionApplyIdentifier(identifier: string, dialect?: SqlComplet
|
||||
if (!requiresMysqlIdentifierQuote(identifier, POSTGRES_IDENTIFIER_KEYWORDS)) return identifier;
|
||||
return `\`${identifier.replaceAll("`", "``")}\``;
|
||||
}
|
||||
if (dialect === "sqlserver") {
|
||||
if (SQLSERVER_REGULAR_IDENTIFIER.test(identifier) && !requiresMysqlIdentifierQuote(identifier.toLowerCase(), SQLSERVER_IDENTIFIER_KEYWORDS)) return identifier;
|
||||
const escaped = identifier.replaceAll("]", "]]");
|
||||
return `[${escaped}]`;
|
||||
}
|
||||
return quoteSqlIdentifier(identifier, dialect);
|
||||
}
|
||||
|
||||
function quoteCompletionApplyName(applyName: string, dialect?: SqlCompletionApplyDialect): string {
|
||||
if (dialect !== "mysql" && dialect !== "oracle") return applyName;
|
||||
if (dialect !== "mysql" && dialect !== "oracle" && dialect !== "sqlserver") return applyName;
|
||||
const parts = splitQualifiedNameRawParts(applyName);
|
||||
if (parts.length === 0) return applyName;
|
||||
return parts.map((part) => (isQuotedIdentifier(part) ? part : quoteCompletionApplyIdentifier(part, dialect))).join(".");
|
||||
return parts.map((part) => ((dialect === "sqlserver" && !part) || isQuotedIdentifier(part) ? part : quoteCompletionApplyIdentifier(part, dialect))).join(".");
|
||||
}
|
||||
|
||||
function quoteCompletionRoutineIdentifier(identifier: string, dialect?: SqlCompletionApplyDialect): string {
|
||||
@@ -3640,7 +3655,8 @@ function buildTableItems(
|
||||
.map((table) => {
|
||||
const qualifiedByContext = !!qualifierSchema && !!table.schema && normalizeIdentifierPart(qualifierSchema) === normalizeIdentifierPart(table.schema);
|
||||
const { ambiguousTableName, defaultApplyName } = resolveTableSchemaQualification(table, dialect, databaseType, currentSchema, schemasByTableName);
|
||||
const suppliedApplyName = table.applyName?.trim();
|
||||
const rawSuppliedApplyName = table.applyName?.trim();
|
||||
const suppliedApplyName = dialect === "sqlserver" && rawSuppliedApplyName ? quoteCompletionApplyName(rawSuppliedApplyName, dialect) : rawSuppliedApplyName;
|
||||
const suppliedApplyNameIsQualified = suppliedApplyName?.includes(".") === true;
|
||||
const applyName = qualifiedByContext ? quoteCompletionApplyIdentifier(table.name, dialect) : ambiguousTableName && !!table.schema && (!suppliedApplyName || !suppliedApplyNameIsQualified) ? defaultApplyName : (suppliedApplyName ?? defaultApplyName);
|
||||
const alias = autoAliasTables ? generateTableCompletionAlias(table.name, existingAliases) : "";
|
||||
|
||||
+57
@@ -995,6 +995,63 @@ test("replaces typed Unicode prefixes through semantic SQL Server completion", (
|
||||
}
|
||||
});
|
||||
|
||||
test("brackets only SQL Server completion identifiers that require delimiters", () => {
|
||||
const sql = "select * from ";
|
||||
const items = buildSqlCompletionItems(sql, sql.length, {
|
||||
tables: [
|
||||
{ name: "Orders", schema: "dbo", type: "table" },
|
||||
{ name: "名称", schema: "dbo", type: "table" },
|
||||
{ name: "04保险事前", schema: "dbo", type: "table" },
|
||||
{ name: "含]括号", schema: "dbo", type: "table" },
|
||||
{ name: "BACKUP", schema: "dbo", type: "table" },
|
||||
],
|
||||
columnsByTable: new Map(),
|
||||
databaseType: "sqlserver",
|
||||
dialect: "sqlserver",
|
||||
});
|
||||
|
||||
assert.deepEqual(Object.fromEntries(items.filter((item) => item.type === "table").map((item) => [item.label, item.apply])), {
|
||||
Orders: "Orders",
|
||||
名称: "名称",
|
||||
"04保险事前": "[04保险事前]",
|
||||
"含]括号": "[含]]括号]",
|
||||
BACKUP: "[BACKUP]",
|
||||
});
|
||||
});
|
||||
|
||||
test("quotes qualified SQL Server table apply names", () => {
|
||||
const sql = "select * from ";
|
||||
const items = buildSqlCompletionItems(sql, sql.length, {
|
||||
tables: [
|
||||
{ name: "04保险事前", schema: "dbo", type: "table", applyName: "dbo.04保险事前" },
|
||||
{ name: "04归档", schema: "dbo", type: "table", applyName: "dbo.[04归档]" },
|
||||
{ name: "04省略模式", schema: "dbo", type: "table", applyName: "datacenter..04省略模式" },
|
||||
{ name: "含].括号", schema: "dbo", type: "table", applyName: "dbo.[含]].括号]" },
|
||||
],
|
||||
columnsByTable: new Map(),
|
||||
databaseType: "sqlserver",
|
||||
dialect: "sqlserver",
|
||||
});
|
||||
|
||||
assert.equal(items.find((item) => item.label === "04保险事前")?.apply, "dbo.[04保险事前]");
|
||||
assert.equal(items.find((item) => item.label === "04归档")?.apply, "dbo.[04归档]");
|
||||
assert.equal(items.find((item) => item.label === "04省略模式")?.apply, "datacenter..[04省略模式]");
|
||||
assert.equal(items.find((item) => item.label === "含].括号")?.apply, "dbo.[含]].括号]");
|
||||
});
|
||||
|
||||
test("quotes qualified SQL Server routine apply names", () => {
|
||||
const sql = "select run";
|
||||
const items = buildSqlCompletionItems(sql, sql.length, {
|
||||
tables: [],
|
||||
objects: [{ name: "run_report", schema: "dbo", type: "procedure", applyName: "dbo.04备份" }],
|
||||
columnsByTable: new Map(),
|
||||
databaseType: "sqlserver",
|
||||
dialect: "sqlserver",
|
||||
});
|
||||
|
||||
assert.equal(items.find((item) => item.label === "run_report")?.apply, "dbo.[04备份]()");
|
||||
});
|
||||
|
||||
test("replaces a Unicode prefix inside an open SQL Server bracket identifier", () => {
|
||||
const sql = "select * from test where [名";
|
||||
const cursor = sql.length;
|
||||
|
||||
Reference in New Issue
Block a user