diff --git a/README.md b/README.md index b41da467ba..a6899b2002 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,8 @@ All fields are optional and all fields that are not specified will be filled wit - [**`useTabs`**](docs/useTabs.md) to use tabs for indentation. - [**`keywordCase`**](docs/keywordCase.md) uppercases or lowercases keywords. - [**`identifierCase`**](docs/identifierCase.md) uppercases or lowercases identifiers. (**experimental!**) +- [**`dataTypeCase`**](docs/dataTypeCase.md) uppercases or lowercases data types. (**experimental!**) +- [**`functionCase`**](docs/functionCase.md) uppercases or lowercases function names. (**experimental!**) - [**`indentStyle`**](docs/indentStyle.md) defines overall indentation style. - [**`logicalOperatorNewline`**](docs/logicalOperatorNewline.md) newline before or after boolean operator (AND, OR, XOR). - [**`expressionWidth`**](docs/expressionWidth.md) maximum number of characters in parenthesized expressions to be kept on single line. diff --git a/docs/dataTypeCase.md b/docs/dataTypeCase.md new file mode 100644 index 0000000000..4c5d4407a4 --- /dev/null +++ b/docs/dataTypeCase.md @@ -0,0 +1,45 @@ +# dataTypeCase (experimental) + +Converts data types to upper- or lowercase. + +## Options + +- `"preserve"` (default) preserves the original case. +- `"upper"` converts to uppercase. +- `"lower"` converts to lowercase. + +### preserve + +```sql +CREATE TABLE user ( + id InTeGeR PRIMARY KEY, + first_name VarChaR(30) NOT NULL, + bio teXT, + is_email_verified BooL, + created_timestamp timestamP +); +``` + +### upper + +```sql +CREATE TABLE user ( + id INTEGER PRIMARY KEY, + first_name VARCHAR(30) NOT NULL, + bio TEXT, + is_email_verified BOOL, + created_timestamp TIMESTAMP +); +``` + +### lower + +```sql +CREATE TABLE user ( + id integer PRIMARY KEY, + first_name varchar(30) NOT NULL, + bio text, + is_email_verified bool, + created_timestamp timestamp +); +``` diff --git a/docs/functionCase.md b/docs/functionCase.md new file mode 100644 index 0000000000..59537605eb --- /dev/null +++ b/docs/functionCase.md @@ -0,0 +1,48 @@ +# functionCase (experimental) + +Converts function names to upper- or lowercase. + +## Options + +- `"preserve"` (default) preserves the original case. +- `"upper"` converts to uppercase. +- `"lower"` converts to lowercase. + +### preserve + +```sql +SELECT + Concat(Trim(first_name), ' ', Trim(last_name)) AS name, + Max(salary) AS max_pay, + Cast(ssid AS INT) +FROM + employee +WHERE + expires_at > Now() +``` + +### upper + +```sql +SELECT + CONCAT(TRIM(first_name), ' ', TRIM(last_name)) AS name, + MAX(salary) AS max_pay, + CAST(ssid AS INT) +FROM + employee +WHERE + expires_at > NOW() +``` + +### lower + +```sql +SELECT + concat(trim(first_name), ' ', trim(last_name)) AS name, + max(salary) AS max_pay, + cast(ssid AS INT) +FROM + employee +WHERE + expires_at > now() +``` diff --git a/docs/identifierCase.md b/docs/identifierCase.md index 5826a89c30..6d8ad300bc 100644 --- a/docs/identifierCase.md +++ b/docs/identifierCase.md @@ -6,7 +6,6 @@ This option doesn't yet support all types of identifiers: - prefixed variables like `@my_var` are not converted. - parameter placeholders like `:param` are not converted. -- function names like `count(*)` are not converted (this are currently governed by `keywordCase` option instead.) ## Options @@ -26,7 +25,8 @@ from where Column6 and Column7 -group by Column4 +group by + Column4 ``` ### upper @@ -41,7 +41,8 @@ from where COLUMN6 and COLUMN7 -group by COLUMN4 +group by + COLUMN4 ``` ### lower @@ -56,5 +57,6 @@ from where column6 and column7 -group by column4 +group by + column4 ``` diff --git a/src/FormatOptions.ts b/src/FormatOptions.ts index 7cf102759a..937a4ac858 100644 --- a/src/FormatOptions.ts +++ b/src/FormatOptions.ts @@ -8,6 +8,10 @@ export type KeywordCase = 'preserve' | 'upper' | 'lower'; export type IdentifierCase = 'preserve' | 'upper' | 'lower'; +export type DataTypeCase = 'preserve' | 'upper' | 'lower'; + +export type FunctionCase = 'preserve' | 'upper' | 'lower'; + export type LogicalOperatorNewline = 'before' | 'after'; export interface FormatOptions { @@ -15,6 +19,8 @@ export interface FormatOptions { useTabs: boolean; keywordCase: KeywordCase; identifierCase: IdentifierCase; + dataTypeCase: DataTypeCase; + functionCase: FunctionCase; indentStyle: IndentStyle; logicalOperatorNewline: LogicalOperatorNewline; expressionWidth: number; diff --git a/src/formatter/ExpressionFormatter.ts b/src/formatter/ExpressionFormatter.ts index 88a6f2694f..fe1505c006 100644 --- a/src/formatter/ExpressionFormatter.ts +++ b/src/formatter/ExpressionFormatter.ts @@ -28,6 +28,8 @@ import { CaseExpressionNode, CaseWhenNode, CaseElseNode, + DataTypeNode, + ParameterizedDataTypeNode, } from '../parser/ast.js'; import Layout, { WS } from './Layout.js'; @@ -94,6 +96,8 @@ export default class ExpressionFormatter { switch (node.type) { case NodeType.function_call: return this.formatFunctionCall(node); + case NodeType.parameterized_data_type: + return this.formatParameterizedDataType(node); case NodeType.array_subscript: return this.formatArraySubscript(node); case NodeType.property_access: @@ -130,6 +134,8 @@ export default class ExpressionFormatter { return this.formatLineComment(node); case NodeType.block_comment: return this.formatBlockComment(node); + case NodeType.data_type: + return this.formatDataType(node); case NodeType.keyword: return this.formatKeywordNode(node); } @@ -137,19 +143,37 @@ export default class ExpressionFormatter { private formatFunctionCall(node: FunctionCallNode) { this.withComments(node.nameKw, () => { - this.layout.add(this.showKw(node.nameKw)); + this.layout.add(this.showFunctionKw(node.nameKw)); + }); + this.formatNode(node.parenthesis); + } + + private formatParameterizedDataType(node: ParameterizedDataTypeNode) { + this.withComments(node.dataType, () => { + this.layout.add(this.showDataType(node.dataType)); }); this.formatNode(node.parenthesis); } private formatArraySubscript(node: ArraySubscriptNode) { + let formattedArray: string; + + switch (node.array.type) { + case NodeType.data_type: + formattedArray = this.showDataType(node.array); + break; + case NodeType.keyword: + formattedArray = this.showKw(node.array); + break; + default: + formattedArray = this.showIdentifier(node.array); + break; + } + this.withComments(node.array, () => { - this.layout.add( - node.array.type === NodeType.keyword - ? this.showKw(node.array) - : this.showIdentifier(node.array) - ); + this.layout.add(formattedArray); }); + this.formatNode(node.parenthesis); } @@ -489,6 +513,10 @@ export default class ExpressionFormatter { } } + private formatDataType(node: DataTypeNode) { + this.layout.add(this.showDataType(node), WS.SPACE); + } + private showKw(node: KeywordNode): string { if (isTabularToken(node.tokenType)) { return toTabularFormat(this.showNonTabularKw(node), this.cfg.indentStyle); @@ -509,6 +537,26 @@ export default class ExpressionFormatter { } } + private showFunctionKw(node: KeywordNode): string { + if (isTabularToken(node.tokenType)) { + return toTabularFormat(this.showNonTabularFunctionKw(node), this.cfg.indentStyle); + } else { + return this.showNonTabularFunctionKw(node); + } + } + + // Like showFunctionKw(), but skips tabular formatting + private showNonTabularFunctionKw(node: KeywordNode): string { + switch (this.cfg.functionCase) { + case 'preserve': + return equalizeWhitespace(node.raw); + case 'upper': + return node.text; + case 'lower': + return node.text.toLowerCase(); + } + } + private showIdentifier(node: IdentifierNode): string { if (node.quoted) { return node.text; @@ -523,4 +571,15 @@ export default class ExpressionFormatter { } } } + + private showDataType(node: DataTypeNode): string { + switch (this.cfg.dataTypeCase) { + case 'preserve': + return equalizeWhitespace(node.raw); + case 'upper': + return node.text; + case 'lower': + return node.text.toLowerCase(); + } + } } diff --git a/src/languages/bigquery/bigquery.formatter.ts b/src/languages/bigquery/bigquery.formatter.ts index a9368adcb2..833e635a32 100644 --- a/src/languages/bigquery/bigquery.formatter.ts +++ b/src/languages/bigquery/bigquery.formatter.ts @@ -164,9 +164,8 @@ export const bigquery: DialectOptions = { reservedSetOperations, reservedJoins, reservedPhrases, - reservedKeywords: - // Temporary, will be replaced by reservedDataTypes - [...new Set(keywords.concat(dataTypes))], + reservedKeywords: keywords, + reservedDataTypes: dataTypes, reservedFunctionNames: functions, extraParens: ['[]'], stringTypes: [ diff --git a/src/languages/bigquery/bigquery.functions.ts b/src/languages/bigquery/bigquery.functions.ts index 8a0663421b..4253fa5a92 100644 --- a/src/languages/bigquery/bigquery.functions.ts +++ b/src/languages/bigquery/bigquery.functions.ts @@ -585,13 +585,4 @@ export const functions: string[] = [ // pivot 'PIVOT', 'UNPIVOT', - - // Data types with parameters like VARCHAR(100) - // https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#parameterized_data_types - 'BYTES', - 'NUMERIC', - 'DECIMAL', - 'BIGNUMERIC', - 'BIGDECIMAL', - 'STRING', ]; diff --git a/src/languages/bigquery/bigquery.keywords.ts b/src/languages/bigquery/bigquery.keywords.ts index 1880e824ca..63d4bd5997 100644 --- a/src/languages/bigquery/bigquery.keywords.ts +++ b/src/languages/bigquery/bigquery.keywords.ts @@ -111,6 +111,7 @@ export const keywords: string[] = [ ]; export const dataTypes: string[] = [ + // https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types 'ARRAY', // parametric, ARRAY 'BOOL', 'BYTES', // parameterised, BYTES(Length) @@ -134,12 +135,4 @@ export const dataTypes: string[] = [ 'STRUCT', // parametric, STRUCT 'TIME', 'TIMEZONE', - - // https://cloud.google.com/bigquery/docs/reference/standard-sql/conversion_functions#formatting_syntax - 'HEX', - 'BASEX', - 'BASE64M', - 'ASCII', - 'UTF-8', - 'UTF8', ]; diff --git a/src/languages/db2/db2.formatter.ts b/src/languages/db2/db2.formatter.ts index e84e9c1fb0..2cbfd09e6c 100644 --- a/src/languages/db2/db2.formatter.ts +++ b/src/languages/db2/db2.formatter.ts @@ -266,9 +266,8 @@ export const db2: DialectOptions = { reservedSetOperations, reservedJoins, reservedPhrases, - reservedKeywords: - // Temporary, will be replaced by reservedDataTypes - [...new Set(keywords.concat(dataTypes))], + reservedKeywords: keywords, + reservedDataTypes: dataTypes, reservedFunctionNames: functions, extraParens: ['[]'], stringTypes: [ diff --git a/src/languages/db2/db2.functions.ts b/src/languages/db2/db2.functions.ts index fc45871741..a2039b83ef 100644 --- a/src/languages/db2/db2.functions.ts +++ b/src/languages/db2/db2.functions.ts @@ -56,25 +56,19 @@ export const functions: string[] = [ 'ATAN', 'ATAN2', 'ATANH', - 'BIGINT', - 'BINARY', 'BITAND', 'BITANDNOT', 'BITOR', 'BITXOR', 'BITNOT', - 'BLOB', - 'BOOLEAN', 'BPCHAR', 'BSON_TO_JSON', 'BTRIM', 'CARDINALITY', 'CEILING', 'CEIL', - 'CHAR', 'CHARACTER_LENGTH', 'CHR', - 'CLOB', 'COALESCE', 'COLLATION_KEY', 'COLLATION_KEY_BIT', @@ -85,8 +79,6 @@ export const functions: string[] = [ 'COT', 'CURSOR_ROWCOUNT', 'DATAPARTITIONNUM', - 'DATE', - 'DATETIME', 'DATE_PART', 'DATE_TRUNC', 'DAY', @@ -98,12 +90,9 @@ export const functions: string[] = [ 'DAYS', 'DAYS_BETWEEN', 'DAYS_TO_END_OF_MONTH', - 'DBCLOB', 'DBPARTITIONNUM', 'DECFLOAT', 'DECFLOAT_FORMAT', - 'DECIMAL', - 'DEC', 'DECODE', 'DECRYPT_BIN', 'DECRYPT_CHAR', @@ -112,7 +101,6 @@ export const functions: string[] = [ 'DIFFERENCE', 'DIGITS', 'DOUBLE_PRECISION', - 'DOUBLE', 'EMPTY_BLOB', 'EMPTY_CLOB', 'EMPTY_DBCLOB', @@ -122,14 +110,10 @@ export const functions: string[] = [ 'EXP', 'EXTRACT', 'FIRST_DAY', - 'FLOAT', - 'FLOAT4', - 'FLOAT8', 'FLOOR', 'FROM_UTC_TIMESTAMP', 'GENERATE_UNIQUE', 'GETHINT', - 'GRAPHIC', 'GREATEST', 'HASH', 'HASH4', @@ -147,12 +131,6 @@ export const functions: string[] = [ 'INSTR2', 'INSTR4', 'INSTRB', - 'INT', - 'INTERVAL', - 'INTEGER', - 'INT2', - 'INT4', - 'INT8', 'INTNAND', 'INTNOR', 'INTNXOR', @@ -193,10 +171,6 @@ export const functions: string[] = [ 'MONTHNAME', 'MONTHS_BETWEEN', 'MULTIPLY_ALT', - 'NCHAR', - 'NCHR', - 'NCLOB', - 'NVARCHAR', 'NEXT_DAY', 'NEXT_MONTH', 'NEXT_QUARTER', @@ -205,7 +179,6 @@ export const functions: string[] = [ 'NORMALIZE_DECFLOAT', 'NOW', 'NULLIF', - 'NUMERIC', 'NVL', 'NVL2', 'OCTET_LENGTH', @@ -224,7 +197,6 @@ export const functions: string[] = [ 'RAND', 'RANDOM', 'RAWTOHEX', - 'REAL', 'REC2XML', 'REGEXP_COUNT', 'REGEXP_EXTRACT', @@ -235,7 +207,8 @@ export const functions: string[] = [ 'REGEXP_SUBSTR', 'REPEAT', 'REPLACE', - 'RID and RID_BIT', + 'RID', + 'RID_BIT', 'RIGHT', 'ROUND', 'ROUND_TIMESTAMP', @@ -249,7 +222,6 @@ export const functions: string[] = [ 'SIGN', 'SIN', 'SINH', - 'SMALLINT', 'SOUNDEX', 'SPACE', 'SQRT', @@ -270,8 +242,6 @@ export const functions: string[] = [ 'THIS_QUARTER', 'THIS_WEEK', 'THIS_YEAR', - 'TIME', - 'TIMESTAMP', 'TIMESTAMP_FORMAT', 'TIMESTAMP_ISO', 'TIMESTAMPDIFF', @@ -301,12 +271,9 @@ export const functions: string[] = [ 'UNICODE_STR', 'UPPER', 'VALUE', - 'VARBINARY', - 'VARCHAR', 'VARCHAR_BIT_FORMAT', 'VARCHAR_FORMAT', 'VARCHAR_FORMAT_BIT', - 'VARGRAPHIC', 'VERIFY_GROUP_FOR_USER', 'VERIFY_ROLE_FOR_USER', 'VERIFY_TRUSTED_CONTEXT_ROLE_FOR_USER', diff --git a/src/languages/db2/db2.keywords.ts b/src/languages/db2/db2.keywords.ts index d012308080..f125edef0d 100644 --- a/src/languages/db2/db2.keywords.ts +++ b/src/languages/db2/db2.keywords.ts @@ -403,12 +403,44 @@ export const keywords: string[] = [ export const dataTypes: string[] = [ // https://www.ibm.com/docs/en/db2-for-zos/12?topic=columns-data-types + 'ARRAY', + 'BIGINT', + 'BINARY', + 'BLOB', + 'BOOLEAN', 'CCSID', 'CHAR', 'CHARACTER', + 'CLOB', 'DATE', + 'DATETIME', + 'DBCLOB', + 'DEC', + 'DECIMAL', 'DOUBLE', - 'LONG', + 'DOUBLE PRECISION', + 'FLOAT', + 'FLOAT4', + 'FLOAT8', + 'GRAPHIC', + 'INT', + 'INT2', + 'INT4', + 'INT8', + 'INTEGER', + 'INTERVAL', + 'LONG VARCHAR', + 'LONG VARGRAPHIC', + 'NCHAR', + 'NCHR', + 'NCLOB', + 'NVARCHAR', + 'NUMERIC', + 'SMALLINT', + 'REAL', 'TIME', 'TIMESTAMP', + 'VARBINARY', + 'VARCHAR', + 'VARGRAPHIC', ]; diff --git a/src/languages/db2i/db2i.formatter.ts b/src/languages/db2i/db2i.formatter.ts index 5c2a7a05b7..04dd2b02ad 100644 --- a/src/languages/db2i/db2i.formatter.ts +++ b/src/languages/db2i/db2i.formatter.ts @@ -162,9 +162,8 @@ export const db2i: DialectOptions = { reservedSetOperations, reservedJoins, reservedPhrases, - reservedKeywords: - // Temporary, will be replaced by reservedDataTypes - [...new Set(keywords.concat(dataTypes))], + reservedKeywords: keywords, + reservedDataTypes: dataTypes, reservedFunctionNames: functions, nestedBlockComments: true, extraParens: ['[]'], diff --git a/src/languages/db2i/db2i.functions.ts b/src/languages/db2i/db2i.functions.ts index a3a42d559d..4eaa0d03d0 100644 --- a/src/languages/db2i/db2i.functions.ts +++ b/src/languages/db2i/db2i.functions.ts @@ -65,25 +65,19 @@ export const functions: string[] = [ 'ATANH', 'BASE64_DECODE', 'BASE64_ENCODE', - 'BIGINT', - 'BINARY', 'BIT_LENGTH', 'BITAND', 'BITANDNOT', 'BITNOT', 'BITOR', 'BITXOR', - 'BLOB', - 'BOOLEAN', 'BSON_TO_JSON', 'CARDINALITY', 'CEIL', 'CEILING', 'CHAR_LENGTH', - 'CHAR', 'CHARACTER_LENGTH', 'CHR', - 'CLOB', 'COALESCE', 'COMPARE_DECFLOAT', 'CONCAT', @@ -96,7 +90,6 @@ export const functions: string[] = [ 'DATABASE', 'DATAPARTITIONNAME', 'DATAPARTITIONNUM', - 'DATE', 'DAY', 'DAYNAME', 'DAYOFMONTH', @@ -104,14 +97,10 @@ export const functions: string[] = [ 'DAYOFWEEK', 'DAYOFYEAR', 'DAYS', - 'DBCLOB', 'DBPARTITIONNAME', 'DBPARTITIONNUM', - 'DEC', 'DECFLOAT_FORMAT', 'DECFLOAT_SORTKEY', - 'DECFLOAT', - 'DECIMAL', 'DECRYPT_BINARY', 'DECRYPT_BIT', 'DECRYPT_CHAR', @@ -137,7 +126,6 @@ export const functions: string[] = [ 'EXP', 'EXTRACT', 'FIRST_DAY', - 'FLOAT', 'FLOOR', 'GENERATE_UNIQUE', 'GET_BLOB_FROM_FILE', @@ -145,7 +133,6 @@ export const functions: string[] = [ 'GET_DBCLOB_FROM_FILE', 'GET_XML_FILE', 'GETHINT', - 'GRAPHIC', 'GREATEST', 'HASH_MD5', 'HASH_ROW', @@ -173,8 +160,6 @@ export const functions: string[] = [ 'IFNULL', 'INSERT', 'INSTR', - 'INT', - 'INTEGER', 'INTERPRET', 'ISFALSE', 'ISNOTFALSE', @@ -234,8 +219,8 @@ export const functions: string[] = [ 'QUARTER', 'RADIANS', 'RAISE_ERROR', - 'RANDOM or RAND', - 'REAL', + 'RANDOM', + 'RAND', 'REGEXP_COUNT', 'REGEXP_INSTR', 'REGEXP_REPLACE', @@ -246,7 +231,6 @@ export const functions: string[] = [ 'RIGHT', 'ROUND_TIMESTAMP', 'ROUND', - 'ROWID', 'RPAD', 'RRN', 'RTRIM', @@ -255,7 +239,6 @@ export const functions: string[] = [ 'SIGN', 'SIN', 'SINH', - 'SMALLINT', 'SOUNDEX', 'SPACE', 'SQRT', @@ -269,10 +252,8 @@ export const functions: string[] = [ 'TABLE_SCHEMA', 'TAN', 'TANH', - 'TIME', 'TIMESTAMP_FORMAT', 'TIMESTAMP_ISO', - 'TIMESTAMP', 'TIMESTAMPDIFF_BIG', 'TIMESTAMPDIFF', 'TO_CHAR', @@ -293,12 +274,9 @@ export const functions: string[] = [ 'URL_ENCODE', 'VALUE', 'VARBINARY_FORMAT', - 'VARBINARY', 'VARCHAR_BIT_FORMAT', 'VARCHAR_FORMAT_BINARY', 'VARCHAR_FORMAT', - 'VARCHAR', - 'VARGRAPHIC', 'VERIFY_GROUP_FOR_USER', 'WEEK_ISO', 'WEEK', diff --git a/src/languages/db2i/db2i.keywords.ts b/src/languages/db2i/db2i.keywords.ts index 9e54732fca..ab897f01c4 100644 --- a/src/languages/db2i/db2i.keywords.ts +++ b/src/languages/db2i/db2i.keywords.ts @@ -498,6 +498,7 @@ export const keywords: string[] = [ export const dataTypes: string[] = [ // https://www.ibm.com/docs/en/i/7.2?topic=iaodsd-odbc-data-types-how-they-correspond-db2-i-database-types + 'ARRAY', 'BIGINT', 'BINARY', 'BIT', @@ -513,9 +514,12 @@ export const dataTypes: string[] = [ 'DBCLOB', 'DECFLOAT', 'DECIMAL', + 'DEC', 'DOUBLE', + 'DOUBLE PRECISION', 'FLOAT', 'GRAPHIC', + 'INT', 'INTEGER', 'LONG', 'NUMERIC', diff --git a/src/languages/hive/hive.formatter.ts b/src/languages/hive/hive.formatter.ts index e62c8c827d..ebb6f74cda 100644 --- a/src/languages/hive/hive.formatter.ts +++ b/src/languages/hive/hive.formatter.ts @@ -91,9 +91,8 @@ export const hive: DialectOptions = { reservedSetOperations, reservedJoins, reservedPhrases, - reservedKeywords: - // Temporary, will be replaced by reservedDataTypes - [...new Set(keywords.concat(dataTypes))], + reservedKeywords: keywords, + reservedDataTypes: dataTypes, reservedFunctionNames: functions, extraParens: ['[]'], stringTypes: ['""-bs', "''-bs"], diff --git a/src/languages/hive/hive.functions.ts b/src/languages/hive/hive.functions.ts index 588e60cd1c..8328f0a095 100644 --- a/src/languages/hive/hive.functions.ts +++ b/src/languages/hive/hive.functions.ts @@ -216,13 +216,4 @@ export const functions: string[] = [ 'CUME_DIST', 'PERCENT_RANK', 'NTILE', - - // Parameterized data types - // https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=82706456 - // Though in reality Hive only supports parameters for DECIMAL(), - // it doesn't hurt to allow others in here as well. - 'DECIMAL', - 'NUMERIC', - 'VARCHAR', - 'CHAR', ]; diff --git a/src/languages/mariadb/mariadb.formatter.ts b/src/languages/mariadb/mariadb.formatter.ts index a102f5ae2e..45ed2e8929 100644 --- a/src/languages/mariadb/mariadb.formatter.ts +++ b/src/languages/mariadb/mariadb.formatter.ts @@ -274,9 +274,8 @@ export const mariadb: DialectOptions = { reservedJoins, reservedPhrases, supportsXor: true, - reservedKeywords: - // Temporary, will be replaced by reservedDataTypes - [...new Set(keywords.concat(dataTypes))], + reservedKeywords: keywords, + reservedDataTypes: dataTypes, reservedFunctionNames: functions, // TODO: support _ char set prefixes such as _utf8, _latin1, _binary, _utf8mb4, etc. stringTypes: [ diff --git a/src/languages/mariadb/mariadb.functions.ts b/src/languages/mariadb/mariadb.functions.ts index e4c6092a34..563883932e 100644 --- a/src/languages/mariadb/mariadb.functions.ts +++ b/src/languages/mariadb/mariadb.functions.ts @@ -236,36 +236,4 @@ export const functions: string[] = [ // CASE expression shorthands 'COALESCE', 'NULLIF', - // Data types with parameters - // https://mariadb.com/kb/en/data-types/ - 'TINYINT', - 'SMALLINT', - 'MEDIUMINT', - 'INT', - 'INTEGER', - 'BIGINT', - 'DECIMAL', - 'DEC', - 'NUMERIC', - 'FIXED', - // 'NUMBER', // ?? In oracle mode only - 'FLOAT', - 'DOUBLE', - 'DOUBLE PRECISION', - 'REAL', - 'BIT', - 'BINARY', - 'BLOB', - 'CHAR', - 'NATIONAL CHAR', - 'CHAR BYTE', - 'ENUM', - 'VARBINARY', - 'VARCHAR', - 'NATIONAL VARCHAR', - // 'SET' // handled as special-case in postProcess - 'TIME', - 'DATETIME', - 'TIMESTAMP', - 'YEAR', ]; diff --git a/src/languages/mariadb/mariadb.keywords.ts b/src/languages/mariadb/mariadb.keywords.ts index d9fea8da11..0e805a65cf 100644 --- a/src/languages/mariadb/mariadb.keywords.ts +++ b/src/languages/mariadb/mariadb.keywords.ts @@ -221,12 +221,18 @@ export const dataTypes: string[] = [ // https://mariadb.com/kb/en/data-types/ 'BIGINT', 'BINARY', + 'BIT', 'BLOB', + 'CHAR BYTE', 'CHAR', 'CHARACTER', + 'DATETIME', 'DEC', 'DECIMAL', + 'DOUBLE PRECISION', 'DOUBLE', + 'ENUM', + 'FIXED', 'FLOAT', 'FLOAT4', 'FLOAT8', @@ -244,10 +250,14 @@ export const dataTypes: string[] = [ 'MEDIUMINT', 'MEDIUMTEXT', 'MIDDLEINT', + 'NATIONAL CHAR', + 'NATIONAL VARCHAR', 'NUMERIC', 'PRECISION', 'REAL', 'SMALLINT', + 'TEXT', + 'TIMESTAMP', 'TINYBLOB', 'TINYINT', 'TINYTEXT', @@ -255,4 +265,7 @@ export const dataTypes: string[] = [ 'VARCHAR', 'VARCHARACTER', 'VARYING', + 'YEAR', + // 'NUMBER', // ?? In oracle mode only + // 'SET' // handled as special-case in postProcess ]; diff --git a/src/languages/mysql/mysql.formatter.ts b/src/languages/mysql/mysql.formatter.ts index 378b9c7b61..02e1e2be30 100644 --- a/src/languages/mysql/mysql.formatter.ts +++ b/src/languages/mysql/mysql.formatter.ts @@ -241,9 +241,8 @@ export const mysql: DialectOptions = { reservedJoins, reservedPhrases, supportsXor: true, - reservedKeywords: - // Temporary, will be replaced by reservedDataTypes - [...new Set(keywords.concat(dataTypes))], + reservedKeywords: keywords, + reservedDataTypes: dataTypes, reservedFunctionNames: functions, // TODO: support _ char set prefixes such as _utf8, _latin1, _binary, _utf8mb4, etc. stringTypes: [ diff --git a/src/languages/mysql/mysql.functions.ts b/src/languages/mysql/mysql.functions.ts index 6464569058..87593e8a42 100644 --- a/src/languages/mysql/mysql.functions.ts +++ b/src/languages/mysql/mysql.functions.ts @@ -421,35 +421,4 @@ export const functions: string[] = [ // 'XOR', 'YEAR', 'YEARWEEK', - // Data types with parameters - // https://dev.mysql.com/doc/refman/8.0/en/data-types.html - 'BIT', - 'TINYINT', - 'SMALLINT', - 'MEDIUMINT', - 'INT', - 'INTEGER', - 'BIGINT', - 'DECIMAL', - 'DEC', - 'NUMERIC', - 'FIXED', - 'FLOAT', - 'DOUBLE', - 'DOUBLE PRECISION', - 'REAL', - 'DATETIME', - 'TIMESTAMP', - 'TIME', - 'YEAR', - 'CHAR', - 'NATIONAL CHAR', - 'VARCHAR', - 'NATIONAL VARCHAR', - 'BINARY', - 'VARBINARY', - 'BLOB', - 'TEXT', - 'ENUM', - // 'SET' // handled as special-case in postProcess ]; diff --git a/src/languages/mysql/mysql.keywords.ts b/src/languages/mysql/mysql.keywords.ts index bbbf5be7f9..999112c1d7 100644 --- a/src/languages/mysql/mysql.keywords.ts +++ b/src/languages/mysql/mysql.keywords.ts @@ -234,12 +234,20 @@ export const dataTypes: string[] = [ // https://dev.mysql.com/doc/refman/8.0/en/data-types.html 'BIGINT', // (R) 'BINARY', // (R) + 'BIT', 'BLOB', // (R) + 'BOOL', // (R) + 'BOOLEAN', // (R) 'CHAR', // (R) 'CHARACTER', // (R) + 'DATE', // (R) + 'DATETIME', // (R) 'DEC', // (R) 'DECIMAL', // (R) + 'DOUBLE PRECISION', 'DOUBLE', // (R) + 'ENUM', + 'FIXED', 'FLOAT', // (R) 'FLOAT4', // (R) 'FLOAT8', // (R) @@ -256,10 +264,15 @@ export const dataTypes: string[] = [ 'MEDIUMINT', // (R) 'MEDIUMTEXT', // (R) 'MIDDLEINT', // (R) + 'NATIONAL CHAR', // (R) + 'NATIONAL VARCHAR', // (R) 'NUMERIC', // (R) 'PRECISION', // (R) 'REAL', // (R) 'SMALLINT', // (R) + 'TEXT', + 'TIME', + 'TIMESTAMP', // (R) 'TINYBLOB', // (R) 'TINYINT', // (R) 'TINYTEXT', // (R) @@ -267,4 +280,6 @@ export const dataTypes: string[] = [ 'VARCHAR', // (R) 'VARCHARACTER', // (R) 'VARYING', // (R) + 'YEAR', + // 'SET' // handled as special-case in postProcess ]; diff --git a/src/languages/n1ql/n1ql.formatter.ts b/src/languages/n1ql/n1ql.formatter.ts index 63a579c9cb..1a101f71ec 100644 --- a/src/languages/n1ql/n1ql.formatter.ts +++ b/src/languages/n1ql/n1ql.formatter.ts @@ -92,9 +92,8 @@ export const n1ql: DialectOptions = { reservedJoins, reservedPhrases, supportsXor: true, - reservedKeywords: - // Temporary, will be replaced by reservedDataTypes - [...new Set(keywords.concat(dataTypes))], + reservedKeywords: keywords, + reservedDataTypes: dataTypes, reservedFunctionNames: functions, // NOTE: single quotes are actually not supported in N1QL, // but we support them anyway as all other SQL dialects do, diff --git a/src/languages/n1ql/n1ql.keywords.ts b/src/languages/n1ql/n1ql.keywords.ts index 43f083ee44..b5e4a33ddd 100644 --- a/src/languages/n1ql/n1ql.keywords.ts +++ b/src/languages/n1ql/n1ql.keywords.ts @@ -6,11 +6,14 @@ export const keywords: string[] = [ 'ANALYZE', 'AND', 'ANY', + 'ARRAY', 'AS', 'ASC', 'AT', 'BEGIN', 'BETWEEN', + 'BINARY', + 'BOOLEAN', 'BREAK', 'BUCKET', 'BUILD', @@ -71,7 +74,6 @@ export const keywords: string[] = [ 'HASH', 'HAVING', 'IF', - 'ISOLATION', 'IGNORE', 'ILIKE', 'IN', @@ -85,6 +87,7 @@ export const keywords: string[] = [ 'INTERSECT', 'INTO', 'IS', + 'ISOLATION', 'JAVASCRIPT', 'JOIN', 'KEY', @@ -115,6 +118,8 @@ export const keywords: string[] = [ 'NTH_VALUE', 'NULL', 'NULLS', + 'NUMBER', + 'OBJECT', 'OFFSET', 'ON', 'OPTION', @@ -163,6 +168,7 @@ export const keywords: string[] = [ 'SOME', 'START', 'STATISTICS', + 'STRING', 'SYSTEM', 'THEN', 'TIES', @@ -201,11 +207,12 @@ export const keywords: string[] = [ ]; export const dataTypes: string[] = [ + // N1QL does not support any way of declaring types for columns. + // It does not support the CREATE TABLE statement nor the CAST() expression. + // + // It does have several keywords like ARRAY and OBJECT, which seem to refer to types, + // but they are used as operators. It also reserves several words like STRING and NUMBER, + // which it actually doesn't use. + // // https://docs.couchbase.com/server/current/n1ql/n1ql-language-reference/datatypes.html - 'ARRAY', - 'BINARY', - 'BOOLEAN', - 'NUMBER', - 'OBJECT', - 'STRING', ]; diff --git a/src/languages/plsql/plsql.formatter.ts b/src/languages/plsql/plsql.formatter.ts index 35880858bc..0bb6a82d4b 100644 --- a/src/languages/plsql/plsql.formatter.ts +++ b/src/languages/plsql/plsql.formatter.ts @@ -91,9 +91,8 @@ export const plsql: DialectOptions = { reservedJoins, reservedPhrases, supportsXor: true, - reservedKeywords: - // Temporary, will be replaced by reservedDataTypes - [...new Set(keywords.concat(dataTypes))], + reservedKeywords: keywords, + reservedDataTypes: dataTypes, reservedFunctionNames: functions, stringTypes: [ { quote: "''-qq", prefixes: ['N'] }, diff --git a/src/languages/plsql/plsql.functions.ts b/src/languages/plsql/plsql.functions.ts index d3858dabcf..cf545e6175 100644 --- a/src/languages/plsql/plsql.functions.ts +++ b/src/languages/plsql/plsql.functions.ts @@ -281,33 +281,4 @@ export const functions: string[] = [ 'PRESENTNNV', 'PRESENTV', 'PREVIOUS', - - // Parameterized data types - // https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Data-Types.html - // Oracle builtin data types - 'VARCHAR2', - 'NVARCHAR2', - 'NUMBER', - 'FLOAT', - 'TIMESTAMP', - 'INTERVAL YEAR', - 'INTERVAL DAY', - 'RAW', - 'UROWID', - 'NCHAR', - // ANSI Data Types - 'CHARACTER', - 'CHAR', - 'CHARACTER VARYING', - 'CHAR VARYING', - 'NATIONAL CHARACTER', - 'NATIONAL CHAR', - 'NATIONAL CHARACTER VARYING', - 'NATIONAL CHAR VARYING', - 'NCHAR VARYING', - 'NUMERIC', - 'DECIMAL', - 'FLOAT', - // SQL/DS and DB2 Data Types - 'VARCHAR', ]; diff --git a/src/languages/plsql/plsql.keywords.ts b/src/languages/plsql/plsql.keywords.ts index c4d92afb7b..1f61e3e731 100644 --- a/src/languages/plsql/plsql.keywords.ts +++ b/src/languages/plsql/plsql.keywords.ts @@ -311,22 +311,36 @@ export const dataTypes: string[] = [ 'BFILE_BASE', 'BINARY', 'BLOB_BASE', - 'CHAR', + 'CHAR VARYING', 'CHAR_BASE', + 'CHAR', + 'CHARACTER VARYING', 'CHARACTER', 'CLOB_BASE', - 'DATE', 'DATE_BASE', + 'DATE', 'DECIMAL', 'DOUBLE', 'FLOAT', 'INT', + 'INTERVAL DAY', + 'INTERVAL YEAR', 'LONG', + 'NATIONAL CHAR VARYING', + 'NATIONAL CHAR', + 'NATIONAL CHARACTER VARYING', + 'NATIONAL CHARACTER', + 'NCHAR VARYING', + 'NCHAR', 'NCHAR', 'NUMBER_BASE', + 'NUMBER', + 'NUMBERIC', + 'NVARCHAR', 'PRECISION', 'RAW', 'TIMESTAMP', + 'UROWID', 'VARCHAR', - 'VARYING', + 'VARCHAR2', ]; diff --git a/src/languages/postgresql/postgresql.formatter.ts b/src/languages/postgresql/postgresql.formatter.ts index 13e3453514..2073a57fb1 100644 --- a/src/languages/postgresql/postgresql.formatter.ts +++ b/src/languages/postgresql/postgresql.formatter.ts @@ -261,9 +261,8 @@ export const postgresql: DialectOptions = { reservedSetOperations, reservedJoins, reservedPhrases, - reservedKeywords: - // Temporary, will be replaced by reservedDataTypes - [...new Set(keywords.concat(dataTypes))], + reservedKeywords: keywords, + reservedDataTypes: dataTypes, reservedFunctionNames: functions, nestedBlockComments: true, extraParens: ['[]'], diff --git a/src/languages/postgresql/postgresql.functions.ts b/src/languages/postgresql/postgresql.functions.ts index b3f256a94e..8d9abc738e 100644 --- a/src/languages/postgresql/postgresql.functions.ts +++ b/src/languages/postgresql/postgresql.functions.ts @@ -708,18 +708,4 @@ export const functions: string[] = [ // cast 'CAST', - - // Parameterized data types - // https://www.postgresql.org/docs/current/datatype.html - 'BIT', - 'BIT VARYING', - 'CHARACTER', - 'CHARACTER VARYING', - 'VARCHAR', - 'CHAR', - 'DECIMAL', - 'NUMERIC', - 'TIME', - 'TIMESTAMP', - 'ENUM', ]; diff --git a/src/languages/postgresql/postgresql.keywords.ts b/src/languages/postgresql/postgresql.keywords.ts index b15a3c3de1..d00cfae0e0 100644 --- a/src/languages/postgresql/postgresql.keywords.ts +++ b/src/languages/postgresql/postgresql.keywords.ts @@ -441,12 +441,15 @@ export const dataTypes: string[] = [ 'ARRAY', // reserved, requires AS 'BIGINT', // (cannot be function or type) 'BIT', // (cannot be function or type) + 'BIT VARYING', 'BOOL', // (cannot be function or type) 'BOOLEAN', // (cannot be function or type) 'CHAR', // (cannot be function or type), requires AS 'CHARACTER', // (cannot be function or type), requires AS + 'CHARACTER VARYING', 'DECIMAL', // (cannot be function or type) 'DOUBLE', + 'ENUM', 'FLOAT', // (cannot be function or type) 'INT', // (cannot be function or type) 'INTEGER', // (cannot be function or type) @@ -460,7 +463,6 @@ export const dataTypes: string[] = [ 'TIMESTAMP', // (cannot be function or type) 'TIMESTAMPTZ', // (cannot be function or type) 'VARCHAR', // (cannot be function or type) - 'VARYING', // requires AS 'XML', 'ZONE', ]; diff --git a/src/languages/redshift/redshift.formatter.ts b/src/languages/redshift/redshift.formatter.ts index c00854c8c5..f9d803f3fb 100644 --- a/src/languages/redshift/redshift.formatter.ts +++ b/src/languages/redshift/redshift.formatter.ts @@ -148,9 +148,8 @@ export const redshift: DialectOptions = { reservedSetOperations, reservedJoins, reservedPhrases, - reservedKeywords: - // Temporary, will be replaced by reservedDataTypes - [...new Set(keywords.concat(dataTypes))], + reservedKeywords: keywords, + reservedDataTypes: dataTypes, reservedFunctionNames: functions, stringTypes: ["''-qq"], identTypes: [`""-qq`], diff --git a/src/languages/redshift/redshift.functions.ts b/src/languages/redshift/redshift.functions.ts index 5c65e1fb7b..e52ad737e7 100644 --- a/src/languages/redshift/redshift.functions.ts +++ b/src/languages/redshift/redshift.functions.ts @@ -360,15 +360,4 @@ export const functions: string[] = [ 'SLICE_NUM', 'USER', 'VERSION', - - // dataTypes - 'DECIMAL', - 'NUMERIC', - 'CHAR', - 'CHARACTER', - 'VARCHAR', - 'CHARACTER VARYING', - 'NCHAR', - 'NVARCHAR', - 'VARBYTE', ]; diff --git a/src/languages/redshift/redshift.keywords.ts b/src/languages/redshift/redshift.keywords.ts index dbf1afe492..46b73e02c1 100644 --- a/src/languages/redshift/redshift.keywords.ts +++ b/src/languages/redshift/redshift.keywords.ts @@ -201,6 +201,22 @@ export const keywords: string[] = [ export const dataTypes: string[] = [ // https://docs.aws.amazon.com/redshift/latest/dg/r_Character_types.html#r_Character_types-text-and-bpchar-types 'ARRAY', + 'BIGINT', 'BPCHAR', + 'CHAR', + 'CHARACTER VARYING', + 'CHARACTER', + 'DECIMAL', + 'INT', + 'INT2', + 'INT4', + 'INT8', + 'INTEGER', + 'NCHAR', + 'NUMERIC', + 'NVARCHAR', + 'SMALLINT', 'TEXT', + 'VARBYTE', + 'VARCHAR', ]; diff --git a/src/languages/singlestoredb/singlestoredb.formatter.ts b/src/languages/singlestoredb/singlestoredb.formatter.ts index 9a9b3360a6..236d845b39 100644 --- a/src/languages/singlestoredb/singlestoredb.formatter.ts +++ b/src/languages/singlestoredb/singlestoredb.formatter.ts @@ -241,9 +241,8 @@ export const singlestoredb: DialectOptions = { reservedSetOperations, reservedJoins, reservedPhrases, - reservedKeywords: - // Temporary, will be replaced by reservedDataTypes - [...new Set(keywords.concat(dataTypes))], + reservedKeywords: keywords, + reservedDataTypes: dataTypes, reservedFunctionNames: functions, // TODO: support _binary"some string" prefix stringTypes: [ diff --git a/src/languages/singlestoredb/singlestoredb.functions.ts b/src/languages/singlestoredb/singlestoredb.functions.ts index 1ecd4e167b..5f30b55007 100644 --- a/src/languages/singlestoredb/singlestoredb.functions.ts +++ b/src/languages/singlestoredb/singlestoredb.functions.ts @@ -278,34 +278,4 @@ export const functions: string[] = [ 'WEEKDAY', 'WEEKOFYEAR', 'YEAR', - // Data types with parameters - // https://docs.singlestore.com/managed-service/en/reference/sql-reference/data-types.html - 'BIT', - 'TINYINT', - 'SMALLINT', - 'MEDIUMINT', - 'INT', - 'INTEGER', - 'BIGINT', - 'DECIMAL', - 'DEC', - 'NUMERIC', - 'FIXED', - 'FLOAT', - 'DOUBLE', - 'DOUBLE PRECISION', - 'REAL', - 'DATETIME', - 'TIMESTAMP', - 'TIME', - 'YEAR', - 'CHAR', - 'NATIONAL CHAR', - 'VARCHAR', - 'NATIONAL VARCHAR', - 'BINARY', - 'VARBINARY', - 'BLOB', - 'TEXT', - 'ENUM', ]; diff --git a/src/languages/singlestoredb/singlestoredb.keywords.ts b/src/languages/singlestoredb/singlestoredb.keywords.ts index 2a384c9ab3..a9f59ada92 100644 --- a/src/languages/singlestoredb/singlestoredb.keywords.ts +++ b/src/languages/singlestoredb/singlestoredb.keywords.ts @@ -217,12 +217,17 @@ export const dataTypes: string[] = [ // https://docs.singlestore.com/cloud/reference/sql-reference/data-types/ 'BIGINT', 'BINARY', + 'BIT', 'BLOB', 'CHAR', 'CHARACTER', + 'DATETIME', 'DEC', 'DECIMAL', + 'DOUBLE PRECISION', 'DOUBLE', + 'ENUM', + 'FIXED', 'FLOAT', 'FLOAT4', 'FLOAT8', @@ -240,10 +245,15 @@ export const dataTypes: string[] = [ 'MEDIUMINT', 'MEDIUMTEXT', 'MIDDLEINT', + 'NATIONAL CHAR', + 'NATIONAL VARCHAR', 'NUMERIC', 'PRECISION', 'REAL', 'SMALLINT', + 'TEXT', + 'TIME', + 'TIMESTAMP', 'TINYBLOB', 'TINYINT', 'TINYTEXT', @@ -251,5 +261,5 @@ export const dataTypes: string[] = [ 'VARBINARY', 'VARCHAR', 'VARCHARACTER', - 'VARYING', + 'YEAR', ]; diff --git a/src/languages/snowflake/snowflake.formatter.ts b/src/languages/snowflake/snowflake.formatter.ts index c91658a080..15c2e1b73c 100644 --- a/src/languages/snowflake/snowflake.formatter.ts +++ b/src/languages/snowflake/snowflake.formatter.ts @@ -304,9 +304,8 @@ export const snowflake: DialectOptions = { reservedSetOperations, reservedJoins, reservedPhrases, - reservedKeywords: - // Temporary, will be replaced by reservedDataTypes - [...new Set(keywords.concat(dataTypes))], + reservedKeywords: keywords, + reservedDataTypes: dataTypes, reservedFunctionNames: functions, stringTypes: ['$$', `''-qq-bs`], identTypes: ['""-qq'], diff --git a/src/languages/spark/spark.formatter.ts b/src/languages/spark/spark.formatter.ts index 198a4780a5..3a856fb813 100644 --- a/src/languages/spark/spark.formatter.ts +++ b/src/languages/spark/spark.formatter.ts @@ -128,9 +128,8 @@ export const spark: DialectOptions = { reservedJoins, reservedPhrases, supportsXor: true, - reservedKeywords: - // Temporary, will be replaced by reservedDataTypes - [...new Set(keywords.concat(dataTypes))], + reservedKeywords: keywords, + reservedDataTypes: dataTypes, reservedFunctionNames: functions, extraParens: ['[]'], stringTypes: [ diff --git a/src/languages/spark/spark.functions.ts b/src/languages/spark/spark.functions.ts index 78e6dbb78f..7d7a6bedf1 100644 --- a/src/languages/spark/spark.functions.ts +++ b/src/languages/spark/spark.functions.ts @@ -83,7 +83,6 @@ export const functions: string[] = [ // http://spark.apache.org/docs/latest/sql-ref-functions-builtin.html#map-functions 'ELEMENT_AT', 'ELEMENT_AT', - 'MAP', 'MAP_CONCAT', 'MAP_ENTRIES', 'MAP_FROM_ARRAYS', @@ -169,20 +168,16 @@ export const functions: string[] = [ 'ATAN2', 'ATANH', 'BASE64', - 'BIGINT', 'BIN', - 'BINARY', 'BIT_COUNT', 'BIT_GET', 'BIT_LENGTH', - 'BOOLEAN', 'BROUND', 'BTRIM', 'CARDINALITY', 'CBRT', 'CEIL', 'CEILING', - 'CHAR', 'CHAR_LENGTH', 'CHARACTER_LENGTH', 'CHR', @@ -196,17 +191,13 @@ export const functions: string[] = [ 'CURRENT_CATALOG', 'CURRENT_DATABASE', 'CURRENT_USER', - 'DATE', - 'DECIMAL', 'DEGREES', - 'DOUBLE', // 'E', 'ELT', 'EXP', 'EXPM1', 'FACTORIAL', 'FIND_IN_SET', - 'FLOAT', 'FLOOR', 'FORALL', 'FORMAT_NUMBER', @@ -223,7 +214,6 @@ export const functions: string[] = [ 'INPUT_FILE_BLOCK_START', 'INPUT_FILE_NAME', 'INSTR', - 'INT', 'ISNAN', 'ISNOTNULL', 'ISNULL', @@ -293,7 +283,6 @@ export const functions: string[] = [ 'SIGNUM', 'SIN', 'SINH', - 'SMALLINT', 'SOUNDEX', 'SPACE', 'SPARK_PARTITION_ID', @@ -305,8 +294,6 @@ export const functions: string[] = [ 'SUBSTRING_INDEX', 'TAN', 'TANH', - 'TIMESTAMP', - 'TINYINT', 'TO_CSV', 'TRANSFORM_KEYS', 'TRANSFORM_VALUES', @@ -340,12 +327,4 @@ export const functions: string[] = [ // Shorthand functions to use in place of CASE expression 'COALESCE', 'NULLIF', - - // Parameterized data types - // https://spark.apache.org/docs/latest/sql-ref-datatypes.html - 'DECIMAL', - 'DEC', - 'NUMERIC', - // No varchar type in Spark, only STRING. Added for the sake of tests - 'VARCHAR', ]; diff --git a/src/languages/spark/spark.keywords.ts b/src/languages/spark/spark.keywords.ts index 1d143f1044..61a9a387ca 100644 --- a/src/languages/spark/spark.keywords.ts +++ b/src/languages/spark/spark.keywords.ts @@ -269,29 +269,31 @@ export const keywords: string[] = [ export const dataTypes: string[] = [ // https://spark.apache.org/docs/latest/sql-ref-datatypes.html + 'ARRAY', + 'BIGINT', + 'BINARY', 'BOOLEAN', 'BYTE', - 'TINYINT', - 'SHORT', - 'SMALLINT', + 'CHAR', + 'DATE', + 'DEC', + 'DECIMAL', + 'DOUBLE', + 'FLOAT', 'INT', 'INTEGER', + 'INTERVAL', 'LONG', - 'BIGINT', - 'FLOAT', + 'MAP', + 'NUMERIC', 'REAL', - 'DOUBLE', - 'DATE', - 'TIMESTAMP', - 'TIMESTAMP_LTZ', - 'TIMESTAMP_NTZ', + 'SHORT', + 'SMALLINT', 'STRING', - 'BINARY', - 'DECIMAL', - 'DEC', - 'NUMERIC', - 'INTERVAL', - 'ARRAY', 'STRUCT', - 'MAP', + 'TIMESTAMP_LTZ', + 'TIMESTAMP_NTZ', + 'TIMESTAMP', + 'TINYINT', + 'VARCHAR', // No varchar type in Spark, only STRING. Added for the sake of tests ]; diff --git a/src/languages/sql/sql.formatter.ts b/src/languages/sql/sql.formatter.ts index 332b3de240..f42506f84b 100644 --- a/src/languages/sql/sql.formatter.ts +++ b/src/languages/sql/sql.formatter.ts @@ -82,9 +82,8 @@ export const sql: DialectOptions = { reservedSetOperations, reservedJoins, reservedPhrases, - reservedKeywords: - // Temporary, will be replaced by reservedDataTypes - [...new Set(keywords.concat(dataTypes))], + reservedKeywords: keywords, + reservedDataTypes: dataTypes, reservedFunctionNames: functions, stringTypes: [ { quote: "''-qq-bs", prefixes: ['N', 'U&'] }, diff --git a/src/languages/sql/sql.functions.ts b/src/languages/sql/sql.functions.ts index d608f6e5f3..feb03caf9b 100644 --- a/src/languages/sql/sql.functions.ts +++ b/src/languages/sql/sql.functions.ts @@ -104,34 +104,4 @@ export const functions: string[] = [ 'ASIN', 'ACOS', 'ATAN', - - // Data types with parameters like VARCHAR(100) - // https://jakewheat.github.io/sql-overview/sql-2008-foundation-grammar.html#predefined-type - 'CHARACTER', - 'CHAR', - 'CHARACTER VARYING', - 'CHAR VARYING', - 'VARCHAR', - 'CHARACTER LARGE OBJECT', - 'CHAR LARGE OBJECT', - 'CLOB', - 'NATIONAL CHARACTER', - 'NATIONAL CHAR', - 'NCHAR', - 'NATIONAL CHARACTER VARYING', - 'NATIONAL CHAR VARYING', - 'NCHAR VARYING', - 'NATIONAL CHARACTER LARGE OBJECT', - 'NCHAR LARGE OBJECT', - 'NCLOB', - 'BINARY', - 'BINARY VARYING', - 'VARBINARY', - 'BINARY LARGE OBJECT', - 'BLOB', - 'NUMERIC', - 'DECIMAL', - 'DEC', - 'TIME', - 'TIMESTAMP', ]; diff --git a/src/languages/sql/sql.keywords.ts b/src/languages/sql/sql.keywords.ts index 79a1f474a6..3dfe858331 100644 --- a/src/languages/sql/sql.keywords.ts +++ b/src/languages/sql/sql.keywords.ts @@ -200,10 +200,16 @@ export const dataTypes: string[] = [ // https://jakewheat.github.io/sql-overview/sql-2008-foundation-grammar.html#_6_1_data_type 'ARRAY', 'BIGINT', + 'BINARY LARGE OBJECT', + 'BINARY VARYING', 'BINARY', 'BLOB', 'BOOLEAN', + 'CHAR LARGE OBJECT', + 'CHAR VARYING', 'CHAR', + 'CHARACTER LARGE OBJECT', + 'CHARACTER VARYING', 'CHARACTER', 'CLOB', 'DATE', @@ -215,7 +221,13 @@ export const dataTypes: string[] = [ 'INTEGER', 'INTERVAL', 'MULTISET', - 'NATIONAL', + 'NATIONAL CHAR VARYING', + 'NATIONAL CHAR', + 'NATIONAL CHARACTER LARGE OBJECT', + 'NATIONAL CHARACTER VARYING', + 'NATIONAL CHARACTER', + 'NCHAR LARGE OBJECT', + 'NCHAR VARYING', 'NCHAR', 'NCLOB', 'NUMERIC', @@ -224,5 +236,4 @@ export const dataTypes: string[] = [ 'TIMESTAMP', 'VARBINARY', 'VARCHAR', - 'VARYING', ]; diff --git a/src/languages/sqlite/sqlite.formatter.ts b/src/languages/sqlite/sqlite.formatter.ts index 91c475ff7d..a66d38f25e 100644 --- a/src/languages/sqlite/sqlite.formatter.ts +++ b/src/languages/sqlite/sqlite.formatter.ts @@ -73,9 +73,8 @@ export const sqlite: DialectOptions = { reservedSetOperations, reservedJoins, reservedPhrases, - reservedKeywords: - // Temporary, will be replaced by reservedDataTypes - [...new Set(keywords.concat(dataTypes))], + reservedKeywords: keywords, + reservedDataTypes: dataTypes, reservedFunctionNames: functions, stringTypes: [ "''-qq", diff --git a/src/languages/sqlite/sqlite.functions.ts b/src/languages/sqlite/sqlite.functions.ts index 782ea8e2d5..7878802d10 100644 --- a/src/languages/sqlite/sqlite.functions.ts +++ b/src/languages/sqlite/sqlite.functions.ts @@ -128,16 +128,4 @@ export const functions: string[] = [ // cast 'CAST', - - // SQLite allows parameters for all data types - // Well, in fact it allows any word as a data type, e.g. CREATE TABLE foo (col1 madeupname(123)); - // https://www.sqlite.org/datatype3.html - 'CHARACTER', - 'VARCHAR', - 'VARYING CHARACTER', - 'NCHAR', - 'NATIVE CHARACTER', - 'NVARCHAR', - 'NUMERIC', - 'DECIMAL', ]; diff --git a/src/languages/sqlite/sqlite.keywords.ts b/src/languages/sqlite/sqlite.keywords.ts index f82376b0ab..90f84a618a 100644 --- a/src/languages/sqlite/sqlite.keywords.ts +++ b/src/languages/sqlite/sqlite.keywords.ts @@ -153,13 +153,24 @@ export const keywords: string[] = [ ]; export const dataTypes: string[] = [ + // SQLite allows any word as a data type, e.g. CREATE TABLE foo (col1 madeupname(123)); + // Here we just list some common ones as SQL Formatter + // is only able to detect a predefined list of data types. // https://www.sqlite.org/stricttables.html // https://www.sqlite.org/datatype3.html 'ANY', 'ARRAY', 'BLOB', + 'CHARACTER', + 'DECIMAL', 'INT', 'INTEGER', + 'NATIVE CHARACTER', + 'NCHAR', + 'NUMERIC', + 'NVARCHAR', 'REAL', 'TEXT', + 'VARCHAR', + 'VARYING CHARACTER', ]; diff --git a/src/languages/transactsql/transactsql.formatter.ts b/src/languages/transactsql/transactsql.formatter.ts index 578ad2e9e2..9ff5585052 100644 --- a/src/languages/transactsql/transactsql.formatter.ts +++ b/src/languages/transactsql/transactsql.formatter.ts @@ -230,9 +230,8 @@ export const transactsql: DialectOptions = { reservedSetOperations, reservedJoins, reservedPhrases, - reservedKeywords: - // Temporary, will be replaced by reservedDataTypes - [...new Set(keywords.concat(dataTypes))], + reservedKeywords: keywords, + reservedDataTypes: dataTypes, reservedFunctionNames: functions, nestedBlockComments: true, stringTypes: [{ quote: "''-qq", prefixes: ['N'] }], diff --git a/src/languages/transactsql/transactsql.functions.ts b/src/languages/transactsql/transactsql.functions.ts index ae055632b2..326a2cb995 100644 --- a/src/languages/transactsql/transactsql.functions.ts +++ b/src/languages/transactsql/transactsql.functions.ts @@ -321,21 +321,4 @@ export const functions: string[] = [ // Shorthand functions to use in place of CASE expression 'COALESCE', 'NULLIF', - - // Parameterized types - // https://docs.microsoft.com/en-us/sql/t-sql/data-types/data-types-transact-sql?view=sql-server-ver15 - - 'DECIMAL', - 'NUMERIC', - 'FLOAT', - 'REAL', - 'DATETIME2', - 'DATETIMEOFFSET', - 'TIME', - 'CHAR', - 'VARCHAR', - 'NCHAR', - 'NVARCHAR', - 'BINARY', - 'VARBINARY', ]; diff --git a/src/languages/transactsql/transactsql.keywords.ts b/src/languages/transactsql/transactsql.keywords.ts index 65516829c2..9bedf6cc0e 100644 --- a/src/languages/transactsql/transactsql.keywords.ts +++ b/src/languages/transactsql/transactsql.keywords.ts @@ -293,10 +293,14 @@ export const keywords: string[] = [ export const dataTypes: string[] = [ // https://learn.microsoft.com/en-us/sql/t-sql/data-types/data-types-transact-sql?view=sql-server-ver15 + 'BINARY', 'BIT', 'CHAR', + 'CHAR', 'CHARACTER', 'DATE', + 'DATETIME2', + 'DATETIMEOFFSET', 'DEC', 'DECIMAL', 'DOUBLE', @@ -306,11 +310,12 @@ export const dataTypes: string[] = [ 'NATIONAL', 'NCHAR', 'NUMERIC', + 'NVARCHAR', 'PRECISION', 'REAL', 'SMALLINT', 'TIME', 'TIMESTAMP', + 'VARBINARY', 'VARCHAR', - 'VARYING', ]; diff --git a/src/languages/trino/trino.formatter.ts b/src/languages/trino/trino.formatter.ts index 1a4f8897ec..e8240d4a9e 100644 --- a/src/languages/trino/trino.formatter.ts +++ b/src/languages/trino/trino.formatter.ts @@ -133,9 +133,8 @@ export const trino: DialectOptions = { reservedSetOperations, reservedJoins, reservedPhrases, - reservedKeywords: - // Temporary, will be replaced by reservedDataTypes - [...new Set(keywords.concat(dataTypes))], + reservedKeywords: keywords, + reservedDataTypes: dataTypes, reservedFunctionNames: functions, // Trino also supports {- ... -} parenthesis. // The formatting of these currently works out as a result of { and - diff --git a/src/lexer/Tokenizer.ts b/src/lexer/Tokenizer.ts index 2df5a6335a..438455e627 100644 --- a/src/lexer/Tokenizer.ts +++ b/src/lexer/Tokenizer.ts @@ -130,6 +130,11 @@ export default class Tokenizer { regex: regex.reservedWord(cfg.reservedFunctionNames, cfg.identChars), text: toCanonical, }, + { + type: TokenType.RESERVED_DATA_TYPE, + regex: regex.reservedWord(cfg.reservedDataTypes ?? [], cfg.identChars), + text: toCanonical, + }, { type: TokenType.RESERVED_KEYWORD, regex: regex.reservedWord(cfg.reservedKeywords, cfg.identChars), diff --git a/src/lexer/TokenizerOptions.ts b/src/lexer/TokenizerOptions.ts index beca4575e5..24fb8ff66e 100644 --- a/src/lexer/TokenizerOptions.ts +++ b/src/lexer/TokenizerOptions.ts @@ -69,6 +69,8 @@ export interface TokenizerOptions { reservedPhrases?: string[]; // built in function names reservedFunctionNames: string[]; + // data types + reservedDataTypes?: string[]; // all other reserved words (not included to any of the above lists) reservedKeywords: string[]; // Types of quotes to use for strings diff --git a/src/lexer/disambiguateTokens.ts b/src/lexer/disambiguateTokens.ts index 4527f6192f..0514fdf005 100644 --- a/src/lexer/disambiguateTokens.ts +++ b/src/lexer/disambiguateTokens.ts @@ -6,7 +6,9 @@ import { isReserved, Token, TokenType } from './token.js'; * Ensures that all RESERVED_FUNCTION_NAME tokens are followed by "(". * If they're not, converts the token to RESERVED_KEYWORD. * - * When IDENTIFIER and RESERVED_KEYWORD token is followed by "[" + * Converts RESERVED_DATA_TYPE tokens followed by "(" to RESERVED_PARAMETERIZED_DATA_TYPE. + * + * When IDENTIFIER or RESERVED_DATA_TYPE token is followed by "[" * converts it to ARRAY_IDENTIFIER or ARRAY_KEYWORD accordingly. * * This is needed to avoid ambiguity in parser which expects function names @@ -17,8 +19,9 @@ export function disambiguateTokens(tokens: Token[]): Token[] { return tokens .map(dotKeywordToIdent) .map(funcNameToKeyword) + .map(dataTypeToParameterizedDataType) .map(identToArrayIdent) - .map(keywordToArrayKeyword); + .map(dataTypeToArrayKeyword); } const dotKeywordToIdent = (token: Token, i: number, tokens: Token[]): Token => { @@ -41,6 +44,16 @@ const funcNameToKeyword = (token: Token, i: number, tokens: Token[]): Token => { return token; }; +const dataTypeToParameterizedDataType = (token: Token, i: number, tokens: Token[]): Token => { + if (token.type === TokenType.RESERVED_DATA_TYPE) { + const nextToken = nextNonCommentToken(tokens, i); + if (nextToken && isOpenParen(nextToken)) { + return { ...token, type: TokenType.RESERVED_PARAMETERIZED_DATA_TYPE }; + } + } + return token; +}; + const identToArrayIdent = (token: Token, i: number, tokens: Token[]): Token => { if (token.type === TokenType.IDENTIFIER) { const nextToken = nextNonCommentToken(tokens, i); @@ -51,8 +64,8 @@ const identToArrayIdent = (token: Token, i: number, tokens: Token[]): Token => { return token; }; -const keywordToArrayKeyword = (token: Token, i: number, tokens: Token[]): Token => { - if (token.type === TokenType.RESERVED_KEYWORD) { +const dataTypeToArrayKeyword = (token: Token, i: number, tokens: Token[]): Token => { + if (token.type === TokenType.RESERVED_DATA_TYPE) { const nextToken = nextNonCommentToken(tokens, i); if (nextToken && isOpenBracket(nextToken)) { return { ...token, type: TokenType.ARRAY_KEYWORD }; diff --git a/src/lexer/token.ts b/src/lexer/token.ts index 4df232bbd8..152102073f 100644 --- a/src/lexer/token.ts +++ b/src/lexer/token.ts @@ -4,6 +4,8 @@ export enum TokenType { IDENTIFIER = 'IDENTIFIER', STRING = 'STRING', VARIABLE = 'VARIABLE', + RESERVED_DATA_TYPE = 'RESERVED_DATA_TYPE', + RESERVED_PARAMETERIZED_DATA_TYPE = 'RESERVED_PARAMETERIZED_DATA_TYPE', RESERVED_KEYWORD = 'RESERVED_KEYWORD', RESERVED_FUNCTION_NAME = 'RESERVED_FUNCTION_NAME', RESERVED_PHRASE = 'RESERVED_PHRASE', @@ -12,7 +14,7 @@ export enum TokenType { RESERVED_SELECT = 'RESERVED_SELECT', RESERVED_JOIN = 'RESERVED_JOIN', ARRAY_IDENTIFIER = 'ARRAY_IDENTIFIER', // IDENTIFIER token in front of [ - ARRAY_KEYWORD = 'ARRAY_KEYWORD', // RESERVED_KEYWORD token in front of [ + ARRAY_KEYWORD = 'ARRAY_KEYWORD', // RESERVED_DATA_TYPE token in front of [ CASE = 'CASE', END = 'END', WHEN = 'WHEN', @@ -73,16 +75,17 @@ export const testToken = /** Util object that allows for easy checking of Reserved Keywords */ export const isToken = { - ARRAY: testToken({ text: 'ARRAY', type: TokenType.RESERVED_KEYWORD }), + ARRAY: testToken({ text: 'ARRAY', type: TokenType.RESERVED_DATA_TYPE }), BY: testToken({ text: 'BY', type: TokenType.RESERVED_KEYWORD }), SET: testToken({ text: 'SET', type: TokenType.RESERVED_CLAUSE }), - STRUCT: testToken({ text: 'STRUCT', type: TokenType.RESERVED_KEYWORD }), + STRUCT: testToken({ text: 'STRUCT', type: TokenType.RESERVED_DATA_TYPE }), WINDOW: testToken({ text: 'WINDOW', type: TokenType.RESERVED_CLAUSE }), VALUES: testToken({ text: 'VALUES', type: TokenType.RESERVED_CLAUSE }), }; /** Checks if token is any Reserved Keyword or Clause */ export const isReserved = (type: TokenType): boolean => + type === TokenType.RESERVED_DATA_TYPE || type === TokenType.RESERVED_KEYWORD || type === TokenType.RESERVED_FUNCTION_NAME || type === TokenType.RESERVED_PHRASE || diff --git a/src/parser/ast.ts b/src/parser/ast.ts index 2a5a7e8424..0a944c0f8c 100644 --- a/src/parser/ast.ts +++ b/src/parser/ast.ts @@ -5,6 +5,7 @@ export enum NodeType { clause = 'clause', set_operation = 'set_operation', function_call = 'function_call', + parameterized_data_type = 'parameterized_data_type', array_subscript = 'array_subscript', property_access = 'property_access', parenthesis = 'parenthesis', @@ -17,6 +18,7 @@ export enum NodeType { literal = 'literal', identifier = 'identifier', keyword = 'keyword', + data_type = 'data_type', parameter = 'parameter', operator = 'operator', comma = 'comma', @@ -53,10 +55,16 @@ export interface FunctionCallNode extends BaseNode { parenthesis: ParenthesisNode; } +export interface ParameterizedDataTypeNode extends BaseNode { + type: NodeType.parameterized_data_type; + dataType: DataTypeNode; + parenthesis: ParenthesisNode; +} + // [] export interface ArraySubscriptNode extends BaseNode { type: NodeType.array_subscript; - array: IdentifierNode | KeywordNode; + array: IdentifierNode | KeywordNode | DataTypeNode; parenthesis: ParenthesisNode; } @@ -129,6 +137,12 @@ export interface IdentifierNode extends BaseNode { text: string; } +export interface DataTypeNode extends BaseNode { + type: NodeType.data_type; + text: string; + raw: string; +} + export interface KeywordNode extends BaseNode { type: NodeType.keyword; tokenType: TokenType; @@ -169,6 +183,7 @@ export type AstNode = | ClauseNode | SetOperationNode | FunctionCallNode + | ParameterizedDataTypeNode | ArraySubscriptNode | PropertyAccessNode | ParenthesisNode @@ -180,6 +195,7 @@ export type AstNode = | AllColumnsAsteriskNode | LiteralNode | IdentifierNode + | DataTypeNode | KeywordNode | ParameterNode | OperatorNode diff --git a/src/parser/grammar.ne b/src/parser/grammar.ne index f380c6bff7..d2d50f6abc 100644 --- a/src/parser/grammar.ne +++ b/src/parser/grammar.ne @@ -1,7 +1,7 @@ @preprocessor typescript @{% import LexerAdapter from './LexerAdapter.js'; -import { NodeType, AstNode, CommentNode, KeywordNode, IdentifierNode } from './ast.js'; +import { NodeType, AstNode, CommentNode, KeywordNode, IdentifierNode, DataTypeNode } from './ast.js'; import { Token, TokenType } from '../lexer/token.js'; // The lexer here is only to provide the has() method, @@ -23,6 +23,12 @@ const toKeywordNode = (token: Token): KeywordNode => ({ raw: token.raw, }); +const toDataTypeNode = (token: Token): DataTypeNode => ({ + type: NodeType.data_type, + text: token.text, + raw: token.raw, +}); + interface CommentAttachments { leading?: CommentNode[]; trailing?: CommentNode[]; @@ -197,6 +203,7 @@ atomic_expression -> | identifier | parameter | literal + | data_type | keyword ) {% unwrap %} array_subscript -> %ARRAY_IDENTIFIER _ square_brackets {% @@ -329,6 +336,18 @@ keyword -> ([[token]]) => toKeywordNode(token) %} +data_type -> + ( %RESERVED_DATA_TYPE ) {% + ([[token]]) => toDataTypeNode(token) +%} +data_type -> %RESERVED_PARAMETERIZED_DATA_TYPE _ parenthesis {% + ([nameToken, _, parens]) => ({ + type: NodeType.parameterized_data_type, + dataType: addComments(toDataTypeNode(nameToken), { trailing: _ }), + parenthesis: parens, + }) +%} + logic_operator -> ( %AND | %OR diff --git a/src/sqlFormatter.ts b/src/sqlFormatter.ts index 620038ce2f..50cb2cbed9 100644 --- a/src/sqlFormatter.ts +++ b/src/sqlFormatter.ts @@ -42,6 +42,8 @@ const defaultOptions: FormatOptions = { useTabs: false, keywordCase: 'preserve', identifierCase: 'preserve', + dataTypeCase: 'preserve', + functionCase: 'preserve', indentStyle: 'standard', logicalOperatorNewline: 'before', expressionWidth: 50, diff --git a/test/behavesLikeMariaDbFormatter.ts b/test/behavesLikeMariaDbFormatter.ts index 873a99caf4..0a2be821f0 100644 --- a/test/behavesLikeMariaDbFormatter.ts +++ b/test/behavesLikeMariaDbFormatter.ts @@ -163,7 +163,10 @@ export default function behavesLikeMariaDbFormatter(format: FormatFn) { `create table account (id int comment 'the most important column'); select * from mysql.user; insert into user (id, name) values (1, 'Blah');`, - { keywordCase: 'upper' } + { + keywordCase: 'upper', + dataTypeCase: 'upper', + } ) ).toBe(dedent` CREATE TABLE account (id INT comment 'the most important column'); diff --git a/test/behavesLikeSqlFormatter.ts b/test/behavesLikeSqlFormatter.ts index 25f990c6ea..bcd8da6537 100644 --- a/test/behavesLikeSqlFormatter.ts +++ b/test/behavesLikeSqlFormatter.ts @@ -16,6 +16,7 @@ import supportsNewlineBeforeSemicolon from './options/newlineBeforeSemicolon.js' import supportsLogicalOperatorNewline from './options/logicalOperatorNewline.js'; import supportsParamTypes from './options/paramTypes.js'; import supportsWindowFunctions from './features/windowFunctions.js'; +import supportsFunctionCase from './options/functionCase.js'; /** * Core tests for all SQL formatters @@ -29,6 +30,7 @@ export default function behavesLikeSqlFormatter(format: FormatFn) { supportsUseTabs(format); supportsKeywordCase(format); supportsIdentifierCase(format); + supportsFunctionCase(format); supportsIndentStyle(format); supportsLinesBetweenQueries(format); supportsExpressionWidth(format); diff --git a/test/bigquery.test.ts b/test/bigquery.test.ts index 03309e060c..d891c10831 100644 --- a/test/bigquery.test.ts +++ b/test/bigquery.test.ts @@ -24,6 +24,7 @@ import supportsMergeInto from './features/mergeInto.js'; import supportsCreateView from './features/createView.js'; import supportsAlterTable from './features/alterTable.js'; import supportsIsDistinctFrom from './features/isDistinctFrom.js'; +import supportsDataTypeCase from './options/dataTypeCase.js'; describe('BigQueryFormatter', () => { const language = 'bigquery'; @@ -55,11 +56,12 @@ describe('BigQueryFormatter', () => { 'EXCEPT DISTINCT', 'INTERSECT DISTINCT', ]); - supportsOperators(format, ['&', '|', '^', '~', '>>', '<<', '||', '=>']); + supportsOperators(format, ['&', '|', '^', '~', '>>', '<<', '||', '=>'], { any: true }); supportsIsDistinctFrom(format); supportsParams(format, { positional: true, named: ['@'], quoted: ['@``'] }); supportsWindow(format); supportsLimiting(format, { limit: true, offset: true }); + supportsDataTypeCase(format); // Note: BigQuery supports single dashes inside identifiers, so my-ident would be // detected as identifier, while other SQL dialects would detect it as @@ -131,7 +133,7 @@ describe('BigQueryFormatter', () => { const result = format('SELECT STRUCT("Alpha" as name, [23.4, 26.3, 26.4] as splits) FROM beta'); expect(result).toBe(dedent` SELECT - STRUCT ("Alpha" as name, [23.4, 26.3, 26.4] as splits) + STRUCT("Alpha" as name, [23.4, 26.3, 26.4] as splits) FROM beta `); @@ -144,6 +146,14 @@ describe('BigQueryFormatter', () => { `); }); + it('STRUCT and ARRAY type case is affected by dataTypeCase option', () => { + expect(format('SELECT array>[(1, "foo")]', { dataTypeCase: 'upper' })) + .toBe(dedent` + SELECT + ARRAY>[(1, "foo")] + `); + }); + // TODO: Possibly incorrect formatting of STRUCT<>() and ARRAY<>() it('supports parametric STRUCT', () => { expect(format('SELECT STRUCT>([])')).toBe(dedent` diff --git a/test/db2.test.ts b/test/db2.test.ts index 8dc642a3fc..e12732eb83 100644 --- a/test/db2.test.ts +++ b/test/db2.test.ts @@ -11,6 +11,7 @@ import supportsStrings from './features/strings.js'; import supportsComments from './features/comments.js'; import supportsOperators from './features/operators.js'; import supportsLimiting from './features/limiting.js'; +import supportsDataTypeCase from './options/dataTypeCase.js'; describe('Db2Formatter', () => { const language = 'db2'; @@ -28,27 +29,32 @@ describe('Db2Formatter', () => { }); supportsDropTable(format); supportsJoin(format, { without: ['NATURAL'] }); - supportsOperators(format, [ - '**', - '%', - '&', - '|', - '^', - '~', - '¬=', - '¬>', - '¬<', - '!>', - '!<', - '^=', - '^>', - '^<', - '||', - '->', - '=>', - ]); + supportsOperators( + format, + [ + '**', + '%', + '&', + '|', + '^', + '~', + '¬=', + '¬>', + '¬<', + '!>', + '!<', + '^=', + '^>', + '^<', + '||', + '->', + '=>', + ], + { any: true } + ); // Additional U& string type in addition to others shared by all DB2 implementations supportsStrings(format, ["U&''"]); + supportsDataTypeCase(format); it('supports non-standard FOR clause', () => { expect(format('SELECT * FROM tbl FOR UPDATE OF other_tbl FOR RS USE AND KEEP EXCLUSIVE LOCKS')) diff --git a/test/db2i.test.ts b/test/db2i.test.ts index 36b3c23885..e844a6ea8c 100644 --- a/test/db2i.test.ts +++ b/test/db2i.test.ts @@ -8,6 +8,7 @@ import supportsDropTable from './features/dropTable.js'; import supportsJoin from './features/join.js'; import supportsOperators from './features/operators.js'; import supportsLimiting from './features/limiting.js'; +import supportsDataTypeCase from './options/dataTypeCase.js'; describe('Db2iFormatter', () => { const language = 'db2i'; @@ -27,5 +28,6 @@ describe('Db2iFormatter', () => { without: ['NATURAL'], additionally: ['EXCEPTION JOIN', 'LEFT EXCEPTION JOIN', 'RIGHT EXCEPTION JOIN'], }); - supportsOperators(format, ['**', '¬=', '¬>', '¬<', '!>', '!<', '||', '=>']); + supportsOperators(format, ['**', '¬=', '¬>', '¬<', '!>', '!<', '||', '=>'], { any: true }); + supportsDataTypeCase(format); }); diff --git a/test/features/arrayLiterals.ts b/test/features/arrayLiterals.ts index beaa24e659..c62dfae25c 100644 --- a/test/features/arrayLiterals.ts +++ b/test/features/arrayLiterals.ts @@ -12,45 +12,77 @@ export default function supportsArrayLiterals(format: FormatFn, cfg: ArrayLitera it('supports ARRAY[] literals', () => { expect( format( - `SELECT ARRAY[1, 2, 3] FROM ARRAY['cammon', 'seriously', 'this', 'is', 'one', 'hello-of-a', 'damn', 'long', 'array'];` + `SELECT ARRAY[1, 2, 3] FROM ARRAY['come-on', 'seriously', 'this', 'is', 'a', 'very', 'very', 'long', 'array'];` ) ).toBe(dedent` SELECT ARRAY[1, 2, 3] FROM ARRAY[ - 'cammon', + 'come-on', 'seriously', 'this', 'is', - 'one', - 'hello-of-a', - 'damn', + 'a', + 'very', + 'very', 'long', 'array' ]; `); }); + + it('dataTypeCase option does NOT affect ARRAY[] literal case', () => { + expect( + format(`SELECT ArrAy[1, 2]`, { + dataTypeCase: 'upper', + }) + ).toBe(dedent` + SELECT + ArrAy[1, 2] + `); + }); + + it('keywordCase option affects ARRAY[] literal case', () => { + expect( + format(`SELECT ArrAy[1, 2]`, { + keywordCase: 'upper', + }) + ).toBe(dedent` + SELECT + ARRAY[1, 2] + `); + }); + + it('dataTypeCase option affects ARRAY type case', () => { + expect( + format(`CREATE TABLE foo ( items ArrAy )`, { + dataTypeCase: 'upper', + }) + ).toBe(dedent` + CREATE TABLE foo (items ARRAY) + `); + }); } if (cfg.withoutArrayPrefix) { it('supports array literals', () => { expect( format( - `SELECT [1, 2, 3] FROM ['cammon', 'seriously', 'this', 'is', 'one', 'hello-of-a', 'damn', 'long', 'array'];` + `SELECT [1, 2, 3] FROM ['come-on', 'seriously', 'this', 'is', 'a', 'very', 'very', 'long', 'array'];` ) ).toBe(dedent` SELECT [1, 2, 3] FROM [ - 'cammon', + 'come-on', 'seriously', 'this', 'is', - 'one', - 'hello-of-a', - 'damn', + 'a', + 'very', + 'very', 'long', 'array' ]; diff --git a/test/features/case.ts b/test/features/case.ts index defb1b4bcf..b44febc035 100644 --- a/test/features/case.ts +++ b/test/features/case.ts @@ -79,7 +79,10 @@ export default function supportsCase(format: FormatFn) { it('properly converts to uppercase in case statements', () => { const result = format( "case trim(sqrt(my_field)) when 'one' then 1 when 'two' then 2 when 'three' then 3 else 4 end;", - { keywordCase: 'upper' } + { + keywordCase: 'upper', + functionCase: 'upper', + } ); expect(result).toBe(dedent` CASE TRIM(SQRT(my_field)) diff --git a/test/features/createTable.ts b/test/features/createTable.ts index b7e8fa64e2..96cc0c0803 100644 --- a/test/features/createTable.ts +++ b/test/features/createTable.ts @@ -9,10 +9,7 @@ interface CreateTableConfig { tableComment?: boolean; } -export default function supportsCreateTable( - format: FormatFn, - { orReplace, ifNotExists, columnComment, tableComment }: CreateTableConfig = {} -) { +export default function supportsCreateTable(format: FormatFn, cfg: CreateTableConfig = {}) { it('formats short CREATE TABLE', () => { expect(format('CREATE TABLE tbl (a INT PRIMARY KEY, b TEXT);')).toBe(dedent` CREATE TABLE tbl (a INT PRIMARY KEY, b TEXT); @@ -34,7 +31,7 @@ export default function supportsCreateTable( `); }); - if (orReplace) { + if (cfg.orReplace) { it('formats short CREATE OR REPLACE TABLE', () => { expect(format('CREATE OR REPLACE TABLE tbl (a INT PRIMARY KEY, b TEXT);')).toBe(dedent` CREATE OR REPLACE TABLE tbl (a INT PRIMARY KEY, b TEXT); @@ -42,7 +39,7 @@ export default function supportsCreateTable( }); } - if (ifNotExists) { + if (cfg.ifNotExists) { it('formats short CREATE TABLE IF NOT EXISTS', () => { expect(format('CREATE TABLE IF NOT EXISTS tbl (a INT PRIMARY KEY, b TEXT);')).toBe(dedent` CREATE TABLE IF NOT EXISTS tbl (a INT PRIMARY KEY, b TEXT); @@ -50,7 +47,7 @@ export default function supportsCreateTable( }); } - if (columnComment) { + if (cfg.columnComment) { it('formats short CREATE TABLE with column comments', () => { expect( format(`CREATE TABLE tbl (a INT COMMENT 'Hello world!', b TEXT COMMENT 'Here we are!');`) @@ -63,7 +60,7 @@ export default function supportsCreateTable( }); } - if (tableComment) { + if (cfg.tableComment) { it('formats short CREATE TABLE with comment', () => { expect(format(`CREATE TABLE tbl (a INT, b TEXT) COMMENT = 'Hello, world!';`)).toBe(dedent` CREATE TABLE tbl (a INT, b TEXT) COMMENT = 'Hello, world!'; diff --git a/test/features/operators.ts b/test/features/operators.ts index 2bc8714d93..d7a6ffb83e 100644 --- a/test/features/operators.ts +++ b/test/features/operators.ts @@ -2,10 +2,15 @@ import dedent from 'dedent-js'; import { FormatFn } from '../../src/sqlFormatter.js'; +type OperatorsConfig = { + logicalOperators?: string[]; + any?: boolean; +}; + export default function supportsOperators( format: FormatFn, operators: string[], - logicalOperators: string[] = ['AND', 'OR'] + cfg: OperatorsConfig = {} ) { // Always test for standard SQL operators const standardOperators = ['+', '-', '*', '/', '>', '<', '=', '<>', '<=', '>=', '!=']; @@ -26,7 +31,7 @@ export default function supportsOperators( }); }); - logicalOperators.forEach(op => { + (cfg.logicalOperators || ['AND', 'OR']).forEach(op => { it(`supports ${op} operator`, () => { const result = format(`SELECT true ${op} false AS foo;`); expect(result).toBe(dedent` @@ -39,11 +44,16 @@ export default function supportsOperators( it('supports set operators', () => { expect(format('foo ALL bar')).toBe('foo ALL bar'); - expect(format('foo = ANY (1, 2, 3)')).toBe('foo = ANY (1, 2, 3)'); expect(format('EXISTS bar')).toBe('EXISTS bar'); expect(format('foo IN (1, 2, 3)')).toBe('foo IN (1, 2, 3)'); expect(format("foo LIKE 'hello%'")).toBe("foo LIKE 'hello%'"); expect(format('foo IS NULL')).toBe('foo IS NULL'); expect(format('UNIQUE foo')).toBe('UNIQUE foo'); }); + + if (cfg.any) { + it('supports ANY set-operator', () => { + expect(format('foo = ANY (1, 2, 3)')).toBe('foo = ANY (1, 2, 3)'); + }); + } } diff --git a/test/hive.test.ts b/test/hive.test.ts index 8327fe7321..0c8c7982bf 100644 --- a/test/hive.test.ts +++ b/test/hive.test.ts @@ -22,6 +22,7 @@ import supportsDeleteFrom from './features/deleteFrom.js'; import supportsTruncateTable from './features/truncateTable.js'; import supportsMergeInto from './features/mergeInto.js'; import supportsCreateView from './features/createView.js'; +import supportsDataTypeCase from './options/dataTypeCase.js'; describe('HiveFormatter', () => { const language = 'hive'; @@ -46,10 +47,11 @@ describe('HiveFormatter', () => { supportsUsing: false, }); supportsSetOperations(format, ['UNION', 'UNION ALL', 'UNION DISTINCT']); - supportsOperators(format, ['%', '~', '^', '|', '&', '<=>', '==', '!', '||']); + supportsOperators(format, ['%', '~', '^', '|', '&', '<=>', '==', '!', '||'], { any: true }); supportsArrayAndMapAccessors(format); supportsWindow(format); supportsLimiting(format, { limit: true }); + supportsDataTypeCase(format); // eslint-disable-next-line no-template-curly-in-string it('recognizes ${hivevar:name} substitution variables', () => { diff --git a/test/mariadb.test.ts b/test/mariadb.test.ts index d76dfdbcf2..3219e92ba2 100644 --- a/test/mariadb.test.ts +++ b/test/mariadb.test.ts @@ -14,6 +14,7 @@ import supportsCreateView from './features/createView.js'; import supportsAlterTable from './features/alterTable.js'; import supportsStrings from './features/strings.js'; import supportsConstraints from './features/constraints.js'; +import supportsDataTypeCase from './options/dataTypeCase.js'; describe('MariaDbFormatter', () => { const language = 'mariadb'; @@ -29,11 +30,10 @@ describe('MariaDbFormatter', () => { additionally: ['STRAIGHT_JOIN'], }); supportsSetOperations(format, [...standardSetOperations, 'MINUS', 'MINUS ALL', 'MINUS DISTINCT']); - supportsOperators( - format, - ['%', ':=', '&', '|', '^', '~', '<<', '>>', '<=>', '&&', '||', '!'], - ['AND', 'OR', 'XOR'] - ); + supportsOperators(format, ['%', ':=', '&', '|', '^', '~', '<<', '>>', '<=>', '&&', '||', '!'], { + logicalOperators: ['AND', 'OR', 'XOR'], + any: true, + }); supportsReturning(format); supportsLimiting(format, { limit: true, offset: true, fetchFirst: true, fetchNext: true }); supportsCreateTable(format, { @@ -52,6 +52,7 @@ describe('MariaDbFormatter', () => { renameTo: true, renameColumn: true, }); + supportsDataTypeCase(format); it(`supports @"name" variables`, () => { expect(format(`SELECT @"foo fo", @"foo\\"x", @"foo""y" FROM tbl;`)).toBe(dedent` diff --git a/test/mysql.test.ts b/test/mysql.test.ts index 405476f83c..893507553d 100644 --- a/test/mysql.test.ts +++ b/test/mysql.test.ts @@ -14,6 +14,7 @@ import supportsCreateView from './features/createView.js'; import supportsAlterTable from './features/alterTable.js'; import supportsStrings from './features/strings.js'; import supportsConstraints from './features/constraints.js'; +import supportsDataTypeCase from './options/dataTypeCase.js'; describe('MySqlFormatter', () => { const language = 'mysql'; @@ -32,7 +33,10 @@ describe('MySqlFormatter', () => { supportsOperators( format, ['%', ':=', '&', '|', '^', '~', '<<', '>>', '<=>', '->', '->>', '&&', '||', '!'], - ['AND', 'OR', 'XOR'] + { + logicalOperators: ['AND', 'OR', 'XOR'], + any: true, + } ); supportsWindow(format); supportsLimiting(format, { limit: true, offset: true }); @@ -54,6 +58,7 @@ describe('MySqlFormatter', () => { renameTo: true, renameColumn: true, }); + supportsDataTypeCase(format); it(`supports @"name" variables`, () => { expect(format(`SELECT @"foo fo", @"foo\\"x", @"foo""y" FROM tbl;`)).toBe(dedent` diff --git a/test/n1ql.test.ts b/test/n1ql.test.ts index 6427d8a2e9..eee6b800a6 100644 --- a/test/n1ql.test.ts +++ b/test/n1ql.test.ts @@ -33,7 +33,10 @@ describe('N1qlFormatter', () => { supportsIdentifiers(format, ['``']); supportsBetween(format); supportsSchema(format); - supportsOperators(format, ['%', '==', '||'], ['AND', 'OR', 'XOR']); + supportsOperators(format, ['%', '==', '||'], { + logicalOperators: ['AND', 'OR', 'XOR'], + any: true, + }); supportsArrayAndMapAccessors(format); supportsArrayLiterals(format, { withoutArrayPrefix: true }); supportsJoin(format, { without: ['FULL', 'CROSS', 'NATURAL'], supportsUsing: false }); diff --git a/test/options/dataTypeCase.ts b/test/options/dataTypeCase.ts new file mode 100644 index 0000000000..d576d30374 --- /dev/null +++ b/test/options/dataTypeCase.ts @@ -0,0 +1,47 @@ +import dedent from 'dedent-js'; + +import { FormatFn } from '../../src/sqlFormatter.js'; + +export default function supportsDataTypeCase(format: FormatFn) { + it('preserves data type keyword case by default', () => { + const result = format( + 'CREATE TABLE users ( user_id iNt PRIMARY KEY, total_earnings Decimal(5, 2) NOT NULL )' + ); + expect(result).toBe(dedent` + CREATE TABLE users ( + user_id iNt PRIMARY KEY, + total_earnings Decimal(5, 2) NOT NULL + ) + `); + }); + + it('converts data type keyword case to uppercase', () => { + const result = format( + 'CREATE TABLE users ( user_id iNt PRIMARY KEY, total_earnings Decimal(5, 2) NOT NULL )', + { + dataTypeCase: 'upper', + } + ); + expect(result).toBe(dedent` + CREATE TABLE users ( + user_id INT PRIMARY KEY, + total_earnings DECIMAL(5, 2) NOT NULL + ) + `); + }); + + it('converts data type keyword case to lowercase', () => { + const result = format( + 'CREATE TABLE users ( user_id iNt PRIMARY KEY, total_earnings Decimal(5, 2) NOT NULL )', + { + dataTypeCase: 'lower', + } + ); + expect(result).toBe(dedent` + CREATE TABLE users ( + user_id int PRIMARY KEY, + total_earnings decimal(5, 2) NOT NULL + ) + `); + }); +} diff --git a/test/options/functionCase.ts b/test/options/functionCase.ts new file mode 100644 index 0000000000..96f16ce489 --- /dev/null +++ b/test/options/functionCase.ts @@ -0,0 +1,42 @@ +import dedent from 'dedent-js'; + +import { FormatFn } from '../../src/sqlFormatter.js'; + +export default function supportsFunctionCase(format: FormatFn) { + it('preserves function name case by default', () => { + const result = format('SELECT MiN(price) AS min_price, Cast(item_code AS INT) FROM products'); + expect(result).toBe(dedent` + SELECT + MiN(price) AS min_price, + Cast(item_code AS INT) + FROM + products + `); + }); + + it('converts function names to uppercase', () => { + const result = format('SELECT MiN(price) AS min_price, Cast(item_code AS INT) FROM products', { + functionCase: 'upper', + }); + expect(result).toBe(dedent` + SELECT + MIN(price) AS min_price, + CAST(item_code AS INT) + FROM + products + `); + }); + + it('converts function names to lowercase', () => { + const result = format('SELECT MiN(price) AS min_price, Cast(item_code AS INT) FROM products', { + functionCase: 'lower', + }); + expect(result).toBe(dedent` + SELECT + min(price) AS min_price, + cast(item_code AS INT) + FROM + products + `); + }); +} diff --git a/test/plsql.test.ts b/test/plsql.test.ts index d1fb8924f9..ba76750753 100644 --- a/test/plsql.test.ts +++ b/test/plsql.test.ts @@ -25,6 +25,7 @@ import supportsUpdate from './features/update.js'; import supportsTruncateTable from './features/truncateTable.js'; import supportsMergeInto from './features/mergeInto.js'; import supportsCreateView from './features/createView.js'; +import supportsDataTypeCase from './options/dataTypeCase.js'; describe('PlSqlFormatter', () => { const language = 'plsql'; @@ -57,13 +58,17 @@ describe('PlSqlFormatter', () => { format, // Missing: '..' operator ['**', ':=', '%', '~=', '^=', '>>', '<<', '=>', '||'], - ['AND', 'OR', 'XOR'] + { + logicalOperators: ['AND', 'OR', 'XOR'], + any: true, + } ); supportsJoin(format, { supportsApply: true }); supportsSetOperations(format, ['UNION', 'UNION ALL', 'EXCEPT', 'INTERSECT']); supportsReturning(format); supportsParams(format, { numbered: [':'], named: [':'] }); supportsLimiting(format, { offset: true, fetchFirst: true, fetchNext: true }); + supportsDataTypeCase(format); it('recognizes _, $, # as part of identifiers', () => { const result = format('SELECT my_col$1#, col.a$, type#, procedure$, user# FROM tbl;'); diff --git a/test/postgresql.test.ts b/test/postgresql.test.ts index 534a57c259..36ea56f8f6 100644 --- a/test/postgresql.test.ts +++ b/test/postgresql.test.ts @@ -29,6 +29,7 @@ import supportsCreateView from './features/createView.js'; import supportsOnConflict from './features/onConflict.js'; import supportsIsDistinctFrom from './features/isDistinctFrom.js'; import supportsArrayLiterals from './features/arrayLiterals.js'; +import supportsDataTypeCase from './options/dataTypeCase.js'; describe('PostgreSqlFormatter', () => { const language = 'postgresql'; @@ -59,90 +60,94 @@ describe('PostgreSqlFormatter', () => { supportsBetween(format); supportsSchema(format); // Missing: '::' type cast (tested separately) - supportsOperators(format, [ - // Arithmetic - '%', - '^', - '|/', - '||/', - '@', - // Assignment - ':=', - // Bitwise - '&', - '|', - '#', - '~', - '<<', - '>>', - // Byte comparison - '~>~', - '~<~', - '~>=~', - '~<=~', - // Geometric - '@-@', - '@@', - '##', - '<->', - '&&', - '&<', - '&>', - '<<|', - '&<|', - '|>>', - '|&>', - '<^', - '^>', - '?#', - '?-', - '?|', - '?-|', - '?||', - '@>', - '<@', - '~=', - // JSON - '?', - '@?', - '?&', - '->', - '->>', - '#>', - '#>>', - '#-', - // Named function params - '=>', - // Network address - '>>=', - '<<=', - // Pattern matching - '~~', - '~~*', - '!~~', - '!~~*', - // POSIX RegExp - '~', - '~*', - '!~', - '!~*', - // Range/multirange - '-|-', - // String concatenation - '||', - // Text search - '@@@', - '!!', - // Trigram/trigraph - '<%', - '<<%', - '%>', - '%>>', - '<<->', - '<->>', - '<<<->', - '<->>>', - ]); + supportsOperators( + format, + [ + // Arithmetic + '%', + '^', + '|/', + '||/', + '@', + // Assignment + ':=', + // Bitwise + '&', + '|', + '#', + '~', + '<<', + '>>', + // Byte comparison + '~>~', + '~<~', + '~>=~', + '~<=~', + // Geometric + '@-@', + '@@', + '##', + '<->', + '&&', + '&<', + '&>', + '<<|', + '&<|', + '|>>', + '|&>', + '<^', + '^>', + '?#', + '?-', + '?|', + '?-|', + '?||', + '@>', + '<@', + '~=', + // JSON + '?', + '@?', + '?&', + '->', + '->>', + '#>', + '#>>', + '#-', + // Named function params + '=>', + // Network address + '>>=', + '<<=', + // Pattern matching + '~~', + '~~*', + '!~~', + '!~~*', + // POSIX RegExp + '~', + '~*', + '!~', + '!~*', + // Range/multirange + '-|-', + // String concatenation + '||', + // Text search + '@@@', + '!!', + // Trigram/trigraph + '<%', + '<<%', + '%>', + '%>>', + '<<->', + '<->>', + '<<<->', + '<->>>', + ], + { any: true } + ); supportsIsDistinctFrom(format); supportsJoin(format); supportsSetOperations(format); @@ -150,6 +155,7 @@ describe('PostgreSqlFormatter', () => { supportsParams(format, { numbered: ['$'] }); supportsWindow(format); supportsLimiting(format, { limit: true, offset: true, fetchFirst: true, fetchNext: true }); + supportsDataTypeCase(format); it('allows $ character as part of identifiers', () => { expect(format('SELECT foo$, some$$ident')).toBe(dedent` diff --git a/test/redshift.test.ts b/test/redshift.test.ts index 6292dc7b29..016967acd1 100644 --- a/test/redshift.test.ts +++ b/test/redshift.test.ts @@ -19,6 +19,7 @@ import supportsStrings from './features/strings.js'; import supportsTruncateTable from './features/truncateTable.js'; import supportsUpdate from './features/update.js'; import supportsParams from './options/param.js'; +import supportsDataTypeCase from './options/dataTypeCase.js'; describe('RedshiftFormatter', () => { const language = 'redshift'; @@ -43,11 +44,14 @@ describe('RedshiftFormatter', () => { supportsStrings(format, ["''-qq"]); supportsIdentifiers(format, [`""-qq`]); // Missing: '#' and '::' operator (tested separately) - supportsOperators(format, ['^', '%', '@', '|/', '||/', '&', '|', '~', '<<', '>>', '||']); + supportsOperators(format, ['^', '%', '@', '|/', '||/', '&', '|', '~', '<<', '>>', '||'], { + any: true, + }); supportsJoin(format); supportsSetOperations(format, ['UNION', 'UNION ALL', 'EXCEPT', 'INTERSECT', 'MINUS']); supportsParams(format, { numbered: ['$'] }); supportsLimiting(format, { limit: true, offset: true }); + supportsDataTypeCase(format); it('formats type-cast operator without spaces', () => { expect(format('SELECT 2 :: numeric AS foo;')).toBe(dedent` diff --git a/test/singlestoredb.test.ts b/test/singlestoredb.test.ts index deac08e45a..858cf94e7f 100644 --- a/test/singlestoredb.test.ts +++ b/test/singlestoredb.test.ts @@ -10,6 +10,7 @@ import supportsCreateTable from './features/createTable.js'; import supportsCreateView from './features/createView.js'; import supportsAlterTable from './features/alterTable.js'; import supportsStrings from './features/strings.js'; +import supportsDataTypeCase from './options/dataTypeCase.js'; describe('SingleStoreDbFormatter', () => { const language = 'singlestoredb'; @@ -35,7 +36,7 @@ describe('SingleStoreDbFormatter', () => { supportsOperators( format, [':=', '&', '|', '^', '~', '<<', '>>', '<=>', '&&', '||', ':>', '!:>'], - ['AND', 'OR'] + { any: true } ); supportsLimiting(format, { limit: true, offset: true }); supportsCreateTable(format, { ifNotExists: true, columnComment: true, tableComment: true }); @@ -46,6 +47,7 @@ describe('SingleStoreDbFormatter', () => { modify: true, renameTo: true, }); + supportsDataTypeCase(format); describe(`formats traversal of semi structured data`, () => { it(`formats '::' path-operator without spaces`, () => { diff --git a/test/snowflake.test.ts b/test/snowflake.test.ts index ecff64b3a0..595e5b7bcb 100644 --- a/test/snowflake.test.ts +++ b/test/snowflake.test.ts @@ -21,6 +21,7 @@ import supportsUpdate from './features/update.js'; import supportsTruncateTable from './features/truncateTable.js'; import supportsCreateView from './features/createView.js'; import supportsConstraints from './features/constraints.js'; +import supportsDataTypeCase from './options/dataTypeCase.js'; describe('SnowflakeFormatter', () => { const language = 'snowflake'; @@ -53,10 +54,11 @@ describe('SnowflakeFormatter', () => { supportsIdentifiers(format, [`""-qq`]); supportsBetween(format); // ':' and '::' are tested later, since they should always be dense - supportsOperators(format, ['%', '||', '=>']); + supportsOperators(format, ['%', '||', '=>'], { any: true }); supportsJoin(format, { without: ['NATURAL INNER JOIN'] }); supportsSetOperations(format, ['UNION', 'UNION ALL', 'MINUS', 'EXCEPT', 'INTERSECT']); supportsLimiting(format, { limit: true, offset: true, fetchFirst: true, fetchNext: true }); + supportsDataTypeCase(format); it('allows $ character as part of unquoted identifiers', () => { expect(format('SELECT foo$')).toBe(dedent` @@ -167,18 +169,18 @@ describe('SnowflakeFormatter', () => { `); }); - it('detects data types as keywords', () => { + it('detects data types', () => { expect( format( `CREATE TABLE tbl (first_column double Precision, second_column numBer (38, 0), third String);`, { - keywordCase: 'upper', + dataTypeCase: 'upper', } ) ).toBe(dedent` CREATE TABLE tbl ( first_column DOUBLE PRECISION, - second_column NUMBER (38, 0), + second_column NUMBER(38, 0), third STRING );`); }); diff --git a/test/spark.test.ts b/test/spark.test.ts index 7dd1cffaa4..ff0910908f 100644 --- a/test/spark.test.ts +++ b/test/spark.test.ts @@ -18,6 +18,7 @@ import supportsLimiting from './features/limiting.js'; import supportsInsertInto from './features/insertInto.js'; import supportsTruncateTable from './features/truncateTable.js'; import supportsCreateView from './features/createView.js'; +import supportsDataTypeCase from './options/dataTypeCase.js'; describe('SparkFormatter', () => { const language = 'spark'; @@ -38,11 +39,10 @@ describe('SparkFormatter', () => { supportsStrings(format, ["''-bs", '""-bs', "X''", 'X""', "R''", 'R""']); supportsIdentifiers(format, ['``']); supportsBetween(format); - supportsOperators( - format, - ['%', '~', '^', '|', '&', '<=>', '==', '!', '||', '->'], - ['AND', 'OR', 'XOR'] - ); + supportsOperators(format, ['%', '~', '^', '|', '&', '<=>', '==', '!', '||', '->'], { + logicalOperators: ['AND', 'OR', 'XOR'], + any: true, + }); supportsArrayAndMapAccessors(format); supportsJoin(format, { additionally: [ @@ -60,6 +60,7 @@ describe('SparkFormatter', () => { }); supportsSetOperations(format); supportsLimiting(format, { limit: true }); + supportsDataTypeCase(format); it('formats basic WINDOW clause', () => { const result = format(`SELECT * FROM tbl WINDOW win1, WINDOW win2, WINDOW win3;`); diff --git a/test/sql.test.ts b/test/sql.test.ts index 9a8bf58093..56d37c6db2 100644 --- a/test/sql.test.ts +++ b/test/sql.test.ts @@ -23,6 +23,7 @@ import supportsInsertInto from './features/insertInto.js'; import supportsUpdate from './features/update.js'; import supportsTruncateTable from './features/truncateTable.js'; import supportsCreateView from './features/createView.js'; +import supportsDataTypeCase from './options/dataTypeCase.js'; describe('SqlFormatter', () => { const language = 'sql'; @@ -50,10 +51,11 @@ describe('SqlFormatter', () => { supportsSchema(format); supportsJoin(format); supportsSetOperations(format); - supportsOperators(format, ['||']); + supportsOperators(format, ['||'], { any: true }); supportsParams(format, { positional: true }); supportsWindow(format); supportsLimiting(format, { limit: true, offset: true, fetchFirst: true, fetchNext: true }); + supportsDataTypeCase(format); it('throws error when encountering characters or operators it does not recognize', () => { expect(() => format('SELECT @name, :bar FROM foo;')).toThrowError( diff --git a/test/sqlite.test.ts b/test/sqlite.test.ts index 78ec8e7438..7d12dea399 100644 --- a/test/sqlite.test.ts +++ b/test/sqlite.test.ts @@ -22,6 +22,7 @@ import supportsInsertInto from './features/insertInto.js'; import supportsUpdate from './features/update.js'; import supportsCreateView from './features/createView.js'; import supportsOnConflict from './features/onConflict.js'; +import supportsDataTypeCase from './options/dataTypeCase.js'; describe('SqliteFormatter', () => { const language = 'sqlite'; @@ -53,6 +54,7 @@ describe('SqliteFormatter', () => { supportsParams(format, { positional: true, numbered: ['?'], named: [':', '$', '@'] }); supportsWindow(format); supportsLimiting(format, { limit: true, offset: true }); + supportsDataTypeCase(format); it('supports REPLACE INTO syntax', () => { expect(format(`REPLACE INTO tbl VALUES (1,'Leopard'),(2,'Dog');`)).toBe(dedent` diff --git a/test/transactsql.test.ts b/test/transactsql.test.ts index 0a9f0fefbb..022514765b 100644 --- a/test/transactsql.test.ts +++ b/test/transactsql.test.ts @@ -23,6 +23,7 @@ import supportsUpdate from './features/update.js'; import supportsTruncateTable from './features/truncateTable.js'; import supportsMergeInto from './features/mergeInto.js'; import supportsCreateView from './features/createView.js'; +import supportsDataTypeCase from './options/dataTypeCase.js'; describe('TransactSqlFormatter', () => { const language = 'transactsql'; @@ -46,28 +47,17 @@ describe('TransactSqlFormatter', () => { supportsIdentifiers(format, [`""-qq`, '[]']); supportsBetween(format); // Missing: `::` scope resolution operator (tested separately) - supportsOperators(format, [ - '%', - '&', - '|', - '^', - '~', - '!<', - '!>', - '+=', - '-=', - '*=', - '/=', - '%=', - '|=', - '&=', - '^=', - ]); + supportsOperators( + format, + ['%', '&', '|', '^', '~', '!<', '!>', '+=', '-=', '*=', '/=', '%=', '|=', '&=', '^='], + { any: true } + ); supportsJoin(format, { without: ['NATURAL'], supportsUsing: false, supportsApply: true }); supportsSetOperations(format, ['UNION', 'UNION ALL', 'EXCEPT', 'INTERSECT']); supportsParams(format, { named: ['@'], quoted: ['@""', '@[]'] }); supportsWindow(format); supportsLimiting(format, { offset: true, fetchFirst: true, fetchNext: true }); + supportsDataTypeCase(format); it('supports language:tsql alias', () => { const result = originalFormat('SELECT [my column] FROM [my table];', { language: 'tsql' }); diff --git a/test/trino.test.ts b/test/trino.test.ts index a300ee4e92..8bf4e5bddd 100644 --- a/test/trino.test.ts +++ b/test/trino.test.ts @@ -25,6 +25,7 @@ import supportsTruncateTable from './features/truncateTable.js'; import supportsCreateView from './features/createView.js'; import supportsAlterTable from './features/alterTable.js'; import supportsIsDistinctFrom from './features/isDistinctFrom.js'; +import supportsDataTypeCase from './options/dataTypeCase.js'; describe('TrinoFormatter', () => { const language = 'trino'; @@ -50,7 +51,7 @@ describe('TrinoFormatter', () => { supportsIdentifiers(format, [`""-qq`]); supportsBetween(format); // Missing: '?' operator (for row patterns) - supportsOperators(format, ['%', '->', '=>', '||', '|', '^', '$'], ['AND', 'OR']); + supportsOperators(format, ['%', '->', '=>', '||', '|', '^', '$'], { any: true }); supportsIsDistinctFrom(format); supportsArrayLiterals(format, { withArrayPrefix: true }); supportsArrayAndMapAccessors(format); @@ -59,6 +60,7 @@ describe('TrinoFormatter', () => { supportsParams(format, { positional: true }); supportsWindow(format); supportsLimiting(format, { limit: true, offset: true, fetchFirst: true, fetchNext: true }); + supportsDataTypeCase(format); it('formats SET SESSION', () => { const result = format('SET SESSION foo = 444;');