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.
Files changed (244) hide show
  1. package/LICENSE +0 -10
  2. package/README.md +95 -85
  3. package/THIRD-PARTY-NOTICES.md +70 -5
  4. package/dist/api.d.ts +5 -3
  5. package/dist/api.js +17 -4
  6. package/dist/bigquery/behavior.d.ts +2 -0
  7. package/dist/bigquery/behavior.js +20 -0
  8. package/dist/bigquery/dot-path.d.ts +0 -2
  9. package/dist/bigquery/dot-path.js +0 -1
  10. package/dist/bigquery/fold.d.ts +8 -0
  11. package/dist/bigquery/fold.js +39 -0
  12. package/dist/bigquery/index.d.ts +7 -0
  13. package/dist/bigquery/index.js +10 -0
  14. package/dist/{infer/bigquery.d.ts → bigquery/infer.d.ts} +2 -2
  15. package/dist/{infer/bigquery.js → bigquery/infer.js} +4 -3
  16. package/dist/bigquery/lower.js +35 -12
  17. package/dist/bigquery/signatures.generated.d.ts +6 -0
  18. package/dist/bigquery/signatures.generated.js +1068 -0
  19. package/dist/completion/atn-walk.d.ts +13 -2
  20. package/dist/completion/atn-walk.js +13 -10
  21. package/dist/completion/complete.d.ts +4 -3
  22. package/dist/completion/complete.js +101 -45
  23. package/dist/completion/config.js +66 -0
  24. package/dist/completion/jinja-slot.d.ts +24 -0
  25. package/dist/completion/jinja-slot.js +126 -0
  26. package/dist/completion/parser-factory.d.ts +13 -1
  27. package/dist/completion/parser-factory.js +48 -0
  28. package/dist/databricks/behavior.d.ts +2 -0
  29. package/dist/databricks/behavior.js +19 -0
  30. package/dist/databricks/fold.d.ts +8 -0
  31. package/dist/databricks/fold.js +27 -0
  32. package/dist/databricks/index.d.ts +7 -0
  33. package/dist/databricks/index.js +10 -0
  34. package/dist/databricks/infer.d.ts +6 -0
  35. package/dist/databricks/infer.js +638 -0
  36. package/dist/databricks/signatures.generated.d.ts +6 -0
  37. package/dist/databricks/signatures.generated.js +1745 -0
  38. package/dist/derived-dialects.js +19 -1
  39. package/dist/dialect-behavior/behavior.d.ts +26 -0
  40. package/dist/dialect-behavior/behavior.js +1 -0
  41. package/dist/dialect-behavior/carrier.d.ts +5 -0
  42. package/dist/dialect-behavior/carrier.js +5 -0
  43. package/dist/dialect-behavior/coerce-rules.d.ts +7 -0
  44. package/dist/dialect-behavior/coerce-rules.js +69 -0
  45. package/dist/dialect-behavior/public-fold.d.ts +6 -0
  46. package/dist/dialect-behavior/public-fold.js +9 -0
  47. package/dist/dialect-behavior/registry.d.ts +6 -0
  48. package/dist/dialect-behavior/registry.js +34 -0
  49. package/dist/dialect-symbols.js +22 -17
  50. package/dist/dialect.d.ts +2 -2
  51. package/dist/document/document.d.ts +1 -1
  52. package/dist/document/document.js +8 -7
  53. package/dist/duckdb/behavior.d.ts +2 -0
  54. package/dist/duckdb/behavior.js +21 -0
  55. package/dist/duckdb/fold.d.ts +8 -0
  56. package/dist/duckdb/fold.js +29 -0
  57. package/dist/duckdb/index.d.ts +7 -0
  58. package/dist/duckdb/index.js +10 -0
  59. package/dist/{infer/duckdb.d.ts → duckdb/infer.d.ts} +2 -2
  60. package/dist/{infer/duckdb.js → duckdb/infer.js} +4 -3
  61. package/dist/duckdb/lower.js +47 -15
  62. package/dist/duckdb/signatures.generated.d.ts +6 -0
  63. package/dist/duckdb/signatures.generated.js +1072 -0
  64. package/dist/generated/bigquery/GoogleSQLParser.js +0 -7060
  65. package/dist/generated/databricks/DatabricksParser.js +0 -4800
  66. package/dist/generated/duckdb/DuckdbParser.js +0 -9100
  67. package/dist/generated/minijinja/MinijinjaParser.js +0 -380
  68. package/dist/generated/mysql/MysqlLexer.js +7357 -0
  69. package/dist/generated/mysql/MysqlParser.js +78520 -0
  70. package/dist/generated/postgres/PostgresParser.js +0 -8530
  71. package/dist/generated/redshift/RedshiftParser.js +0 -10970
  72. package/dist/generated/snowflake/SnowflakeParser.js +0 -7320
  73. package/dist/generated/sqlite/SqliteLexer.js +945 -0
  74. package/dist/generated/sqlite/SqliteParser.js +14682 -0
  75. package/dist/generated/trino/TrinoParser.js +0 -3770
  76. package/dist/generated/tsql/TSqlParser.js +0 -8420
  77. package/dist/ident/fold.d.ts +24 -15
  78. package/dist/ident/fold.js +12 -145
  79. package/dist/index.d.ts +6 -2
  80. package/dist/index.js +14 -8
  81. package/dist/infer/functions.d.ts +22 -11
  82. package/dist/infer/functions.js +26 -888
  83. package/dist/infer/infer.js +16 -14
  84. package/dist/infer/nullability.js +3 -4
  85. package/dist/infer/types.d.ts +1 -1
  86. package/dist/infer/types.js +7 -7
  87. package/dist/ir/ir.d.ts +26 -17
  88. package/dist/ir/part-span.d.ts +16 -1
  89. package/dist/ir/part-span.js +38 -10
  90. package/dist/ir/span.js +0 -2
  91. package/dist/ir/walk.js +3 -3
  92. package/dist/lineage/hops.js +13 -10
  93. package/dist/lineage/lineage.js +10 -7
  94. package/dist/minijinja/apply-tags.d.ts +14 -7
  95. package/dist/minijinja/apply-tags.js +66 -121
  96. package/dist/minijinja/parse.js +6 -6
  97. package/dist/minijinja/tag-ast.d.ts +29 -36
  98. package/dist/minijinja/tag-ast.js +210 -90
  99. package/dist/mysql/behavior.d.ts +2 -0
  100. package/dist/mysql/behavior.js +21 -0
  101. package/dist/mysql/fold.d.ts +8 -0
  102. package/dist/mysql/fold.js +49 -0
  103. package/dist/mysql/index.d.ts +7 -0
  104. package/dist/mysql/index.js +10 -0
  105. package/dist/mysql/infer.d.ts +20 -0
  106. package/dist/mysql/infer.js +156 -0
  107. package/dist/mysql/lower.d.ts +13 -0
  108. package/dist/mysql/lower.js +1443 -0
  109. package/dist/mysql/parse.d.ts +10 -0
  110. package/dist/mysql/parse.js +70 -0
  111. package/dist/mysql/signatures.generated.d.ts +6 -0
  112. package/dist/mysql/signatures.generated.js +508 -0
  113. package/dist/postgres/behavior.d.ts +2 -0
  114. package/dist/postgres/behavior.js +19 -0
  115. package/dist/postgres/fold.d.ts +8 -0
  116. package/dist/postgres/fold.js +30 -0
  117. package/dist/postgres/index.d.ts +7 -0
  118. package/dist/postgres/index.js +10 -0
  119. package/dist/{infer/postgres.d.ts → postgres/infer.d.ts} +2 -2
  120. package/dist/{infer/postgres.js → postgres/infer.js} +4 -3
  121. package/dist/postgres/lower.js +2 -2
  122. package/dist/postgres/signatures.generated.d.ts +6 -0
  123. package/dist/postgres/signatures.generated.js +2973 -0
  124. package/dist/qualify/check-calls.js +80 -134
  125. package/dist/qualify/qualify.js +16 -14
  126. package/dist/qualify/schema-provider.js +2 -2
  127. package/dist/qualify/schema.js +3 -3
  128. package/dist/qualify/template-provider.d.ts +45 -12
  129. package/dist/qualify/template-provider.js +69 -38
  130. package/dist/redshift/behavior.d.ts +2 -0
  131. package/dist/redshift/behavior.js +19 -0
  132. package/dist/redshift/fold.d.ts +8 -0
  133. package/dist/redshift/fold.js +31 -0
  134. package/dist/redshift/index.d.ts +7 -0
  135. package/dist/redshift/index.js +10 -0
  136. package/dist/{infer/redshift.d.ts → redshift/infer.d.ts} +2 -2
  137. package/dist/{infer/redshift.js → redshift/infer.js} +4 -3
  138. package/dist/redshift/lower.js +2 -2
  139. package/dist/redshift/signatures.generated.d.ts +6 -0
  140. package/dist/redshift/signatures.generated.js +757 -0
  141. package/dist/references/references.js +17 -12
  142. package/dist/scope/like-pattern.d.ts +2 -0
  143. package/dist/scope/like-pattern.js +15 -0
  144. package/dist/scope/scope.d.ts +5 -5
  145. package/dist/scope/scope.js +48 -42
  146. package/dist/sema/resolve.js +17 -12
  147. package/dist/session.d.ts +2 -2
  148. package/dist/signature/signature.d.ts +14 -6
  149. package/dist/signature/signature.js +30 -22
  150. package/dist/signature/signatures.d.ts +14 -12
  151. package/dist/signature/signatures.js +42 -582
  152. package/dist/snowflake/behavior.d.ts +2 -0
  153. package/dist/snowflake/behavior.js +22 -0
  154. package/dist/snowflake/fold.d.ts +8 -0
  155. package/dist/snowflake/fold.js +25 -0
  156. package/dist/snowflake/index.d.ts +7 -0
  157. package/dist/snowflake/index.js +10 -0
  158. package/dist/{infer/snowflake.d.ts → snowflake/infer.d.ts} +2 -2
  159. package/dist/{infer/snowflake.js → snowflake/infer.js} +4 -3
  160. package/dist/snowflake/lower.js +59 -19
  161. package/dist/snowflake/signatures.generated.d.ts +6 -0
  162. package/dist/snowflake/signatures.generated.js +2080 -0
  163. package/dist/sqlite/behavior.d.ts +2 -0
  164. package/dist/sqlite/behavior.js +19 -0
  165. package/dist/sqlite/fold.d.ts +8 -0
  166. package/dist/sqlite/fold.js +41 -0
  167. package/dist/sqlite/index.d.ts +7 -0
  168. package/dist/sqlite/index.js +10 -0
  169. package/dist/sqlite/infer.d.ts +12 -0
  170. package/dist/sqlite/infer.js +122 -0
  171. package/dist/sqlite/lower.d.ts +11 -0
  172. package/dist/sqlite/lower.js +1093 -0
  173. package/dist/sqlite/parse.d.ts +10 -0
  174. package/dist/sqlite/parse.js +70 -0
  175. package/dist/sqlite/signatures.generated.d.ts +6 -0
  176. package/dist/sqlite/signatures.generated.js +277 -0
  177. package/dist/symbols/symbols.js +14 -12
  178. package/dist/token/classify.js +31 -0
  179. package/dist/token/tokenize.js +4 -0
  180. package/dist/trino/behavior.d.ts +2 -0
  181. package/dist/trino/behavior.js +21 -0
  182. package/dist/trino/fold.d.ts +8 -0
  183. package/dist/trino/fold.js +39 -0
  184. package/dist/trino/index.d.ts +7 -0
  185. package/dist/trino/index.js +10 -0
  186. package/dist/{infer/trino.d.ts → trino/infer.d.ts} +2 -2
  187. package/dist/{infer/trino.js → trino/infer.js} +4 -3
  188. package/dist/trino/lower.js +5 -5
  189. package/dist/trino/signatures.generated.d.ts +6 -0
  190. package/dist/trino/signatures.generated.js +968 -0
  191. package/dist/tsql/behavior.d.ts +2 -0
  192. package/dist/tsql/behavior.js +20 -0
  193. package/dist/tsql/fold.d.ts +8 -0
  194. package/dist/tsql/fold.js +34 -0
  195. package/dist/tsql/index.d.ts +7 -0
  196. package/dist/tsql/index.js +10 -0
  197. package/dist/tsql/infer.d.ts +16 -0
  198. package/dist/tsql/infer.js +289 -0
  199. package/dist/tsql/lower.js +4 -4
  200. package/dist/tsql/signatures.generated.d.ts +6 -0
  201. package/dist/tsql/signatures.generated.js +640 -0
  202. package/package.json +15 -11
  203. package/dist/generated/bigquery/GoogleSQLLexer.d.ts +0 -407
  204. package/dist/generated/bigquery/GoogleSQLParser.d.ts +0 -9558
  205. package/dist/generated/bigquery/GoogleSQLParserListener.d.ts +0 -7777
  206. package/dist/generated/bigquery/GoogleSQLParserListener.js +0 -7070
  207. package/dist/generated/databricks/DatabricksLexer.d.ts +0 -566
  208. package/dist/generated/databricks/DatabricksParser.d.ts +0 -7771
  209. package/dist/generated/databricks/DatabricksParserListener.d.ts +0 -5737
  210. package/dist/generated/databricks/DatabricksParserListener.js +0 -5256
  211. package/dist/generated/duckdb/DuckdbLexer.d.ts +0 -691
  212. package/dist/generated/duckdb/DuckdbParser.d.ts +0 -13932
  213. package/dist/generated/duckdb/DuckdbParserListener.d.ts +0 -10049
  214. package/dist/generated/duckdb/DuckdbParserListener.js +0 -9138
  215. package/dist/generated/minijinja/MinijinjaLexer.d.ts +0 -108
  216. package/dist/generated/minijinja/MinijinjaParser.d.ts +0 -604
  217. package/dist/generated/minijinja/MinijinjaParserListener.d.ts +0 -449
  218. package/dist/generated/minijinja/MinijinjaParserListener.js +0 -410
  219. package/dist/generated/postgres/PostgresLexer.d.ts +0 -663
  220. package/dist/generated/postgres/PostgresParser.d.ts +0 -12963
  221. package/dist/generated/postgres/PostgresParserListener.d.ts +0 -9408
  222. package/dist/generated/postgres/PostgresParserListener.js +0 -8554
  223. package/dist/generated/redshift/RedshiftLexer.d.ts +0 -954
  224. package/dist/generated/redshift/RedshiftParser.d.ts +0 -16939
  225. package/dist/generated/redshift/RedshiftParserListener.d.ts +0 -12092
  226. package/dist/generated/redshift/RedshiftParserListener.js +0 -10994
  227. package/dist/generated/snowflake/SnowflakeLexer.d.ts +0 -1046
  228. package/dist/generated/snowflake/SnowflakeParser.d.ts +0 -14196
  229. package/dist/generated/snowflake/SnowflakeParserListener.d.ts +0 -8063
  230. package/dist/generated/snowflake/SnowflakeParserListener.js +0 -7330
  231. package/dist/generated/trino/TrinoLexer.d.ts +0 -381
  232. package/dist/generated/trino/TrinoParser.d.ts +0 -5340
  233. package/dist/generated/trino/TrinoParserListener.d.ts +0 -4704
  234. package/dist/generated/trino/TrinoParserListener.js +0 -4326
  235. package/dist/generated/tsql/TSqlLexer.d.ts +0 -1278
  236. package/dist/generated/tsql/TSqlParser.d.ts +0 -17267
  237. package/dist/generated/tsql/TSqlParserListener.d.ts +0 -9697
  238. package/dist/generated/tsql/TSqlParserListener.js +0 -8854
  239. package/dist/infer/dialect.d.ts +0 -21
  240. package/dist/infer/dialect.js +0 -74
  241. package/dist/infer/literals.d.ts +0 -6
  242. package/dist/infer/literals.js +0 -44
  243. package/dist/signature/generated/tsql.d.ts +0 -3
  244. package/dist/signature/generated/tsql.js +0 -260
@@ -1,5 +1,5 @@
1
1
  // ---------------------------------------------------------------------------
2
- // Derived-dialect → dialect map. The eight grammars parse more than eight
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,5 @@
1
+ import type { DialectBehavior } from "./behavior.js";
2
+ /** The dialect behavior for a scope (or anything carrying a `.dialect` tag). */
3
+ export declare function behaviorOf(carrier: {
4
+ dialect?: string;
5
+ }): DialectBehavior;
@@ -0,0 +1,5 @@
1
+ import { resolveBehavior } from "./registry.js";
2
+ /** The dialect behavior for a scope (or anything carrying a `.dialect` tag). */
3
+ export function behaviorOf(carrier) {
4
+ return resolveBehavior(carrier.dialect);
5
+ }
@@ -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
+ }
@@ -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 curated
16
- // per-dialect signature table (src/signature/signatures.ts FUNCTION_SIGNATURES),
17
- // (c) its harvested long-tail counterpart (HARVESTED_SIGNATURES — populated for
18
- // T-SQL only on this branch, the rest map to `{}` per that file's own header), and
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 { inferDialect } from "./infer/dialect.js";
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/snowflake.js";
82
- import { BQ_ALIASES } from "./infer/bigquery.js";
83
- import { REDSHIFT_ALIASES } from "./infer/redshift.js";
84
- import { POSTGRES_ALIASES } from "./infer/postgres.js";
85
- import { DUCKDB_ALIASES } from "./infer/duckdb.js";
86
- import { TRINO_ALIASES } from "./infer/trino.js";
87
- import { FUNCTION_SIGNATURES, HARVESTED_SIGNATURES } from "./signature/signatures.js";
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(inferDialect(dialect).functions))
138
+ for (const name of Object.keys(resolveBehavior(dialect).functions))
132
139
  out.add(name.toUpperCase());
133
- for (const name of Object.keys(FUNCTION_SIGNATURES[dialect]))
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 eight. */
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 (see `cellBaseAt` in src/lsp/ranges.ts). Single-cell: identical to today. */
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 { foldIdentifier } from "../ident/fold.js";
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 (see `cellBaseAt` in src/lsp/ranges.ts). Single-cell: identical to today. */
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 = foldIdentifier(cteRef.def.name, doc.dialect);
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: foldIdentifier(pair.name, scope.dialect), raw: pair.name, span });
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: foldIdentifier(p.name, scope.dialect), raw: p.name, span });
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 = foldIdentifier(col.raw, scope.dialect);
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(foldIdentifier(name, scope.dialect));
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,2 @@
1
+ import type { DialectBehavior } from "../dialect-behavior/behavior.js";
2
+ export declare const duckdbBehavior: DialectBehavior;
@@ -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,7 @@
1
+ import { lower } from "./lower.js";
2
+ import { parseDuckdb } from "./parse.js";
3
+ export declare const duckdb: {
4
+ parse: typeof parseDuckdb;
5
+ lower: typeof lower;
6
+ behavior: import("../dialect-behavior/behavior.js").DialectBehavior;
7
+ };
@@ -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 "./types.js";
2
- import type { FnRule } from "./functions.js";
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 "./types.js";
2
- import { commonType } from "./coerce.js";
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, "duckdb");
52
+ return parseType(text, DUCKDB_ALIASES, fold);
52
53
  }
53
54
  const S = scalar("string");
54
55
  const I = scalar("int");
@@ -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 "../ident/fold.js";
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 (flagged
18
- // `pivot`/`unpivot` — visible gaps, not silent drops).
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 — keep the source relation + ON/USING column refs visible, flag the
263
- * reshape as unsupported (duckdb.org/docs/current/sql/statements/pivot.md, unpivot.md). */
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 kind = stmt.ruleIndex === P.RULE_pivotstmt ? "pivot" : "unpivot";
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
- for (const on of [
277
- ...collectOfRule(stmt, P.RULE_pivot_on),
278
- ...collectOfRule(stmt, P.RULE_unpivot_on),
279
- ...collectOfRule(stmt, P.RULE_target_el),
280
- ]) {
281
- for (const a of directChildrenOfRule(on, P.RULE_a_expr))
282
- columnsOf(lowerExpr(a), columns, "projection");
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
- unsupported: [kind],
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), "duckdb") : (leftmostToken(app) ?? "")).toLowerCase();
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[]>;