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
package/dist/derived-dialects.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// ---------------------------------------------------------------------------
|
|
2
|
-
// Derived-dialect → dialect map. The
|
|
2
|
+
// Derived-dialect → dialect map. The grammars parse more than their own named
|
|
3
3
|
// engines: a *derived dialect* is an engine with no grammar of its own whose
|
|
4
4
|
// SQL surface is a subset of — or identical to — one we already parse (Amazon
|
|
5
5
|
// Athena's engine is Trino, AWS Glue runs Spark, Microsoft Fabric / Azure
|
|
@@ -20,6 +20,8 @@ export const DERIVED_DIALECTS = {
|
|
|
20
20
|
postgres: "postgres",
|
|
21
21
|
duckdb: "duckdb",
|
|
22
22
|
trino: "trino",
|
|
23
|
+
sqlite: "sqlite",
|
|
24
|
+
mysql: "mysql",
|
|
23
25
|
// our dialect name (not an engine name) — accepted so both vocabularies work
|
|
24
26
|
tsql: "tsql",
|
|
25
27
|
// Spark SQL family — Databricks SQL = Spark SQL; AWS Glue runs Spark
|
|
@@ -34,6 +36,22 @@ export const DERIVED_DIALECTS = {
|
|
|
34
36
|
// is Trino's predecessor
|
|
35
37
|
athena: "trino",
|
|
36
38
|
presto: "trino",
|
|
39
|
+
// MariaDB — forked from MySQL 5.1 and still a near-superset for ordinary DQL/DML, so mapped to
|
|
40
|
+
// the mysql grammar as a PARTIAL derived alias (Open Gap, not full coverage — MariaDB's own
|
|
41
|
+
// extensions are unmodeled). B-R5.5 spot-checked four MariaDB-specific statements against the
|
|
42
|
+
// mysql/Positive-Technologies grammar (grammars/mysql/) by actually parsing them
|
|
43
|
+
// (temp_auto/mariadb-probe.mts, parseMysql()): all four FAIL —
|
|
44
|
+
// `SELECT NEXT VALUE FOR seq_name` (mariadb.com/docs/.../sequences/next-value) — "no viable
|
|
45
|
+
// alternative" (no NEXT/VALUE/FOR sequence-expression production);
|
|
46
|
+
// `DELETE FROM t WHERE id = 1 RETURNING *` (mariadb.com/docs/.../delete) — "mismatched input
|
|
47
|
+
// 'RETURNING'" (no RETURNING clause on DELETE; MySQL itself has none either);
|
|
48
|
+
// `INSERT INTO t (a) VALUES (1) RETURNING *` (mariadb.com/docs/.../insert) — "extraneous input
|
|
49
|
+
// '*'", same missing-RETURNING gap;
|
|
50
|
+
// `CREATE SEQUENCE seq_name START WITH 1 INCREMENT BY 1` (mariadb.com/docs/.../sequences/
|
|
51
|
+
// create-sequence) — "no viable alternative" (no CREATE SEQUENCE DDL in this grammar).
|
|
52
|
+
// A plain `SELECT a, b FROM t WHERE a = 1` control probe parses with 0 errors, confirming
|
|
53
|
+
// ordinary DQL still works — the alias covers that surface, not MariaDB's own additions.
|
|
54
|
+
mariadb: "mysql",
|
|
37
55
|
// Alternate spelling of the engine name (alias class, same as our own dialect
|
|
38
56
|
// names above). Admitted 2026-07-10 on the anvil channel's request; caveat noted
|
|
39
57
|
// there: no dbt adapter is attested to emit `postgresql` as its adapter_type —
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { IdentKind } from "../ident/fold.js";
|
|
2
|
+
import type { FnRule } from "../infer/functions.js";
|
|
3
|
+
import type { Type } from "../infer/types.js";
|
|
4
|
+
import type { Expr } from "../ir/ir.js";
|
|
5
|
+
import type { FnSignature } from "../signature/signatures.js";
|
|
6
|
+
export interface DialectBehavior {
|
|
7
|
+
fold(raw: string, kind?: IdentKind): string;
|
|
8
|
+
displayName(raw: string): string;
|
|
9
|
+
foldTableName(parts: string[]): string[];
|
|
10
|
+
matchesSourceKey(key: string, rawPart: string): boolean;
|
|
11
|
+
likeMatch(pattern: string, name: string): boolean;
|
|
12
|
+
literal(text: string): Type;
|
|
13
|
+
parseType(text: string): Type;
|
|
14
|
+
functions: Record<string, FnRule>;
|
|
15
|
+
division: "float" | "integer" | "decimal";
|
|
16
|
+
special?(fn: Extract<Expr, {
|
|
17
|
+
kind: "function";
|
|
18
|
+
}>): Type | undefined;
|
|
19
|
+
/** The dialect's merged function-signature table (curated overrides folded over the harvested
|
|
20
|
+
* long tail at generation time — src/<dialect>/signatures.generated.ts). Each name maps to an
|
|
21
|
+
* ordered overload SET, not a single shape. The arity checker trusts every overload regardless of
|
|
22
|
+
* origin; operand-type checking trusts a name with exactly one overload of "curated" origin only. */
|
|
23
|
+
signatures: Record<string, readonly FnSignature[]>;
|
|
24
|
+
/** Whether an argument type is acceptable for a declared param (dialect implicit-coercion rules). */
|
|
25
|
+
accepts(argType: Type, paramText: string | undefined): boolean;
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Type } from "../infer/types.js";
|
|
2
|
+
export declare const IMPLICIT_STR_TO_NUM: ReadonlySet<string>;
|
|
3
|
+
export declare const IMPLICIT_BOOL_NUM: ReadonlySet<string>;
|
|
4
|
+
/** Whether `argType` is acceptable for a declared param whose type text is `paramText`. `parseType`
|
|
5
|
+
* parses that text with the dialect's rules; `strToNum`/`boolNum` are the dialect's implicit-coercion
|
|
6
|
+
* flags (IMPLICIT_STR_TO_NUM / IMPLICIT_BOOL_NUM membership). Pure engine, takes no dialect. */
|
|
7
|
+
export declare function acceptsFor(parseType: (text: string) => Type, strToNum: boolean, boolNum: boolean, argType: Type, paramText: string | undefined): boolean;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const NUMERIC = new Set(["tinyint", "smallint", "int", "bigint", "float", "double", "decimal"]);
|
|
2
|
+
const TEMPORAL = new Set(["date", "timestamp", "time", "interval"]);
|
|
3
|
+
function familyOf(name) {
|
|
4
|
+
if (NUMERIC.has(name))
|
|
5
|
+
return "num";
|
|
6
|
+
if (name === "string")
|
|
7
|
+
return "str";
|
|
8
|
+
if (name === "boolean")
|
|
9
|
+
return "bool";
|
|
10
|
+
if (TEMPORAL.has(name))
|
|
11
|
+
return "temporal";
|
|
12
|
+
if (name === "binary")
|
|
13
|
+
return "binary";
|
|
14
|
+
return "other";
|
|
15
|
+
}
|
|
16
|
+
// Dialects that implicitly bridge STRING to numeric in a function argument, so a str->num mismatch must
|
|
17
|
+
// NOT be flagged. Doc-cited per dialect:
|
|
18
|
+
// - databricks: implicit crosscasting casts STRING to the expected numeric type
|
|
19
|
+
// (docs.databricks.com/sql/language-manual/sql-ref-datatype-rules);
|
|
20
|
+
// - tsql: char/varchar to int/decimal is an implicit conversion in the CAST/CONVERT chart
|
|
21
|
+
// (learn.microsoft.com/sql/t-sql/functions/cast-and-convert-transact-sql);
|
|
22
|
+
// - snowflake: VARCHAR containing a number coerces to NUMBER
|
|
23
|
+
// (docs.snowflake.com/en/sql-reference/data-type-conversion);
|
|
24
|
+
// - redshift: PG-8.0 lineage keeps pre-8.3 implicit text to numeric casts
|
|
25
|
+
// (docs.aws.amazon.com/redshift/latest/dg/c_Supported_data_types.html);
|
|
26
|
+
// - postgres / duckdb: a quoted constant is initially UNKNOWN and coerces to whatever the call needs
|
|
27
|
+
// (postgresql.org/docs/18 sql-syntax-lexical 4.1.2.1), but our inference types every quoted literal
|
|
28
|
+
// as `string`, so rejecting would false-fire on valid SQL;
|
|
29
|
+
// - sqlite: TEXT/NUMERIC type affinity coerces a TEXT value against a numeric argument
|
|
30
|
+
// (sqlite.org/datatype3.html "Type Affinity");
|
|
31
|
+
// - mysql: numeric context converts a string operand to a number automatically
|
|
32
|
+
// (dev.mysql.com/doc/refman/8.4/en/type-conversion.html).
|
|
33
|
+
// NOT in the set (rejection stays live, corpus-proven): bigquery, trino.
|
|
34
|
+
export const IMPLICIT_STR_TO_NUM = new Set([
|
|
35
|
+
"databricks",
|
|
36
|
+
"tsql",
|
|
37
|
+
"snowflake",
|
|
38
|
+
"redshift",
|
|
39
|
+
"postgres",
|
|
40
|
+
"duckdb",
|
|
41
|
+
"sqlite",
|
|
42
|
+
"mysql",
|
|
43
|
+
]);
|
|
44
|
+
// Dialects that implicitly bridge boolean to/from numeric. tsql: `bit` (aliased to boolean) converts
|
|
45
|
+
// to/from int per the CAST/CONVERT chart. mysql: BOOL/BOOLEAN is a documented TINYINT(1) synonym
|
|
46
|
+
// (dev.mysql.com/doc/refman/8.4/en/numeric-type-syntax.html) and a comparison result is 1/0/NULL,
|
|
47
|
+
// assignable anywhere an integer is expected. Everywhere else bool<->num rejection is corpus-proven safe;
|
|
48
|
+
// sqlite is left out (TRUE/FALSE are literal aliases for 1/0 and SQLITE_ALIASES has no bool key, so this
|
|
49
|
+
// checker never sees a boolean-typed sqlite argument from a declared column).
|
|
50
|
+
export const IMPLICIT_BOOL_NUM = new Set(["tsql", "mysql"]);
|
|
51
|
+
/** Whether `argType` is acceptable for a declared param whose type text is `paramText`. `parseType`
|
|
52
|
+
* parses that text with the dialect's rules; `strToNum`/`boolNum` are the dialect's implicit-coercion
|
|
53
|
+
* flags (IMPLICIT_STR_TO_NUM / IMPLICIT_BOOL_NUM membership). Pure engine, takes no dialect. */
|
|
54
|
+
export function acceptsFor(parseType, strToNum, boolNum, argType, paramText) {
|
|
55
|
+
if (!paramText)
|
|
56
|
+
return true; // untyped param -> no information, accept
|
|
57
|
+
const param = parseType(paramText);
|
|
58
|
+
if (param.kind !== "scalar" || argType.kind !== "scalar")
|
|
59
|
+
return true; // complex/unknown -> accept
|
|
60
|
+
const fa = familyOf(argType.name);
|
|
61
|
+
const fp = familyOf(param.name);
|
|
62
|
+
if (fa === fp)
|
|
63
|
+
return true; // same family -> accept
|
|
64
|
+
if (fa === "str" && fp === "num")
|
|
65
|
+
return strToNum;
|
|
66
|
+
if ((fa === "bool" && fp === "num") || (fa === "num" && fp === "bool"))
|
|
67
|
+
return boolNum;
|
|
68
|
+
return true; // every other cross-family pair accepts
|
|
69
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { IdentKind } from "../ident/fold.js";
|
|
2
|
+
export type { IdentKind };
|
|
3
|
+
/** Fold an identifier to its identity key under the dialect's rules. */
|
|
4
|
+
export declare function foldIdentifier(raw: string, dialect: string | undefined, kind?: IdentKind): string;
|
|
5
|
+
/** Presentation twin: strip delimiters, no case change. Never use for comparison. */
|
|
6
|
+
export declare function displayName(raw: string, dialect: string | undefined): string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { resolveBehavior } from "./registry.js";
|
|
2
|
+
/** Fold an identifier to its identity key under the dialect's rules. */
|
|
3
|
+
export function foldIdentifier(raw, dialect, kind = "other") {
|
|
4
|
+
return resolveBehavior(dialect).fold(raw, kind);
|
|
5
|
+
}
|
|
6
|
+
/** Presentation twin: strip delimiters, no case change. Never use for comparison. */
|
|
7
|
+
export function displayName(raw, dialect) {
|
|
8
|
+
return resolveBehavior(dialect).displayName(raw);
|
|
9
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Dialect } from "../dialect.js";
|
|
2
|
+
import type { DialectBehavior } from "./behavior.js";
|
|
3
|
+
export declare const BEHAVIORS: Record<Dialect, DialectBehavior>;
|
|
4
|
+
/** Resolve a dialect string (the IR/Scope tag) to its behavior. Throws on an unregistered/absent
|
|
5
|
+
* dialect — sqllens applies NO default; the consumer must supply a supported Dialect. */
|
|
6
|
+
export declare function resolveBehavior(name: string | undefined): DialectBehavior;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { databricksBehavior } from "../databricks/behavior.js";
|
|
2
|
+
import { tsqlBehavior } from "../tsql/behavior.js";
|
|
3
|
+
import { snowflakeBehavior } from "../snowflake/behavior.js";
|
|
4
|
+
import { bigqueryBehavior } from "../bigquery/behavior.js";
|
|
5
|
+
import { redshiftBehavior } from "../redshift/behavior.js";
|
|
6
|
+
import { postgresBehavior } from "../postgres/behavior.js";
|
|
7
|
+
import { duckdbBehavior } from "../duckdb/behavior.js";
|
|
8
|
+
import { trinoBehavior } from "../trino/behavior.js";
|
|
9
|
+
import { sqliteBehavior } from "../sqlite/behavior.js";
|
|
10
|
+
import { mysqlBehavior } from "../mysql/behavior.js";
|
|
11
|
+
export const BEHAVIORS = {
|
|
12
|
+
databricks: databricksBehavior,
|
|
13
|
+
tsql: tsqlBehavior,
|
|
14
|
+
snowflake: snowflakeBehavior,
|
|
15
|
+
bigquery: bigqueryBehavior,
|
|
16
|
+
redshift: redshiftBehavior,
|
|
17
|
+
postgres: postgresBehavior,
|
|
18
|
+
duckdb: duckdbBehavior,
|
|
19
|
+
trino: trinoBehavior,
|
|
20
|
+
sqlite: sqliteBehavior,
|
|
21
|
+
mysql: mysqlBehavior,
|
|
22
|
+
};
|
|
23
|
+
/** Resolve a dialect string (the IR/Scope tag) to its behavior. Throws on an unregistered/absent
|
|
24
|
+
* dialect — sqllens applies NO default; the consumer must supply a supported Dialect. */
|
|
25
|
+
export function resolveBehavior(name) {
|
|
26
|
+
// Object.hasOwn guards against inherited keys ("constructor", "toString", …) resolving to a
|
|
27
|
+
// prototype member instead of throwing.
|
|
28
|
+
const b = name !== undefined && Object.hasOwn(BEHAVIORS, name)
|
|
29
|
+
? BEHAVIORS[name]
|
|
30
|
+
: undefined;
|
|
31
|
+
if (!b)
|
|
32
|
+
throw new Error(`sqllens: no behavior for dialect "${name}" — supply a supported Dialect.`);
|
|
33
|
+
return b;
|
|
34
|
+
}
|
package/dist/dialect-symbols.js
CHANGED
|
@@ -12,11 +12,10 @@
|
|
|
12
12
|
// Sources, per set:
|
|
13
13
|
//
|
|
14
14
|
// - functions: the union of (a) the dialect's type-inference registry keys
|
|
15
|
-
// (src/infer/dialect.ts `inferDialect(dialect).functions`), (b) the
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
// (d) for databricks only, the Spark higher-order function names
|
|
15
|
+
// (src/infer/dialect.ts `inferDialect(dialect).functions`), (b) the dialect's
|
|
16
|
+
// merged function-signature table (src/signature/signatures.ts SIGNATURES,
|
|
17
|
+
// built by tools/harvest-signatures.mjs from curated overrides folded over the
|
|
18
|
+
// harvested long tail), and (c) for databricks only, the Spark higher-order function names
|
|
20
19
|
// (src/infer/infer.ts HOF_LAMBDA_ARG: transform/zip_with/aggregate/reduce/
|
|
21
20
|
// transform_keys/transform_values). Those six are genuine Spark builtins
|
|
22
21
|
// (spark.apache.org/docs/latest/api/sql/#aggregate) that never get a FnRule registry
|
|
@@ -75,16 +74,20 @@ import { RedshiftLexer } from "./generated/redshift/RedshiftLexer.js";
|
|
|
75
74
|
import { PostgresLexer } from "./generated/postgres/PostgresLexer.js";
|
|
76
75
|
import { DuckdbLexer } from "./generated/duckdb/DuckdbLexer.js";
|
|
77
76
|
import { TrinoLexer } from "./generated/trino/TrinoLexer.js";
|
|
78
|
-
import {
|
|
77
|
+
import { SqliteLexer } from "./generated/sqlite/SqliteLexer.js";
|
|
78
|
+
import { MysqlLexer } from "./generated/mysql/MysqlLexer.js";
|
|
79
|
+
import { resolveBehavior } from "./dialect-behavior/registry.js";
|
|
79
80
|
import { HOF_LAMBDA_ARG } from "./infer/infer.js";
|
|
80
81
|
import { SCALAR_ALIASES, TSQL_ALIASES } from "./infer/types.js";
|
|
81
|
-
import { SNOWFLAKE_ALIASES } from "./infer
|
|
82
|
-
import { BQ_ALIASES } from "./infer
|
|
83
|
-
import { REDSHIFT_ALIASES } from "./infer
|
|
84
|
-
import { POSTGRES_ALIASES } from "./infer
|
|
85
|
-
import { DUCKDB_ALIASES } from "./infer
|
|
86
|
-
import { TRINO_ALIASES } from "./infer
|
|
87
|
-
import {
|
|
82
|
+
import { SNOWFLAKE_ALIASES } from "./snowflake/infer.js";
|
|
83
|
+
import { BQ_ALIASES } from "./bigquery/infer.js";
|
|
84
|
+
import { REDSHIFT_ALIASES } from "./redshift/infer.js";
|
|
85
|
+
import { POSTGRES_ALIASES } from "./postgres/infer.js";
|
|
86
|
+
import { DUCKDB_ALIASES } from "./duckdb/infer.js";
|
|
87
|
+
import { TRINO_ALIASES } from "./trino/infer.js";
|
|
88
|
+
import { SQLITE_ALIASES } from "./sqlite/infer.js";
|
|
89
|
+
import { MYSQL_ALIASES } from "./mysql/infer.js";
|
|
90
|
+
import { SIGNATURES } from "./signature/signatures.js";
|
|
88
91
|
// bigquery's generated lexer class is GoogleSQLLexer (the fork is Bytebase's GoogleSQL grammar).
|
|
89
92
|
const LEXERS = {
|
|
90
93
|
databricks: () => new DatabricksLexer(CharStream.fromString("")),
|
|
@@ -95,6 +98,8 @@ const LEXERS = {
|
|
|
95
98
|
postgres: () => new PostgresLexer(CharStream.fromString("")),
|
|
96
99
|
duckdb: () => new DuckdbLexer(CharStream.fromString("")),
|
|
97
100
|
trino: () => new TrinoLexer(CharStream.fromString("")),
|
|
101
|
+
sqlite: () => new SqliteLexer(CharStream.fromString("")),
|
|
102
|
+
mysql: () => new MysqlLexer(CharStream.fromString("")),
|
|
98
103
|
};
|
|
99
104
|
// The scalar-type-alias table per dialect (see module header, `types` set). Databricks has no
|
|
100
105
|
// dedicated table — dialect.ts's `parseType` falls back to types.ts's default (SCALAR_ALIASES).
|
|
@@ -107,6 +112,8 @@ const TYPE_ALIASES = {
|
|
|
107
112
|
postgres: POSTGRES_ALIASES,
|
|
108
113
|
duckdb: DUCKDB_ALIASES,
|
|
109
114
|
trino: TRINO_ALIASES,
|
|
115
|
+
sqlite: SQLITE_ALIASES,
|
|
116
|
+
mysql: MYSQL_ALIASES,
|
|
110
117
|
};
|
|
111
118
|
/** A bare, keyword-shaped literal token text: letters/digits/underscore, starting with a
|
|
112
119
|
* letter or underscore. Filters out punctuation (`'('`, `','`) and operator (`'<>'`, `'::'`)
|
|
@@ -128,11 +135,9 @@ function keywordsFor(dialect) {
|
|
|
128
135
|
}
|
|
129
136
|
function functionsFor(dialect) {
|
|
130
137
|
const out = new Set();
|
|
131
|
-
for (const name of Object.keys(
|
|
138
|
+
for (const name of Object.keys(resolveBehavior(dialect).functions))
|
|
132
139
|
out.add(name.toUpperCase());
|
|
133
|
-
for (const name of Object.keys(
|
|
134
|
-
out.add(name.toUpperCase());
|
|
135
|
-
for (const name of Object.keys(HARVESTED_SIGNATURES[dialect]))
|
|
140
|
+
for (const name of Object.keys(SIGNATURES[dialect]))
|
|
136
141
|
out.add(name.toUpperCase());
|
|
137
142
|
if (dialect === "databricks") {
|
|
138
143
|
for (const name of Object.keys(HOF_LAMBDA_ARG))
|
package/dist/dialect.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/** The dialects reachable through the unified surface. Each has its own grammar/CST and a
|
|
2
|
-
* parse+lower pair; everything after lower() runs unchanged on all
|
|
3
|
-
export type Dialect = "databricks" | "tsql" | "snowflake" | "bigquery" | "redshift" | "postgres" | "duckdb" | "trino";
|
|
2
|
+
* parse+lower pair; everything after lower() runs unchanged on all of them. */
|
|
3
|
+
export type Dialect = "databricks" | "tsql" | "snowflake" | "bigquery" | "redshift" | "postgres" | "duckdb" | "trino" | "sqlite" | "mysql";
|
|
@@ -190,7 +190,7 @@ export declare class SqlDocument {
|
|
|
190
190
|
* comes from the CELL owning `offset` (with a cell-relative offset), so a node in statement 2 of a
|
|
191
191
|
* multi-cell document resolves through its own scope tree — NOT the compound facade. The returned
|
|
192
192
|
* `expr.cst` carries CELL-relative spans; a caller turning it into a document Range shifts it by the
|
|
193
|
-
* owning cell's start
|
|
193
|
+
* owning cell's start. Single-cell: identical to today. */
|
|
194
194
|
nodeAt(offset: number): NodeHit | undefined;
|
|
195
195
|
/** The declaration + every occurrence of the symbol at `offset` (the references engine,
|
|
196
196
|
* cell-aware): resolved over the CELL owning the offset — its own scopes/ast, with a
|
|
@@ -35,7 +35,8 @@ import { referencesAt as referencesAtScopes } from "../references/references.js"
|
|
|
35
35
|
import { freezeIR } from "../ir/freeze.js";
|
|
36
36
|
import { partSpanOf, starSpanOf } from "../ir/part-span.js";
|
|
37
37
|
import { OPEN_PROVIDER } from "../qualify/template-provider.js";
|
|
38
|
-
import {
|
|
38
|
+
import { behaviorOf } from "../dialect-behavior/carrier.js";
|
|
39
|
+
import { resolveBehavior } from "../dialect-behavior/registry.js";
|
|
39
40
|
import { LineIndex } from "./line-index.js";
|
|
40
41
|
import { nodeAt } from "./node-at.js";
|
|
41
42
|
import { splitStatements } from "./split.js";
|
|
@@ -346,7 +347,7 @@ export class SqlDocument {
|
|
|
346
347
|
* comes from the CELL owning `offset` (with a cell-relative offset), so a node in statement 2 of a
|
|
347
348
|
* multi-cell document resolves through its own scope tree — NOT the compound facade. The returned
|
|
348
349
|
* `expr.cst` carries CELL-relative spans; a caller turning it into a document Range shifts it by the
|
|
349
|
-
* owning cell's start
|
|
350
|
+
* owning cell's start. Single-cell: identical to today. */
|
|
350
351
|
nodeAt(offset) {
|
|
351
352
|
const cell = this.cellAt(offset);
|
|
352
353
|
if (!cell)
|
|
@@ -617,7 +618,7 @@ export class SqlDocument {
|
|
|
617
618
|
const declarationSpan = partSpanOf(cteRef.def.nameCst ?? cteRef.def.cst);
|
|
618
619
|
if (!declarationSpan)
|
|
619
620
|
continue; // no real token to key on — never fabricate a span
|
|
620
|
-
const name =
|
|
621
|
+
const name = resolveBehavior(doc.dialect).fold(cteRef.def.name);
|
|
621
622
|
const key = `${name}:${declarationSpan.start}`;
|
|
622
623
|
let entry = byKey.get(key);
|
|
623
624
|
if (!entry) {
|
|
@@ -756,12 +757,12 @@ function scopeOutputColumns(scope, qualification) {
|
|
|
756
757
|
// zero-width convention (that exists so expanded Syms are never cursor hit-test
|
|
757
758
|
// targets; these pairs are name/position enumeration, where a real span is useful).
|
|
758
759
|
for (const pair of pairs)
|
|
759
|
-
out.push({ name:
|
|
760
|
+
out.push({ name: behaviorOf(scope).fold(pair.name), raw: pair.name, span });
|
|
760
761
|
}
|
|
761
762
|
else if (p.name !== undefined) {
|
|
762
763
|
const span = partSpanOf(p.aliasCst ?? p.cst);
|
|
763
764
|
if (span)
|
|
764
|
-
out.push({ name:
|
|
765
|
+
out.push({ name: behaviorOf(scope).fold(p.name), raw: p.name, span });
|
|
765
766
|
}
|
|
766
767
|
// else: anonymous expression — no determinable name, skip
|
|
767
768
|
}
|
|
@@ -784,14 +785,14 @@ function scopeOutputColumns(scope, qualification) {
|
|
|
784
785
|
const declared = new Map();
|
|
785
786
|
for (const branch of [scope.branches.left, scope.branches.right]) {
|
|
786
787
|
for (const col of scopeOutputColumns(branch, qualification)) {
|
|
787
|
-
const key =
|
|
788
|
+
const key = behaviorOf(scope).fold(col.raw);
|
|
788
789
|
if (!declared.has(key))
|
|
789
790
|
declared.set(key, col);
|
|
790
791
|
}
|
|
791
792
|
}
|
|
792
793
|
const out = [];
|
|
793
794
|
for (const name of names) {
|
|
794
|
-
const hit = declared.get(
|
|
795
|
+
const hit = declared.get(behaviorOf(scope).fold(name));
|
|
795
796
|
if (hit)
|
|
796
797
|
out.push(hit); // a name no branch declares a span for is skipped, never fabricated
|
|
797
798
|
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { acceptsFor } from "../dialect-behavior/coerce-rules.js";
|
|
2
|
+
import { likePatternToRegExp } from "../scope/like-pattern.js";
|
|
3
|
+
import { SIGNATURES } from "../signature/signatures.js";
|
|
4
|
+
import { displayName, fold, foldTableName, matchesSourceKey } from "./fold.js";
|
|
5
|
+
import { duckdbLiteral, duckdbParseType, DUCKDB_FUNCTION_RETURNS } from "./infer.js";
|
|
6
|
+
// DuckDB implicit coercion: a quoted constant is initially UNKNOWN and coerces to whatever the call
|
|
7
|
+
// needs (str->num), no bool<->num.
|
|
8
|
+
// STR_TO_NUM=true, BOOL_NUM=false
|
|
9
|
+
export const duckdbBehavior = {
|
|
10
|
+
fold,
|
|
11
|
+
displayName,
|
|
12
|
+
foldTableName,
|
|
13
|
+
matchesSourceKey,
|
|
14
|
+
likeMatch: (pattern, value) => likePatternToRegExp(pattern).test(value),
|
|
15
|
+
literal: duckdbLiteral,
|
|
16
|
+
parseType: duckdbParseType,
|
|
17
|
+
functions: DUCKDB_FUNCTION_RETURNS,
|
|
18
|
+
division: "float",
|
|
19
|
+
signatures: SIGNATURES.duckdb,
|
|
20
|
+
accepts: (argType, paramText) => acceptsFor(duckdbParseType, true, false, argType, paramText),
|
|
21
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type FoldRule, type IdentKind } from "../ident/fold.js";
|
|
2
|
+
export declare const DUCKDB_FOLD_RULE: FoldRule;
|
|
3
|
+
/** Fold an identifier to its DuckDB identity key. */
|
|
4
|
+
export declare function fold(raw: string, kind?: IdentKind): string;
|
|
5
|
+
/** Presentation twin: strip delimiters, no case change. */
|
|
6
|
+
export declare function displayName(raw: string): string;
|
|
7
|
+
export declare function foldTableName(parts: string[]): string[];
|
|
8
|
+
export declare function matchesSourceKey(key: string, rawPart: string): boolean;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// DuckDB identifier folding. The FoldRule plus its bound engine, colocated here because BOTH the
|
|
2
|
+
// upstream lower() and the downstream DialectBehavior need it (the fold rule is the one dialect concern
|
|
3
|
+
// used at two stages).
|
|
4
|
+
// duckdb.org/docs/current/sql/dialect/keywords_and_identifiers.html — verified live: "Identifiers
|
|
5
|
+
// in DuckDB are always case-insensitive, similarly to PostgreSQL. However, unlike PostgreSQL...
|
|
6
|
+
// DuckDB also treats quoted identifiers as case-insensitive" — quoting only preserves the
|
|
7
|
+
// identifier for DISPLAY, not identity. Doubled-quote escape: "Double quotes can be escaped by
|
|
8
|
+
// repeating the quote character."
|
|
9
|
+
import { displayWith, foldWith } from "../ident/fold.js";
|
|
10
|
+
const DOUBLE_QUOTE = ['"', '"'];
|
|
11
|
+
export const DUCKDB_FOLD_RULE = {
|
|
12
|
+
delimiters: [DOUBLE_QUOTE],
|
|
13
|
+
unquoted: "lower",
|
|
14
|
+
quoted: "lower",
|
|
15
|
+
};
|
|
16
|
+
/** Fold an identifier to its DuckDB identity key. */
|
|
17
|
+
export function fold(raw, kind = "other") {
|
|
18
|
+
return foldWith(DUCKDB_FOLD_RULE, raw, kind);
|
|
19
|
+
}
|
|
20
|
+
/** Presentation twin: strip delimiters, no case change. */
|
|
21
|
+
export function displayName(raw) {
|
|
22
|
+
return displayWith(DUCKDB_FOLD_RULE, raw);
|
|
23
|
+
}
|
|
24
|
+
export function foldTableName(parts) {
|
|
25
|
+
return parts.map((p) => fold(p, "table"));
|
|
26
|
+
}
|
|
27
|
+
export function matchesSourceKey(key, rawPart) {
|
|
28
|
+
return key === fold(rawPart) || key === fold(rawPart, "table");
|
|
29
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// The complete duckdb dialect module: parse + lower (front end) and behavior (semantic knowledge).
|
|
2
|
+
// The registry wires this; to understand everything sqllens does for duckdb, read this folder.
|
|
3
|
+
import { duckdbBehavior } from "./behavior.js";
|
|
4
|
+
import { lower } from "./lower.js";
|
|
5
|
+
import { parseDuckdb } from "./parse.js";
|
|
6
|
+
export const duckdb = {
|
|
7
|
+
parse: parseDuckdb,
|
|
8
|
+
lower,
|
|
9
|
+
behavior: duckdbBehavior,
|
|
10
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { type Type } from "
|
|
2
|
-
import type { FnRule } from "
|
|
1
|
+
import { type Type } from "../infer/types.js";
|
|
2
|
+
import type { FnRule } from "../infer/functions.js";
|
|
3
3
|
export declare const DUCKDB_ALIASES: Record<string, string>;
|
|
4
4
|
export declare function duckdbParseType(text: string): Type;
|
|
5
5
|
/** DuckDB literal forms — sql/data_types/literal_types.md: integers without a decimal point are
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { parseType, scalar, UNKNOWN } from "
|
|
2
|
-
import { commonType } from "
|
|
1
|
+
import { parseType, scalar, UNKNOWN } from "../infer/types.js";
|
|
2
|
+
import { commonType } from "../infer/coerce.js";
|
|
3
|
+
import { fold } from "./fold.js";
|
|
3
4
|
// ---------------------------------------------------------------------------
|
|
4
5
|
// DuckDB inference knowledge. Scalar-name aliases map DuckDB's type vocabulary
|
|
5
6
|
// onto the shared canonical names (duckdb.org/docs/current/sql/data_types/
|
|
@@ -48,7 +49,7 @@ export const DUCKDB_ALIASES = {
|
|
|
48
49
|
json: "json",
|
|
49
50
|
};
|
|
50
51
|
export function duckdbParseType(text) {
|
|
51
|
-
return parseType(text, DUCKDB_ALIASES,
|
|
52
|
+
return parseType(text, DUCKDB_ALIASES, fold);
|
|
52
53
|
}
|
|
53
54
|
const S = scalar("string");
|
|
54
55
|
const I = scalar("int");
|
package/dist/duckdb/lower.js
CHANGED
|
@@ -3,7 +3,7 @@ import { DuckdbParser as P } from "../generated/duckdb/DuckdbParser.js";
|
|
|
3
3
|
import { keywordCategory, swallowedCategories, swallowedStatements } from "../ir/statement.js";
|
|
4
4
|
import { partSpansOf } from "../ir/part-span.js";
|
|
5
5
|
import { freezeIR } from "../ir/freeze.js";
|
|
6
|
-
import { displayName } from "
|
|
6
|
+
import { displayName } from "./fold.js";
|
|
7
7
|
// ---------------------------------------------------------------------------
|
|
8
8
|
// Lowering — DuckDB (fork of this repo's grammars/postgres pair, TVL lineage)
|
|
9
9
|
// CST -> the shared dialect-neutral IR (src/ir/ir.ts). The semantic layer runs
|
|
@@ -14,8 +14,8 @@ import { displayName } from "../ident/fold.js";
|
|
|
14
14
|
// QUALIFY, GROUP BY ALL, star EXCLUDE/REPLACE/RENAME + COLUMNS(), prefix
|
|
15
15
|
// aliases (x: 42, alias: tbl), list/struct/map literals + comprehensions,
|
|
16
16
|
// `lambda x:` lambdas, method chaining (x.f(y) → f(x, y)), string-literal
|
|
17
|
-
// relations (FROM 'file.parquet'), and the PIVOT/UNPIVOT statements (
|
|
18
|
-
//
|
|
17
|
+
// relations (FROM 'file.parquet'), and the PIVOT/UNPIVOT statements (modelled
|
|
18
|
+
// onto the shared PivotInfo/UnpivotInfo; the statement PIVOT is `dynamic`).
|
|
19
19
|
// ---------------------------------------------------------------------------
|
|
20
20
|
// https://duckdb.org/docs/current/sql/functions/aggregates.html — general + approximate +
|
|
21
21
|
// statistical + ordered-set aggregate names (holistic/nested families included).
|
|
@@ -259,10 +259,15 @@ function nonQuery(cst, reason) {
|
|
|
259
259
|
cst,
|
|
260
260
|
};
|
|
261
261
|
}
|
|
262
|
-
/** PIVOT/UNPIVOT statement —
|
|
263
|
-
*
|
|
262
|
+
/** PIVOT/UNPIVOT statement — modelled onto the shared PivotInfo/UnpivotInfo IR, the same shapes the
|
|
263
|
+
* sibling dialects produce (duckdb.org/docs/current/sql/statements/pivot.md, unpivot.md).
|
|
264
|
+
*
|
|
265
|
+
* PIVOT `⟨rel⟩ ON ⟨cols⟩ USING ⟨aggs⟩ [GROUP BY ⟨rows⟩]`: the distinct ON-values become output columns,
|
|
266
|
+
* so the output is data-dependent — modelled as a `dynamic` PivotInfo (structure captured; output
|
|
267
|
+
* resolves to unknown, never a wrong set). UNPIVOT `⟨rel⟩ ON ⟨cols⟩ [INTO NAME ⟨n⟩ VALUE ⟨v⟩]` is a
|
|
268
|
+
* static reshape (passthrough minus the ON columns, plus the name/value columns), modelled exactly. */
|
|
264
269
|
function lowerPivotStmt(stmt) {
|
|
265
|
-
const
|
|
270
|
+
const isPivot = stmt.ruleIndex === P.RULE_pivotstmt;
|
|
266
271
|
const from = [];
|
|
267
272
|
const qn = directChildrenOfRule(stmt, P.RULE_qualified_name)[0];
|
|
268
273
|
if (qn)
|
|
@@ -273,13 +278,39 @@ function lowerPivotStmt(stmt) {
|
|
|
273
278
|
from.push({ kind: "subquery", query: inner ? lowerSelectStmt(inner) : emptyQuery(sw), cst: sw });
|
|
274
279
|
}
|
|
275
280
|
const columns = [];
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
281
|
+
// Column names referenced by a clause's a_expr children, recorded into `columns` for lineage.
|
|
282
|
+
const colNames = (clauseRule, clause) => {
|
|
283
|
+
const names = [];
|
|
284
|
+
for (const node of collectOfRule(stmt, clauseRule))
|
|
285
|
+
for (const a of directChildrenOfRule(node, P.RULE_a_expr)) {
|
|
286
|
+
const refs = [];
|
|
287
|
+
columnsOf(lowerExpr(a), refs, clause);
|
|
288
|
+
for (const r of refs) {
|
|
289
|
+
columns.push(r);
|
|
290
|
+
names.push(r.parts[r.parts.length - 1]);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return names;
|
|
294
|
+
};
|
|
295
|
+
let pivot;
|
|
296
|
+
let unpivot;
|
|
297
|
+
if (isPivot) {
|
|
298
|
+
const forColumns = colNames(P.RULE_pivot_on, "projection"); // ON columns → the pivot key
|
|
299
|
+
const aggColumns = colNames(P.RULE_target_el, "projection"); // USING aggregate arguments
|
|
300
|
+
colNames(P.RULE_group_by_list, "groupBy"); // GROUP BY dims → referenced, kept visible for lineage
|
|
301
|
+
pivot = { values: [], forColumns, aggColumns, dynamic: true };
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
const removed = colNames(P.RULE_unpivot_on, "projection"); // ON columns consumed into rows
|
|
305
|
+
// INTO NAME ⟨colid⟩ VALUE ⟨name_list⟩; default DuckDB column names are "name"/"value".
|
|
306
|
+
const nameColumn = directChildrenOfRule(stmt, P.RULE_colid)[0];
|
|
307
|
+
const valueList = directChildrenOfRule(stmt, P.RULE_name_list)[0];
|
|
308
|
+
const valueName = valueList ? collectOfRule(valueList, P.RULE_name)[0] : undefined;
|
|
309
|
+
unpivot = {
|
|
310
|
+
valueColumn: valueName ? textOf(valueName) : "value",
|
|
311
|
+
nameColumn: nameColumn ? textOf(nameColumn) : "name",
|
|
312
|
+
removed,
|
|
313
|
+
};
|
|
283
314
|
}
|
|
284
315
|
const body = {
|
|
285
316
|
kind: "select",
|
|
@@ -287,7 +318,8 @@ function lowerPivotStmt(stmt) {
|
|
|
287
318
|
from,
|
|
288
319
|
columns,
|
|
289
320
|
aggregated: false,
|
|
290
|
-
|
|
321
|
+
pivot,
|
|
322
|
+
unpivot,
|
|
291
323
|
cst: stmt,
|
|
292
324
|
};
|
|
293
325
|
return { kind: "query", ctes: [], body, cst: stmt };
|
|
@@ -1356,7 +1388,7 @@ function lowerFuncExpr(node) {
|
|
|
1356
1388
|
const fname = directChildrenOfRule(app, P.RULE_func_name)[0] ??
|
|
1357
1389
|
directChildrenOfRule(app, P.RULE_plain_func_name)[0] ??
|
|
1358
1390
|
directChildrenOfRule(app, P.RULE_dotted_func_name)[0];
|
|
1359
|
-
const name = (fname ? displayName(lastName(fname)
|
|
1391
|
+
const name = (fname ? displayName(lastName(fname)) : (leftmostToken(app) ?? "")).toLowerCase();
|
|
1360
1392
|
const args = funcArgs(app);
|
|
1361
1393
|
const within = directChildrenOfRule(node, P.RULE_within_group_clause)[0];
|
|
1362
1394
|
if (within) {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { FnSignature } from "../signature/signatures.js";
|
|
2
|
+
/** The merged function-signature table for duckdb: curated overrides folded over the harvested
|
|
3
|
+
* doc-derived long tail (overrides win by key, replacing the whole overload set), keyed by
|
|
4
|
+
* lowercased name. Each name maps to an ORDERED overload set - a name with one documented shape
|
|
5
|
+
* is a one-element array. `origin` says which layer produced the set. */
|
|
6
|
+
export declare const DUCKDB_SIGNATURES: Record<string, FnSignature[]>;
|