diff --git a/AUTHORS b/AUTHORS index e70abaeff..fbbfee1fd 100644 --- a/AUTHORS +++ b/AUTHORS @@ -10,6 +10,7 @@ Andrew Benjamin Bellamy bingou Boris Verkhovskiy +Christian Jorgensen Christopher Manouvrier Damon Davison Davut Can Abacigil diff --git a/README.md b/README.md index 98f3aa706..bcfeea856 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ sql-formatter -h ``` usage: sql-formatter [-h] [-o OUTPUT] \ -[-l {bigquery,db2,hive,mariadb,mysql,n1ql,plsql,postgresql,redshift,singlestoredb,snowflake,spark,sql,sqlite,transactsql,trino,tsql}] [-c CONFIG] [--version] [FILE] +[-l {bigquery,db2,db2i,hive,mariadb,mysql,n1ql,plsql,postgresql,redshift,singlestoredb,snowflake,spark,sql,sqlite,transactsql,trino,tsql}] [-c CONFIG] [--version] [FILE] SQL Formatter @@ -107,7 +107,7 @@ optional arguments: -o, --output OUTPUT File to write SQL output (defaults to stdout) --fix Update the file in-place - -l, --language {bigquery,db2,hive,mariadb,mysql,n1ql,plsql,postgresql,redshift,singlestoredb,snowflake,spark,sql,sqlite,trino,tsql} + -l, --language {bigquery,db2,db2i,hive,mariadb,mysql,n1ql,plsql,postgresql,redshift,singlestoredb,snowflake,spark,sql,sqlite,trino,tsql} SQL dialect (defaults to basic sql) -c, --config CONFIG Path to config json file (will use default configs if unspecified) diff --git a/docs/dialect.md b/docs/dialect.md index 2e3ed8656..fc973f58f 100644 --- a/docs/dialect.md +++ b/docs/dialect.md @@ -23,6 +23,7 @@ The following dialects can be imported from `"sql-formatter"` module: - `sql` - [Standard SQL][] - `bigquery` - [GCP BigQuery][] - `db2` - [IBM DB2][] +- `db2i` - [IBM DB2i][] (experimental) - `hive` - [Apache Hive][] - `mariadb` - [MariaDB][] - `mysql` - [MySQL][] @@ -69,6 +70,7 @@ You likely only want to use this if your other alternative is to fork SQL Format [standard sql]: https://en.wikipedia.org/wiki/SQL:2011 [gcp bigquery]: https://cloud.google.com/bigquery [ibm db2]: https://www.ibm.com/analytics/us/en/technology/db2/ +[ibm db2i]: https://www.ibm.com/docs/en/i/7.5?topic=overview-db2-i [apache hive]: https://hive.apache.org/ [mariadb]: https://mariadb.com/ [mysql]: https://www.mysql.com/ diff --git a/docs/language.md b/docs/language.md index b71c4b0ca..df38263c1 100644 --- a/docs/language.md +++ b/docs/language.md @@ -15,6 +15,7 @@ const result = format('SELECT * FROM tbl', { dialect: 'sqlite' }); - `"sql"` - (default) [Standard SQL][] - `"bigquery"` - [GCP BigQuery][] - `"db2"` - [IBM DB2][] +- `"db2i"` - [IBM DB2i][] (experimental) - `"hive"` - [Apache Hive][] - `"mariadb"` - [MariaDB][] - `"mysql"` - [MySQL][] @@ -47,6 +48,7 @@ See docs for [dialect][] option. [standard sql]: https://en.wikipedia.org/wiki/SQL:2011 [gcp bigquery]: https://cloud.google.com/bigquery [ibm db2]: https://www.ibm.com/analytics/us/en/technology/db2/ +[ibm db2i]: https://www.ibm.com/docs/en/i/7.5?topic=overview-db2-i [apache hive]: https://hive.apache.org/ [mariadb]: https://mariadb.com/ [mysql]: https://www.mysql.com/ diff --git a/docs/params.md b/docs/params.md index 2da66db1b..575d54ceb 100644 --- a/docs/params.md +++ b/docs/params.md @@ -110,6 +110,7 @@ The placeholder types available by default depend on SQL dialect used: - sql - `?` - bigquery - `?`, `@name`, `` @`name` `` - db2 - `?`, `:name` +- db2i - `?`, `:name` - hive - _no support_ - mariadb - `?` - mysql - `?` diff --git a/package.json b/package.json index 36213b052..d64c15d07 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "pl/sql", "transact-sql", "db2", + "db2i", "sqlite", "trino", "presto", diff --git a/src/allDialects.ts b/src/allDialects.ts index 7ba24ffb1..5bc46b899 100644 --- a/src/allDialects.ts +++ b/src/allDialects.ts @@ -1,5 +1,6 @@ export { bigquery } from './languages/bigquery/bigquery.formatter.js'; export { db2 } from './languages/db2/db2.formatter.js'; +export { db2i } from './languages/db2i/db2i.formatter.js'; export { hive } from './languages/hive/hive.formatter.js'; export { mariadb } from './languages/mariadb/mariadb.formatter.js'; export { mysql } from './languages/mysql/mysql.formatter.js'; diff --git a/src/languages/db2i/db2i.formatter.ts b/src/languages/db2i/db2i.formatter.ts new file mode 100644 index 000000000..d76e17cdf --- /dev/null +++ b/src/languages/db2i/db2i.formatter.ts @@ -0,0 +1,179 @@ +import { DialectOptions } from '../../dialect.js'; +import { expandPhrases } from '../../expandPhrases.js'; +import { functions } from './db2i.functions.js'; +import { keywords } from './db2i.keywords.js'; + +const reservedSelect = expandPhrases(['SELECT [ALL | DISTINCT]']); + +const reservedClauses = expandPhrases([ + // queries + 'WITH', + 'INTO', + 'FROM', + 'WHERE', + 'GROUP BY', + 'HAVING', + 'PARTITION BY', + 'ORDER [SIBLINGS] BY [INPUT SEQUENCE]', + 'OFFSET', + 'FETCH {FIRST | NEXT}', + 'LIMIT', + 'FOR UPDATE [OF]', + 'FOR READ ONLY', + 'OPTIMIZE FOR', + // Data modification + // - insert: + 'INSERT INTO', + 'VALUES', + // - update: + 'SET', + // - merge: + 'MERGE INTO', + 'WHEN [NOT] MATCHED [THEN]', + 'UPDATE SET', + 'DELETE', + 'INSERT', + // Data definition - table + 'CREATE [OR REPLACE] TABLE', + 'FOR SYSTEM NAME', + // Data definition - view + 'CREATE [OR REPLACE] [RECURSIVE] VIEW', +]); + +const onelineClauses = expandPhrases([ + // - update: + 'UPDATE', + 'WHERE CURRENT OF', + 'WITH {NC | RR | RS | CS | UR}', + // - delete: + 'DELETE FROM', + // - drop table: + 'DROP TABLE', + // alter table: + 'ALTER TABLE', + 'ADD [COLUMN]', + 'ALTER [COLUMN]', + 'DROP [COLUMN]', + 'RENAME [COLUMN]', + 'SET DATA TYPE', // for alter column + 'SET {GENERATED ALWAYS | GENERATED BY DEFAULT}', // for alter column + 'SET NOT NULL', // for alter column + 'SET {NOT HIDDEN | IMPLICITLY HIDDEN}', // for alter column + 'SET FIELDPROC', // for alter column + 'DROP {DEFAULT | NOT NULL | GENERATED | IDENTITY | ROW CHANGE TIMESTAMP | FIELDPROC}', // for alter column + // - truncate: + 'TRUNCATE [TABLE]', + // other + 'SET [CURRENT] SCHEMA', + 'SET CURRENT_SCHEMA', + // https://www.ibm.com/docs/en/i/7.5?topic=reference-statements + 'ALLOCATE CURSOR', + 'ALLOCATE [SQL] DESCRIPTOR [LOCAL | GLOBAL] SQL', + 'ALTER [SPECIFIC] {FUNCTION | PROCEDURE}', + 'ALTER {MASK | PERMISSION | SEQUENCE | TRIGGER}', + 'ASSOCIATE [RESULT SET] {LOCATOR | LOCATORS}', + 'BEGIN DECLARE SECTION', + 'CALL', + 'CLOSE', + 'COMMENT ON {ALIAS | COLUMN | CONSTRAINT | INDEX | MASK | PACKAGE | PARAMETER | PERMISSION | SEQUENCE | TABLE | TRIGGER | VARIABLE | XSROBJECT}', + 'COMMENT ON [SPECIFIC] {FUNCTION | PROCEDURE | ROUTINE}', + 'COMMENT ON PARAMETER SPECIFIC {FUNCTION | PROCEDURE | ROUTINE}', + 'COMMENT ON [TABLE FUNCTION] RETURN COLUMN', + 'COMMENT ON [TABLE FUNCTION] RETURN COLUMN SPECIFIC [PROCEDURE | ROUTINE]', + 'COMMIT [WORK] [HOLD]', + 'CONNECT [TO | RESET] USER', + 'CREATE [OR REPLACE] {ALIAS | FUNCTION | MASK | PERMISSION | PROCEDURE | SEQUENCE | TRIGGER | VARIABLE}', + 'CREATE [ENCODED VECTOR] INDEX', + 'CREATE UNIQUE [WHERE NOT NULL] INDEX', + 'CREATE SCHEMA', + 'CREATE TYPE', + 'DEALLOCATE [SQL] DESCRIPTOR [LOCAL | GLOBAL]', + 'DECLARE CURSOR', + 'DECLARE GLOBAL TEMPORARY TABLE', + 'DECLARE', + 'DESCRIBE CURSOR', + 'DESCRIBE INPUT', + 'DESCRIBE [OUTPUT]', + 'DESCRIBE {PROCEDURE | ROUTINE}', + 'DESCRIBE TABLE', + 'DISCONNECT ALL [SQL]', + 'DISCONNECT [CURRENT]', + 'DROP {ALIAS | INDEX | MASK | PACKAGE | PERMISSION | SCHEMA | SEQUENCE | TABLE | TYPE | VARIABLE | XSROBJECT} [IF EXISTS]', + 'DROP [SPECIFIC] {FUNCTION | PROCEDURE | ROUTINE} [IF EXISTS]', + 'END DECLARE SECTION', + 'EXECUTE [IMMEDIATE]', + // 'FETCH {NEXT | PRIOR | FIRST | LAST | BEFORE | AFTER | CURRENT} [FROM]', + 'FREE LOCATOR', + 'GET [SQL] DESCRIPTOR [LOCAL | GLOBAL]', + 'GET [CURRENT | STACKED] DIAGNOSTICS', + 'GRANT {ALL [PRIVILEGES] | ALTER | EXECUTE} ON {FUNCTION | PROCEDURE | ROUTINE | PACKAGE | SCHEMA | SEQUENCE | TABLE | TYPE | VARIABLE | XSROBJECT}', + 'HOLD LOCATOR', + 'INCLUDE', + 'LABEL ON {ALIAS | COLUMN | CONSTRAINT | INDEX | MASK | PACKAGE | PERMISSION | SEQUENCE | TABLE | TRIGGER | VARIABLE | XSROBJECT}', + 'LABEL ON [SPECIFIC] {FUNCTION | PROCEDURE | ROUTINE}', + 'LOCK TABLE', + 'OPEN', + 'PREPARE', + 'REFRESH TABLE', + 'RELEASE', + 'RELEASE [TO] SAVEPOINT', + 'RENAME [TABLE | INDEX] TO', + 'REVOKE {ALL [PRIVILEGES] | ALTER | EXECUTE} ON {FUNCTION | PROCEDURE | ROUTINE | PACKAGE | SCHEMA | SEQUENCE | TABLE | TYPE | VARIABLE | XSROBJECT}', + 'ROLLBACK [WORK] [HOLD | TO SAVEPOINT]', + 'SAVEPOINT', + 'SET CONNECTION', + 'SET CURRENT {DEBUG MODE | DECFLOAT ROUNDING MODE | DEGREE | IMPLICIT XMLPARSE OPTION | TEMPORAL SYSTEM_TIME}', + 'SET [SQL] DESCRIPTOR [LOCAL | GLOBAL]', + 'SET ENCRYPTION PASSWORD', + 'SET OPTION', + 'SET {[CURRENT [FUNCTION]] PATH | CURRENT_PATH}', + 'SET RESULT SETS [WITH RETURN [TO CALLER | TO CLIENT]', + 'SET SESSION AUTHORIZATION', + 'SET SESSION_USER', + 'SET TRANSACTION', + 'SIGNAL SQLSTATE [VALUE]', + 'TAG', + 'TRANSFER OWNERSHIP OF', + 'WHENEVER {NOT FOUND | SQLERROR | SQLWARNING}', +]); + +const reservedSetOperations = expandPhrases(['UNION [ALL]', 'EXCEPT [ALL]', 'INTERSECT [ALL]']); + +const reservedJoins = expandPhrases([ + 'JOIN', + '{LEFT | RIGHT | FULL} [OUTER] JOIN', + '{LEFT | RIGHT } EXCEPTION JOIN', + '{INNER | CROSS} JOIN', +]); + +const reservedPhrases = expandPhrases([ + 'ON DELETE', + 'ON UPDATE', + 'SET NULL', + '{ROWS | RANGE} BETWEEN', +]); + +// https://www.ibm.com/docs/en/i/7.5?topic=reference-sql +export const db2i: DialectOptions = { + tokenizerOptions: { + reservedSelect, + reservedClauses: [...reservedClauses, ...onelineClauses], + reservedSetOperations, + reservedJoins, + reservedPhrases, + reservedKeywords: keywords, + reservedFunctionNames: functions, + stringTypes: [ + { quote: "''-qq", prefixes: ['G', 'N', 'U&'] }, + { quote: "''-raw", prefixes: ['X', 'BX', 'GX', 'UX'], requirePrefix: true }, + ], + identTypes: [`""-qq`], + identChars: { first: '@#$' }, + paramTypes: { positional: true, named: [':'] }, + paramChars: { first: '@#$', rest: '@#$' }, + operators: ['**', '¬=', '¬>', '¬<', '!>', '!<', '||'], + }, + formatOptions: { + onelineClauses, + }, +}; diff --git a/src/languages/db2i/db2i.functions.ts b/src/languages/db2i/db2i.functions.ts new file mode 100644 index 000000000..6c933dcc2 --- /dev/null +++ b/src/languages/db2i/db2i.functions.ts @@ -0,0 +1,367 @@ +import { flatKeywordList } from '../../utils.js'; + +export const functions = flatKeywordList({ + // https://www.ibm.com/docs/en/i/7.5?topic=functions-aggregate + aggregate: [ + // TODO: 'ANY', - conflicts with test for ANY predicate in 'operators.ys'!! + 'ARRAY_AGG', + 'AVG', + 'CORR', + 'CORRELATION', + 'COUNT', + 'COUNT_BIG', + 'COVAR_POP', + 'COVARIANCE', + 'COVAR', + 'COVAR_SAMP', + 'COVARIANCE_SAMP', + 'EVERY', + 'GROUPING', + 'JSON_ARRAYAGG', + 'JSON_OBJECTAGG', + 'LISTAGG', + 'MAX', + 'MEDIAN', + 'MIN', + 'PERCENTILE_CONT', + 'PERCENTILE_DISC', + // https://www.ibm.com/docs/en/i/7.5?topic=functions-regression' + 'REGR_AVGX', + 'REGR_AVGY', + 'REGR_COUNT', + 'REGR_INTERCEPT', + 'REGR_R2', + 'REGR_SLOPE', + 'REGR_SXX', + 'REGR_SXY', + 'REGR_SYY', + 'SOME', + 'STDDEV_POP', + 'STDDEV', + 'STDDEV_SAMP', + 'SUM', + 'VAR_POP', + 'VARIANCE', + 'VAR', + 'VAR_SAMP', + 'VARIANCE_SAMP', + 'XMLAGG', + 'XMLGROUP', + ], + // https://www.ibm.com/docs/en/i/7.5?topic=functions-scalar + scalar: [ + 'ABS', + 'ABSVAL', + 'ACOS', + 'ADD_DAYS', + 'ADD_HOURS', + 'ADD_MINUTES', + 'ADD_MONTHS', + 'ADD_SECONDS', + 'ADD_YEARS', + 'ANTILOG', + 'ARRAY_MAX_CARDINALITY', + 'ARRAY_TRIM', + 'ASCII', + 'ASIN', + 'ATAN', + 'ATAN2', + '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', + 'CONTAINS', + 'COS', + 'COSH', + 'COT', + 'CURDATE', + 'CURTIME', + 'DATABASE', + 'DATAPARTITIONNAME', + 'DATAPARTITIONNUM', + 'DATE', + 'DAY', + 'DAYNAME', + 'DAYOFMONTH', + 'DAYOFWEEK_ISO', + 'DAYOFWEEK', + 'DAYOFYEAR', + 'DAYS', + 'DBCLOB', + 'DBPARTITIONNAME', + 'DBPARTITIONNUM', + 'DEC', + 'DECFLOAT_FORMAT', + 'DECFLOAT_SORTKEY', + 'DECFLOAT', + 'DECIMAL', + 'DECRYPT_BINARY', + 'DECRYPT_BIT', + 'DECRYPT_CHAR', + 'DECRYPT_DB', + 'DEGREES', + 'DIFFERENCE', + 'DIGITS', + 'DLCOMMENT', + 'DLLINKTYPE', + 'DLURLCOMPLETE', + 'DLURLPATH', + 'DLURLPATHONLY', + 'DLURLSCHEME', + 'DLURLSERVER', + 'DLVALUE', + 'DOUBLE_PRECISION', + 'DOUBLE', + 'ENCRPYT', + 'ENCRYPT_AES', + 'ENCRYPT_AES256', + 'ENCRYPT_RC2', + 'ENCRYPT_TDES', + 'EXP', + 'EXTRACT', + 'FIRST_DAY', + 'FLOAT', + 'FLOOR', + 'GENERATE_UNIQUE', + 'GET_BLOB_FROM_FILE', + 'GET_CLOB_FROM_FILE', + 'GET_DBCLOB_FROM_FILE', + 'GET_XML_FILE', + 'GETHINT', + 'GRAPHIC', + 'GREATEST', + 'HASH_MD5', + 'HASH_ROW', + 'HASH_SHA1', + 'HASH_SHA256', + 'HASH_SHA512', + 'HASH_VALUES', + 'HASHED_VALUE', + 'HEX', + 'HEXTORAW', + 'HOUR', + 'HTML_ENTITY_DECODE', + 'HTML_ENTITY_ENCODE', + 'HTTP_DELETE_BLOB', + 'HTTP_DELETE', + 'HTTP_GET_BLOB', + 'HTTP_GET', + 'HTTP_PATCH_BLOB', + 'HTTP_PATCH', + 'HTTP_POST_BLOB', + 'HTTP_POST', + 'HTTP_PUT_BLOB', + 'HTTP_PUT', + 'IDENTITY_VAL_LOCAL', + 'IFNULL', + 'INSERT', + 'INSTR', + 'INT', + 'INTEGER', + 'INTERPRET', + 'ISFALSE', + 'ISNOTFALSE', + 'ISNOTTRUE', + 'ISTRUE', + 'JSON_ARRAY', + 'JSON_OBJECT', + 'JSON_QUERY', + 'JSON_TO_BSON', + 'JSON_UPDATE', + 'JSON_VALUE', + 'JULIAN_DAY', + 'LAND', + 'LAST_DAY', + 'LCASE', + 'LEAST', + 'LEFT', + 'LENGTH', + 'LN', + 'LNOT', + 'LOCATE_IN_STRING', + 'LOCATE', + 'LOG10', + 'LOR', + 'LOWER', + 'LPAD', + 'LTRIM', + 'MAX_CARDINALITY', + 'MAX', + 'MICROSECOND', + 'MIDNIGHT_SECONDS', + 'MIN', + 'MINUTE', + 'MOD', + 'MONTH', + 'MONTHNAME', + 'MONTHS_BETWEEN', + 'MQREAD', + 'MQREADCLOB', + 'MQRECEIVE', + 'MQRECEIVECLOB', + 'MQSEND', + 'MULTIPLY_ALT', + 'NEXT_DAY', + 'NORMALIZE_DECFLOAT', + 'NOW', + 'NULLIF', + 'NVL', + 'OCTET_LENGTH', + 'OVERLAY', + 'PI', + 'POSITION', + 'POSSTR', + 'POW', + 'POWER', + 'QUANTIZE', + 'QUARTER', + 'RADIANS', + 'RAISE_ERROR', + 'RANDOM or RAND', + 'REAL', + 'REGEXP_COUNT', + 'REGEXP_INSTR', + 'REGEXP_REPLACE', + 'REGEXP_SUBSTR', + 'REPEAT', + 'REPLACE', + 'RID', + 'RIGHT', + 'ROUND_TIMESTAMP', + 'ROUND', + 'ROWID', + 'RPAD', + 'RRN', + 'RTRIM', + 'SCORE', + 'SECOND', + 'SIGN', + 'SIN', + 'SINH', + 'SMALLINT', + 'SOUNDEX', + 'SPACE', + 'SQRT', + 'STRIP', + 'STRLEFT', + 'STRPOS', + 'STRRIGHT', + 'SUBSTR', + 'SUBSTRING', + 'TABLE_NAME', + 'TABLE_SCHEMA', + 'TAN', + 'TANH', + 'TIME', + 'TIMESTAMP_FORMAT', + 'TIMESTAMP_ISO', + 'TIMESTAMP', + 'TIMESTAMPDIFF_BIG', + 'TIMESTAMPDIFF', + 'TO_CHAR', + 'TO_CLOB', + 'TO_DATE', + 'TO_NUMBER', + 'TO_TIMESTAMP', + 'TOTALORDER', + 'TRANSLATE', + 'TRIM_ARRAY', + 'TRIM', + 'TRUNC_TIMESTAMP', + 'TRUNC', + 'TRUNCATE', + 'UCASE', + 'UPPER', + 'URL_DECODE', + 'URL_ENCODE', + 'VALUE', + 'VARBINARY_FORMAT', + 'VARBINARY', + 'VARCHAR_BIT_FORMAT', + 'VARCHAR_FORMAT_BINARY', + 'VARCHAR_FORMAT', + 'VARCHAR', + 'VARGRAPHIC', + 'VERIFY_GROUP_FOR_USER', + 'WEEK_ISO', + 'WEEK', + 'WRAP', + 'XMLATTRIBUTES', + 'XMLCOMMENT', + 'XMLCONCAT', + 'XMLDOCUMENT', + 'XMLELEMENT', + 'XMLFOREST', + 'XMLNAMESPACES', + 'XMLPARSE', + 'XMLPI', + 'XMLROW', + 'XMLSERIALIZE', + 'XMLTEXT', + 'XMLVALIDATE', + 'XOR', + 'XSLTRANSFORM', + 'YEAR', + 'ZONED', + ], + // https://www.ibm.com/docs/en/i/7.5?topic=functions-table + table: [ + 'BASE_TABLE', + 'HTTP_DELETE_BLOB_VERBOSE', + 'HTTP_DELETE_VERBOSE', + 'HTTP_GET_BLOB_VERBOSE', + 'HTTP_GET_VERBOSE', + 'HTTP_PATCH_BLOB_VERBOSE', + 'HTTP_PATCH_VERBOSE', + 'HTTP_POST_BLOB_VERBOSE', + 'HTTP_POST_VERBOSE', + 'HTTP_PUT_BLOB_VERBOSE', + 'HTTP_PUT_VERBOSE', + 'JSON_TABLE', + 'MQREADALL', + 'MQREADALLCLOB', + 'MQRECEIVEALL', + 'MQRECEIVEALLCLOB', + 'XMLTABLE', + ], + // https://www.ibm.com/docs/en/db2-for-zos/11?topic=functions-row + row: ['UNPACK'], + // https://www.ibm.com/docs/en/i/7.5?topic=expressions-olap-specifications + olap: [ + 'CUME_DIST', + 'DENSE_RANK', + 'FIRST_VALUE', + 'LAG', + 'LAST_VALUE', + 'LEAD', + 'NTH_VALUE', + 'NTILE', + 'PERCENT_RANK', + 'RANK', + 'RATIO_TO_REPORT', + 'ROW_NUMBER', + ], + // Type casting + cast: ['CAST'], +}); diff --git a/src/languages/db2i/db2i.keywords.ts b/src/languages/db2i/db2i.keywords.ts new file mode 100644 index 000000000..0d254ddd5 --- /dev/null +++ b/src/languages/db2i/db2i.keywords.ts @@ -0,0 +1,514 @@ +import { flatKeywordList } from '../../utils.js'; + +export const keywords = flatKeywordList({ + // https://www.ibm.com/docs/en/i/7.5?topic=words-reserved + // TODO: This list likely contains all keywords, not only the reserved ones, + // try to filter it down to just the reserved keywords. + standard: [ + 'ABSENT', + 'ACCORDING', + 'ACCTNG', + 'ACTION', + 'ACTIVATE', + 'ADD', + 'ALIAS', + 'ALL', + 'ALLOCATE', + 'ALLOW', + 'ALTER', + 'AND', + 'ANY', + 'APPEND', + 'APPLNAME', + 'ARRAY', + 'ARRAY_AGG', + 'ARRAY_TRIM', + 'AS', + 'ASC', + 'ASENSITIVE', + 'ASSOCIATE', + 'ATOMIC', + 'ATTACH', + 'ATTRIBUTES', + 'AUTHORIZATION', + 'AUTONOMOUS', + 'BEFORE', + 'BEGIN', + 'BETWEEN', + 'BINARY', + 'BIND', + 'BIT', + 'BOOLEAN', + 'BSON', + 'BUFFERPOOL', + 'BY', + 'CACHE', + 'CALL', + 'CALLED', + 'CARDINALITY', + 'CASE', + 'CAST', + 'CCSID', + 'CHAR', + 'CHARACTER', + 'CHECK', + 'CL', + 'CLOSE', + 'CLUSTER', + 'COLLECT', + 'COLLECTION', + 'COLUMN', + 'COMMENT', + 'COMMIT', + 'COMPACT', + 'COMPARISONS', + 'COMPRESS', + 'CONCAT', + 'CONCURRENT', + 'CONDITION', + 'CONNECT', + 'CONNECT_BY_ROOT', + 'CONNECTION', + 'CONSTANT', + 'CONSTRAINT', + 'CONTAINS', + 'CONTENT', + 'CONTINUE', + 'COPY', + 'COUNT', + 'COUNT_BIG', + 'CREATE', + 'CREATEIN', + 'CROSS', + 'CUBE', + 'CUME_DIST', + 'CURRENT', + 'CURRENT_DATE', + 'CURRENT_PATH', + 'CURRENT_SCHEMA', + 'CURRENT_SERVER', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURRENT_TIMEZONE', + 'CURRENT_USER', + 'CURSOR', + 'CYCLE', + 'DATA', + 'DATABASE', + 'DATAPARTITIONNAME', + 'DATAPARTITIONNUM', + 'DATE', + 'DAY', + 'DAYS', + 'DB2GENERAL', + 'DB2GENRL', + 'DB2SQL', + 'DBINFO', + 'DBPARTITIONNAME', + 'DBPARTITIONNUM', + 'DEACTIVATE', + 'DEALLOCATE', + 'DECLARE', + 'DEFAULT', + 'DEFAULTS', + 'DEFER', + 'DEFINE', + 'DEFINITION', + 'DELETE', + 'DELETING', + 'DENSE_RANK', + 'DENSERANK', + 'DESC', + 'DESCRIBE', + 'DESCRIPTOR', + 'DETACH', + 'DETERMINISTIC', + 'DIAGNOSTICS', + 'DISABLE', + 'DISALLOW', + 'DISCONNECT', + 'DISTINCT', + 'DO', + 'DOCUMENT', + 'DOUBLE', + 'DROP', + 'DYNAMIC', + 'EACH', + 'ELSE', + 'ELSEIF', + 'EMPTY', + 'ENABLE', + 'ENCODING', + 'ENCRYPTION', + 'END', + 'END-EXEC', + 'ENDING', + 'ENFORCED', + 'ERROR', + 'ESCAPE', + 'EVERY', + 'EXCEPT', + 'EXCEPTION', + 'EXCLUDING', + 'EXCLUSIVE', + 'EXECUTE', + 'EXISTS', + 'EXIT', + 'EXTEND', + 'EXTERNAL', + 'EXTRACT', + 'FALSE', + 'FENCED', + 'FETCH', + 'FIELDPROC', + 'FILE', + 'FINAL', + 'FIRST_VALUE', + 'FOR', + 'FOREIGN', + 'FORMAT', + 'FREE', + 'FREEPAGE', + 'FROM', + 'FULL', + 'FUNCTION', + 'GBPCACHE', + 'GENERAL', + 'GENERATED', + 'GET', + 'GLOBAL', + 'GO', + 'GOTO', + 'GRANT', + 'GRAPHIC', + 'GROUP', + 'HANDLER', + 'HASH', + 'HASH_ROW', + 'HASHED_VALUE', + 'HAVING', + 'HINT', + 'HOLD', + 'HOUR', + 'HOURS', + // 'ID', Not actually a reserved keyword + 'IDENTITY', + 'IF', + 'IGNORE', + 'IMMEDIATE', + 'IMPLICITLY', + 'IN', + 'INCLUDE', + 'INCLUDING', + 'INCLUSIVE', + 'INCREMENT', + 'INDEX', + 'INDEXBP', + 'INDICATOR', + 'INF', + 'INFINITY', + 'INHERIT', + 'INLINE', + 'INNER', + 'INOUT', + 'INSENSITIVE', + 'INSERT', + 'INSERTING', + 'INTEGRITY', + 'INTERPRET', + 'INTERSECT', + 'INTO', + 'IS', + 'ISNULL', + 'ISOLATION', + 'ITERATE', + 'JAVA', + 'JOIN', + 'JSON', + 'JSON_ARRAY', + 'JSON_ARRAYAGG', + 'JSON_EXISTS', + 'JSON_OBJECT', + 'JSON_OBJECTAGG', + 'JSON_QUERY', + 'JSON_TABLE', + 'JSON_VALUE', + 'KEEP', + 'KEY', + 'KEYS', + 'LABEL', + 'LAG', + 'LANGUAGE', + 'LAST_VALUE', + 'LATERAL', + 'LEAD', + 'LEAVE', + 'LEFT', + 'LEVEL2', + 'LIKE', + 'LIMIT', + 'LINKTYPE', + 'LISTAGG', + 'LOCAL', + 'LOCALDATE', + 'LOCALTIME', + 'LOCALTIMESTAMP', + 'LOCATION', + 'LOCATOR', + 'LOCK', + 'LOCKSIZE', + 'LOG', + 'LOGGED', + 'LONG', + 'LOOP', + 'MAINTAINED', + 'MASK', + 'MATCHED', + 'MATERIALIZED', + 'MAXVALUE', + 'MERGE', + 'MICROSECOND', + 'MICROSECONDS', + 'MINPCTUSED', + 'MINUTE', + 'MINUTES', + 'MINVALUE', + 'MIRROR', + 'MIXED', + 'MODE', + 'MODIFIES', + 'MONTH', + 'MONTHS', + 'NAMESPACE', + 'NAN', + 'NATIONAL', + 'NCHAR', + 'NCLOB', + 'NESTED', + 'NEW', + 'NEW_TABLE', + 'NEXTVAL', + 'NO', + 'NOCACHE', + 'NOCYCLE', + 'NODENAME', + 'NODENUMBER', + 'NOMAXVALUE', + 'NOMINVALUE', + 'NONE', + 'NOORDER', + 'NORMALIZED', + 'NOT', + 'NOTNULL', + 'NTH_VALUE', + 'NTILE', + 'NULL', + 'NULLS', + 'NVARCHAR', + 'OBID', + 'OBJECT', + 'OF', + 'OFF', + 'OFFSET', + 'OLD', + 'OLD_TABLE', + 'OMIT', + 'ON', + 'ONLY', + 'OPEN', + 'OPTIMIZE', + 'OPTION', + 'OR', + 'ORDER', + 'ORDINALITY', + 'ORGANIZE', + 'OUT', + 'OUTER', + 'OVER', + 'OVERLAY', + 'OVERRIDING', + 'PACKAGE', + 'PADDED', + 'PAGE', + 'PAGESIZE', + 'PARAMETER', + 'PART', + 'PARTITION', + 'PARTITIONED', + 'PARTITIONING', + 'PARTITIONS', + 'PASSING', + 'PASSWORD', + 'PATH', + 'PCTFREE', + 'PERCENT_RANK', + 'PERCENTILE_CONT', + 'PERCENTILE_DISC', + 'PERIOD', + 'PERMISSION', + 'PIECESIZE', + 'PIPE', + 'PLAN', + 'POSITION', + 'PREPARE', + 'PREVVAL', + 'PRIMARY', + 'PRIOR', + 'PRIQTY', + 'PRIVILEGES', + 'PROCEDURE', + 'PROGRAM', + 'PROGRAMID', + 'QUERY', + 'RANGE', + 'RANK', + 'RATIO_TO_REPORT', + 'RCDFMT', + 'READ', + 'READS', + 'RECOVERY', + 'REFERENCES', + 'REFERENCING', + 'REFRESH', + 'REGEXP_LIKE', + 'RELEASE', + 'RENAME', + 'REPEAT', + 'RESET', + 'RESIGNAL', + 'RESTART', + 'RESULT', + 'RESULT_SET_LOCATOR', + 'RETURN', + 'RETURNING', + 'RETURNS', + 'REVOKE', + 'RID', + 'RIGHT', + 'ROLLBACK', + 'ROLLUP', + 'ROUTINE', + 'ROW', + 'ROW_NUMBER', + 'ROWNUMBER', + 'ROWS', + 'RRN', + 'RUN', + 'SAVEPOINT', + 'SBCS', + 'SCALAR', + 'SCHEMA', + 'SCRATCHPAD', + 'SCROLL', + 'SEARCH', + 'SECOND', + 'SECONDS', + 'SECQTY', + 'SECURED', + 'SELECT', + 'SENSITIVE', + 'SEQUENCE', + 'SESSION', + 'SESSION_USER', + 'SET', + 'SIGNAL', + 'SIMPLE', + 'SKIP', + 'SNAN', + 'SOME', + 'SOURCE', + 'SPECIFIC', + 'SQL', + 'SQLID', + 'SQLIND_DEFAULT', + 'SQLIND_UNASSIGNED', + 'STACKED', + 'START', + 'STARTING', + 'STATEMENT', + 'STATIC', + 'STOGROUP', + 'SUBSTRING', + 'SUMMARY', + 'SYNONYM', + 'SYSTEM_TIME', + 'SYSTEM_USER', + 'TABLE', + 'TABLESPACE', + 'TABLESPACES', + 'TAG', + 'THEN', + 'THREADSAFE', + 'TIME', + 'TIMESTAMP', + 'TO', + 'TRANSACTION', + 'TRANSFER', + 'TRIGGER', + 'TRIM', + 'TRIM_ARRAY', + 'TRUE', + 'TRUNCATE', + 'TRY_CAST', + 'TYPE', + 'UNDO', + 'UNION', + 'UNIQUE', + 'UNIT', + 'UNKNOWN', + 'UNNEST', + 'UNTIL', + 'UPDATE', + 'UPDATING', + 'URI', + 'USAGE', + 'USE', + 'USER', + 'USERID', + 'USING', + 'VALUE', + 'VALUES', + 'VARIABLE', + 'VARIANT', + 'VCAT', + 'VERSION', + 'VERSIONING', + 'VIEW', + 'VOLATILE', + 'WAIT', + 'WHEN', + 'WHENEVER', + 'WHERE', + 'WHILE', + 'WITH', + 'WITHIN', + 'WITHOUT', + 'WRAPPED', + 'WRAPPER', + 'WRITE', + 'WRKSTNNAME', + 'XMLAGG', + 'XMLATTRIBUTES', + 'XMLCAST', + 'XMLCOMMENT', + 'XMLCONCAT', + 'XMLDOCUMENT', + 'XMLELEMENT', + 'XMLFOREST', + 'XMLGROUP', + 'XMLNAMESPACES', + 'XMLPARSE', + 'XMLPI', + 'XMLROW', + 'XMLSERIALIZE', + 'XMLTABLE', + 'XMLTEXT', + 'XMLVALIDATE', + 'XSLTRANSFORM', + 'XSROBJECT', + 'YEAR', + 'YEARS', + 'YES', + 'ZONE', + ], +}); diff --git a/src/sqlFormatter.ts b/src/sqlFormatter.ts index a58924b2c..dc5e2c97f 100644 --- a/src/sqlFormatter.ts +++ b/src/sqlFormatter.ts @@ -8,6 +8,7 @@ import { ConfigError, validateConfig } from './validateConfig.js'; const dialectNameMap: Record = { bigquery: 'bigquery', db2: 'db2', + db2i: 'db2i', hive: 'hive', mariadb: 'mariadb', mysql: 'mysql', diff --git a/static/index.html b/static/index.html index 1e6cab550..9e2a3af4a 100644 --- a/static/index.html +++ b/static/index.html @@ -60,6 +60,7 @@

Options

+ diff --git a/test/db2i.test.ts b/test/db2i.test.ts new file mode 100644 index 000000000..7d713b9e3 --- /dev/null +++ b/test/db2i.test.ts @@ -0,0 +1,140 @@ +import dedent from 'dedent-js'; + +import { format as originalFormat, FormatFn } from '../src/sqlFormatter.js'; +import behavesLikeSqlFormatter from './behavesLikeSqlFormatter.js'; + +import supportsAlterTable from './features/alterTable.js'; +import supportsBetween from './features/between.js'; +import supportsCreateTable from './features/createTable.js'; +import supportsDropTable from './features/dropTable.js'; +import supportsJoin from './features/join.js'; +import supportsOperators from './features/operators.js'; +import supportsSchema from './features/schema.js'; +import supportsStrings from './features/strings.js'; +import supportsConstraints from './features/constraints.js'; +import supportsDeleteFrom from './features/deleteFrom.js'; +import supportsComments from './features/comments.js'; +import supportsCommentOn from './features/commentOn.js'; +import supportsIdentifiers from './features/identifiers.js'; +import supportsParams from './options/param.js'; +import supportsSetOperations from './features/setOperations.js'; +import supportsLimiting from './features/limiting.js'; +import supportsInsertInto from './features/insertInto.js'; +import supportsUpdate from './features/update.js'; +import supportsTruncateTable from './features/truncateTable.js'; +import supportsMergeInto from './features/mergeInto.js'; +import supportsCreateView from './features/createView.js'; + +describe('Db2iFormatter', () => { + const language = 'db2i'; + const format: FormatFn = (query, cfg = {}) => originalFormat(query, { ...cfg, language }); + + behavesLikeSqlFormatter(format); + supportsComments(format); + supportsCommentOn(format); + supportsCreateView(format, { orReplace: true }); + supportsCreateTable(format, { orReplace: true }); + supportsDropTable(format, { ifExists: true }); + supportsConstraints(format, ['NO ACTION', 'RESTRICT', 'CASCADE', 'SET NULL']); + supportsAlterTable(format, { + addColumn: true, + dropColumn: true, + renameColumn: true, + }); + supportsDeleteFrom(format); + supportsInsertInto(format); + supportsUpdate(format, { whereCurrentOf: true }); + supportsTruncateTable(format, { withoutTable: true }); + supportsMergeInto(format); + supportsStrings(format, ["''-qq", "X''", "U&''", "N''"]); + supportsIdentifiers(format, [`""-qq`]); + supportsBetween(format); + supportsSchema(format); + supportsOperators(format, ['**', '¬=', '¬>', '¬<', '!>', '!<', '||']); + supportsJoin(format, { without: ['NATURAL'], supportsUsing: true }); + supportsSetOperations(format, [ + 'UNION', + 'UNION ALL', + 'EXCEPT', + 'EXCEPT ALL', + 'INTERSECT', + 'INTERSECT ALL', + ]); + supportsParams(format, { positional: true, named: [':'] }); + supportsLimiting(format, { fetchFirst: true }); + + it('formats only -- as a line comment', () => { + const result = format(` + SELECT col FROM + -- This is a comment + MyTable; + `); + expect(result).toBe(dedent` + SELECT + col + FROM + -- This is a comment + MyTable; + `); + }); + + // DB2-specific string types + it('supports strings with G, GX, BX, UX prefixes', () => { + expect(format(`SELECT G'blah blah', GX'01AC', BX'0101', UX'CCF239' FROM foo`)).toBe(dedent` + SELECT + G'blah blah', + GX'01AC', + BX'0101', + UX'CCF239' + FROM + foo + `); + }); + + it('supports @, #, $ characters at the start of identifiers', () => { + expect(format(`SELECT @foo, #bar, $zap`)).toBe(dedent` + SELECT + @foo, + #bar, + $zap + `); + }); + + it('supports @, #, $ characters in named parameters', () => { + expect(format(`SELECT :foo@bar, :foo#bar, :foo$bar, :@zip, :#zap, :$zop`)).toBe(dedent` + SELECT + :foo@bar, + :foo#bar, + :foo$bar, + :@zip, + :#zap, + :$zop + `); + }); + + it('supports WITH isolation level modifiers for UPDATE statement', () => { + expect(format('UPDATE foo SET x = 10 WITH CS')).toBe(dedent` + UPDATE foo + SET + x = 10 + WITH CS + `); + }); + + it('formats ALTER TABLE ... ALTER COLUMN', () => { + expect( + format( + `ALTER TABLE t ALTER COLUMN foo SET DATA TYPE VARCHAR; + ALTER TABLE t ALTER COLUMN foo SET NOT NULL;` + ) + ).toBe(dedent` + ALTER TABLE t + ALTER COLUMN foo + SET DATA TYPE VARCHAR; + + ALTER TABLE t + ALTER COLUMN foo + SET NOT NULL; + `); + }); +});