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
@@ -1,15 +1,15 @@
1
- import { foldIdentifier } from "../ident/fold.js";
1
+ import { behaviorOf } from "../dialect-behavior/carrier.js";
2
+ import { resolveBehavior } from "../dialect-behavior/registry.js";
2
3
  import { asProvider, tableSourceColumns } from "../qualify/relation-columns.js";
3
- import { likePatternToRegExp, resolveScopes } from "../scope/scope.js";
4
+ import { resolveScopes } from "../scope/scope.js";
4
5
  import { resolveColumnSource } from "../sema/resolve.js";
5
6
  import { coerce, commonType } from "./coerce.js";
6
- import { inferDialect } from "./dialect.js";
7
7
  import { parseType, scalar, UNKNOWN } from "./types.js";
8
8
  const BOOLEAN = scalar("boolean");
9
9
  const INT = scalar("int");
10
10
  const freshCtx = () => ({ seen: new Set(), env: new Map() });
11
11
  export function inferType(expr, scope, schema, ctx = freshCtx()) {
12
- const d = inferDialect(scope.dialect);
12
+ const d = behaviorOf(scope);
13
13
  switch (expr.kind) {
14
14
  case "literal":
15
15
  return d.literal(expr.text);
@@ -74,14 +74,14 @@ function columnType(col, scope, schema, ctx) {
74
74
  return v ? VALUE_TYPES[v.type] : UNKNOWN;
75
75
  }
76
76
  if (col.parts.length === 1) {
77
- const param = ctx.env.get(foldIdentifier(col.parts[0], scope.dialect)); // a lambda parameter shadows columns
77
+ const param = ctx.env.get(behaviorOf(scope).fold(col.parts[0])); // a lambda parameter shadows columns
78
78
  if (param)
79
79
  return param;
80
80
  }
81
81
  const found = resolveColumnSource(scope, col.parts, schema);
82
82
  if (!found)
83
83
  return UNKNOWN;
84
- const base = sourceColumnType(found.source, found.column, schema, ctx, inferDialect(scope.dialect), scope.dialect);
84
+ const base = sourceColumnType(found.source, found.column, schema, ctx, behaviorOf(scope), scope.dialect);
85
85
  return found.fields.length ? fieldType(base, found.fields, scope.dialect) : base;
86
86
  }
87
87
  function sourceColumnType(src, column, schema, ctx, d, dialect) {
@@ -139,7 +139,7 @@ function starPassthroughType(child, column, schema, ctx) {
139
139
  continue; // renamed away
140
140
  if (star.exclude?.some((e) => eq(e, under, d)))
141
141
  continue;
142
- if (star.ilike !== undefined && !likePatternToRegExp(star.ilike).test(foldIdentifier(under, d)))
142
+ if (star.ilike !== undefined && !resolveBehavior(d).likeMatch(star.ilike, resolveBehavior(d).fold(under)))
143
143
  continue;
144
144
  ctx.seen.add(child);
145
145
  const replaced = star.replace?.find((r) => eq(r.column, under, d));
@@ -159,7 +159,7 @@ function fieldType(type, fields, dialect) {
159
159
  if (t.kind !== "struct")
160
160
  return UNKNOWN;
161
161
  // Struct-field names on a Type are stored FOLDED — fold only the reference side.
162
- const hit = t.fields.find((ff) => ff.name === foldIdentifier(f, dialect));
162
+ const hit = t.fields.find((ff) => ff.name === resolveBehavior(dialect).fold(f));
163
163
  if (!hit)
164
164
  return UNKNOWN;
165
165
  t = hit.type;
@@ -170,7 +170,7 @@ function fieldType(type, fields, dialect) {
170
170
  function functionType(fn, scope, schema, ctx) {
171
171
  const name = fn.name.toLowerCase();
172
172
  const args = fn.args;
173
- const d = inferDialect(scope.dialect);
173
+ const d = behaviorOf(scope);
174
174
  // Higher-order and constructor forms are BARE-name Spark/GoogleSQL builtins; a qualified/dotted
175
175
  // call (e.g. a `dataset.transform(...)` UDF) must not borrow them.
176
176
  if (fn.qualifier === undefined) {
@@ -255,7 +255,7 @@ function lambdaResult(lambda, paramTypes, scope, schema, ctx) {
255
255
  if (lambda?.kind !== "lambda")
256
256
  return undefined;
257
257
  const env = new Map(ctx.env);
258
- lambda.params.forEach((p, i) => env.set(foldIdentifier(p, scope.dialect), paramTypes[i] ?? UNKNOWN));
258
+ lambda.params.forEach((p, i) => env.set(behaviorOf(scope).fold(p), paramTypes[i] ?? UNKNOWN));
259
259
  return inferType(lambda.body, scope, schema, { seen: ctx.seen, env });
260
260
  }
261
261
  function arrayElem(t) {
@@ -275,7 +275,7 @@ function constructor(name, args, scope, schema, ctx) {
275
275
  const key = args[i];
276
276
  const fname = key.kind === "literal" ? stringValue(key.text) : `col${i / 2 + 1}`;
277
277
  fields.push({
278
- name: foldIdentifier(fname, scope.dialect),
278
+ name: behaviorOf(scope).fold(fname),
279
279
  type: inferType(args[i + 1], scope, schema, ctx),
280
280
  });
281
281
  }
@@ -285,14 +285,16 @@ function constructor(name, args, scope, schema, ctx) {
285
285
  return {
286
286
  kind: "struct",
287
287
  fields: args.map((a, i) => ({
288
- name: a.kind === "column" ? foldIdentifier(a.parts[a.parts.length - 1], scope.dialect) : `col${i + 1}`,
288
+ name: a.kind === "column" ? behaviorOf(scope).fold(a.parts[a.parts.length - 1]) : `col${i + 1}`,
289
289
  type: inferType(a, scope, schema, ctx),
290
290
  })),
291
291
  };
292
292
  }
293
293
  if (name === "from_json") {
294
294
  const s = args[1];
295
- return s?.kind === "literal" ? parseType(stringValue(s.text), undefined, scope.dialect) : UNKNOWN;
295
+ return s?.kind === "literal"
296
+ ? parseType(stringValue(s.text), undefined, (n) => behaviorOf(scope).fold(n))
297
+ : UNKNOWN;
296
298
  }
297
299
  return undefined;
298
300
  }
@@ -352,5 +354,5 @@ function stringValue(text) {
352
354
  return quoted ? t.slice(1, -1) : t;
353
355
  }
354
356
  function eq(a, b, dialect) {
355
- return foldIdentifier(a, dialect) === foldIdentifier(b, dialect);
357
+ return resolveBehavior(dialect).fold(a) === resolveBehavior(dialect).fold(b);
356
358
  }
@@ -1,6 +1,5 @@
1
- import { foldIdentifier } from "../ident/fold.js";
1
+ import { resolveBehavior } from "../dialect-behavior/registry.js";
2
2
  import { tableSourceColumns } from "../qualify/relation-columns.js";
3
- import { likePatternToRegExp } from "../scope/scope.js";
4
3
  import { resolveColumnSource } from "../sema/resolve.js";
5
4
  const freshCtx = () => ({ seen: new Set() });
6
5
  export function inferNullability(expr, scope, schema, ctx = freshCtx()) {
@@ -118,7 +117,7 @@ function starPassthroughNullability(child, column, schema, ctx) {
118
117
  continue; // renamed away
119
118
  if (star.exclude?.some((e) => eq(e, under, d)))
120
119
  continue;
121
- if (star.ilike !== undefined && !likePatternToRegExp(star.ilike).test(foldIdentifier(under, d)))
120
+ if (star.ilike !== undefined && !resolveBehavior(d).likeMatch(star.ilike, resolveBehavior(d).fold(under)))
122
121
  continue;
123
122
  ctx.seen.add(child);
124
123
  const replaced = star.replace?.find((r) => eq(r.column, under, d));
@@ -283,5 +282,5 @@ function functionNullability(fn, scope, schema, ctx) {
283
282
  }
284
283
  // --- helpers ---------------------------------------------------------------
285
284
  function eq(a, b, dialect) {
286
- return foldIdentifier(a, dialect) === foldIdentifier(b, dialect);
285
+ return resolveBehavior(dialect).fold(a) === resolveBehavior(dialect).fold(b);
287
286
  }
@@ -26,7 +26,7 @@ export declare function scalar(name: string): Type;
26
26
  * T-SQL, e.g. bit→boolean, datetime→timestamp, nvarchar→string). `dialect` folds struct field
27
27
  * names with that dialect's identifier rules (fields are stored folded — identity keys); absent,
28
28
  * the default fold (backtick-strip + lower) reproduces the legacy behavior. */
29
- export declare function parseType(text: string, aliases?: Record<string, string>, dialect?: string): Type;
29
+ export declare function parseType(text: string, aliases?: Record<string, string>, foldField?: (name: string) => string): Type;
30
30
  /** Spark/Databricks scalar type aliases → the shared canonical names (also the default table for
31
31
  * `parseType` below). Exported so `src/dialect-symbols.ts` can build the databricks `types` set from
32
32
  * it without duplicating the table. */
@@ -8,7 +8,6 @@
8
8
  // identity keys. Comparisons against a raw reference fold only the reference side; re-folding a
9
9
  // stored name would corrupt a preserved-case (quoted snowflake/postgres) field.
10
10
  // ---------------------------------------------------------------------------
11
- import { foldIdentifier } from "../ident/fold.js";
12
11
  export const UNKNOWN = { kind: "unknown" };
13
12
  /** A scalar type by canonical name (after alias normalisation). */
14
13
  export function scalar(name) {
@@ -19,20 +18,20 @@ export function scalar(name) {
19
18
  * T-SQL, e.g. bit→boolean, datetime→timestamp, nvarchar→string). `dialect` folds struct field
20
19
  * names with that dialect's identifier rules (fields are stored folded — identity keys); absent,
21
20
  * the default fold (backtick-strip + lower) reproduces the legacy behavior. */
22
- export function parseType(text, aliases = SCALAR_ALIASES, dialect) {
21
+ export function parseType(text, aliases = SCALAR_ALIASES, foldField) {
23
22
  const s = text.trim();
24
23
  if (s === "")
25
24
  return UNKNOWN;
26
25
  const array = /^array\s*<(.*)>$/is.exec(s);
27
26
  if (array)
28
- return { kind: "array", element: parseType(array[1], undefined, dialect) };
27
+ return { kind: "array", element: parseType(array[1], undefined, foldField) };
29
28
  const map = /^map\s*<(.*)>$/is.exec(s);
30
29
  if (map) {
31
30
  const [key, value] = splitTopLevel(map[1]);
32
31
  return {
33
32
  kind: "map",
34
- key: parseType(key ?? "", undefined, dialect),
35
- value: parseType(value ?? "", undefined, dialect),
33
+ key: parseType(key ?? "", undefined, foldField),
34
+ value: parseType(value ?? "", undefined, foldField),
36
35
  };
37
36
  }
38
37
  const struct = /^struct\s*<(.*)>$/is.exec(s);
@@ -42,9 +41,10 @@ export function parseType(text, aliases = SCALAR_ALIASES, dialect) {
42
41
  const colon = topLevelColon(part);
43
42
  if (colon < 0)
44
43
  continue;
45
- const name = foldIdentifier(part.slice(0, colon).trim(), dialect);
44
+ const raw = part.slice(0, colon).trim();
45
+ const name = foldField ? foldField(raw) : raw;
46
46
  if (name) {
47
- fields.push({ name, type: parseType(stripComment(part.slice(colon + 1).trim()), undefined, dialect) });
47
+ fields.push({ name, type: parseType(stripComment(part.slice(colon + 1).trim()), undefined, foldField) });
48
48
  }
49
49
  }
50
50
  return { kind: "struct", fields };
package/dist/ir/ir.d.ts CHANGED
@@ -118,6 +118,11 @@ export interface PivotInfo {
118
118
  /** The pivoted relation's alias (T-SQL `… PIVOT (…) AS pvt`), referenced by later columns.
119
119
  * Absent for Spark, where the pivot transforms the SELECT directly. */
120
120
  alias?: string;
121
+ /** True when the output columns are data-dependent and cannot be enumerated statically (DuckDB's
122
+ * statement `PIVOT … ON … USING` with no fixed IN-list — the distinct ON-values become columns).
123
+ * The reshape is still modelled (source + consumed columns visible); downstream resolves the output
124
+ * to "unknown" rather than guessing a column set. */
125
+ dynamic?: boolean;
121
126
  }
122
127
  export interface UnpivotInfo {
123
128
  /** The value column the unpivot produces. */
@@ -612,28 +617,32 @@ export interface LateralViewSource {
612
617
  pseudo?: true;
613
618
  cst: ParserRuleContext;
614
619
  }
615
- /** Present when a source was written as a minijinja template tag ({{ ref('x') }} / {{ source('a','b') }} /
616
- * a macro call in a FROM slot). Attached post-lower by the jinja front end (src/minijinja/apply-tags.ts);
617
- * plain SQL parses never carry it. `opaque: true` = the tag's output relation is undeterminable
618
- * (macro / computed ref) and `name` is the raw placeholder — qualify treats the source as an opaque
619
- * relation (no unknown-table/-column diagnostics). Without `opaque`, `name` carries the tag's literal
620
- * dbt-logical name parts (ref model, or [sourceName, tableName]). This type is IR-neutral (no import
621
- * from src/minijinja); consumers needing the full TagNode correlate by `span` with parseTemplated().tags. */
620
+ /** Present when a source was written as a minijinja template tag: a call in a FROM slot
621
+ * ({{ ref('x') }} / {{ source('a','b') }} / a macro), or a non-call expression. Attached post-lower by
622
+ * the jinja front end (src/minijinja/apply-tags.ts); plain SQL parses never carry it. When the
623
+ * TemplateProvider resolves the call's relation, `name` carries the resolved name parts and the
624
+ * source's columns bind through the provider. When it does not (the neutral provider, a plain macro, a
625
+ * computed call), `name` stays the raw placeholder and the source is an opaque relation (no
626
+ * unknown-table/-column diagnostics). The tag AST and this marker are dbt-NEUTRAL: ref vs source is not
627
+ * a stored kind but the callee name on `call` (call.name === "ref"), resolved by the provider the same
628
+ * way a SQL function's type comes from a dialect registry. This type is IR-neutral (no import from
629
+ * src/minijinja); consumers needing the full TagNode correlate by `span` with parseTemplated().tags. */
622
630
  export interface TemplateSourceInfo {
623
- /** `"expr"` = a non-call templated expression in the slot (a bare variable `{{ t }}`, a concat
624
- * `{{ a ~ b }}`, …) — always `opaque` (its relation is undeterminable), `name` stays the raw
625
- * placeholder, exactly like the macro-opaque case. */
626
- kind: "ref" | "source" | "macro" | "expr";
631
+ /** `"call"` = a template call occupies the slot; its `call` is the provider key that names the
632
+ * source and types its columns. `"expr"` = a non-call templated expression (a bare variable
633
+ * `{{ t }}`, a concat `{{ a ~ b }}`) that carries no call, so it stays opaque and `name` is the
634
+ * raw placeholder. */
635
+ kind: "call" | "expr";
627
636
  /** The whole tag's span ({{ … }} inclusive), document coordinates. */
628
637
  span: PartSpan;
629
638
  opaque?: true;
630
- /** Present when a bare `{{ t }}` resolved through a literal `{% set t = ref('x') %}`: `kind`/`name`
631
- * carry the resolved ref/source, but the TagNode at `span` is the USE site (kind "other"), not a
632
- * ref/source node — consumers correlating span→TagNode must not expect a ref node here. */
639
+ /** Present when a bare `{{ t }}` resolved through a literal `{% set t = ref('x') %}`: `name`/`call`
640
+ * carry the resolved relation, but the TagNode at `span` is the USE site (kind "other"), not the
641
+ * call node, so consumers correlating span to TagNode must not expect a call node here. */
633
642
  indirect?: true;
634
- /** The provider key for this source's tag (ref/source/macro calls, and resolved set-indirection):
635
- * the semantic layer resolves the source through `TemplateProvider.expansion(call)`. Absent only
636
- * on the opaque `"expr"` kind (no call to ask about). */
643
+ /** The provider key for this source's tag (any call in a FROM slot, and resolved set-indirection):
644
+ * the semantic layer resolves the source through `TemplateProvider.expansion(call)`. Absent only on
645
+ * the opaque `"expr"` kind (no call to ask about). */
637
646
  call?: TemplateCall;
638
647
  }
639
648
  export interface TableSource {
package/dist/ir/span.js CHANGED
@@ -2,8 +2,6 @@
2
2
  // START line/column; when the last token's TEXT spans newlines (a multi-line string literal,
3
3
  // dollar-quoted body, or block comment), the end position must advance the line and reset the
4
4
  // column. This is the one place that does that math so symbols.ts and qualify.ts agree.
5
- // (src/lsp/ranges.ts works on an antlr Token directly and carries an equivalent fix of its own —
6
- // it cannot import from the application layer the other way around.)
7
5
  /**
8
6
  * End position (exclusive) of a token, given its 1-based start `line`, 0-based start `column`, and
9
7
  * `text`. For a single-line token this is the start line and `column + text.length`. For a token
package/dist/ir/walk.js CHANGED
@@ -1,8 +1,8 @@
1
1
  // ---------------------------------------------------------------------------
2
2
  // Shared IR walk helpers — expr/query-tree traversal with no scope/document
3
- // knowledge, so both src/document/node-at.ts (offset → smallest covering Expr)
4
- // and src/lsp/features/folding.ts (IR → foldable ranges) single-source the same
5
- // walk instead of carrying byte-for-byte duplicates.
3
+ // knowledge, so src/document/node-at.ts (offset → smallest covering Expr) and
4
+ // any other IR-only consumer single-source the same walk instead of carrying
5
+ // byte-for-byte duplicates.
6
6
  // ---------------------------------------------------------------------------
7
7
  /** Every QueryExpr reachable in the IR (the AST and its nested query blocks). */
8
8
  export function allQueryExprs(root) {
@@ -1,4 +1,5 @@
1
- import { foldIdentifier, foldTableName } from "../ident/fold.js";
1
+ import { behaviorOf } from "../dialect-behavior/carrier.js";
2
+ import { resolveBehavior } from "../dialect-behavior/registry.js";
2
3
  import { OPEN_PROVIDER } from "../qualify/template-provider.js";
3
4
  import { nodeAt } from "../document/node-at.js";
4
5
  import { findProducerProjection, resolveColumnSource } from "../sema/resolve.js";
@@ -129,7 +130,7 @@ function followColumn(scope, parts, walk, trail = []) {
129
130
  const { source, column } = binding;
130
131
  if (source.kind === "table") {
131
132
  // A recursive CTE's self-reference is a plain table here (see Walk.activeCtes) — cycle-guard it.
132
- if (source.name.length === 1 && walk.activeCtes.has(foldIdentifier(source.name[0], scope.dialect)))
133
+ if (source.name.length === 1 && walk.activeCtes.has(behaviorOf(scope).fold(source.name[0])))
133
134
  return [{ kind: "unresolved", via }];
134
135
  return [{ kind: "origin", origin: { table: source.name, column }, via }];
135
136
  }
@@ -141,7 +142,7 @@ function followColumn(scope, parts, walk, trail = []) {
141
142
  }
142
143
  if (walk.seen.has(child))
143
144
  return [{ kind: "unresolved", via }]; // recursive-CTE cycle guard
144
- const cteName = source.kind === "cte" ? foldIdentifier(source.ref.def.name, scope.dialect) : undefined;
145
+ const cteName = source.kind === "cte" ? behaviorOf(scope).fold(source.ref.def.name) : undefined;
145
146
  walk.seen.add(child);
146
147
  if (cteName)
147
148
  walk.activeCtes.add(cteName);
@@ -195,11 +196,12 @@ function forkLegs(setopScope, column, aliases, dialect, walk, trail = []) {
195
196
  /** The position of `column` in a set-op's output: via declared CTE aliases if given, else the set-op's
196
197
  * own output names (the left branch's, positionally). -1 when not determinable (falls back to name). */
197
198
  function columnIndex(setopScope, column, aliases, dialect) {
198
- const want = foldIdentifier(column, dialect);
199
+ const bh = resolveBehavior(dialect);
200
+ const want = bh.fold(column);
199
201
  if (aliases)
200
- return aliases.findIndex((a) => foldIdentifier(a, dialect) === want);
202
+ return aliases.findIndex((a) => bh.fold(a) === want);
201
203
  const outs = setopScope.outputs;
202
- return outs !== "unknown" ? outs.findIndex((o) => foldIdentifier(o, dialect) === want) : -1;
204
+ return outs !== "unknown" ? outs.findIndex((o) => bh.fold(o) === want) : -1;
203
205
  }
204
206
  /** The projection producing `column` in one set-op leg — by name for BY NAME, else by position with a
205
207
  * name fallback (positional legs can differ in column name: `SELECT a AS x … UNION SELECT b …`). */
@@ -207,9 +209,9 @@ function legProducer(leg, column, idx, byName, dialect) {
207
209
  if (leg.body.kind !== "select")
208
210
  return undefined;
209
211
  const projs = leg.body.projections;
210
- const byNameHit = () => projs.find((p) => !p.isStar &&
211
- p.name !== undefined &&
212
- foldIdentifier(p.name, dialect) === foldIdentifier(column, dialect));
212
+ const bh = resolveBehavior(dialect);
213
+ const wantName = bh.fold(column);
214
+ const byNameHit = () => projs.find((p) => !p.isStar && p.name !== undefined && bh.fold(p.name) === wantName);
213
215
  if (byName)
214
216
  return byNameHit();
215
217
  if (idx >= 0 && idx < projs.length && !projs[idx].isStar)
@@ -407,8 +409,9 @@ function childExprs(expr) {
407
409
  }
408
410
  }
409
411
  function dedupOrigins(origins, dialect) {
412
+ const b = resolveBehavior(dialect);
410
413
  const by = new Map();
411
414
  for (const o of origins)
412
- by.set(`${foldTableName(o.table, dialect).join(".")}.${foldIdentifier(o.column, dialect)}`, o);
415
+ by.set(`${b.foldTableName(o.table).join(".")}.${b.fold(o.column)}`, o);
413
416
  return [...by.values()];
414
417
  }
@@ -1,4 +1,5 @@
1
- import { foldIdentifier, foldTableName, matchesSourceKey } from "../ident/fold.js";
1
+ import { behaviorOf } from "../dialect-behavior/carrier.js";
2
+ import { resolveBehavior } from "../dialect-behavior/registry.js";
2
3
  import { resolveScopes } from "../scope/scope.js";
3
4
  import { columnNamesOf, findProducerProjection, resolveColumnSource } from "../sema/resolve.js";
4
5
  /** Lineage of a query's output columns: each output → the base-table columns it derives from. */
@@ -71,12 +72,12 @@ function pipeStageLineage(scope, schema, seen) {
71
72
  return [...aggs, ...keys];
72
73
  }
73
74
  case "drop": {
74
- const fold = (n) => foldIdentifier(n, scope.dialect);
75
+ const fold = (n) => behaviorOf(scope).fold(n);
75
76
  const dropped = new Set(stage.drop.map(fold));
76
77
  return passthrough().filter((l) => !dropped.has(fold(l.output)));
77
78
  }
78
79
  case "rename": {
79
- const fold = (n) => foldIdentifier(n, scope.dialect);
80
+ const fold = (n) => behaviorOf(scope).fold(n);
80
81
  const map = new Map(stage.renames.map((r) => [fold(r.from), r.to]));
81
82
  return passthrough().map((l) => ({
82
83
  output: map.get(fold(l.output)) ?? l.output,
@@ -84,7 +85,7 @@ function pipeStageLineage(scope, schema, seen) {
84
85
  }));
85
86
  }
86
87
  case "set": {
87
- const fold = (n) => foldIdentifier(n, scope.dialect);
88
+ const fold = (n) => behaviorOf(scope).fold(n);
88
89
  const set = new Map(stage.assignments.map((a) => [fold(a.column), a.expr]));
89
90
  return passthrough().map((l) => {
90
91
  const e = set.get(fold(l.output));
@@ -113,7 +114,7 @@ function starLineage(scope, qualifier, schema, seen) {
113
114
  const want = qualifier ? (qualifier[qualifier.length - 1] ?? "") : undefined;
114
115
  const out = [];
115
116
  for (const [key, src] of scope.sources) {
116
- if (want !== undefined && !matchesSourceKey(key, want, scope.dialect))
117
+ if (want !== undefined && !behaviorOf(scope).matchesSourceKey(key, want))
117
118
  continue;
118
119
  for (const col of columnNamesOf(src, schema, undefined, scope.dialect) ?? []) {
119
120
  out.push({ output: col, origins: dedup(columnOrigins(src, col, schema, seen), scope.dialect) });
@@ -217,12 +218,14 @@ function subqueryOrigins(query, schema, seen, dialect) {
217
218
  return exprOrigins(root.body.projections[0].expr, root, schema, seen);
218
219
  }
219
220
  function dedup(origins, dialect) {
221
+ const b = resolveBehavior(dialect);
220
222
  const byKey = new Map();
221
223
  for (const o of origins) {
222
- byKey.set(`${foldTableName(o.table, dialect).join(".")}.${foldIdentifier(o.column, dialect)}`, o);
224
+ byKey.set(`${b.foldTableName(o.table).join(".")}.${b.fold(o.column)}`, o);
223
225
  }
224
226
  return [...byKey.values()];
225
227
  }
226
228
  function eq(a, b, dialect) {
227
- return foldIdentifier(a, dialect) === foldIdentifier(b, dialect);
229
+ const bh = resolveBehavior(dialect);
230
+ return bh.fold(a) === bh.fold(b);
228
231
  }
@@ -1,5 +1,6 @@
1
1
  import type { QueryExpr } from "../ir/ir.js";
2
- import type { TagNode } from "./tag-ast.js";
2
+ import type { TemplateCall, TemplateProvider } from "../qualify/template-provider.js";
3
+ import type { MacroCall, TagNode } from "./tag-ast.js";
3
4
  /** The rebuilt AST plus the tag↔node correlations collected while building it. */
4
5
  export interface TagCorrelation {
5
6
  ast: QueryExpr;
@@ -9,10 +10,16 @@ export interface TagCorrelation {
9
10
  byTag: Map<TagNode, object>;
10
11
  }
11
12
  /**
12
- * Rewrite templated FROM/JOIN sources in `ast` to carry their dbt-logical name +
13
- * a `template` marker, correlating each source to a tag by span containment.
14
- * Returns the SAME `ast` reference when nothing correlates (structural sharing);
15
- * returns a re-frozen rebuilt tree otherwise. Total — never throws; the
16
- * correlation maps are empty (not absent) on the no-op and error paths.
13
+ * Rewrite templated FROM/JOIN sources in `ast` to carry their provider-resolved name (when the
14
+ * provider resolves the call) plus a `template` marker, correlating each source to a tag by span
15
+ * containment. Returns the SAME `ast` reference when nothing correlates (structural sharing);
16
+ * returns a re-frozen rebuilt tree otherwise. Total, never throws; the correlation maps are empty
17
+ * (not absent) on the no-op and error paths.
17
18
  */
18
- export declare function applyTemplateTags(ast: QueryExpr, tags: TagNode[], text: string): TagCorrelation;
19
+ export declare function applyTemplateTags(ast: QueryExpr, tags: TagNode[], text: string, provider: TemplateProvider): TagCorrelation;
20
+ /**
21
+ * The provider key of a tag-AST MacroCall — name + package + literal args, with kwargs
22
+ * carried separately (the channel-agreed TemplateCall contract: quote-stripped, escapes
23
+ * NOT resolved, computed arguments null, kwargs never dropped).
24
+ */
25
+ export declare function callOf(mc: MacroCall, text: string): TemplateCall;