@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 +5 -0
- package/dist/autocomplete/content-assist.d.ts +2 -0
- package/dist/autocomplete/token-classification.d.ts +1 -3
- package/dist/index.cjs +57 -14
- package/dist/index.js +57 -14
- package/package.json +1 -1
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-
|
|
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
|
-
|
|
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
|
-
|
|
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;
|