fix(editor): resolve JOIN tables for navigation
This commit is contained in:
parent
e1d6d1224e
commit
024a4aedd6
|
|
@ -16,10 +16,10 @@ import type {
|
|||
SqlSemanticToken,
|
||||
} from "@/lib/sql/semantic/types";
|
||||
|
||||
const TABLE_INTRODUCERS = new Set(["from", "join", "update", "into", "using", "apply"]);
|
||||
const TABLE_INTRODUCERS = new Set(["from", "join", "straight_join", "update", "into", "using", "apply"]);
|
||||
const JOIN_MODIFIERS = new Set(["left", "right", "inner", "outer", "cross", "full", "natural"]);
|
||||
const CLAUSE_BOUNDARIES = new Set(["where", "group", "having", "order", "limit", "offset", "union", "intersect", "except", "on", "set", "values", "returning"]);
|
||||
const ALIAS_BLACKLIST = new Set([...CLAUSE_BOUNDARIES, "join", "left", "right", "inner", "outer", "cross", "full", "natural", "as", "select", "from"]);
|
||||
const ALIAS_BLACKLIST = new Set([...CLAUSE_BOUNDARIES, "join", "straight_join", "left", "right", "inner", "outer", "cross", "full", "natural", "as", "select", "from"]);
|
||||
|
||||
interface ParseState {
|
||||
dialect: SqlSemanticDialectAdapter;
|
||||
|
|
|
|||
|
|
@ -2205,6 +2205,7 @@ function extractReferencedTables(sql: string): SqlCompletionReferencedTable[] {
|
|||
"select",
|
||||
"from",
|
||||
"join",
|
||||
"straight_join",
|
||||
"left",
|
||||
"right",
|
||||
"inner",
|
||||
|
|
@ -2287,11 +2288,16 @@ function extractReferencedTables(sql: string): SqlCompletionReferencedTable[] {
|
|||
"respect",
|
||||
]);
|
||||
|
||||
const pattern = /\b(?:from|join|update|apply)\s+((?:"[^"]+"|`[^`]+`|[^\s,;()]+)(?:\.(?:"[^"]+"|`[^`]+`|[^\s,;()]+))?)(?:\s+(?:as\s+)?([A-Za-z_][\w$]*))?/gi;
|
||||
// STRAIGHT_JOIN is a standalone MySQL table introducer, not a modifier followed by JOIN.
|
||||
const pattern = /\b(?:from|join|straight_join|update|apply)\s+((?:"[^"]+"|`[^`]+`|[^\s,;()]+)(?:\.(?:"[^"]+"|`[^`]+`|[^\s,;()]+))?)(?:\s+(?:as\s+)?([A-Za-z_][\w$]*))?/gi;
|
||||
const referenced: SqlCompletionReferencedTable[] = [];
|
||||
for (const match of sql.matchAll(pattern)) {
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = pattern.exec(sql)) !== null) {
|
||||
const rawName = match[1];
|
||||
const alias = match[2];
|
||||
if (alias && ALIAS_BLACKLIST.has(alias.toLowerCase())) {
|
||||
pattern.lastIndex = match.index + match[0].length - alias.length;
|
||||
}
|
||||
const quotedName = !!rawName && (rawName.startsWith('"') || rawName.startsWith("`"));
|
||||
if (!quotedName && rawName && ALIAS_BLACKLIST.has(rawName.toLowerCase())) continue;
|
||||
// Filter out SQL keywords that accidentally matched as aliases
|
||||
|
|
|
|||
|
|
@ -967,6 +967,87 @@ test("keeps completed references while removing active JOIN table prefixes", ()
|
|||
);
|
||||
});
|
||||
|
||||
test("extracts JOIN tables without explicit aliases", () => {
|
||||
const sql = "select * from a join b on a.id = b.id";
|
||||
const context = getSqlCompletionContext(sql, sql.length);
|
||||
|
||||
assert.deepEqual(
|
||||
context.referencedTables.map((table) => table.name),
|
||||
["a", "b"],
|
||||
);
|
||||
});
|
||||
|
||||
test("extracts MySQL backtick-qualified tables across a JOIN", () => {
|
||||
const sql = [
|
||||
"select",
|
||||
" `jobdb`.`job_application_ats_process`.`process_id`,",
|
||||
" count(*)",
|
||||
"from",
|
||||
" `jobdb`.`job_application`",
|
||||
"join `jobdb`.`job_application_ats_process` on",
|
||||
" `jobdb`.`job_application`.`id` = `jobdb`.`job_application_ats_process`.`app_id`",
|
||||
].join("\n");
|
||||
const context = getSqlCompletionContext(sql, sql.length);
|
||||
|
||||
assert.deepEqual(
|
||||
context.referencedTables.map(({ schema, name }) => ({ schema, name })),
|
||||
[
|
||||
{ schema: "jobdb", name: "job_application" },
|
||||
{ schema: "jobdb", name: "job_application_ats_process" },
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("extracts every table across consecutive JOINs", () => {
|
||||
const sql = "select * from db.a join db.b on 1=1 join db.c on 2=2";
|
||||
const context = getSqlCompletionContext(sql, sql.length);
|
||||
|
||||
assert.deepEqual(
|
||||
context.referencedTables.map(({ schema, name }) => ({ schema, name })),
|
||||
[
|
||||
{ schema: "db", name: "a" },
|
||||
{ schema: "db", name: "b" },
|
||||
{ schema: "db", name: "c" },
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("extracts tables across a MySQL STRAIGHT_JOIN", () => {
|
||||
const sql = "select * from db.a straight_join db.b on db.a.id = db.b.id";
|
||||
const context = getSqlCompletionContext(sql, sql.length);
|
||||
|
||||
assert.deepEqual(
|
||||
context.referencedTables.map(({ schema, name, alias }) => ({ schema, name, alias })),
|
||||
[
|
||||
{ schema: "db", name: "a", alias: undefined },
|
||||
{ schema: "db", name: "b", alias: undefined },
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("keeps explicit table aliases across a JOIN", () => {
|
||||
const sql = "select * from db.a x join db.b y";
|
||||
const context = getSqlCompletionContext(sql, sql.length);
|
||||
|
||||
assert.deepEqual(
|
||||
context.referencedTables.map(({ schema, name, alias }) => ({ schema, name, alias })),
|
||||
[
|
||||
{ schema: "db", name: "a", alias: "x" },
|
||||
{ schema: "db", name: "b", alias: "y" },
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("does not treat WHERE as a table alias", () => {
|
||||
const sql = "select * from a where id = 1";
|
||||
const context = getSqlCompletionContext(sql, sql.length);
|
||||
|
||||
assert.deepEqual(
|
||||
context.referencedTables.map(({ name, alias }) => ({ name, alias })),
|
||||
[{ name: "a", alias: undefined }],
|
||||
);
|
||||
});
|
||||
|
||||
test("ranks exact table matches above prefix and fuzzy matches", () => {
|
||||
const items = buildSqlCompletionItems("select * from toh", "select * from toh".length, {
|
||||
tables: [
|
||||
|
|
|
|||
Loading…
Reference in New Issue