diff --git a/src/autocomplete/autocomplete-types.ts b/src/autocomplete/autocomplete-types.ts index fca53558..5dadca19 100644 --- a/src/autocomplete/autocomplete-types.ts +++ b/src/autocomplete/autocomplete-types.ts @@ -27,13 +27,13 @@ export interface AutocompleteResultBase { export interface MySqlAutocompleteResult extends AutocompleteResultBase { suggestIndexes?: boolean; suggestTriggers?: boolean; - suggestConstraints?: boolean; + suggestConstraints?: ConstraintSuggestion; } export interface PostgreSqlAutocompleteResult extends AutocompleteResultBase { suggestIndexes?: boolean; suggestTriggers?: boolean; - suggestConstraints?: boolean; + suggestConstraints?: ConstraintSuggestion; suggestSequences?: boolean; suggestSchemas?: boolean; } @@ -50,10 +50,19 @@ export interface KeywordSuggestion { value: string; } -export interface ColumnSuggestion { - tables?: {name: string; alias?: string}[]; +export interface Table { + name: string; + alias?: string; +} + +export interface TableContextSuggestion { + tables?: Table[]; } +export type ColumnSuggestion = TableContextSuggestion; + +export type ConstraintSuggestion = TableContextSuggestion; + export enum TableOrViewSuggestion { ALL = 'ALL', TABLES = 'TABLES', @@ -80,36 +89,45 @@ export interface ISymbolTableVisitor { scope: c3.ScopedSymbol; } +export type SymbolTableVisitor = ISymbolTableVisitor & AbstractParseTreeVisitor<{}>; + export type GetParseTree
= (
parser: P,
type?: TableQueryPosition['type'] | 'select',
) => ParseTree;
-export type GenerateSuggestionsFromRulesResult = Partial & {
+export type ProcessVisitedRulesResult = Partial & {
shouldSuggestColumns?: boolean;
shouldSuggestColumnAliases?: boolean;
+ shouldSuggestConstraints?: boolean;
};
-export type GenerateSuggestionsFromRules = (
+export type ProcessVisitedRules = (
rules: c3.CandidatesCollection['rules'],
cursorTokenIndex: number,
tokenStream: TokenStream,
-) => GenerateSuggestionsFromRulesResult;
+) => ProcessVisitedRulesResult;
+
+export type EnrichAutocompleteResult = (
+ result: AutocompleteResultBase,
+ rules: c3.CandidatesCollection['rules'],
+ tokenStream: TokenStream,
+ cursorTokenIndex: number,
+ cursor: CursorPosition,
+ query: string,
+) => A;
export interface AutocompleteData<
A extends AutocompleteResultBase,
L extends LexerType,
P extends ParserType,
- S extends ISymbolTableVisitor & AbstractParseTreeVisitor<{}>,
> {
Lexer: LexerConstructor ;
- SymbolTableVisitor: SymbolTableVisitorConstructor ;
tokenDictionary: TokenDictionary;
- generateSuggestionsFromRules: GenerateSuggestionsFromRules;
ignoredTokens: Set ,
query: string,
): Pick ,
- SymbolTableVisitor: SymbolTableVisitorConstructor ,
- initialTokenStream: TokenStream,
- cursor: CursorPosition,
- initialQuery: string,
- shouldSuggestColumnAliases?: boolean,
-): Pick ,
- SymbolTableVisitor: SymbolTableVisitorConstructor ,
- generateSuggestionsFromRules: GenerateSuggestionsFromRules,
+ enrichAutocompleteResult: EnrichAutocompleteResult,
query: string,
cursor: CursorPosition,
-): AutocompleteResultBase {
- const inputStream = CharStreams.fromString(query);
- const lexer = new Lexer(inputStream);
- const tokenStream = new CommonTokenStream(lexer);
- const parser = new Parser(tokenStream);
+): A {
+ const parser = createParser(Lexer, Parser, query);
+ const {tokenStream} = parser;
const errorListener = new SqlErrorListener(tokenDictionary.SPACE);
parser.removeErrorListeners();
@@ -182,12 +64,8 @@ export function parseQuery<
const core = new c3.CodeCompletionCore(parser);
core.ignoredTokens = ignoredTokens;
- core.preferredRules = preferredRules;
+ core.preferredRules = rulesToVisit;
const cursorTokenIndex = findCursorTokenIndex(tokenStream, cursor, tokenDictionary.SPACE);
- const suggestKeywords: KeywordSuggestion[] = [];
- let result: AutocompleteResultBase = {
- errors: errorListener.errors,
- };
if (cursorTokenIndex === undefined) {
throw new Error(
@@ -195,10 +73,8 @@ export function parseQuery<
);
}
+ const suggestKeywords: KeywordSuggestion[] = [];
const {tokens, rules} = core.collectCandidates(cursorTokenIndex);
- const {shouldSuggestColumns, shouldSuggestColumnAliases, ...suggestionsFromRules} =
- generateSuggestionsFromRules(rules, cursorTokenIndex, tokenStream);
- result = {...result, ...suggestionsFromRules};
tokens.forEach((_, tokenType) => {
// Literal keyword names are quoted
const literalName = parser.vocabulary.getLiteralName(tokenType)?.replace(quotesRegex, '$1');
@@ -214,33 +90,12 @@ export function parseQuery<
});
});
- const cursorIndex = getCursorIndex(query, cursor);
- // We can get this by token instead of splitting the string
- const currentStatement = getCurrentStatement(query, cursorIndex);
-
- if (shouldSuggestColumns) {
- const {suggestColumns, suggestColumnAliases} = getColumnSuggestions(
- Lexer,
- Parser,
- SymbolTableVisitor,
- tokenDictionary,
- explicitlyParseJoin,
- getParseTree,
- tokenStream,
- cursor,
- query,
- shouldSuggestColumnAliases,
- );
- result.suggestColumns = suggestColumns;
- result.suggestColumnAliases = suggestColumnAliases;
- }
+ const result: AutocompleteResultBase = {
+ errors: errorListener.errors,
+ suggestKeywords,
+ };
- result.suggestTemplates = shouldSuggestTemplates(
- currentStatement.statement,
- currentStatement.cursorIndex,
- );
- result.suggestKeywords = suggestKeywords;
- return result;
+ return enrichAutocompleteResult(result, rules, tokenStream, cursorTokenIndex, cursor, query);
}
export function parseMySqlQueryWithoutCursor(
@@ -259,13 +114,11 @@ export function parseMySqlQuery(query: string, cursor: CursorPosition): MySqlAut
return parseQuery(
mySqlAutocompleteData.Lexer,
mySqlAutocompleteData.Parser,
- mySqlAutocompleteData.SymbolTableVisitor,
mySqlAutocompleteData.tokenDictionary,
mySqlAutocompleteData.ignoredTokens,
- mySqlAutocompleteData.preferredRules,
- mySqlAutocompleteData.explicitlyParseJoin,
+ mySqlAutocompleteData.rulesToVisit,
mySqlAutocompleteData.getParseTree,
- mySqlAutocompleteData.generateSuggestionsFromRules,
+ mySqlAutocompleteData.enrichAutocompleteResult,
query,
cursor,
);
@@ -290,13 +143,11 @@ export function parsePostgreSqlQuery(
return parseQuery(
postgreSqlAutocompleteData.Lexer,
postgreSqlAutocompleteData.Parser,
- postgreSqlAutocompleteData.SymbolTableVisitor,
postgreSqlAutocompleteData.tokenDictionary,
postgreSqlAutocompleteData.ignoredTokens,
- postgreSqlAutocompleteData.preferredRules,
- postgreSqlAutocompleteData.explicitlyParseJoin,
+ postgreSqlAutocompleteData.rulesToVisit,
postgreSqlAutocompleteData.getParseTree,
- postgreSqlAutocompleteData.generateSuggestionsFromRules,
+ postgreSqlAutocompleteData.enrichAutocompleteResult,
query,
cursor,
);
@@ -321,13 +172,11 @@ export function parseClickHouseQuery(
return parseQuery(
clickHouseAutocompleteData.Lexer,
clickHouseAutocompleteData.Parser,
- clickHouseAutocompleteData.SymbolTableVisitor,
clickHouseAutocompleteData.tokenDictionary,
clickHouseAutocompleteData.ignoredTokens,
- clickHouseAutocompleteData.preferredRules,
- clickHouseAutocompleteData.explicitlyParseJoin,
+ clickHouseAutocompleteData.rulesToVisit,
clickHouseAutocompleteData.getParseTree,
- clickHouseAutocompleteData.generateSuggestionsFromRules,
+ clickHouseAutocompleteData.enrichAutocompleteResult,
query,
cursor,
);
diff --git a/src/autocomplete/databases/clickhouse/clickhouse-autocomplete.ts b/src/autocomplete/databases/clickhouse/clickhouse-autocomplete.ts
index ae475455..553b0b3c 100644
--- a/src/autocomplete/databases/clickhouse/clickhouse-autocomplete.ts
+++ b/src/autocomplete/databases/clickhouse/clickhouse-autocomplete.ts
@@ -4,9 +4,11 @@ import * as c3 from 'antlr4-c3';
import {ColumnAliasSymbol, TableSymbol} from '../../shared/symbol-table.js';
import {
AutocompleteData,
+ AutocompleteResultBase,
ClickHouseAutocompleteResult,
- GenerateSuggestionsFromRulesResult,
+ CursorPosition,
ISymbolTableVisitor,
+ ProcessVisitedRulesResult,
TableOrViewSuggestion,
} from '../../autocomplete-types.js';
import {ClickHouseLexer} from './generated/ClickHouseLexer.js';
@@ -17,8 +19,14 @@ import {
TableIdentifierContext,
} from './generated/ClickHouseParser.js';
import {ClickHouseParserVisitor} from './generated/ClickHouseParserVisitor.js';
-import {TableQueryPosition, TokenDictionary, getPreviousToken} from '../../shared/tables.js';
+import {
+ TableQueryPosition,
+ TokenDictionary,
+ getContextSuggestions,
+ getPreviousToken,
+} from '../../shared/tables.js';
import {isStartingToWriteRule} from '../../shared/cursor';
+import {shouldSuggestTemplates} from '../../shared/query.js';
const engines = ['Null', 'Set', 'Log', 'Memory', 'TinyLog', 'StripeLog'];
@@ -83,7 +91,7 @@ function getIgnoredTokens(): number[] {
const ignoredTokens = new Set(getIgnoredTokens());
-const preferredRules = new Set([
+const rulesToVisit = new Set([
ClickHouseParser.RULE_databaseIdentifier,
ClickHouseParser.RULE_tableIdentifier,
ClickHouseParser.RULE_identifier,
@@ -151,11 +159,11 @@ class ClickHouseSymbolTableVisitor
};
}
-function generateSuggestionsFromRules(
+function processVisitedRules(
rules: c3.CandidatesCollection['rules'],
cursorTokenIndex: number,
tokenStream: TokenStream,
-): GenerateSuggestionsFromRulesResult ,
+ query: string,
+): P {
+ const inputStream = CharStreams.fromString(query);
+ const lexer = new Lexer(inputStream);
+ const tokenStream = new CommonTokenStream(lexer);
+ const parser = new Parser(tokenStream);
+
+ parser.removeErrorListeners();
+
+ return parser;
+}
diff --git a/src/autocomplete/shared/symbol-table.ts b/src/autocomplete/shared/symbol-table.ts
index 677c288a..fd113802 100644
--- a/src/autocomplete/shared/symbol-table.ts
+++ b/src/autocomplete/shared/symbol-table.ts
@@ -1,4 +1,5 @@
import * as c3 from 'antlr4-c3';
+import {ColumnAliasSuggestion, SymbolTableVisitor, Table} from '../autocomplete-types';
export class TableSymbol extends c3.TypedSymbol {
name: string;
@@ -12,6 +13,13 @@ export class TableSymbol extends c3.TypedSymbol {
}
}
+export function getTablesFromSymbolTable(visitor: SymbolTableVisitor): Table[] {
+ return visitor.symbolTable.getNestedSymbolsOfTypeSync(TableSymbol).map((tableSymbol) => ({
+ name: tableSymbol.name,
+ alias: tableSymbol.alias,
+ }));
+}
+
export class ColumnAliasSymbol extends c3.TypedSymbol {
name: string;
@@ -21,3 +29,11 @@ export class ColumnAliasSymbol extends c3.TypedSymbol {
this.name = name;
}
}
+
+export function getColumnAliasesFromSymbolTable(
+ visitor: SymbolTableVisitor,
+): ColumnAliasSuggestion[] {
+ return visitor.symbolTable
+ .getNestedSymbolsOfTypeSync(ColumnAliasSymbol)
+ .map(({name}) => ({name}));
+}
diff --git a/src/autocomplete/shared/tables.ts b/src/autocomplete/shared/tables.ts
index c7484ed3..f867f14d 100644
--- a/src/autocomplete/shared/tables.ts
+++ b/src/autocomplete/shared/tables.ts
@@ -1,4 +1,16 @@
-import {Token, TokenStream} from 'antlr4ng';
+import {Lexer as LexerType, Parser as ParserType, Token, TokenStream} from 'antlr4ng';
+import {findCursorTokenIndex} from './cursor';
+import {createParser} from './query';
+import {getColumnAliasesFromSymbolTable, getTablesFromSymbolTable} from './symbol-table';
+import {
+ AutocompleteResultBase,
+ CursorPosition,
+ GetParseTree,
+ LexerConstructor,
+ ParserConstructor,
+ SymbolTableVisitor,
+ TableContextSuggestion,
+} from '../autocomplete-types';
interface TableQueryPositionBase {
start: number;
@@ -224,3 +236,82 @@ export function getPreviousToken(
return undefined;
}
+
+interface ContextSuggestions {
+ tableContextSuggestion?: TableContextSuggestion;
+ suggestColumnAliases?: AutocompleteResultBase['suggestColumnAliases'];
+}
+
+export function getContextSuggestions ,
+ symbolTableVisitor: SymbolTableVisitor,
+ tokenDictionary: TokenDictionary,
+ getParseTree: GetParseTree ,
+ tokenStream: TokenStream,
+ cursor: CursorPosition,
+ query: string,
+ explicitlyParseJoin?: boolean,
+): ContextSuggestions {
+ // The actual token index, without special logic for spaces
+ const actualCursorTokenIndex = findCursorTokenIndex(
+ tokenStream,
+ cursor,
+ tokenDictionary.SPACE,
+ true,
+ );
+ if (!actualCursorTokenIndex) {
+ throw new Error(
+ `Could not find actualCursorTokenIndex at Ln ${cursor.line}, Col ${cursor.column}`,
+ );
+ }
+
+ const contextSuggestions: ContextSuggestions = {};
+ const tableQueryPosition = getTableQueryPosition(
+ tokenStream,
+ actualCursorTokenIndex,
+ tokenDictionary,
+ );
+
+ if (tableQueryPosition) {
+ const tableQuery = query.slice(tableQueryPosition.start, tableQueryPosition.end);
+ const parser = createParser(Lexer, Parser, tableQuery);
+ const parseTree = getParseTree(parser, tableQueryPosition.type);
+
+ symbolTableVisitor.visit(parseTree);
+
+ if (explicitlyParseJoin && tableQueryPosition.joinTableQueryPosition) {
+ const joinTableQuery = query.slice(
+ tableQueryPosition.joinTableQueryPosition.start,
+ tableQueryPosition.joinTableQueryPosition.end,
+ );
+ const joinParser = createParser(Lexer, Parser, joinTableQuery);
+ const joinParseTree = getParseTree(joinParser, 'from');
+ symbolTableVisitor.visit(joinParseTree);
+ }
+
+ if (tableQueryPosition.selectTableQueryPosition) {
+ const selectTableQuery = query.slice(
+ tableQueryPosition.selectTableQueryPosition.start,
+ tableQueryPosition.selectTableQueryPosition.end,
+ );
+ const selectParser = createParser(Lexer, Parser, selectTableQuery);
+ const selectParseTree = getParseTree(selectParser, 'select');
+ symbolTableVisitor.visit(selectParseTree);
+ }
+
+ const tables = getTablesFromSymbolTable(symbolTableVisitor);
+ if (tables.length) {
+ contextSuggestions.tableContextSuggestion = {
+ tables,
+ };
+ }
+
+ const columnAliases = getColumnAliasesFromSymbolTable(symbolTableVisitor);
+ if (columnAliases.length) {
+ contextSuggestions.suggestColumnAliases = columnAliases.map(({name}) => ({name}));
+ }
+ }
+
+ return contextSuggestions;
+}
diff --git a/src/index.ts b/src/index.ts
index 76750462..21655a18 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -19,4 +19,6 @@ export {
ColumnAliasSuggestion,
EngineSuggestion,
CursorPosition,
+ ConstraintSuggestion,
+ Table,
} from './autocomplete/autocomplete-types';
;
getParseTree: GetParseTree,
- tokenDictionary: TokenDictionary,
- explicitlyParseJoin: boolean,
- getParseTree: GetParseTree,
tokenDictionary: TokenDictionary,
ignoredTokens: Set