sqllens 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +0 -10
- package/README.md +95 -85
- package/THIRD-PARTY-NOTICES.md +70 -5
- package/dist/api.d.ts +5 -3
- package/dist/api.js +17 -4
- package/dist/bigquery/behavior.d.ts +2 -0
- package/dist/bigquery/behavior.js +20 -0
- package/dist/bigquery/dot-path.d.ts +0 -2
- package/dist/bigquery/dot-path.js +0 -1
- package/dist/bigquery/fold.d.ts +8 -0
- package/dist/bigquery/fold.js +39 -0
- package/dist/bigquery/index.d.ts +7 -0
- package/dist/bigquery/index.js +10 -0
- package/dist/{infer/bigquery.d.ts → bigquery/infer.d.ts} +2 -2
- package/dist/{infer/bigquery.js → bigquery/infer.js} +4 -3
- package/dist/bigquery/lower.js +35 -12
- package/dist/bigquery/signatures.generated.d.ts +6 -0
- package/dist/bigquery/signatures.generated.js +1068 -0
- package/dist/completion/atn-walk.d.ts +13 -2
- package/dist/completion/atn-walk.js +13 -10
- package/dist/completion/complete.d.ts +4 -3
- package/dist/completion/complete.js +101 -45
- package/dist/completion/config.js +66 -0
- package/dist/completion/jinja-slot.d.ts +24 -0
- package/dist/completion/jinja-slot.js +126 -0
- package/dist/completion/parser-factory.d.ts +13 -1
- package/dist/completion/parser-factory.js +48 -0
- package/dist/databricks/behavior.d.ts +2 -0
- package/dist/databricks/behavior.js +19 -0
- package/dist/databricks/fold.d.ts +8 -0
- package/dist/databricks/fold.js +27 -0
- package/dist/databricks/index.d.ts +7 -0
- package/dist/databricks/index.js +10 -0
- package/dist/databricks/infer.d.ts +6 -0
- package/dist/databricks/infer.js +638 -0
- package/dist/databricks/signatures.generated.d.ts +6 -0
- package/dist/databricks/signatures.generated.js +1745 -0
- package/dist/derived-dialects.js +19 -1
- package/dist/dialect-behavior/behavior.d.ts +26 -0
- package/dist/dialect-behavior/behavior.js +1 -0
- package/dist/dialect-behavior/carrier.d.ts +5 -0
- package/dist/dialect-behavior/carrier.js +5 -0
- package/dist/dialect-behavior/coerce-rules.d.ts +7 -0
- package/dist/dialect-behavior/coerce-rules.js +69 -0
- package/dist/dialect-behavior/public-fold.d.ts +6 -0
- package/dist/dialect-behavior/public-fold.js +9 -0
- package/dist/dialect-behavior/registry.d.ts +6 -0
- package/dist/dialect-behavior/registry.js +34 -0
- package/dist/dialect-symbols.js +22 -17
- package/dist/dialect.d.ts +2 -2
- package/dist/document/document.d.ts +1 -1
- package/dist/document/document.js +8 -7
- package/dist/duckdb/behavior.d.ts +2 -0
- package/dist/duckdb/behavior.js +21 -0
- package/dist/duckdb/fold.d.ts +8 -0
- package/dist/duckdb/fold.js +29 -0
- package/dist/duckdb/index.d.ts +7 -0
- package/dist/duckdb/index.js +10 -0
- package/dist/{infer/duckdb.d.ts → duckdb/infer.d.ts} +2 -2
- package/dist/{infer/duckdb.js → duckdb/infer.js} +4 -3
- package/dist/duckdb/lower.js +47 -15
- package/dist/duckdb/signatures.generated.d.ts +6 -0
- package/dist/duckdb/signatures.generated.js +1072 -0
- package/dist/generated/bigquery/GoogleSQLParser.js +0 -7060
- package/dist/generated/databricks/DatabricksParser.js +0 -4800
- package/dist/generated/duckdb/DuckdbParser.js +0 -9100
- package/dist/generated/minijinja/MinijinjaParser.js +0 -380
- package/dist/generated/mysql/MysqlLexer.js +7357 -0
- package/dist/generated/mysql/MysqlParser.js +78520 -0
- package/dist/generated/postgres/PostgresParser.js +0 -8530
- package/dist/generated/redshift/RedshiftParser.js +0 -10970
- package/dist/generated/snowflake/SnowflakeParser.js +0 -7320
- package/dist/generated/sqlite/SqliteLexer.js +945 -0
- package/dist/generated/sqlite/SqliteParser.js +14682 -0
- package/dist/generated/trino/TrinoParser.js +0 -3770
- package/dist/generated/tsql/TSqlParser.js +0 -8420
- package/dist/ident/fold.d.ts +24 -15
- package/dist/ident/fold.js +12 -145
- package/dist/index.d.ts +6 -2
- package/dist/index.js +14 -8
- package/dist/infer/functions.d.ts +22 -11
- package/dist/infer/functions.js +26 -888
- package/dist/infer/infer.js +16 -14
- package/dist/infer/nullability.js +3 -4
- package/dist/infer/types.d.ts +1 -1
- package/dist/infer/types.js +7 -7
- package/dist/ir/ir.d.ts +26 -17
- package/dist/ir/part-span.d.ts +16 -1
- package/dist/ir/part-span.js +38 -10
- package/dist/ir/span.js +0 -2
- package/dist/ir/walk.js +3 -3
- package/dist/lineage/hops.js +13 -10
- package/dist/lineage/lineage.js +10 -7
- package/dist/minijinja/apply-tags.d.ts +14 -7
- package/dist/minijinja/apply-tags.js +66 -121
- package/dist/minijinja/parse.js +6 -6
- package/dist/minijinja/tag-ast.d.ts +29 -36
- package/dist/minijinja/tag-ast.js +210 -90
- package/dist/mysql/behavior.d.ts +2 -0
- package/dist/mysql/behavior.js +21 -0
- package/dist/mysql/fold.d.ts +8 -0
- package/dist/mysql/fold.js +49 -0
- package/dist/mysql/index.d.ts +7 -0
- package/dist/mysql/index.js +10 -0
- package/dist/mysql/infer.d.ts +20 -0
- package/dist/mysql/infer.js +156 -0
- package/dist/mysql/lower.d.ts +13 -0
- package/dist/mysql/lower.js +1443 -0
- package/dist/mysql/parse.d.ts +10 -0
- package/dist/mysql/parse.js +70 -0
- package/dist/mysql/signatures.generated.d.ts +6 -0
- package/dist/mysql/signatures.generated.js +508 -0
- package/dist/postgres/behavior.d.ts +2 -0
- package/dist/postgres/behavior.js +19 -0
- package/dist/postgres/fold.d.ts +8 -0
- package/dist/postgres/fold.js +30 -0
- package/dist/postgres/index.d.ts +7 -0
- package/dist/postgres/index.js +10 -0
- package/dist/{infer/postgres.d.ts → postgres/infer.d.ts} +2 -2
- package/dist/{infer/postgres.js → postgres/infer.js} +4 -3
- package/dist/postgres/lower.js +2 -2
- package/dist/postgres/signatures.generated.d.ts +6 -0
- package/dist/postgres/signatures.generated.js +2973 -0
- package/dist/qualify/check-calls.js +80 -134
- package/dist/qualify/qualify.js +16 -14
- package/dist/qualify/schema-provider.js +2 -2
- package/dist/qualify/schema.js +3 -3
- package/dist/qualify/template-provider.d.ts +45 -12
- package/dist/qualify/template-provider.js +69 -38
- package/dist/redshift/behavior.d.ts +2 -0
- package/dist/redshift/behavior.js +19 -0
- package/dist/redshift/fold.d.ts +8 -0
- package/dist/redshift/fold.js +31 -0
- package/dist/redshift/index.d.ts +7 -0
- package/dist/redshift/index.js +10 -0
- package/dist/{infer/redshift.d.ts → redshift/infer.d.ts} +2 -2
- package/dist/{infer/redshift.js → redshift/infer.js} +4 -3
- package/dist/redshift/lower.js +2 -2
- package/dist/redshift/signatures.generated.d.ts +6 -0
- package/dist/redshift/signatures.generated.js +757 -0
- package/dist/references/references.js +17 -12
- package/dist/scope/like-pattern.d.ts +2 -0
- package/dist/scope/like-pattern.js +15 -0
- package/dist/scope/scope.d.ts +5 -5
- package/dist/scope/scope.js +48 -42
- package/dist/sema/resolve.js +17 -12
- package/dist/session.d.ts +2 -2
- package/dist/signature/signature.d.ts +14 -6
- package/dist/signature/signature.js +30 -22
- package/dist/signature/signatures.d.ts +14 -12
- package/dist/signature/signatures.js +42 -582
- package/dist/snowflake/behavior.d.ts +2 -0
- package/dist/snowflake/behavior.js +22 -0
- package/dist/snowflake/fold.d.ts +8 -0
- package/dist/snowflake/fold.js +25 -0
- package/dist/snowflake/index.d.ts +7 -0
- package/dist/snowflake/index.js +10 -0
- package/dist/{infer/snowflake.d.ts → snowflake/infer.d.ts} +2 -2
- package/dist/{infer/snowflake.js → snowflake/infer.js} +4 -3
- package/dist/snowflake/lower.js +59 -19
- package/dist/snowflake/signatures.generated.d.ts +6 -0
- package/dist/snowflake/signatures.generated.js +2080 -0
- package/dist/sqlite/behavior.d.ts +2 -0
- package/dist/sqlite/behavior.js +19 -0
- package/dist/sqlite/fold.d.ts +8 -0
- package/dist/sqlite/fold.js +41 -0
- package/dist/sqlite/index.d.ts +7 -0
- package/dist/sqlite/index.js +10 -0
- package/dist/sqlite/infer.d.ts +12 -0
- package/dist/sqlite/infer.js +122 -0
- package/dist/sqlite/lower.d.ts +11 -0
- package/dist/sqlite/lower.js +1093 -0
- package/dist/sqlite/parse.d.ts +10 -0
- package/dist/sqlite/parse.js +70 -0
- package/dist/sqlite/signatures.generated.d.ts +6 -0
- package/dist/sqlite/signatures.generated.js +277 -0
- package/dist/symbols/symbols.js +14 -12
- package/dist/token/classify.js +31 -0
- package/dist/token/tokenize.js +4 -0
- package/dist/trino/behavior.d.ts +2 -0
- package/dist/trino/behavior.js +21 -0
- package/dist/trino/fold.d.ts +8 -0
- package/dist/trino/fold.js +39 -0
- package/dist/trino/index.d.ts +7 -0
- package/dist/trino/index.js +10 -0
- package/dist/{infer/trino.d.ts → trino/infer.d.ts} +2 -2
- package/dist/{infer/trino.js → trino/infer.js} +4 -3
- package/dist/trino/lower.js +5 -5
- package/dist/trino/signatures.generated.d.ts +6 -0
- package/dist/trino/signatures.generated.js +968 -0
- package/dist/tsql/behavior.d.ts +2 -0
- package/dist/tsql/behavior.js +20 -0
- package/dist/tsql/fold.d.ts +8 -0
- package/dist/tsql/fold.js +34 -0
- package/dist/tsql/index.d.ts +7 -0
- package/dist/tsql/index.js +10 -0
- package/dist/tsql/infer.d.ts +16 -0
- package/dist/tsql/infer.js +289 -0
- package/dist/tsql/lower.js +4 -4
- package/dist/tsql/signatures.generated.d.ts +6 -0
- package/dist/tsql/signatures.generated.js +640 -0
- package/package.json +15 -11
- package/dist/generated/bigquery/GoogleSQLLexer.d.ts +0 -407
- package/dist/generated/bigquery/GoogleSQLParser.d.ts +0 -9558
- package/dist/generated/bigquery/GoogleSQLParserListener.d.ts +0 -7777
- package/dist/generated/bigquery/GoogleSQLParserListener.js +0 -7070
- package/dist/generated/databricks/DatabricksLexer.d.ts +0 -566
- package/dist/generated/databricks/DatabricksParser.d.ts +0 -7771
- package/dist/generated/databricks/DatabricksParserListener.d.ts +0 -5737
- package/dist/generated/databricks/DatabricksParserListener.js +0 -5256
- package/dist/generated/duckdb/DuckdbLexer.d.ts +0 -691
- package/dist/generated/duckdb/DuckdbParser.d.ts +0 -13932
- package/dist/generated/duckdb/DuckdbParserListener.d.ts +0 -10049
- package/dist/generated/duckdb/DuckdbParserListener.js +0 -9138
- package/dist/generated/minijinja/MinijinjaLexer.d.ts +0 -108
- package/dist/generated/minijinja/MinijinjaParser.d.ts +0 -604
- package/dist/generated/minijinja/MinijinjaParserListener.d.ts +0 -449
- package/dist/generated/minijinja/MinijinjaParserListener.js +0 -410
- package/dist/generated/postgres/PostgresLexer.d.ts +0 -663
- package/dist/generated/postgres/PostgresParser.d.ts +0 -12963
- package/dist/generated/postgres/PostgresParserListener.d.ts +0 -9408
- package/dist/generated/postgres/PostgresParserListener.js +0 -8554
- package/dist/generated/redshift/RedshiftLexer.d.ts +0 -954
- package/dist/generated/redshift/RedshiftParser.d.ts +0 -16939
- package/dist/generated/redshift/RedshiftParserListener.d.ts +0 -12092
- package/dist/generated/redshift/RedshiftParserListener.js +0 -10994
- package/dist/generated/snowflake/SnowflakeLexer.d.ts +0 -1046
- package/dist/generated/snowflake/SnowflakeParser.d.ts +0 -14196
- package/dist/generated/snowflake/SnowflakeParserListener.d.ts +0 -8063
- package/dist/generated/snowflake/SnowflakeParserListener.js +0 -7330
- package/dist/generated/trino/TrinoLexer.d.ts +0 -381
- package/dist/generated/trino/TrinoParser.d.ts +0 -5340
- package/dist/generated/trino/TrinoParserListener.d.ts +0 -4704
- package/dist/generated/trino/TrinoParserListener.js +0 -4326
- package/dist/generated/tsql/TSqlLexer.d.ts +0 -1278
- package/dist/generated/tsql/TSqlParser.d.ts +0 -17267
- package/dist/generated/tsql/TSqlParserListener.d.ts +0 -9697
- package/dist/generated/tsql/TSqlParserListener.js +0 -8854
- package/dist/infer/dialect.d.ts +0 -21
- package/dist/infer/dialect.js +0 -74
- package/dist/infer/literals.d.ts +0 -6
- package/dist/infer/literals.js +0 -44
- package/dist/signature/generated/tsql.d.ts +0 -3
- package/dist/signature/generated/tsql.js +0 -260
|
@@ -0,0 +1,1093 @@
|
|
|
1
|
+
import { ParserRuleContext, TerminalNode } from "antlr4ng";
|
|
2
|
+
import { SqliteParser as P } from "../generated/sqlite/SqliteParser.js";
|
|
3
|
+
import { keywordCategory, swallowedCategories, swallowedStatements } from "../ir/statement.js";
|
|
4
|
+
import { partSpansOf } from "../ir/part-span.js";
|
|
5
|
+
import { freezeIR } from "../ir/freeze.js";
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Lowering — SQLite (grammars-v4 sql/sqlite fork, Martin Mirchev's precedence-
|
|
8
|
+
// cascade `expr` variant) CST -> the shared, dialect-neutral IR (src/ir/ir.ts).
|
|
9
|
+
// The semantic layer runs on the IR unchanged; only this file knows SQLite's
|
|
10
|
+
// grammar. Core query path: select_stmt, select_core, join_clause,
|
|
11
|
+
// table_or_subquery, result_column, and the expr_or→…→expr_base cascade.
|
|
12
|
+
//
|
|
13
|
+
// Navigation is by rule index against the generated parser, never by string
|
|
14
|
+
// comparison of rule names. Nested `select_stmt` nodes belong to their own
|
|
15
|
+
// scope, so shallow walks never descend into them.
|
|
16
|
+
//
|
|
17
|
+
// Identifier-delimiter contract (docs/identifier-delimiter-contract.md): SQLite
|
|
18
|
+
// follows the "kept" convention shared by every dialect except BigQuery — every
|
|
19
|
+
// identifier field (ColumnRef.parts / TableSource.name / TableSource.alias /
|
|
20
|
+
// CteDef.name / Projection.name) carries its RAW text with quoting delimiters
|
|
21
|
+
// intact (SQLite quotes are "double", [brackets], `backticks`). Case-folding
|
|
22
|
+
// and delimiter-stripping for identity/display happen downstream in
|
|
23
|
+
// src/ident/fold.ts (foldIdentifier / displayName), never here.
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// sqlite.org/lang_aggfunc.html + lang_corefunc's aggregate list — used only to set the
|
|
26
|
+
// `aggregate` heuristic flag (which feeds SelectExpr.aggregated); type inference is separate.
|
|
27
|
+
const AGGREGATES = new Set(["count", "sum", "total", "avg", "min", "max", "group_concat", "string_agg"]);
|
|
28
|
+
/** Lower a parsed SQLite file (`parse`: a `;`-separated sql_stmt_list) into the IR. A single
|
|
29
|
+
* SELECT/VALUES statement lowers fully; anything else (DDL/DML/pragma/utility, multi-statement
|
|
30
|
+
* batches, EXPLAIN) becomes a flagged non-query body — a valid parse never throws. */
|
|
31
|
+
export function lower(tree) {
|
|
32
|
+
const q = lowerImpl(tree);
|
|
33
|
+
q.dialect = "sqlite";
|
|
34
|
+
return freezeIR(q);
|
|
35
|
+
}
|
|
36
|
+
function lowerImpl(tree) {
|
|
37
|
+
const statement = statementCategory(tree);
|
|
38
|
+
const list = firstOfRule(tree, P.RULE_sql_stmt_list);
|
|
39
|
+
const stmts = list ? directChildrenOfRule(list, P.RULE_sql_stmt) : [];
|
|
40
|
+
// Recovery-swallowed statements count toward batch-ness: a broken statement makes recovery dump
|
|
41
|
+
// the rest of the batch as flat error nodes, so the sql_stmt count alone under-reports (issue #21).
|
|
42
|
+
const swallowed = swallowedStatements(tree);
|
|
43
|
+
const total = stmts.length + swallowed;
|
|
44
|
+
if (total !== 1 || stmts.length !== 1) {
|
|
45
|
+
// Anchor a multi-statement span to the FIRST statement, not the whole `parse` container (which
|
|
46
|
+
// reaches EOF), so a downstream AST index read doesn't see a bogus enclosure over statements 2..n.
|
|
47
|
+
const cst = total > 1 && stmts.length > 0 ? stmts[0] : tree;
|
|
48
|
+
const q = nonQuery(cst, total > 1 ? "multi-statement" : total === 1 ? "broken" : "empty");
|
|
49
|
+
q.statement = statement;
|
|
50
|
+
return q;
|
|
51
|
+
}
|
|
52
|
+
const stmt = stmts[0];
|
|
53
|
+
// EXPLAIN / EXPLAIN QUERY PLAN returns bytecode or a plan, not the query's rows → utility, not a query.
|
|
54
|
+
const select = hasDirectToken(stmt, P.EXPLAIN_) ? undefined : directChildrenOfRule(stmt, P.RULE_select_stmt)[0];
|
|
55
|
+
if (select) {
|
|
56
|
+
const q = lowerSelectStmt(select);
|
|
57
|
+
q.statement = statement;
|
|
58
|
+
return q;
|
|
59
|
+
}
|
|
60
|
+
const q = nonQuery(stmt, "non-query");
|
|
61
|
+
q.statement = statement;
|
|
62
|
+
return q;
|
|
63
|
+
}
|
|
64
|
+
// --- statement categories ------------------------------------------------------
|
|
65
|
+
/** Per-statement categories for every `sql_stmt` in a parsed `parse`, in source order — the
|
|
66
|
+
* file-level view behind statementCategory (which folds >1 into "compound"). Parity with the
|
|
67
|
+
* other dialects; feeds the corpus reclassifier. */
|
|
68
|
+
export function statementCategories(tree) {
|
|
69
|
+
const list = firstOfRule(tree, P.RULE_sql_stmt_list);
|
|
70
|
+
const stmts = list ? directChildrenOfRule(list, P.RULE_sql_stmt) : [];
|
|
71
|
+
return [...stmts.map(stmtCategory), ...swallowedCategories(tree)];
|
|
72
|
+
}
|
|
73
|
+
function statementCategory(tree) {
|
|
74
|
+
const cats = statementCategories(tree);
|
|
75
|
+
if (cats.length === 0)
|
|
76
|
+
return "other";
|
|
77
|
+
if (cats.length > 1)
|
|
78
|
+
return "compound";
|
|
79
|
+
return cats[0];
|
|
80
|
+
}
|
|
81
|
+
// SQLite's sql_stmt groups every statement kind as a distinct sub-rule, so the category is exact
|
|
82
|
+
// per rule index. Maintenance commands (ANALYZE / REINDEX / VACUUM) and ATTACH / DETACH / PRAGMA
|
|
83
|
+
// are session/admin utilities; transaction control is tcl; object DML/DDL are the read/write/define
|
|
84
|
+
// paths. EXPLAIN prefixes any of these but yields a plan, not rows → utility.
|
|
85
|
+
const STMT_CATEGORY = [
|
|
86
|
+
[P.RULE_select_stmt, "query"],
|
|
87
|
+
[P.RULE_insert_stmt, "dml"],
|
|
88
|
+
[P.RULE_update_stmt, "dml"],
|
|
89
|
+
[P.RULE_delete_stmt, "dml"],
|
|
90
|
+
[P.RULE_alter_table_stmt, "ddl"],
|
|
91
|
+
[P.RULE_create_index_stmt, "ddl"],
|
|
92
|
+
[P.RULE_create_table_stmt, "ddl"],
|
|
93
|
+
[P.RULE_create_trigger_stmt, "ddl"],
|
|
94
|
+
[P.RULE_create_view_stmt, "ddl"],
|
|
95
|
+
[P.RULE_create_virtual_table_stmt, "ddl"],
|
|
96
|
+
[P.RULE_drop_stmt, "ddl"],
|
|
97
|
+
[P.RULE_begin_stmt, "tcl"],
|
|
98
|
+
[P.RULE_commit_stmt, "tcl"],
|
|
99
|
+
[P.RULE_rollback_stmt, "tcl"],
|
|
100
|
+
[P.RULE_savepoint_stmt, "tcl"],
|
|
101
|
+
[P.RULE_release_stmt, "tcl"],
|
|
102
|
+
[P.RULE_analyze_stmt, "utility"],
|
|
103
|
+
[P.RULE_attach_stmt, "utility"],
|
|
104
|
+
[P.RULE_detach_stmt, "utility"],
|
|
105
|
+
[P.RULE_pragma_stmt, "utility"],
|
|
106
|
+
[P.RULE_reindex_stmt, "utility"],
|
|
107
|
+
[P.RULE_vacuum_stmt, "utility"],
|
|
108
|
+
];
|
|
109
|
+
function stmtCategory(stmt) {
|
|
110
|
+
if (hasDirectToken(stmt, P.EXPLAIN_))
|
|
111
|
+
return "utility";
|
|
112
|
+
for (const [rule, cat] of STMT_CATEGORY)
|
|
113
|
+
if (directChildrenOfRule(stmt, rule).length)
|
|
114
|
+
return cat;
|
|
115
|
+
// No finer rule matched (mid-edit / recovery) — the leading keyword is the honest fallback.
|
|
116
|
+
return keywordCategory(stmt.start?.text ?? "");
|
|
117
|
+
}
|
|
118
|
+
function nonQuery(cst, reason) {
|
|
119
|
+
return {
|
|
120
|
+
kind: "query",
|
|
121
|
+
ctes: [],
|
|
122
|
+
body: { kind: "select", projections: [], from: [], columns: [], aggregated: false, unsupported: [reason], cst },
|
|
123
|
+
cst,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
// --- SELECT statement ----------------------------------------------------------
|
|
127
|
+
/** select_stmt: with_clause? select_core (compound_operator select_core)* order_clause? limit_clause? */
|
|
128
|
+
function lowerSelectStmt(stmt) {
|
|
129
|
+
const withClause = directChildrenOfRule(stmt, P.RULE_with_clause)[0];
|
|
130
|
+
const ctes = withClause ? directChildrenOfRule(withClause, P.RULE_common_table_expression).map(lowerCte) : [];
|
|
131
|
+
const cores = directChildrenOfRule(stmt, P.RULE_select_core);
|
|
132
|
+
const ops = directChildrenOfRule(stmt, P.RULE_compound_operator);
|
|
133
|
+
const body = foldCompound(cores, ops, stmt);
|
|
134
|
+
const orderClause = directChildrenOfRule(stmt, P.RULE_order_clause)[0];
|
|
135
|
+
const orderBy = orderClause ? lowerOrderBy(orderClause) : undefined;
|
|
136
|
+
// A trailing ORDER BY resolves against the whole compound's output columns. SQLite never
|
|
137
|
+
// produces a pipe body, but QueryBody includes PipeExpr (no `columns`), so narrow first.
|
|
138
|
+
if (orderBy && body.kind !== "pipe")
|
|
139
|
+
for (const o of orderBy)
|
|
140
|
+
columnsOf(o, body.columns, "orderBy");
|
|
141
|
+
const limitClause = directChildrenOfRule(stmt, P.RULE_limit_clause)[0];
|
|
142
|
+
const limit = limitClause ? lowerLimit(limitClause) : undefined;
|
|
143
|
+
return { kind: "query", ctes, body, orderBy, limit, cst: stmt };
|
|
144
|
+
}
|
|
145
|
+
/** Left-fold a compound SELECT (cores interleaved with UNION/INTERSECT/EXCEPT operators).
|
|
146
|
+
* SQLite compound operators are left-associative. */
|
|
147
|
+
function foldCompound(cores, ops, cst) {
|
|
148
|
+
let body = cores[0] ? buildSelectCore(cores[0]) : emptyBody(cst);
|
|
149
|
+
for (let i = 0; i < ops.length; i++) {
|
|
150
|
+
const right = cores[i + 1] ? buildSelectCore(cores[i + 1]) : emptyBody(ops[i]);
|
|
151
|
+
body = {
|
|
152
|
+
kind: "setop",
|
|
153
|
+
op: compoundOp(ops[i]),
|
|
154
|
+
all: hasDirectToken(ops[i], P.ALL_),
|
|
155
|
+
left: body,
|
|
156
|
+
right,
|
|
157
|
+
columns: [],
|
|
158
|
+
cst: ops[i],
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
return body;
|
|
162
|
+
}
|
|
163
|
+
/** compound_operator: UNION_ ALL_? | INTERSECT_ | EXCEPT_ */
|
|
164
|
+
function compoundOp(op) {
|
|
165
|
+
if (hasDirectToken(op, P.INTERSECT_))
|
|
166
|
+
return "intersect";
|
|
167
|
+
if (hasDirectToken(op, P.EXCEPT_))
|
|
168
|
+
return "except";
|
|
169
|
+
return "union";
|
|
170
|
+
}
|
|
171
|
+
/** common_table_expression: cte_table_name AS_ (NOT_? MATERIALIZED_)? '(' select_stmt ')'
|
|
172
|
+
* cte_table_name: table_name ('(' column_name (',' column_name)* ')')? */
|
|
173
|
+
function lowerCte(cte) {
|
|
174
|
+
const ctn = directChildrenOfRule(cte, P.RULE_cte_table_name)[0];
|
|
175
|
+
const nameNode = ctn ? directChildrenOfRule(ctn, P.RULE_table_name)[0] : undefined;
|
|
176
|
+
const cols = ctn ? directChildrenOfRule(ctn, P.RULE_column_name).map((c) => c.getText()) : [];
|
|
177
|
+
const sel = directChildrenOfRule(cte, P.RULE_select_stmt)[0];
|
|
178
|
+
return {
|
|
179
|
+
name: nameNode ? nameNode.getText() : "",
|
|
180
|
+
nameCst: nameNode,
|
|
181
|
+
columnAliases: cols.length ? cols : undefined,
|
|
182
|
+
body: sel ? lowerSelectStmt(sel) : emptyQuery(cte),
|
|
183
|
+
cst: cte,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
// --- the SELECT core -----------------------------------------------------------
|
|
187
|
+
/** select_core:
|
|
188
|
+
* SELECT_ (DISTINCT_|ALL_)? result_column (',' result_column)* (FROM_ join_clause)?
|
|
189
|
+
* (WHERE_ expr)? (GROUP_ BY_ expr (',' expr)* (HAVING_ expr)?)? (WINDOW_ …)?
|
|
190
|
+
* | values_clause */
|
|
191
|
+
function buildSelectCore(core) {
|
|
192
|
+
const values = directChildrenOfRule(core, P.RULE_values_clause)[0];
|
|
193
|
+
if (values)
|
|
194
|
+
return buildValues(values);
|
|
195
|
+
const projections = directChildrenOfRule(core, P.RULE_result_column).map(buildProjection);
|
|
196
|
+
const joinClause = directChildrenOfRule(core, P.RULE_join_clause)[0];
|
|
197
|
+
const from = joinClause ? buildSources(joinClause) : [];
|
|
198
|
+
const { joinConditions, onByConstraint, usingByConstraint } = joinClause
|
|
199
|
+
? collectJoinData(joinClause)
|
|
200
|
+
: { joinConditions: [], onByConstraint: new Map(), usingByConstraint: new Map() };
|
|
201
|
+
const joins = joinClause ? buildJoins(joinClause, from, onByConstraint, usingByConstraint) : [];
|
|
202
|
+
const { whereExpr, groupBy, having } = selectCoreClauses(core);
|
|
203
|
+
const aggregated = (groupBy !== undefined && groupBy.length > 0) ||
|
|
204
|
+
projections.some((p) => hasAggregate(p.expr)) ||
|
|
205
|
+
(having !== undefined && hasAggregate(having));
|
|
206
|
+
const columns = [];
|
|
207
|
+
for (const p of projections)
|
|
208
|
+
columnsOf(p.expr, columns, "projection");
|
|
209
|
+
if (whereExpr)
|
|
210
|
+
columnsOf(whereExpr, columns, "where");
|
|
211
|
+
for (const j of joinConditions)
|
|
212
|
+
columnsOf(j, columns, "join");
|
|
213
|
+
for (const g of groupBy ?? [])
|
|
214
|
+
columnsOf(g, columns, "groupBy");
|
|
215
|
+
if (having)
|
|
216
|
+
columnsOf(having, columns, "having");
|
|
217
|
+
const subqueries = extractExpressionSubqueries(core);
|
|
218
|
+
return {
|
|
219
|
+
kind: "select",
|
|
220
|
+
projections,
|
|
221
|
+
from,
|
|
222
|
+
columns,
|
|
223
|
+
where: whereExpr,
|
|
224
|
+
joinConditions: joinConditions.length ? joinConditions : undefined,
|
|
225
|
+
joins: joins.length ? joins : undefined,
|
|
226
|
+
groupBy,
|
|
227
|
+
having,
|
|
228
|
+
aggregated,
|
|
229
|
+
subqueries: subqueries.length ? subqueries : undefined,
|
|
230
|
+
cst: core,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
// --- expression subqueries (scalar / IN / EXISTS) --------------------------------
|
|
234
|
+
/** Expression subqueries appearing in this select's expressions (SELECT list, WHERE, GROUP BY,
|
|
235
|
+
* HAVING, JOIN ON, TVF args) — every `select_stmt` reachable without crossing another select_stmt,
|
|
236
|
+
* EXCLUDING the FROM subqueries (a `table_or_subquery`'s direct select_stmt child is a Source,
|
|
237
|
+
* scoped separately by buildSource). Mirrors snowflake's extractExpressionSubqueries: the scope
|
|
238
|
+
* pass discovers expression-nested subqueries ONLY through `SelectExpr.subqueries`, so their
|
|
239
|
+
* (possibly correlated) columns resolve as children of this scope. */
|
|
240
|
+
function extractExpressionSubqueries(core) {
|
|
241
|
+
const out = [];
|
|
242
|
+
const walk = (n) => {
|
|
243
|
+
for (const child of kidsOf(n)) {
|
|
244
|
+
if (!(child instanceof ParserRuleContext))
|
|
245
|
+
continue;
|
|
246
|
+
if (child.ruleIndex === P.RULE_select_stmt) {
|
|
247
|
+
// A table_or_subquery's select_stmt is a FROM source, not an expression subquery.
|
|
248
|
+
if (!isRule(child.parent ?? undefined, P.RULE_table_or_subquery))
|
|
249
|
+
out.push(lowerSelectStmt(child));
|
|
250
|
+
continue; // its own scope — don't descend
|
|
251
|
+
}
|
|
252
|
+
walk(child);
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
walk(core);
|
|
256
|
+
return out;
|
|
257
|
+
}
|
|
258
|
+
/** The WHERE / GROUP BY / HAVING exprs of a select_core. They are all direct `expr` children of
|
|
259
|
+
* select_core (result_column and join_clause exprs are nested, so never direct children); classify
|
|
260
|
+
* each by the keyword token that most recently preceded it. */
|
|
261
|
+
function selectCoreClauses(core) {
|
|
262
|
+
let mode;
|
|
263
|
+
let whereExpr;
|
|
264
|
+
let having;
|
|
265
|
+
const groupBy = [];
|
|
266
|
+
for (const c of kidsOf(core)) {
|
|
267
|
+
if (c instanceof TerminalNode) {
|
|
268
|
+
const t = c.symbol.type;
|
|
269
|
+
if (t === P.WHERE_)
|
|
270
|
+
mode = "where";
|
|
271
|
+
else if (t === P.GROUP_)
|
|
272
|
+
mode = "group";
|
|
273
|
+
else if (t === P.HAVING_)
|
|
274
|
+
mode = "having";
|
|
275
|
+
else if (t === P.WINDOW_)
|
|
276
|
+
mode = undefined;
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if (c instanceof ParserRuleContext && c.ruleIndex === P.RULE_expr) {
|
|
280
|
+
const e = lowerExpr(c);
|
|
281
|
+
if (mode === "where")
|
|
282
|
+
whereExpr = e;
|
|
283
|
+
else if (mode === "group")
|
|
284
|
+
groupBy.push(e);
|
|
285
|
+
else if (mode === "having")
|
|
286
|
+
having = e;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return { whereExpr, groupBy: groupBy.length ? groupBy : undefined, having };
|
|
290
|
+
}
|
|
291
|
+
/** values_clause: VALUES_ value_row (',' value_row)* / value_row: '(' expr (',' expr)* ')'.
|
|
292
|
+
* Lowers to a modelled select whose projections carry the row exprs, named column1…columnN
|
|
293
|
+
* (SQLite's default VALUES output names). */
|
|
294
|
+
function buildValues(values) {
|
|
295
|
+
const firstRow = directChildrenOfRule(values, P.RULE_value_row)[0];
|
|
296
|
+
const exprs = firstRow ? directChildrenOfRule(firstRow, P.RULE_expr) : [];
|
|
297
|
+
const projections = exprs.map((e, i) => ({
|
|
298
|
+
name: `column${i + 1}`,
|
|
299
|
+
isStar: false,
|
|
300
|
+
expr: lowerExpr(e),
|
|
301
|
+
cst: e,
|
|
302
|
+
}));
|
|
303
|
+
const columns = [];
|
|
304
|
+
for (const p of projections)
|
|
305
|
+
columnsOf(p.expr, columns, "projection");
|
|
306
|
+
const subqueries = extractExpressionSubqueries(values);
|
|
307
|
+
return {
|
|
308
|
+
kind: "select",
|
|
309
|
+
projections,
|
|
310
|
+
from: [],
|
|
311
|
+
columns,
|
|
312
|
+
aggregated: false,
|
|
313
|
+
subqueries: subqueries.length ? subqueries : undefined,
|
|
314
|
+
cst: values,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
// --- projections ---------------------------------------------------------------
|
|
318
|
+
/** result_column: STAR | table_name '.' STAR | expr (AS_? column_alias)? */
|
|
319
|
+
function buildProjection(rc) {
|
|
320
|
+
if (hasDirectToken(rc, P.STAR)) {
|
|
321
|
+
const tableName = directChildrenOfRule(rc, P.RULE_table_name)[0];
|
|
322
|
+
const expr = { kind: "star", qualifier: tableName ? [tableName.getText()] : undefined, cst: rc };
|
|
323
|
+
return { name: undefined, isStar: true, expr, cst: rc };
|
|
324
|
+
}
|
|
325
|
+
const exprNode = directChildrenOfRule(rc, P.RULE_expr)[0];
|
|
326
|
+
const expr = exprNode ? lowerExpr(exprNode) : otherExpr(rc);
|
|
327
|
+
const aliasNode = directChildrenOfRule(rc, P.RULE_column_alias)[0];
|
|
328
|
+
let name = aliasNode ? aliasNode.getText() : undefined;
|
|
329
|
+
if (name === undefined && expr.kind === "column")
|
|
330
|
+
name = expr.parts[expr.parts.length - 1];
|
|
331
|
+
return { name, isStar: false, expr, ...(aliasNode ? { aliasCst: aliasNode } : {}), cst: rc };
|
|
332
|
+
}
|
|
333
|
+
// --- sources -------------------------------------------------------------------
|
|
334
|
+
/** The base sources of a join_clause, in source order. A parenthesized join `( join_clause )` is a
|
|
335
|
+
* grouping (NOT a derived table — its columns stay directly visible), so its inner sources are
|
|
336
|
+
* flattened into this level rather than hidden behind an anonymous subquery. */
|
|
337
|
+
function buildSources(joinClause) {
|
|
338
|
+
const out = [];
|
|
339
|
+
const addSource = (tos) => {
|
|
340
|
+
const sel = directChildrenOfRule(tos, P.RULE_select_stmt)[0];
|
|
341
|
+
const nested = sel ? undefined : directChildrenOfRule(tos, P.RULE_join_clause)[0];
|
|
342
|
+
if (nested)
|
|
343
|
+
out.push(...buildSources(nested));
|
|
344
|
+
else
|
|
345
|
+
out.push(buildSource(tos));
|
|
346
|
+
};
|
|
347
|
+
// join_clause: table_or_subquery join_step*. The left operand is a direct table_or_subquery child;
|
|
348
|
+
// each subsequent operand lives inside a join_step. Direct-then-steps preserves source order.
|
|
349
|
+
for (const tos of directChildrenOfRule(joinClause, P.RULE_table_or_subquery))
|
|
350
|
+
addSource(tos);
|
|
351
|
+
for (const step of directChildrenOfRule(joinClause, P.RULE_join_step)) {
|
|
352
|
+
const tos = directChildrenOfRule(step, P.RULE_table_or_subquery)[0];
|
|
353
|
+
if (tos)
|
|
354
|
+
addSource(tos);
|
|
355
|
+
}
|
|
356
|
+
return out;
|
|
357
|
+
}
|
|
358
|
+
/** table_or_subquery — one of:
|
|
359
|
+
* (schema_name '.')? table_name (AS_ table_alias | table_alias_excluding_joins)? …
|
|
360
|
+
* | (schema_name '.')? table_function_name '(' expr (',' expr)* ')' (AS_? table_alias)?
|
|
361
|
+
* | '(' select_stmt ')' (AS_? table_alias)? */
|
|
362
|
+
function buildSource(tos) {
|
|
363
|
+
const { alias, aliasCst } = tableAliasOf(tos);
|
|
364
|
+
// '(' select_stmt ')' — a derived table.
|
|
365
|
+
const sel = directChildrenOfRule(tos, P.RULE_select_stmt)[0];
|
|
366
|
+
if (sel) {
|
|
367
|
+
return { kind: "subquery", query: lowerSelectStmt(sel), alias, aliasCst, cst: tos };
|
|
368
|
+
}
|
|
369
|
+
// table_function_name(args) — a table-valued function; its output columns need the signature
|
|
370
|
+
// (unknown without a catalog, never wrong — the inference contract).
|
|
371
|
+
const tfn = directChildrenOfRule(tos, P.RULE_table_function_name)[0];
|
|
372
|
+
if (tfn) {
|
|
373
|
+
const schemaName = directChildrenOfRule(tos, P.RULE_schema_name)[0];
|
|
374
|
+
const partNodes = [schemaName, tfn].filter((n) => n !== undefined);
|
|
375
|
+
return {
|
|
376
|
+
kind: "table",
|
|
377
|
+
name: partNodes.map((n) => n.getText()),
|
|
378
|
+
namePartSpans: partSpansOf(partNodes),
|
|
379
|
+
alias,
|
|
380
|
+
aliasCst,
|
|
381
|
+
cst: tos,
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
// (schema_name '.')? table_name — a plain table reference.
|
|
385
|
+
const tableName = directChildrenOfRule(tos, P.RULE_table_name)[0];
|
|
386
|
+
const schemaName = directChildrenOfRule(tos, P.RULE_schema_name)[0];
|
|
387
|
+
const partNodes = [schemaName, tableName].filter((n) => n !== undefined);
|
|
388
|
+
const parts = partNodes.map((n) => n.getText());
|
|
389
|
+
return {
|
|
390
|
+
kind: "table",
|
|
391
|
+
name: parts.length ? parts : [tos.getText()],
|
|
392
|
+
namePartSpans: partNodes.length ? partSpansOf(partNodes) : undefined,
|
|
393
|
+
alias,
|
|
394
|
+
aliasCst,
|
|
395
|
+
cst: tos,
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
/** The FROM-source alias: `AS table_alias` / bare `table_alias_excluding_joins` (plain table),
|
|
399
|
+
* or `AS? table_alias` (subquery / table function). */
|
|
400
|
+
function tableAliasOf(tos) {
|
|
401
|
+
const a = directChildrenOfRule(tos, P.RULE_table_alias)[0] ??
|
|
402
|
+
directChildrenOfRule(tos, P.RULE_table_alias_excluding_joins)[0];
|
|
403
|
+
return a ? { alias: a.getText(), aliasCst: a } : {};
|
|
404
|
+
}
|
|
405
|
+
// --- joins ---------------------------------------------------------------------
|
|
406
|
+
/** ON exprs and USING column lists for every join_constraint in this select's join tree (descending
|
|
407
|
+
* through parenthesized nested join_clauses, but NOT into subquery select_stmts). The ON exprs are
|
|
408
|
+
* lowered ONCE and keyed by their join_constraint CST so buildJoins shares the same Expr objects
|
|
409
|
+
* (the reference-identity contract between joinConditions and joins). */
|
|
410
|
+
function collectJoinData(joinClause) {
|
|
411
|
+
const joinConditions = [];
|
|
412
|
+
const onByConstraint = new Map();
|
|
413
|
+
const usingByConstraint = new Map();
|
|
414
|
+
for (const jc of shallowCollectOfRule(joinClause, P.RULE_join_constraint)) {
|
|
415
|
+
const onExpr = directChildrenOfRule(jc, P.RULE_expr)[0];
|
|
416
|
+
if (onExpr) {
|
|
417
|
+
const e = lowerExpr(onExpr);
|
|
418
|
+
joinConditions.push(e);
|
|
419
|
+
onByConstraint.set(jc, e);
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
const cols = directChildrenOfRule(jc, P.RULE_column_name).map((c) => c.getText());
|
|
423
|
+
if (cols.length)
|
|
424
|
+
usingByConstraint.set(jc, cols);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
return { joinConditions, onByConstraint, usingByConstraint };
|
|
428
|
+
}
|
|
429
|
+
/** join_clause: table_or_subquery join_step* / join_step: join_operator table_or_subquery
|
|
430
|
+
* join_constraint?. One Join per join_step whose operator is an explicit JOIN (a bare COMMA step is a
|
|
431
|
+
* plain FROM entry, not a join — the IR contract). Each join.source is the reference-identical `from`
|
|
432
|
+
* entry for that step's right table_or_subquery; join.on is the shared Expr from onByConstraint.
|
|
433
|
+
* Join.cst is the `join_step` node itself, so its span is the full `[type] JOIN <table> [ON …|USING …]`
|
|
434
|
+
* construct (the src/ir/ir.ts Join.cst contract) — the reason the grammar carries the join_step rule. */
|
|
435
|
+
function buildJoins(joinClause, from, onByConstraint, usingByConstraint) {
|
|
436
|
+
const joins = [];
|
|
437
|
+
for (const step of directChildrenOfRule(joinClause, P.RULE_join_step)) {
|
|
438
|
+
const op = directChildrenOfRule(step, P.RULE_join_operator)[0];
|
|
439
|
+
if (!op || hasDirectToken(op, P.COMMA))
|
|
440
|
+
continue; // comma → plain from entry, not a join
|
|
441
|
+
const tos = directChildrenOfRule(step, P.RULE_table_or_subquery)[0];
|
|
442
|
+
if (!tos)
|
|
443
|
+
continue;
|
|
444
|
+
const source = sourceFor(from, tos);
|
|
445
|
+
if (!source)
|
|
446
|
+
continue;
|
|
447
|
+
const jc = directChildrenOfRule(step, P.RULE_join_constraint)[0];
|
|
448
|
+
const { kind, natural } = joinKind(op);
|
|
449
|
+
const on = jc ? onByConstraint.get(jc) : undefined;
|
|
450
|
+
const using = jc ? usingByConstraint.get(jc) : undefined;
|
|
451
|
+
joins.push({ kind, source, on, using, natural: natural || undefined, cst: step });
|
|
452
|
+
}
|
|
453
|
+
return joins;
|
|
454
|
+
}
|
|
455
|
+
/** The `from` entry for a join's right table_or_subquery: the source whose cst IS that node, or (a
|
|
456
|
+
* flattened parenthesized-join operand) the first source nested within it. */
|
|
457
|
+
function sourceFor(from, tos) {
|
|
458
|
+
return from.find((s) => s.cst === tos) ?? from.find((s) => isWithin(s.cst, tos));
|
|
459
|
+
}
|
|
460
|
+
/** join_operator: COMMA | NATURAL_? ((LEFT_|RIGHT_|FULL_) OUTER_? | INNER_ | CROSS_)? JOIN_ */
|
|
461
|
+
function joinKind(op) {
|
|
462
|
+
const natural = hasDirectToken(op, P.NATURAL_);
|
|
463
|
+
if (hasDirectToken(op, P.CROSS_))
|
|
464
|
+
return { kind: "cross", natural };
|
|
465
|
+
if (hasDirectToken(op, P.LEFT_))
|
|
466
|
+
return { kind: "left", natural };
|
|
467
|
+
if (hasDirectToken(op, P.RIGHT_))
|
|
468
|
+
return { kind: "right", natural };
|
|
469
|
+
if (hasDirectToken(op, P.FULL_))
|
|
470
|
+
return { kind: "full", natural };
|
|
471
|
+
if (hasDirectToken(op, P.INNER_))
|
|
472
|
+
return { kind: "inner", natural };
|
|
473
|
+
return { kind: natural ? "natural" : "inner", natural };
|
|
474
|
+
}
|
|
475
|
+
// --- ORDER BY / LIMIT ----------------------------------------------------------
|
|
476
|
+
/** order_clause: ORDER_ BY_ ordering_term (',' ordering_term)* / ordering_term: expr … */
|
|
477
|
+
function lowerOrderBy(orderClause) {
|
|
478
|
+
const items = orderingTermExprs(orderClause).map(lowerExpr);
|
|
479
|
+
return items.length ? items : undefined;
|
|
480
|
+
}
|
|
481
|
+
function orderingTermExprs(orderClause) {
|
|
482
|
+
return directChildrenOfRule(orderClause, P.RULE_ordering_term)
|
|
483
|
+
.map((ot) => directChildrenOfRule(ot, P.RULE_expr)[0])
|
|
484
|
+
.filter((e) => e !== undefined);
|
|
485
|
+
}
|
|
486
|
+
/** limit_clause: LIMIT_ expr ((OFFSET_ | COMMA) expr)?. The comma form is SQLite's `LIMIT offset,
|
|
487
|
+
* count`; the OFFSET form is `LIMIT count OFFSET offset`. */
|
|
488
|
+
function lowerLimit(limitClause) {
|
|
489
|
+
const exprs = directChildrenOfRule(limitClause, P.RULE_expr);
|
|
490
|
+
if (exprs.length === 0)
|
|
491
|
+
return undefined;
|
|
492
|
+
const info = {};
|
|
493
|
+
if (hasDirectToken(limitClause, P.COMMA)) {
|
|
494
|
+
info.offset = lowerExpr(exprs[0]);
|
|
495
|
+
if (exprs[1])
|
|
496
|
+
info.top = lowerExpr(exprs[1]);
|
|
497
|
+
}
|
|
498
|
+
else {
|
|
499
|
+
info.top = lowerExpr(exprs[0]);
|
|
500
|
+
if (exprs[1])
|
|
501
|
+
info.offset = lowerExpr(exprs[1]);
|
|
502
|
+
}
|
|
503
|
+
return info;
|
|
504
|
+
}
|
|
505
|
+
// --- the expr precedence cascade ----------------------------------------------
|
|
506
|
+
// expr → expr_or → expr_and → expr_not → expr_binary → expr_comparison → expr_bitwise
|
|
507
|
+
// → expr_addition → expr_multiplication → expr_string → expr_collate → expr_unary
|
|
508
|
+
// → expr_base → expr_recursive
|
|
509
|
+
function lowerExpr(node) {
|
|
510
|
+
switch (node.ruleIndex) {
|
|
511
|
+
case P.RULE_expr: {
|
|
512
|
+
const c = directChildrenOfRule(node, P.RULE_expr_or)[0];
|
|
513
|
+
return c ? lowerExpr(c) : otherExpr(node);
|
|
514
|
+
}
|
|
515
|
+
case P.RULE_expr_or:
|
|
516
|
+
return foldLeft(node, P.RULE_expr_and, (t) => (t === P.OR_ ? "or" : undefined));
|
|
517
|
+
case P.RULE_expr_and:
|
|
518
|
+
return foldLeft(node, P.RULE_expr_not, (t) => (t === P.AND_ ? "and" : undefined));
|
|
519
|
+
case P.RULE_expr_not:
|
|
520
|
+
return lowerNot(node);
|
|
521
|
+
case P.RULE_expr_binary:
|
|
522
|
+
return lowerBinary(node);
|
|
523
|
+
case P.RULE_expr_comparison:
|
|
524
|
+
return foldLeft(node, P.RULE_expr_bitwise, comparisonOp);
|
|
525
|
+
case P.RULE_expr_bitwise:
|
|
526
|
+
return foldLeft(node, P.RULE_expr_addition, bitwiseOp);
|
|
527
|
+
case P.RULE_expr_addition:
|
|
528
|
+
return foldLeft(node, P.RULE_expr_multiplication, (t) => t === P.PLUS ? "+" : t === P.MINUS ? "-" : undefined);
|
|
529
|
+
case P.RULE_expr_multiplication:
|
|
530
|
+
return foldLeft(node, P.RULE_expr_string, (t) => t === P.STAR ? "*" : t === P.DIV ? "/" : t === P.MOD ? "%" : undefined);
|
|
531
|
+
case P.RULE_expr_string:
|
|
532
|
+
return foldLeft(node, P.RULE_expr_collate, (t) => t === P.PIPE2 ? "||" : t === P.JPTR ? "->" : t === P.JPTR2 ? "->>" : undefined);
|
|
533
|
+
case P.RULE_expr_collate:
|
|
534
|
+
return lowerCollate(node);
|
|
535
|
+
case P.RULE_expr_unary:
|
|
536
|
+
return lowerUnary(node);
|
|
537
|
+
case P.RULE_expr_base:
|
|
538
|
+
return lowerExprBase(node);
|
|
539
|
+
case P.RULE_expr_recursive:
|
|
540
|
+
return lowerExprRecursive(node);
|
|
541
|
+
default:
|
|
542
|
+
return otherExpr(node);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
function comparisonOp(t) {
|
|
546
|
+
return t === P.LT ? "<" : t === P.LT_EQ ? "<=" : t === P.GT ? ">" : t === P.GT_EQ ? ">=" : undefined;
|
|
547
|
+
}
|
|
548
|
+
function bitwiseOp(t) {
|
|
549
|
+
return t === P.LT2 ? "<<" : t === P.GT2 ? ">>" : t === P.AMP ? "&" : t === P.PIPE ? "|" : undefined;
|
|
550
|
+
}
|
|
551
|
+
/** A left-associative `child (OP child)*` cascade level: fold the operand rule-nodes into a chain of
|
|
552
|
+
* binary exprs, taking the operator string from the interleaved tokens. A single operand passes
|
|
553
|
+
* through untouched. */
|
|
554
|
+
function foldLeft(node, childRule, opFor) {
|
|
555
|
+
let left;
|
|
556
|
+
let op;
|
|
557
|
+
for (const c of kidsOf(node)) {
|
|
558
|
+
if (c instanceof TerminalNode) {
|
|
559
|
+
const o = opFor(c.symbol.type);
|
|
560
|
+
if (o !== undefined)
|
|
561
|
+
op = o;
|
|
562
|
+
continue;
|
|
563
|
+
}
|
|
564
|
+
if (c instanceof ParserRuleContext && c.ruleIndex === childRule) {
|
|
565
|
+
const e = lowerExpr(c);
|
|
566
|
+
left = left === undefined ? e : { kind: "binary", op: op ?? "", left, right: e, cst: node };
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return left ?? otherExpr(node);
|
|
570
|
+
}
|
|
571
|
+
/** expr_not: NOT_* expr_binary — wrap in a `not` unary once per leading NOT. */
|
|
572
|
+
function lowerNot(node) {
|
|
573
|
+
const inner = directChildrenOfRule(node, P.RULE_expr_binary)[0];
|
|
574
|
+
let e = inner ? lowerExpr(inner) : otherExpr(node);
|
|
575
|
+
for (const c of kidsOf(node)) {
|
|
576
|
+
if (c instanceof TerminalNode && c.symbol.type === P.NOT_)
|
|
577
|
+
e = { kind: "unary", op: "not", operand: e, cst: node };
|
|
578
|
+
}
|
|
579
|
+
return e;
|
|
580
|
+
}
|
|
581
|
+
/** expr_collate: expr_unary (COLLATE_ collation_name)* — COLLATE is a passthrough (no column refs). */
|
|
582
|
+
function lowerCollate(node) {
|
|
583
|
+
const inner = directChildrenOfRule(node, P.RULE_expr_unary)[0];
|
|
584
|
+
return inner ? lowerExpr(inner) : otherExpr(node);
|
|
585
|
+
}
|
|
586
|
+
/** expr_unary: (MINUS | PLUS | TILDE)* expr_base — wrap each leading sign, innermost binds tightest. */
|
|
587
|
+
function lowerUnary(node) {
|
|
588
|
+
const base = directChildrenOfRule(node, P.RULE_expr_base)[0];
|
|
589
|
+
let e = base ? lowerExpr(base) : otherExpr(node);
|
|
590
|
+
const signs = [];
|
|
591
|
+
for (const c of kidsOf(node)) {
|
|
592
|
+
if (c instanceof TerminalNode &&
|
|
593
|
+
(c.symbol.type === P.MINUS || c.symbol.type === P.PLUS || c.symbol.type === P.TILDE)) {
|
|
594
|
+
signs.push(c.symbol.type);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
for (let i = signs.length - 1; i >= 0; i--) {
|
|
598
|
+
const op = signs[i] === P.MINUS ? "-" : signs[i] === P.PLUS ? "+" : "~";
|
|
599
|
+
e = { kind: "unary", op, operand: e, cst: node };
|
|
600
|
+
}
|
|
601
|
+
return e;
|
|
602
|
+
}
|
|
603
|
+
/** expr_base:
|
|
604
|
+
* literal_value | BIND_PARAMETER | (schema_name '.')? table_name '.' column_name
|
|
605
|
+
* | column_name_excluding_string | (NOT_? EXISTS_)? '(' select_stmt ')' | raise_function
|
|
606
|
+
* | expr_recursive */
|
|
607
|
+
function lowerExprBase(node) {
|
|
608
|
+
const lit = directChildrenOfRule(node, P.RULE_literal_value)[0];
|
|
609
|
+
if (lit)
|
|
610
|
+
return { kind: "literal", text: lit.getText(), cst: node };
|
|
611
|
+
if (hasDirectToken(node, P.BIND_PARAMETER))
|
|
612
|
+
return { kind: "literal", text: node.getText(), cst: node };
|
|
613
|
+
// (schema_name '.')? table_name '.' column_name — a qualified column reference.
|
|
614
|
+
const colName = directChildrenOfRule(node, P.RULE_column_name)[0];
|
|
615
|
+
if (colName) {
|
|
616
|
+
const schemaName = directChildrenOfRule(node, P.RULE_schema_name)[0];
|
|
617
|
+
const tableName = directChildrenOfRule(node, P.RULE_table_name)[0];
|
|
618
|
+
const partNodes = [schemaName, tableName, colName].filter((n) => n !== undefined);
|
|
619
|
+
return {
|
|
620
|
+
kind: "column",
|
|
621
|
+
parts: partNodes.map((n) => n.getText()),
|
|
622
|
+
partSpans: partSpansOf(partNodes),
|
|
623
|
+
cst: node,
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
// column_name_excluding_string — a bare (unqualified) column reference.
|
|
627
|
+
const bareCol = directChildrenOfRule(node, P.RULE_column_name_excluding_string)[0];
|
|
628
|
+
if (bareCol) {
|
|
629
|
+
return { kind: "column", parts: [bareCol.getText()], partSpans: partSpansOf([bareCol]), cst: node };
|
|
630
|
+
}
|
|
631
|
+
// (NOT_? EXISTS_)? '(' select_stmt ')' — a scalar subquery or an [NOT] EXISTS test.
|
|
632
|
+
const sel = directChildrenOfRule(node, P.RULE_select_stmt)[0];
|
|
633
|
+
if (sel) {
|
|
634
|
+
if (hasDirectToken(node, P.EXISTS_)) {
|
|
635
|
+
const exists = { kind: "exists", query: lowerSelectStmt(sel), cst: node };
|
|
636
|
+
return hasDirectToken(node, P.NOT_) ? { kind: "unary", op: "not", operand: exists, cst: node } : exists;
|
|
637
|
+
}
|
|
638
|
+
return { kind: "subquery", query: lowerSelectStmt(sel), cst: node };
|
|
639
|
+
}
|
|
640
|
+
const rec = directChildrenOfRule(node, P.RULE_expr_recursive)[0];
|
|
641
|
+
if (rec)
|
|
642
|
+
return lowerExprRecursive(rec);
|
|
643
|
+
return otherExpr(node); // raise_function, or an unmodelled shape — columns recovered from the CST
|
|
644
|
+
}
|
|
645
|
+
/** expr_recursive:
|
|
646
|
+
* function_name '(' (DISTINCT_? expr (',' expr)* order_clause? | STAR)? ')' percentile? filter? over?
|
|
647
|
+
* | '(' expr (',' expr)* ')' | CAST_ '(' expr AS_ type_name ')' | CASE_ … */
|
|
648
|
+
function lowerExprRecursive(node) {
|
|
649
|
+
const fn = directChildrenOfRule(node, P.RULE_function_name)[0];
|
|
650
|
+
if (fn)
|
|
651
|
+
return lowerFunction(node, fn);
|
|
652
|
+
if (hasDirectToken(node, P.CAST_)) {
|
|
653
|
+
const inner = directChildrenOfRule(node, P.RULE_expr)[0];
|
|
654
|
+
const tn = directChildrenOfRule(node, P.RULE_type_name)[0];
|
|
655
|
+
return {
|
|
656
|
+
kind: "cast",
|
|
657
|
+
expr: inner ? lowerExpr(inner) : otherExpr(node),
|
|
658
|
+
typeText: tn ? tn.getText() : "",
|
|
659
|
+
cst: node,
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
if (hasDirectToken(node, P.CASE_))
|
|
663
|
+
return lowerCase(node);
|
|
664
|
+
// '(' expr (',' expr)* ')' — a parenthesized single expr is grouping (passthrough); a comma tuple
|
|
665
|
+
// has no IR node, so it stays `other` (columnsOf recovers its column refs from the CST).
|
|
666
|
+
const exprs = directChildrenOfRule(node, P.RULE_expr);
|
|
667
|
+
if (exprs.length === 1)
|
|
668
|
+
return lowerExpr(exprs[0]);
|
|
669
|
+
return otherExpr(node);
|
|
670
|
+
}
|
|
671
|
+
function lowerFunction(node, fnNode) {
|
|
672
|
+
const name = fnNode.getText().toLowerCase();
|
|
673
|
+
const argExprs = directChildrenOfRule(node, P.RULE_expr).map(lowerExpr);
|
|
674
|
+
// aggregate ORDER BY (group_concat(x ORDER BY y)), WITHIN GROUP (percentile) and FILTER (WHERE …)
|
|
675
|
+
// all feed the call — conserve their column refs as arguments.
|
|
676
|
+
const orderClause = directChildrenOfRule(node, P.RULE_order_clause)[0];
|
|
677
|
+
const orderArgs = orderClause ? orderingTermExprs(orderClause).map(lowerExpr) : [];
|
|
678
|
+
const pc = directChildrenOfRule(node, P.RULE_percentile_clause)[0];
|
|
679
|
+
const pcArgs = pc ? directChildrenOfRule(pc, P.RULE_expr).map(lowerExpr) : [];
|
|
680
|
+
const fc = directChildrenOfRule(node, P.RULE_filter_clause)[0];
|
|
681
|
+
const fcArgs = fc ? directChildrenOfRule(fc, P.RULE_expr).map(lowerExpr) : [];
|
|
682
|
+
const over = directChildrenOfRule(node, P.RULE_over_clause)[0];
|
|
683
|
+
return {
|
|
684
|
+
kind: "function",
|
|
685
|
+
name,
|
|
686
|
+
args: [...argExprs, ...orderArgs, ...pcArgs, ...fcArgs],
|
|
687
|
+
aggregate: AGGREGATES.has(name),
|
|
688
|
+
distinct: hasDirectToken(node, P.DISTINCT_),
|
|
689
|
+
window: over ? lowerOver(over) : undefined,
|
|
690
|
+
cst: node,
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
/** over_clause: OVER_ (window_name | '(' base_window_name? (PARTITION_ BY_ expr (',' expr)*)?
|
|
694
|
+
* order_clause? frame_spec? ')'). PARTITION BY exprs are direct expr children; ORDER BY exprs are
|
|
695
|
+
* nested in the order_clause. */
|
|
696
|
+
function lowerOver(over) {
|
|
697
|
+
const partitionBy = directChildrenOfRule(over, P.RULE_expr).map(lowerExpr);
|
|
698
|
+
const orderClause = directChildrenOfRule(over, P.RULE_order_clause)[0];
|
|
699
|
+
const orderBy = orderClause ? orderingTermExprs(orderClause).map(lowerExpr) : [];
|
|
700
|
+
return { partitionBy, orderBy, cst: over };
|
|
701
|
+
}
|
|
702
|
+
/** CASE_ expr? (WHEN_ expr THEN_ expr)+ (ELSE_ expr)? END_. A simple CASE with a subject desugars to
|
|
703
|
+
* `subject = whenValue` so the subject's columns/types are seen (mirrors the other dialects). */
|
|
704
|
+
function lowerCase(node) {
|
|
705
|
+
let subject;
|
|
706
|
+
let elseExpr;
|
|
707
|
+
let pendingWhen;
|
|
708
|
+
let mode = "subject";
|
|
709
|
+
const whens = [];
|
|
710
|
+
for (const c of kidsOf(node)) {
|
|
711
|
+
if (c instanceof TerminalNode) {
|
|
712
|
+
const t = c.symbol.type;
|
|
713
|
+
if (t === P.WHEN_)
|
|
714
|
+
mode = "when";
|
|
715
|
+
else if (t === P.THEN_)
|
|
716
|
+
mode = "then";
|
|
717
|
+
else if (t === P.ELSE_)
|
|
718
|
+
mode = "else";
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
721
|
+
if (!(c instanceof ParserRuleContext && c.ruleIndex === P.RULE_expr))
|
|
722
|
+
continue;
|
|
723
|
+
const e = lowerExpr(c);
|
|
724
|
+
if (mode === "subject")
|
|
725
|
+
subject = e;
|
|
726
|
+
else if (mode === "when")
|
|
727
|
+
pendingWhen = e;
|
|
728
|
+
else if (mode === "then") {
|
|
729
|
+
whens.push({ when: pendingWhen ?? otherExpr(node), then: e });
|
|
730
|
+
pendingWhen = undefined;
|
|
731
|
+
}
|
|
732
|
+
else
|
|
733
|
+
elseExpr = e;
|
|
734
|
+
}
|
|
735
|
+
if (subject) {
|
|
736
|
+
const desugared = whens.map((w) => ({
|
|
737
|
+
when: { kind: "binary", op: "=", left: subject, right: w.when, cst: node },
|
|
738
|
+
then: w.then,
|
|
739
|
+
}));
|
|
740
|
+
return { kind: "case", whens: desugared, elseExpr, cst: node };
|
|
741
|
+
}
|
|
742
|
+
return { kind: "case", whens, elseExpr, cst: node };
|
|
743
|
+
}
|
|
744
|
+
// --- expr_binary (the postfix predicate loop) ----------------------------------
|
|
745
|
+
// expr_binary: expr_comparison (
|
|
746
|
+
// (ASSIGN|EQ|NOT_EQ1|NOT_EQ2) expr_comparison
|
|
747
|
+
// | IS_ NOT_? (DISTINCT_ FROM_)? expr_comparison
|
|
748
|
+
// | NOT_? BETWEEN_ expr_comparison AND_ expr_comparison
|
|
749
|
+
// | NOT_? IN_ ( '(' (select_stmt | expr_comparison (',' expr_comparison)*)? ')'
|
|
750
|
+
// | (schema_name '.')? table_name
|
|
751
|
+
// | (schema_name '.')? table_function_name '(' (expr_comparison …)? ')' )
|
|
752
|
+
// | NOT_? ( LIKE_ expr_comparison (ESCAPE_ expr_comparison)? | (GLOB_|REGEXP_|MATCH_) expr_comparison )
|
|
753
|
+
// | ISNULL_ | NOTNULL_ | NOT_ NULL_
|
|
754
|
+
// )*
|
|
755
|
+
function lowerBinary(node) {
|
|
756
|
+
const kids = kidsOf(node);
|
|
757
|
+
const firstR = nextRule(kids, 0, P.RULE_expr_comparison);
|
|
758
|
+
let left = firstR ? lowerExpr(firstR.node) : otherExpr(node);
|
|
759
|
+
let i = firstR ? firstR.index + 1 : kids.length;
|
|
760
|
+
const operand = (from) => {
|
|
761
|
+
const r = nextRule(kids, from, P.RULE_expr_comparison);
|
|
762
|
+
return r ? { expr: lowerExpr(r.node), index: r.index + 1 } : { expr: otherExpr(node), index: from };
|
|
763
|
+
};
|
|
764
|
+
while (i < kids.length) {
|
|
765
|
+
const c = kids[i];
|
|
766
|
+
if (!(c instanceof TerminalNode)) {
|
|
767
|
+
i++;
|
|
768
|
+
continue;
|
|
769
|
+
}
|
|
770
|
+
const t = c.symbol.type;
|
|
771
|
+
if (t === P.ASSIGN || t === P.EQ || t === P.NOT_EQ1 || t === P.NOT_EQ2) {
|
|
772
|
+
const { expr, index } = operand(i + 1);
|
|
773
|
+
left = { kind: "binary", op: c.getText(), left, right: expr, cst: node };
|
|
774
|
+
i = index;
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
if (t === P.IS_) {
|
|
778
|
+
i++;
|
|
779
|
+
let negated = false;
|
|
780
|
+
if (isToken(kids[i], P.NOT_)) {
|
|
781
|
+
negated = true;
|
|
782
|
+
i++;
|
|
783
|
+
}
|
|
784
|
+
const distinct = isToken(kids[i], P.DISTINCT_);
|
|
785
|
+
if (distinct) {
|
|
786
|
+
i++;
|
|
787
|
+
if (isToken(kids[i], P.FROM_))
|
|
788
|
+
i++;
|
|
789
|
+
}
|
|
790
|
+
const { expr, index } = operand(i);
|
|
791
|
+
i = index;
|
|
792
|
+
left = distinct
|
|
793
|
+
? { kind: "predicate", op: "distinct from", negated, operand: left, args: [expr], cst: node }
|
|
794
|
+
: { kind: "binary", op: negated ? "is not" : "is", left, right: expr, cst: node };
|
|
795
|
+
continue;
|
|
796
|
+
}
|
|
797
|
+
let negated = false;
|
|
798
|
+
if (t === P.NOT_) {
|
|
799
|
+
// NOT NULL is a nullness predicate; otherwise NOT prefixes BETWEEN / IN / LIKE-family.
|
|
800
|
+
if (isToken(kids[i + 1], P.NULL_)) {
|
|
801
|
+
left = { kind: "predicate", op: "null", negated: true, operand: left, args: [], cst: node };
|
|
802
|
+
i += 2;
|
|
803
|
+
continue;
|
|
804
|
+
}
|
|
805
|
+
negated = true;
|
|
806
|
+
i++;
|
|
807
|
+
}
|
|
808
|
+
const ot = kids[i] instanceof TerminalNode ? kids[i].symbol.type : undefined;
|
|
809
|
+
if (ot === P.BETWEEN_) {
|
|
810
|
+
const lo = operand(i + 1);
|
|
811
|
+
const hi = operand(lo.index);
|
|
812
|
+
left = { kind: "predicate", op: "between", negated, operand: left, args: [lo.expr, hi.expr], cst: node };
|
|
813
|
+
i = hi.index;
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
if (ot === P.IN_) {
|
|
817
|
+
const { args, nextIndex } = consumeInPayload(kids, i + 1);
|
|
818
|
+
left = { kind: "predicate", op: "in", negated, operand: left, args, cst: node };
|
|
819
|
+
i = nextIndex;
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
if (ot === P.LIKE_ || ot === P.GLOB_ || ot === P.REGEXP_ || ot === P.MATCH_) {
|
|
823
|
+
const op = ot === P.LIKE_ ? "like" : ot === P.GLOB_ ? "glob" : ot === P.REGEXP_ ? "rlike" : "match";
|
|
824
|
+
const pat = operand(i + 1);
|
|
825
|
+
const args = [pat.expr];
|
|
826
|
+
i = pat.index;
|
|
827
|
+
if (isToken(kids[i], P.ESCAPE_)) {
|
|
828
|
+
const esc = operand(i + 1);
|
|
829
|
+
args.push(esc.expr);
|
|
830
|
+
i = esc.index;
|
|
831
|
+
}
|
|
832
|
+
left = { kind: "predicate", op, negated, operand: left, args, cst: node };
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
if (ot === P.ISNULL_) {
|
|
836
|
+
left = { kind: "predicate", op: "null", negated: false, operand: left, args: [], cst: node };
|
|
837
|
+
i++;
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
if (ot === P.NOTNULL_) {
|
|
841
|
+
left = { kind: "predicate", op: "null", negated: true, operand: left, args: [], cst: node };
|
|
842
|
+
i++;
|
|
843
|
+
continue;
|
|
844
|
+
}
|
|
845
|
+
i++; // an unrecognized token — skip, never throw
|
|
846
|
+
}
|
|
847
|
+
return left;
|
|
848
|
+
}
|
|
849
|
+
/** Consume an IN payload starting at `from` (just after the IN_ token): a parenthesized
|
|
850
|
+
* select_stmt / expr_comparison list, a bare (schema.)?table_name, or a table_function_name(args).
|
|
851
|
+
* Returns the collected args and the index past the payload. */
|
|
852
|
+
function consumeInPayload(kids, from) {
|
|
853
|
+
const args = [];
|
|
854
|
+
const push = (c) => {
|
|
855
|
+
if (c.ruleIndex === P.RULE_select_stmt)
|
|
856
|
+
args.push({ kind: "subquery", query: lowerSelectStmt(c), cst: c });
|
|
857
|
+
else if (c.ruleIndex === P.RULE_expr_comparison)
|
|
858
|
+
args.push(lowerExpr(c));
|
|
859
|
+
};
|
|
860
|
+
let i = from;
|
|
861
|
+
if (isToken(kids[i], P.OPEN_PAR)) {
|
|
862
|
+
let depth = 0;
|
|
863
|
+
for (; i < kids.length; i++) {
|
|
864
|
+
const c = kids[i];
|
|
865
|
+
if (c instanceof TerminalNode) {
|
|
866
|
+
if (c.symbol.type === P.OPEN_PAR)
|
|
867
|
+
depth++;
|
|
868
|
+
else if (c.symbol.type === P.CLOSE_PAR && --depth === 0) {
|
|
869
|
+
i++;
|
|
870
|
+
break;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
else if (c instanceof ParserRuleContext)
|
|
874
|
+
push(c);
|
|
875
|
+
}
|
|
876
|
+
return { args, nextIndex: i };
|
|
877
|
+
}
|
|
878
|
+
// Non-paren forms: consume the name rule-nodes (and a following table-function paren group).
|
|
879
|
+
while (i < kids.length) {
|
|
880
|
+
const c = kids[i];
|
|
881
|
+
if (isRule(c, P.RULE_schema_name) || isRule(c, P.RULE_table_name) || isRule(c, P.RULE_table_function_name)) {
|
|
882
|
+
i++;
|
|
883
|
+
continue;
|
|
884
|
+
}
|
|
885
|
+
if (isToken(c, P.DOT)) {
|
|
886
|
+
i++;
|
|
887
|
+
continue;
|
|
888
|
+
}
|
|
889
|
+
if (isToken(c, P.OPEN_PAR)) {
|
|
890
|
+
let depth = 0;
|
|
891
|
+
for (; i < kids.length; i++) {
|
|
892
|
+
const k = kids[i];
|
|
893
|
+
if (k instanceof TerminalNode) {
|
|
894
|
+
if (k.symbol.type === P.OPEN_PAR)
|
|
895
|
+
depth++;
|
|
896
|
+
else if (k.symbol.type === P.CLOSE_PAR && --depth === 0) {
|
|
897
|
+
i++;
|
|
898
|
+
break;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
else if (k instanceof ParserRuleContext)
|
|
902
|
+
push(k);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
break;
|
|
906
|
+
}
|
|
907
|
+
return { args, nextIndex: i };
|
|
908
|
+
}
|
|
909
|
+
// --- column extraction (single source of truth for SelectExpr.columns) ---------
|
|
910
|
+
function columnsOf(expr, acc, clause) {
|
|
911
|
+
switch (expr.kind) {
|
|
912
|
+
case "column":
|
|
913
|
+
acc.push({ kind: "columnref", parts: expr.parts, clause, cst: expr.cst, partSpans: expr.partSpans });
|
|
914
|
+
break;
|
|
915
|
+
case "binary":
|
|
916
|
+
columnsOf(expr.left, acc, clause);
|
|
917
|
+
columnsOf(expr.right, acc, clause);
|
|
918
|
+
break;
|
|
919
|
+
case "unary":
|
|
920
|
+
columnsOf(expr.operand, acc, clause);
|
|
921
|
+
break;
|
|
922
|
+
case "cast":
|
|
923
|
+
columnsOf(expr.expr, acc, clause);
|
|
924
|
+
break;
|
|
925
|
+
case "function":
|
|
926
|
+
expr.args.forEach((a) => columnsOf(a, acc, clause));
|
|
927
|
+
expr.window?.partitionBy.forEach((a) => columnsOf(a, acc, clause));
|
|
928
|
+
expr.window?.orderBy.forEach((a) => columnsOf(a, acc, clause));
|
|
929
|
+
break;
|
|
930
|
+
case "case":
|
|
931
|
+
expr.whens.forEach((w) => {
|
|
932
|
+
columnsOf(w.when, acc, clause);
|
|
933
|
+
columnsOf(w.then, acc, clause);
|
|
934
|
+
});
|
|
935
|
+
if (expr.elseExpr)
|
|
936
|
+
columnsOf(expr.elseExpr, acc, clause);
|
|
937
|
+
break;
|
|
938
|
+
case "predicate":
|
|
939
|
+
columnsOf(expr.operand, acc, clause);
|
|
940
|
+
expr.args.forEach((a) => columnsOf(a, acc, clause));
|
|
941
|
+
break;
|
|
942
|
+
case "subscript":
|
|
943
|
+
columnsOf(expr.base, acc, clause);
|
|
944
|
+
columnsOf(expr.index, acc, clause);
|
|
945
|
+
break;
|
|
946
|
+
case "other":
|
|
947
|
+
cstColumnRefs(expr.cst, acc, clause);
|
|
948
|
+
break;
|
|
949
|
+
// literal / star / subquery / exists / lambda / with → no column refs at this level
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
/** Fallback: recover column references from inside an unmodelled `other` node — descend the CST,
|
|
953
|
+
* lowering any expr_base column, but never into a nested select_stmt (its own scope). */
|
|
954
|
+
function cstColumnRefs(node, acc, clause) {
|
|
955
|
+
for (const child of kidsOf(node)) {
|
|
956
|
+
if (!(child instanceof ParserRuleContext))
|
|
957
|
+
continue;
|
|
958
|
+
if (child.ruleIndex === P.RULE_select_stmt)
|
|
959
|
+
continue;
|
|
960
|
+
if (child.ruleIndex === P.RULE_expr_base) {
|
|
961
|
+
const e = lowerExprBase(child);
|
|
962
|
+
if (e.kind === "column") {
|
|
963
|
+
acc.push({ kind: "columnref", parts: e.parts, clause, cst: child, partSpans: e.partSpans });
|
|
964
|
+
continue;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
cstColumnRefs(child, acc, clause);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
function hasAggregate(expr) {
|
|
971
|
+
switch (expr.kind) {
|
|
972
|
+
case "function":
|
|
973
|
+
return (expr.aggregate && !expr.window) || expr.args.some(hasAggregate);
|
|
974
|
+
case "binary":
|
|
975
|
+
return hasAggregate(expr.left) || hasAggregate(expr.right);
|
|
976
|
+
case "unary":
|
|
977
|
+
return hasAggregate(expr.operand);
|
|
978
|
+
case "cast":
|
|
979
|
+
return hasAggregate(expr.expr);
|
|
980
|
+
case "case":
|
|
981
|
+
return (expr.whens.some((w) => hasAggregate(w.when) || hasAggregate(w.then)) ||
|
|
982
|
+
(expr.elseExpr !== undefined && hasAggregate(expr.elseExpr)));
|
|
983
|
+
case "predicate":
|
|
984
|
+
return hasAggregate(expr.operand) || expr.args.some(hasAggregate);
|
|
985
|
+
case "subscript":
|
|
986
|
+
return hasAggregate(expr.base);
|
|
987
|
+
default:
|
|
988
|
+
return false;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
// --- CST navigation helpers ----------------------------------------------------
|
|
992
|
+
function kidsOf(node) {
|
|
993
|
+
if (node instanceof ParserRuleContext)
|
|
994
|
+
return node.children ?? [];
|
|
995
|
+
const out = [];
|
|
996
|
+
for (let i = 0; i < node.getChildCount(); i++) {
|
|
997
|
+
const c = node.getChild(i);
|
|
998
|
+
if (c)
|
|
999
|
+
out.push(c);
|
|
1000
|
+
}
|
|
1001
|
+
return out;
|
|
1002
|
+
}
|
|
1003
|
+
function* descendants(node) {
|
|
1004
|
+
for (const child of kidsOf(node)) {
|
|
1005
|
+
if (child instanceof ParserRuleContext) {
|
|
1006
|
+
yield child;
|
|
1007
|
+
yield* descendants(child);
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
function firstOfRule(node, ruleIndex) {
|
|
1012
|
+
for (const d of descendants(node))
|
|
1013
|
+
if (d.ruleIndex === ruleIndex)
|
|
1014
|
+
return d;
|
|
1015
|
+
return undefined;
|
|
1016
|
+
}
|
|
1017
|
+
function directChildrenOfRule(node, ruleIndex) {
|
|
1018
|
+
const out = [];
|
|
1019
|
+
for (const child of kidsOf(node)) {
|
|
1020
|
+
if (child instanceof ParserRuleContext && child.ruleIndex === ruleIndex)
|
|
1021
|
+
out.push(child);
|
|
1022
|
+
}
|
|
1023
|
+
return out;
|
|
1024
|
+
}
|
|
1025
|
+
/** Collect rule nodes within `node` but not inside a nested select_stmt (its own scope); matched
|
|
1026
|
+
* nodes are not themselves descended into. */
|
|
1027
|
+
function shallowCollectOfRule(node, ruleIndex) {
|
|
1028
|
+
const out = [];
|
|
1029
|
+
const walk = (n) => {
|
|
1030
|
+
for (const child of kidsOf(n)) {
|
|
1031
|
+
if (!(child instanceof ParserRuleContext))
|
|
1032
|
+
continue;
|
|
1033
|
+
if (child.ruleIndex === ruleIndex)
|
|
1034
|
+
out.push(child);
|
|
1035
|
+
else if (child.ruleIndex === P.RULE_select_stmt)
|
|
1036
|
+
continue;
|
|
1037
|
+
else
|
|
1038
|
+
walk(child);
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
walk(node);
|
|
1042
|
+
return out;
|
|
1043
|
+
}
|
|
1044
|
+
/** The first `kids[j >= from]` that is a rule node of `ruleIndex`, with its index. */
|
|
1045
|
+
function nextRule(kids, from, ruleIndex) {
|
|
1046
|
+
for (let j = from; j < kids.length; j++) {
|
|
1047
|
+
const c = kids[j];
|
|
1048
|
+
if (c instanceof ParserRuleContext && c.ruleIndex === ruleIndex)
|
|
1049
|
+
return { node: c, index: j };
|
|
1050
|
+
}
|
|
1051
|
+
return undefined;
|
|
1052
|
+
}
|
|
1053
|
+
function isRule(node, ruleIndex) {
|
|
1054
|
+
return node instanceof ParserRuleContext && node.ruleIndex === ruleIndex;
|
|
1055
|
+
}
|
|
1056
|
+
function isToken(node, type) {
|
|
1057
|
+
return node instanceof TerminalNode && node.symbol.type === type;
|
|
1058
|
+
}
|
|
1059
|
+
function hasDirectToken(node, type) {
|
|
1060
|
+
for (const child of kidsOf(node)) {
|
|
1061
|
+
if (child instanceof TerminalNode && child.symbol.type === type)
|
|
1062
|
+
return true;
|
|
1063
|
+
}
|
|
1064
|
+
return false;
|
|
1065
|
+
}
|
|
1066
|
+
/** True when `node` is a descendant of `ancestor` (used to match a flattened parenthesized-join
|
|
1067
|
+
* source back to its enclosing table_or_subquery). */
|
|
1068
|
+
function isWithin(node, ancestor) {
|
|
1069
|
+
let n = node instanceof ParserRuleContext ? node.parent : undefined;
|
|
1070
|
+
while (n) {
|
|
1071
|
+
if (n === ancestor)
|
|
1072
|
+
return true;
|
|
1073
|
+
n = n.parent;
|
|
1074
|
+
}
|
|
1075
|
+
return false;
|
|
1076
|
+
}
|
|
1077
|
+
function otherExpr(node) {
|
|
1078
|
+
return { kind: "other", text: node.getText(), cst: node };
|
|
1079
|
+
}
|
|
1080
|
+
function emptyBody(cst) {
|
|
1081
|
+
return {
|
|
1082
|
+
kind: "select",
|
|
1083
|
+
projections: [],
|
|
1084
|
+
from: [],
|
|
1085
|
+
columns: [],
|
|
1086
|
+
aggregated: false,
|
|
1087
|
+
unsupported: ["unparsed"],
|
|
1088
|
+
cst,
|
|
1089
|
+
};
|
|
1090
|
+
}
|
|
1091
|
+
function emptyQuery(cst) {
|
|
1092
|
+
return { kind: "query", ctes: [], body: emptyBody(cst), cst };
|
|
1093
|
+
}
|