feat: mysql auto complete (#219)

* refactor: mysql auto complete

* test: mysql auto complete

---------

Co-authored-by: liuyi <liuyi@dtstack.com>
This commit is contained in:
琉易 2023-11-28 21:17:18 +08:00 committed by GitHub
parent e203f1a48a
commit 3dadc0c4b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 8332 additions and 7507 deletions

View File

@ -1016,16 +1016,6 @@ BIT_STRING: BIT_STRING_L;
STRING_CHARSET_NAME: '_' CHARSET_NAME; STRING_CHARSET_NAME: '_' CHARSET_NAME;
// Hack for dotID
// Prevent recognize string: .123somelatin AS ((.123), FLOAT_LITERAL), ((somelatin), ID)
// it must recoginze: .123somelatin AS ((.), DOT), (123somelatin, ID)
DOT_ID: '.' ID_LITERAL;
// Identifiers // Identifiers
ID: ID_LITERAL; ID: ID_LITERAL;

View File

@ -24,7 +24,8 @@ THE SOFTWARE.
*/ */
// https://github.com/antlr/grammars-v4/blob/master/sql/mysql/Positive-Technologies/MySqlParser.g4 // https://github.com/antlr/grammars-v4/blob/master/sql/mysql/Positive-Technologies/MySqlParser.g4
// SQL Statements: https://dev.mysql.com/doc/refman/8.0/en/sql-statements.html // SQL Statements v8.0: https://dev.mysql.com/doc/refman/8.0/en/sql-statements.html
// SQL Statements v5.7: https://dev.mysql.com/doc/refman/5.7/en/sql-statements.html
parser grammar MySqlParser; parser grammar MySqlParser;
@ -2335,7 +2336,7 @@ simpleId
; ;
dottedId dottedId
: DOT_ID : DOT ID
| '.' uid | '.' uid
; ;
@ -2530,7 +2531,7 @@ functionCall
| aggregateWindowedFunction #aggregateFunctionCall | aggregateWindowedFunction #aggregateFunctionCall
| nonAggregateWindowedFunction #nonAggregateFunctionCall | nonAggregateWindowedFunction #nonAggregateFunctionCall
| scalarFunctionName '(' functionArgs? ')' #scalarFunctionCall | scalarFunctionName '(' functionArgs? ')' #scalarFunctionCall
| fullId '(' functionArgs? ')' #udfFunctionCall | functionName '(' functionArgs? ')' #udfFunctionCall
| passwordFunctionClause #passwordFunctionCall | passwordFunctionClause #passwordFunctionCall
; ;

File diff suppressed because one or more lines are too long

View File

@ -886,14 +886,13 @@ REAL_LITERAL=885
NULL_SPEC_LITERAL=886 NULL_SPEC_LITERAL=886
BIT_STRING=887 BIT_STRING=887
STRING_CHARSET_NAME=888 STRING_CHARSET_NAME=888
DOT_ID=889 ID=889
ID=890 REVERSE_QUOTE_ID=890
REVERSE_QUOTE_ID=891 HOST_IP_ADDRESS=891
HOST_IP_ADDRESS=892 LOCAL_ID=892
LOCAL_ID=893 GLOBAL_ID=893
GLOBAL_ID=894 PERSIST_ID=894
PERSIST_ID=895 ERROR_RECONGNIGION=895
ERROR_RECONGNIGION=896
'ACTIVE'=5 'ACTIVE'=5
'ADD'=6 'ADD'=6
'ALL'=7 'ALL'=7

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -886,14 +886,13 @@ REAL_LITERAL=885
NULL_SPEC_LITERAL=886 NULL_SPEC_LITERAL=886
BIT_STRING=887 BIT_STRING=887
STRING_CHARSET_NAME=888 STRING_CHARSET_NAME=888
DOT_ID=889 ID=889
ID=890 REVERSE_QUOTE_ID=890
REVERSE_QUOTE_ID=891 HOST_IP_ADDRESS=891
HOST_IP_ADDRESS=892 LOCAL_ID=892
LOCAL_ID=893 GLOBAL_ID=893
GLOBAL_ID=894 PERSIST_ID=894
PERSIST_ID=895 ERROR_RECONGNIGION=895
ERROR_RECONGNIGION=896
'ACTIVE'=5 'ACTIVE'=5
'ADD'=6 'ADD'=6
'ALL'=7 'ALL'=7

File diff suppressed because one or more lines are too long

View File

@ -1,9 +1,10 @@
import { Token } from 'antlr4ts'; import { Token } from 'antlr4ts';
import { CandidatesCollection } from 'antlr4-c3'; import { CandidatesCollection } from 'antlr4-c3';
import { MySqlLexer } from '../lib/mysql/MySqlLexer'; import { MySqlLexer } from '../lib/mysql/MySqlLexer';
import { MySqlParser, ProgramContext } from '../lib/mysql/MySqlParser'; import { MySqlParser, ProgramContext, SqlStatementsContext } from '../lib/mysql/MySqlParser';
import BasicParser from './common/basicParser'; import BasicParser from './common/basicParser';
import { Suggestions } from './common/basic-parser-types'; import { Suggestions, SyntaxContextType, SyntaxSuggestion } from './common/basic-parser-types';
import { MySqlParserListener } from 'src/lib/mysql/MySqlParserListener';
export default class MySQL extends BasicParser<MySqlLexer, ProgramContext, MySqlParser> { export default class MySQL extends BasicParser<MySqlLexer, ProgramContext, MySqlParser> {
protected createLexerFormCharStream(charStreams): MySqlLexer { protected createLexerFormCharStream(charStreams): MySqlLexer {
@ -15,20 +16,123 @@ export default class MySQL extends BasicParser<MySqlLexer, ProgramContext, MySql
return new MySqlParser(tokenStream); return new MySqlParser(tokenStream);
} }
protected preferredRules: Set<number> = new Set(); protected preferredRules: Set<number> = new Set([
MySqlParser.RULE_databaseName,
MySqlParser.RULE_databaseNameCreate,
MySqlParser.RULE_tableName,
MySqlParser.RULE_tableNameCreate,
MySqlParser.RULE_viewName,
MySqlParser.RULE_viewNameCreate,
MySqlParser.RULE_functionName,
MySqlParser.RULE_functionNameCreate,
MySqlParser.RULE_columnName,
MySqlParser.RULE_columnNameCreate,
]);
protected get splitListener() { protected get splitListener() {
return null as any; return new mysqlSplitListener();
} }
protected processCandidates( protected processCandidates(
candidates: CandidatesCollection, candidates: CandidatesCollection,
allTokens: Token[], allTokens: Token[],
caretTokenIndex: number caretTokenIndex: number,
tokenIndexOffset: number
): Suggestions<Token> { ): Suggestions<Token> {
const originalSyntaxSuggestions: SyntaxSuggestion<Token>[] = [];
const keywords: string[] = [];
for (const candidate of candidates.rules) {
const [ruleType, candidateRule] = candidate;
const startTokenIndex = candidateRule.startTokenIndex + tokenIndexOffset;
const tokenRanges = allTokens.slice(
startTokenIndex,
caretTokenIndex + tokenIndexOffset + 1
);
let syntaxContextType: SyntaxContextType;
switch (ruleType) {
case MySqlParser.RULE_databaseName: {
syntaxContextType = SyntaxContextType.DATABASE;
break;
}
case MySqlParser.RULE_databaseNameCreate: {
syntaxContextType = SyntaxContextType.DATABASE_CREATE;
break;
}
case MySqlParser.RULE_tableName: {
syntaxContextType = SyntaxContextType.TABLE;
break;
}
case MySqlParser.RULE_tableNameCreate: {
syntaxContextType = SyntaxContextType.TABLE_CREATE;
break;
}
case MySqlParser.RULE_viewName: {
syntaxContextType = SyntaxContextType.VIEW;
break;
}
case MySqlParser.RULE_viewNameCreate: {
syntaxContextType = SyntaxContextType.VIEW_CREATE;
break;
}
case MySqlParser.RULE_functionName: {
syntaxContextType = SyntaxContextType.FUNCTION;
break;
}
case MySqlParser.RULE_functionNameCreate: {
syntaxContextType = SyntaxContextType.FUNCTION_CREATE;
break;
}
case MySqlParser.RULE_columnName: {
syntaxContextType = SyntaxContextType.COLUMN;
break;
}
case MySqlParser.RULE_columnNameCreate: {
syntaxContextType = SyntaxContextType.COLUMN_CREATE;
break;
}
default:
break;
}
if (syntaxContextType) {
originalSyntaxSuggestions.push({
syntaxContextType,
wordRanges: tokenRanges,
});
}
}
for (const candidate of candidates.tokens) {
const symbolicName = this._parser.vocabulary.getSymbolicName(candidate[0]);
const displayName = this._parser.vocabulary.getDisplayName(candidate[0]);
if (symbolicName && symbolicName.startsWith('KW_')) {
const keyword =
displayName.startsWith("'") && displayName.endsWith("'")
? displayName.slice(1, -1)
: displayName;
keywords.push(keyword);
}
}
return { return {
syntax: [], syntax: originalSyntaxSuggestions,
keywords: [], keywords,
}; };
} }
} }
export class mysqlSplitListener implements MySqlParserListener {
private _statementsContext: SqlStatementsContext[] = [];
exitSqlStatements = (ctx: SqlStatementsContext) => {
this._statementsContext.push(ctx);
};
enterSqlStatements = (ctx: SqlStatementsContext) => {};
get statementsContext() {
return this._statementsContext;
}
}

View File

@ -0,0 +1,49 @@
INSERT INTO db.tb ;
SELECT * FROM db.;
CREATE TABLE db. VALUES;
DROP TABLE IF EXISTS db.a;
CREATE OR REPLACE VIEW db.v;
DROP VIEW db.v;
CREATE FUNCTION fn1;
SELECT name, calculate_age(birthday) AS age FROM students;
CREATE DATABASE db;
DROP SCHEMA IF EXISTS sch;
ANALYZE TABLE t UPDATE HISTOGRAM ON c1, c2, c3 WITH 10 BUCKETS;
SELECT name, age FROM students;
ALTER TABLE StudentInfo RENAME COLUMN ;
ALTER TABLE StudentInfo RENAME COLUMN name TO t;
ALTER TABLE t1 ADD c2 INT GENERATED ALWAYS AS (c1 + 1) STORED;
ALTER TABLE StudentInfo CHANGE FirstName;
INSERT INTO students ( );
INSERT INTO students ( id, n );
SELECT ;
SELECT id, n;
SELECT FROM tbl;
SELECT id, n FROM tbl;
SELECT id, n FROM tbl GROUP BY ;
SELECT id, n FROM tbl ORDER BY name, i ;
SELECT id FROM tb1 GROUP BY ROLLUP( );

View File

@ -0,0 +1,16 @@
ALTER
;
CREATE
;
DELETE
;
DESCRIBE
;
DROP
;
INSERT
;
LOAD
;
SHOW
;

View File

@ -0,0 +1,443 @@
import fs from 'fs';
import path from 'path';
import { CaretPosition, SyntaxContextType } from '../../../../src/parser/common/basic-parser-types';
import MySQL from '../../../../src/parser/mysql';
import { commentOtherLine } from '../../../helper';
const syntaxSql = fs.readFileSync(
path.join(__dirname, 'fixtures', 'syntaxSuggestion.sql'),
'utf-8'
);
describe('MySQL Syntax Suggestion', () => {
const parser = new MySQL();
test('Validate Syntax SQL', () => {
expect(parser.validate(syntaxSql).length).not.toBe(0);
});
test('Insert table ', () => {
const pos: CaretPosition = {
lineNumber: 1,
column: 18,
};
const syntaxes = parser.getSuggestionAtCaretPosition(
commentOtherLine(syntaxSql, pos.lineNumber),
pos
)?.syntax;
const suggestion = syntaxes?.find(
(syn) => syn.syntaxContextType === SyntaxContextType.TABLE
);
expect(suggestion).not.toBeUndefined();
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.', 'tb']);
});
test('Select table ', () => {
const pos: CaretPosition = {
lineNumber: 3,
column: 18,
};
const syntaxes = parser.getSuggestionAtCaretPosition(
commentOtherLine(syntaxSql, pos.lineNumber),
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('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', '.']);
});
test('DROP table ', () => {
const pos: CaretPosition = {
lineNumber: 7,
column: 26,
};
const syntaxes = parser.getSuggestionAtCaretPosition(
commentOtherLine(syntaxSql, pos.lineNumber),
pos
)?.syntax;
const suggestion = syntaxes?.find(
(syn) => syn.syntaxContextType === SyntaxContextType.TABLE
);
expect(suggestion).not.toBeUndefined();
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.', 'a']);
});
test('Create view ', () => {
const pos: CaretPosition = {
lineNumber: 9,
column: 28,
};
const syntaxes = parser.getSuggestionAtCaretPosition(
commentOtherLine(syntaxSql, pos.lineNumber),
pos
)?.syntax;
const suggestion = syntaxes?.find(
(syn) => syn.syntaxContextType === SyntaxContextType.VIEW_CREATE
);
expect(suggestion).not.toBeUndefined();
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.', 'v']);
});
test('Drop view ', () => {
const pos: CaretPosition = {
lineNumber: 11,
column: 15,
};
const syntaxes = parser.getSuggestionAtCaretPosition(
commentOtherLine(syntaxSql, pos.lineNumber),
pos
)?.syntax;
const suggestion = syntaxes?.find(
(syn) => syn.syntaxContextType === SyntaxContextType.VIEW
);
expect(suggestion).not.toBeUndefined();
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db', '.', 'v']);
});
test('Create function ', () => {
const pos: CaretPosition = {
lineNumber: 13,
column: 20,
};
const syntaxes = parser.getSuggestionAtCaretPosition(
commentOtherLine(syntaxSql, pos.lineNumber),
pos
)?.syntax;
const suggestion = syntaxes?.find(
(syn) => syn.syntaxContextType === SyntaxContextType.FUNCTION_CREATE
);
expect(suggestion).not.toBeUndefined();
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['fn1']);
});
test('Use function', () => {
const pos: CaretPosition = {
lineNumber: 15,
column: 27,
};
const syntaxes = parser.getSuggestionAtCaretPosition(
commentOtherLine(syntaxSql, pos.lineNumber),
pos
)?.syntax;
const suggestion = syntaxes?.find(
(syn) => syn.syntaxContextType === SyntaxContextType.FUNCTION
);
expect(suggestion).not.toBeUndefined();
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['calculate_age']);
});
test('Create database', () => {
const pos: CaretPosition = {
lineNumber: 17,
column: 19,
};
const syntaxes = parser.getSuggestionAtCaretPosition(
commentOtherLine(syntaxSql, pos.lineNumber),
pos
)?.syntax;
const suggestion = syntaxes?.find(
(syn) => syn.syntaxContextType === SyntaxContextType.DATABASE_CREATE
);
expect(suggestion).not.toBeUndefined();
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['db']);
});
test('Drop database', () => {
const pos: CaretPosition = {
lineNumber: 19,
column: 26,
};
const syntaxes = parser.getSuggestionAtCaretPosition(
commentOtherLine(syntaxSql, pos.lineNumber),
pos
)?.syntax;
const suggestion = syntaxes?.find(
(syn) => syn.syntaxContextType === SyntaxContextType.DATABASE
);
expect(suggestion).not.toBeUndefined();
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['sch']);
});
test('Analyze table on column', () => {
const pos: CaretPosition = {
lineNumber: 21,
column: 39,
};
const syntaxes = parser.getSuggestionAtCaretPosition(
commentOtherLine(syntaxSql, pos.lineNumber),
pos
)?.syntax;
const suggestion = syntaxes?.find(
(syn) => syn.syntaxContextType === SyntaxContextType.COLUMN
);
expect(suggestion).not.toBeUndefined();
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['c1']);
});
test('Select columnName', () => {
const pos: CaretPosition = {
lineNumber: 23,
column: 17,
};
const syntaxes = parser.getSuggestionAtCaretPosition(
commentOtherLine(syntaxSql, pos.lineNumber),
pos
)?.syntax;
const suggestion = syntaxes?.find(
(syn) => syn.syntaxContextType === SyntaxContextType.COLUMN
);
expect(suggestion).not.toBeUndefined();
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['age']);
});
test('Alter table rename columns', () => {
const pos: CaretPosition = {
lineNumber: 25,
column: 39,
};
const syntaxes = parser.getSuggestionAtCaretPosition(
commentOtherLine(syntaxSql, pos.lineNumber),
pos
)?.syntax;
const suggestion = syntaxes?.find(
(syn) => syn.syntaxContextType === SyntaxContextType.COLUMN
);
expect(suggestion).not.toBeUndefined();
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual([]);
});
test('Alter table rename columns to', () => {
const pos: CaretPosition = {
lineNumber: 27,
column: 48,
};
const syntaxes = parser.getSuggestionAtCaretPosition(
commentOtherLine(syntaxSql, pos.lineNumber),
pos
)?.syntax;
const suggestion = syntaxes?.find(
(syn) => syn.syntaxContextType === SyntaxContextType.COLUMN_CREATE
);
expect(suggestion).not.toBeUndefined();
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['t']);
});
test('Alter table add column', () => {
const pos: CaretPosition = {
lineNumber: 29,
column: 22,
};
const syntaxes = parser.getSuggestionAtCaretPosition(
commentOtherLine(syntaxSql, pos.lineNumber),
pos
)?.syntax;
const suggestion = syntaxes?.find(
(syn) => syn.syntaxContextType === SyntaxContextType.COLUMN
);
expect(suggestion).not.toBeUndefined();
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['c2']);
});
test('Alter table change columns', () => {
const pos: CaretPosition = {
lineNumber: 31,
column: 41,
};
const syntaxes = parser.getSuggestionAtCaretPosition(
commentOtherLine(syntaxSql, pos.lineNumber),
pos
)?.syntax;
const suggestion = syntaxes?.find(
(syn) => syn.syntaxContextType === SyntaxContextType.COLUMN
);
expect(suggestion).not.toBeUndefined();
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['FirstName']);
});
test('Insert into table spec columns', () => {
const pos: CaretPosition = {
lineNumber: 33,
column: 24,
};
const syntaxes = parser.getSuggestionAtCaretPosition(
commentOtherLine(syntaxSql, pos.lineNumber),
pos
)?.syntax;
const suggestion = syntaxes?.find(
(syn) => syn.syntaxContextType === SyntaxContextType.COLUMN
);
expect(suggestion).not.toBeUndefined();
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual([]);
});
test('Insert into table spec columns2', () => {
const pos: CaretPosition = {
lineNumber: 35,
column: 29,
};
const syntaxes = parser.getSuggestionAtCaretPosition(
commentOtherLine(syntaxSql, pos.lineNumber),
pos
)?.syntax;
const suggestion = syntaxes?.find(
(syn) => syn.syntaxContextType === SyntaxContextType.COLUMN
);
expect(suggestion).not.toBeUndefined();
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['n']);
});
test('Select columns case empty', () => {
const pos: CaretPosition = {
lineNumber: 37,
column: 8,
};
const syntaxes = parser.getSuggestionAtCaretPosition(
commentOtherLine(syntaxSql, pos.lineNumber),
pos
)?.syntax;
const suggestion = syntaxes?.find(
(syn) => syn.syntaxContextType === SyntaxContextType.COLUMN
);
expect(suggestion).not.toBeUndefined();
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual([]);
});
test('Select columns case seq', () => {
const pos: CaretPosition = {
lineNumber: 39,
column: 13,
};
const syntaxes = parser.getSuggestionAtCaretPosition(
commentOtherLine(syntaxSql, pos.lineNumber),
pos
)?.syntax;
const suggestion = syntaxes?.find(
(syn) => syn.syntaxContextType === SyntaxContextType.COLUMN
);
expect(suggestion).not.toBeUndefined();
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['n']);
});
test('Select columns from table case empty', () => {
const pos: CaretPosition = {
lineNumber: 41,
column: 8,
};
const syntaxes = parser.getSuggestionAtCaretPosition(
commentOtherLine(syntaxSql, pos.lineNumber),
pos
)?.syntax;
const suggestion = syntaxes?.find(
(syn) => syn.syntaxContextType === SyntaxContextType.COLUMN
);
expect(suggestion).not.toBeUndefined();
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual([]);
});
test('Select columns from table case seq', () => {
const pos: CaretPosition = {
lineNumber: 43,
column: 13,
};
const syntaxes = parser.getSuggestionAtCaretPosition(
commentOtherLine(syntaxSql, pos.lineNumber),
pos
)?.syntax;
const suggestion = syntaxes?.find(
(syn) => syn.syntaxContextType === SyntaxContextType.COLUMN
);
expect(suggestion).not.toBeUndefined();
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['n']);
});
test('Select group by', () => {
const pos: CaretPosition = {
lineNumber: 45,
column: 32,
};
const syntaxes = parser.getSuggestionAtCaretPosition(
commentOtherLine(syntaxSql, pos.lineNumber),
pos
)?.syntax;
const suggestion = syntaxes?.find(
(syn) => syn.syntaxContextType === SyntaxContextType.COLUMN
);
expect(suggestion).not.toBeUndefined();
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual([]);
});
test('Select group by', () => {
const pos: CaretPosition = {
lineNumber: 47,
column: 39,
};
const syntaxes = parser.getSuggestionAtCaretPosition(
commentOtherLine(syntaxSql, pos.lineNumber),
pos
)?.syntax;
const suggestion = syntaxes?.find(
(syn) => syn.syntaxContextType === SyntaxContextType.COLUMN
);
expect(suggestion).not.toBeUndefined();
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual(['i']);
});
test('Select group by rollup', () => {
const pos: CaretPosition = {
lineNumber: 49,
column: 37,
};
const syntaxes = parser.getSuggestionAtCaretPosition(
commentOtherLine(syntaxSql, pos.lineNumber),
pos
)?.syntax;
const suggestion = syntaxes?.find(
(syn) => syn.syntaxContextType === SyntaxContextType.COLUMN
);
expect(suggestion).not.toBeUndefined();
expect(suggestion?.wordRanges.map((token) => token.text)).toEqual([]);
});
});

View File

@ -0,0 +1,238 @@
import fs from 'fs';
import path from 'path';
import { CaretPosition } from '../../../../src/parser/common/basic-parser-types';
import MySQL from '../../../../src/parser/mysql';
import { commentOtherLine } from '../../../helper';
const tokenSql = fs.readFileSync(path.join(__dirname, 'fixtures', 'tokenSuggestion.sql'), 'utf-8');
describe('MySQL Token Suggestion', () => {
const parser = new MySQL();
test('After ALTER', () => {
const pos: CaretPosition = {
lineNumber: 1,
column: 7,
};
const suggestion = parser.getSuggestionAtCaretPosition(
commentOtherLine(tokenSql, pos.lineNumber),
pos
)?.keywords;
expect(suggestion).toEqual([
'RESOURCE',
'USER',
'VIEW',
'SQL',
'DEFINER',
'ALGORITHM',
'TABLESPACE',
'UNDO',
'TABLE',
'SERVER',
'PROCEDURE',
'LOGFILE',
'INSTANCE',
'FUNCTION',
'EVENT',
'DATABASE',
'SCHEMA',
]);
});
test('After CREATE', () => {
const pos: CaretPosition = {
lineNumber: 3,
column: 8,
};
const suggestion = parser.getSuggestionAtCaretPosition(
commentOtherLine(tokenSql, pos.lineNumber),
pos
)?.keywords;
expect(suggestion).toEqual([
'RESOURCE',
'USER',
'ROLE',
'VIEW',
'SQL',
'DEFINER',
'ALGORITHM',
'OR',
'TRIGGER',
'TABLESPACE',
'UNDO',
'TABLE',
'TEMPORARY',
'SERVER',
'FUNCTION',
'AGGREGATE',
'PROCEDURE',
'LOGFILE',
'INDEX',
'FULLTEXT',
'SPATIAL',
'UNIQUE',
'OFFLINE',
'ONLINE',
'EVENT',
'DATABASE',
'SCHEMA',
]);
});
test('After DELETE', () => {
const pos: CaretPosition = {
lineNumber: 5,
column: 8,
};
const suggestion = parser.getSuggestionAtCaretPosition(
commentOtherLine(tokenSql, pos.lineNumber),
pos
)?.keywords;
expect(suggestion).toEqual(['FROM', 'IGNORE', 'QUICK', 'LOW_PRIORITY']);
});
test('After DESCRIBE', () => {
const pos: CaretPosition = {
lineNumber: 7,
column: 10,
};
const suggestion = parser.getSuggestionAtCaretPosition(
commentOtherLine(tokenSql, pos.lineNumber),
pos
)?.keywords;
expect(suggestion).toEqual([
'ANALYZE',
'SELECT',
'DELETE',
'INSERT',
'REPLACE',
'UPDATE',
'FOR',
'FORMAT',
'PARTITIONS',
'EXTENDED',
]);
});
test('After DROP', () => {
const pos: CaretPosition = {
lineNumber: 9,
column: 6,
};
const suggestion = parser.getSuggestionAtCaretPosition(
commentOtherLine(tokenSql, pos.lineNumber),
pos
)?.keywords;
expect(suggestion).toEqual([
'RESOURCE',
'USER',
'PREPARE',
'ROLE',
'VIEW',
'TRIGGER',
'TABLESPACE',
'UNDO',
'TABLE',
'TEMPORARY',
'SPATIAL',
'SERVER',
'FUNCTION',
'PROCEDURE',
'LOGFILE',
'INDEX',
'EVENT',
'DATABASE',
'SCHEMA',
]);
});
test('After INSERT', () => {
const pos: CaretPosition = {
lineNumber: 11,
column: 8,
};
const suggestion = parser.getSuggestionAtCaretPosition(
commentOtherLine(tokenSql, pos.lineNumber),
pos
)?.keywords;
expect(suggestion).toEqual(['INTO', 'IGNORE', 'DELAYED', 'HIGH_PRIORITY', 'LOW_PRIORITY']);
});
test('After LOAD', () => {
const pos: CaretPosition = {
lineNumber: 13,
column: 6,
};
const suggestion = parser.getSuggestionAtCaretPosition(
commentOtherLine(tokenSql, pos.lineNumber),
pos
)?.keywords;
expect(suggestion).toEqual(['INDEX', 'XML', 'DATA']);
});
test('After SHOW', () => {
const pos: CaretPosition = {
lineNumber: 15,
column: 6,
};
const suggestion = parser.getSuggestionAtCaretPosition(
commentOtherLine(tokenSql, pos.lineNumber),
pos
)?.keywords;
expect(suggestion).toEqual([
'REPLICAS',
'REPLICA',
'SLAVE',
'PROFILE',
'OPEN',
'INDEX',
'KEYS',
'INDEXES',
'EXTENDED',
'GRANTS',
'FUNCTION',
'PROCEDURE',
'EVENTS',
'TABLE',
'FULL',
'TABLES',
'TRIGGERS',
'COUNT',
'ERRORS',
'WARNINGS',
'STORAGE',
'ENGINES',
'MASTER',
'PLUGINS',
'PRIVILEGES',
'PROCESSLIST',
'PROFILES',
'AUTHORS',
'CONTRIBUTORS',
'ENGINE',
'CREATE',
'COLUMNS',
'FIELDS',
'CHARACTER',
'CHARSET',
'COLLATION',
'DATABASES',
'SCHEMAS',
'GLOBAL',
'SESSION',
'STATUS',
'VARIABLES',
'BINLOG',
'RELAYLOG',
'BINARY',
]);
});
});

View File

@ -68,7 +68,6 @@ ALTER TABLE t1 ADD COLUMN c2 INT GENERATED ALWAYS AS (c1 + 1) STORED;
ALTER TABLE t1 MODIFY COLUMN c2 TINYINT GENERATED ALWAYS AS (c1 + 5) STORED; ALTER TABLE t1 MODIFY COLUMN c2 TINYINT GENERATED ALWAYS AS (c1 + 5) STORED;
ALTER TABLE t1 CHANGE c2 c3 INT GENERATED ALWAYS AS (c1 + 1) STORED; ALTER TABLE t1 CHANGE c2 c3 INT GENERATED ALWAYS AS (c1 + 1) STORED;
ALTER TABLE t1 DROP COLUMN c3; ALTER TABLE t1 DROP COLUMN c3;
ALTER TABLE t1 ADD COLUMN c2 INT GENERATED ALWAYS AS (c1 + 1) STORED;
ALTER TABLE t1 MODIFY COLUMN c2 INT GENERATED ALWAYS AS (c1 + 1) STORED; ALTER TABLE t1 MODIFY COLUMN c2 INT GENERATED ALWAYS AS (c1 + 1) STORED;
ALTER TABLE t1 MODIFY COLUMN c2 INT; ALTER TABLE t1 MODIFY COLUMN c2 INT;