diff --git a/src/parser/common/basicParser.ts b/src/parser/common/basicParser.ts index be783a2..de1d7da 100644 --- a/src/parser/common/basicParser.ts +++ b/src/parser/common/basicParser.ts @@ -277,55 +277,74 @@ export default abstract class BasicParser< /** * Split sql by statement. - * Try to collect candidates from the caret statement only. + * Try to collect candidates in as small a range as possible. */ this.listen(splitListener, this._parseTree); + const statementCount = splitListener.statementsContext?.length; + const statementsContext = splitListener.statementsContext; // If there are multiple statements. - if (splitListener.statementsContext.length > 1) { - // find statement rule context where caretPosition is located. - const caretStatementContext = splitListener?.statementsContext.find((ctx) => { - return ( - caretTokenIndex <= ctx.stop?.tokenIndex && - caretTokenIndex >= ctx.start.tokenIndex - ); - }); + if (statementCount > 1) { + /** + * Find a minimum valid range, reparse the fragment, and provide a new parse tree to C3. + * The boundaries of this range must be statements with no syntax errors. + * This can ensure the stable performance of the C3. + */ + let startStatement: ParserRuleContext; + let stopStatement: ParserRuleContext; + + for (let index = 0; index < statementCount; index++) { + const ctx = statementsContext[index]; + const isCurrentCtxValid = !ctx.exception; + if (!isCurrentCtxValid) continue; - if (caretStatementContext) { - c3Context = caretStatementContext; - } else { - const lastStatementToken = - splitListener.statementsContext[splitListener?.statementsContext.length - 1] - .start; /** - * If caretStatementContext is not found and it follows all statements. - * Reparses part of the input following the penultimate statement. - * And c3 will collect candidates in the new parseTreeContext. + * Ensure that the statementContext before the left boundary + * and the last statementContext on the right boundary are qualified SQL statements. */ - if (caretTokenIndex > lastStatementToken?.tokenIndex) { - /** - * Save offset of the tokenIndex in the partInput - * compared to the tokenIndex in the whole input - */ - tokenIndexOffset = lastStatementToken?.tokenIndex; - // Correct caretTokenIndex - caretTokenIndex = caretTokenIndex - tokenIndexOffset; + const isPrevCtxValid = index === 0 || !statementsContext[index - 1]?.exception; + const isNextCtxValid = + index === statementCount - 1 || !statementsContext[index + 1]?.exception; - const inputSlice = input.slice(lastStatementToken.startIndex); - const lexer = this.createLexer(inputSlice); - lexer.removeErrorListeners(); + if (ctx.stop.tokenIndex < caretTokenIndex && isPrevCtxValid) { + startStatement = ctx; + } - const tokenStream = new CommonTokenStream(lexer); - tokenStream.fill(); - const parser = this.createParserFromTokenStream(tokenStream); - parser.removeErrorListeners(); - parser.buildParseTree = true; - parser.errorHandler = new ErrorStrategy(); - - sqlParserIns = parser; - c3Context = parser.program(); + if (!stopStatement && ctx.start.tokenIndex > caretTokenIndex && isNextCtxValid) { + stopStatement = ctx; + break; } } + + // A boundary consisting of the index of the input. + const startIndex = startStatement?.start?.startIndex ?? 0; + const stopIndex = stopStatement?.stop?.stopIndex ?? input.length - 1; + + /** + * Save offset of the tokenIndex in the range of input + * compared to the tokenIndex in the whole input + */ + tokenIndexOffset = startStatement?.start?.tokenIndex ?? 0; + caretTokenIndex = caretTokenIndex - tokenIndexOffset; + + /** + * Reparse the input fragment, + * and c3 will collect candidates in the newly generated parseTree. + */ + const inputSlice = input.slice(startIndex, stopIndex); + + const lexer = this.createLexer(inputSlice); + lexer.removeErrorListeners(); + const tokenStream = new CommonTokenStream(lexer); + tokenStream.fill(); + + const parser = this.createParserFromTokenStream(tokenStream); + parser.removeErrorListeners(); + parser.buildParseTree = true; + parser.errorHandler = new ErrorStrategy(); + + sqlParserIns = parser; + c3Context = parser.program(); } const core = new CodeCompletionCore(sqlParserIns); diff --git a/test/parser/flinksql/suggestion/fixtures/multipleSql.sql b/test/parser/flinksql/suggestion/fixtures/multipleSql.sql deleted file mode 100644 index a3975de..0000000 --- a/test/parser/flinksql/suggestion/fixtures/multipleSql.sql +++ /dev/null @@ -1,19 +0,0 @@ -CREATE TABLE orders ( - order_uid BIGINT, - product_id BIGINT, - price DECIMAL(32, 2), - order_time TIMESTAMP(3) -) WITH ( - 'connector' = 'datagen' -); - -CREATE TABLE orders ( - order_uid BIGINT, - product_id BIGINT, - price DECIMAL(32, 2), - order_time TIMESTAMP(3) -) WITH ( - 'connector' = 'datagen' -); - -use cat1. \ No newline at end of file diff --git a/test/parser/flinksql/suggestion/fixtures/multipleStatement.sql b/test/parser/flinksql/suggestion/fixtures/multipleStatement.sql new file mode 100644 index 0000000..3008bd7 --- /dev/null +++ b/test/parser/flinksql/suggestion/fixtures/multipleStatement.sql @@ -0,0 +1,31 @@ +SELECT * FROM -- unfinished + +CREATE TEMPORARY VIEW IF NOT EXISTS v AS SELECT col1 FROM tbl; + +CREATE TEMPORARY TABLE client_errors ( + log_time TIMESTAMP(3), + request_line STRING, + status_code STRING, + size INT +) WITH ( + 'connector' = 'stream-x' +); + +ALTER VIEW v1 RENAME TO v2; + +CREATE TABLE db. ; -- unfinished + +LOAD MODULE CORE; + +REMOVE JAR '.jar' + +INSERT INTO VALUES (100, 99.9 / 10, 'abc', true, now ()); -- unfinished + +CREATE DATABASE IF NOT EXISTS dataApi COMMENT 'test create database' WITH ('key1' = 'value1', 'key2.a' = 'value2.a'); + +DROP DATABASE IF EXISTS Orders RESTRICT; + +INSERT INTO country_page_view +SELECT `user`, + cnt +FROM db. ; -- unfinished \ No newline at end of file diff --git a/test/parser/flinksql/suggestion/multipleStatement.test.ts b/test/parser/flinksql/suggestion/multipleStatement.test.ts new file mode 100644 index 0000000..29ed999 --- /dev/null +++ b/test/parser/flinksql/suggestion/multipleStatement.test.ts @@ -0,0 +1,69 @@ +import fs from 'fs'; +import path from 'path'; +import { CaretPosition, SyntaxContextType } from '../../../../src/parser/common/basic-parser-types'; +import FlinkSQL from '../../../../src/parser/flinksql'; + +const syntaxSql = fs.readFileSync( + path.join(__dirname, 'fixtures', 'multipleStatement.sql'), + 'utf-8' +); + +describe('FlinkSQL Multiple Statements Syntax Suggestion', () => { + const parser = new FlinkSQL(); + + test('Select from table ', () => { + const pos: CaretPosition = { + lineNumber: 1, + column: 15, + }; + const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE + ); + + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual([]); + }); + + test('Create table ', () => { + const pos: CaretPosition = { + lineNumber: 16, + column: 17, + }; + const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE_CREATE + ); + + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.']); + }); + + test('Insert into table ', () => { + const pos: CaretPosition = { + lineNumber: 22, + column: 13, + }; + const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE + ); + + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual([]); + }); + + test('Insert into select from table ', () => { + const pos: CaretPosition = { + lineNumber: 31, + column: 9, + }; + const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE + ); + + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.']); + }); +}); diff --git a/test/parser/flinksql/suggestion/syntaxSuggestion.test.ts b/test/parser/flinksql/suggestion/syntaxSuggestion.test.ts index 185f296..b223fac 100644 --- a/test/parser/flinksql/suggestion/syntaxSuggestion.test.ts +++ b/test/parser/flinksql/suggestion/syntaxSuggestion.test.ts @@ -8,7 +8,6 @@ const syntaxSql = fs.readFileSync( path.join(__dirname, 'fixtures', 'syntaxSuggestion.sql'), 'utf-8' ); -const multipleSql = fs.readFileSync(path.join(__dirname, 'fixtures', 'multipleSql.sql'), 'utf-8'); describe('Flink SQL Syntax Suggestion', () => { const parser = new FlinkSQL(); @@ -19,20 +18,6 @@ describe('Flink SQL Syntax Suggestion', () => { expect(parser.validate(syntaxSql).length).not.toBe(0); }); - test('Multiple SQL use database', () => { - const pos: CaretPosition = { - lineNumber: 19, - column: 10, - }; - const syntaxes = parser.getSuggestionAtCaretPosition(multipleSql, pos)?.syntax; - const suggestion = syntaxes?.find( - (syn) => syn.syntaxContextType === SyntaxContextType.DATABASE - ); - - expect(suggestion).not.toBeUndefined(); - expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['cat1', '.']); - }); - test('Drop catalog', () => { const pos: CaretPosition = { lineNumber: 1, diff --git a/test/parser/flinksql/suggestion/tokenSuggestion.test.ts b/test/parser/flinksql/suggestion/tokenSuggestion.test.ts index b386f5e..bb47b07 100644 --- a/test/parser/flinksql/suggestion/tokenSuggestion.test.ts +++ b/test/parser/flinksql/suggestion/tokenSuggestion.test.ts @@ -2,6 +2,7 @@ import fs from 'fs'; import path from 'path'; import { CaretPosition } from '../../../../src/parser/common/basic-parser-types'; import FlinkSQL from '../../../../src/parser/flinksql'; +import { commentOtherLine } from '../../../helper'; const tokenSql = fs.readFileSync(path.join(__dirname, 'fixtures', 'tokenSuggestion.sql'), 'utf-8'); @@ -13,7 +14,10 @@ describe('Flink SQL Token Suggestion', () => { lineNumber: 3, column: 5, }; - const suggestion = parser.getSuggestionAtCaretPosition(tokenSql, pos)?.keywords; + const suggestion = parser.getSuggestionAtCaretPosition( + commentOtherLine(tokenSql, pos.lineNumber), + pos + )?.keywords; expect(suggestion).toEqual(['MODULES', 'CATALOG']); }); @@ -23,7 +27,10 @@ describe('Flink SQL Token Suggestion', () => { lineNumber: 5, column: 8, }; - const suggestion = parser.getSuggestionAtCaretPosition(tokenSql, pos)?.keywords; + const suggestion = parser.getSuggestionAtCaretPosition( + commentOtherLine(tokenSql, pos.lineNumber), + pos + )?.keywords; expect(suggestion).toEqual([ 'CATALOG', @@ -40,7 +47,10 @@ describe('Flink SQL Token Suggestion', () => { lineNumber: 7, column: 6, }; - const suggestion = parser.getSuggestionAtCaretPosition(tokenSql, pos)?.keywords; + const suggestion = parser.getSuggestionAtCaretPosition( + commentOtherLine(tokenSql, pos.lineNumber), + pos + )?.keywords; expect(suggestion).toEqual([ 'MODULES', diff --git a/test/parser/hive/suggestion/fixtures/multipleStatement.sql b/test/parser/hive/suggestion/fixtures/multipleStatement.sql new file mode 100644 index 0000000..63a63fb --- /dev/null +++ b/test/parser/hive/suggestion/fixtures/multipleStatement.sql @@ -0,0 +1,21 @@ +SELECT * FROM -- unfinished + +CREATE VIEW mydb.bro_view AS SELECT * FROM mydb.sale_tbl; + +CREATE TEMPORARY EXTERNAL TABLE list_bucket_multiple (col1 STRING, col2 INT, col3 STRING); + +ALTER VIEW myview1 SET TBLPROPERTIES ('author'='hayden','date'='2023-09-04') + +CREATE TABLE db. ; -- unfinished + +DROP CONNECTOR connector1; + +SET ROLE `admin`; + +INSERT INTO VALUES (100, 99.9 / 10, 'abc', true, now ()); -- unfinished + +ALTER TABLE tbl1 RENAME TO tbl2; + +ALTER SCHEMA database_name SET OWNER USER `admin`; + +INSERT OVERWRITE LOCAL DIRECTORY '/path/to/output' SELECT col1, col2 FROM ; -- unfinished \ No newline at end of file diff --git a/test/parser/hive/suggestion/multipleStatement.test.ts b/test/parser/hive/suggestion/multipleStatement.test.ts new file mode 100644 index 0000000..75125e4 --- /dev/null +++ b/test/parser/hive/suggestion/multipleStatement.test.ts @@ -0,0 +1,69 @@ +import fs from 'fs'; +import path from 'path'; +import { CaretPosition, SyntaxContextType } from '../../../../src/parser/common/basic-parser-types'; +import HiveSQL from '../../../../src/parser/hive'; + +const syntaxSql = fs.readFileSync( + path.join(__dirname, 'fixtures', 'multipleStatement.sql'), + 'utf-8' +); + +describe('HiveSQL Multiple Statements Syntax Suggestion', () => { + const parser = new HiveSQL(); + + test('Select from table ', () => { + const pos: CaretPosition = { + lineNumber: 1, + column: 15, + }; + const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE + ); + + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual([]); + }); + + test('Create table ', () => { + const pos: CaretPosition = { + lineNumber: 9, + column: 17, + }; + const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE_CREATE + ); + + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.']); + }); + + test('Insert into table ', () => { + const pos: CaretPosition = { + lineNumber: 15, + column: 13, + }; + const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE + ); + + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual([]); + }); + + test('Insert into select from table ', () => { + const pos: CaretPosition = { + lineNumber: 21, + column: 75, + }; + const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE + ); + + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual([]); + }); +}); diff --git a/test/parser/hive/suggestion/tokenSuggestion.test.ts b/test/parser/hive/suggestion/tokenSuggestion.test.ts index 7b7782d..b813f2e 100644 --- a/test/parser/hive/suggestion/tokenSuggestion.test.ts +++ b/test/parser/hive/suggestion/tokenSuggestion.test.ts @@ -2,6 +2,7 @@ import fs from 'fs'; import path from 'path'; import { CaretPosition } from '../../../../src/parser/common/basic-parser-types'; import HiveSQL from '../../../../src/parser/hive'; +import { commentOtherLine } from '../../../helper'; const tokenSql = fs.readFileSync(path.join(__dirname, 'fixtures', 'tokenSuggestion.sql'), 'utf-8'); @@ -13,7 +14,10 @@ describe('Hive SQL Token Suggestion', () => { lineNumber: 1, column: 7, }; - const suggestion = parser.getSuggestionAtCaretPosition(tokenSql, pos)?.keywords; + const suggestion = parser.getSuggestionAtCaretPosition( + commentOtherLine(tokenSql, pos.lineNumber), + pos + )?.keywords; expect(suggestion).toEqual([ 'APPLICATION', 'GROUP', @@ -37,7 +41,10 @@ describe('Hive SQL Token Suggestion', () => { lineNumber: 3, column: 8, }; - const suggestion = parser.getSuggestionAtCaretPosition(tokenSql, pos)?.keywords; + const suggestion = parser.getSuggestionAtCaretPosition( + commentOtherLine(tokenSql, pos.lineNumber), + pos + )?.keywords; expect(suggestion).toEqual([ 'CONNECTOR', 'APPLICATION', @@ -69,7 +76,10 @@ describe('Hive SQL Token Suggestion', () => { lineNumber: 5, column: 8, }; - const suggestion = parser.getSuggestionAtCaretPosition(tokenSql, pos)?.keywords; + const suggestion = parser.getSuggestionAtCaretPosition( + commentOtherLine(tokenSql, pos.lineNumber), + pos + )?.keywords; expect(suggestion).toEqual(['FROM']); }); @@ -78,7 +88,10 @@ describe('Hive SQL Token Suggestion', () => { lineNumber: 7, column: 10, }; - const suggestion = parser.getSuggestionAtCaretPosition(tokenSql, pos)?.keywords; + const suggestion = parser.getSuggestionAtCaretPosition( + commentOtherLine(tokenSql, pos.lineNumber), + pos + )?.keywords; expect(suggestion).toEqual([ 'EXTENDED', 'FORMATTED', @@ -94,7 +107,10 @@ describe('Hive SQL Token Suggestion', () => { lineNumber: 9, column: 6, }; - const suggestion = parser.getSuggestionAtCaretPosition(tokenSql, pos)?.keywords; + const suggestion = parser.getSuggestionAtCaretPosition( + commentOtherLine(tokenSql, pos.lineNumber), + pos + )?.keywords; expect(suggestion).toEqual([ 'CONNECTOR', 'APPLICATION', @@ -121,7 +137,10 @@ describe('Hive SQL Token Suggestion', () => { lineNumber: 11, column: 8, }; - const suggestion = parser.getSuggestionAtCaretPosition(tokenSql, pos)?.keywords; + const suggestion = parser.getSuggestionAtCaretPosition( + commentOtherLine(tokenSql, pos.lineNumber), + pos + )?.keywords; expect(suggestion).toEqual(['TABLE']); }); @@ -130,7 +149,10 @@ describe('Hive SQL Token Suggestion', () => { lineNumber: 13, column: 8, }; - const suggestion = parser.getSuggestionAtCaretPosition(tokenSql, pos)?.keywords; + const suggestion = parser.getSuggestionAtCaretPosition( + commentOtherLine(tokenSql, pos.lineNumber), + pos + )?.keywords; expect(suggestion).toEqual(['FROM', 'TABLE', 'EXTERNAL']); }); @@ -139,7 +161,10 @@ describe('Hive SQL Token Suggestion', () => { lineNumber: 15, column: 8, }; - const suggestion = parser.getSuggestionAtCaretPosition(tokenSql, pos)?.keywords; + const suggestion = parser.getSuggestionAtCaretPosition( + commentOtherLine(tokenSql, pos.lineNumber), + pos + )?.keywords; expect(suggestion).toEqual(['INTO', 'OVERWRITE']); }); @@ -148,7 +173,10 @@ describe('Hive SQL Token Suggestion', () => { lineNumber: 17, column: 6, }; - const suggestion = parser.getSuggestionAtCaretPosition(tokenSql, pos)?.keywords; + const suggestion = parser.getSuggestionAtCaretPosition( + commentOtherLine(tokenSql, pos.lineNumber), + pos + )?.keywords; expect(suggestion).toEqual(['DATA']); }); @@ -157,7 +185,10 @@ describe('Hive SQL Token Suggestion', () => { lineNumber: 19, column: 6, }; - const suggestion = parser.getSuggestionAtCaretPosition(tokenSql, pos)?.keywords; + const suggestion = parser.getSuggestionAtCaretPosition( + commentOtherLine(tokenSql, pos.lineNumber), + pos + )?.keywords; expect(suggestion).toEqual([ 'CURRENT', 'ROLES', diff --git a/test/parser/impala/suggestion/fixtures/multipleStatement.sql b/test/parser/impala/suggestion/fixtures/multipleStatement.sql new file mode 100644 index 0000000..a8efde3 --- /dev/null +++ b/test/parser/impala/suggestion/fixtures/multipleStatement.sql @@ -0,0 +1,21 @@ +SELECT * FROM -- unfinished + +CREATE VIEW my_view AS SELECT * FROM my_table; + +create table census (name string, census_year int) partitioned by (year int); + +ALTER VIEW v1 RENAME TO db2.v2; + +CREATE TABLE db. ; -- unfinished + +INSERT INTO target_table (col1, col2, col3) PARTITION (year = 2016, month IN (10, 11, 12)) SELECT * FROM dual; + +EXPLAIN INSERT INTO t1 VALUES (1, 'one'), (2, 'two'), (3, 'three'); + +INSERT INTO VALUES (100, 99.9 / 10, 'abc', true, now ()); -- unfinished + +ALTER TABLE my_table REPLACE COLUMNS (age INT COMMENT 'Updated Age'); + +ALTER DATABASE my_db SET OWNER USER 'impala'; + +INSERT INTO t2 (c1) SELECT c1 FROM t1.; -- unfinished \ No newline at end of file diff --git a/test/parser/impala/suggestion/multipleStatement.test.ts b/test/parser/impala/suggestion/multipleStatement.test.ts new file mode 100644 index 0000000..9f6a52b --- /dev/null +++ b/test/parser/impala/suggestion/multipleStatement.test.ts @@ -0,0 +1,69 @@ +import fs from 'fs'; +import path from 'path'; +import { CaretPosition, SyntaxContextType } from '../../../../src/parser/common/basic-parser-types'; +import ImpalaSQL from '../../../../src/parser/impala'; + +const syntaxSql = fs.readFileSync( + path.join(__dirname, 'fixtures', 'multipleStatement.sql'), + 'utf-8' +); + +describe('ImpalaSQL Multiple Statements Syntax Suggestion', () => { + const parser = new ImpalaSQL(); + + test('Select from table ', () => { + const pos: CaretPosition = { + lineNumber: 1, + column: 15, + }; + const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE + ); + + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual([]); + }); + + test('Create table ', () => { + const pos: CaretPosition = { + lineNumber: 9, + column: 17, + }; + const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE_CREATE + ); + + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.']); + }); + + test('Insert into table ', () => { + const pos: CaretPosition = { + lineNumber: 15, + column: 13, + }; + const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE + ); + + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual([]); + }); + + test('Insert into select from table ', () => { + const pos: CaretPosition = { + lineNumber: 21, + column: 39, + }; + const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE + ); + + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['t1', '.']); + }); +}); diff --git a/test/parser/impala/suggestion/syntaxSuggestion.test.ts b/test/parser/impala/suggestion/syntaxSuggestion.test.ts index 4d1e664..3a3a04a 100644 --- a/test/parser/impala/suggestion/syntaxSuggestion.test.ts +++ b/test/parser/impala/suggestion/syntaxSuggestion.test.ts @@ -2,6 +2,7 @@ import fs from 'fs'; import path from 'path'; import { CaretPosition, SyntaxContextType } from '../../../../src/parser/common/basic-parser-types'; import ImpalaSQL from '../../../../src/parser/impala'; +import { commentOtherLine } from '../../../helper'; const syntaxSql = fs.readFileSync( path.join(__dirname, 'fixtures', 'syntaxSuggestion.sql'), @@ -16,7 +17,10 @@ describe('Impala SQL Syntax Suggestion', () => { lineNumber: 1, column: 20, }; - const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const syntaxes = parser.getSuggestionAtCaretPosition( + commentOtherLine(syntaxSql, pos.lineNumber), + pos + )?.syntax; const suggestion = syntaxes?.find( (syn) => syn.syntaxContextType === SyntaxContextType.TABLE ); @@ -30,7 +34,10 @@ describe('Impala SQL Syntax Suggestion', () => { lineNumber: 3, column: 27, }; - const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const syntaxes = parser.getSuggestionAtCaretPosition( + commentOtherLine(syntaxSql, pos.lineNumber), + pos + )?.syntax; const suggestion = syntaxes?.find( (syn) => syn.syntaxContextType === SyntaxContextType.FUNCTION ); @@ -44,7 +51,10 @@ describe('Impala SQL Syntax Suggestion', () => { lineNumber: 5, column: 19, }; - const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const syntaxes = parser.getSuggestionAtCaretPosition( + commentOtherLine(syntaxSql, pos.lineNumber), + pos + )?.syntax; const suggestion = syntaxes?.find( (syn) => syn.syntaxContextType === SyntaxContextType.DATABASE ); @@ -58,7 +68,10 @@ describe('Impala SQL Syntax Suggestion', () => { lineNumber: 7, column: 21, }; - const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const syntaxes = parser.getSuggestionAtCaretPosition( + commentOtherLine(syntaxSql, pos.lineNumber), + pos + )?.syntax; const suggestion = syntaxes?.find( (syn) => syn.syntaxContextType === SyntaxContextType.TABLE ); @@ -72,7 +85,10 @@ describe('Impala SQL Syntax Suggestion', () => { lineNumber: 7, column: 39, }; - const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const syntaxes = parser.getSuggestionAtCaretPosition( + commentOtherLine(syntaxSql, pos.lineNumber), + pos + )?.syntax; const suggestion = syntaxes?.find( (syn) => syn.syntaxContextType === SyntaxContextType.COLUMN ); @@ -86,7 +102,10 @@ describe('Impala SQL Syntax Suggestion', () => { lineNumber: 9, column: 19, }; - const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const syntaxes = parser.getSuggestionAtCaretPosition( + commentOtherLine(syntaxSql, pos.lineNumber), + pos + )?.syntax; const suggestion = syntaxes?.find( (syn) => syn.syntaxContextType === SyntaxContextType.VIEW ); @@ -100,7 +119,10 @@ describe('Impala SQL Syntax Suggestion', () => { lineNumber: 11, column: 12, }; - const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const syntaxes = parser.getSuggestionAtCaretPosition( + commentOtherLine(syntaxSql, pos.lineNumber), + pos + )?.syntax; const suggestion = syntaxes?.find( (syn) => syn.syntaxContextType === SyntaxContextType.VIEW ); @@ -114,7 +136,10 @@ describe('Impala SQL Syntax Suggestion', () => { lineNumber: 13, column: 20, }; - const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const syntaxes = parser.getSuggestionAtCaretPosition( + commentOtherLine(syntaxSql, pos.lineNumber), + pos + )?.syntax; const suggestion = syntaxes?.find( (syn) => syn.syntaxContextType === SyntaxContextType.DATABASE ); @@ -128,7 +153,10 @@ describe('Impala SQL Syntax Suggestion', () => { lineNumber: 15, column: 20, }; - const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const syntaxes = parser.getSuggestionAtCaretPosition( + commentOtherLine(syntaxSql, pos.lineNumber), + pos + )?.syntax; const suggestion = syntaxes?.find( (syn) => syn.syntaxContextType === SyntaxContextType.TABLE ); @@ -142,7 +170,10 @@ describe('Impala SQL Syntax Suggestion', () => { lineNumber: 17, column: 22, }; - const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const syntaxes = parser.getSuggestionAtCaretPosition( + commentOtherLine(syntaxSql, pos.lineNumber), + pos + )?.syntax; const suggestion = syntaxes?.find( (syn) => syn.syntaxContextType === SyntaxContextType.FUNCTION ); @@ -156,7 +187,10 @@ describe('Impala SQL Syntax Suggestion', () => { lineNumber: 19, column: 21, }; - const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const syntaxes = parser.getSuggestionAtCaretPosition( + commentOtherLine(syntaxSql, pos.lineNumber), + pos + )?.syntax; const suggestion = syntaxes?.find( (syn) => syn.syntaxContextType === SyntaxContextType.TABLE ); @@ -170,7 +204,10 @@ describe('Impala SQL Syntax Suggestion', () => { lineNumber: 21, column: 15, }; - const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const syntaxes = parser.getSuggestionAtCaretPosition( + commentOtherLine(syntaxSql, pos.lineNumber), + pos + )?.syntax; const suggestion = syntaxes?.find( (syn) => syn.syntaxContextType === SyntaxContextType.VIEW_CREATE ); @@ -184,7 +221,10 @@ describe('Impala SQL Syntax Suggestion', () => { lineNumber: 23, column: 20, }; - const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const syntaxes = parser.getSuggestionAtCaretPosition( + commentOtherLine(syntaxSql, pos.lineNumber), + pos + )?.syntax; const suggestion = syntaxes?.find( (syn) => syn.syntaxContextType === SyntaxContextType.TABLE_CREATE ); @@ -198,7 +238,10 @@ describe('Impala SQL Syntax Suggestion', () => { lineNumber: 25, column: 20, }; - const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const syntaxes = parser.getSuggestionAtCaretPosition( + commentOtherLine(syntaxSql, pos.lineNumber), + pos + )?.syntax; const suggestion = syntaxes?.find( (syn) => syn.syntaxContextType === SyntaxContextType.FUNCTION_CREATE ); @@ -212,7 +255,10 @@ describe('Impala SQL Syntax Suggestion', () => { lineNumber: 27, column: 25, }; - const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const syntaxes = parser.getSuggestionAtCaretPosition( + commentOtherLine(syntaxSql, pos.lineNumber), + pos + )?.syntax; const suggestion = syntaxes?.find( (syn) => syn.syntaxContextType === SyntaxContextType.DATABASE_CREATE ); @@ -226,7 +272,10 @@ describe('Impala SQL Syntax Suggestion', () => { lineNumber: 29, column: 19, }; - const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const syntaxes = parser.getSuggestionAtCaretPosition( + commentOtherLine(syntaxSql, pos.lineNumber), + pos + )?.syntax; const suggestion = syntaxes?.find( (syn) => syn.syntaxContextType === SyntaxContextType.TABLE ); @@ -240,7 +289,10 @@ describe('Impala SQL Syntax Suggestion', () => { lineNumber: 31, column: 22, }; - const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const syntaxes = parser.getSuggestionAtCaretPosition( + commentOtherLine(syntaxSql, pos.lineNumber), + pos + )?.syntax; const suggestion = syntaxes?.find( (syn) => syn.syntaxContextType === SyntaxContextType.TABLE ); @@ -254,7 +306,10 @@ describe('Impala SQL Syntax Suggestion', () => { lineNumber: 33, column: 22, }; - const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const syntaxes = parser.getSuggestionAtCaretPosition( + commentOtherLine(syntaxSql, pos.lineNumber), + pos + )?.syntax; const suggestion = syntaxes?.find( (syn) => syn.syntaxContextType === SyntaxContextType.TABLE ); @@ -268,7 +323,10 @@ describe('Impala SQL Syntax Suggestion', () => { lineNumber: 35, column: 20, }; - const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const syntaxes = parser.getSuggestionAtCaretPosition( + commentOtherLine(syntaxSql, pos.lineNumber), + pos + )?.syntax; const suggestion = syntaxes?.find( (syn) => syn.syntaxContextType === SyntaxContextType.VIEW ); @@ -282,7 +340,10 @@ describe('Impala SQL Syntax Suggestion', () => { lineNumber: 37, column: 22, }; - const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const syntaxes = parser.getSuggestionAtCaretPosition( + commentOtherLine(syntaxSql, pos.lineNumber), + pos + )?.syntax; const suggestion = syntaxes?.find( (syn) => syn.syntaxContextType === SyntaxContextType.COLUMN ); @@ -296,7 +357,10 @@ describe('Impala SQL Syntax Suggestion', () => { lineNumber: 39, column: 22, }; - const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const syntaxes = parser.getSuggestionAtCaretPosition( + commentOtherLine(syntaxSql, pos.lineNumber), + pos + )?.syntax; const suggestion = syntaxes?.find( (syn) => syn.syntaxContextType === SyntaxContextType.COLUMN ); @@ -310,7 +374,10 @@ describe('Impala SQL Syntax Suggestion', () => { lineNumber: 41, column: 36, }; - const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const syntaxes = parser.getSuggestionAtCaretPosition( + commentOtherLine(syntaxSql, pos.lineNumber), + pos + )?.syntax; const suggestion = syntaxes?.find( (syn) => syn.syntaxContextType === SyntaxContextType.COLUMN_CREATE ); @@ -324,7 +391,10 @@ describe('Impala SQL Syntax Suggestion', () => { lineNumber: 43, column: 36, }; - const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const syntaxes = parser.getSuggestionAtCaretPosition( + commentOtherLine(syntaxSql, pos.lineNumber), + pos + )?.syntax; const suggestion = syntaxes?.find( (syn) => syn.syntaxContextType === SyntaxContextType.COLUMN_CREATE ); @@ -338,7 +408,10 @@ describe('Impala SQL Syntax Suggestion', () => { lineNumber: 45, column: 45, }; - const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const syntaxes = parser.getSuggestionAtCaretPosition( + commentOtherLine(syntaxSql, pos.lineNumber), + pos + )?.syntax; const suggestion = syntaxes?.find( (syn) => syn.syntaxContextType === SyntaxContextType.COLUMN_CREATE ); @@ -352,7 +425,10 @@ describe('Impala SQL Syntax Suggestion', () => { lineNumber: 47, column: 49, }; - const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const syntaxes = parser.getSuggestionAtCaretPosition( + commentOtherLine(syntaxSql, pos.lineNumber), + pos + )?.syntax; const suggestion = syntaxes?.find( (syn) => syn.syntaxContextType === SyntaxContextType.COLUMN_CREATE ); diff --git a/test/parser/impala/suggestion/tokenSuggestion.test.ts b/test/parser/impala/suggestion/tokenSuggestion.test.ts index 14b8bfe..300b685 100644 --- a/test/parser/impala/suggestion/tokenSuggestion.test.ts +++ b/test/parser/impala/suggestion/tokenSuggestion.test.ts @@ -14,7 +14,10 @@ describe('Impala SQL Token Suggestion', () => { lineNumber: 1, column: 7, }; - const suggestion = parser.getSuggestionAtCaretPosition(tokenSql, pos)?.keywords; + const suggestion = parser.getSuggestionAtCaretPosition( + commentOtherLine(tokenSql, pos.lineNumber), + pos + )?.keywords; expect(suggestion).toEqual(['TABLE', 'VIEW', 'DATABASE']); }); diff --git a/test/parser/mysql/suggestion/fixtures/multipleStatement.sql b/test/parser/mysql/suggestion/fixtures/multipleStatement.sql new file mode 100644 index 0000000..10a8b5b --- /dev/null +++ b/test/parser/mysql/suggestion/fixtures/multipleStatement.sql @@ -0,0 +1,21 @@ +SELECT * FROM -- unfinished + +CREATE SCHEMA IF NOT EXISTS db_name DEFAULT ENCRYPTION 'Y'; + +CREATE TABLE test (a INT NOT NULL AUTO_INCREMENT, PRIMARY KEY (a), KEY(b)) ENGINE=InnoDB SELECT b,c FROM test2; + +ALTER LOGFILE GROUP lg_3 ADD UNDOFILE 'undo_10.dat' INITIAL_SIZE=32M ENGINE=NDBCLUSTER; + +CREATE TABLE db. LIKE orig_tbl; -- unfinished + +INSERT HIGH_PRIORITY IGNORE INTO tbl_temp2 (fld_id) VALUES ROW(1,-2,3), ROW(5,7,9), ROW(4,6,8); + +CHANGE REPLICATION FILTER REPLICATE_DO_DB = (db3, db4); + +INSERT INTO VALUES (1,2,3),(4,5,6) ON DUPLICATE KEY UPDATE c=VALUES(a)+VALUES(b); -- unfinished + +ALTER FUNCTION function_name LANGUAGE SQL; + +ALTER FUNCTION function_name NOT DETERMINISTIC; + +INSERT LOW_PRIORITY IGNORE INTO tbl_temp2 (fld_id) SELECT tbl_temp1.fld_order_id FROM -- unfinished \ No newline at end of file diff --git a/test/parser/mysql/suggestion/multipleStatement.test.ts b/test/parser/mysql/suggestion/multipleStatement.test.ts new file mode 100644 index 0000000..f62c30a --- /dev/null +++ b/test/parser/mysql/suggestion/multipleStatement.test.ts @@ -0,0 +1,69 @@ +import fs from 'fs'; +import path from 'path'; +import { CaretPosition, SyntaxContextType } from '../../../../src/parser/common/basic-parser-types'; +import MySQL from '../../../../src/parser/mysql'; + +const syntaxSql = fs.readFileSync( + path.join(__dirname, 'fixtures', 'multipleStatement.sql'), + 'utf-8' +); + +describe('MySQL Multiple Statements Syntax Suggestion', () => { + const parser = new MySQL(); + + test('Select from table ', () => { + const pos: CaretPosition = { + lineNumber: 1, + column: 15, + }; + const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE + ); + + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual([]); + }); + + test('Create table ', () => { + const pos: CaretPosition = { + lineNumber: 9, + column: 17, + }; + const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE_CREATE + ); + + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.']); + }); + + test('Insert into table ', () => { + const pos: CaretPosition = { + lineNumber: 15, + column: 13, + }; + const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE + ); + + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual([]); + }); + + test('Insert into select from table ', () => { + const pos: CaretPosition = { + lineNumber: 21, + column: 87, + }; + const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE + ); + + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual([]); + }); +}); diff --git a/test/parser/mysql/suggestion/syntaxSuggestion.test.ts b/test/parser/mysql/suggestion/syntaxSuggestion.test.ts index 446dc72..45d0a9b 100644 --- a/test/parser/mysql/suggestion/syntaxSuggestion.test.ts +++ b/test/parser/mysql/suggestion/syntaxSuggestion.test.ts @@ -50,23 +50,22 @@ describe('MySQL Syntax Suggestion', () => { expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.']); }); - // TODO: fix bug of basic parser and decomment following test - // test('Create table ', () => { - // const pos: CaretPosition = { - // lineNumber: 5, - // column: 17, - // }; - // const syntaxes = parser.getSuggestionAtCaretPosition( - // commentOtherLine(syntaxSql, pos.lineNumber), - // pos - // )?.syntax; - // const suggestion = syntaxes?.find( - // (syn) => syn.syntaxContextType === SyntaxContextType.TABLE_CREATE - // ); + test('Create table ', () => { + const pos: CaretPosition = { + lineNumber: 5, + column: 17, + }; + const syntaxes = parser.getSuggestionAtCaretPosition( + commentOtherLine(syntaxSql, pos.lineNumber), + pos + )?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE_CREATE + ); - // expect(suggestion).not.toBeUndefined(); - // expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.']); - // }); + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.']); + }); test('DROP table ', () => { const pos: CaretPosition = { diff --git a/test/parser/mysql/syntax/administration.test.ts b/test/parser/mysql/syntax/administration.test.ts index a4f4a89..ff11df6 100644 --- a/test/parser/mysql/syntax/administration.test.ts +++ b/test/parser/mysql/syntax/administration.test.ts @@ -42,7 +42,7 @@ describe('MySQL Database Administration Syntax Tests', () => { it(sql, () => { const result = parser.validate(sql); if (result.length) { - console.log(result, `\n请检查 sql: ${sql}`); + console.log(result, `\nPlease check sql: ${sql}`); } expect(result.length).toBe(0); }); diff --git a/test/parser/mysql/syntax/ddl.test.ts b/test/parser/mysql/syntax/ddl.test.ts index bac48b8..4e2a943 100644 --- a/test/parser/mysql/syntax/ddl.test.ts +++ b/test/parser/mysql/syntax/ddl.test.ts @@ -49,7 +49,7 @@ describe('MySQL DDL Syntax Tests', () => { it(sql, () => { const result = parser.validate(sql); if (result.length) { - console.log(result, `\n请检查 sql: ${sql}`); + console.log(result, `\nPlease check sql: ${sql}`); } expect(result.length).toBe(0); }); diff --git a/test/parser/mysql/syntax/dml.test.ts b/test/parser/mysql/syntax/dml.test.ts index b708a27..0f81687 100644 --- a/test/parser/mysql/syntax/dml.test.ts +++ b/test/parser/mysql/syntax/dml.test.ts @@ -33,7 +33,7 @@ describe('MySQL DML Syntax Tests', () => { it(sql, () => { const result = parser.validate(sql); if (result.length) { - console.log(result, `\n请检查 sql: ${sql}`); + console.log(result, `\nPlease check sql: ${sql}`); } expect(result.length).toBe(0); }); diff --git a/test/parser/mysql/syntax/other.test.ts b/test/parser/mysql/syntax/other.test.ts index ba17a82..7a2b256 100644 --- a/test/parser/mysql/syntax/other.test.ts +++ b/test/parser/mysql/syntax/other.test.ts @@ -27,7 +27,7 @@ describe('MySQL Transactional and Locking, Replication, Prepared Compound and Ut it(sql, () => { const result = parser.validate(sql); if (result.length) { - console.log(result, `\n请检查 sql: ${sql}`); + console.log(result, `\nPlease check sql: ${sql}`); } expect(result.length).toBe(0); }); diff --git a/test/parser/pgsql/suggestion/fixtures/multipleStatement.sql b/test/parser/pgsql/suggestion/fixtures/multipleStatement.sql new file mode 100644 index 0000000..be7b9aa --- /dev/null +++ b/test/parser/pgsql/suggestion/fixtures/multipleStatement.sql @@ -0,0 +1,21 @@ +CREATE TABLE VALUES -- unfinished + +CREATE UNLOGGED TABLE table1 (col1 int) INHERITS (table_parent) WITHOUT OIDS ON COMMIT DROP; + +CREATE SCHEMA schemaname AUTHORIZATION username; + +ALTER TABLE products ADD FOREIGN KEY (product_group_id) REFERENCES product_groups; + +SELECT * FROM db. ; -- unfinished + +INSERT INTO weather (date, city, temp_hi, temp_lo) VALUES ('1994-11-29', 'Hayward', 54, 37); + +ANALYZE VERBOSE table_name ( column_name, column_name2); + +INSERT INTO weather (date, city, temp_hi, temp_lo) VALUES ('1994-11-29', 'Hayward', 54, 37); -- unfinished + +DROP TABLE products CASCADE; + +DROP AGGREGATE aggname2(int); + +INSERT INTO products (product_no, name, price) SELECT * FROM db. ; -- unfinished \ No newline at end of file diff --git a/test/parser/pgsql/suggestion/multipleStatement.test.ts b/test/parser/pgsql/suggestion/multipleStatement.test.ts new file mode 100644 index 0000000..e9bb21d --- /dev/null +++ b/test/parser/pgsql/suggestion/multipleStatement.test.ts @@ -0,0 +1,69 @@ +import fs from 'fs'; +import path from 'path'; +import { CaretPosition, SyntaxContextType } from '../../../../src/parser/common/basic-parser-types'; +import PgSQL from '../../../../src/parser/pgsql'; + +const syntaxSql = fs.readFileSync( + path.join(__dirname, 'fixtures', 'multipleStatement.sql'), + 'utf-8' +); + +describe('PgSQL Multiple Statements Syntax Suggestion', () => { + const parser = new PgSQL(); + + test('Create table ', () => { + const pos: CaretPosition = { + lineNumber: 1, + column: 14, + }; + const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE_CREATE + ); + + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual([]); + }); + + test('Select from table', () => { + const pos: CaretPosition = { + lineNumber: 9, + column: 18, + }; + const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE + ); + + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.']); + }); + + test('Insert into table ', () => { + const pos: CaretPosition = { + lineNumber: 15, + column: 13, + }; + const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE + ); + + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual([]); + }); + + test('Insert into select from table ', () => { + const pos: CaretPosition = { + lineNumber: 21, + column: 65, + }; + const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE + ); + + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.']); + }); +}); diff --git a/test/parser/pgsql/suggestion/syntaxSuggestion.test.ts b/test/parser/pgsql/suggestion/syntaxSuggestion.test.ts index 618f5d5..f0e4940 100644 --- a/test/parser/pgsql/suggestion/syntaxSuggestion.test.ts +++ b/test/parser/pgsql/suggestion/syntaxSuggestion.test.ts @@ -638,29 +638,28 @@ describe('Postgre SQL Syntax Suggestion', () => { lineNumber: 59, column: 48, }; - // const pos1: CaretPosition = { - // lineNumber: 59, - // column: 93, - // }; + const pos1: CaretPosition = { + lineNumber: 59, + column: 93, + }; const syntaxes = parser.getSuggestionAtCaretPosition( commentOtherLine(syntaxSql, pos.lineNumber), pos )?.syntax; - // const syntaxes1 = parser.getSuggestionAtCaretPosition( - // commentOtherLine(syntaxSql, pos1.lineNumber), - // pos1 - // )?.syntax; + const syntaxes1 = parser.getSuggestionAtCaretPosition( + commentOtherLine(syntaxSql, pos1.lineNumber), + pos1 + )?.syntax; const suggestion = syntaxes?.find( (syn) => syn.syntaxContextType === SyntaxContextType.COLUMN ); - // const suggestion1 = syntaxes1?.find( - // (syn) => syn.syntaxContextType === SyntaxContextType.FUNCTION - // ); + const suggestion1 = syntaxes1?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.FUNCTION + ); expect(suggestion).not.toBeUndefined(); expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['column_name']); - // TODO: fix bug of basic parser and decomment following case - // expect(suggestion1).not.toBeUndefined(); - // expect(suggestion1?.wordRanges.map((token) => token.text)).toEqual(['function_name']); + expect(suggestion1).not.toBeUndefined(); + expect(suggestion1?.wordRanges.map((token) => token.text)).toEqual(['function_name']); }); test('GRANT With Column', () => { diff --git a/test/parser/spark/suggestion/fixtures/multipleStatement.sql b/test/parser/spark/suggestion/fixtures/multipleStatement.sql new file mode 100644 index 0000000..b794f30 --- /dev/null +++ b/test/parser/spark/suggestion/fixtures/multipleStatement.sql @@ -0,0 +1,21 @@ +CREATE TABLE VALUES -- unfinished + +CREATE TABLE student (id INT, name STRING, age INT) STORED AS ORC; + +CREATE SCHEMA customer_db WITH DBPROPERTIES (ID=001, Name='John'); + +ALTER TABLE StudentInfo ADD COLUMNS (LastName string, DOB timestamp); + +SELECT * FROM db. ; -- unfinished + +INSERT INTO weather (date, city, temp_hi, temp_lo) VALUES ('1994-11-29', 'Hayward', 54, 37); + +DESC EXTENDED students name; + +INSERT INTO weather (date, city, temp_hi, temp_lo) VALUES ('1994-11-29', 'Hayward', 54, 37); -- unfinished + +DROP TABLE IF EXISTS employable; + +DROP TEMPORARY FUNCTION test_avg; + +INSERT INTO products (product_no, name, price) SELECT * FROM db. ; -- unfinished \ No newline at end of file diff --git a/test/parser/spark/suggestion/multipleStatement.test.ts b/test/parser/spark/suggestion/multipleStatement.test.ts new file mode 100644 index 0000000..92270c5 --- /dev/null +++ b/test/parser/spark/suggestion/multipleStatement.test.ts @@ -0,0 +1,69 @@ +import fs from 'fs'; +import path from 'path'; +import { CaretPosition, SyntaxContextType } from '../../../../src/parser/common/basic-parser-types'; +import SparkSQL from '../../../../src/parser/spark'; + +const syntaxSql = fs.readFileSync( + path.join(__dirname, 'fixtures', 'multipleStatement.sql'), + 'utf-8' +); + +describe('SparkSQL Multiple Statements Syntax Suggestion', () => { + const parser = new SparkSQL(); + + test('Create table ', () => { + const pos: CaretPosition = { + lineNumber: 1, + column: 14, + }; + const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE_CREATE + ); + + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual([]); + }); + + test('Select from table', () => { + const pos: CaretPosition = { + lineNumber: 9, + column: 18, + }; + const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE + ); + + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.']); + }); + + test('Insert into table ', () => { + const pos: CaretPosition = { + lineNumber: 15, + column: 13, + }; + const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE + ); + + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual([]); + }); + + test('Insert into select from table ', () => { + const pos: CaretPosition = { + lineNumber: 21, + column: 65, + }; + const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE + ); + + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.']); + }); +}); diff --git a/test/parser/spark/suggestion/tokenSuggestion.test.ts b/test/parser/spark/suggestion/tokenSuggestion.test.ts index d18709f..b632f01 100644 --- a/test/parser/spark/suggestion/tokenSuggestion.test.ts +++ b/test/parser/spark/suggestion/tokenSuggestion.test.ts @@ -2,6 +2,7 @@ import fs from 'fs'; import path from 'path'; import { CaretPosition } from '../../../../src/parser/common/basic-parser-types'; import SparkSQL from '../../../../src/parser/spark'; +import { commentOtherLine } from '../../../helper'; const tokenSql = fs.readFileSync(path.join(__dirname, 'fixtures', 'tokenSuggestion.sql'), 'utf-8'); @@ -13,7 +14,10 @@ describe('Spark SQL Token Suggestion', () => { lineNumber: 1, column: 7, }; - const suggestion = parser.getSuggestionAtCaretPosition(tokenSql, pos)?.keywords; + const suggestion = parser.getSuggestionAtCaretPosition( + commentOtherLine(tokenSql, pos.lineNumber), + pos + )?.keywords; expect(suggestion).toEqual(['TABLE', 'INDEX', 'VIEW', 'DATABASE', 'NAMESPACE', 'SCHEMA']); }); @@ -23,7 +27,10 @@ describe('Spark SQL Token Suggestion', () => { lineNumber: 3, column: 8, }; - const suggestion = parser.getSuggestionAtCaretPosition(tokenSql, pos)?.keywords; + const suggestion = parser.getSuggestionAtCaretPosition( + commentOtherLine(tokenSql, pos.lineNumber), + pos + )?.keywords; expect(suggestion).toEqual([ 'TEMPORARY', @@ -46,7 +53,10 @@ describe('Spark SQL Token Suggestion', () => { lineNumber: 5, column: 8, }; - const suggestion = parser.getSuggestionAtCaretPosition(tokenSql, pos)?.keywords; + const suggestion = parser.getSuggestionAtCaretPosition( + commentOtherLine(tokenSql, pos.lineNumber), + pos + )?.keywords; expect(suggestion).toEqual(['FROM']); }); @@ -56,7 +66,10 @@ describe('Spark SQL Token Suggestion', () => { lineNumber: 7, column: 10, }; - const suggestion = parser.getSuggestionAtCaretPosition(tokenSql, pos)?.keywords; + const suggestion = parser.getSuggestionAtCaretPosition( + commentOtherLine(tokenSql, pos.lineNumber), + pos + )?.keywords; expect(suggestion).toEqual([ 'WITH', @@ -79,7 +92,10 @@ describe('Spark SQL Token Suggestion', () => { lineNumber: 9, column: 6, }; - const suggestion = parser.getSuggestionAtCaretPosition(tokenSql, pos)?.keywords; + const suggestion = parser.getSuggestionAtCaretPosition( + commentOtherLine(tokenSql, pos.lineNumber), + pos + )?.keywords; expect(suggestion).toEqual([ 'TEMPORARY', @@ -99,7 +115,10 @@ describe('Spark SQL Token Suggestion', () => { lineNumber: 11, column: 8, }; - const suggestion = parser.getSuggestionAtCaretPosition(tokenSql, pos)?.keywords; + const suggestion = parser.getSuggestionAtCaretPosition( + commentOtherLine(tokenSql, pos.lineNumber), + pos + )?.keywords; expect(suggestion).toEqual(['OVERWRITE', 'INTO']); }); @@ -109,7 +128,10 @@ describe('Spark SQL Token Suggestion', () => { lineNumber: 13, column: 6, }; - const suggestion = parser.getSuggestionAtCaretPosition(tokenSql, pos)?.keywords; + const suggestion = parser.getSuggestionAtCaretPosition( + commentOtherLine(tokenSql, pos.lineNumber), + pos + )?.keywords; expect(suggestion).toEqual(['DATA']); }); @@ -119,7 +141,10 @@ describe('Spark SQL Token Suggestion', () => { lineNumber: 15, column: 6, }; - const suggestion = parser.getSuggestionAtCaretPosition(tokenSql, pos)?.keywords; + const suggestion = parser.getSuggestionAtCaretPosition( + commentOtherLine(tokenSql, pos.lineNumber), + pos + )?.keywords; expect(suggestion).toEqual([ 'LOCKS', @@ -154,7 +179,10 @@ describe('Spark SQL Token Suggestion', () => { lineNumber: 17, column: 8, }; - const suggestion = parser.getSuggestionAtCaretPosition(tokenSql, pos)?.keywords; + const suggestion = parser.getSuggestionAtCaretPosition( + commentOtherLine(tokenSql, pos.lineNumber), + pos + )?.keywords; expect(suggestion).toEqual(['TABLE']); }); diff --git a/test/parser/trinosql/suggestion/fixtures/multipleStatement.sql b/test/parser/trinosql/suggestion/fixtures/multipleStatement.sql new file mode 100644 index 0000000..d3f3c1a --- /dev/null +++ b/test/parser/trinosql/suggestion/fixtures/multipleStatement.sql @@ -0,0 +1,22 @@ +CREATE TABLE VALUES -- unfinished + +ALTER SCHEMA foo RENAME TO bar; + +DELETE FROM t; + +DENY SELECT ON SCHEMA s TO USER u; + +SELECT ids FROM db. ; -- unfinished + +INSERT INTO weather (date, city, temp_hi, temp_lo) VALUES ('1994-11-29', 'Hayward', 54, 37); + +EXPLAIN ANALYZE VERBOSE SELECT * FROM t; + +INSERT INTO weather (date, city, temp_hi, temp_lo) VALUES ('1994-11-29', 'Hayward', 54, 37); -- unfinished + +DENY SELECT ON SCHEMA s TO USER u; + +CALL catalog.schema.test(); + +INSERT INTO products (product_no, name, price) SELECT * FROM db. ; -- unfinished + diff --git a/test/parser/trinosql/suggestion/multipleStatement.test.ts b/test/parser/trinosql/suggestion/multipleStatement.test.ts new file mode 100644 index 0000000..5eedb12 --- /dev/null +++ b/test/parser/trinosql/suggestion/multipleStatement.test.ts @@ -0,0 +1,69 @@ +import fs from 'fs'; +import path from 'path'; +import { CaretPosition, SyntaxContextType } from '../../../../src/parser/common/basic-parser-types'; +import TrinoSQL from '../../../../src/parser/trinosql'; + +const syntaxSql = fs.readFileSync( + path.join(__dirname, 'fixtures', 'multipleStatement.sql'), + 'utf-8' +); + +describe('TrinoSQL Multiple Statements Syntax Suggestion', () => { + const parser = new TrinoSQL(); + + test('Create table ', () => { + const pos: CaretPosition = { + lineNumber: 1, + column: 14, + }; + const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE_CREATE + ); + + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual([]); + }); + + test('Select from table', () => { + const pos: CaretPosition = { + lineNumber: 9, + column: 20, + }; + const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE + ); + + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.']); + }); + + test('Insert into table ', () => { + const pos: CaretPosition = { + lineNumber: 15, + column: 13, + }; + const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE + ); + + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual([]); + }); + + test('Insert into select from table ', () => { + const pos: CaretPosition = { + lineNumber: 21, + column: 65, + }; + const syntaxes = parser.getSuggestionAtCaretPosition(syntaxSql, pos)?.syntax; + const suggestion = syntaxes?.find( + (syn) => syn.syntaxContextType === SyntaxContextType.TABLE + ); + + expect(suggestion).not.toBeUndefined(); + expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.']); + }); +}); diff --git a/test/parser/trinosql/suggestion/tokenSuggestion.test.ts b/test/parser/trinosql/suggestion/tokenSuggestion.test.ts index dfec69c..d7143c3 100644 --- a/test/parser/trinosql/suggestion/tokenSuggestion.test.ts +++ b/test/parser/trinosql/suggestion/tokenSuggestion.test.ts @@ -14,7 +14,10 @@ describe('Trino SQL Token Suggestion', () => { lineNumber: 1, column: 7, }; - const suggestion = parser.getSuggestionAtCaretPosition(tokenSql, pos)?.keywords; + const suggestion = parser.getSuggestionAtCaretPosition( + commentOtherLine(tokenSql, pos.lineNumber), + pos + )?.keywords; expect(suggestion).toEqual(['VIEW', 'MATERIALIZED', 'TABLE', 'SCHEMA']); });