@questdb/sql-parser 0.1.4 → 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,11 @@
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
+
4
9
  ## 0.1.4 - 2026.03.17
5
10
  ### Added
6
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)
@@ -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
@@ -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) {
@@ -10349,13 +10354,12 @@ function buildSuggestions(tokenTypes, schema, tablesInScope, options) {
10349
10354
  }
10350
10355
  seenKeywords.add(keyword);
10351
10356
  const kind = "keyword" /* Keyword */;
10352
- const priority = EXPRESSION_OPERATORS.has(name) ? 3 /* MediumLow */ : 2 /* Medium */;
10353
10357
  suggestions.push({
10354
10358
  label: keyword,
10355
10359
  kind,
10356
10360
  insertText: keyword,
10357
10361
  filterText: keyword.toLowerCase(),
10358
- priority
10362
+ priority: 2 /* Medium */
10359
10363
  });
10360
10364
  }
10361
10365
  if (expectsIdentifier) {
@@ -10465,6 +10469,20 @@ function buildSuggestions(tokenTypes, schema, tablesInScope, options) {
10465
10469
  }
10466
10470
 
10467
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
+ }
10468
10486
  var TABLE_NAME_TOKENS = /* @__PURE__ */ new Set([
10469
10487
  "From",
10470
10488
  "Join",
@@ -10541,7 +10559,8 @@ function createAutocompleteProvider(schema) {
10541
10559
  qualifiedTableRef,
10542
10560
  suggestColumns,
10543
10561
  suggestTables,
10544
- referencedColumns
10562
+ referencedColumns,
10563
+ isConditionContext
10545
10564
  } = getContentAssist(query, cursorOffset);
10546
10565
  const effectiveSchema = Object.keys(cteColumns).length > 0 ? {
10547
10566
  ...normalizedSchema,
@@ -10582,6 +10601,30 @@ function createAutocompleteProvider(schema) {
10582
10601
  if (suggestTables) {
10583
10602
  rankTableSuggestions(suggestions, referencedColumns, columnIndex);
10584
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
+ }
10585
10628
  return suggestions;
10586
10629
  }
10587
10630
  const fallbackTokens = isMidWord && tokensBefore.length > 0 ? tokensBefore.slice(0, -1) : tokensBefore;
package/dist/index.js CHANGED
@@ -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) {
@@ -10289,13 +10294,12 @@ function buildSuggestions(tokenTypes, schema, tablesInScope, options) {
10289
10294
  }
10290
10295
  seenKeywords.add(keyword);
10291
10296
  const kind = "keyword" /* Keyword */;
10292
- const priority = EXPRESSION_OPERATORS.has(name) ? 3 /* MediumLow */ : 2 /* Medium */;
10293
10297
  suggestions.push({
10294
10298
  label: keyword,
10295
10299
  kind,
10296
10300
  insertText: keyword,
10297
10301
  filterText: keyword.toLowerCase(),
10298
- priority
10302
+ priority: 2 /* Medium */
10299
10303
  });
10300
10304
  }
10301
10305
  if (expectsIdentifier) {
@@ -10405,6 +10409,20 @@ function buildSuggestions(tokenTypes, schema, tablesInScope, options) {
10405
10409
  }
10406
10410
 
10407
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
+ }
10408
10426
  var TABLE_NAME_TOKENS = /* @__PURE__ */ new Set([
10409
10427
  "From",
10410
10428
  "Join",
@@ -10481,7 +10499,8 @@ function createAutocompleteProvider(schema) {
10481
10499
  qualifiedTableRef,
10482
10500
  suggestColumns,
10483
10501
  suggestTables,
10484
- referencedColumns
10502
+ referencedColumns,
10503
+ isConditionContext
10485
10504
  } = getContentAssist(query, cursorOffset);
10486
10505
  const effectiveSchema = Object.keys(cteColumns).length > 0 ? {
10487
10506
  ...normalizedSchema,
@@ -10522,6 +10541,30 @@ function createAutocompleteProvider(schema) {
10522
10541
  if (suggestTables) {
10523
10542
  rankTableSuggestions(suggestions, referencedColumns, columnIndex);
10524
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
+ }
10525
10568
  return suggestions;
10526
10569
  }
10527
10570
  const fallbackTokens = isMidWord && tokensBefore.length > 0 ? tokensBefore.slice(0, -1) : tokensBefore;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@questdb/sql-parser",
3
- "version": "0.1.4",
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",