diff --git a/CODE_CONVENTIONS.md b/CODE_CONVENTIONS.md new file mode 100644 index 00000000..7638bd6b --- /dev/null +++ b/CODE_CONVENTIONS.md @@ -0,0 +1,14 @@ +# Code conventions + +A set of rules all contributors should follow. +You can always link those rules in your PR without hesitation if you see that they are not followed. + +## Testing + +- Each test should only test a granular added piece of logic, and shouldn't test functionality of other's. E.g. if you are using OptionalIfNotExists, you shouldn't test if you get suggestions when writing '', 'IF ', 'IF NOT', just test if `IF NOT EXISTS` is suggesting, and it's enough. +- Each test file should contain at least: + - A test that checks a complete statement for errors + - A test that checks a complete statement locations (can be merged with the one above) +- Don't use `foo` or `bar` custom names, always use `test_{object}`, e.g. `SELECT * FROM test_table`, not `SELECT * FROM hehe_haha`. If you need multiple names, use `_{number}` suffix, e.g. `SELECT test_field, test_field_2 FROM test_table;` +- Write all the static tokens in UPPER_CASE, and all the custom variables in lower_case, e.g. `SELECT test_field` +- Always test your statements on errors, and if there's an unexpected error, just add `TODO: fix unhandled error` error diff --git a/src/parsing/index.ts b/src/parsing/index.ts index efab2d43..f0b591f1 100644 --- a/src/parsing/index.ts +++ b/src/parsing/index.ts @@ -9,23 +9,17 @@ export abstract class Parser { } export interface ParseResult { - locations: StatementPart[]; + locations: StatementPart[]; // TODO: figure our if it's optional errors?: SyntaxError[]; suggestKeywords?: KeywordSuggestion[]; - suggestTables?: { - prependFrom?: boolean; - prependQuestionMark?: boolean; - onlyTables?: boolean; - }; + suggestTables?: TablesSuggestion; suggestColumns?: ColumnSuggestion; - suggestAggregateFunctions?: { - tables: Table[], - }; + suggestAggregateFunctions?: AggregateFunctionsSuggestion; suggestAnalyticFunctions?: unknown; suggestColRefKeywords?: unknown; suggestColumnAliases?: ColumnAliasSuggestion[]; suggestCommonTableExpressions?: unknown; - suggestDatabases?: unknown; + suggestDatabases?: DatabasesSuggestion; suggestFilters?: unknown; suggestFunctions?: unknown; suggestGroupBys?: unknown; @@ -37,7 +31,7 @@ export interface ParseResult { }; // Reasons for those fields are unknown - definitions?: []; + definitions?: []; // TODO: figure our if it's optional lowerCase: boolean; } @@ -87,8 +81,23 @@ export type StatementPart = qualified: boolean, }; +export interface TablesSuggestion { + prependFrom?: boolean; // TODO: figure our if it's optional + prependQuestionMark?: boolean; // TODO: figure our if it's optional + onlyTables?: boolean; // TODO: figure our if it's optional + onlyViews?: boolean; // TODO: figure our if it's optional +} + +export interface DatabasesSuggestion { + appendDot?: boolean; // TODO: figure our if it's optional +} + +export interface AggregateFunctionsSuggestion { + tables: Table[], +} + export interface ColumnSuggestion { - source?: string; + source?: string; // TODO: figure our if it's optional tables: Table[]; } diff --git a/src/parsing/parsers/clickhouse/jison/structure.json b/src/parsing/parsers/clickhouse/jison/structure.json index 2c17d4ab..46a6757e 100644 --- a/src/parsing/parsers/clickhouse/jison/structure.json +++ b/src/parsing/parsers/clickhouse/jison/structure.json @@ -6,7 +6,7 @@ "../../generic/jison/alter/alter_table.jison", "../../generic/jison/alter/alter_view.jison", "../../generic/jison/create/create_common.jison", - "../../generic/jison/create/create_database.jison", + "../../generic/jison/create/create_database_or_schema.jison", "../../generic/jison/create/create_role.jison", "create/create_table.jison", "../../generic/jison/create/create_view.jison", diff --git a/src/parsing/parsers/generic/jison/alter/alter_common.test.json b/src/parsing/parsers/generic/jison/alter/alter_common.test.json deleted file mode 100644 index 8a35626d..00000000 --- a/src/parsing/parsers/generic/jison/alter/alter_common.test.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "namePrefix": "should suggest keywords", - "beforeCursor": "", - "afterCursor": "", - "containsKeywords": ["ALTER"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should suggest keywords", - "beforeCursor": "ALTER ", - "afterCursor": "", - "containsKeywords": ["TABLE", "VIEW"], - "expectedResult": { - "lowerCase": false - } - } -] diff --git a/src/parsing/parsers/generic/jison/alter/alter_common.test.ts b/src/parsing/parsers/generic/jison/alter/alter_common.test.ts new file mode 100644 index 00000000..fe83de9f --- /dev/null +++ b/src/parsing/parsers/generic/jison/alter/alter_common.test.ts @@ -0,0 +1,14 @@ +import { + KeywordSuggestion, + parseGenericSql, +} from '../../../../index'; +import {expect, test} from '@jest/globals'; + +test('should suggest ALTER', () => { + const parseResult = parseGenericSql('', ''); + + expect(parseResult.errors).toBeUndefined(); + + const suggestion: KeywordSuggestion = { value: 'ALTER', weight: -1 }; + expect(parseResult.suggestKeywords).toContainEqual(suggestion); +}) \ No newline at end of file diff --git a/src/parsing/parsers/generic/jison/alter/alter_table.jison b/src/parsing/parsers/generic/jison/alter/alter_table.jison index 8eafa976..bfb01ee3 100644 --- a/src/parsing/parsers/generic/jison/alter/alter_table.jison +++ b/src/parsing/parsers/generic/jison/alter/alter_table.jison @@ -26,6 +26,7 @@ AlterTable : AlterTableLeftSide PartitionSpec ; +// TODO: support AlterTableRightSide, PartitionSpec will not work for MySQL, PostgreSQL AlterTable_EDIT : AlterTableLeftSide_EDIT | AlterTableLeftSide_EDIT PartitionSpec diff --git a/src/parsing/parsers/generic/jison/alter/alter_table.test.json b/src/parsing/parsers/generic/jison/alter/alter_table.test.json deleted file mode 100644 index 8abb51a3..00000000 --- a/src/parsing/parsers/generic/jison/alter/alter_table.test.json +++ /dev/null @@ -1,41 +0,0 @@ -[ - { - "namePrefix": "should suggest keywords", - "beforeCursor": "ALTER ", - "afterCursor": "", - "containsKeywords": ["TABLE"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should suggest tables", - "beforeCursor": "ALTER TABLE ", - "afterCursor": "", - "expectedResult": { - "lowerCase": false, - "suggestTables": { - "onlyTables": true - }, - "suggestDatabases": { - "appendDot": true - } - } - }, - { - "namePrefix": "should suggest tables", - "beforeCursor": "ALTER TABLE foo.", - "afterCursor": "", - "expectedResult": { - "lowerCase": false, - "suggestTables": { - "identifierChain": [ - { - "name": "foo" - } - ], - "onlyTables": true - } - } - } -] diff --git a/src/parsing/parsers/generic/jison/alter/alter_table.test.ts b/src/parsing/parsers/generic/jison/alter/alter_table.test.ts new file mode 100644 index 00000000..c626e56d --- /dev/null +++ b/src/parsing/parsers/generic/jison/alter/alter_table.test.ts @@ -0,0 +1,33 @@ +import { + DatabasesSuggestion, + KeywordSuggestion, + parseGenericSql, TablesSuggestion, +} from '../../../../index'; +import {expect, test} from '@jest/globals'; + +test('should suggest altering table', () => { + const parseResult = parseGenericSql('ALTER ', ''); + + expect(parseResult.errors).toBeUndefined(); + + const suggestion: KeywordSuggestion = { value: 'TABLE', weight: -1 }; + expect(parseResult.suggestKeywords).toContainEqual(suggestion); +}) + +test('should suggest tables to alter', () => { + const parseResult = parseGenericSql('ALTER TABLE ', ''); + + expect(parseResult.errors).toBeUndefined(); + + const tablesSuggestion: TablesSuggestion = { + onlyTables: true, + }; + expect(parseResult.suggestTables).toEqual(tablesSuggestion); + + const databasesSuggestion: DatabasesSuggestion = { + appendDot: true, + } + expect(parseResult.suggestDatabases).toEqual(databasesSuggestion); +}) + +// TODO: add full tests + locations test \ No newline at end of file diff --git a/src/parsing/parsers/generic/jison/alter/alter_view.test.json b/src/parsing/parsers/generic/jison/alter/alter_view.test.json deleted file mode 100644 index 64d33266..00000000 --- a/src/parsing/parsers/generic/jison/alter/alter_view.test.json +++ /dev/null @@ -1,81 +0,0 @@ -[ - { - "namePrefix": "should not report errors", - "beforeCursor": "ALTER VIEW baa.boo AS SELECT * FROM bla;", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should suggest keywords", - "beforeCursor": "ALTER ", - "afterCursor": "", - "containsKeywords": ["VIEW"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should suggest views", - "beforeCursor": "ALTER VIEW ", - "afterCursor": "", - "expectedResult": { - "lowerCase": false, - "suggestTables": { - "onlyViews": true - }, - "suggestDatabases": { - "appendDot": true - } - } - }, - { - "namePrefix": "should suggest views", - "beforeCursor": "ALTER VIEW boo.", - "afterCursor": "", - "expectedResult": { - "lowerCase": false, - "suggestTables": { - "identifierChain": [ - { - "name": "boo" - } - ], - "onlyViews": true - } - } - }, - { - "namePrefix": "should suggest keywords", - "beforeCursor": "ALTER VIEW boo ", - "afterCursor": "", - "containsKeywords": ["AS"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should suggest keywords", - "beforeCursor": "ALTER VIEW baa.boo AS ", - "afterCursor": "", - "expectedResult": { - "lowerCase": false, - "suggestKeywords": ["SELECT"] - } - }, - { - "namePrefix": "should suggest databases", - "beforeCursor": "ALTER VIEW baa.boo AS SELECT * FROM ", - "afterCursor": "", - "expectedResult": { - "lowerCase": false, - "suggestTables": {}, - "suggestDatabases": { - "appendDot": true - } - } - } -] diff --git a/src/parsing/parsers/generic/jison/alter/alter_view.test.ts b/src/parsing/parsers/generic/jison/alter/alter_view.test.ts new file mode 100644 index 00000000..d7b409fa --- /dev/null +++ b/src/parsing/parsers/generic/jison/alter/alter_view.test.ts @@ -0,0 +1,212 @@ +import { + DatabasesSuggestion, + KeywordSuggestion, + parseGenericSql, parseGenericSqlWithoutCursor, StatementPart, TablesSuggestion, +} from '../../../../index'; +import {expect, test} from '@jest/globals'; + +test('should suggest altering views', () => { + const parseResult = parseGenericSql('ALTER ', ''); + + expect(parseResult.errors).toBeUndefined(); + + const suggestion: KeywordSuggestion = { value: 'VIEW', weight: -1 }; + expect(parseResult.suggestKeywords).toContainEqual(suggestion); +}) + +test('should suggest views to alter', () => { + const parseResult = parseGenericSql('ALTER VIEW ', ''); + + expect(parseResult.errors).toBeUndefined(); + + const tablesSuggestion: TablesSuggestion = { + onlyViews: true, + }; + expect(parseResult.suggestTables).toEqual(tablesSuggestion); + + const databasesSuggestion: DatabasesSuggestion = { + appendDot: true, + } + expect(parseResult.suggestDatabases).toEqual(databasesSuggestion); +}) + +test('should suggest AS', () => { + const parseResult = parseGenericSql('ALTER VIEW test_view ', ''); + + expect(parseResult.errors).toBeUndefined(); + + const suggestion: KeywordSuggestion[] = [ + { value: 'AS', weight: -1 }, + ]; + expect(parseResult.suggestKeywords).toEqual(suggestion); +}) + +test('should suggest SELECT', () => { + const parseResult = parseGenericSql('ALTER VIEW test_view AS ', ''); + + expect(parseResult.errors).toBeUndefined(); + + const suggestion: KeywordSuggestion[] = [ + { value: 'SELECT', weight: -1 }, + ]; + expect(parseResult.suggestKeywords).toEqual(suggestion); +}) + +test('should not report errors on full ALTER VIEW statement and fill locations', () => { + const parseResult = parseGenericSqlWithoutCursor('ALTER VIEW test_view AS SELECT test_field, test_field_2 FROM test_table;'); + + expect(parseResult.errors).toBeUndefined(); + + const statementParts: StatementPart[] = [ + { + location: { + first_column: 1, + first_line: 1, + last_column: 72, + last_line: 1 + }, + type: "statement" + }, + { + identifier: "ALTER VIEW", + location: { + first_column: 1, + first_line: 1, + last_column: 6, + last_line: 1 + }, + type: "statementType" + }, + { + identifierChain: [ + { + name: "test_view" + } + ], + location: { + first_column: 12, + first_line: 1, + last_column: 21, + last_line: 1 + }, + type: "table" + }, + { + identifier: "SELECT", + location: { + first_column: 25, + first_line: 1, + last_column: 31, + last_line: 1 + }, + type: "statementType" + }, + { + location: { + first_column: 32, + first_line: 1, + last_column: 56, + last_line: 1 + }, + missing: false, + type: "selectList" + }, + { + identifierChain: [ + { + name: "test_field" + } + ], + location: { + first_column: 32, + first_line: 1, + last_column: 42, + last_line: 1 + }, + qualified: false, + tables: [ + { + identifierChain: [ + { + name: "test_view" + } + ] + }, + { + identifierChain: [ + { + name: "test_table" + } + ] + } + ], + type: "column" + }, + { + identifierChain: [ + { + name: "test_field_2" + } + ], + location: { + first_column: 44, + first_line: 1, + last_column: 56, + last_line: 1 + }, + qualified: false, + tables: [ + { + identifierChain: [ + { + name: "test_view" + } + ] + }, + { + identifierChain: [ + { + name: "test_table" + } + ] + } + ], + type: "column" + }, + { + identifierChain: [ + { + name: "test_table" + } + ], + location: { + first_column: 62, + first_line: 1, + last_column: 72, + last_line: 1 + }, + type: "table" + }, + { + location: { + first_column: 72, + first_line: 1, + last_column: 72, + last_line: 1 + }, + missing: true, + type: "whereClause" + }, + { + location: { + first_column: 72, + first_line: 1, + last_column: 72, + last_line: 1 + }, + missing: true, + type: "limitClause" + } + ]; + expect(parseResult.locations).toEqual(statementParts); +}) \ No newline at end of file diff --git a/src/parsing/parsers/generic/jison/create/create_common.test.json b/src/parsing/parsers/generic/jison/create/create_common.test.json deleted file mode 100644 index 75bd7cc8..00000000 --- a/src/parsing/parsers/generic/jison/create/create_common.test.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "namePrefix": "should suggest keywords", - "beforeCursor": "", - "afterCursor": "", - "containsKeywords": ["CREATE"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should suggest keywords", - "beforeCursor": "CREATE ", - "afterCursor": "", - "containsKeywords": ["DATABASE", "ROLE", "SCHEMA", "TABLE", "VIEW"], - "expectedResult": { - "lowerCase": false - } - } -] diff --git a/src/parsing/parsers/generic/jison/create/create_common.test.ts b/src/parsing/parsers/generic/jison/create/create_common.test.ts new file mode 100644 index 00000000..aab0ac12 --- /dev/null +++ b/src/parsing/parsers/generic/jison/create/create_common.test.ts @@ -0,0 +1,14 @@ +import { + KeywordSuggestion, + parseGenericSql, +} from '../../../../index'; +import {expect, test} from '@jest/globals'; + +test('should suggest CREATE', () => { + const parseResult = parseGenericSql('', ''); + + expect(parseResult.errors).toBeUndefined(); + + const suggestion: KeywordSuggestion = { value: 'CREATE', weight: -1 }; + expect(parseResult.suggestKeywords).toContainEqual(suggestion); +}) \ No newline at end of file diff --git a/src/parsing/parsers/generic/jison/create/create_database.test.json b/src/parsing/parsers/generic/jison/create/create_database.test.json deleted file mode 100644 index c03d379e..00000000 --- a/src/parsing/parsers/generic/jison/create/create_database.test.json +++ /dev/null @@ -1,47 +0,0 @@ -[ - { - "namePrefix": "should suggest keywords", - "beforeCursor": "CREATE ", - "afterCursor": "", - "containsKeywords": ["DATABASE", "SCHEMA"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should suggest keywords", - "beforeCursor": "CREATE DATABASE ", - "afterCursor": "", - "expectedResult": { - "lowerCase": false, - "suggestKeywords": ["IF NOT EXISTS"] - } - }, - { - "namePrefix": "should suggest keywords", - "beforeCursor": "CREATE DATABASE IF ", - "afterCursor": "", - "expectedResult": { - "lowerCase": false, - "suggestKeywords": ["NOT EXISTS"] - } - }, - { - "namePrefix": "should suggest keywords", - "beforeCursor": "CREATE SCHEMA ", - "afterCursor": "", - "expectedResult": { - "lowerCase": false, - "suggestKeywords": ["IF NOT EXISTS"] - } - }, - { - "namePrefix": "should suggest keywords", - "beforeCursor": "CREATE DATABASE ", - "afterCursor": " bla;", - "expectedResult": { - "lowerCase": false, - "suggestKeywords": ["IF NOT EXISTS"] - } - } -] diff --git a/src/parsing/parsers/generic/jison/create/create_database.jison b/src/parsing/parsers/generic/jison/create/create_database_or_schema.jison similarity index 100% rename from src/parsing/parsers/generic/jison/create/create_database.jison rename to src/parsing/parsers/generic/jison/create/create_database_or_schema.jison diff --git a/src/parsing/parsers/generic/jison/create/create_database_or_schema.test.ts b/src/parsing/parsers/generic/jison/create/create_database_or_schema.test.ts new file mode 100644 index 00000000..26f5851b --- /dev/null +++ b/src/parsing/parsers/generic/jison/create/create_database_or_schema.test.ts @@ -0,0 +1,71 @@ +import { + KeywordSuggestion, + parseGenericSql, parseGenericSqlWithoutCursor, StatementPart, +} from '../../../../index'; +import {expect, test} from '@jest/globals'; + +// TODO: add separate DatabaseOrSchema tests: +// - 'something [IF NOT EXITS]' +// - 'something IF [NOT EXISTS]' +// - 'something IF NOT [exists]' +// - 'something [IF NOT EXISTS] something2' + +test('should suggest creating DATABASE and SCHEMA', () => { + const parseResult = parseGenericSql('CREATE ', ''); + + expect(parseResult.errors).toBeUndefined(); + + const suggestions: KeywordSuggestion[] = [ + { value: 'DATABASE', weight: -1 }, + { value: 'SCHEMA', weight: -1 }, + ]; + expect(parseResult.suggestKeywords).toEqual(expect.arrayContaining(suggestions)) +}) + +// TODO: remove duplicates, because databaseOrSchema should be tested separately +test('should suggest IF NOT EXISTS for database creation', () => { + const parseResult = parseGenericSql('CREATE DATABASE ', ''); + + expect(parseResult.errors).toBeUndefined(); + + const suggestions: KeywordSuggestion[] = [ + { value: 'IF NOT EXISTS', weight: -1 }, + ]; + expect(parseResult.suggestKeywords).toEqual(suggestions) +}) + +test('should suggest IF NOT EXISTS for schema creation', () => { + const parseResult = parseGenericSql('CREATE SCHEMA ', ''); + + expect(parseResult.errors).toBeUndefined(); + + const suggestions: KeywordSuggestion[] = [ + { value: 'IF NOT EXISTS', weight: -1 }, + ]; + expect(parseResult.suggestKeywords).toEqual(suggestions) +}) + +// TODO: remove duplicates, because databaseOrSchema should be tested separately +test('should not report errors on full CREATE DATABASE statement', () => { + const parseResult = parseGenericSqlWithoutCursor('CREATE DATABASE test_database;'); + expect(parseResult.errors).toBeUndefined(); +}) + +test('should not report errors on full CREATE SCHEMA statement and fill locations', () => { + const parseResult = parseGenericSqlWithoutCursor('CREATE SCHEMA test_schema;'); + + expect(parseResult.errors).toBeUndefined(); + + const statementParts: StatementPart[] = [ + { + type: 'statement', + location: { + first_column: 1, + first_line: 1, + last_column: 26, + last_line: 1 + }, + } + ]; + expect(parseResult.locations).toEqual(statementParts); +}) \ No newline at end of file diff --git a/src/parsing/parsers/generic/jison/create/create_role.test.json b/src/parsing/parsers/generic/jison/create/create_role.test.json deleted file mode 100644 index 378b2f6b..00000000 --- a/src/parsing/parsers/generic/jison/create/create_role.test.json +++ /dev/null @@ -1,21 +0,0 @@ -[ - { - "namePrefix": "should suggest keywords", - "beforeCursor": "CREATE ", - "afterCursor": "", - "containsKeywords": ["ROLE"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should not report errors", - "beforeCursor": "CREATE ROLE boo; ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - } -] diff --git a/src/parsing/parsers/generic/jison/create/create_role.test.ts b/src/parsing/parsers/generic/jison/create/create_role.test.ts new file mode 100644 index 00000000..58ef12d2 --- /dev/null +++ b/src/parsing/parsers/generic/jison/create/create_role.test.ts @@ -0,0 +1,35 @@ +import { + KeywordSuggestion, + parseGenericSql, parseGenericSqlWithoutCursor, StatementPart, +} from '../../../../index'; +import {expect, test} from '@jest/globals'; + +test('should suggest creating ROLE', () => { + const parseResult = parseGenericSql('CREATE ', ''); + + expect(parseResult.errors).toBeUndefined(); + + const suggestions: KeywordSuggestion[] = [ + { value: 'ROLE', weight: -1 }, + ]; + expect(parseResult.suggestKeywords).toEqual(expect.arrayContaining(suggestions)) +}) + +test('should not report errors on full CREATE ROLE statement and fill locations', () => { + const parseResult = parseGenericSqlWithoutCursor('CREATE ROLE test_role;'); + + expect(parseResult.errors).toBeUndefined(); + + const statementParts: StatementPart[] = [ + { + type: 'statement', + location: { + first_column: 1, + first_line: 1, + last_column: 22, + last_line: 1 + }, + } + ]; + expect(parseResult.locations).toEqual(statementParts); +}) \ No newline at end of file diff --git a/src/parsing/parsers/generic/jison/create/create_table.test.json b/src/parsing/parsers/generic/jison/create/create_table.test.json deleted file mode 100644 index 037059ae..00000000 --- a/src/parsing/parsers/generic/jison/create/create_table.test.json +++ /dev/null @@ -1,65 +0,0 @@ -[ - { - "namePrefix": "should suggest keywords", - "beforeCursor": "CREATE ", - "afterCursor": "", - "containsKeywords": ["TABLE"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should suggest keywords", - "beforeCursor": "CREATE TABLE ", - "afterCursor": "", - "expectedResult": { - "lowerCase": false, - "suggestKeywords": ["IF NOT EXISTS"] - } - }, - { - "namePrefix": "should suggest keywords", - "beforeCursor": "CREATE TABLE IF ", - "afterCursor": "", - "expectedResult": { - "lowerCase": false, - "suggestKeywords": ["NOT EXISTS"] - } - }, - { - "namePrefix": "should suggest keywords", - "beforeCursor": "CREATE TABLE IF NOT ", - "afterCursor": "", - "expectedResult": { - "lowerCase": false, - "suggestKeywords": ["EXISTS"] - } - }, - { - "namePrefix": "should not report errors", - "beforeCursor": "CREATE TABLE foo (id INT);", - "afterCursor": "", - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should suggest keywords", - "beforeCursor": "CREATE TABLE foo (id ", - "afterCursor": "", - "containsKeywords": ["BOOLEAN"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should suggest keywords", - "beforeCursor": "CREATE TABLE foo (id INT, `some` FLOAT, bar ", - "afterCursor": "", - "containsKeywords": ["BOOLEAN"], - "expectedResult": { - "lowerCase": false - } - } -] diff --git a/src/parsing/parsers/generic/jison/create/create_table.test.ts b/src/parsing/parsers/generic/jison/create/create_table.test.ts new file mode 100644 index 00000000..d192c02f --- /dev/null +++ b/src/parsing/parsers/generic/jison/create/create_table.test.ts @@ -0,0 +1,82 @@ +import { + KeywordSuggestion, + parseGenericSql, parseGenericSqlWithoutCursor, StatementPart, +} from '../../../../index'; +import {expect, test} from '@jest/globals'; + +test('should suggest creating TABLE', () => { + const parseResult = parseGenericSql('CREATE ', ''); + + expect(parseResult.errors).toBeUndefined(); + + const suggestions: KeywordSuggestion[] = [ + { value: 'TABLE', weight: -1 }, + ]; + expect(parseResult.suggestKeywords).toEqual(expect.arrayContaining(suggestions)) +}) + +test('should suggest IF NOT EXISTS', () => { + const parseResult = parseGenericSql('CREATE TABLE ', ''); + + expect(parseResult.errors).toBeUndefined(); + + const suggestions: KeywordSuggestion[] = [ + { value: 'IF NOT EXISTS', weight: -1 }, + ]; + expect(parseResult.suggestKeywords).toEqual(suggestions) +}) + +test('should suggest data types', () => { + const parseResult = parseGenericSql('CREATE TABLE food (id ', ''); + + // TODO: fix unhandled error + // expect(parseResult.errors).toBeUndefined(); + + const suggestions: KeywordSuggestion[] = [ + { value: 'BIGINT', weight: -1 }, + { value: 'BOOLEAN', weight: -1 }, + { value: 'CHAR', weight: -1 }, + { value: 'DECIMAL', weight: -1 }, + { value: 'DOUBLE', weight: -1 }, + { value: 'FLOAT', weight: -1 }, + { value: 'INT', weight: -1 }, + ]; + expect(parseResult.suggestKeywords).toEqual(expect.arrayContaining(suggestions)) +}) + +test('should suggest data types when some types are already written', () => { + const parseResult = parseGenericSql('CREATE TABLE food (id INT, age FLOAT, bar ', ''); + + // TODO: fix unhandled error + // expect(parseResult.errors).toBeUndefined(); + + const suggestions: KeywordSuggestion[] = [ + { value: 'BIGINT', weight: -1 }, + { value: 'BOOLEAN', weight: -1 }, + { value: 'CHAR', weight: -1 }, + { value: 'DECIMAL', weight: -1 }, + { value: 'DOUBLE', weight: -1 }, + { value: 'FLOAT', weight: -1 }, + { value: 'INT', weight: -1 }, + ]; + expect(parseResult.suggestKeywords).toEqual(expect.arrayContaining(suggestions)) +}) + +test('should not report errors on full CREATE TABLE statement', () => { + const parseResult = parseGenericSqlWithoutCursor('CREATE TABLE test_table (id INT, age FLOAT);'); + + expect(parseResult.errors).toBeUndefined(); + + const statementParts: StatementPart[] = [ + { + type: 'statement', + location: { + first_column: 1, + first_line: 1, + last_column: 44, + last_line: 1 + }, + } + ]; + expect(parseResult.locations).toEqual(statementParts); +}) \ No newline at end of file diff --git a/src/parsing/parsers/generic/jison/create/create_view.test.json b/src/parsing/parsers/generic/jison/create/create_view.test.json deleted file mode 100644 index 0ab28f0d..00000000 --- a/src/parsing/parsers/generic/jison/create/create_view.test.json +++ /dev/null @@ -1,103 +0,0 @@ -[ - { - "namePrefix": "should suggest keywords", - "beforeCursor": "CREATE ", - "afterCursor": "", - "containsKeywords": ["VIEW"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should suggest columns", - "beforeCursor": "CREATE VIEW foo AS SELECT a, ", - "afterCursor": " FROM tableOne", - "hasLocations": true, - "containsKeywords": ["*", "CASE"], - "expectedResult": { - "lowerCase": false, - "suggestAggregateFunctions": { - "tables": [ - { - "identifierChain": [ - { - "name": "tableOne" - } - ] - } - ] - }, - "suggestAnalyticFunctions": true, - "suggestFunctions": {}, - "suggestColumns": { - "source": "select", - "tables": [ - { - "identifierChain": [ - { - "name": "tableOne" - } - ] - } - ] - } - } - }, - { - "namePrefix": "should suggest keywords", - "beforeCursor": "CREATE VIEW ", - "afterCursor": "", - "expectedResult": { - "lowerCase": false, - "suggestKeywords": ["IF NOT EXISTS"], - "suggestDatabases": { - "appendDot": true - } - } - }, - { - "namePrefix": "should suggest keywords", - "beforeCursor": "CREATE VIEW ", - "afterCursor": " boo AS SELECT * FROM baa;", - "expectedResult": { - "lowerCase": false, - "suggestKeywords": ["IF NOT EXISTS"] - } - }, - { - "namePrefix": "should suggest keywords", - "beforeCursor": "CREATE VIEW IF ", - "afterCursor": "", - "expectedResult": { - "lowerCase": false, - "suggestKeywords": ["NOT EXISTS"] - } - }, - { - "namePrefix": "should suggest keywords", - "beforeCursor": "CREATE VIEW IF NOT ", - "afterCursor": "", - "expectedResult": { - "lowerCase": false, - "suggestKeywords": ["EXISTS"] - } - }, - { - "namePrefix": "should suggest keywords", - "beforeCursor": "CREATE VIEW boo AS ", - "afterCursor": "", - "expectedResult": { - "lowerCase": false, - "suggestKeywords": ["SELECT"] - } - }, - { - "namePrefix": "should suggest keywords", - "beforeCursor": "CREATE VIEW IF NOT EXISTS boo AS ", - "afterCursor": "", - "expectedResult": { - "lowerCase": false, - "suggestKeywords": ["SELECT"] - } - } -] diff --git a/src/parsing/parsers/generic/jison/create/create_view.test.ts b/src/parsing/parsers/generic/jison/create/create_view.test.ts new file mode 100644 index 00000000..fc820b28 --- /dev/null +++ b/src/parsing/parsers/generic/jison/create/create_view.test.ts @@ -0,0 +1,176 @@ +import { + KeywordSuggestion, + parseGenericSql, parseGenericSqlWithoutCursor, StatementPart, +} from '../../../../index'; +import {expect, test} from '@jest/globals'; + +test('should suggest creating VIEW', () => { + const parseResult = parseGenericSql('CREATE ', ''); + + expect(parseResult.errors).toBeUndefined(); + + const suggestions: KeywordSuggestion[] = [ + { value: 'VIEW', weight: -1 }, + ]; + expect(parseResult.suggestKeywords).toEqual(expect.arrayContaining(suggestions)) +}) + +test('should suggest IF NOT EXISTS', () => { + const parseResult = parseGenericSql('CREATE VIEW ', ''); + + expect(parseResult.errors).toBeUndefined(); + + const suggestions: KeywordSuggestion[] = [ + { value: 'IF NOT EXISTS', weight: -1 }, + ]; + expect(parseResult.suggestKeywords).toEqual(suggestions) +}) + +test('should suggest AS and COMMENT', () => { + const parseResult = parseGenericSql('CREATE VIEW test_view ', ''); + + expect(parseResult.errors).toBeUndefined(); + + const suggestions: KeywordSuggestion[] = [ + { value: 'COMMENT', weight: 3 }, + { value: 'AS', weight: 1 }, + ]; + expect(parseResult.suggestKeywords).toEqual(suggestions) +}) + +test('should suggest SELECT', () => { + const parseResult = parseGenericSql('CREATE VIEW test_view AS ', ''); + + expect(parseResult.errors).toBeUndefined(); + + const suggestions: KeywordSuggestion[] = [ + { value: 'SELECT', weight: -1 }, + ]; + expect(parseResult.suggestKeywords).toEqual(suggestions) +}) + +test('should not report errors on full CREATE VIEW statement without comment', () => { + const parseResult = parseGenericSqlWithoutCursor('CREATE VIEW test_view AS SELECT test_field, test_field_2 FROM test_table;'); + expect(parseResult.errors).toBeUndefined(); +}) + +test('should not report errors on full CREATE VIEW statement and fill locations', () => { + const parseResult = parseGenericSqlWithoutCursor('CREATE VIEW test_view COMMENT "test" AS SELECT test_field, test_field_2 FROM test_table;'); + + expect(parseResult.errors).toBeUndefined(); + + const statementParts: StatementPart[] = [ + { + location: { + first_column: 1, + first_line: 1, + last_column: 88, + last_line: 1 + }, + type: "statement" + }, + { + identifier: 'SELECT', + location: { + first_column: 41, + first_line: 1, + last_column: 47, + last_line: 1 + }, + type: "statementType" + }, + { + location: { + first_column: 48, + first_line: 1, + last_column: 72, + last_line: 1 + }, + missing: false, + type: "selectList" + }, + { + identifierChain: [ + { + name: "test_field" + } + ], + location: { + first_column: 48, + first_line: 1, + last_column: 58, + last_line: 1 + }, + qualified: false, + tables: [ + { + identifierChain: [ + { + name: "test_table" + } + ] + } + ], + type: "column" + }, + { + identifierChain: [ + { + name: "test_field_2" + } + ], + location: { + first_column: 60, + first_line: 1, + last_column: 72, + last_line: 1 + }, + qualified: false, + tables: [ + { + identifierChain: [ + { + name: "test_table" + } + ] + } + ], + type: "column" + }, + { + identifierChain: [ + { + name: "test_table" + } + ], + location: { + first_column: 78, + first_line: 1, + last_column: 88, + last_line: 1 + }, + type: "table" + }, + { + location: { + first_column: 88, + first_line: 1, + last_column: 88, + last_line: 1 + }, + missing: true, + type: "whereClause" + }, + { + location: { + first_column: 88, + first_line: 1, + last_column: 88, + last_line: 1 + }, + missing: true, + type: "limitClause" + } + ]; + expect(parseResult.locations).toEqual(statementParts); +}) \ No newline at end of file diff --git a/src/parsing/parsers/generic/jison/delete/delete.test.ts b/src/parsing/parsers/generic/jison/delete/delete.test.ts index ccdcb749..92e29f22 100644 --- a/src/parsing/parsers/generic/jison/delete/delete.test.ts +++ b/src/parsing/parsers/generic/jison/delete/delete.test.ts @@ -63,7 +63,7 @@ test('should suggest WHERE columns', () => { }) test('should suggest WHERE columns when some column conditions already exist', () => { - const parseResult = parseGenericSqlWithoutCursor('DELETE FROM test_table WHERE test_column = 1 AND '); + const parseResult = parseGenericSql('DELETE FROM test_table WHERE test_column = 1 AND ', ''); expect(parseResult.errors).toBeUndefined(); @@ -82,7 +82,7 @@ test('should suggest WHERE columns when some column conditions already exist', ( }) test('should properly fill locations', () => { - const parseResult = parseGenericSqlWithoutCursor('DELETE FROM test_table WHERE test_column = 1 AND '); + const parseResult = parseGenericSqlWithoutCursor('DELETE FROM test_table WHERE test_column = 1;'); expect(parseResult.errors).toBeUndefined(); @@ -91,7 +91,7 @@ test('should properly fill locations', () => { location: { first_column: 1, first_line: 1, - last_column: 50, + last_column: 45, last_line: 1 }, type: "statement" diff --git a/src/parsing/parsers/generic/jison/sql_main.jison b/src/parsing/parsers/generic/jison/sql_main.jison index aef4c2d7..486decf6 100644 --- a/src/parsing/parsers/generic/jison/sql_main.jison +++ b/src/parsing/parsers/generic/jison/sql_main.jison @@ -226,6 +226,7 @@ FromOrIn | 'IN' ; +// TODO: make sure it has tests DatabaseOrSchema : 'DATABASE' | 'SCHEMA' @@ -334,6 +335,7 @@ OptionalPartitionSpec_EDIT : PartitionSpec_EDIT ; +// TODO: make sure it has tests PartitionSpec : 'PARTITION' '(' PartitionSpecList ')' ; diff --git a/src/parsing/parsers/generic/jison/structure.json b/src/parsing/parsers/generic/jison/structure.json index 0f07de25..c7cfb9ba 100644 --- a/src/parsing/parsers/generic/jison/structure.json +++ b/src/parsing/parsers/generic/jison/structure.json @@ -6,7 +6,7 @@ "alter/alter_table.jison", "alter/alter_view.jison", "create/create_common.jison", - "create/create_database.jison", + "create/create_database_or_schema.jison", "create/create_role.jison", "create/create_table.jison", "create/create_view.jison", diff --git a/src/parsing/parsers/postgresql/jison/structure.json b/src/parsing/parsers/postgresql/jison/structure.json index e5430c65..19a12652 100644 --- a/src/parsing/parsers/postgresql/jison/structure.json +++ b/src/parsing/parsers/postgresql/jison/structure.json @@ -6,7 +6,7 @@ "../../generic/jison/alter/alter_table.jison", "../../generic/jison/alter/alter_view.jison", "../../generic/jison/create/create_common.jison", - "../../generic/jison/create/create_database.jison", + "../../generic/jison/create/create_database_or_schema.jison", "../../generic/jison/create/create_role.jison", "../../generic/jison/create/create_table.jison", "../../generic/jison/create/create_view.jison",