@questdb/sql-parser 0.1.1 → 0.1.2

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,8 +1,12 @@
1
1
  # Changelog
2
2
 
3
3
 
4
- ## 0.1.1 - 2026.02.23
4
+ ## 0.1.2 - 2026.02.25
5
+ ### Fixed
6
+ - Prioritize tables with mentioned columns in the suggestions [#6](https://github.com/questdb/sql-parser/pull/6)
5
7
 
8
+
9
+ ## 0.1.1 - 2026.02.23
6
10
  ### Fixed
7
11
  - grammar-level table/column classification, join-specific suggestions [#2](https://github.com/questdb/sql-parser/pull/2)
8
12
 
@@ -37,7 +37,30 @@ export interface ContentAssistResult {
37
37
  suggestColumns: boolean;
38
38
  /** Whether the grammar context expects table names (tableName positions, or expression context) */
39
39
  suggestTables: boolean;
40
+ /**
41
+ * Bare column names (lowercase) referenced before the cursor in expression
42
+ * context. Used by the provider to boost tables containing all these columns.
43
+ */
44
+ referencedColumns: Set<string>;
40
45
  }
46
+ /**
47
+ * Extract bare column names referenced in expression context from a token list.
48
+ *
49
+ * Scans the tokens and collects identifier names that are likely column
50
+ * references, excluding:
51
+ * - Qualified identifiers (followed by a Dot token — table/alias qualifiers)
52
+ * - Middle segments of multi-part names (preceded AND followed by a Dot)
53
+ * - Known table names and aliases (matched against tableAndAliasSet)
54
+ * - Function calls (followed by a left-parenthesis token)
55
+ *
56
+ * @param tokens - Tokens to scan
57
+ * @param tableAndAliasSet - Lowercase table names and aliases already in scope
58
+ * (built from tablesInScope by the caller). Identifiers matching any of these
59
+ * are excluded because they are table/alias references, not column names.
60
+ *
61
+ * Returns a Set of lowercase column names for efficient lookup.
62
+ */
63
+ export declare function extractReferencedColumns(tokens: IToken[], tableAndAliasSet: Set<string>): Set<string>;
41
64
  /**
42
65
  * Get content assist suggestions for a SQL string at a given cursor position
43
66
  *
package/dist/index.cjs CHANGED
@@ -9630,6 +9630,32 @@ function inferTableFromQualifiedRef(tokensBefore, isMidWord) {
9630
9630
  const table = tableToken.tokenType.name === "QuotedIdentifier" ? tableToken.image.slice(1, -1) : tableToken.image;
9631
9631
  return { table };
9632
9632
  }
9633
+ function extractReferencedColumns(tokens, tableAndAliasSet) {
9634
+ const result = /* @__PURE__ */ new Set();
9635
+ for (let i = 0; i < tokens.length; i++) {
9636
+ const token = tokens[i];
9637
+ const name = token.tokenType.name;
9638
+ if (name !== "Identifier" && name !== "QuotedIdentifier" && !IDENTIFIER_KEYWORD_TOKENS.has(name)) {
9639
+ continue;
9640
+ }
9641
+ if (i + 1 < tokens.length && tokens[i + 1].tokenType.name === "Dot") {
9642
+ continue;
9643
+ }
9644
+ if (i > 0 && tokens[i - 1].tokenType.name === "Dot" && i + 1 < tokens.length && tokens[i + 1].tokenType.name === "Dot") {
9645
+ continue;
9646
+ }
9647
+ if (i + 1 < tokens.length && tokens[i + 1].tokenType.name === "LParen") {
9648
+ continue;
9649
+ }
9650
+ const image = name === "QuotedIdentifier" ? token.image.slice(1, -1) : token.image;
9651
+ const lower = image.toLowerCase();
9652
+ if (tableAndAliasSet.has(lower)) {
9653
+ continue;
9654
+ }
9655
+ result.add(lower);
9656
+ }
9657
+ return result;
9658
+ }
9633
9659
  function getContentAssist(fullSql, cursorOffset) {
9634
9660
  const fullTokens = QuestDBLexer.tokenize(fullSql).tokens;
9635
9661
  for (const token of fullTokens) {
@@ -9646,7 +9672,8 @@ function getContentAssist(fullSql, cursorOffset) {
9646
9672
  isMidWord: true,
9647
9673
  lexErrors: [],
9648
9674
  suggestColumns: false,
9649
- suggestTables: false
9675
+ suggestTables: false,
9676
+ referencedColumns: /* @__PURE__ */ new Set()
9650
9677
  };
9651
9678
  }
9652
9679
  }
@@ -9698,6 +9725,15 @@ function getContentAssist(fullSql, cursorOffset) {
9698
9725
  if (tablesInScope.length === 0 && qualifiedRef) {
9699
9726
  tablesInScope.push(qualifiedRef);
9700
9727
  }
9728
+ const tableAndAliasSet = /* @__PURE__ */ new Set();
9729
+ for (const t of tablesInScope) {
9730
+ tableAndAliasSet.add(t.table.toLowerCase());
9731
+ if (t.alias) tableAndAliasSet.add(t.alias.toLowerCase());
9732
+ }
9733
+ const referencedColumns = extractReferencedColumns(
9734
+ tokensForAssist,
9735
+ tableAndAliasSet
9736
+ );
9701
9737
  return {
9702
9738
  nextTokenTypes,
9703
9739
  tablesInScope,
@@ -9707,7 +9743,8 @@ function getContentAssist(fullSql, cursorOffset) {
9707
9743
  lexErrors: lexResult.errors,
9708
9744
  qualifiedTableRef: qualifiedRef?.table,
9709
9745
  suggestColumns,
9710
- suggestTables
9746
+ suggestTables,
9747
+ referencedColumns
9711
9748
  };
9712
9749
  }
9713
9750
  function getNextValidTokens(sql) {
@@ -10287,6 +10324,35 @@ var TABLE_NAME_TOKENS = /* @__PURE__ */ new Set([
10287
10324
  "Table",
10288
10325
  "View"
10289
10326
  ]);
10327
+ function buildColumnIndex(schema) {
10328
+ const index = /* @__PURE__ */ new Map();
10329
+ for (const table of schema.tables) {
10330
+ const key = table.name.toLowerCase();
10331
+ const cols = schema.columns[key];
10332
+ if (cols) {
10333
+ index.set(key, new Set(cols.map((c) => c.name.toLowerCase())));
10334
+ }
10335
+ }
10336
+ return index;
10337
+ }
10338
+ function rankTableSuggestions(suggestions, referencedColumns, columnIndex) {
10339
+ if (referencedColumns.size === 0) return;
10340
+ const scores = /* @__PURE__ */ new Map();
10341
+ for (const [tableName, colNames] of columnIndex) {
10342
+ let count = 0;
10343
+ for (const ref of referencedColumns) {
10344
+ if (colNames.has(ref)) count++;
10345
+ }
10346
+ if (count > 0) scores.set(tableName, count);
10347
+ }
10348
+ if (scores.size === 0) return;
10349
+ for (const s of suggestions) {
10350
+ if (s.kind !== "table" /* Table */) continue;
10351
+ const score = scores.get(s.label.toLowerCase());
10352
+ if (score === void 0) continue;
10353
+ s.priority = score === referencedColumns.size ? 1 /* High */ : 2 /* Medium */;
10354
+ }
10355
+ }
10290
10356
  function getLastSignificantTokens(tokens) {
10291
10357
  const result = [];
10292
10358
  for (let i = tokens.length - 1; i >= 0; i--) {
@@ -10310,6 +10376,7 @@ function createAutocompleteProvider(schema) {
10310
10376
  ])
10311
10377
  )
10312
10378
  };
10379
+ const columnIndex = buildColumnIndex(normalizedSchema);
10313
10380
  return {
10314
10381
  getSuggestions(query, cursorOffset) {
10315
10382
  const {
@@ -10320,7 +10387,8 @@ function createAutocompleteProvider(schema) {
10320
10387
  isMidWord,
10321
10388
  qualifiedTableRef,
10322
10389
  suggestColumns,
10323
- suggestTables
10390
+ suggestTables,
10391
+ referencedColumns
10324
10392
  } = getContentAssist(query, cursorOffset);
10325
10393
  const effectiveSchema = Object.keys(cteColumns).length > 0 ? {
10326
10394
  ...normalizedSchema,
@@ -10348,7 +10416,7 @@ function createAutocompleteProvider(schema) {
10348
10416
  }
10349
10417
  }
10350
10418
  if (nextTokenTypes.length > 0) {
10351
- return buildSuggestions(
10419
+ const suggestions = buildSuggestions(
10352
10420
  nextTokenTypes,
10353
10421
  effectiveSchema,
10354
10422
  effectiveTablesInScope,
@@ -10358,6 +10426,10 @@ function createAutocompleteProvider(schema) {
10358
10426
  isMidWord
10359
10427
  }
10360
10428
  );
10429
+ if (suggestTables) {
10430
+ rankTableSuggestions(suggestions, referencedColumns, columnIndex);
10431
+ }
10432
+ return suggestions;
10361
10433
  }
10362
10434
  const fallbackTokens = isMidWord && tokensBefore.length > 0 ? tokensBefore.slice(0, -1) : tokensBefore;
10363
10435
  const [lastFallback] = getLastSignificantTokens(fallbackTokens);
@@ -10386,6 +10458,7 @@ function createAutocompleteProvider(schema) {
10386
10458
  });
10387
10459
  }
10388
10460
  }
10461
+ rankTableSuggestions(suggestions, referencedColumns, columnIndex);
10389
10462
  return suggestions;
10390
10463
  }
10391
10464
  return [];
package/dist/index.js CHANGED
@@ -9570,6 +9570,32 @@ function inferTableFromQualifiedRef(tokensBefore, isMidWord) {
9570
9570
  const table = tableToken.tokenType.name === "QuotedIdentifier" ? tableToken.image.slice(1, -1) : tableToken.image;
9571
9571
  return { table };
9572
9572
  }
9573
+ function extractReferencedColumns(tokens, tableAndAliasSet) {
9574
+ const result = /* @__PURE__ */ new Set();
9575
+ for (let i = 0; i < tokens.length; i++) {
9576
+ const token = tokens[i];
9577
+ const name = token.tokenType.name;
9578
+ if (name !== "Identifier" && name !== "QuotedIdentifier" && !IDENTIFIER_KEYWORD_TOKENS.has(name)) {
9579
+ continue;
9580
+ }
9581
+ if (i + 1 < tokens.length && tokens[i + 1].tokenType.name === "Dot") {
9582
+ continue;
9583
+ }
9584
+ if (i > 0 && tokens[i - 1].tokenType.name === "Dot" && i + 1 < tokens.length && tokens[i + 1].tokenType.name === "Dot") {
9585
+ continue;
9586
+ }
9587
+ if (i + 1 < tokens.length && tokens[i + 1].tokenType.name === "LParen") {
9588
+ continue;
9589
+ }
9590
+ const image = name === "QuotedIdentifier" ? token.image.slice(1, -1) : token.image;
9591
+ const lower = image.toLowerCase();
9592
+ if (tableAndAliasSet.has(lower)) {
9593
+ continue;
9594
+ }
9595
+ result.add(lower);
9596
+ }
9597
+ return result;
9598
+ }
9573
9599
  function getContentAssist(fullSql, cursorOffset) {
9574
9600
  const fullTokens = QuestDBLexer.tokenize(fullSql).tokens;
9575
9601
  for (const token of fullTokens) {
@@ -9586,7 +9612,8 @@ function getContentAssist(fullSql, cursorOffset) {
9586
9612
  isMidWord: true,
9587
9613
  lexErrors: [],
9588
9614
  suggestColumns: false,
9589
- suggestTables: false
9615
+ suggestTables: false,
9616
+ referencedColumns: /* @__PURE__ */ new Set()
9590
9617
  };
9591
9618
  }
9592
9619
  }
@@ -9638,6 +9665,15 @@ function getContentAssist(fullSql, cursorOffset) {
9638
9665
  if (tablesInScope.length === 0 && qualifiedRef) {
9639
9666
  tablesInScope.push(qualifiedRef);
9640
9667
  }
9668
+ const tableAndAliasSet = /* @__PURE__ */ new Set();
9669
+ for (const t of tablesInScope) {
9670
+ tableAndAliasSet.add(t.table.toLowerCase());
9671
+ if (t.alias) tableAndAliasSet.add(t.alias.toLowerCase());
9672
+ }
9673
+ const referencedColumns = extractReferencedColumns(
9674
+ tokensForAssist,
9675
+ tableAndAliasSet
9676
+ );
9641
9677
  return {
9642
9678
  nextTokenTypes,
9643
9679
  tablesInScope,
@@ -9647,7 +9683,8 @@ function getContentAssist(fullSql, cursorOffset) {
9647
9683
  lexErrors: lexResult.errors,
9648
9684
  qualifiedTableRef: qualifiedRef?.table,
9649
9685
  suggestColumns,
9650
- suggestTables
9686
+ suggestTables,
9687
+ referencedColumns
9651
9688
  };
9652
9689
  }
9653
9690
  function getNextValidTokens(sql) {
@@ -10227,6 +10264,35 @@ var TABLE_NAME_TOKENS = /* @__PURE__ */ new Set([
10227
10264
  "Table",
10228
10265
  "View"
10229
10266
  ]);
10267
+ function buildColumnIndex(schema) {
10268
+ const index = /* @__PURE__ */ new Map();
10269
+ for (const table of schema.tables) {
10270
+ const key = table.name.toLowerCase();
10271
+ const cols = schema.columns[key];
10272
+ if (cols) {
10273
+ index.set(key, new Set(cols.map((c) => c.name.toLowerCase())));
10274
+ }
10275
+ }
10276
+ return index;
10277
+ }
10278
+ function rankTableSuggestions(suggestions, referencedColumns, columnIndex) {
10279
+ if (referencedColumns.size === 0) return;
10280
+ const scores = /* @__PURE__ */ new Map();
10281
+ for (const [tableName, colNames] of columnIndex) {
10282
+ let count = 0;
10283
+ for (const ref of referencedColumns) {
10284
+ if (colNames.has(ref)) count++;
10285
+ }
10286
+ if (count > 0) scores.set(tableName, count);
10287
+ }
10288
+ if (scores.size === 0) return;
10289
+ for (const s of suggestions) {
10290
+ if (s.kind !== "table" /* Table */) continue;
10291
+ const score = scores.get(s.label.toLowerCase());
10292
+ if (score === void 0) continue;
10293
+ s.priority = score === referencedColumns.size ? 1 /* High */ : 2 /* Medium */;
10294
+ }
10295
+ }
10230
10296
  function getLastSignificantTokens(tokens) {
10231
10297
  const result = [];
10232
10298
  for (let i = tokens.length - 1; i >= 0; i--) {
@@ -10250,6 +10316,7 @@ function createAutocompleteProvider(schema) {
10250
10316
  ])
10251
10317
  )
10252
10318
  };
10319
+ const columnIndex = buildColumnIndex(normalizedSchema);
10253
10320
  return {
10254
10321
  getSuggestions(query, cursorOffset) {
10255
10322
  const {
@@ -10260,7 +10327,8 @@ function createAutocompleteProvider(schema) {
10260
10327
  isMidWord,
10261
10328
  qualifiedTableRef,
10262
10329
  suggestColumns,
10263
- suggestTables
10330
+ suggestTables,
10331
+ referencedColumns
10264
10332
  } = getContentAssist(query, cursorOffset);
10265
10333
  const effectiveSchema = Object.keys(cteColumns).length > 0 ? {
10266
10334
  ...normalizedSchema,
@@ -10288,7 +10356,7 @@ function createAutocompleteProvider(schema) {
10288
10356
  }
10289
10357
  }
10290
10358
  if (nextTokenTypes.length > 0) {
10291
- return buildSuggestions(
10359
+ const suggestions = buildSuggestions(
10292
10360
  nextTokenTypes,
10293
10361
  effectiveSchema,
10294
10362
  effectiveTablesInScope,
@@ -10298,6 +10366,10 @@ function createAutocompleteProvider(schema) {
10298
10366
  isMidWord
10299
10367
  }
10300
10368
  );
10369
+ if (suggestTables) {
10370
+ rankTableSuggestions(suggestions, referencedColumns, columnIndex);
10371
+ }
10372
+ return suggestions;
10301
10373
  }
10302
10374
  const fallbackTokens = isMidWord && tokensBefore.length > 0 ? tokensBefore.slice(0, -1) : tokensBefore;
10303
10375
  const [lastFallback] = getLastSignificantTokens(fallbackTokens);
@@ -10326,6 +10398,7 @@ function createAutocompleteProvider(schema) {
10326
10398
  });
10327
10399
  }
10328
10400
  }
10401
+ rankTableSuggestions(suggestions, referencedColumns, columnIndex);
10329
10402
  return suggestions;
10330
10403
  }
10331
10404
  return [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@questdb/sql-parser",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "SQL parser for QuestDB syntax using Chevrotain",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",