feat(sqlCompletion): 优化表别名建议功能,避免使用 SQL 关键字 (#2805)
This commit is contained in:
parent
8364f468a0
commit
7eff6e544b
|
|
@ -1,3 +1,4 @@
|
|||
import { Cassandra, MariaSQL, MSSQL, MySQL, PLSQL, PostgreSQL, SQLite, StandardSQL } from "@codemirror/lang-sql";
|
||||
import type { DatabaseType, SqlSnippet } from "@/types/database";
|
||||
import { buildMongoCompletionItemsFromContext, type MongoCompletionItem } from "@/lib/mongo/mongoCompletion";
|
||||
|
||||
|
|
@ -1064,6 +1065,15 @@ const SQL_ALIAS_RESERVED_WORDS = new Set([
|
|||
"with",
|
||||
]);
|
||||
|
||||
const SQL_ALIAS_KEYWORD_WORDS = new Set(sqlAliasKeywordWords(SQL_KEYWORDS.join(" "), StandardSQL.spec.keywords, MySQL.spec.keywords, MariaSQL.spec.keywords, PostgreSQL.spec.keywords, MSSQL.spec.keywords, SQLite.spec.keywords, PLSQL.spec.keywords, Cassandra.spec.keywords));
|
||||
|
||||
function sqlAliasKeywordWords(...sources: Array<string | undefined>): string[] {
|
||||
return sources
|
||||
.flatMap((source) => (source ?? "").split(/\s+/))
|
||||
.filter((keyword) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(keyword))
|
||||
.map((keyword) => keyword.toLowerCase());
|
||||
}
|
||||
|
||||
export interface SqlCompletionTable {
|
||||
name: string;
|
||||
schema?: string;
|
||||
|
|
@ -3045,7 +3055,7 @@ function generateTableCompletionAlias(tableName: string, existing = new Set<stri
|
|||
const candidates = buildAliasCandidates(tableName);
|
||||
|
||||
for (const candidate of candidates.filter(Boolean)) {
|
||||
if (SQL_ALIAS_RESERVED_WORDS.has(candidate.toLowerCase())) continue;
|
||||
if (isUnsafeSqlAlias(candidate.toLowerCase())) continue;
|
||||
if (!existing.has(candidate.toLowerCase())) return candidate;
|
||||
for (let index = 2; index < 100; index++) {
|
||||
const numbered = `${candidate}${index}`;
|
||||
|
|
@ -3081,7 +3091,11 @@ function buildAliasCandidates(tableName: string): string[] {
|
|||
|
||||
function aliasConflicts(candidate: string, existing: Set<string>): boolean {
|
||||
const lower = candidate.toLowerCase();
|
||||
return existing.has(lower) || SQL_ALIAS_RESERVED_WORDS.has(lower);
|
||||
return existing.has(lower) || isUnsafeSqlAlias(lower);
|
||||
}
|
||||
|
||||
function isUnsafeSqlAlias(candidate: string): boolean {
|
||||
return SQL_ALIAS_RESERVED_WORDS.has(candidate) || SQL_ALIAS_KEYWORD_WORDS.has(candidate);
|
||||
}
|
||||
|
||||
function isFollowedByJoin(beforeToken: string): boolean {
|
||||
|
|
|
|||
|
|
@ -1832,6 +1832,50 @@ test("automatic table aliases avoid reserved words", () => {
|
|||
assert.equal(tableItem!.apply, "orders AS ord");
|
||||
});
|
||||
|
||||
test("table alias suggestions avoid SQL keywords", () => {
|
||||
const items = buildSqlCompletionItems("select * from item_file ", "select * from item_file ".length, {
|
||||
tables: [{ name: "item_file", schema: "public", type: "table" }],
|
||||
columnsByTable,
|
||||
});
|
||||
|
||||
const aliasItem = items.find((item) => item.type === "snippet" && item.detail === "alias for item_file");
|
||||
assert.ok(aliasItem);
|
||||
assert.notEqual(aliasItem!.apply, "AS if ");
|
||||
assert.equal(aliasItem!.apply, "AS it ");
|
||||
});
|
||||
|
||||
test("automatic table aliases avoid SQL keywords", () => {
|
||||
const cases: Array<[string, string]> = [
|
||||
["account_store", "account_store AS ac"],
|
||||
["account_type", "account_type AS ac"],
|
||||
["data_order", "data_order AS da"],
|
||||
["invoice_note", "invoice_note AS inv"],
|
||||
["item_file", "item_file AS it"],
|
||||
["item_status", "item_status AS it"],
|
||||
["new_order", "new_order AS ne"],
|
||||
["no_config", "no_config AS nc"],
|
||||
["order_node", "order_node AS ord"],
|
||||
["order_flow", "order_flow AS ord"],
|
||||
["order_region", "order_region AS ord"],
|
||||
["row_value", "row_value AS rv"],
|
||||
["use_case", "use_case AS uc"],
|
||||
["user_role", "user_role AS ur"],
|
||||
];
|
||||
|
||||
for (const [tableName, expectedApply] of cases) {
|
||||
const sql = `select * from ${tableName.slice(0, 3)}`;
|
||||
const items = buildSqlCompletionItems(sql, sql.length, {
|
||||
tables: [{ name: tableName, schema: "public", type: "table" }],
|
||||
columnsByTable,
|
||||
autoAliasTables: true,
|
||||
});
|
||||
|
||||
const tableItem = items.find((item) => item.type === "table" && item.label === tableName);
|
||||
assert.ok(tableItem, `should suggest ${tableName}`);
|
||||
assert.equal(tableItem!.apply, expectedApply);
|
||||
}
|
||||
});
|
||||
|
||||
test("table alias suggestions avoid existing aliases", () => {
|
||||
const items = buildSqlCompletionItems("select * from customer_orders co join customer_orders ", "select * from customer_orders co join customer_orders ".length, {
|
||||
tables: [...tables, { name: "customer_orders", schema: "public", type: "table" }],
|
||||
|
|
|
|||
Loading…
Reference in New Issue