@questdb/sql-parser 0.1.3 → 0.1.5

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.
package/CHANGELOG.md CHANGED
@@ -1,6 +1,20 @@
1
1
  # Changelog
2
2
 
3
3
 
4
+ ## 0.1.5 - 2026.03.18
5
+ ### Fixed
6
+ boost expression operators over clause keywords in WHERE context [#15](https://github.com/questdb/sql-parser/pull/15)
7
+
8
+
9
+ ## 0.1.4 - 2026.03.17
10
+ ### Added
11
+ - Compound JOIN suggestions: suggest "LEFT JOIN", "ASOF JOIN" etc. as single completions instead of bare keywords [#13](https://github.com/questdb/sql-parser/pull/13)
12
+ - CTE grammar: extract `selectBody` rule so DECLARE/WITH are not suggested after WITH clause [#13](https://github.com/questdb/sql-parser/pull/13)
13
+
14
+ ### Fixed
15
+ - Table suggestion ranking: tables no longer interleave with columns in autocomplete [#13](https://github.com/questdb/sql-parser/pull/13)
16
+
17
+
4
18
  ## 0.1.3 - 2026.03.04
5
19
  ### Added
6
20
  - horizon join support [#9](https://github.com/questdb/sql-parser/pull/9)
@@ -42,6 +42,8 @@ export interface ContentAssistResult {
42
42
  * context. Used by the provider to boost tables containing all these columns.
43
43
  */
44
44
  referencedColumns: Set<string>;
45
+ /** Whether the cursor is inside a WHERE clause expression */
46
+ isConditionContext: boolean;
45
47
  }
46
48
  /**
47
49
  * Extract bare column names referenced in expression context from a token list.
@@ -13,9 +13,7 @@ export declare const IDENTIFIER_TOKENS: Set<string>;
13
13
  */
14
14
  export declare const IDENTIFIER_KEYWORD_TOKENS: Set<string>;
15
15
  /**
16
- * Expression-continuation operators that are valid after any expression but
17
- * should be deprioritized so clause-level keywords (ASC, DESC, LIMIT, etc.)
18
- * appear first in the suggestion list.
16
+ * Expression-level operators and keywords (as opposed to clause-level keywords).
19
17
  */
20
18
  export declare const EXPRESSION_OPERATORS: Set<string>;
21
19
  /**
package/dist/index.cjs CHANGED
@@ -1275,9 +1275,8 @@ var QuestDBParser = class extends import_chevrotain3.CstParser {
1275
1275
  ALT: () => this.SUBRULE(this.updateStatement)
1276
1276
  },
1277
1277
  {
1278
- // SELECT: delegate to selectStatement (its optional declareClause/
1279
- // withClause simply won't match since WITH was already consumed)
1280
- ALT: () => this.SUBRULE(this.selectStatement)
1278
+ // SELECT after WITH: no DECLARE/WITH prefixes allowed here
1279
+ ALT: () => this.SUBRULE(this.selectBody)
1281
1280
  }
1282
1281
  ]);
1283
1282
  });
@@ -1287,6 +1286,9 @@ var QuestDBParser = class extends import_chevrotain3.CstParser {
1287
1286
  this.selectStatement = this.RULE("selectStatement", () => {
1288
1287
  this.OPTION(() => this.SUBRULE(this.declareClause));
1289
1288
  this.OPTION2(() => this.SUBRULE(this.withClause));
1289
+ this.SUBRULE(this.selectBody);
1290
+ });
1291
+ this.selectBody = this.RULE("selectBody", () => {
1290
1292
  this.SUBRULE(this.simpleSelect);
1291
1293
  this.MANY(() => {
1292
1294
  this.SUBRULE(this.setOperation);
@@ -1655,17 +1657,13 @@ var QuestDBParser = class extends import_chevrotain3.CstParser {
1655
1657
  { ALT: () => this.CONSUME(NumberLiteral) }
1656
1658
  ]);
1657
1659
  });
1658
- // Standard joins: (INNER | LEFT [OUTER] | RIGHT [OUTER] | FULL [OUTER] | CROSS)? JOIN + ON
1660
+ // Standard joins: (INNER | LEFT [OUTER] | CROSS)? JOIN + ON
1659
1661
  this.standardJoin = this.RULE("standardJoin", () => {
1660
1662
  this.OPTION(() => {
1661
1663
  this.OR([
1662
1664
  {
1663
1665
  ALT: () => {
1664
- this.OR1([
1665
- { ALT: () => this.CONSUME(Left) },
1666
- { ALT: () => this.CONSUME(Right) },
1667
- { ALT: () => this.CONSUME(Full) }
1668
- ]);
1666
+ this.CONSUME(Left);
1669
1667
  this.OPTION1(() => this.CONSUME(Outer));
1670
1668
  }
1671
1669
  },
@@ -4796,8 +4794,8 @@ var QuestDBVisitor = class extends BaseVisitor {
4796
4794
  inner = this.visit(ctx.insertStatement);
4797
4795
  } else if (ctx.updateStatement) {
4798
4796
  inner = this.visit(ctx.updateStatement);
4799
- } else if (ctx.selectStatement) {
4800
- inner = this.visit(ctx.selectStatement);
4797
+ } else if (ctx.selectBody) {
4798
+ inner = this.visit(ctx.selectBody);
4801
4799
  } else {
4802
4800
  throw new Error("withStatement: expected insert, update, or select");
4803
4801
  }
@@ -4808,13 +4806,17 @@ var QuestDBVisitor = class extends BaseVisitor {
4808
4806
  // SELECT Statement
4809
4807
  // ==========================================================================
4810
4808
  selectStatement(ctx) {
4811
- const result = this.visit(ctx.simpleSelect);
4809
+ const result = this.visit(ctx.selectBody);
4812
4810
  if (ctx.declareClause) {
4813
4811
  result.declare = this.visit(ctx.declareClause);
4814
4812
  }
4815
4813
  if (ctx.withClause) {
4816
4814
  result.with = this.visit(ctx.withClause);
4817
4815
  }
4816
+ return result;
4817
+ }
4818
+ selectBody(ctx) {
4819
+ const result = this.visit(ctx.simpleSelect);
4818
4820
  if (ctx.setOperation && ctx.setOperation.length > 0) {
4819
4821
  result.setOperations = ctx.setOperation.map(
4820
4822
  (op) => this.visit(op)
@@ -5160,8 +5162,6 @@ var QuestDBVisitor = class extends BaseVisitor {
5160
5162
  };
5161
5163
  if (ctx.Inner) result.joinType = "inner";
5162
5164
  else if (ctx.Left) result.joinType = "left";
5163
- else if (ctx.Right) result.joinType = "right";
5164
- else if (ctx.Full) result.joinType = "full";
5165
5165
  else if (ctx.Cross) result.joinType = "cross";
5166
5166
  if (ctx.Outer) result.outer = true;
5167
5167
  if (ctx.expression) {
@@ -9210,18 +9210,11 @@ var EXPRESSION_OPERATORS = /* @__PURE__ */ new Set([
9210
9210
  "Like",
9211
9211
  "Ilike",
9212
9212
  "Within",
9213
- // Subquery/set operators
9214
9213
  "All",
9215
9214
  "Any",
9216
9215
  "Some",
9217
- // Expression-start keywords that continue an expression context
9218
9216
  "Case",
9219
- "Cast",
9220
- // Query connectors — valid after any complete query but should not
9221
- // overshadow clause-level keywords the user is more likely typing.
9222
- "Union",
9223
- "Except",
9224
- "Intersect"
9217
+ "Cast"
9225
9218
  ]);
9226
9219
  var PUNCTUATION_TOKENS = /* @__PURE__ */ new Set([
9227
9220
  "LParen",
@@ -9742,7 +9735,15 @@ function computeSuggestions(tokens) {
9742
9735
  }
9743
9736
  }
9744
9737
  }
9745
- return { nextTokenTypes: result, suggestColumns, suggestTables };
9738
+ const isConditionContext = effectiveSuggestions.some(
9739
+ (s) => EXPRESSION_OPERATORS.has(s.nextTokenType.name) && s.ruleStack.includes("whereClause")
9740
+ );
9741
+ return {
9742
+ nextTokenTypes: result,
9743
+ suggestColumns,
9744
+ suggestTables,
9745
+ isConditionContext
9746
+ };
9746
9747
  }
9747
9748
  function inferTableFromQualifiedRef(tokensBefore, isMidWord) {
9748
9749
  const effective = isMidWord ? tokensBefore.slice(0, -1) : tokensBefore;
@@ -9797,7 +9798,8 @@ function getContentAssist(fullSql, cursorOffset) {
9797
9798
  lexErrors: [],
9798
9799
  suggestColumns: false,
9799
9800
  suggestTables: false,
9800
- referencedColumns: /* @__PURE__ */ new Set()
9801
+ referencedColumns: /* @__PURE__ */ new Set(),
9802
+ isConditionContext: false
9801
9803
  };
9802
9804
  }
9803
9805
  }
@@ -9810,11 +9812,13 @@ function getContentAssist(fullSql, cursorOffset) {
9810
9812
  let nextTokenTypes = [];
9811
9813
  let suggestColumns = false;
9812
9814
  let suggestTables = false;
9815
+ let isConditionContext = false;
9813
9816
  try {
9814
9817
  const computed = computeSuggestions(tokensForAssist);
9815
9818
  nextTokenTypes = computed.nextTokenTypes;
9816
9819
  suggestColumns = computed.suggestColumns;
9817
9820
  suggestTables = computed.suggestTables;
9821
+ isConditionContext = computed.isConditionContext;
9818
9822
  } catch (e) {
9819
9823
  }
9820
9824
  const { tables: tablesInScope, cteColumns } = extractTables(
@@ -9868,7 +9872,8 @@ function getContentAssist(fullSql, cursorOffset) {
9868
9872
  qualifiedTableRef: qualifiedRef?.table,
9869
9873
  suggestColumns,
9870
9874
  suggestTables,
9871
- referencedColumns
9875
+ referencedColumns,
9876
+ isConditionContext
9872
9877
  };
9873
9878
  }
9874
9879
  function getNextValidTokens(sql) {
@@ -10299,6 +10304,17 @@ function getAllColumns(schema) {
10299
10304
  }
10300
10305
  return columns;
10301
10306
  }
10307
+ var JOIN_COMPOUND_MAP = /* @__PURE__ */ new Map([
10308
+ ["Left", "LEFT JOIN"],
10309
+ ["Inner", "INNER JOIN"],
10310
+ ["Cross", "CROSS JOIN"],
10311
+ ["Asof", "ASOF JOIN"],
10312
+ ["Lt", "LT JOIN"],
10313
+ ["Splice", "SPLICE JOIN"],
10314
+ ["Window", "WINDOW JOIN"],
10315
+ ["Horizon", "HORIZON JOIN"],
10316
+ ["Outer", "OUTER JOIN"]
10317
+ ]);
10302
10318
  function buildSuggestions(tokenTypes, schema, tablesInScope, options) {
10303
10319
  const suggestions = [];
10304
10320
  const seenKeywords = /* @__PURE__ */ new Set();
@@ -10306,6 +10322,7 @@ function buildSuggestions(tokenTypes, schema, tablesInScope, options) {
10306
10322
  const includeColumns = options?.includeColumns ?? true;
10307
10323
  const includeTables = options?.includeTables ?? true;
10308
10324
  const isMidWord = options?.isMidWord ?? false;
10325
+ const isJoinContext = tokenTypes.some((t) => t.name === "Join");
10309
10326
  for (const tokenType of tokenTypes) {
10310
10327
  const name = tokenType.name;
10311
10328
  if (SKIP_TOKENS.has(name)) {
@@ -10318,19 +10335,31 @@ function buildSuggestions(tokenTypes, schema, tablesInScope, options) {
10318
10335
  if (name === "Identifier" || name === "QuotedIdentifier") {
10319
10336
  continue;
10320
10337
  }
10338
+ if (isJoinContext && JOIN_COMPOUND_MAP.has(name)) {
10339
+ const compound = JOIN_COMPOUND_MAP.get(name);
10340
+ if (seenKeywords.has(compound)) continue;
10341
+ seenKeywords.add(compound);
10342
+ suggestions.push({
10343
+ label: compound,
10344
+ kind: "keyword" /* Keyword */,
10345
+ insertText: compound,
10346
+ filterText: compound.toLowerCase(),
10347
+ priority: 2 /* Medium */
10348
+ });
10349
+ continue;
10350
+ }
10321
10351
  const keyword = tokenNameToKeyword(name);
10322
10352
  if (seenKeywords.has(keyword)) {
10323
10353
  continue;
10324
10354
  }
10325
10355
  seenKeywords.add(keyword);
10326
10356
  const kind = "keyword" /* Keyword */;
10327
- const priority = EXPRESSION_OPERATORS.has(name) ? 3 /* MediumLow */ : 2 /* Medium */;
10328
10357
  suggestions.push({
10329
10358
  label: keyword,
10330
10359
  kind,
10331
10360
  insertText: keyword,
10332
10361
  filterText: keyword.toLowerCase(),
10333
- priority
10362
+ priority: 2 /* Medium */
10334
10363
  });
10335
10364
  }
10336
10365
  if (expectsIdentifier) {
@@ -10440,6 +10469,20 @@ function buildSuggestions(tokenTypes, schema, tablesInScope, options) {
10440
10469
  }
10441
10470
 
10442
10471
  // src/autocomplete/provider.ts
10472
+ var EXPRESSION_OPERATOR_LABELS = new Set(
10473
+ [...EXPRESSION_OPERATORS].map(tokenNameToKeyword)
10474
+ );
10475
+ function isSchemaColumn(image, tablesInScope, schema) {
10476
+ const nameLower = image.toLowerCase();
10477
+ for (const ref of tablesInScope) {
10478
+ const cols = schema.columns[ref.table.toLowerCase()];
10479
+ if (cols?.some((c) => c.name.toLowerCase() === nameLower)) return true;
10480
+ }
10481
+ for (const cols of Object.values(schema.columns)) {
10482
+ if (cols.some((c) => c.name.toLowerCase() === nameLower)) return true;
10483
+ }
10484
+ return false;
10485
+ }
10443
10486
  var TABLE_NAME_TOKENS = /* @__PURE__ */ new Set([
10444
10487
  "From",
10445
10488
  "Join",
@@ -10478,7 +10521,7 @@ function rankTableSuggestions(suggestions, referencedColumns, columnIndex) {
10478
10521
  if (s.kind !== "table" /* Table */) continue;
10479
10522
  const score = scores.get(s.label.toLowerCase());
10480
10523
  if (score === void 0) continue;
10481
- s.priority = score === referencedColumns.size ? 1 /* High */ : 2 /* Medium */;
10524
+ s.priority = score === referencedColumns.size ? 2 /* Medium */ : 3 /* MediumLow */;
10482
10525
  }
10483
10526
  }
10484
10527
  function getLastSignificantTokens(tokens) {
@@ -10516,7 +10559,8 @@ function createAutocompleteProvider(schema) {
10516
10559
  qualifiedTableRef,
10517
10560
  suggestColumns,
10518
10561
  suggestTables,
10519
- referencedColumns
10562
+ referencedColumns,
10563
+ isConditionContext
10520
10564
  } = getContentAssist(query, cursorOffset);
10521
10565
  const effectiveSchema = Object.keys(cteColumns).length > 0 ? {
10522
10566
  ...normalizedSchema,
@@ -10557,6 +10601,30 @@ function createAutocompleteProvider(schema) {
10557
10601
  if (suggestTables) {
10558
10602
  rankTableSuggestions(suggestions, referencedColumns, columnIndex);
10559
10603
  }
10604
+ if (isConditionContext) {
10605
+ const hasExpressionOperators = nextTokenTypes.some(
10606
+ (t) => EXPRESSION_OPERATORS.has(t.name)
10607
+ );
10608
+ if (hasExpressionOperators) {
10609
+ const effectiveTokens = isMidWord && tokensBefore.length > 0 ? tokensBefore.slice(0, -1) : tokensBefore;
10610
+ const lastToken = effectiveTokens[effectiveTokens.length - 1];
10611
+ const lastTokenName = lastToken?.tokenType?.name;
10612
+ if (lastTokenName && !SKIP_TOKENS.has(lastTokenName) && !PUNCTUATION_TOKENS.has(lastTokenName) && isSchemaColumn(
10613
+ lastToken.image,
10614
+ effectiveTablesInScope,
10615
+ effectiveSchema
10616
+ )) {
10617
+ for (const s of suggestions) {
10618
+ if (s.kind !== "keyword" /* Keyword */) continue;
10619
+ if (s.label === "IN") {
10620
+ s.priority = 1 /* High */;
10621
+ } else if (!EXPRESSION_OPERATOR_LABELS.has(s.label)) {
10622
+ s.priority = 3 /* MediumLow */;
10623
+ }
10624
+ }
10625
+ }
10626
+ }
10627
+ }
10560
10628
  return suggestions;
10561
10629
  }
10562
10630
  const fallbackTokens = isMidWord && tokensBefore.length > 0 ? tokensBefore.slice(0, -1) : tokensBefore;
package/dist/index.js CHANGED
@@ -1215,9 +1215,8 @@ var QuestDBParser = class extends CstParser {
1215
1215
  ALT: () => this.SUBRULE(this.updateStatement)
1216
1216
  },
1217
1217
  {
1218
- // SELECT: delegate to selectStatement (its optional declareClause/
1219
- // withClause simply won't match since WITH was already consumed)
1220
- ALT: () => this.SUBRULE(this.selectStatement)
1218
+ // SELECT after WITH: no DECLARE/WITH prefixes allowed here
1219
+ ALT: () => this.SUBRULE(this.selectBody)
1221
1220
  }
1222
1221
  ]);
1223
1222
  });
@@ -1227,6 +1226,9 @@ var QuestDBParser = class extends CstParser {
1227
1226
  this.selectStatement = this.RULE("selectStatement", () => {
1228
1227
  this.OPTION(() => this.SUBRULE(this.declareClause));
1229
1228
  this.OPTION2(() => this.SUBRULE(this.withClause));
1229
+ this.SUBRULE(this.selectBody);
1230
+ });
1231
+ this.selectBody = this.RULE("selectBody", () => {
1230
1232
  this.SUBRULE(this.simpleSelect);
1231
1233
  this.MANY(() => {
1232
1234
  this.SUBRULE(this.setOperation);
@@ -1595,17 +1597,13 @@ var QuestDBParser = class extends CstParser {
1595
1597
  { ALT: () => this.CONSUME(NumberLiteral) }
1596
1598
  ]);
1597
1599
  });
1598
- // Standard joins: (INNER | LEFT [OUTER] | RIGHT [OUTER] | FULL [OUTER] | CROSS)? JOIN + ON
1600
+ // Standard joins: (INNER | LEFT [OUTER] | CROSS)? JOIN + ON
1599
1601
  this.standardJoin = this.RULE("standardJoin", () => {
1600
1602
  this.OPTION(() => {
1601
1603
  this.OR([
1602
1604
  {
1603
1605
  ALT: () => {
1604
- this.OR1([
1605
- { ALT: () => this.CONSUME(Left) },
1606
- { ALT: () => this.CONSUME(Right) },
1607
- { ALT: () => this.CONSUME(Full) }
1608
- ]);
1606
+ this.CONSUME(Left);
1609
1607
  this.OPTION1(() => this.CONSUME(Outer));
1610
1608
  }
1611
1609
  },
@@ -4736,8 +4734,8 @@ var QuestDBVisitor = class extends BaseVisitor {
4736
4734
  inner = this.visit(ctx.insertStatement);
4737
4735
  } else if (ctx.updateStatement) {
4738
4736
  inner = this.visit(ctx.updateStatement);
4739
- } else if (ctx.selectStatement) {
4740
- inner = this.visit(ctx.selectStatement);
4737
+ } else if (ctx.selectBody) {
4738
+ inner = this.visit(ctx.selectBody);
4741
4739
  } else {
4742
4740
  throw new Error("withStatement: expected insert, update, or select");
4743
4741
  }
@@ -4748,13 +4746,17 @@ var QuestDBVisitor = class extends BaseVisitor {
4748
4746
  // SELECT Statement
4749
4747
  // ==========================================================================
4750
4748
  selectStatement(ctx) {
4751
- const result = this.visit(ctx.simpleSelect);
4749
+ const result = this.visit(ctx.selectBody);
4752
4750
  if (ctx.declareClause) {
4753
4751
  result.declare = this.visit(ctx.declareClause);
4754
4752
  }
4755
4753
  if (ctx.withClause) {
4756
4754
  result.with = this.visit(ctx.withClause);
4757
4755
  }
4756
+ return result;
4757
+ }
4758
+ selectBody(ctx) {
4759
+ const result = this.visit(ctx.simpleSelect);
4758
4760
  if (ctx.setOperation && ctx.setOperation.length > 0) {
4759
4761
  result.setOperations = ctx.setOperation.map(
4760
4762
  (op) => this.visit(op)
@@ -5100,8 +5102,6 @@ var QuestDBVisitor = class extends BaseVisitor {
5100
5102
  };
5101
5103
  if (ctx.Inner) result.joinType = "inner";
5102
5104
  else if (ctx.Left) result.joinType = "left";
5103
- else if (ctx.Right) result.joinType = "right";
5104
- else if (ctx.Full) result.joinType = "full";
5105
5105
  else if (ctx.Cross) result.joinType = "cross";
5106
5106
  if (ctx.Outer) result.outer = true;
5107
5107
  if (ctx.expression) {
@@ -9150,18 +9150,11 @@ var EXPRESSION_OPERATORS = /* @__PURE__ */ new Set([
9150
9150
  "Like",
9151
9151
  "Ilike",
9152
9152
  "Within",
9153
- // Subquery/set operators
9154
9153
  "All",
9155
9154
  "Any",
9156
9155
  "Some",
9157
- // Expression-start keywords that continue an expression context
9158
9156
  "Case",
9159
- "Cast",
9160
- // Query connectors — valid after any complete query but should not
9161
- // overshadow clause-level keywords the user is more likely typing.
9162
- "Union",
9163
- "Except",
9164
- "Intersect"
9157
+ "Cast"
9165
9158
  ]);
9166
9159
  var PUNCTUATION_TOKENS = /* @__PURE__ */ new Set([
9167
9160
  "LParen",
@@ -9682,7 +9675,15 @@ function computeSuggestions(tokens) {
9682
9675
  }
9683
9676
  }
9684
9677
  }
9685
- return { nextTokenTypes: result, suggestColumns, suggestTables };
9678
+ const isConditionContext = effectiveSuggestions.some(
9679
+ (s) => EXPRESSION_OPERATORS.has(s.nextTokenType.name) && s.ruleStack.includes("whereClause")
9680
+ );
9681
+ return {
9682
+ nextTokenTypes: result,
9683
+ suggestColumns,
9684
+ suggestTables,
9685
+ isConditionContext
9686
+ };
9686
9687
  }
9687
9688
  function inferTableFromQualifiedRef(tokensBefore, isMidWord) {
9688
9689
  const effective = isMidWord ? tokensBefore.slice(0, -1) : tokensBefore;
@@ -9737,7 +9738,8 @@ function getContentAssist(fullSql, cursorOffset) {
9737
9738
  lexErrors: [],
9738
9739
  suggestColumns: false,
9739
9740
  suggestTables: false,
9740
- referencedColumns: /* @__PURE__ */ new Set()
9741
+ referencedColumns: /* @__PURE__ */ new Set(),
9742
+ isConditionContext: false
9741
9743
  };
9742
9744
  }
9743
9745
  }
@@ -9750,11 +9752,13 @@ function getContentAssist(fullSql, cursorOffset) {
9750
9752
  let nextTokenTypes = [];
9751
9753
  let suggestColumns = false;
9752
9754
  let suggestTables = false;
9755
+ let isConditionContext = false;
9753
9756
  try {
9754
9757
  const computed = computeSuggestions(tokensForAssist);
9755
9758
  nextTokenTypes = computed.nextTokenTypes;
9756
9759
  suggestColumns = computed.suggestColumns;
9757
9760
  suggestTables = computed.suggestTables;
9761
+ isConditionContext = computed.isConditionContext;
9758
9762
  } catch (e) {
9759
9763
  }
9760
9764
  const { tables: tablesInScope, cteColumns } = extractTables(
@@ -9808,7 +9812,8 @@ function getContentAssist(fullSql, cursorOffset) {
9808
9812
  qualifiedTableRef: qualifiedRef?.table,
9809
9813
  suggestColumns,
9810
9814
  suggestTables,
9811
- referencedColumns
9815
+ referencedColumns,
9816
+ isConditionContext
9812
9817
  };
9813
9818
  }
9814
9819
  function getNextValidTokens(sql) {
@@ -10239,6 +10244,17 @@ function getAllColumns(schema) {
10239
10244
  }
10240
10245
  return columns;
10241
10246
  }
10247
+ var JOIN_COMPOUND_MAP = /* @__PURE__ */ new Map([
10248
+ ["Left", "LEFT JOIN"],
10249
+ ["Inner", "INNER JOIN"],
10250
+ ["Cross", "CROSS JOIN"],
10251
+ ["Asof", "ASOF JOIN"],
10252
+ ["Lt", "LT JOIN"],
10253
+ ["Splice", "SPLICE JOIN"],
10254
+ ["Window", "WINDOW JOIN"],
10255
+ ["Horizon", "HORIZON JOIN"],
10256
+ ["Outer", "OUTER JOIN"]
10257
+ ]);
10242
10258
  function buildSuggestions(tokenTypes, schema, tablesInScope, options) {
10243
10259
  const suggestions = [];
10244
10260
  const seenKeywords = /* @__PURE__ */ new Set();
@@ -10246,6 +10262,7 @@ function buildSuggestions(tokenTypes, schema, tablesInScope, options) {
10246
10262
  const includeColumns = options?.includeColumns ?? true;
10247
10263
  const includeTables = options?.includeTables ?? true;
10248
10264
  const isMidWord = options?.isMidWord ?? false;
10265
+ const isJoinContext = tokenTypes.some((t) => t.name === "Join");
10249
10266
  for (const tokenType of tokenTypes) {
10250
10267
  const name = tokenType.name;
10251
10268
  if (SKIP_TOKENS.has(name)) {
@@ -10258,19 +10275,31 @@ function buildSuggestions(tokenTypes, schema, tablesInScope, options) {
10258
10275
  if (name === "Identifier" || name === "QuotedIdentifier") {
10259
10276
  continue;
10260
10277
  }
10278
+ if (isJoinContext && JOIN_COMPOUND_MAP.has(name)) {
10279
+ const compound = JOIN_COMPOUND_MAP.get(name);
10280
+ if (seenKeywords.has(compound)) continue;
10281
+ seenKeywords.add(compound);
10282
+ suggestions.push({
10283
+ label: compound,
10284
+ kind: "keyword" /* Keyword */,
10285
+ insertText: compound,
10286
+ filterText: compound.toLowerCase(),
10287
+ priority: 2 /* Medium */
10288
+ });
10289
+ continue;
10290
+ }
10261
10291
  const keyword = tokenNameToKeyword(name);
10262
10292
  if (seenKeywords.has(keyword)) {
10263
10293
  continue;
10264
10294
  }
10265
10295
  seenKeywords.add(keyword);
10266
10296
  const kind = "keyword" /* Keyword */;
10267
- const priority = EXPRESSION_OPERATORS.has(name) ? 3 /* MediumLow */ : 2 /* Medium */;
10268
10297
  suggestions.push({
10269
10298
  label: keyword,
10270
10299
  kind,
10271
10300
  insertText: keyword,
10272
10301
  filterText: keyword.toLowerCase(),
10273
- priority
10302
+ priority: 2 /* Medium */
10274
10303
  });
10275
10304
  }
10276
10305
  if (expectsIdentifier) {
@@ -10380,6 +10409,20 @@ function buildSuggestions(tokenTypes, schema, tablesInScope, options) {
10380
10409
  }
10381
10410
 
10382
10411
  // src/autocomplete/provider.ts
10412
+ var EXPRESSION_OPERATOR_LABELS = new Set(
10413
+ [...EXPRESSION_OPERATORS].map(tokenNameToKeyword)
10414
+ );
10415
+ function isSchemaColumn(image, tablesInScope, schema) {
10416
+ const nameLower = image.toLowerCase();
10417
+ for (const ref of tablesInScope) {
10418
+ const cols = schema.columns[ref.table.toLowerCase()];
10419
+ if (cols?.some((c) => c.name.toLowerCase() === nameLower)) return true;
10420
+ }
10421
+ for (const cols of Object.values(schema.columns)) {
10422
+ if (cols.some((c) => c.name.toLowerCase() === nameLower)) return true;
10423
+ }
10424
+ return false;
10425
+ }
10383
10426
  var TABLE_NAME_TOKENS = /* @__PURE__ */ new Set([
10384
10427
  "From",
10385
10428
  "Join",
@@ -10418,7 +10461,7 @@ function rankTableSuggestions(suggestions, referencedColumns, columnIndex) {
10418
10461
  if (s.kind !== "table" /* Table */) continue;
10419
10462
  const score = scores.get(s.label.toLowerCase());
10420
10463
  if (score === void 0) continue;
10421
- s.priority = score === referencedColumns.size ? 1 /* High */ : 2 /* Medium */;
10464
+ s.priority = score === referencedColumns.size ? 2 /* Medium */ : 3 /* MediumLow */;
10422
10465
  }
10423
10466
  }
10424
10467
  function getLastSignificantTokens(tokens) {
@@ -10456,7 +10499,8 @@ function createAutocompleteProvider(schema) {
10456
10499
  qualifiedTableRef,
10457
10500
  suggestColumns,
10458
10501
  suggestTables,
10459
- referencedColumns
10502
+ referencedColumns,
10503
+ isConditionContext
10460
10504
  } = getContentAssist(query, cursorOffset);
10461
10505
  const effectiveSchema = Object.keys(cteColumns).length > 0 ? {
10462
10506
  ...normalizedSchema,
@@ -10497,6 +10541,30 @@ function createAutocompleteProvider(schema) {
10497
10541
  if (suggestTables) {
10498
10542
  rankTableSuggestions(suggestions, referencedColumns, columnIndex);
10499
10543
  }
10544
+ if (isConditionContext) {
10545
+ const hasExpressionOperators = nextTokenTypes.some(
10546
+ (t) => EXPRESSION_OPERATORS.has(t.name)
10547
+ );
10548
+ if (hasExpressionOperators) {
10549
+ const effectiveTokens = isMidWord && tokensBefore.length > 0 ? tokensBefore.slice(0, -1) : tokensBefore;
10550
+ const lastToken = effectiveTokens[effectiveTokens.length - 1];
10551
+ const lastTokenName = lastToken?.tokenType?.name;
10552
+ if (lastTokenName && !SKIP_TOKENS.has(lastTokenName) && !PUNCTUATION_TOKENS.has(lastTokenName) && isSchemaColumn(
10553
+ lastToken.image,
10554
+ effectiveTablesInScope,
10555
+ effectiveSchema
10556
+ )) {
10557
+ for (const s of suggestions) {
10558
+ if (s.kind !== "keyword" /* Keyword */) continue;
10559
+ if (s.label === "IN") {
10560
+ s.priority = 1 /* High */;
10561
+ } else if (!EXPRESSION_OPERATOR_LABELS.has(s.label)) {
10562
+ s.priority = 3 /* MediumLow */;
10563
+ }
10564
+ }
10565
+ }
10566
+ }
10567
+ }
10500
10568
  return suggestions;
10501
10569
  }
10502
10570
  const fallbackTokens = isMidWord && tokensBefore.length > 0 ? tokensBefore.slice(0, -1) : tokensBefore;
@@ -603,7 +603,7 @@ export interface TableRef extends AstNode {
603
603
  }
604
604
  export interface JoinClause extends AstNode {
605
605
  type: "join";
606
- joinType?: "inner" | "left" | "right" | "full" | "cross" | "asof" | "lt" | "splice" | "window" | "horizon";
606
+ joinType?: "inner" | "left" | "cross" | "asof" | "lt" | "splice" | "window" | "horizon";
607
607
  outer?: boolean;
608
608
  table: TableRef;
609
609
  on?: Expression;
@@ -13,6 +13,7 @@ declare class QuestDBParser extends CstParser {
13
13
  statement: import("chevrotain").ParserMethod<[], import("chevrotain").CstNode>;
14
14
  private withStatement;
15
15
  private selectStatement;
16
+ private selectBody;
16
17
  private withClause;
17
18
  private cteDefinition;
18
19
  private simpleSelect;
@@ -1,5 +1,5 @@
1
1
  import * as AST from "./ast";
2
- import type { AddUserStatementCstChildren, AdditiveExpressionCstChildren, AlignToClauseCstChildren, AlterGroupStatementCstChildren, AlterMaterializedViewActionCstChildren, AlterMaterializedViewStatementCstChildren, AlterServiceAccountStatementCstChildren, AlterStatementCstChildren, AlterTableActionCstChildren, AlterTableStatementCstChildren, AlterUserActionCstChildren, AlterUserStatementCstChildren, AlterViewStatementCstChildren, AndExpressionCstChildren, ArrayBracketBodyCstChildren, ArrayElementCstChildren, ArrayLiteralCstChildren, ArraySubscriptCstChildren, AsofLtJoinCstChildren, AssumeServiceAccountStatementCstChildren, BackupStatementCstChildren, BatchClauseCstChildren, BitAndExpressionCstChildren, BitOrExpressionCstChildren, BitXorExpressionCstChildren, BooleanLiteralCstChildren, CancelQueryStatementCstChildren, CaseExpressionCstChildren, CastDefinitionCstChildren, CastExpressionCstChildren, CheckpointStatementCstChildren, ColumnDefinitionCstChildren, ColumnRefCstChildren, CompileViewStatementCstChildren, ConcatExpressionCstChildren, Ipv4ContainmentExpressionCstChildren, ConvertPartitionTargetCstChildren, CopyCancelCstChildren, CopyFromCstChildren, CopyOptionCstChildren, CopyOptionsCstChildren, CopyStatementCstChildren, CopyToCstChildren, CreateGroupStatementCstChildren, CreateMaterializedViewBodyCstChildren, CreateServiceAccountStatementCstChildren, CreateStatementCstChildren, CreateTableBodyCstChildren, CreateUserStatementCstChildren, CreateViewBodyCstChildren, CteDefinitionCstChildren, DataTypeCstChildren, DeclareAssignmentCstChildren, DeclareClauseCstChildren, DedupClauseCstChildren, DropGroupStatementCstChildren, DropMaterializedViewStatementCstChildren, DropServiceAccountStatementCstChildren, DropStatementCstChildren, DropTableStatementCstChildren, DropUserStatementCstChildren, DropViewStatementCstChildren, DurationExpressionCstChildren, EqualityExpressionCstChildren, ExitServiceAccountStatementCstChildren, ExplainStatementCstChildren, ExpressionCstChildren, FillClauseCstChildren, FillValueCstChildren, FromClauseCstChildren, FromToClauseCstChildren, FunctionCallCstChildren, FunctionNameCstChildren, GrantAssumeServiceAccountStatementCstChildren, GrantStatementCstChildren, GrantTableTargetCstChildren, GroupByClauseCstChildren, IdentifierCstChildren, IdentifierExpressionCstChildren, ImplicitSelectBodyCstChildren, ImplicitSelectStatementCstChildren, IndexDefinitionCstChildren, InsertStatementCstChildren, IntervalValueCstChildren, JoinClauseCstChildren, LatestOnClauseCstChildren, LimitClauseCstChildren, LiteralCstChildren, MaterializedViewPartitionCstChildren, MaterializedViewPeriodCstChildren, MaterializedViewRefreshCstChildren, MultiplicativeExpressionCstChildren, NotExpressionCstChildren, OrExpressionCstChildren, OrderByClauseCstChildren, OrderByItemCstChildren, OverClauseCstChildren, PartitionPeriodCstChildren, PermissionListCstChildren, PermissionTokenCstChildren, PivotAggregationCstChildren, PivotBodyCstChildren, PivotForClauseCstChildren, PivotInValueCstChildren, PivotStatementCstChildren, PrimaryExpressionCstChildren, QualifiedNameCstChildren, QualifiedStarCstChildren, ReindexTableStatementCstChildren, RefreshMaterializedViewStatementCstChildren, RelationalExpressionCstChildren, RemoveUserStatementCstChildren, RenameTableStatementCstChildren, ResumeWalStatementCstChildren, RevokeAssumeServiceAccountStatementCstChildren, RevokeStatementCstChildren, SampleByClauseCstChildren, SelectItemCstChildren, SelectListCstChildren, SelectStatementCstChildren, SetClauseCstChildren, SetExpressionCstChildren, SetOperationCstChildren, SetTypeStatementCstChildren, ShowStatementCstChildren, SimpleSelectCstChildren, SnapshotStatementCstChildren, SpliceJoinCstChildren, HorizonJoinCstChildren, HorizonOffsetCstChildren, StandardJoinCstChildren, StatementCstChildren, StatementsCstChildren, StringOrIdentifierCstChildren, StringOrQualifiedNameCstChildren, TableFunctionCallCstChildren, TableFunctionNameCstChildren, TableNameCstChildren, TableNameOrStringCstChildren, TableParamCstChildren, TableParamNameCstChildren, TableRefCstChildren, TimeUnitCstChildren, TimeZoneValueCstChildren, TruncateTableStatementCstChildren, TypeCastExpressionCstChildren, UnaryExpressionCstChildren, UpdateStatementCstChildren, VacuumTableStatementCstChildren, ValuesClauseCstChildren, ValuesListCstChildren, WhereClauseCstChildren, WindowFrameBoundCstChildren, WindowFrameClauseCstChildren, WindowJoinBoundCstChildren, WindowJoinCstChildren, WindowPartitionByClauseCstChildren, WithClauseCstChildren, WithStatementCstChildren } from "./cst-types";
2
+ import type { AddUserStatementCstChildren, AdditiveExpressionCstChildren, AlignToClauseCstChildren, AlterGroupStatementCstChildren, AlterMaterializedViewActionCstChildren, AlterMaterializedViewStatementCstChildren, AlterServiceAccountStatementCstChildren, AlterStatementCstChildren, AlterTableActionCstChildren, AlterTableStatementCstChildren, AlterUserActionCstChildren, AlterUserStatementCstChildren, AlterViewStatementCstChildren, AndExpressionCstChildren, ArrayBracketBodyCstChildren, ArrayElementCstChildren, ArrayLiteralCstChildren, ArraySubscriptCstChildren, AsofLtJoinCstChildren, AssumeServiceAccountStatementCstChildren, BackupStatementCstChildren, BatchClauseCstChildren, BitAndExpressionCstChildren, BitOrExpressionCstChildren, BitXorExpressionCstChildren, BooleanLiteralCstChildren, CancelQueryStatementCstChildren, CaseExpressionCstChildren, CastDefinitionCstChildren, CastExpressionCstChildren, CheckpointStatementCstChildren, ColumnDefinitionCstChildren, ColumnRefCstChildren, CompileViewStatementCstChildren, ConcatExpressionCstChildren, Ipv4ContainmentExpressionCstChildren, ConvertPartitionTargetCstChildren, CopyCancelCstChildren, CopyFromCstChildren, CopyOptionCstChildren, CopyOptionsCstChildren, CopyStatementCstChildren, CopyToCstChildren, CreateGroupStatementCstChildren, CreateMaterializedViewBodyCstChildren, CreateServiceAccountStatementCstChildren, CreateStatementCstChildren, CreateTableBodyCstChildren, CreateUserStatementCstChildren, CreateViewBodyCstChildren, CteDefinitionCstChildren, DataTypeCstChildren, DeclareAssignmentCstChildren, DeclareClauseCstChildren, DedupClauseCstChildren, DropGroupStatementCstChildren, DropMaterializedViewStatementCstChildren, DropServiceAccountStatementCstChildren, DropStatementCstChildren, DropTableStatementCstChildren, DropUserStatementCstChildren, DropViewStatementCstChildren, DurationExpressionCstChildren, EqualityExpressionCstChildren, ExitServiceAccountStatementCstChildren, ExplainStatementCstChildren, ExpressionCstChildren, FillClauseCstChildren, FillValueCstChildren, FromClauseCstChildren, FromToClauseCstChildren, FunctionCallCstChildren, FunctionNameCstChildren, GrantAssumeServiceAccountStatementCstChildren, GrantStatementCstChildren, GrantTableTargetCstChildren, GroupByClauseCstChildren, IdentifierCstChildren, IdentifierExpressionCstChildren, ImplicitSelectBodyCstChildren, ImplicitSelectStatementCstChildren, IndexDefinitionCstChildren, InsertStatementCstChildren, IntervalValueCstChildren, JoinClauseCstChildren, LatestOnClauseCstChildren, LimitClauseCstChildren, LiteralCstChildren, MaterializedViewPartitionCstChildren, MaterializedViewPeriodCstChildren, MaterializedViewRefreshCstChildren, MultiplicativeExpressionCstChildren, NotExpressionCstChildren, OrExpressionCstChildren, OrderByClauseCstChildren, OrderByItemCstChildren, OverClauseCstChildren, PartitionPeriodCstChildren, PermissionListCstChildren, PermissionTokenCstChildren, PivotAggregationCstChildren, PivotBodyCstChildren, PivotForClauseCstChildren, PivotInValueCstChildren, PivotStatementCstChildren, PrimaryExpressionCstChildren, QualifiedNameCstChildren, QualifiedStarCstChildren, ReindexTableStatementCstChildren, RefreshMaterializedViewStatementCstChildren, RelationalExpressionCstChildren, RemoveUserStatementCstChildren, RenameTableStatementCstChildren, ResumeWalStatementCstChildren, RevokeAssumeServiceAccountStatementCstChildren, RevokeStatementCstChildren, SampleByClauseCstChildren, SelectItemCstChildren, SelectListCstChildren, SelectBodyCstChildren, SelectStatementCstChildren, SetClauseCstChildren, SetExpressionCstChildren, SetOperationCstChildren, SetTypeStatementCstChildren, ShowStatementCstChildren, SimpleSelectCstChildren, SnapshotStatementCstChildren, SpliceJoinCstChildren, HorizonJoinCstChildren, HorizonOffsetCstChildren, StandardJoinCstChildren, StatementCstChildren, StatementsCstChildren, StringOrIdentifierCstChildren, StringOrQualifiedNameCstChildren, TableFunctionCallCstChildren, TableFunctionNameCstChildren, TableNameCstChildren, TableNameOrStringCstChildren, TableParamCstChildren, TableParamNameCstChildren, TableRefCstChildren, TimeUnitCstChildren, TimeZoneValueCstChildren, TruncateTableStatementCstChildren, TypeCastExpressionCstChildren, UnaryExpressionCstChildren, UpdateStatementCstChildren, VacuumTableStatementCstChildren, ValuesClauseCstChildren, ValuesListCstChildren, WhereClauseCstChildren, WindowFrameBoundCstChildren, WindowFrameClauseCstChildren, WindowJoinBoundCstChildren, WindowJoinCstChildren, WindowPartitionByClauseCstChildren, WithClauseCstChildren, WithStatementCstChildren } from "./cst-types";
3
3
  declare const BaseVisitor: new (...args: any[]) => import("chevrotain").ICstVisitor<any, any>;
4
4
  declare class QuestDBVisitor extends BaseVisitor {
5
5
  constructor();
@@ -8,6 +8,7 @@ declare class QuestDBVisitor extends BaseVisitor {
8
8
  statement(ctx: StatementCstChildren): AST.Statement;
9
9
  withStatement(ctx: WithStatementCstChildren): AST.Statement;
10
10
  selectStatement(ctx: SelectStatementCstChildren): AST.SelectStatement;
11
+ selectBody(ctx: SelectBodyCstChildren): AST.SelectStatement;
11
12
  withClause(ctx: WithClauseCstChildren): AST.CTE[];
12
13
  cteDefinition(ctx: CteDefinitionCstChildren): AST.CTE;
13
14
  simpleSelect(ctx: SimpleSelectCstChildren): AST.SelectStatement;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@questdb/sql-parser",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "SQL parser for QuestDB syntax using Chevrotain",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",