diff --git a/src/plugins/data/public/antlr/opensearch_sql/code_completion.test.ts b/src/plugins/data/public/antlr/opensearch_sql/code_completion.test.ts
index d059d3c344a4..246a8830c3bf 100644
--- a/src/plugins/data/public/antlr/opensearch_sql/code_completion.test.ts
+++ b/src/plugins/data/public/antlr/opensearch_sql/code_completion.test.ts
@@ -120,7 +120,7 @@ describe('sql code_completion', () => {
});
});
- it.skip('should suggest aggregate functions when appropriate', async () => {
+ it('should suggest aggregate functions when appropriate', async () => {
const result = await getSimpleSuggestions('SELECT * FROM test-index WHERE ');
SQL_SYMBOLS.AGREGATE_FUNCTIONS.forEach((func) => {
diff --git a/src/plugins/data/public/antlr/opensearch_sql/opensearch_sql_autocomplete.ts b/src/plugins/data/public/antlr/opensearch_sql/opensearch_sql_autocomplete.ts
index 752447cdf29c..0b4f3da1af4b 100644
--- a/src/plugins/data/public/antlr/opensearch_sql/opensearch_sql_autocomplete.ts
+++ b/src/plugins/data/public/antlr/opensearch_sql/opensearch_sql_autocomplete.ts
@@ -178,9 +178,10 @@ export function processVisitedRules(
break;
}
case OpenSearchSQLParser.RULE_predicate: {
- rerunWithoutRules.push(ruleId);
+ rerunWithoutRules.push(ruleId); // rerun to fetch aggs by blocking pred
+
/**
- * creates a list of the tokens from the start of the pedicate to the end
+ * create a list of the tokens from the start of the pedicate to the end
* intentionally omit all tokens with type SPACE
* now we know we only have "significant tokens"
*/
diff --git a/src/plugins/data/public/antlr/shared/types.ts b/src/plugins/data/public/antlr/shared/types.ts
index e47432e640e8..df1350b1bb28 100644
--- a/src/plugins/data/public/antlr/shared/types.ts
+++ b/src/plugins/data/public/antlr/shared/types.ts
@@ -156,5 +156,4 @@ export interface ParsingSubject {
query: string;
cursor: CursorPosition;
context?: ParserRuleContext;
- previousResult?: A;
}
diff --git a/src/plugins/data/public/antlr/shared/utils.ts b/src/plugins/data/public/antlr/shared/utils.ts
index b07cc8959d3b..79158c00a0f0 100644
--- a/src/plugins/data/public/antlr/shared/utils.ts
+++ b/src/plugins/data/public/antlr/shared/utils.ts
@@ -183,7 +183,7 @@ export const formatFieldsToSuggestions = (
return fieldSuggestions;
};
-export const parseQuery = <
+const singleParseQuery = <
A extends AutocompleteResultBase,
L extends LexerType,
P extends ParserType
@@ -198,8 +198,7 @@ export const parseQuery = <
query,
cursor,
context,
- previousResult,
-}: ParsingSubject): AutocompleteResultBase => {
+}: ParsingSubject): A => {
const parser = createParser(Lexer, Parser, query);
const { tokenStream } = parser;
const errorListener = new GeneralErrorListener(tokenDictionary.SPACE);
@@ -239,122 +238,101 @@ export const parseQuery = <
suggestKeywords,
};
- // console.clear();
- // console.log('cursorTokenIndex', cursorTokenIndex);
- // console.log('Formatted Token Stream:');
- // let index = 0;
- // try {
- // while (true) {
- // const token = tokenStream.get(index);
- // if (!token) break;
-
- // const isCurrentToken = index === cursorTokenIndex;
- // const tokenInfo = `Token ${index}: ${token.text} (Type: ${token.type})`;
-
- // if (isCurrentToken) {
- // console.log(`%c${tokenInfo}`, 'background-color: red; font-weight: bold;');
- // } else {
- // console.log(tokenInfo);
- // }
-
- // index++;
- // }
- // } catch (error) {
- // console.error('Error while iterating through token stream:', error);
- // }
-
- const currentResult = enrichAutocompleteResult(
- result,
- rules,
- tokenStream,
- cursorTokenIndex,
- cursor,
- query
- );
-
- // combine previous and current result
- const combinedResult: A = {};
-
- // look at every field in both results then combine inside of combinedResult
-
- if (previousResult) {
- Object.keys({ ...currentResult, ...previousResult }).forEach((key) => {
- const currentField = currentResult[key as keyof A];
- const previousField = previousResult[key as keyof A];
-
- if (currentField && previousField) {
- combinedResult[key as keyof A] = {
- ...currentField,
- suggestKeywords: [
- ...(previousField.suggestKeywords ?? []),
- ...(currentField.suggestKeywords ?? []),
- ],
- };
- } else if (currentField) {
- combinedResult[key as keyof A] = currentField;
- } else if (previousField) {
- combinedResult[key as keyof A] = previousField;
- }
- });
- }
-
- // // combine previous and current result
- // if (previousResultKeywords) {
- // // only need to modify initial results if there are context keywords
- // if (!currentResult?.suggestKeywords) {
- // // set initial keywords to be context keywords
- // currentResult.suggestKeywords = previousResultKeywords;
- // } else {
- // // merge initial and context keywords
- // const combined = [...currentResult.suggestKeywords, ...previousResultKeywords];
-
- // // ES6 magic to filter out duplicate objects based on id field
- // currentResult.suggestKeywords = combined.filter(
- // (item, index, self) => index === self.findIndex((other) => other.id === item.id)
- // );
- // }
- // }
-
- // return when we don't have any more rerun and combine rules
- if (!currentResult?.rerunWithoutRules || currentResult.rerunWithoutRules.length === 0) {
- return currentResult;
- }
-
- // pop the top/last rule in the rerun and combine list
- const nextRuleToAvoid = currentResult.rerunWithoutRules.pop();
- if (!nextRuleToAvoid) {
- // rerunWithoutRules being undefined or empty should lead to early return up above
- throw new Error('nextRuleToAvoid is undefined');
- }
-
- const nextRulesToVisit = new Set(
- Array.from(rulesToVisit).filter((rule) => rule !== nextRuleToAvoid)
- );
-
- /**
- * TODO:
- *
- * need to handle the issue where the previous results findings from things like
- * if we should suggest operators, persist. rn only the keywords are persisting
- * across runs
- *
- * might have to fix the issue in a way where we grab all characteristics across runs,
- * this thing might break for ppl if we need characteristics from later runs, where we
- * unlease descendants
- */
+ return enrichAutocompleteResult(result, rules, tokenStream, cursorTokenIndex, cursor, query);
+};
- // call parseQuery again without specified rule and with this run's result
- return parseQuery({
+export const parseQuery = <
+ A extends AutocompleteResultBase,
+ L extends LexerType,
+ P extends ParserType
+>({
+ Lexer,
+ Parser,
+ tokenDictionary,
+ ignoredTokens,
+ rulesToVisit,
+ getParseTree,
+ enrichAutocompleteResult,
+ query,
+ cursor,
+ context,
+}: ParsingSubject): AutocompleteResultBase => {
+ const result = singleParseQuery({
Lexer,
Parser,
tokenDictionary,
ignoredTokens,
- rulesToVisit: nextRulesToVisit,
+ rulesToVisit,
getParseTree,
enrichAutocompleteResult,
query,
cursor,
context,
- previousResultKeywords: currentResult,
});
+
+ if (!result.rerunWithoutRules) {
+ return result;
+ }
+
+ // go through each rule in list and run a singleParseQuery without it, combining results
+ result.rerunWithoutRules.forEach((rule) => {
+ const modifiedRulesToVisit = new Set(rulesToVisit);
+ modifiedRulesToVisit.delete(rule);
+
+ const nextResult = singleParseQuery({
+ Lexer,
+ Parser,
+ tokenDictionary,
+ ignoredTokens,
+ rulesToVisit: modifiedRulesToVisit,
+ getParseTree,
+ enrichAutocompleteResult,
+ query,
+ cursor,
+ context,
+ });
+
+ // combine result and nextResult
+ for (const [field, value] of Object.entries(result)) {
+ if (field === 'suggestColumns') {
+ // combine tables inside of suggestColumns
+ result.suggestColumns = {
+ tables: [
+ ...(result.suggestColumns?.tables ?? []),
+ ...(nextResult.suggestColumns?.tables ?? []),
+ ],
+ };
+ continue;
+ }
+
+ switch (typeof value) {
+ case 'boolean':
+ // if a boolean is true, keep overall result true
+ result[field as keyof A] ||= nextResult[field as keyof A];
+ break;
+ case 'undefined':
+ // use the latter result
+ result[field as keyof A] = nextResult[field as keyof A];
+ break;
+ case 'object':
+ if (Array.isArray(value)) {
+ // combine arrays
+ const combined = [
+ ...((result[field as keyof A] as any[]) ?? []),
+ ...((nextResult[field as keyof A] as any[]) ?? []),
+ ];
+ // ES6 magic to filter out duplicate objects based on id field
+ (result[field as keyof A] as any[]) = combined.filter(
+ (item, index, self) => index === self.findIndex((other) => other.id === item.id)
+ );
+ break;
+ }
+ default:
+ // use the initial result
+ break;
+ }
+ }
+ });
+
+ return result;
};