mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-24 15:56:50 +00:00
175 lines
3.1 KiB
TypeScript
175 lines
3.1 KiB
TypeScript
/**
|
|
* Utilities for SQL identifier navigation (Ctrl/Cmd + click on table/column names).
|
|
*/
|
|
|
|
const SQL_KEYWORDS_SET = new Set([
|
|
"select",
|
|
"from",
|
|
"where",
|
|
"join",
|
|
"left",
|
|
"right",
|
|
"inner",
|
|
"outer",
|
|
"on",
|
|
"group",
|
|
"by",
|
|
"order",
|
|
"asc",
|
|
"desc",
|
|
"having",
|
|
"limit",
|
|
"offset",
|
|
"insert",
|
|
"into",
|
|
"values",
|
|
"update",
|
|
"set",
|
|
"delete",
|
|
"create",
|
|
"table",
|
|
"view",
|
|
"as",
|
|
"and",
|
|
"or",
|
|
"not",
|
|
"in",
|
|
"is",
|
|
"null",
|
|
"like",
|
|
"distinct",
|
|
"union",
|
|
"all",
|
|
"exists",
|
|
"between",
|
|
"case",
|
|
"when",
|
|
"then",
|
|
"else",
|
|
"end",
|
|
"count",
|
|
"sum",
|
|
"avg",
|
|
"min",
|
|
"max",
|
|
"coalesce",
|
|
"cast",
|
|
"alter",
|
|
"drop",
|
|
"add",
|
|
"column",
|
|
"index",
|
|
"primary",
|
|
"key",
|
|
"foreign",
|
|
"references",
|
|
"constraint",
|
|
"default",
|
|
"check",
|
|
"unique",
|
|
"begin",
|
|
"commit",
|
|
"rollback",
|
|
"truncate",
|
|
"explain",
|
|
"analyze",
|
|
"with",
|
|
"recursive",
|
|
"over",
|
|
"partition",
|
|
"row_number",
|
|
"rank",
|
|
"dense_rank",
|
|
"lag",
|
|
"lead",
|
|
"first_value",
|
|
"last_value",
|
|
"ntile",
|
|
"cross",
|
|
"full",
|
|
"natural",
|
|
"using",
|
|
"lateral",
|
|
"unnest",
|
|
"filter",
|
|
"exclude",
|
|
"replace",
|
|
"qualify",
|
|
"pivot",
|
|
"unpivot",
|
|
"asof",
|
|
"positional",
|
|
"anti",
|
|
"semi",
|
|
"sample",
|
|
"struct",
|
|
"map",
|
|
"list",
|
|
"array",
|
|
"lambda",
|
|
"copy",
|
|
"export",
|
|
"import",
|
|
"describe",
|
|
"show",
|
|
"summarize",
|
|
"pragma",
|
|
"tablesample",
|
|
"read_csv",
|
|
"read_parquet",
|
|
"read_json",
|
|
"list_transform",
|
|
]);
|
|
|
|
/** Extract identifier at position `pos` in the document. */
|
|
export function extractIdentifierAt(doc: string, pos: number): string | null {
|
|
if (pos < 0 || pos > doc.length) return null;
|
|
|
|
const char = doc[pos];
|
|
const idChar = (c: string) => /^[A-Za-z0-9_]$/.test(c);
|
|
|
|
// Backtick-quoted: `identifier`
|
|
if (char === "`") {
|
|
let start = pos;
|
|
while (start > 0 && doc[start - 1] === "`") start--;
|
|
const end = doc.indexOf("`", start + 1);
|
|
if (end < 0) return null;
|
|
return doc.slice(start + 1, end);
|
|
}
|
|
|
|
// Double-quoted: "identifier"
|
|
if (char === '"') {
|
|
let start = pos;
|
|
while (start > 0 && doc[start - 1] === '"') start--;
|
|
const end = doc.indexOf('"', start + 1);
|
|
if (end < 0) return null;
|
|
return doc.slice(start + 1, end);
|
|
}
|
|
|
|
// Unquoted identifier (may be qualified like schema.table)
|
|
if (!idChar(char) && char !== ".") return null;
|
|
|
|
let start = pos;
|
|
while (start > 0 && (idChar(doc[start - 1]) || doc[start - 1] === ".")) start--;
|
|
let end = pos;
|
|
while (end < doc.length && (idChar(doc[end]) || doc[end] === ".")) end++;
|
|
|
|
const result = doc.slice(start, end);
|
|
if (!/[A-Za-z0-9_]/.test(result)) return null;
|
|
return result;
|
|
}
|
|
|
|
/** Check whether the identifier is a SQL keyword (not a table/column name). */
|
|
export function isSqlKeyword(identifier: string): boolean {
|
|
return SQL_KEYWORDS_SET.has(identifier.toLowerCase());
|
|
}
|
|
|
|
/** Match identifier against known table names (case-insensitive). */
|
|
export function matchTable(
|
|
identifier: string,
|
|
tables: Array<{ name: string; schema?: string }>,
|
|
): { name: string; schema?: string } | null {
|
|
const lower = identifier.toLowerCase();
|
|
return tables.find((t) => t.name.toLowerCase() === lower) ?? null;
|
|
}
|