@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 +5 -1
- package/dist/autocomplete/content-assist.d.ts +23 -0
- package/dist/index.cjs +77 -4
- package/dist/index.js +77 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
3
|
|
|
4
|
-
## 0.1.
|
|
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
|
-
|
|
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
|
-
|
|
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 [];
|