sqllens 1.1.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 (234) hide show
  1. package/README.md +2 -74
  2. package/THIRD-PARTY-NOTICES.md +50 -5
  3. package/dist/api.d.ts +5 -3
  4. package/dist/api.js +11 -4
  5. package/dist/bigquery/behavior.d.ts +2 -0
  6. package/dist/bigquery/behavior.js +20 -0
  7. package/dist/bigquery/dot-path.d.ts +0 -2
  8. package/dist/bigquery/dot-path.js +0 -1
  9. package/dist/bigquery/fold.d.ts +8 -0
  10. package/dist/bigquery/fold.js +39 -0
  11. package/dist/bigquery/index.d.ts +7 -0
  12. package/dist/bigquery/index.js +10 -0
  13. package/dist/{infer/bigquery.d.ts → bigquery/infer.d.ts} +2 -2
  14. package/dist/{infer/bigquery.js → bigquery/infer.js} +4 -3
  15. package/dist/bigquery/lower.js +35 -12
  16. package/dist/bigquery/signatures.generated.d.ts +6 -0
  17. package/dist/bigquery/signatures.generated.js +1068 -0
  18. package/dist/completion/atn-walk.d.ts +13 -2
  19. package/dist/completion/atn-walk.js +13 -10
  20. package/dist/completion/complete.d.ts +4 -3
  21. package/dist/completion/complete.js +101 -45
  22. package/dist/completion/jinja-slot.d.ts +24 -0
  23. package/dist/completion/jinja-slot.js +126 -0
  24. package/dist/completion/parser-factory.d.ts +13 -1
  25. package/dist/completion/parser-factory.js +12 -0
  26. package/dist/databricks/behavior.d.ts +2 -0
  27. package/dist/databricks/behavior.js +19 -0
  28. package/dist/databricks/fold.d.ts +8 -0
  29. package/dist/databricks/fold.js +27 -0
  30. package/dist/databricks/index.d.ts +7 -0
  31. package/dist/databricks/index.js +10 -0
  32. package/dist/databricks/infer.d.ts +6 -0
  33. package/dist/databricks/infer.js +638 -0
  34. package/dist/databricks/signatures.generated.d.ts +6 -0
  35. package/dist/databricks/signatures.generated.js +1745 -0
  36. package/dist/dialect-behavior/behavior.d.ts +26 -0
  37. package/dist/dialect-behavior/behavior.js +1 -0
  38. package/dist/dialect-behavior/carrier.d.ts +5 -0
  39. package/dist/dialect-behavior/carrier.js +5 -0
  40. package/dist/dialect-behavior/coerce-rules.d.ts +7 -0
  41. package/dist/dialect-behavior/coerce-rules.js +69 -0
  42. package/dist/dialect-behavior/public-fold.d.ts +6 -0
  43. package/dist/dialect-behavior/public-fold.js +9 -0
  44. package/dist/dialect-behavior/registry.d.ts +6 -0
  45. package/dist/dialect-behavior/registry.js +34 -0
  46. package/dist/dialect-symbols.js +16 -19
  47. package/dist/document/document.d.ts +1 -1
  48. package/dist/document/document.js +8 -7
  49. package/dist/duckdb/behavior.d.ts +2 -0
  50. package/dist/duckdb/behavior.js +21 -0
  51. package/dist/duckdb/fold.d.ts +8 -0
  52. package/dist/duckdb/fold.js +29 -0
  53. package/dist/duckdb/index.d.ts +7 -0
  54. package/dist/duckdb/index.js +10 -0
  55. package/dist/{infer/duckdb.d.ts → duckdb/infer.d.ts} +2 -2
  56. package/dist/{infer/duckdb.js → duckdb/infer.js} +4 -3
  57. package/dist/duckdb/lower.js +47 -15
  58. package/dist/duckdb/signatures.generated.d.ts +6 -0
  59. package/dist/duckdb/signatures.generated.js +1072 -0
  60. package/dist/generated/bigquery/GoogleSQLParser.js +0 -7060
  61. package/dist/generated/databricks/DatabricksParser.js +0 -4800
  62. package/dist/generated/duckdb/DuckdbParser.js +0 -9100
  63. package/dist/generated/minijinja/MinijinjaParser.js +0 -380
  64. package/dist/generated/mysql/MysqlParser.js +0 -6330
  65. package/dist/generated/postgres/PostgresParser.js +0 -8530
  66. package/dist/generated/redshift/RedshiftParser.js +0 -10970
  67. package/dist/generated/snowflake/SnowflakeParser.js +0 -7320
  68. package/dist/generated/sqlite/SqliteParser.js +0 -1150
  69. package/dist/generated/trino/TrinoParser.js +0 -3770
  70. package/dist/generated/tsql/TSqlParser.js +0 -8420
  71. package/dist/ident/fold.d.ts +24 -15
  72. package/dist/ident/fold.js +12 -194
  73. package/dist/index.d.ts +2 -2
  74. package/dist/index.js +10 -8
  75. package/dist/infer/functions.d.ts +22 -11
  76. package/dist/infer/functions.js +26 -888
  77. package/dist/infer/infer.js +16 -14
  78. package/dist/infer/nullability.js +3 -4
  79. package/dist/infer/types.d.ts +1 -1
  80. package/dist/infer/types.js +7 -7
  81. package/dist/ir/ir.d.ts +26 -17
  82. package/dist/ir/span.js +0 -2
  83. package/dist/ir/walk.js +3 -3
  84. package/dist/lineage/hops.js +13 -10
  85. package/dist/lineage/lineage.js +10 -7
  86. package/dist/minijinja/apply-tags.d.ts +14 -7
  87. package/dist/minijinja/apply-tags.js +66 -121
  88. package/dist/minijinja/parse.js +5 -5
  89. package/dist/minijinja/tag-ast.d.ts +29 -36
  90. package/dist/minijinja/tag-ast.js +210 -90
  91. package/dist/mysql/behavior.d.ts +2 -0
  92. package/dist/mysql/behavior.js +21 -0
  93. package/dist/mysql/fold.d.ts +8 -0
  94. package/dist/mysql/fold.js +49 -0
  95. package/dist/mysql/index.d.ts +7 -0
  96. package/dist/mysql/index.js +10 -0
  97. package/dist/{infer/mysql.d.ts → mysql/infer.d.ts} +2 -2
  98. package/dist/{infer/mysql.js → mysql/infer.js} +3 -2
  99. package/dist/mysql/signatures.generated.d.ts +6 -0
  100. package/dist/mysql/signatures.generated.js +508 -0
  101. package/dist/postgres/behavior.d.ts +2 -0
  102. package/dist/postgres/behavior.js +19 -0
  103. package/dist/postgres/fold.d.ts +8 -0
  104. package/dist/postgres/fold.js +30 -0
  105. package/dist/postgres/index.d.ts +7 -0
  106. package/dist/postgres/index.js +10 -0
  107. package/dist/{infer/postgres.d.ts → postgres/infer.d.ts} +2 -2
  108. package/dist/{infer/postgres.js → postgres/infer.js} +4 -3
  109. package/dist/postgres/lower.js +2 -2
  110. package/dist/postgres/signatures.generated.d.ts +6 -0
  111. package/dist/postgres/signatures.generated.js +2973 -0
  112. package/dist/qualify/check-calls.js +80 -161
  113. package/dist/qualify/qualify.js +16 -14
  114. package/dist/qualify/schema-provider.js +2 -2
  115. package/dist/qualify/schema.js +3 -3
  116. package/dist/qualify/template-provider.d.ts +45 -12
  117. package/dist/qualify/template-provider.js +69 -38
  118. package/dist/redshift/behavior.d.ts +2 -0
  119. package/dist/redshift/behavior.js +19 -0
  120. package/dist/redshift/fold.d.ts +8 -0
  121. package/dist/redshift/fold.js +31 -0
  122. package/dist/redshift/index.d.ts +7 -0
  123. package/dist/redshift/index.js +10 -0
  124. package/dist/{infer/redshift.d.ts → redshift/infer.d.ts} +2 -2
  125. package/dist/{infer/redshift.js → redshift/infer.js} +4 -3
  126. package/dist/redshift/lower.js +2 -2
  127. package/dist/redshift/signatures.generated.d.ts +6 -0
  128. package/dist/redshift/signatures.generated.js +757 -0
  129. package/dist/references/references.js +17 -12
  130. package/dist/scope/like-pattern.d.ts +2 -0
  131. package/dist/scope/like-pattern.js +15 -0
  132. package/dist/scope/scope.d.ts +5 -5
  133. package/dist/scope/scope.js +48 -42
  134. package/dist/sema/resolve.js +17 -12
  135. package/dist/session.d.ts +2 -2
  136. package/dist/signature/signature.d.ts +14 -6
  137. package/dist/signature/signature.js +30 -22
  138. package/dist/signature/signatures.d.ts +14 -12
  139. package/dist/signature/signatures.js +42 -721
  140. package/dist/snowflake/behavior.d.ts +2 -0
  141. package/dist/snowflake/behavior.js +22 -0
  142. package/dist/snowflake/fold.d.ts +8 -0
  143. package/dist/snowflake/fold.js +25 -0
  144. package/dist/snowflake/index.d.ts +7 -0
  145. package/dist/snowflake/index.js +10 -0
  146. package/dist/{infer/snowflake.d.ts → snowflake/infer.d.ts} +2 -2
  147. package/dist/{infer/snowflake.js → snowflake/infer.js} +4 -3
  148. package/dist/snowflake/lower.js +59 -19
  149. package/dist/snowflake/signatures.generated.d.ts +6 -0
  150. package/dist/snowflake/signatures.generated.js +2080 -0
  151. package/dist/sqlite/behavior.d.ts +2 -0
  152. package/dist/sqlite/behavior.js +19 -0
  153. package/dist/sqlite/fold.d.ts +8 -0
  154. package/dist/sqlite/fold.js +41 -0
  155. package/dist/sqlite/index.d.ts +7 -0
  156. package/dist/sqlite/index.js +10 -0
  157. package/dist/{infer/sqlite.d.ts → sqlite/infer.d.ts} +2 -2
  158. package/dist/{infer/sqlite.js → sqlite/infer.js} +4 -3
  159. package/dist/sqlite/signatures.generated.d.ts +6 -0
  160. package/dist/sqlite/signatures.generated.js +277 -0
  161. package/dist/symbols/symbols.js +14 -12
  162. package/dist/trino/behavior.d.ts +2 -0
  163. package/dist/trino/behavior.js +21 -0
  164. package/dist/trino/fold.d.ts +8 -0
  165. package/dist/trino/fold.js +39 -0
  166. package/dist/trino/index.d.ts +7 -0
  167. package/dist/trino/index.js +10 -0
  168. package/dist/{infer/trino.d.ts → trino/infer.d.ts} +2 -2
  169. package/dist/{infer/trino.js → trino/infer.js} +4 -3
  170. package/dist/trino/lower.js +5 -5
  171. package/dist/trino/signatures.generated.d.ts +6 -0
  172. package/dist/trino/signatures.generated.js +968 -0
  173. package/dist/tsql/behavior.d.ts +2 -0
  174. package/dist/tsql/behavior.js +20 -0
  175. package/dist/tsql/fold.d.ts +8 -0
  176. package/dist/tsql/fold.js +34 -0
  177. package/dist/tsql/index.d.ts +7 -0
  178. package/dist/tsql/index.js +10 -0
  179. package/dist/tsql/infer.d.ts +16 -0
  180. package/dist/tsql/infer.js +289 -0
  181. package/dist/tsql/lower.js +4 -4
  182. package/dist/tsql/signatures.generated.d.ts +6 -0
  183. package/dist/tsql/signatures.generated.js +640 -0
  184. package/package.json +5 -10
  185. package/dist/generated/bigquery/GoogleSQLLexer.d.ts +0 -407
  186. package/dist/generated/bigquery/GoogleSQLParser.d.ts +0 -9558
  187. package/dist/generated/bigquery/GoogleSQLParserListener.d.ts +0 -7777
  188. package/dist/generated/bigquery/GoogleSQLParserListener.js +0 -7070
  189. package/dist/generated/databricks/DatabricksLexer.d.ts +0 -566
  190. package/dist/generated/databricks/DatabricksParser.d.ts +0 -7771
  191. package/dist/generated/databricks/DatabricksParserListener.d.ts +0 -5737
  192. package/dist/generated/databricks/DatabricksParserListener.js +0 -5256
  193. package/dist/generated/duckdb/DuckdbLexer.d.ts +0 -691
  194. package/dist/generated/duckdb/DuckdbParser.d.ts +0 -13932
  195. package/dist/generated/duckdb/DuckdbParserListener.d.ts +0 -10049
  196. package/dist/generated/duckdb/DuckdbParserListener.js +0 -9138
  197. package/dist/generated/minijinja/MinijinjaLexer.d.ts +0 -108
  198. package/dist/generated/minijinja/MinijinjaParser.d.ts +0 -604
  199. package/dist/generated/minijinja/MinijinjaParserListener.d.ts +0 -449
  200. package/dist/generated/minijinja/MinijinjaParserListener.js +0 -410
  201. package/dist/generated/mysql/MysqlLexer.d.ts +0 -1198
  202. package/dist/generated/mysql/MysqlParser.d.ts +0 -10992
  203. package/dist/generated/mysql/MysqlParserListener.d.ts +0 -7592
  204. package/dist/generated/mysql/MysqlParserListener.js +0 -6958
  205. package/dist/generated/postgres/PostgresLexer.d.ts +0 -663
  206. package/dist/generated/postgres/PostgresParser.d.ts +0 -12963
  207. package/dist/generated/postgres/PostgresParserListener.d.ts +0 -9408
  208. package/dist/generated/postgres/PostgresParserListener.js +0 -8554
  209. package/dist/generated/redshift/RedshiftLexer.d.ts +0 -954
  210. package/dist/generated/redshift/RedshiftParser.d.ts +0 -16939
  211. package/dist/generated/redshift/RedshiftParserListener.d.ts +0 -12092
  212. package/dist/generated/redshift/RedshiftParserListener.js +0 -10994
  213. package/dist/generated/snowflake/SnowflakeLexer.d.ts +0 -1046
  214. package/dist/generated/snowflake/SnowflakeParser.d.ts +0 -14196
  215. package/dist/generated/snowflake/SnowflakeParserListener.d.ts +0 -8063
  216. package/dist/generated/snowflake/SnowflakeParserListener.js +0 -7330
  217. package/dist/generated/sqlite/SqliteLexer.d.ts +0 -210
  218. package/dist/generated/sqlite/SqliteParser.d.ts +0 -2115
  219. package/dist/generated/sqlite/SqliteParserListener.d.ts +0 -1276
  220. package/dist/generated/sqlite/SqliteParserListener.js +0 -1160
  221. package/dist/generated/trino/TrinoLexer.d.ts +0 -381
  222. package/dist/generated/trino/TrinoParser.d.ts +0 -5340
  223. package/dist/generated/trino/TrinoParserListener.d.ts +0 -4704
  224. package/dist/generated/trino/TrinoParserListener.js +0 -4326
  225. package/dist/generated/tsql/TSqlLexer.d.ts +0 -1278
  226. package/dist/generated/tsql/TSqlParser.d.ts +0 -17267
  227. package/dist/generated/tsql/TSqlParserListener.d.ts +0 -9697
  228. package/dist/generated/tsql/TSqlParserListener.js +0 -8854
  229. package/dist/infer/dialect.d.ts +0 -21
  230. package/dist/infer/dialect.js +0 -100
  231. package/dist/infer/literals.d.ts +0 -6
  232. package/dist/infer/literals.js +0 -44
  233. package/dist/signature/generated/tsql.d.ts +0 -3
  234. package/dist/signature/generated/tsql.js +0 -260
@@ -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
@@ -77,18 +76,18 @@ import { DuckdbLexer } from "./generated/duckdb/DuckdbLexer.js";
77
76
  import { TrinoLexer } from "./generated/trino/TrinoLexer.js";
78
77
  import { SqliteLexer } from "./generated/sqlite/SqliteLexer.js";
79
78
  import { MysqlLexer } from "./generated/mysql/MysqlLexer.js";
80
- import { inferDialect } from "./infer/dialect.js";
79
+ import { resolveBehavior } from "./dialect-behavior/registry.js";
81
80
  import { HOF_LAMBDA_ARG } from "./infer/infer.js";
82
81
  import { SCALAR_ALIASES, TSQL_ALIASES } from "./infer/types.js";
83
- import { SNOWFLAKE_ALIASES } from "./infer/snowflake.js";
84
- import { BQ_ALIASES } from "./infer/bigquery.js";
85
- import { REDSHIFT_ALIASES } from "./infer/redshift.js";
86
- import { POSTGRES_ALIASES } from "./infer/postgres.js";
87
- import { DUCKDB_ALIASES } from "./infer/duckdb.js";
88
- import { TRINO_ALIASES } from "./infer/trino.js";
89
- import { SQLITE_ALIASES } from "./infer/sqlite.js";
90
- import { MYSQL_ALIASES } from "./infer/mysql.js";
91
- 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";
92
91
  // bigquery's generated lexer class is GoogleSQLLexer (the fork is Bytebase's GoogleSQL grammar).
93
92
  const LEXERS = {
94
93
  databricks: () => new DatabricksLexer(CharStream.fromString("")),
@@ -136,11 +135,9 @@ function keywordsFor(dialect) {
136
135
  }
137
136
  function functionsFor(dialect) {
138
137
  const out = new Set();
139
- for (const name of Object.keys(inferDialect(dialect).functions))
138
+ for (const name of Object.keys(resolveBehavior(dialect).functions))
140
139
  out.add(name.toUpperCase());
141
- for (const name of Object.keys(FUNCTION_SIGNATURES[dialect]))
142
- out.add(name.toUpperCase());
143
- for (const name of Object.keys(HARVESTED_SIGNATURES[dialect]))
140
+ for (const name of Object.keys(SIGNATURES[dialect]))
144
141
  out.add(name.toUpperCase());
145
142
  if (dialect === "databricks") {
146
143
  for (const name of Object.keys(HOF_LAMBDA_ARG))
@@ -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[]>;