diff --git a/src/autocomplete/index.ts b/src/autocomplete/index.ts index c665aca2..f6e293a9 100644 --- a/src/autocomplete/index.ts +++ b/src/autocomplete/index.ts @@ -28,10 +28,7 @@ export interface ParseResult { suggestGroupBys?: unknown; suggestIdentifiers?: IdentifierSuggestion[]; suggestTemplates?: boolean; - suggestEngines?: { - engines: Engines; - functionalEngines: Engines; - }; + suggestEngines?: EnginesSuggestion; colRef?: ColumnReference; useDatabase?: string; @@ -176,6 +173,11 @@ export interface ColumnAliasSuggestion { type Engines = string[]; +export type EnginesSuggestion = { + engines: Engines; + functionalEngines: Engines; +}; + export function parseGenericSql(queryBeforeCursor: string, queryAfterCursor: string): ParseResult { const parser = genericAutocompleteParser as Parser; return parser.parseSql(queryBeforeCursor, queryAfterCursor); diff --git a/src/autocomplete/parsers/clickhouse/grammar/create/create_table.test.json b/src/autocomplete/parsers/clickhouse/grammar/create/create_table.test.json deleted file mode 100644 index e74b8e46..00000000 --- a/src/autocomplete/parsers/clickhouse/grammar/create/create_table.test.json +++ /dev/null @@ -1,480 +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": ["DOUBLE"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should suggest keywords", - "beforeCursor": "CREATE TABLE foo (id INT, `some` FLOAT, bar ", - "afterCursor": "", - "containsKeywords": ["DOUBLE"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should suggest ENGINE keyword", - "beforeCursor": "CREATE TABLE foo (id INT, `some` FLOAT) ", - "afterCursor": "", - "containsKeywords": ["ENGINE"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should suggest equal symbol keyword", - "beforeCursor": "CREATE TABLE foo (id INT, `some` FLOAT) ENGINE ", - "afterCursor": "", - "containsKeywords": ["="], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should suggest engines", - "beforeCursor": "CREATE TABLE foo (id INT, `some` FLOAT) ENGINE = ", - "afterCursor": "", - "expectedResult": { - "lowerCase": false, - "suggestEngines": { - "engines": ["Null", "Set", "Log", "Memory", "TinyLog", "StripeLog"], - "functionalEngines": [ - "MergeTree()", - "Merge()", - "ReplacingMergeTree()", - "CollapsingMergeTree()", - "AggregatingMergeTree()", - "Buffer()", - "Dictionary()", - "Distributed()", - "File()", - "GraphiteMergeTree()", - "Join()", - "Kafka()", - "MySQL()", - "URL()", - "ReplicatedAggregatingMergeTree()", - "ReplicatedCollapsingMergeTree()", - "ReplicatedGraphiteMergeTree()", - "ReplicatedMergeTree()", - "ReplicatedReplacingMergeTree()", - "ReplicatedSummingMergeTree()", - "ReplicatedVersionedCollapsingMergeTree()", - "SummingMergeTree()", - "VersionedCollapsingMergeTree()", - "PostgreSQL()" - ] - } - } - }, - { - "namePrefix": "should not throw errors", - "beforeCursor": "CREATE TABLE foo (id INT, `some` FLOAT) ENGINE = Memory; ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should throw error", - "beforeCursor": "CREATE TABLE foo (id INT, `some` FLOAT); ", - "afterCursor": "", - "noErrors": false, - "containsKeywords": ["SELECT"], - "expectedErrors": [ - { - "text": ";", - "token": ";", - "line": 0, - "loc": {"first_line": 1, "last_line": 1, "first_column": 39, "last_column": 40} - } - ], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should not throw error", - "beforeCursor": "CREATE TABLE foo (id INT, `some` FLOAT) ENGINE = ReplacingMergeTree(update_ts); ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should throw error", - "beforeCursor": "CREATE TABLE foo (id UInt32, `some` Float64) ENGINE = ReplacingMergeTree(update_ts, ) ", - "afterCursor": "", - "noErrors": false, - "expectedErrors": [ - { - "text": ")", - "token": ")", - "line": 0, - "loc": {"first_line": 1, "last_line": 1, "first_column": 84, "last_column": 85} - } - ], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should accept Enum argument", - "beforeCursor": "CREATE TABLE foo (id Enum8('string' = 1)) ENGINE = Memory; ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should accept Enum argument", - "beforeCursor": "CREATE TABLE foo (id Enum8('string' = 1, 'string', 'string')) ENGINE = Memory; ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should accept simple aggregate function", - "beforeCursor": "CREATE TABLE foo (id SimpleAggregateFunction(sum, String)) ENGINE = Memory; ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should accept Decimal64", - "beforeCursor": "CREATE TABLE foo (id Decimal64(12)) ENGINE = Memory; ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should accept Decimal", - "beforeCursor": "CREATE TABLE foo (id Decimal(12, 12)) ENGINE = Memory; ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should accept Nullable", - "beforeCursor": "CREATE TABLE foo (id Nullable(Decimal(12, 12))) ENGINE = Memory; ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should accept aggregate function", - "beforeCursor": "CREATE TABLE foo (id AggregateFunction(sum, String)) ENGINE = Memory; ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should accept date time", - "beforeCursor": "CREATE TABLE foo (id DateTime('Some/Timezone')) ENGINE = Memory; ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should accept date time", - "beforeCursor": "CREATE TABLE foo (id Tuple(s String, i Int64)) ENGINE = Memory; ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should accept date time", - "beforeCursor": "CREATE TABLE foo (id Nested(id String, data Int64)) ENGINE = Memory; ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should accept geo type", - "beforeCursor": "CREATE TABLE foo (id Point) ENGINE = Memory; ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should accept geo type", - "beforeCursor": "CREATE TABLE foo (id Ring) ENGINE = Memory; ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should accept geo type", - "beforeCursor": "CREATE TABLE foo (id Polygon) ENGINE = Memory; ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should accept binary type", - "beforeCursor": "CREATE TABLE foo (id BINARY(12)) ENGINE = Memory; ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should accept binary type", - "beforeCursor": "CREATE TABLE foo (id BINARY(NULL)) ENGINE = Memory; ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should accept GeoType", - "beforeCursor": "CREATE TABLE foo (id MultiPolygon) ENGINE = Memory; ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should accept LowCardinality type", - "beforeCursor": "CREATE TABLE foo (id LowCardinality(String)) ENGINE = Memory; ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should accept Array type", - "beforeCursor": "CREATE TABLE foo (id Array(Array(String))) ENGINE = Memory; ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should accept FixedString type", - "beforeCursor": "CREATE TABLE foo (id FixedString(12)) ENGINE = Memory; ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should accept FixedString type", - "beforeCursor": "CREATE TABLE foo (id FixedString) ENGINE = Memory; ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should accept TIMESTAMP type", - "beforeCursor": "CREATE TABLE foo (id TIMESTAMP('data')) ENGINE = Memory; ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should accept TIMESTAMP type", - "beforeCursor": "CREATE TABLE foo (id TIMESTAMP(NULL)) ENGINE = Memory; ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should accept all data types", - "beforeCursor": "CREATE TABLE foo (field IPv6, field IPv4, field LowCardinality(String), field Decimal(1, 2), field String, field Decimal64(1), field Decimal32(1), field Decimal128(1), field Float64, field Float32, field Int64, field SimpleAggregateFunction(sum, String), field AggregateFunction(sum, String), field Array(String), field Array(Array(String)), field Nothing, field UInt16, field Enum16('text', 'text' = 1), field Enum16('text', 'text' = -1), field UInt32, field Date, field Int8, field Int32, field Enum8('text', 'text' = 1), field Enum8('text', 'text' = -1), field UInt64, field IntervalSecond, field Int16, field DateTime('Country/Timezone'), field Enum('text', 'text' = 1), field Enum('text', 'text' = -1), field Tuple(text1 String, text2 String), field IntervalMonth, field Nested(field String), field IntervalMinute, field IntervalHour, field IntervalWeek, field IntervalDay, field UInt8, field IntervalQuarter, field UUID, field IntervalYear, field LONGBLOB, field LONGBLOB(12), field MEDIUMBLOB, field MEDIUMBLOB(12), field TINYBLOB, field TINYBLOB(12), field BIGINT, field SMALLINT, field TIMESTAMP(NULL), field TIMESTAMP('value'), field INTEGER, field INT, field DOUBLE, field MEDIUMTEXT, field MEDIUMTEXT(12), field TINYINT, field DEC(1, 2), field BINARY(NULL), field BINARY(12), field FLOAT, field CHAR, field CHAR(12), field VARCHAR, field VARCHAR(12), field TEXT, field TINYTEXT, field TINYTEXT(12), field LONGTEXT, field LONGTEXT(12), field BLOB, field BLOB(12), field Point, field Ring, field Polygon, field MultiPolygon, field Map(String, String)) ENGINE = Memory; ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should suggest all data types", - "beforeCursor": "CREATE TABLE foo (id ", - "afterCursor": ") ENGINE = Memory; ", - "noErrors": true, - "containsKeywords": [ - "IPv6", - "IPv4", - "LowCardinality", - "Decimal", - "String", - "Decimal64", - "Decimal32", - "Decimal128", - "Float64", - "Float32", - "Int64", - "SimpleAggregateFunction", - "Array", - "Nothing", - "UInt16", - "Enum16", - "UInt32", - "Date", - "Int8", - "Int32", - "Enum8", - "UInt64", - "IntervalSecond", - "Int16", - "FixedString", - "Nullable", - "AggregateFunction", - "DateTime", - "Enum", - "Tuple", - "IntervalMonth", - "Nested", - "IntervalMinute", - "IntervalHour", - "IntervalWeek", - "IntervalDay", - "UInt8", - "IntervalQuarter", - "UUID", - "IntervalYear", - "LONGBLOB", - "MEDIUMBLOB", - "TINYBLOB", - "BIGINT", - "SMALLINT", - "TIMESTAMP", - "INTEGER", - "INT", - "DOUBLE", - "MEDIUMTEXT", - "TINYINT", - "DEC", - "BINARY", - "FLOAT", - "CHAR", - "VARCHAR", - "TEXT", - "TINYTEXT", - "LONGTEXT", - "BLOB", - "Point", - "Ring", - "Polygon", - "MultiPolygon" - ], - "expectedResult": { - "lowerCase": false - } - } -] diff --git a/src/autocomplete/parsers/clickhouse/grammar/create/create_table.test.ts b/src/autocomplete/parsers/clickhouse/grammar/create/create_table.test.ts new file mode 100644 index 00000000..f17b5655 --- /dev/null +++ b/src/autocomplete/parsers/clickhouse/grammar/create/create_table.test.ts @@ -0,0 +1,456 @@ +import {expect, test} from '@jest/globals'; + +import { + EnginesSuggestion, + KeywordSuggestion, + ParserSyntaxError, + parseClickHouse, +} from '../../../../index'; + +test('should suggest TABLE', () => { + const parseResult = parseClickHouse('CREATE ', ''); + + expect(parseResult.errors).toBeUndefined(); + + const suggestion: KeywordSuggestion = {value: 'TABLE', weight: -1}; + expect(parseResult.suggestKeywords).toContainEqual(suggestion); +}); + +test('should suggest IF NOT EXISTS', () => { + const parseResult = parseClickHouse('CREATE TABLE ', ''); + + expect(parseResult.errors).toBeUndefined(); + + const suggestion: KeywordSuggestion = {value: 'IF NOT EXISTS', weight: -1}; + expect(parseResult.suggestKeywords).toContainEqual(suggestion); +}); + +test('should suggest NOT EXISTS', () => { + const parseResult = parseClickHouse('CREATE TABLE IF ', ''); + + expect(parseResult.errors).toBeUndefined(); + + const suggestion: KeywordSuggestion = {value: 'NOT EXISTS', weight: -1}; + expect(parseResult.suggestKeywords).toContainEqual(suggestion); +}); + +test('should suggest EXISTS', () => { + const parseResult = parseClickHouse('CREATE TABLE IF NOT ', ''); + + expect(parseResult.errors).toBeUndefined(); + + const suggestion: KeywordSuggestion = {value: 'EXISTS', weight: -1}; + expect(parseResult.suggestKeywords).toContainEqual(suggestion); +}); + +test('should suggest DOUBLE', () => { + const parseResult = parseClickHouse('CREATE TABLE test_table (test_column ', ''); + + const suggestion: KeywordSuggestion = {value: 'DOUBLE', weight: -1}; + expect(parseResult.suggestKeywords).toContainEqual(suggestion); +}); + +test('should suggest DOUBLE for third column', () => { + const parseResult = parseClickHouse( + 'CREATE TABLE test_table (test_column_1 INT, test_column_2 FLOAT, test_column_3 ', + '', + ); + + const suggestion: KeywordSuggestion = {value: 'DOUBLE', weight: -1}; + expect(parseResult.suggestKeywords).toContainEqual(suggestion); +}); + +test('should suggest ENGINE', () => { + const parseResult = parseClickHouse( + 'CREATE TABLE test_table (test_column_1 INT, test_column_2 FLOAT) ', + '', + ); + + const suggestion: KeywordSuggestion = {value: 'ENGINE', weight: 13}; + expect(parseResult.suggestKeywords).toContainEqual(suggestion); +}); + +test('should suggest equal sign', () => { + const parseResult = parseClickHouse( + 'CREATE TABLE test_table (test_column_1 INT, test_column_2 FLOAT) ENGINE ', + '', + ); + + const suggestion: KeywordSuggestion = {value: '=', weight: -1}; + expect(parseResult.suggestKeywords).toContainEqual(suggestion); +}); + +test('should suggest engines', () => { + const parseResult = parseClickHouse( + 'CREATE TABLE test_table (test_column_1 INT, test_column_2 FLOAT) ENGINE = ', + '', + ); + + const engineSuggestion: EnginesSuggestion = { + engines: ['Null', 'Set', 'Log', 'Memory', 'TinyLog', 'StripeLog'], + functionalEngines: [ + 'MergeTree()', + 'Merge()', + 'ReplacingMergeTree()', + 'CollapsingMergeTree()', + 'AggregatingMergeTree()', + 'Buffer()', + 'Dictionary()', + 'Distributed()', + 'File()', + 'GraphiteMergeTree()', + 'Join()', + 'Kafka()', + 'MySQL()', + 'URL()', + 'ReplicatedAggregatingMergeTree()', + 'ReplicatedCollapsingMergeTree()', + 'ReplicatedGraphiteMergeTree()', + 'ReplicatedMergeTree()', + 'ReplicatedReplacingMergeTree()', + 'ReplicatedSummingMergeTree()', + 'ReplicatedVersionedCollapsingMergeTree()', + 'SummingMergeTree()', + 'VersionedCollapsingMergeTree()', + 'PostgreSQL()', + ], + }; + expect(parseResult.suggestEngines).toEqual(engineSuggestion); +}); + +test('should not report error on statement with Memory engine', () => { + const parseResult = parseClickHouse( + 'CREATE TABLE test_table (test_column_1 INT, test_column_2 FLOAT) ENGINE = Memory; ', + '', + ); + + expect(parseResult.errors).toBeUndefined(); +}); + +test('should report error because of missing ENGINE', () => { + const parseResult = parseClickHouse('CREATE TABLE test_table (test_column INT); ', ''); + + const errors: ParserSyntaxError[] = [ + { + expected: ["'CURSOR'", "'PARTITION'", "'ENGINE'"], + line: 0, + loc: {first_column: 41, first_line: 1, last_column: 42, last_line: 1}, + recoverable: true, + ruleId: '76_745', + text: ';', + token: ';', + }, + ]; + + expect(parseResult.errors).toEqual(errors); +}); + +test('should not report error on statement with ReplacingMergeTree engine', () => { + const parseResult = parseClickHouse( + 'CREATE TABLE test_table (test_column_1 INT, test_column_2 FLOAT) ENGINE = ReplacingMergeTree(update_ts); ', + '', + ); + + expect(parseResult.errors).toBeUndefined(); +}); + +test('should report error because of incomplete ReplacingMergeTree syntax', () => { + const parseResult = parseClickHouse( + 'CREATE TABLE test_table (test_column_1 UInt32, `test_column_2` Float64) ENGINE = ReplacingMergeTree(update_ts, ) ', + '', + ); + + const error: Partial = { + text: ')', + token: ')', + line: 0, + loc: {first_line: 1, last_line: 1, first_column: 111, last_column: 112}, + }; + + expect(parseResult.errors).toContainEqual(expect.objectContaining(error)); +}); + +test('should accept Enum', () => { + const parseResult = parseClickHouse( + "CREATE TABLE test_table (test_column Enum8('string' = 1)) ENGINE = Memory; ", + '', + ); + + expect(parseResult.errors).toBeUndefined(); +}); + +test('should accept complex Enum', () => { + const parseResult = parseClickHouse( + "CREATE TABLE test_table (test_column Enum8('string' = 1, 'string', 'string')) ENGINE = Memory; ", + '', + ); + + expect(parseResult.errors).toBeUndefined(); +}); + +test('should accept SimpleAggregateFunction', () => { + const parseResult = parseClickHouse( + 'CREATE TABLE test_table (test_column SimpleAggregateFunction(sum, String)) ENGINE = Memory; ', + '', + ); + + expect(parseResult.errors).toBeUndefined(); +}); + +test('should accept Decimal64', () => { + const parseResult = parseClickHouse( + 'CREATE TABLE test_table (test_column Decimal64(12)) ENGINE = Memory; ', + '', + ); + + expect(parseResult.errors).toBeUndefined(); +}); + +test('should accept Decimal', () => { + const parseResult = parseClickHouse( + 'CREATE TABLE test_table (test_column Decimal(12, 12)) ENGINE = Memory; ', + '', + ); + + expect(parseResult.errors).toBeUndefined(); +}); + +test('should accept Nullable', () => { + const parseResult = parseClickHouse( + 'CREATE TABLE test_table (test_column Nullable(Decimal(12, 12))) ENGINE = Memory; ', + '', + ); + + expect(parseResult.errors).toBeUndefined(); +}); + +test('should accept AggregateFunction', () => { + const parseResult = parseClickHouse( + 'CREATE TABLE test_table (test_column AggregateFunction(sum, String)) ENGINE = Memory; ', + '', + ); + + expect(parseResult.errors).toBeUndefined(); +}); + +test('should accept DateTime', () => { + const parseResult = parseClickHouse( + "CREATE TABLE test_table (test_column DateTime('Some/Timezone')) ENGINE = Memory; ", + '', + ); + + expect(parseResult.errors).toBeUndefined(); +}); + +test('should accept Tuple', () => { + const parseResult = parseClickHouse( + 'CREATE TABLE test_table (test_column Tuple(s String, i Int64)) ENGINE = Memory; ', + '', + ); + + expect(parseResult.errors).toBeUndefined(); +}); + +test('should accept Nested', () => { + const parseResult = parseClickHouse( + 'CREATE TABLE test_table (test_column Nested(id String, data Int64)) ENGINE = Memory; ', + '', + ); + + expect(parseResult.errors).toBeUndefined(); +}); + +test('should accept Point', () => { + const parseResult = parseClickHouse( + 'CREATE TABLE test_table (test_column Point) ENGINE = Memory; ', + '', + ); + + expect(parseResult.errors).toBeUndefined(); +}); + +test('should accept Ring', () => { + const parseResult = parseClickHouse( + 'CREATE TABLE test_table (test_column Ring) ENGINE = Memory; ', + '', + ); + + expect(parseResult.errors).toBeUndefined(); +}); + +test('should accept Polygon', () => { + const parseResult = parseClickHouse( + 'CREATE TABLE test_table (test_column Polygon) ENGINE = Memory; ', + '', + ); + + expect(parseResult.errors).toBeUndefined(); +}); + +test('should accept BINARY', () => { + const parseResult = parseClickHouse( + 'CREATE TABLE test_table (test_column BINARY(12)) ENGINE = Memory; ', + '', + ); + + expect(parseResult.errors).toBeUndefined(); +}); + +test('should accept BINARY(NULL)', () => { + const parseResult = parseClickHouse( + 'CREATE TABLE test_table (test_column BINARY(NULL)) ENGINE = Memory; ', + '', + ); + + expect(parseResult.errors).toBeUndefined(); +}); + +test('should accept MultiPolygon', () => { + const parseResult = parseClickHouse( + 'CREATE TABLE test_table (test_column MultiPolygon) ENGINE = Memory; ', + '', + ); + + expect(parseResult.errors).toBeUndefined(); +}); + +test('should accept LowCardinality', () => { + const parseResult = parseClickHouse( + 'CREATE TABLE test_table (test_column LowCardinality(String)) ENGINE = Memory; ', + '', + ); + + expect(parseResult.errors).toBeUndefined(); +}); + +test('should accept Array', () => { + const parseResult = parseClickHouse( + 'CREATE TABLE test_table (test_column Array(Array(String))) ENGINE = Memory; ', + '', + ); + + expect(parseResult.errors).toBeUndefined(); +}); + +test('should accept FixedString', () => { + const parseResult = parseClickHouse( + 'CREATE TABLE test_table (test_column FixedString(12)) ENGINE = Memory; ', + '', + ); + + expect(parseResult.errors).toBeUndefined(); +}); + +test('should accept FixedString without arguments', () => { + const parseResult = parseClickHouse( + 'CREATE TABLE test_table (test_column FixedString) ENGINE = Memory; ', + '', + ); + + expect(parseResult.errors).toBeUndefined(); +}); + +test('should accept TIMESTAMP', () => { + const parseResult = parseClickHouse( + "CREATE TABLE test_table (test_column TIMESTAMP('data')) ENGINE = Memory; ", + '', + ); + + expect(parseResult.errors).toBeUndefined(); +}); + +test('should accept TIMESTAMP(NULL)', () => { + const parseResult = parseClickHouse( + 'CREATE TABLE test_table (test_column TIMESTAMP(NULL)) ENGINE = Memory; ', + '', + ); + + expect(parseResult.errors).toBeUndefined(); +}); + +test('should accept all data types', () => { + const parseResult = parseClickHouse( + "CREATE TABLE test_table (field IPv6, field IPv4, field LowCardinality(String), field Decimal(1, 2), field String, field Decimal64(1), field Decimal32(1), field Decimal128(1), field Float64, field Float32, field Int64, field SimpleAggregateFunction(sum, String), field AggregateFunction(sum, String), field Array(String), field Array(Array(String)), field Nothing, field UInt16, field Enum16('text', 'text' = 1), field Enum16('text', 'text' = -1), field UInt32, field Date, field Int8, field Int32, field Enum8('text', 'text' = 1), field Enum8('text', 'text' = -1), field UInt64, field IntervalSecond, field Int16, field DateTime('Country/Timezone'), field Enum('text', 'text' = 1), field Enum('text', 'text' = -1), field Tuple(text1 String, text2 String), field IntervalMonth, field Nested(field String), field IntervalMinute, field IntervalHour, field IntervalWeek, field IntervalDay, field UInt8, field IntervalQuarter, field UUID, field IntervalYear, field LONGBLOB, field LONGBLOB(12), field MEDIUMBLOB, field MEDIUMBLOB(12), field TINYBLOB, field TINYBLOB(12), field BIGINT, field SMALLINT, field TIMESTAMP(NULL), field TIMESTAMP('value'), field INTEGER, field INT, field DOUBLE, field MEDIUMTEXT, field MEDIUMTEXT(12), field TINYINT, field DEC(1, 2), field BINARY(NULL), field BINARY(12), field FLOAT, field CHAR, field CHAR(12), field VARCHAR, field VARCHAR(12), field TEXT, field TINYTEXT, field TINYTEXT(12), field LONGTEXT, field LONGTEXT(12), field BLOB, field BLOB(12), field Point, field Ring, field Polygon, field MultiPolygon, field Map(String, String)) ENGINE = Memory; ", + '', + ); + + expect(parseResult.errors).toBeUndefined(); +}); + +test('should suggest all data types', () => { + const parseResult = parseClickHouse( + 'CREATE TABLE test_table (test_column ', + ') ENGINE = Memory; ', + ); + + expect(parseResult.errors).toBeUndefined(); + + const suggestions: KeywordSuggestion[] = [ + {value: 'AggregateFunction', weight: -1}, + {value: 'Array', weight: -1}, + {value: 'BIGINT', weight: -1}, + {value: 'BINARY', weight: -1}, + {value: 'BLOB', weight: -1}, + {value: 'CHAR', weight: -1}, + {value: 'Date', weight: -1}, + {value: 'DateTime', weight: -1}, + {value: 'DEC', weight: -1}, + {value: 'Decimal', weight: -1}, + {value: 'Decimal128', weight: -1}, + {value: 'Decimal32', weight: -1}, + {value: 'Decimal64', weight: -1}, + {value: 'DOUBLE', weight: -1}, + {value: 'Enum', weight: -1}, + {value: 'Enum16', weight: -1}, + {value: 'Enum8', weight: -1}, + {value: 'FixedString', weight: -1}, + {value: 'FLOAT', weight: -1}, + {value: 'Float32', weight: -1}, + {value: 'Float64', weight: -1}, + {value: 'INT', weight: -1}, + {value: 'Int16', weight: -1}, + {value: 'Int32', weight: -1}, + {value: 'Int64', weight: -1}, + {value: 'Int8', weight: -1}, + {value: 'INTEGER', weight: -1}, + {value: 'IntervalDay', weight: -1}, + {value: 'IntervalHour', weight: -1}, + {value: 'IntervalMinute', weight: -1}, + {value: 'IntervalMonth', weight: -1}, + {value: 'IntervalQuarter', weight: -1}, + {value: 'IntervalSecond', weight: -1}, + {value: 'IntervalWeek', weight: -1}, + {value: 'IntervalYear', weight: -1}, + {value: 'IPv4', weight: -1}, + {value: 'IPv6', weight: -1}, + {value: 'LONGBLOB', weight: -1}, + {value: 'LONGTEXT', weight: -1}, + {value: 'LowCardinality', weight: -1}, + {value: 'MEDIUMBLOB', weight: -1}, + {value: 'MEDIUMTEXT', weight: -1}, + {value: 'MultiPolygon', weight: -1}, + {value: 'Nested', weight: -1}, + {value: 'Nothing', weight: -1}, + {value: 'Nullable', weight: -1}, + {value: 'Point', weight: -1}, + {value: 'Polygon', weight: -1}, + {value: 'Ring', weight: -1}, + {value: 'SimpleAggregateFunction', weight: -1}, + {value: 'SMALLINT', weight: -1}, + {value: 'String', weight: -1}, + {value: 'TEXT', weight: -1}, + {value: 'TIMESTAMP', weight: -1}, + {value: 'TINYBLOB', weight: -1}, + {value: 'TINYINT', weight: -1}, + {value: 'TINYTEXT', weight: -1}, + {value: 'Tuple', weight: -1}, + {value: 'UInt16', weight: -1}, + {value: 'UInt32', weight: -1}, + {value: 'UInt64', weight: -1}, + {value: 'UInt8', weight: -1}, + {value: 'UUID', weight: -1}, + {value: 'VARCHAR', weight: -1}, + ]; + + expect(parseResult.suggestKeywords).toEqual(suggestions); +}); diff --git a/src/autocomplete/parsers/clickhouse/grammar/explain/explain.test.json b/src/autocomplete/parsers/clickhouse/grammar/explain/explain.test.json deleted file mode 100644 index 81287db2..00000000 --- a/src/autocomplete/parsers/clickhouse/grammar/explain/explain.test.json +++ /dev/null @@ -1,69 +0,0 @@ -[ - { - "namePrefix": "should not report errors on EXPLAIN SELECT statement", - "beforeCursor": "EXPLAIN SELECT * FROM test_table; ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should not report errors on EXPLAIN DELETE statement", - "beforeCursor": "EXPLAIN DELETE FROM test_table; ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should not report errors on EXPLAIN CREATE statement", - "beforeCursor": "EXPLAIN CREATE TABLE Persons (id int) ENGINE = Memory; ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should report error on double EXPLAIN statement", - "beforeCursor": "EXPLAIN EXPLAIN SELECT * FROM test_table; ", - "afterCursor": "", - "expectedErrors": [ - { - "text": "EXPLAIN", - "token": "EXPLAIN", - "loc": { - "first_line": 1, - "last_line": 1, - "first_column": 8, - "last_column": 15 - } - } - ] - }, - { - "namePrefix": "should suggest EXPLAIN", - "beforeCursor": "", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["EXPLAIN"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should suggest query", - "beforeCursor": "EXPLAIN ", - "afterCursor": "", - "noErrors": true, - "containsKeywords": ["ALTER", "CREATE", "DELETE", "DROP", "INSERT", "SELECT", "SET", "UPDATE"], - "expectedResult": { - "lowerCase": false - } - } -] diff --git a/src/autocomplete/parsers/clickhouse/grammar/explain/explain.test.ts b/src/autocomplete/parsers/clickhouse/grammar/explain/explain.test.ts new file mode 100644 index 00000000..a00d774a --- /dev/null +++ b/src/autocomplete/parsers/clickhouse/grammar/explain/explain.test.ts @@ -0,0 +1,74 @@ +import {expect, test} from '@jest/globals'; + +import {KeywordSuggestion, ParserSyntaxError, parseClickHouse} from '../../../../index'; + +test('should not report errors on EXPLAIN SELECT statement', () => { + const parseResult = parseClickHouse('EXPLAIN SELECT * FROM test_table; ', ''); + + expect(parseResult.errors).toBeUndefined(); +}); + +test('should not report errors on EXPLAIN DELETE statement', () => { + const parseResult = parseClickHouse('EXPLAIN DELETE FROM test_table; ', ''); + + expect(parseResult.errors).toBeUndefined(); +}); + +test('should not report errors on EXPLAIN CREATE statement', () => { + const parseResult = parseClickHouse( + 'EXPLAIN CREATE TABLE test_table (test_column int) ENGINE = Memory; ', + '', + ); + + expect(parseResult.errors).toBeUndefined(); +}); + +test('should report error on double EXPLAIN statement', () => { + const parseResult = parseClickHouse('EXPLAIN EXPLAIN SELECT * FROM test_table ', ''); + + const error: Partial = { + text: 'EXPLAIN', + token: 'EXPLAIN', + loc: { + first_line: 1, + last_line: 1, + first_column: 8, + last_column: 15, + }, + }; + expect(parseResult.errors).toContainEqual(expect.objectContaining(error)); +}); + +test('should suggest EXPLAIN', () => { + const parseResult = parseClickHouse('', ''); + + expect(parseResult.errors).toBeUndefined(); + + const suggestion: KeywordSuggestion = {value: 'EXPLAIN', weight: -1}; + expect(parseResult.suggestKeywords).toContainEqual(suggestion); +}); + +test('should suggest query', () => { + const parseResult = parseClickHouse('EXPLAIN ', ''); + + expect(parseResult.errors).toBeUndefined(); + + const suggestions: KeywordSuggestion[] = [ + {value: 'ALTER', weight: -1}, + {value: 'CREATE', weight: -1}, + {value: 'DELETE', weight: -1}, + {value: 'DESCRIBE', weight: -1}, + {value: 'DROP', weight: -1}, + {value: 'GRANT', weight: -1}, + {value: 'INSERT', weight: -1}, + {value: 'REVOKE', weight: -1}, + {value: 'SELECT', weight: -1}, + {value: 'SET', weight: -1}, + {value: 'SHOW', weight: -1}, + {value: 'TRUNCATE', weight: -1}, + {value: 'UPDATE', weight: -1}, + {value: 'USE', weight: -1}, + {value: 'WITH', weight: -1}, + ]; + expect(parseResult.suggestKeywords).toEqual(suggestions); +}); diff --git a/src/autocomplete/parsers/clickhouse/grammar/main.test.json b/src/autocomplete/parsers/clickhouse/grammar/main.test.json deleted file mode 100644 index 3276cf47..00000000 --- a/src/autocomplete/parsers/clickhouse/grammar/main.test.json +++ /dev/null @@ -1,71 +0,0 @@ -[ - { - "namePrefix": "should suggest keywords", - "beforeCursor": "[;;", - "afterCursor": "", - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should suggest keywords", - "beforeCursor": ";", - "afterCursor": ";", - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should suggest keywords", - "beforeCursor": "", - "afterCursor": ";;;;", - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should suggest keywords", - "beforeCursor": "foo", - "afterCursor": "bar", - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false - } - }, - { - "namePrefix": "should contain suggestTemplates", - "beforeCursor": "", - "afterCursor": "", - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false, - "suggestTemplates": true - } - }, - { - "namePrefix": "should contain suggestTemplates with EXPLAIN prefix", - "beforeCursor": "EXPLAIN ", - "afterCursor": "", - "containsKeywords": ["SELECT"], - "expectedResult": { - "lowerCase": false, - "suggestTemplates": true - } - }, - { - "namePrefix": "should not contain suggestTemplates", - "beforeCursor": "SELECT * FROM ", - "afterCursor": "", - "expectedResult": { - "lowerCase": false, - "suggestTemplates": false, - "suggestTables": {}, - "suggestDatabases": { - "appendDot": true - } - } - } -] diff --git a/src/autocomplete/parsers/clickhouse/grammar/main.test.ts b/src/autocomplete/parsers/clickhouse/grammar/main.test.ts new file mode 100644 index 00000000..e4220029 --- /dev/null +++ b/src/autocomplete/parsers/clickhouse/grammar/main.test.ts @@ -0,0 +1,63 @@ +import {expect, test} from '@jest/globals'; + +import {KeywordSuggestion, parseClickHouse} from '../../../index'; + +test('should suggest SELECT despite errors before cursor', () => { + const parseResult = parseClickHouse('[;;', ''); + + const suggestion: KeywordSuggestion = {value: 'SELECT', weight: -1}; + expect(parseResult.suggestKeywords).toContainEqual(suggestion); +}); + +test('should suggest SELECT despite errors before and after cursor', () => { + const parseResult = parseClickHouse(';', ';'); + + const suggestion: KeywordSuggestion = {value: 'SELECT', weight: -1}; + expect(parseResult.suggestKeywords).toContainEqual(suggestion); +}); + +test('should suggest SELECT despite errors after cursor', () => { + const parseResult = parseClickHouse('', ';;;;'); + + const suggestion: KeywordSuggestion = {value: 'SELECT', weight: -1}; + expect(parseResult.suggestKeywords).toContainEqual(suggestion); +}); + +test('should suggest SELECT with non-empty editor', () => { + const parseResult = parseClickHouse('test_database', 'test_table'); + + expect(parseResult.errors).toBeUndefined(); + + const suggestion: KeywordSuggestion = {value: 'SELECT', weight: -1}; + expect(parseResult.suggestKeywords).toContainEqual(suggestion); +}); + +test('should suggest SELECT and contain suggestTemplates', () => { + const parseResult = parseClickHouse('', ''); + + expect(parseResult.errors).toBeUndefined(); + + const suggestion: KeywordSuggestion = {value: 'SELECT', weight: -1}; + expect(parseResult.suggestKeywords).toContainEqual(suggestion); + + expect(parseResult.suggestTemplates).toEqual(true); +}); + +test('should suggest SELECT and contain suggestTemplates with EXPLAIN prefix', () => { + const parseResult = parseClickHouse('EXPLAIN', ''); + + expect(parseResult.errors).toBeUndefined(); + + const suggestion: KeywordSuggestion = {value: 'SELECT', weight: -1}; + expect(parseResult.suggestKeywords).toContainEqual(suggestion); + + expect(parseResult.suggestTemplates).toEqual(true); +}); + +test('should not contain suggestTemplates with SELECT prefix', () => { + const parseResult = parseClickHouse('SELECT * FROM ', ''); + + expect(parseResult.errors).toBeUndefined(); + + expect(parseResult.suggestTemplates).toBeUndefined(); +}); diff --git a/src/autocomplete/parsers/clickhouse/grammar/select/clickhouse_select.test.ts b/src/autocomplete/parsers/clickhouse/grammar/select/clickhouse_select.test.ts new file mode 100644 index 00000000..fc9e7fc5 --- /dev/null +++ b/src/autocomplete/parsers/clickhouse/grammar/select/clickhouse_select.test.ts @@ -0,0 +1,35 @@ +import {expect, test} from '@jest/globals'; + +import {KeywordSuggestion, parseClickHouse} from '../../../../index'; + +// Only Clickhouse specific tests + +test('should suggest type keywords after CAST ... AS', () => { + const parseResult = parseClickHouse('SELECT CAST(test AS ', ''); + + const stringSuggestion: KeywordSuggestion = {value: 'String', weight: -1}; + expect(parseResult.suggestKeywords).toContainEqual(stringSuggestion); + + const textSuggestion: KeywordSuggestion = {value: 'TEXT', weight: -1}; + expect(parseResult.suggestKeywords).toContainEqual(textSuggestion); +}); + +test('should suggest type keywords after CAST ... AS ST', () => { + const parseResult = parseClickHouse('SELECT CAST(test AS ST', ''); + + const stringSuggestion: KeywordSuggestion = {value: 'String', weight: -1}; + expect(parseResult.suggestKeywords).toContainEqual(stringSuggestion); + + const textSuggestion: KeywordSuggestion = {value: 'TEXT', weight: -1}; + expect(parseResult.suggestKeywords).toContainEqual(textSuggestion); +}); + +test('should suggest type keywords after CAST(AS', () => { + const parseResult = parseClickHouse('SELECT CAST(AS ', ''); + + const stringSuggestion: KeywordSuggestion = {value: 'String', weight: -1}; + expect(parseResult.suggestKeywords).toContainEqual(stringSuggestion); + + const textSuggestion: KeywordSuggestion = {value: 'TEXT', weight: -1}; + expect(parseResult.suggestKeywords).toContainEqual(textSuggestion); +});