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,4 +1,4 @@
1
- import { type Parser } from "antlr4ng";
1
+ import { type ATN } from "antlr4ng";
2
2
  /**
3
3
  * What can legally come next at the caret:
4
4
  * - `tokens`: candidate terminal token TYPES (keywords/punctuation/literals) collectable there.
@@ -9,6 +9,13 @@ export interface Candidates {
9
9
  tokens: Set<number>;
10
10
  rules: Set<number>;
11
11
  }
12
+ /** The minimal token view the walk needs: each token's antlr type + channel, in source order. Both
13
+ * antlr's `Token` and our neutral `Token` (src/token/token.ts) satisfy it, so the walk runs over
14
+ * the document's OWN already-lexed token stream, never a re-parse. */
15
+ export interface WalkToken {
16
+ type: number;
17
+ channel: number;
18
+ }
12
19
  /**
13
20
  * Our own ATN candidate-collection walk — a reimplementation of antlr4-c3's
14
21
  * `CodeCompletionCore` (`collectCandidates` / `processRule` / `translateStackToRuleIndex`),
@@ -20,5 +27,9 @@ export interface Candidates {
20
27
  * (`tokenListIndex === caretListIndex`) every terminal transition's label contributes its token
21
28
  * types as candidates — unless the current rule call stack is inside a preferred (name/column)
22
29
  * rule, in which case we record that rule and suppress the raw tokens it subsumes.
30
+ *
31
+ * `atn` is the dialect's parser ATN (input-independent, a per-dialect static) and `tokens` is the
32
+ * document's own lexed token stream up to (at least) the caret; the walk consumes only their `type`
33
+ * and `channel`, so it reuses the already-parsed tokens rather than re-lexing the source.
23
34
  */
24
- export declare function collectCandidates(parser: Parser, startRuleIndex: number, caretTokenIndex: number, preferredRules: Set<number>, ignoredTokens: Set<number>): Candidates;
35
+ export declare function collectCandidates(atn: ATN, startRuleIndex: number, tokens: readonly WalkToken[], caretTokenIndex: number, preferredRules: Set<number>, ignoredTokens: Set<number>): Candidates;
@@ -10,13 +10,17 @@ import { AtomTransition, NotSetTransition, RangeTransition, RuleStopState, RuleT
10
10
  * (`tokenListIndex === caretListIndex`) every terminal transition's label contributes its token
11
11
  * types as candidates — unless the current rule call stack is inside a preferred (name/column)
12
12
  * rule, in which case we record that rule and suppress the raw tokens it subsumes.
13
+ *
14
+ * `atn` is the dialect's parser ATN (input-independent, a per-dialect static) and `tokens` is the
15
+ * document's own lexed token stream up to (at least) the caret; the walk consumes only their `type`
16
+ * and `channel`, so it reuses the already-parsed tokens rather than re-lexing the source.
13
17
  */
14
- export function collectCandidates(parser, startRuleIndex, caretTokenIndex, preferredRules, ignoredTokens) {
15
- const walk = new CandidateWalk(parser, caretTokenIndex, preferredRules, ignoredTokens);
18
+ export function collectCandidates(atn, startRuleIndex, tokens, caretTokenIndex, preferredRules, ignoredTokens) {
19
+ const walk = new CandidateWalk(atn, tokens, caretTokenIndex, preferredRules, ignoredTokens);
16
20
  return walk.run(startRuleIndex);
17
21
  }
18
22
  class CandidateWalk {
19
- parser;
23
+ atn;
20
24
  preferredRules;
21
25
  ignoredTokens;
22
26
  tokens = { tokens: new Set(), rules: new Set() };
@@ -32,16 +36,15 @@ class CandidateWalk {
32
36
  * during a walk — that persistence is what kills the blowup.
33
37
  */
34
38
  shortcutMap = new Map();
35
- constructor(parser, caretTokenIndex, preferredRules, ignoredTokens) {
36
- this.parser = parser;
39
+ constructor(atn, tokens, caretTokenIndex, preferredRules, ignoredTokens) {
40
+ this.atn = atn;
37
41
  this.preferredRules = preferredRules;
38
42
  this.ignoredTokens = ignoredTokens;
39
43
  // Precompute the on-channel token types from 0 up to the caret token. The walk consumes
40
44
  // these to prune paths that cannot match what the user already typed. Mirrors c3's
41
45
  // `tokens` array built in `collectCandidates`.
42
- const stream = this.parser.inputStream;
43
- for (let i = 0; i <= caretTokenIndex; i++) {
44
- const tok = stream.get(i);
46
+ for (let i = 0; i <= caretTokenIndex && i < tokens.length; i++) {
47
+ const tok = tokens[i];
45
48
  if (tok.channel !== Token.DEFAULT_CHANNEL)
46
49
  continue;
47
50
  this.inputTypes.push(tok.type);
@@ -52,7 +55,7 @@ class CandidateWalk {
52
55
  this.caretListIndex = this.inputTypes.length - 1;
53
56
  }
54
57
  run(startRuleIndex) {
55
- const startState = this.parser.atn.ruleToStartState[startRuleIndex];
58
+ const startState = this.atn.ruleToStartState[startRuleIndex];
56
59
  if (startState)
57
60
  this.processRule(startState, 0, []);
58
61
  return this.tokens;
@@ -204,7 +207,7 @@ class CandidateWalk {
204
207
  }
205
208
  }
206
209
  collectComplement(transition) {
207
- const max = this.parser.atn.maxTokenType;
210
+ const max = this.atn.maxTokenType;
208
211
  // Cap the enumeration: a wide-open NotSet/Wildcard is not a useful keyword list.
209
212
  const COMPLEMENT_CAP = 64;
210
213
  const excluded = transition instanceof NotSetTransition && transition.label ? transition.label : null;
@@ -1,11 +1,12 @@
1
1
  import type { SqlDocument } from "../document/document.js";
2
2
  import type { SchemaProvider } from "../qualify/schema-provider.js";
3
3
  /** One completion candidate. The editor filters this list by the typed prefix and applies the
4
- * chosen label at the caret; we only produce the labels, anchored at the caret offset. */
4
+ * chosen label at the caret; we only produce the labels, anchored at the caret offset. The
5
+ * `"template"` kind is a host candidate for a jinja call slot (a dbt model for a ref's arg). */
5
6
  export interface Completion {
6
7
  label: string;
7
- kind: "keyword" | "column" | "table" | "function";
8
- /** Extra display info — e.g. a column's type when the schema knows it. */
8
+ kind: "keyword" | "column" | "table" | "function" | "template";
9
+ /** Extra display info, e.g. a column's type when the schema knows it. */
9
10
  detail?: string;
10
11
  }
11
12
  /**
@@ -2,9 +2,11 @@
2
2
  // completeAt() — scope-aware completion over a SqlDocument.
3
3
  //
4
4
  // The interactive editor feature that lives in the BROKEN-input world: the user
5
- // is mid-keystroke, so this runs its OWN error-tolerant lex+parse of the current
6
- // text (via makeParser), positions the ATN candidate walk at the caret, and turns
7
- // the raw {tokens, rules} the walk reports into editor completion items:
5
+ // is mid-keystroke, so this drives an ATN candidate walk over the DOCUMENT'S OWN
6
+ // already-lexed token stream (cell.tokens, reused not re-parsed; the document's
7
+ // error-tolerant parse already ran, and for a templated document over the jinja
8
+ // placeholder), positions it at the caret, and turns the raw {tokens, rules} the
9
+ // walk reports into editor completion items:
8
10
  // - keywords — candidate token types whose grammar literal is a word (FROM, …)
9
11
  // - tables — schema table names, when the caret is at a relation-name slot
10
12
  // - columns — the scope's visible columns, when at a value/column slot
@@ -15,11 +17,13 @@
15
17
  // ---------------------------------------------------------------------------
16
18
  import { Token } from "antlr4ng";
17
19
  import { nodeAt } from "../document/node-at.js";
18
- import { displayName, foldIdentifier } from "../ident/fold.js";
19
- import { inferDialect } from "../infer/dialect.js";
20
+ import { resolveBehavior } from "../dialect-behavior/registry.js";
21
+ import { DefaultTemplateProvider } from "../qualify/template-provider.js";
22
+ import { callOf } from "../minijinja/apply-tags.js";
20
23
  import { collectCandidates } from "./atn-walk.js";
24
+ import { jinjaSlotAt } from "./jinja-slot.js";
21
25
  import { COMPLETION_CONFIG } from "./config.js";
22
- import { makeParser } from "./parser-factory.js";
26
+ import { completionMeta } from "./parser-factory.js";
23
27
  /**
24
28
  * Completion candidates for the caret at `offset` in `doc`. Schema-aware when a `Schema` is given
25
29
  * (table names + column types). NEVER throws: on broken / mid-edit input it still returns the
@@ -38,24 +42,47 @@ export function completeAt(doc, offset, schema) {
38
42
  export const complete = completeAt;
39
43
  function collect(doc, offset, schema) {
40
44
  const dialect = doc.dialect;
45
+ // Inside a jinja tag ({{ ref('| }}, {% if | %}, {{ a ~ | }}) the caret is not in SQL at all, so SQL
46
+ // completion is wrong: the tag was blanked to a placeholder sitting in some SQL slot, so the walk
47
+ // would otherwise offer keywords/tables/columns inside the jinja. A recognized call slot answers the
48
+ // host's candidates through the template provider (the neutral provider offers none); any other
49
+ // position strictly inside a tag answers nothing. Only a caret outside every tag falls through to
50
+ // ordinary SQL completion below. Tags are reused from the document, never re-parsed.
51
+ const tags = doc.templated?.tags;
52
+ if (tags) {
53
+ const slot = jinjaSlotAt(tags, doc.text, offset);
54
+ if (slot)
55
+ return templateCompletions(slot, schema);
56
+ if (tags.some((t) => offset > t.tagSpan.start && offset < t.tagSpan.end))
57
+ return [];
58
+ }
41
59
  const cfg = COMPLETION_CONFIG[dialect];
42
- // Route to the statement CELL owning the caret: the ATN walk parses that cell's text (with a
43
- // cell-relative caret) and the visible-column lookup runs over that cell's own scope tree — so a
44
- // caret in statement 2 of a multi-statement document completes through its real scope, not the
45
- // compound facade. Single-cell: the cell IS the document, so this is identical to a whole-doc walk.
60
+ // Route to the statement CELL owning the caret: the visible-column lookup runs over that cell's
61
+ // own scope tree (cell-relative caret) and the ATN walk over that cell's own tokens, so a caret
62
+ // in statement 2 of a multi-statement document completes through its real scope, not the compound
63
+ // facade. Single-cell: the cell IS the document, so this is identical to a whole-doc walk.
46
64
  const cell = doc.cellAt(offset);
47
- const cellText = cell ? cell.text : doc.text;
48
- const cellOffset = cell ? offset - cell.span.start : offset;
49
65
  const cellScopes = cell ? cell.scopes : doc.scopes;
50
66
  const cellAst = cell ? cell.ast : doc.ast;
51
- // Completion runs its own error-tolerant parse to position the walk (expected — the walk needs
52
- // a parser whose ATN we DFS, not the document's valid-parse CST).
53
- const m = makeParser(cellText, dialect);
54
- // runEntry() first: the CommonTokenStream fills lazily, so the full token list (needed to find
55
- // the caret token) only exists after the parse drives it.
56
- m.runEntry();
57
- const caretIdx = caretTokenIndex(m, cellOffset);
58
- const cand = collectCandidates(m.parser, m.entryRuleIndex, caretIdx, cfg.preferredRules, cfg.ignoredTokens);
67
+ // Two coordinate spaces: the scope/column lookup is CELL-relative (cell.scopes/cell.ast carry
68
+ // cell-relative spans), the token walk is DOCUMENT-relative (cell.tokens are shifted to doc
69
+ // coordinates), so `offset` drives the walk and `cellOffset` the scope lookup.
70
+ const cellOffset = cell ? offset - cell.span.start : offset;
71
+ // The ATN walk reuses the DOCUMENT'S OWN already-lexed token stream instead of re-parsing the
72
+ // text. For a TEMPLATED document those tokens are the SQL-over-placeholder stream (the jinja tags
73
+ // are channel-2 tokens the walk skips), so completion sees real SQL at document-true offsets and
74
+ // never has to re-derive the placeholder; the raw `{{ }}` text that made a fresh lexer die from
75
+ // char 0 is never handed to a lexer again. A synthetic EOF closes the stream (mapTokens drops
76
+ // antlr's EOF sentinel), matching the entry rule's EOF anchor; its `start` past every real token
77
+ // keeps it the caret-index fallback for an end-of-input caret.
78
+ const meta = completionMeta(dialect);
79
+ const end = cell ? cell.span.end : doc.text.length;
80
+ const walkTokens = [
81
+ ...(cell ? cell.tokens : doc.tokens),
82
+ { type: Token.EOF, channel: Token.DEFAULT_CHANNEL, start: end, text: "" },
83
+ ];
84
+ const caretIdx = caretTokenIndex(walkTokens, offset);
85
+ const cand = collectCandidates(meta.atn, meta.entryRuleIndex, walkTokens, caretIdx, cfg.preferredRules, cfg.ignoredTokens);
59
86
  const out = [];
60
87
  const seen = new Set(); // dedup by `${kind}\0${label}`
61
88
  const add = (c) => {
@@ -67,7 +94,7 @@ function collect(doc, offset, schema) {
67
94
  };
68
95
  // keywords — from candidate token types whose grammar literal is a word.
69
96
  for (const type of cand.tokens) {
70
- const label = keywordLabel(m, type);
97
+ const label = keywordLabel(meta.vocabulary, type);
71
98
  if (label)
72
99
  add({ label, kind: "keyword" });
73
100
  }
@@ -85,20 +112,34 @@ function collect(doc, offset, schema) {
85
112
  for (const c of visibleColumns(cellScopes, cellAst, dialect, cellOffset, schema))
86
113
  add(c);
87
114
  if (schema)
88
- for (const c of fromRelationColumns(m, cfg, schema, dialect))
115
+ for (const c of fromRelationColumns(walkTokens, cfg, schema, dialect, doc.templated?.tags, doc.text))
89
116
  add(c);
90
117
  }
91
118
  // functions — value/column slot: the dialect's inference-registry function names.
92
119
  if (atColumn) {
93
- for (const fn of Object.keys(inferDialect(dialect).functions))
120
+ for (const fn of Object.keys(resolveBehavior(dialect).functions))
94
121
  add({ label: fn, kind: "function" });
95
122
  }
96
123
  return out;
97
124
  }
125
+ /** The host's candidates for a jinja call slot, as completions. The template provider carries them,
126
+ * so this reads the `schema` when it is one (a DbtTemplateProvider IS a SchemaProvider, and the host
127
+ * already passes it here for column/table completion); the neutral provider offers none. A jinja slot
128
+ * with no candidates still returns [], never SQL keywords, so a caret inside a tag never leaks SQL
129
+ * completion. */
130
+ function templateCompletions(slot, schema) {
131
+ if (!(schema instanceof DefaultTemplateProvider))
132
+ return [];
133
+ return schema.templateCandidates(slot.callee, slot.argIndex, slot.packageName).map((c) => ({
134
+ label: c.label,
135
+ kind: "template",
136
+ ...(c.detail !== undefined ? { detail: c.detail } : {}),
137
+ }));
138
+ }
98
139
  /** The walk's caret token index: the first default-channel token whose `.start >= offset`; for an
99
- * end-of-input caret that is the EOF token's index. Mirrors Task 10's tests' caret helper. */
100
- function caretTokenIndex(m, offset) {
101
- const toks = m.tokenStream.getTokens();
140
+ * end-of-input caret that is the EOF sentinel's index (last entry). Mirrors Task 10's tests' caret
141
+ * helper. `toks` is the document's own token stream (doc coordinates) with the EOF sentinel appended. */
142
+ function caretTokenIndex(toks, offset) {
102
143
  for (let i = 0; i < toks.length; i++) {
103
144
  const t = toks[i];
104
145
  if (!t || t.channel !== Token.DEFAULT_CHANNEL)
@@ -111,8 +152,8 @@ function caretTokenIndex(m, offset) {
111
152
  /** A candidate token type → a keyword label, or undefined if it is punctuation/operator or has no
112
153
  * literal name. The grammar literal is single-quoted (`"'FROM'"`); strip the quotes and keep it
113
154
  * only when it starts with a letter/underscore. */
114
- function keywordLabel(m, type) {
115
- const literal = m.lexer.vocabulary.getLiteralName(type);
155
+ function keywordLabel(vocabulary, type) {
156
+ const literal = vocabulary.getLiteralName(type);
116
157
  if (!literal)
117
158
  return undefined;
118
159
  const unquoted = literal.startsWith("'") && literal.endsWith("'") ? literal.slice(1, -1) : literal;
@@ -127,33 +168,47 @@ function intersects(a, b) {
127
168
  return false;
128
169
  }
129
170
  /**
130
- * Broken-input FROM-relation fallback. The grammar reads a mid-edit `SELECT ‹caret› FROM t` as
171
+ * Broken-input FROM-relation fallback. The grammar reads a mid-edit `SELECT <caret> FROM t` as
131
172
  * `SELECT FROM AS t` (FROM is a non-reserved identifier in Spark), so the document's scope has no
132
173
  * `t` source and scope-based columns come back empty. To still offer the FROM relation's columns,
133
174
  * scan the token stream for `<relationKeyword> <name>` (FROM/JOIN followed by an identifier) and
134
175
  * surface those tables' schema columns. Token-driven, so it survives the mis-parse; gated by config
135
- * token sets, so the core stays dialect-neutral.
176
+ * token sets, so the core stays dialect-neutral. A `{{ ref('orders') }}` FROM source blanks to a
177
+ * placeholder identifier, so that name token is resolved through the template provider first (see
178
+ * `columnsForName`), then the same schema lookup a plain table gets.
136
179
  */
137
- function fromRelationColumns(m, cfg, schema, dialect) {
180
+ function fromRelationColumns(walkTokens, cfg, schema, dialect, tags, text) {
138
181
  if (cfg.relationKeywordTokens.size === 0)
139
182
  return [];
140
- // Default-channel tokens only — hidden whitespace/comments sit between FROM and the name.
141
- const toks = m.tokenStream.getTokens().filter((t) => t.channel === Token.DEFAULT_CHANNEL);
183
+ // Default-channel tokens only: hidden whitespace/comments sit between FROM and the name.
184
+ const toks = walkTokens.filter((t) => t.channel === Token.DEFAULT_CHANNEL);
142
185
  const out = [];
186
+ const emit = (cols) => {
187
+ if (cols)
188
+ for (const c of cols)
189
+ out.push({ label: c.name, kind: "column", detail: c.type });
190
+ };
143
191
  for (let i = 0; i + 1 < toks.length; i++) {
144
- const t = toks[i];
145
- const n = toks[i + 1];
146
- if (!t || !n)
192
+ const kw = toks[i];
193
+ const next = toks[i + 1];
194
+ if (!kw || !next)
147
195
  continue;
148
- if (!cfg.relationKeywordTokens.has(t.type))
196
+ if (!cfg.relationKeywordTokens.has(kw.type))
149
197
  continue;
150
- if (!cfg.nameTokens.has(n.type))
198
+ // A templated source ({{ ref('orders') }}) blanks to a channel-2 tag the walk skips, so it sits
199
+ // in the gap between the relation keyword and the next SQL token (the alias, or the next clause).
200
+ // Resolve it through the provider: relationOf(call) -> name, then its columns come from the
201
+ // relation answer or the same schema.columnsFor a plain table gets. A plain schema / the neutral
202
+ // provider resolves nothing for it, so it contributes no fabricated columns.
203
+ const tag = tags?.find((t) => t.kind === "call" && t.tagSpan.start >= kw.start && t.tagSpan.start < next.start);
204
+ if (tag && schema instanceof DefaultTemplateProvider) {
205
+ const rel = schema.relationOf(callOf(tag, text));
206
+ emit(rel ? (rel.columns ?? schema.columnsFor(rel.nameParts, dialect)) : undefined);
151
207
  continue;
152
- const cols = schema.columnsFor([n.text ?? ""], dialect);
153
- if (!cols)
154
- continue;
155
- for (const c of cols)
156
- out.push({ label: c.name, kind: "column", detail: c.type });
208
+ }
209
+ // Plain table: the next SQL token is the relation name.
210
+ if (cfg.nameTokens.has(next.type))
211
+ emit(schema.columnsFor([next.text ?? ""], dialect));
157
212
  }
158
213
  return out;
159
214
  }
@@ -164,16 +219,17 @@ function visibleColumns(scopes, ast, dialect, offset, schema) {
164
219
  const scope = enclosingScope(scopes, ast, offset);
165
220
  if (!scope)
166
221
  return [];
222
+ const behavior = resolveBehavior(dialect);
167
223
  const out = [];
168
224
  const seen = new Set();
169
225
  for (const src of scope.sources.values()) {
170
226
  for (const col of columnsOf(src, dialect, schema)) {
171
227
  // Dedup by folded IDENTITY (quoted/unquoted twins collapse); labels render via displayName.
172
- const key = foldIdentifier(col.label, dialect);
228
+ const key = behavior.fold(col.label);
173
229
  if (seen.has(key))
174
230
  continue;
175
231
  seen.add(key);
176
- out.push({ ...col, label: displayName(col.label, dialect) });
232
+ out.push({ ...col, label: behavior.displayName(col.label) });
177
233
  }
178
234
  }
179
235
  return out;
@@ -0,0 +1,24 @@
1
+ import type { TagNode } from "../minijinja/tag-ast.js";
2
+ /** Where the caret sits inside a jinja call tag. NEUTRAL, the callee is a bare string; the dbt
3
+ * meaning of the slot (ref arg0 = a model) is the consumer's to apply. */
4
+ export interface JinjaSlot {
5
+ /** The callee name, e.g. `"ref"`, `"source"`, `"my_macro"`. */
6
+ callee: string;
7
+ /** Dotted package before the callee (`dbt_utils` in `dbt_utils.star(...)`). */
8
+ packageName?: string;
9
+ /** 0-based index of the positional arg the caret is in. The callee-name slot (caret still in the
10
+ * callee identifier, `{{ my_mac|`) is `-1`. */
11
+ argIndex: number;
12
+ /** The already-typed text of this slot up to the caret, quote-stripped, the prefix a consumer
13
+ * filters its candidates by. Empty when the slot is untyped (`{{ ref(|`). */
14
+ prefix: string;
15
+ /** True when the slot is unclosed / mid-typing (an `incomplete` call node, or a bare callee with no
16
+ * open paren yet). */
17
+ incomplete: boolean;
18
+ }
19
+ /**
20
+ * The jinja completion slot at `offset`, or undefined when the caret is not in a completable jinja
21
+ * position. `tags` is `parseTemplated(...).tags` (or `doc.templated.tags`); `text` is the document
22
+ * source. Reuses the already-computed tags; never re-parses.
23
+ */
24
+ export declare function jinjaSlotAt(tags: readonly TagNode[], text: string, offset: number): JinjaSlot | undefined;
@@ -0,0 +1,126 @@
1
+ // ---------------------------------------------------------------------------
2
+ // jinjaSlotAt(), where the caret sits inside a jinja tag, for completion.
3
+ //
4
+ // The NEUTRAL half of jinja completion (anvil REQ1/REQ2): given the templated
5
+ // document's tags + the caret offset, it says which call the caret is in and which
6
+ // arg slot, `{{ ref('cu| }}` -> { callee: "ref", argIndex: 0, prefix: "cu" }. It
7
+ // carries NO dbt vocabulary: it does not know that `ref`'s arg 0 is a model. A
8
+ // consumer (a DbtTemplateProvider / the host) maps callee + argIndex to a role (ref
9
+ // arg0 -> a model name) and supplies the candidates, exactly the way the SQL side
10
+ // maps a grammar slot to a schema lookup.
11
+ //
12
+ // It finds the INNERMOST call covering the caret across every tag's call list, so a
13
+ // nested `outer(inner(|))` reports `inner`, and a call embedded in a control tag
14
+ // (`{% if is_incremental(| %}`) is found at all, not just a top-level `{{ call() }}`.
15
+ // A caret on a bare leading identifier with no open paren yet (`{{ re`, still typing
16
+ // the callee) is a callee-name slot too, read straight off the text.
17
+ //
18
+ // Reuses the parse: it reads the tags the document already produced, never re-parses.
19
+ // Total: returns undefined off any jinja completion slot; never throws.
20
+ // ---------------------------------------------------------------------------
21
+ /**
22
+ * The jinja completion slot at `offset`, or undefined when the caret is not in a completable jinja
23
+ * position. `tags` is `parseTemplated(...).tags` (or `doc.templated.tags`); `text` is the document
24
+ * source. Reuses the already-computed tags; never re-parses.
25
+ */
26
+ export function jinjaSlotAt(tags, text, offset) {
27
+ const hit = innermostCallAt(tags, offset);
28
+ if (hit)
29
+ return slotFromCall(hit, text, offset);
30
+ // No call covers the caret: a bare leading identifier being typed is still a callee-name slot.
31
+ return bareCalleeSlot(tags, text, offset);
32
+ }
33
+ /** The innermost call covering `offset`: the top-level call node of a `{{ call() }}` tag, plus every
34
+ * nested call (`calls[1..]`) and every call embedded in a `{% … %}` control tag (`calls[]`). The
35
+ * smallest covering extent wins, so `inner` beats `outer` and a control-tag call is reachable. */
36
+ function innermostCallAt(tags, offset) {
37
+ let best;
38
+ const consider = (h) => {
39
+ if (offset < h.start || offset > h.end)
40
+ return;
41
+ if (!best || h.end - h.start < best.end - best.start)
42
+ best = h;
43
+ };
44
+ for (const t of tags) {
45
+ if (t.kind === "call") {
46
+ // The node's own top-level call carries the `incomplete` flag; its tag span is the extent so a
47
+ // caret in the tag's leading whitespace still resolves to it. `calls[0]` duplicates this
48
+ // top-level, so nested calls are `calls[1..]`.
49
+ consider({
50
+ name: t.name,
51
+ nameSpan: t.nameSpan,
52
+ ...(t.packageName !== undefined ? { packageName: t.packageName } : {}),
53
+ ...(t.argsSpan ? { argsSpan: t.argsSpan } : {}),
54
+ args: t.args,
55
+ incomplete: t.incomplete === true,
56
+ start: t.tagSpan.start,
57
+ end: t.tagSpan.end,
58
+ });
59
+ for (const c of t.calls.slice(1))
60
+ consider(macroHit(c));
61
+ }
62
+ else if (t.kind === "control") {
63
+ for (const c of t.calls)
64
+ consider(macroHit(c));
65
+ }
66
+ }
67
+ return best;
68
+ }
69
+ /** A nested / control-embedded MacroCall as a CallHit: its extent is the callee (with any package)
70
+ * through the close paren, so it is tighter than the enclosing tag and wins the innermost pick. */
71
+ function macroHit(c) {
72
+ return {
73
+ name: c.name,
74
+ nameSpan: c.nameSpan,
75
+ ...(c.packageName !== undefined ? { packageName: c.packageName } : {}),
76
+ ...(c.argsSpan ? { argsSpan: c.argsSpan } : {}),
77
+ args: c.args,
78
+ incomplete: false,
79
+ start: c.packageSpan?.start ?? c.nameSpan.start,
80
+ end: c.argsSpan?.end ?? c.nameSpan.end,
81
+ };
82
+ }
83
+ /** The slot for a caret inside a resolved call: the callee name, or the positional argument. */
84
+ function slotFromCall(c, text, offset) {
85
+ const base = { callee: c.name, ...(c.packageName !== undefined ? { packageName: c.packageName } : {}) };
86
+ // Callee-name slot: the caret is still within (or right at the end of) the callee identifier,
87
+ // before the open paren, the user is typing the macro name itself.
88
+ if (offset <= c.nameSpan.end) {
89
+ return { ...base, argIndex: -1, prefix: text.slice(c.nameSpan.start, offset), incomplete: c.incomplete };
90
+ }
91
+ // Between the name and the open paren (e.g. whitespace) is no completable slot.
92
+ const parenStart = c.argsSpan?.start ?? Number.MAX_SAFE_INTEGER;
93
+ if (offset < parenStart)
94
+ return undefined;
95
+ // Inside the arguments. The arg whose span covers the caret; else the caret sits in a gap (after
96
+ // the open paren or a comma), so the slot is the next arg being typed = the count of args that
97
+ // already ended before the caret.
98
+ const inArg = c.args.findIndex((a) => offset >= a.span.start && offset <= a.span.end);
99
+ if (inArg >= 0) {
100
+ return { ...base, argIndex: inArg, prefix: stripQuote(text.slice(c.args[inArg].span.start, offset)), incomplete: c.incomplete };
101
+ }
102
+ const argIndex = c.args.filter((a) => a.span.end <= offset).length;
103
+ return { ...base, argIndex, prefix: "", incomplete: c.incomplete };
104
+ }
105
+ /** A bare leading identifier being typed in a `{{ }}` expression (`{{ re`, `{{ region`) as a
106
+ * callee-name slot. The `other` tag drops the identifier, so read it off the text: only an
107
+ * expression tag (opens `{{`), and only when the caret sits on a single leading identifier (nothing
108
+ * but whitespace before it, no member access or operators). So a callee being typed toward a call
109
+ * and a bare variable both offer the host's callee candidates, filtered by the prefix. */
110
+ function bareCalleeSlot(tags, text, offset) {
111
+ const tag = tags.find((t) => t.kind === "other" && offset > t.tagSpan.start && offset <= t.tagSpan.end);
112
+ if (!tag)
113
+ return undefined;
114
+ if (text.slice(tag.tagSpan.start, tag.tagSpan.start + 2) !== "{{")
115
+ return undefined;
116
+ const before = text.slice(tag.tagSpan.start + 2, offset);
117
+ const m = /^\s*([A-Za-z_]\w*)$/.exec(before);
118
+ if (!m)
119
+ return undefined;
120
+ return { callee: m[1], argIndex: -1, prefix: m[1], incomplete: true };
121
+ }
122
+ /** Drop a single leading quote from a partial string arg (`'cu` -> `cu`) so the prefix is the value
123
+ * the consumer filters by. Leaves a non-string arg untouched. */
124
+ function stripQuote(raw) {
125
+ return raw.replace(/^['"]/, "");
126
+ }
@@ -1,4 +1,4 @@
1
- import { CommonTokenStream, type Lexer, type Parser, type ParserRuleContext } from "antlr4ng";
1
+ import { type ATN, CommonTokenStream, type Lexer, type Parser, type ParserRuleContext, type Vocabulary } from "antlr4ng";
2
2
  import type { Dialect } from "../dialect.js";
3
3
  /**
4
4
  * A ready-to-walk parser for the completion engine: the lexer, the token stream, the entry
@@ -21,3 +21,15 @@ export interface MadeParser {
21
21
  }
22
22
  /** Build a fresh error-tolerant parser for `dialect`, lexing `sql`. */
23
23
  export declare function makeParser(sql: string, dialect: Dialect): MadeParser;
24
+ /** The input-INDEPENDENT parser facts the ATN candidate walk needs: the dialect's parser ATN, the
25
+ * lexer vocabulary (for keyword literal labels), and the batch entry rule's index. All three are
26
+ * per-dialect statics (the ATN and vocabulary are shared across every parser/lexer instance), so
27
+ * they are grabbed once from a throwaway empty-input factory and reused; no source is re-lexed. */
28
+ export interface CompletionMeta {
29
+ atn: ATN;
30
+ vocabulary: Vocabulary;
31
+ entryRuleIndex: number;
32
+ }
33
+ /** The cached {@link CompletionMeta} for `dialect`, built once. Completion drives the walk over the
34
+ * document's own token stream plus this meta, instead of re-parsing the source text. */
35
+ export declare function completionMeta(dialect: Dialect): CompletionMeta;
@@ -191,3 +191,15 @@ const FACTORIES = {
191
191
  export function makeParser(sql, dialect) {
192
192
  return FACTORIES[dialect](sql);
193
193
  }
194
+ const META_CACHE = new Map();
195
+ /** The cached {@link CompletionMeta} for `dialect`, built once. Completion drives the walk over the
196
+ * document's own token stream plus this meta, instead of re-parsing the source text. */
197
+ export function completionMeta(dialect) {
198
+ let meta = META_CACHE.get(dialect);
199
+ if (!meta) {
200
+ const m = makeParser("", dialect);
201
+ meta = { atn: m.parser.atn, vocabulary: m.lexer.vocabulary, entryRuleIndex: m.entryRuleIndex };
202
+ META_CACHE.set(dialect, meta);
203
+ }
204
+ return meta;
205
+ }
@@ -0,0 +1,2 @@
1
+ import type { DialectBehavior } from "../dialect-behavior/behavior.js";
2
+ export declare const databricksBehavior: DialectBehavior;
@@ -0,0 +1,19 @@
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 { databricksLiteral, databricksParseType, DATABRICKS_FUNCTION_RETURNS } from "./infer.js";
6
+ export const databricksBehavior = {
7
+ fold,
8
+ displayName,
9
+ foldTableName,
10
+ matchesSourceKey,
11
+ likeMatch: (pattern, value) => likePatternToRegExp(pattern).test(value),
12
+ literal: databricksLiteral,
13
+ parseType: databricksParseType,
14
+ functions: DATABRICKS_FUNCTION_RETURNS,
15
+ division: "float",
16
+ signatures: SIGNATURES.databricks,
17
+ // Databricks implicit coercion: STRING containing a number coerces to numeric (STR_TO_NUM=true), no bool<->num (BOOL_NUM=false).
18
+ accepts: (argType, paramText) => acceptsFor(databricksParseType, true, false, argType, paramText),
19
+ };
@@ -0,0 +1,8 @@
1
+ import { type FoldRule, type IdentKind } from "../ident/fold.js";
2
+ export declare const DATABRICKS_FOLD_RULE: FoldRule;
3
+ /** Fold an identifier to its Databricks 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,27 @@
1
+ // Databricks 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
+ // docs.databricks.com/en/sql/language-manual/sql-ref-identifiers.html — verified live:
5
+ // "Identifiers are case-insensitive when referenced." Backtick escaping is doubling, not
6
+ // case-quoting: "Use ` to escape ` itself" (example: `` `a``b` `` → `` a`b ``).
7
+ import { displayWith, foldWith } from "../ident/fold.js";
8
+ const BACKTICK = ["`", "`"];
9
+ export const DATABRICKS_FOLD_RULE = {
10
+ delimiters: [BACKTICK],
11
+ unquoted: "lower",
12
+ quoted: "lower",
13
+ };
14
+ /** Fold an identifier to its Databricks identity key. */
15
+ export function fold(raw, kind = "other") {
16
+ return foldWith(DATABRICKS_FOLD_RULE, raw, kind);
17
+ }
18
+ /** Presentation twin: strip delimiters, no case change. */
19
+ export function displayName(raw) {
20
+ return displayWith(DATABRICKS_FOLD_RULE, raw);
21
+ }
22
+ export function foldTableName(parts) {
23
+ return parts.map((p) => fold(p, "table"));
24
+ }
25
+ export function matchesSourceKey(key, rawPart) {
26
+ return key === fold(rawPart) || key === fold(rawPart, "table");
27
+ }
@@ -0,0 +1,7 @@
1
+ import { lower } from "./lower.js";
2
+ import { parseDatabricks } from "./parse.js";
3
+ export declare const databricks: {
4
+ parse: typeof parseDatabricks;
5
+ lower: typeof lower;
6
+ behavior: import("../dialect-behavior/behavior.js").DialectBehavior;
7
+ };
@@ -0,0 +1,10 @@
1
+ // The complete databricks dialect module: parse + lower (front end) and behavior (semantic knowledge).
2
+ // The registry wires this; to understand everything sqllens does for databricks, read this folder.
3
+ import { databricksBehavior } from "./behavior.js";
4
+ import { lower } from "./lower.js";
5
+ import { parseDatabricks } from "./parse.js";
6
+ export const databricks = {
7
+ parse: parseDatabricks,
8
+ lower,
9
+ behavior: databricksBehavior,
10
+ };
@@ -0,0 +1,6 @@
1
+ import { type FnRule } from "../infer/functions.js";
2
+ import { type Type } from "../infer/types.js";
3
+ export declare const DATABRICKS_FUNCTION_RETURNS: Record<string, FnRule>;
4
+ /** Databricks/Spark literal forms. */
5
+ export declare function databricksLiteral(text: string): Type;
6
+ export declare function databricksParseType(text: string): Type;