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,51 +1,42 @@
1
1
  import { endPosition } from "../ir/span.js";
2
2
  import { inferType } from "../infer/infer.js";
3
- import { inferDialect } from "../infer/dialect.js";
4
- import { FUNCTION_SIGNATURES, HARVESTED_SIGNATURES } from "../signature/signatures.js";
3
+ import { behaviorOf } from "../dialect-behavior/carrier.js";
5
4
  // ---------------------------------------------------------------------------
6
- // Call-signature diagnostics — arity + operand types, over the modelled function
5
+ // Call-signature diagnostics: arity + operand types, over the modelled function
7
6
  // calls in the IR. Never-wrong: a diagnostic fires ONLY when the checker is
8
7
  // certain the call is wrong. Two rules, in order of strictness:
9
8
  //
10
- // - ARITY (curated only): the name is in the CURATED signature table AND the
11
- // call's arg count is matched by NO overload's [min, max] window → wrong-arity.
12
- // A variadic signature accepts any count (the last param repeats), so it never
13
- // flags. min = the count of non-optional params; max = the param count.
9
+ // - ARITY (both origins): the name is in the dialect's merged signature table
10
+ // (src/<dialect>/signatures.generated.ts, curated overrides folded over the
11
+ // harvested long tail), and it maps to an ORDERED OVERLOAD SET, not a single
12
+ // shape. It fires wrong-arity only when the call's arg count is matched by
13
+ // NO overload's [min, max] window. A variadic overload accepts any count at
14
+ // or above its own min (the last param repeats), so it never itself causes a
15
+ // flag. min = the count of non-optional params; max = the param count. Every
16
+ // overload's origin is trusted for arity (the harvested tables carry a
17
+ // trustworthy optional/variadic encoding, tools/harvest-signatures.mjs's
18
+ // NEVER-WRONG CONTRACT), so this is a union check across the whole set.
14
19
  //
15
- // - OPERAND TYPE (curated only): every argument type is inferable (≠ unknown)
16
- // AND some argument position is rejected under `accepts()` (no implicit
17
- // widening path to the declared param type) → wrong-argument-type. Any
18
- // `unknown` argument type anywhere makes the whole call silent.
19
- //
20
- // HARVESTED signatures are NOT trusted for either rejection: their param lists
21
- // (tools/harvest-signatures.mjs, T-SQL 151 entries) carry no optional/variadic
22
- // encoding and no reliable types, so an arity/type check over them would fire on
23
- // valid SQL (see ARITY_USES_HARVESTED below). They still drive signatureAt() hints.
20
+ // - OPERAND TYPE: only when the name has EXACTLY ONE overload and its origin is
21
+ // "curated", the unambiguous case. A 2+-overload name skips type checking
22
+ // entirely (which specific overload the call means isn't decided here, so no
23
+ // operand-type rejection can be trusted). When it does apply: every argument
24
+ // type is inferable (not unknown) AND some argument position is rejected
25
+ // under `accepts()` (no implicit widening path to the declared param type),
26
+ // so it fires wrong-argument-type. Any `unknown` argument type anywhere makes
27
+ // the whole call silent. A harvested-origin overload never drives a
28
+ // wrong-argument-type diagnostic (its param types aren't reliable enough).
24
29
  //
25
30
  // A qualified/dotted call (`ns.fn(...)`, sequence `.NEXTVAL`) does NOT match a
26
- // bare-name curated entry — the tables are bare-name only, so it stays silent.
31
+ // bare-name signature: the table is bare-name only, so it stays silent.
27
32
  // A named-argument call (`fn(x => v)`) can't be mapped to a positional arg list
28
33
  // confidently, so it too stays silent.
29
34
  //
30
35
  // The checker walks the scope tree; for each scope it inspects THAT scope's own
31
- // expressions (not nested subquery/EXISTS bodies — those are checked when their
36
+ // expressions (not nested subquery/EXISTS bodies: those are checked when their
32
37
  // child scope is visited), so an argument's type is inferred in the scope where
33
38
  // the call actually lives.
34
39
  // ---------------------------------------------------------------------------
35
- /** Risk-flag (c) escape hatch. Harvested arity data (T-SQL's 151-entry generated table is the only
36
- * one today) does not encode which params are optional, nor mark variadic reliably, so an arity
37
- * check over it fires on valid SQL. Kept curated-only, VISIBLY, for every dialect. Flip a dialect
38
- * on only once its harvested table earns it (optional/variadic encoding proven against the corpus). */
39
- const ARITY_USES_HARVESTED = {
40
- databricks: false,
41
- tsql: false,
42
- snowflake: false,
43
- bigquery: false,
44
- redshift: false,
45
- postgres: false,
46
- duckdb: false,
47
- trino: false,
48
- };
49
40
  export function checkCalls(tree, schema, diagnostics) {
50
41
  const visit = (scope) => {
51
42
  for (const expr of ownExprs(scope))
@@ -161,140 +152,95 @@ function checkOneCall(fn, scope, schema, diagnostics) {
161
152
  // A named-argument invocation (fn(x => v)) can't be mapped to a positional arg list confidently.
162
153
  if (fn.argNames?.some((n) => n !== undefined))
163
154
  return;
164
- // A qualified/dotted call must not borrow a bare-name signature (the tables are bare-name only).
155
+ // A qualified/dotted call must not borrow a bare-name signature (the table is bare-name only).
165
156
  if (fn.qualifier !== undefined)
166
157
  return;
167
158
  // Aggregate / window / DISTINCT forms carry modifiers the IR folds into (or out of) the arg list
168
- // unevenly — count(*)→0 args, sum(x) FILTER/OVER/WITHIN GROUP, a dropped DISTINCT keyword — so the
169
- // positional arg count isn't a reliable signal. Per the never-wrong contract, stay SILENT on them.
159
+ // unevenly: count(*) becomes 0 args, sum(x) FILTER/OVER/WITHIN GROUP, a dropped DISTINCT keyword,
160
+ // so the positional arg count isn't a reliable signal. Per the never-wrong contract, stay SILENT.
170
161
  if (fn.aggregate || fn.window || fn.distinct)
171
162
  return;
172
- const dialect = scope.dialect;
163
+ const b = behaviorOf(scope);
173
164
  const name = fn.name.toLowerCase();
174
- const curated = FUNCTION_SIGNATURES[dialect]?.[name];
175
- // Arity overloads: the curated signature always; the harvested one ONLY for a dialect whose
176
- // harvested arity data is trusted (none today — see ARITY_USES_HARVESTED). The rule fires when NO
177
- // overload accepts the arg count.
178
- const overloads = [];
179
- if (curated)
180
- overloads.push(curated);
181
- if (ARITY_USES_HARVESTED[dialect]) {
182
- const harvested = HARVESTED_SIGNATURES[dialect]?.[name];
183
- if (harvested && harvested !== curated)
184
- overloads.push(harvested);
185
- }
186
- if (overloads.length === 0)
187
- return; // uncurated (and no trusted harvested) — silent
165
+ const overloads = b.signatures[name];
166
+ if (!overloads)
167
+ return; // unknown name, silent
188
168
  const args = fn.args;
189
169
  // Trust the IR arg list ONLY when it faithfully mirrors what was written. Some special call forms
190
170
  // lower to an arg list that doesn't match the source positionally: a keyword arg the lowering drops
191
171
  // (T-SQL/BigQuery DATEADD/DATE_DIFF's datepart), a boolean condition split into comparands (T-SQL
192
172
  // IIF), or the SQL-standard `f(x FROM y FOR z)` / nested-call over-capture (Postgres-family TRIM/
193
173
  // SUBSTRING). Comparing the IR arg count to the top-level comma count in the written call catches all
194
- // of these generically — a mismatch means the positional shape isn't reliable, so stay SILENT.
174
+ // of these generically: a mismatch means the positional shape isn't reliable, so stay SILENT.
195
175
  const written = writtenArgCount(fn.cst);
196
176
  if (written !== null && written !== args.length)
197
177
  return;
198
- // --- arity: fire only when NO overload accepts the count ---
199
- if (overloads.every((s) => !arityAccepts(s, args.length))) {
178
+ // --- arity: fires only when NO overload's [min, max] window accepts the count ---
179
+ if (!arityAccepts(overloads, args.length)) {
200
180
  diagnostics.push(callDiag("wrong-arity", fn.cst, arityMessage(overloads, args.length)));
201
181
  return; // one diagnostic per call — don't also type-check a call of the wrong shape
202
182
  }
203
- // --- operand type (CURATED only — harvested param types are never trusted for rejection) ---
204
- if (!curated)
183
+ // --- operand type: only the unambiguous case, exactly one overload and it's curated-origin ---
184
+ if (overloads.length !== 1)
185
+ return;
186
+ const sig = overloads[0];
187
+ if (sig.origin !== "curated")
205
188
  return;
206
189
  const types = args.map((a) => inferType(a, scope, schema));
207
190
  if (types.some((t) => t.kind === "unknown"))
208
191
  return; // any unknown → silent
209
192
  for (let i = 0; i < types.length; i++) {
210
- const param = curated.variadic ? curated.params[Math.min(i, curated.params.length - 1)] : curated.params[i];
211
- if (param && !accepts(types[i], param.type, dialect)) {
212
- diagnostics.push(callDiag("wrong-argument-type", fn.cst, argMessage(curated, i, param.type ?? "?", types[i])));
193
+ const param = sig.variadic ? sig.params[Math.min(i, sig.params.length - 1)] : sig.params[i];
194
+ if (param && !b.accepts(types[i], param.type)) {
195
+ diagnostics.push(callDiag("wrong-argument-type", fn.cst, argMessage(sig, i, param.type ?? "?", types[i])));
213
196
  return; // one diagnostic per call
214
197
  }
215
198
  }
216
199
  }
217
- /** Whether a signature accepts `n` positional args. A variadic signature accepts any count (its last
218
- * param repeats); a fixed one accepts [non-optional count, param count]. */
219
- function arityAccepts(sig, n) {
220
- if (sig.variadic)
221
- return true;
222
- const min = sig.params.filter((p) => !p.optional).length;
223
- return n >= min && n <= sig.params.length;
224
- }
225
- const NUMERIC = new Set(["tinyint", "smallint", "int", "bigint", "float", "double", "decimal"]);
226
- const TEMPORAL = new Set(["date", "timestamp", "time", "interval"]);
227
- function familyOf(name) {
228
- if (NUMERIC.has(name))
229
- return "num";
230
- if (name === "string")
231
- return "str";
232
- if (name === "boolean")
233
- return "bool";
234
- if (TEMPORAL.has(name))
235
- return "temporal";
236
- if (name === "binary")
237
- return "binary";
238
- return "other";
239
- }
240
- /** Dialects that implicitly bridge STRING→numeric in a function argument, so a str→num mismatch must
241
- * NOT be flagged. Doc-cited per dialect:
242
- * - databricks: implicit crosscasting casts STRING to the expected numeric type
243
- * (docs.databricks.com/sql/language-manual/sql-ref-datatype-rules — the docs corpus itself carries
244
- * `substring('hello', '1', 2)` and `date_add(date'2011-11-30', '5')` as documented-valid examples);
245
- * - tsql: char/varchar→int/decimal is an implicit conversion in the CAST/CONVERT conversion chart
246
- * (learn.microsoft.com/sql/t-sql/functions/cast-and-convert-transact-sql) — `ABS('1')` is valid;
247
- * - snowflake: VARCHAR containing a number coerces to NUMBER
248
- * (docs.snowflake.com/en/sql-reference/data-type-conversion — implicit casting/coercion);
249
- * - redshift: PG-8.0 lineage keeps pre-8.3 implicit text→numeric casts
250
- * (docs.aws.amazon.com/redshift/latest/dg/c_Supported_data_types.html — type compatibility:
251
- * CHAR/VARCHAR→numeric implicit);
252
- * - postgres / duckdb: a quoted constant is initially of UNKNOWN type (postgresql.org/docs/18
253
- * sql-syntax-lexical §4.1.2.1) and coerces to whatever the call needs — `abs('1')` is valid — but
254
- * our inference types every quoted literal as `string`, so a str-typed arg may really be an
255
- * untyped literal; rejecting would false-fire on valid SQL.
256
- * NOT in the set (rejection stays live, corpus-proven): bigquery — no STRING→numeric coercion in the
257
- * conversion rules (`ABS('1')` is "No matching signature"; 14.7k analyzer positives sweep clean);
258
- * trino — implicit coercion is numeric/character widening only (`abs('1')` is "Unexpected
259
- * parameters"; 635 docs-corpus positives sweep clean). */
260
- const IMPLICIT_STR_TO_NUM = new Set([
261
- "databricks",
262
- "tsql",
263
- "snowflake",
264
- "redshift",
265
- "postgres",
266
- "duckdb",
267
- ]);
268
- /** Dialects that implicitly bridge boolean↔numeric: T-SQL only, whose `bit` (aliased to boolean by
269
- * TSQL_ALIASES) converts to/from int implicitly per the same CAST/CONVERT chart. Everywhere else
270
- * bool→num / num→bool rejection is safe (Spark: "cannot resolve 'abs(true)' due to data type
271
- * mismatch"; Snowflake: "Invalid argument types for function 'ABS': (BOOLEAN)"; PG/DuckDB/BigQuery/
272
- * Trino likewise reject) — and corpus-proven across all eight sweeps. */
273
- const IMPLICIT_BOOL_NUM = new Set(["tsql"]);
274
- function accepts(argType, paramText, dialect) {
275
- if (!paramText)
276
- return true; // untyped param → no information, accept
277
- const param = inferDialect(dialect).parseType(paramText);
278
- if (param.kind !== "scalar" || argType.kind !== "scalar")
279
- return true; // complex/unknown → accept
280
- const fa = familyOf(argType.name);
281
- const fp = familyOf(param.name);
282
- if (fa === fp)
283
- return true; // same family → accept
284
- if (fa === "str" && fp === "num")
285
- return IMPLICIT_STR_TO_NUM.has(dialect);
286
- if ((fa === "bool" && fp === "num") || (fa === "num" && fp === "bool"))
287
- return IMPLICIT_BOOL_NUM.has(dialect);
288
- return true; // every other cross-family pair (incl. → str, temporal, binary, other) — accept
200
+ /** Whether ANY overload in the set accepts `n` positional args. A variadic overload accepts any count
201
+ * at or above its own min (its last param repeats); a fixed one accepts [non-optional count, param
202
+ * count]. */
203
+ function arityAccepts(overloads, n) {
204
+ return overloads.some((sig) => {
205
+ if (sig.variadic)
206
+ return true;
207
+ const min = sig.params.filter((p) => !p.optional).length;
208
+ return n >= min && n <= sig.params.length;
209
+ });
289
210
  }
290
211
  // ---------------------------------------------------------------------------
291
212
  // Diagnostics
292
213
  // ---------------------------------------------------------------------------
214
+ /** One overload's own [min, max] arity window (max is Infinity for a variadic overload). */
215
+ function windowOf(sig) {
216
+ return [sig.params.filter((p) => !p.optional).length, sig.variadic ? Infinity : sig.params.length];
217
+ }
218
+ /** One [lo, hi] window rendered the way a single contiguous range has always been rendered: an exact
219
+ * count, an unbounded "lo+", or a "lo–hi" span (the en dash there is intentional and frozen, this
220
+ * exact message shape is corpus-pinned, see tests/corpus/bigquery.analyzer.test.ts). */
221
+ function rangeStr([lo, hi]) {
222
+ return lo === hi ? `${lo}` : hi === Infinity ? `${lo}+` : `${lo}–${hi}`;
223
+ }
224
+ /** The arity-window UNION across every overload, merged into the fewest disjoint ranges (windows that
225
+ * overlap or touch collapse into one). A single-overload name always reduces to exactly one range, so
226
+ * this reproduces the pre-overload-aware message byte-for-byte in that case; a genuine multi-overload
227
+ * name with a gap between ranges (e.g. 2 args or 4 args, nothing in between) renders each range and
228
+ * joins them with " or ". */
293
229
  function arityMessage(overloads, got) {
294
- const lo = Math.min(...overloads.map((s) => s.params.filter((p) => !p.optional).length));
295
- const hi = Math.max(...overloads.map((s) => (s.variadic ? Infinity : s.params.length)));
296
- const want = lo === hi ? `${lo}` : hi === Infinity ? `${lo}+` : `${lo}–${hi}`;
297
- return `${overloads[0].name} expects ${want} argument${hi === 1 ? "" : "s"}, got ${got}`;
230
+ const windows = overloads.map(windowOf).sort((a, b) => a[0] - b[0]);
231
+ const groups = [];
232
+ for (const w of windows) {
233
+ const last = groups[groups.length - 1];
234
+ if (last && w[0] <= last[1] + 1) {
235
+ last[1] = Math.max(last[1], w[1]);
236
+ }
237
+ else {
238
+ groups.push([w[0], w[1]]);
239
+ }
240
+ }
241
+ const want = groups.map(rangeStr).join(" or ");
242
+ const overallHi = Math.max(...groups.map((g) => g[1]));
243
+ return `${overloads[0].name} expects ${want} argument${overallHi === 1 ? "" : "s"}, got ${got}`;
298
244
  }
299
245
  function argMessage(sig, i, paramType, got) {
300
246
  const gotName = got.kind === "scalar" ? got.name : got.kind;
@@ -1,5 +1,6 @@
1
- import { foldIdentifier, matchesSourceKey } from "../ident/fold.js";
2
- import { applyPivotCols, applyStarModifiers, applyUnpivotCols, likePatternToRegExp, mergeByName, pivotSourceOutputs, splitColumnRefInScope, } from "../scope/scope.js";
1
+ import { behaviorOf } from "../dialect-behavior/carrier.js";
2
+ import { resolveBehavior } from "../dialect-behavior/registry.js";
3
+ import { applyPivotCols, applyStarModifiers, applyUnpivotCols, mergeByName, pivotSourceOutputs, splitColumnRefInScope, } from "../scope/scope.js";
3
4
  import { endPosition } from "../ir/span.js";
4
5
  import { inferType } from "../infer/infer.js";
5
6
  import { checkCalls } from "./check-calls.js";
@@ -161,7 +162,7 @@ function resolvePipeStage(scope, schema, resolved, diagnostics, starPairs) {
161
162
  return [...aggs, ...keys];
162
163
  }
163
164
  case "drop": {
164
- const fold = (n) => foldIdentifier(n, scope.dialect);
165
+ const fold = (n) => behaviorOf(scope).fold(n);
165
166
  return incoming === "unknown"
166
167
  ? "unknown"
167
168
  : incoming.filter((c) => !stage.drop.some((d) => fold(d) === fold(c)));
@@ -169,7 +170,7 @@ function resolvePipeStage(scope, schema, resolved, diagnostics, starPairs) {
169
170
  case "rename": {
170
171
  if (incoming === "unknown")
171
172
  return "unknown";
172
- const fold = (n) => foldIdentifier(n, scope.dialect);
173
+ const fold = (n) => behaviorOf(scope).fold(n);
173
174
  const map = new Map(stage.renames.map((r) => [fold(r.from), r.to]));
174
175
  return incoming.map((c) => map.get(fold(c)) ?? c);
175
176
  }
@@ -211,7 +212,7 @@ function expandStarPairs(scope, schema, resolved, diagnostics, qualifier) {
211
212
  const cols = [];
212
213
  let matched = false;
213
214
  for (const [key, src] of scope.sources) {
214
- if (want !== undefined && !matchesSourceKey(key, want, scope.dialect))
215
+ if (want !== undefined && !behaviorOf(scope).matchesSourceKey(key, want))
215
216
  continue;
216
217
  // A pseudo-column source (Snowflake/Oracle CONNECT BY's LEVEL) resolves by name but is
217
218
  // excluded from a bare `*` — real pseudo-column semantics. A qualified star can't target
@@ -237,15 +238,16 @@ function expandStar(scope, schema, resolved, diagnostics, qualifier) {
237
238
  * has its own established call sites (e.g. projectionColumns below) that don't need sourceKey and
238
239
  * whose signature this task leaves untouched. */
239
240
  function applyStarModifiersToPairs(pairs, star, dialect) {
240
- const fold = (n) => foldIdentifier(n, dialect);
241
+ const fold = (n) => resolveBehavior(dialect).fold(n);
241
242
  let out = pairs;
242
243
  if (star.exclude) {
243
244
  const removed = new Set(star.exclude.map(fold));
244
245
  out = out.filter((p) => !removed.has(fold(p.name)));
245
246
  }
246
247
  if (star.ilike !== undefined) {
247
- const rx = likePatternToRegExp(star.ilike);
248
- out = out.filter((p) => rx.test(fold(p.name)));
248
+ const b = resolveBehavior(dialect);
249
+ const pat = star.ilike;
250
+ out = out.filter((p) => b.likeMatch(pat, fold(p.name)));
249
251
  }
250
252
  if (star.rename) {
251
253
  const renames = new Map(star.rename.map((r) => [fold(r.from), r.to]));
@@ -282,7 +284,7 @@ function checkSourceColumns(src, schema, resolved, diagnostics, dialect) {
282
284
  if (src.kind === "graphtable")
283
285
  return known(resolved.get(src.scope));
284
286
  if (src.kind === "pivot") {
285
- return known(pivotSourceOutputs(src, (s) => checkSourceColumns(s, schema, resolved, diagnostics, dialect) ?? "unknown"));
287
+ return known(pivotSourceOutputs(src, (s) => checkSourceColumns(s, schema, resolved, diagnostics, dialect) ?? "unknown", dialect));
286
288
  }
287
289
  return src.source.columnAliases ?? known(resolved.get(src.scope));
288
290
  }
@@ -323,14 +325,14 @@ function checkColumn(scope, ref, schema, resolved, diagnostics) {
323
325
  // path `f` against `c`'s struct type — resolved from a table schema or threaded through a
324
326
  // derived (CTE/subquery) column; see checkFieldPath.
325
327
  const split = splitColumnRefInScope(scope, ref.parts);
326
- const name = foldIdentifier(split.column, scope.dialect);
328
+ const name = behaviorOf(scope).fold(split.column);
327
329
  if (split.qualifier !== undefined) {
328
330
  for (let s = scope; s; s = s.parent) {
329
331
  const src = s.sources.get(split.qualifier);
330
332
  if (!src)
331
333
  continue;
332
334
  const cols = sourceColumns(src, schema, resolved, s.dialect);
333
- if (cols && !cols.some((c) => foldIdentifier(c, s.dialect) === name)) {
335
+ if (cols && !cols.some((c) => behaviorOf(s).fold(c) === name)) {
334
336
  diagnostics.push(columnDiag("unknown-column", ref, `Unknown column: ${ref.parts.join(".")}`));
335
337
  return; // base column missing — don't also walk its (nonexistent) fields
336
338
  }
@@ -350,7 +352,7 @@ function checkColumn(scope, ref, schema, resolved, diagnostics) {
350
352
  const cols = sourceColumns(src, schema, resolved, s.dialect);
351
353
  if (!cols)
352
354
  unknown++;
353
- else if (cols.some((c) => foldIdentifier(c, s.dialect) === name))
355
+ else if (cols.some((c) => behaviorOf(s).fold(c) === name))
354
356
  matches++;
355
357
  }
356
358
  if (matches > 1) {
@@ -385,7 +387,7 @@ function checkFieldPath(fields, scope, schema, ref, diagnostics) {
385
387
  return; // unknown / non-struct — don't flag
386
388
  // Struct-field names on a Type are stored FOLDED (parseType folds them at parse time), so
387
389
  // only the reference side folds here — re-folding a preserved-case stored name would corrupt it.
388
- const hit = type.fields.find((f) => f.name === foldIdentifier(field, scope.dialect));
390
+ const hit = type.fields.find((f) => f.name === behaviorOf(scope).fold(field));
389
391
  if (!hit) {
390
392
  diagnostics.push(columnDiag("unknown-field", ref, `Unknown field: ${ref.parts.join(".")}`));
391
393
  return;
@@ -412,7 +414,7 @@ function sourceColumns(src, schema, resolved, dialect) {
412
414
  if (src.kind === "graphtable")
413
415
  return known(resolved.get(src.scope));
414
416
  if (src.kind === "pivot")
415
- return known(pivotSourceOutputs(src, (s) => sourceColumns(s, schema, resolved, dialect) ?? "unknown"));
417
+ return known(pivotSourceOutputs(src, (s) => sourceColumns(s, schema, resolved, dialect) ?? "unknown", dialect));
416
418
  return src.source.columnAliases ?? known(resolved.get(src.scope));
417
419
  }
418
420
  /** Full positioned span of a CST node — 1-based line, 0-based column, endColumn one past the last
@@ -21,7 +21,7 @@
21
21
  // FOLDED name parts (the catalog identity key), never the raw source text. A host
22
22
  // keys its cache by that folded path.
23
23
  // ---------------------------------------------------------------------------
24
- import { foldIdentifier } from "../ident/fold.js";
24
+ import { resolveBehavior } from "../dialect-behavior/registry.js";
25
25
  /** A resolve-on-demand catalog. Implements SchemaProvider by delegating each folded lookup to a
26
26
  * TableResolver and recording the misses; prime() drains those misses through the resolver's
27
27
  * async fetch, re-probes, and bumps `version` when anything new arrived. */
@@ -48,7 +48,7 @@ export class CallbackSchema {
48
48
  return this._misses;
49
49
  }
50
50
  columnsFor(parts, dialect) {
51
- const folded = parts.map((p) => foldIdentifier(p, dialect, "table"));
51
+ const folded = parts.map((p) => resolveBehavior(dialect).fold(p, "table"));
52
52
  const cols = this.resolver.resolve(folded);
53
53
  if (cols === undefined) {
54
54
  this.recordMiss(folded);
@@ -16,7 +16,7 @@
16
16
  // "table") — same as an unquoted table reference in the query. Quoted-key support (a mapping key
17
17
  // meant to represent a quoted/case-exact identifier) is out of scope.
18
18
  // ---------------------------------------------------------------------------
19
- import { foldIdentifier } from "../ident/fold.js";
19
+ import { resolveBehavior } from "../dialect-behavior/registry.js";
20
20
  export class Schema {
21
21
  /** A declared mapping is a CLOSED world: a miss means the table does not exist (unknown-table fires). */
22
22
  world = "closed";
@@ -35,7 +35,7 @@ export class Schema {
35
35
  * (unfolded) — the fold for `dialect` happens here, once. */
36
36
  columnsFor(parts, dialect) {
37
37
  const idx = this.indexFor(dialect);
38
- const fold = (p) => foldIdentifier(p, dialect, "table");
38
+ const fold = (p) => resolveBehavior(dialect).fold(p, "table");
39
39
  const full = parts.map(fold).join(".");
40
40
  return idx.byPath.get(full) ?? idx.byTable.get(fold(parts[parts.length - 1] ?? ""));
41
41
  }
@@ -59,7 +59,7 @@ export class Schema {
59
59
  const isTable = entries.length > 0 && entries.every(([, v]) => typeof v === "string" || isLeaf(v));
60
60
  if (isTable) {
61
61
  const cols = entries.map(([name, leaf]) => toColumn(name, leaf));
62
- const fold = (p) => foldIdentifier(p, dialect, "table");
62
+ const fold = (p) => resolveBehavior(dialect).fold(p, "table");
63
63
  idx.byPath.set(path.map(fold).join("."), cols);
64
64
  const bare = fold(path[path.length - 1] ?? "");
65
65
  if (!idx.byTable.has(bare))
@@ -37,6 +37,14 @@ export interface ResolvedRelation {
37
37
  /** The relation's columns, or undefined until an async describe lands (async warm). */
38
38
  columns?: Column[];
39
39
  }
40
+ /** A completion candidate a host offers for a template call slot (a dbt model for a ref's arg 0, a
41
+ * source name for a source's arg 0). `label` is what the editor inserts at the caret; `detail` is
42
+ * optional display text (a schema, a path). The neutral provider offers none. See
43
+ * `TemplateProvider.templateCandidates`. */
44
+ export interface TemplateCandidate {
45
+ label: string;
46
+ detail?: string;
47
+ }
40
48
  /** Everything known about one call's expansion. Every field optional; `undefined` = unknown. */
41
49
  export interface ResolvedExpansion {
42
50
  /** Parse-time shape. When absent, derived from the strongest present field:
@@ -80,23 +88,30 @@ export declare class DefaultTemplateProvider implements SchemaProvider {
80
88
  tables(_dialect?: string): string[];
81
89
  /** Monotonic invalidation signal — bumps when `prime()` warmed anything new. */
82
90
  get version(): number;
83
- /** The relation a call produces. Default knowledge: `ref(...)` → the dbt-LOGICAL model name,
84
- * `source(a,b)` → the logical [source, table] — literal args only, never fabricated. An
85
- * overriding provider answers the PHYSICAL relation (+columns) from its manifest/describe. */
86
- relationOf(call: TemplateCall): ResolvedRelation | undefined;
87
- /** The scalar value a call yields. Default knowledge: `env_var` returns a string (dbt docs);
88
- * `var` and user macros are unknown. */
89
- valueOf(call: TemplateCall): {
91
+ /** The relation a call produces. The NEUTRAL floor knows no macro vocabulary, so it answers
92
+ * undefined for everything: `ref`/`source` are dbt macros, not minijinja knowledge, and live in
93
+ * `DbtTemplateProvider`. An overriding provider answers the relation (+columns) it knows. */
94
+ relationOf(_call: TemplateCall): ResolvedRelation | undefined;
95
+ /** The scalar value a call yields. Neutral floor: unknown (no macro vocabulary). `env_var` is a
96
+ * dbt builtin known by `DbtTemplateProvider`. */
97
+ valueOf(_call: TemplateCall): {
90
98
  type: ValueType;
91
99
  } | undefined;
92
- /** The rendered-output shape of a call. Default knowledge: the no-output builtins → "nothing".
93
- * Everything else unknown (the engine derives a shape from stronger fields, or falls back to
94
- * its positional fill). */
95
- shapeOf(call: TemplateCall): ExpansionShape | undefined;
100
+ /** The rendered-output shape of a call. Neutral floor: unknown (the engine derives a shape from
101
+ * stronger fields, or falls back to its positional fill). The dbt no-output builtins → "nothing"
102
+ * is `DbtTemplateProvider` knowledge. */
103
+ shapeOf(_call: TemplateCall): ExpansionShape | undefined;
96
104
  /** The columns a column-list-producing macro emits. Default: unknown. */
97
105
  columnsOf(_call: TemplateCall): Column[] | undefined;
98
106
  /** The items a loop collection holds. Default: unknown (loops analyze one representative pass). */
99
107
  collectionOf(_call: TemplateCall): string[] | undefined;
108
+ /** Completion candidates for a template call slot: the caret sits in `callee`'s positional argument
109
+ * `argIndex` (a dbt ref's arg 0 answers the model names, a source's arg 0 the source names, arg 1
110
+ * the table names). `argIndex` is -1 when the caret is still in the callee name itself, so a host
111
+ * can answer the macro/callee names it knows. `packageName` is the dotted package (`dbt_utils` in
112
+ * `dbt_utils.star(...)`). The NEUTRAL provider knows no vocabulary and offers none; a host answers
113
+ * from its catalog. `completeAt` reads this when the caret is inside a jinja tag. */
114
+ templateCandidates(_callee: string, _argIndex: number, _packageName?: string): TemplateCandidate[];
100
115
  /**
101
116
  * Everything known about `call`, composed from the granular methods. Field precedence for the
102
117
  * shape (channel-agreed): an EXPLICIT `shapeOf` answer always wins; absent, derived
@@ -134,7 +149,25 @@ export declare class DefaultTemplateProvider implements SchemaProvider {
134
149
  prime(): Promise<boolean>;
135
150
  private drain;
136
151
  }
137
- /** The provider type the engine consults — the shipped base (or any subclass of it). */
152
+ /**
153
+ * The shipped dbt overlay: a `DefaultTemplateProvider` that knows dbt's built-in macros and nothing
154
+ * more. `ref(...)` resolves to the dbt-LOGICAL model name, `source(a,b)` to the logical
155
+ * [source, table], `env_var` to a string, and the no-output builtins
156
+ * (config/docs/print/log/return/exceptions) render nothing. Static famous-macro knowledge ONLY: no
157
+ * manifest, no warehouse, no project config, no runtime. A dbt consumer with runtime knowledge
158
+ * extends THIS (not the neutral `DefaultTemplateProvider`) and overrides the granular methods with
159
+ * what its manifest / describe cache resolve to. This class is where the dbt vocabulary lives so the
160
+ * neutral core and default do not carry it.
161
+ */
162
+ export declare class DbtTemplateProvider extends DefaultTemplateProvider {
163
+ relationOf(call: TemplateCall): ResolvedRelation | undefined;
164
+ valueOf(call: TemplateCall): {
165
+ type: ValueType;
166
+ } | undefined;
167
+ shapeOf(call: TemplateCall): ExpansionShape | undefined;
168
+ }
169
+ /** The provider type the engine consults — the shipped base (or any subclass of it, e.g.
170
+ * `DbtTemplateProvider`). */
138
171
  export type TemplateProvider = DefaultTemplateProvider;
139
172
  /**
140
173
  * The ONE shared no-configuration default — an OPEN world that answers nothing and diagnoses