diff --git a/src/formatter/ExpressionFormatter.ts b/src/formatter/ExpressionFormatter.ts index 610d38b6c..93874c17b 100644 --- a/src/formatter/ExpressionFormatter.ts +++ b/src/formatter/ExpressionFormatter.ts @@ -36,7 +36,7 @@ import InlineLayout, { InlineLayoutError } from './InlineLayout.js'; interface ExpressionFormatterParams { cfg: FormatOptions; - dialectCfg: DialectFormatOptions; + dialectCfg: ProcessedDialectFormatOptions; params: Params; layout: Layout; inline?: boolean; @@ -45,12 +45,21 @@ interface ExpressionFormatterParams { export interface DialectFormatOptions { // List of operators that should always be formatted without surrounding spaces alwaysDenseOperators?: string[]; + // List of clauses that should be formatted on a single line + onelineClauses: string[]; +} + +// Contains the same data as DialectFormatOptions, +// but optimized for faster and more conventient lookup. +export interface ProcessedDialectFormatOptions { + alwaysDenseOperators: string[]; + onelineClauses: Record; } /** Formats a generic SQL expression */ export default class ExpressionFormatter { private cfg: FormatOptions; - private dialectCfg: DialectFormatOptions; + private dialectCfg: ProcessedDialectFormatOptions; private params: Params; private layout: Layout; @@ -207,18 +216,36 @@ export default class ExpressionFormatter { } private formatClause(node: ClauseNode) { - if (isTabularStyle(this.cfg)) { - this.layout.add(WS.NEWLINE, WS.INDENT, this.showKw(node.nameKw), WS.SPACE); + if (this.isOnelineClause(node)) { + this.formatClauseInOnelineStyle(node); + } else if (isTabularStyle(this.cfg)) { + this.formatClauseInTabularStyle(node); } else { - this.layout.add(WS.NEWLINE, WS.INDENT, this.showKw(node.nameKw), WS.NEWLINE); + this.formatClauseInIndentedStyle(node); } + } + + private isOnelineClause(node: ClauseNode): boolean { + return this.dialectCfg.onelineClauses[node.nameKw.text]; + } + + private formatClauseInIndentedStyle(node: ClauseNode) { + this.layout.add(WS.NEWLINE, WS.INDENT, this.showKw(node.nameKw), WS.NEWLINE); this.layout.indentation.increaseTopLevel(); + this.layout.add(WS.INDENT); + this.layout = this.formatSubExpression(node.children); + this.layout.indentation.decreaseTopLevel(); + } - if (!isTabularStyle(this.cfg)) { - this.layout.add(WS.INDENT); - } + private formatClauseInOnelineStyle(node: ClauseNode) { + this.layout.add(WS.NEWLINE, WS.INDENT, this.showKw(node.nameKw), WS.SPACE); this.layout = this.formatSubExpression(node.children); + } + private formatClauseInTabularStyle(node: ClauseNode) { + this.layout.add(WS.NEWLINE, WS.INDENT, this.showKw(node.nameKw), WS.SPACE); + this.layout.indentation.increaseTopLevel(); + this.layout = this.formatSubExpression(node.children); this.layout.indentation.decreaseTopLevel(); } @@ -267,7 +294,7 @@ export default class ExpressionFormatter { } private formatOperator({ text }: OperatorNode) { - if (this.cfg.denseOperators || this.dialectCfg.alwaysDenseOperators?.includes(text)) { + if (this.cfg.denseOperators || this.dialectCfg.alwaysDenseOperators.includes(text)) { this.layout.add(WS.NO_SPACE, text); } else if (text === ':') { this.layout.add(WS.NO_SPACE, text, WS.SPACE); diff --git a/src/formatter/Formatter.ts b/src/formatter/Formatter.ts index 0c24a66ca..fd319d11f 100644 --- a/src/formatter/Formatter.ts +++ b/src/formatter/Formatter.ts @@ -5,10 +5,14 @@ import Tokenizer from '../lexer/Tokenizer.js'; import { createParser } from '../parser/createParser.js'; import { StatementNode } from '../parser/ast.js'; +import { cacheInClassField } from '../utils.js'; import formatCommaPositions from './formatCommaPositions.js'; import formatAliasPositions from './formatAliasPositions.js'; -import ExpressionFormatter, { DialectFormatOptions } from './ExpressionFormatter.js'; +import ExpressionFormatter, { + DialectFormatOptions, + ProcessedDialectFormatOptions, +} from './ExpressionFormatter.js'; import Layout, { WS } from './Layout.js'; import Indentation from './Indentation.js'; @@ -33,18 +37,24 @@ export default class Formatter { // So we wouldn't need to recreate the tokenizer, which is kinda expensive, // for each call to format() function. private cachedTokenizer(): Tokenizer { - const cls: Function & { cachedTokenizer?: Tokenizer } = this.constructor; - if (!cls.cachedTokenizer) { - cls.cachedTokenizer = this.tokenizer(); - } - return cls.cachedTokenizer; + return cacheInClassField(this.constructor, 'cachedTokenizer', () => this.tokenizer()); } /** - * Dialect-specific formatting configuration, optionally provided by subclass. + * Dialect-specific formatting configuration, provided by subclass. */ protected formatOptions(): DialectFormatOptions { - return {}; + throw new Error('formatOptions() not implemented by sybclass'); + } + + private cachedFormatOptions(): ProcessedDialectFormatOptions { + return cacheInClassField(this.constructor, 'cachedFormatOptions', () => { + const opts = this.formatOptions(); + return { + alwaysDenseOperators: opts.alwaysDenseOperators || [], + onelineClauses: Object.fromEntries(opts.onelineClauses.map(name => [name, true])), + }; + }); } /** @@ -73,7 +83,7 @@ export default class Formatter { private formatStatement(statement: StatementNode): string { const layout = new ExpressionFormatter({ cfg: this.cfg, - dialectCfg: this.formatOptions(), + dialectCfg: this.cachedFormatOptions(), params: this.params, layout: new Layout(new Indentation(indentString(this.cfg))), }).format(statement.children); diff --git a/src/languages/bigquery/bigquery.formatter.ts b/src/languages/bigquery/bigquery.formatter.ts index 5aa93c360..9c7b13e45 100644 --- a/src/languages/bigquery/bigquery.formatter.ts +++ b/src/languages/bigquery/bigquery.formatter.ts @@ -1,4 +1,5 @@ import Formatter from '../../formatter/Formatter.js'; +import { DialectFormatOptions } from '../../formatter/ExpressionFormatter.js'; import Tokenizer from '../../lexer/Tokenizer.js'; import { EOF_TOKEN, isToken, TokenType, Token } from '../../lexer/token.js'; import { expandPhrases } from '../../expandPhrases.js'; @@ -26,12 +27,7 @@ const reservedClauses = expandPhrases([ 'INSERT [INTO]', 'VALUES', // - update: - 'UPDATE', 'SET', - // - delete: - 'DELETE [FROM]', - // - truncate: - 'TRUNCATE TABLE', // - merge: 'MERGE [INTO]', 'WHEN [NOT] MATCHED [BY SOURCE | BY TARGET] [THEN]', @@ -39,6 +35,20 @@ const reservedClauses = expandPhrases([ // Data definition, https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language 'CREATE [OR REPLACE] [MATERIALIZED] VIEW [IF NOT EXISTS]', 'CREATE [OR REPLACE] [TEMP|TEMPORARY|SNAPSHOT|EXTERNAL] TABLE [IF NOT EXISTS]', + + 'CLUSTER BY', + 'FOR SYSTEM_TIME AS OF', // CREATE SNAPSHOT TABLE + 'WITH CONNECTION', + 'WITH PARTITION COLUMNS', + 'REMOTE WITH CONNECTION', +]); + +const onelineClauses = expandPhrases([ + // - update: + 'UPDATE', + // - delete: + 'DELETE [FROM]', + // - drop table: 'DROP [SNAPSHOT | EXTERNAL] TABLE [IF EXISTS]', // - alter table: 'ALTER TABLE [IF EXISTS]', @@ -50,29 +60,35 @@ const reservedClauses = expandPhrases([ 'SET OPTIONS', // for alter column 'DROP NOT NULL', // for alter column 'SET DATA TYPE', // for alter column - + // - alter schema + 'ALTER SCHEMA [IF EXISTS]', + // - alter view + 'ALTER [MATERIALIZED] VIEW [IF EXISTS]', + // - alter bi_capacity + 'ALTER BI_CAPACITY', + // - truncate: + 'TRUNCATE TABLE', + // - create schema 'CREATE SCHEMA [IF NOT EXISTS]', 'DEFAULT COLLATE', - 'CLUSTER BY', - 'FOR SYSTEM_TIME AS OF', // CREATE SNAPSHOT TABLE - 'WITH CONNECTION', - 'WITH PARTITION COLUMNS', + + // stored procedures 'CREATE [OR REPLACE] [TEMP|TEMPORARY|TABLE] FUNCTION [IF NOT EXISTS]', - 'REMOTE WITH CONNECTION', - 'RETURNS TABLE', 'CREATE [OR REPLACE] PROCEDURE [IF NOT EXISTS]', + // row access policy 'CREATE [OR REPLACE] ROW ACCESS POLICY [IF NOT EXISTS]', 'GRANT TO', 'FILTER USING', + // capacity 'CREATE CAPACITY', 'AS JSON', + // reservation 'CREATE RESERVATION', + // assignment 'CREATE ASSIGNMENT', + // search index 'CREATE SEARCH INDEX [IF NOT EXISTS]', - 'ALTER SCHEMA [IF EXISTS]', - - 'ALTER [MATERIALIZED] VIEW [IF EXISTS]', - 'ALTER BI_CAPACITY', + // drop 'DROP SCHEMA [IF EXISTS]', 'DROP [MATERIALIZED] VIEW [IF EXISTS]', 'DROP [TABLE] FUNCTION [IF EXISTS]', @@ -143,8 +159,8 @@ export default class BigQueryFormatter extends Formatter { // TODO: handle trailing comma in select clause tokenizer() { return new Tokenizer({ - reservedClauses, reservedSelect, + reservedClauses: [...reservedClauses, ...onelineClauses], reservedSetOperations, reservedJoins, reservedPhrases, @@ -169,6 +185,12 @@ export default class BigQueryFormatter extends Formatter { postProcess, }); } + + formatOptions(): DialectFormatOptions { + return { + onelineClauses, + }; + } } function postProcess(tokens: Token[]): Token[] { diff --git a/src/languages/db2/db2.formatter.ts b/src/languages/db2/db2.formatter.ts index 4a6480c74..7fdef5c68 100644 --- a/src/languages/db2/db2.formatter.ts +++ b/src/languages/db2/db2.formatter.ts @@ -1,5 +1,6 @@ import { expandPhrases } from '../../expandPhrases.js'; import Formatter from '../../formatter/Formatter.js'; +import { DialectFormatOptions } from '../../formatter/ExpressionFormatter.js'; import Tokenizer from '../../lexer/Tokenizer.js'; import { functions } from './db2.functions.js'; import { keywords } from './db2.keywords.js'; @@ -21,14 +22,7 @@ const reservedClauses = expandPhrases([ 'INSERT INTO', 'VALUES', // - update: - 'UPDATE', 'SET', - 'WHERE CURRENT OF', - 'WITH {RR | RS | CS | UR}', - // - delete: - 'DELETE FROM', - // - truncate: - 'TRUNCATE [TABLE]', // - merge: 'MERGE INTO', 'WHEN [NOT] MATCHED [THEN]', @@ -37,6 +31,16 @@ const reservedClauses = expandPhrases([ // Data definition 'CREATE [OR REPLACE] VIEW', 'CREATE [GLOBAL TEMPORARY] TABLE', +]); + +const onelineClauses = expandPhrases([ + // - update: + 'UPDATE', + 'WHERE CURRENT OF', + 'WITH {RR | RS | CS | UR}', + // - delete: + 'DELETE FROM', + // - drop table: 'DROP TABLE [HIERARCHY]', // alter table: 'ALTER TABLE', @@ -47,7 +51,12 @@ const reservedClauses = expandPhrases([ 'SET DATA TYPE', // for alter column 'SET NOT NULL', // for alter column 'DROP {IDENTITY | EXPRESSION | DEFAULT | NOT NULL}', // for alter column - + // - truncate: + 'TRUNCATE [TABLE]', + // other + 'SET [CURRENT] SCHEMA', + 'AFTER', + 'GO', // https://www.ibm.com/docs/en/db2-for-zos/11?topic=statements-list-supported 'ALLOCATE CURSOR', 'ALTER DATABASE', @@ -147,15 +156,10 @@ const reservedClauses = expandPhrases([ 'SET CURRENT TEMPORAL SYSTEM_TIME', 'SET ENCRYPTION PASSWORD', 'SET PATH', - 'SET SCHEMA', 'SET SESSION TIME ZONE', 'SIGNAL', 'VALUES INTO', 'WHENEVER', - // other - 'AFTER', - 'GO', - 'SET CURRENT SCHEMA', ]); const reservedSetOperations = expandPhrases(['UNION [ALL]', 'EXCEPT [ALL]', 'INTERSECT [ALL]']); @@ -177,8 +181,8 @@ const reservedPhrases = expandPhrases([ export default class Db2Formatter extends Formatter { tokenizer() { return new Tokenizer({ - reservedClauses, reservedSelect, + reservedClauses: [...reservedClauses, ...onelineClauses], reservedSetOperations, reservedJoins, reservedPhrases, @@ -194,4 +198,10 @@ export default class Db2Formatter extends Formatter { operators: ['**', '¬=', '¬>', '¬<', '!>', '!<', '||'], }); } + + formatOptions(): DialectFormatOptions { + return { + onelineClauses, + }; + } } diff --git a/src/languages/hive/hive.formatter.ts b/src/languages/hive/hive.formatter.ts index 44e247fa9..997c829a6 100644 --- a/src/languages/hive/hive.formatter.ts +++ b/src/languages/hive/hive.formatter.ts @@ -1,5 +1,6 @@ import { expandPhrases } from '../../expandPhrases.js'; import Formatter from '../../formatter/Formatter.js'; +import { DialectFormatOptions } from '../../formatter/ExpressionFormatter.js'; import Tokenizer from '../../lexer/Tokenizer.js'; import { functions } from './hive.functions.js'; import { keywords } from './hive.keywords.js'; @@ -27,12 +28,7 @@ const reservedClauses = expandPhrases([ 'INSERT INTO [TABLE]', 'VALUES', // - update: - 'UPDATE', 'SET', - // - delete: - 'DELETE FROM', - // - truncate: - 'TRUNCATE [TABLE]', // - merge: 'MERGE INTO', 'WHEN [NOT] MATCHED [THEN]', @@ -48,11 +44,20 @@ const reservedClauses = expandPhrases([ // Data definition 'CREATE [MATERIALIZED] VIEW [IF NOT EXISTS]', 'CREATE [TEMPORARY] [EXTERNAL] TABLE [IF NOT EXISTS]', +]); + +const onelineClauses = expandPhrases([ + // - update: + 'UPDATE', + // - delete: + 'DELETE FROM', + // - drop table: 'DROP TABLE [IF EXISTS]', // - alter table: 'ALTER TABLE', 'RENAME TO', - + // - truncate: + 'TRUNCATE [TABLE]', // other 'ALTER', 'CREATE', @@ -60,9 +65,7 @@ const reservedClauses = expandPhrases([ 'DESCRIBE', 'DROP', 'FETCH', - 'SET SCHEMA', // added 'SHOW', - // newline keywords 'STORED AS', 'STORED BY', 'ROW FORMAT', @@ -84,8 +87,8 @@ const reservedPhrases = expandPhrases(['{ROWS | RANGE} BETWEEN']); export default class HiveFormatter extends Formatter { tokenizer() { return new Tokenizer({ - reservedClauses, reservedSelect, + reservedClauses: [...reservedClauses, ...onelineClauses], reservedSetOperations, reservedJoins, reservedPhrases, @@ -98,4 +101,10 @@ export default class HiveFormatter extends Formatter { operators: ['%', '~', '^', '|', '&', '<=>', '==', '!', '||'], }); } + + formatOptions(): DialectFormatOptions { + return { + onelineClauses, + }; + } } diff --git a/src/languages/mariadb/mariadb.formatter.ts b/src/languages/mariadb/mariadb.formatter.ts index 0de9e7d7e..89ceac0dd 100644 --- a/src/languages/mariadb/mariadb.formatter.ts +++ b/src/languages/mariadb/mariadb.formatter.ts @@ -1,5 +1,6 @@ import { expandPhrases } from '../../expandPhrases.js'; import Formatter from '../../formatter/Formatter.js'; +import { DialectFormatOptions } from '../../formatter/ExpressionFormatter.js'; import Tokenizer from '../../lexer/Tokenizer.js'; import { EOF_TOKEN, isToken, Token, TokenType } from '../../lexer/token.js'; import { keywords } from './mariadb.keywords.js'; @@ -25,15 +26,20 @@ const reservedClauses = expandPhrases([ 'REPLACE [LOW_PRIORITY | DELAYED] [INTO]', 'VALUES', // - update: - 'UPDATE [LOW_PRIORITY] [IGNORE]', 'SET', - // - delete: - 'DELETE [LOW_PRIORITY] [QUICK] [IGNORE] FROM', - // - truncate: - 'TRUNCATE [TABLE]', // Data definition 'CREATE [OR REPLACE] [SQL SECURITY DEFINER | SQL SECURITY INVOKER] VIEW [IF NOT EXISTS]', 'CREATE [OR REPLACE] [TEMPORARY] TABLE [IF NOT EXISTS]', + // other + 'RETURNING', +]); + +const onelineClauses = expandPhrases([ + // - update: + 'UPDATE [LOW_PRIORITY] [IGNORE]', + // - delete: + 'DELETE [LOW_PRIORITY] [QUICK] [IGNORE] FROM', + // - drop table: 'DROP [TEMPORARY] TABLE [IF EXISTS]', // - alter table: 'ALTER [ONLINE] [IGNORE] TABLE [IF EXISTS]', @@ -45,7 +51,8 @@ const reservedClauses = expandPhrases([ 'ALTER [COLUMN]', '{SET | DROP} DEFAULT', // for alter column 'SET {VISIBLE | INVISIBLE}', // for alter column - + // - truncate: + 'TRUNCATE [TABLE]', // https://mariadb.com/docs/reference/mdb/sql-statements/ 'ALTER DATABASE', 'ALTER DATABASE COMMENT', @@ -129,7 +136,6 @@ const reservedClauses = expandPhrases([ 'RESET REPLICA', 'RESET SLAVE', 'RESIGNAL', - 'RETURNING', 'REVOKE', 'ROLLBACK', 'SAVEPOINT', @@ -263,8 +269,8 @@ const reservedPhrases = expandPhrases([ export default class MariaDbFormatter extends Formatter { tokenizer() { return new Tokenizer({ - reservedClauses, reservedSelect, + reservedClauses: [...reservedClauses, ...onelineClauses], reservedSetOperations, reservedJoins, reservedPhrases, @@ -291,6 +297,12 @@ export default class MariaDbFormatter extends Formatter { postProcess, }); } + + formatOptions(): DialectFormatOptions { + return { + onelineClauses, + }; + } } function postProcess(tokens: Token[]) { diff --git a/src/languages/mysql/mysql.formatter.ts b/src/languages/mysql/mysql.formatter.ts index b9f6d833c..a21e102cd 100644 --- a/src/languages/mysql/mysql.formatter.ts +++ b/src/languages/mysql/mysql.formatter.ts @@ -1,5 +1,6 @@ import { expandPhrases } from '../../expandPhrases.js'; import Formatter from '../../formatter/Formatter.js'; +import { DialectFormatOptions } from '../../formatter/ExpressionFormatter.js'; import Tokenizer from '../../lexer/Tokenizer.js'; import { EOF_TOKEN, isToken, Token, TokenType } from '../../lexer/token.js'; import { keywords } from './mysql.keywords.js'; @@ -25,15 +26,18 @@ const reservedClauses = expandPhrases([ 'REPLACE [LOW_PRIORITY | DELAYED] [INTO]', 'VALUES', // - update: - 'UPDATE [LOW_PRIORITY] [IGNORE]', 'SET', - // - delete: - 'DELETE [LOW_PRIORITY] [QUICK] [IGNORE] FROM', - // - truncate: - 'TRUNCATE [TABLE]', // Data definition 'CREATE [OR REPLACE] [SQL SECURITY DEFINER | SQL SECURITY INVOKER] VIEW [IF NOT EXISTS]', 'CREATE [TEMPORARY] TABLE [IF NOT EXISTS]', +]); + +const onelineClauses = expandPhrases([ + // - update: + 'UPDATE [LOW_PRIORITY] [IGNORE]', + // - delete: + 'DELETE [LOW_PRIORITY] [QUICK] [IGNORE] FROM', + // - drop table: 'DROP [TEMPORARY] TABLE [IF EXISTS]', // - alter table: 'ALTER TABLE', @@ -44,7 +48,8 @@ const reservedClauses = expandPhrases([ 'RENAME COLUMN', 'ALTER [COLUMN]', '{SET | DROP} DEFAULT', // for alter column - + // - truncate: + 'TRUNCATE [TABLE]', // https://dev.mysql.com/doc/refman/8.0/en/sql-statements.html 'ALTER DATABASE', 'ALTER EVENT', @@ -231,8 +236,8 @@ const reservedPhrases = expandPhrases([ export default class MySqlFormatter extends Formatter { tokenizer() { return new Tokenizer({ - reservedClauses, reservedSelect, + reservedClauses: [...reservedClauses, ...onelineClauses], reservedSetOperations, reservedJoins, reservedPhrases, @@ -259,6 +264,12 @@ export default class MySqlFormatter extends Formatter { postProcess, }); } + + formatOptions(): DialectFormatOptions { + return { + onelineClauses, + }; + } } function postProcess(tokens: Token[]) { diff --git a/src/languages/n1ql/n1ql.formatter.ts b/src/languages/n1ql/n1ql.formatter.ts index 05a6a8fc2..7e7029ca9 100644 --- a/src/languages/n1ql/n1ql.formatter.ts +++ b/src/languages/n1ql/n1ql.formatter.ts @@ -1,5 +1,6 @@ import { expandPhrases } from '../../expandPhrases.js'; import Formatter from '../../formatter/Formatter.js'; +import { DialectFormatOptions } from '../../formatter/ExpressionFormatter.js'; import Tokenizer from '../../lexer/Tokenizer.js'; import { functions } from './n1ql.functions.js'; import { keywords } from './n1ql.keywords.js'; @@ -23,15 +24,25 @@ const reservedClauses = expandPhrases([ 'INSERT INTO', 'VALUES', // - update: - 'UPDATE', 'SET', - // - delete: - 'DELETE FROM', // - merge: 'MERGE INTO', 'WHEN [NOT] MATCHED THEN', 'UPDATE SET', 'INSERT', + // other + 'NEST', + 'UNNEST', + 'RETURNING', +]); + +const onelineClauses = expandPhrases([ + // - update: + 'UPDATE', + // - delete: + 'DELETE FROM', + // - set schema: + 'SET SCHEMA', // https://docs.couchbase.com/server/current/n1ql/n1ql-language-reference/reservedwords.html 'ADVISE', 'ALTER INDEX', @@ -54,7 +65,6 @@ const reservedClauses = expandPhrases([ 'GRANT', 'INFER', 'PREPARE', - 'RETURNING', 'REVOKE', 'ROLLBACK TRANSACTION', 'SAVEPOINT', @@ -63,12 +73,9 @@ const reservedClauses = expandPhrases([ 'UPSERT', // other 'LET', - 'NEST', 'SET CURRENT SCHEMA', - 'SET SCHEMA', 'SHOW', - 'UNNEST', - 'USE KEYS', + 'USE [PRIMARY] KEYS', ]); const reservedSetOperations = expandPhrases(['UNION [ALL]', 'EXCEPT [ALL]', 'INTERSECT [ALL]']); @@ -81,8 +88,8 @@ const reservedPhrases = expandPhrases(['{ROWS | RANGE | GROUPS} BETWEEN']); export default class N1qlFormatter extends Formatter { tokenizer() { return new Tokenizer({ - reservedClauses, reservedSelect, + reservedClauses: [...reservedClauses, ...onelineClauses], reservedSetOperations, reservedJoins, reservedPhrases, @@ -100,4 +107,10 @@ export default class N1qlFormatter extends Formatter { operators: ['%', '==', ':', '||'], }); } + + formatOptions(): DialectFormatOptions { + return { + onelineClauses, + }; + } } diff --git a/src/languages/plsql/plsql.formatter.ts b/src/languages/plsql/plsql.formatter.ts index 2e8b5b246..9879c2dd6 100644 --- a/src/languages/plsql/plsql.formatter.ts +++ b/src/languages/plsql/plsql.formatter.ts @@ -25,12 +25,7 @@ const reservedClauses = expandPhrases([ 'INSERT [INTO | ALL INTO]', 'VALUES', // - update: - 'UPDATE [ONLY]', 'SET', - // - delete: - 'DELETE FROM [ONLY]', - // - truncate: - 'TRUNCATE TABLE', // - merge: 'MERGE [INTO]', 'WHEN [NOT] MATCHED [THEN]', @@ -39,6 +34,16 @@ const reservedClauses = expandPhrases([ 'CREATE [OR REPLACE] [NO FORCE | FORCE] [EDITIONING | EDITIONABLE | EDITIONABLE EDITIONING | NONEDITIONABLE] VIEW', 'CREATE MATERIALIZED VIEW', 'CREATE [GLOBAL TEMPORARY | PRIVATE TEMPORARY | SHARDED | DUPLICATED | IMMUTABLE BLOCKCHAIN | BLOCKCHAIN | IMMUTABLE] TABLE', + // other + 'RETURNING', +]); + +const onelineClauses = expandPhrases([ + // - update: + 'UPDATE [ONLY]', + // - delete: + 'DELETE FROM [ONLY]', + // - drop table: 'DROP TABLE', // - alter table: 'ALTER TABLE', @@ -47,17 +52,17 @@ const reservedClauses = expandPhrases([ 'MODIFY', 'RENAME TO', 'RENAME COLUMN', - + // - truncate: + 'TRUNCATE TABLE', // other + 'SET SCHEMA', 'BEGIN', 'CONNECT BY', 'DECLARE', 'EXCEPT', 'EXCEPTION', 'LOOP', - 'RETURNING', 'START WITH', - 'SET SCHEMA', ]); const reservedSetOperations = expandPhrases(['UNION [ALL]', 'EXCEPT', 'INTERSECT']); @@ -81,8 +86,8 @@ const reservedPhrases = expandPhrases([ export default class PlSqlFormatter extends Formatter { tokenizer() { return new Tokenizer({ - reservedClauses, reservedSelect, + reservedClauses: [...reservedClauses, ...onelineClauses], reservedSetOperations, reservedJoins, reservedPhrases, @@ -119,7 +124,10 @@ export default class PlSqlFormatter extends Formatter { } formatOptions(): DialectFormatOptions { - return { alwaysDenseOperators: ['@'] }; + return { + alwaysDenseOperators: ['@'], + onelineClauses, + }; } } diff --git a/src/languages/postgresql/postgresql.formatter.ts b/src/languages/postgresql/postgresql.formatter.ts index 7c3d8a6cb..5ad88bd08 100644 --- a/src/languages/postgresql/postgresql.formatter.ts +++ b/src/languages/postgresql/postgresql.formatter.ts @@ -25,17 +25,22 @@ const reservedClauses = expandPhrases([ 'INSERT INTO', 'VALUES', // - update: - 'UPDATE [ONLY]', 'SET', - 'WHERE CURRENT OF', - // - delete: - 'DELETE FROM [ONLY]', - // - truncate: - 'TRUNCATE [TABLE] [ONLY]', // Data definition 'CREATE [OR REPLACE] [TEMP | TEMPORARY] [RECURSIVE] VIEW', 'CREATE MATERIALIZED VIEW [IF NOT EXISTS]', 'CREATE [GLOBAL | LOCAL] [TEMPORARY | TEMP | UNLOGGED] TABLE [IF NOT EXISTS]', + // other + 'RETURNING', +]); + +const onelineClauses = expandPhrases([ + // - update: + 'UPDATE [ONLY]', + 'WHERE CURRENT OF', + // - delete: + 'DELETE FROM [ONLY]', + // - drop table: 'DROP TABLE [IF EXISTS]', // - alter table: 'ALTER TABLE [IF EXISTS] [ONLY]', @@ -48,7 +53,11 @@ const reservedClauses = expandPhrases([ '[SET DATA] TYPE', // for alter column '{SET | DROP} DEFAULT', // for alter column '{SET | DROP} NOT NULL', // for alter column - + // - truncate: + 'TRUNCATE [TABLE] [ONLY]', + // other + 'SET SCHEMA', + 'AFTER', // https://www.postgresql.org/docs/14/sql-commands.html 'ABORT', 'ALTER AGGREGATE', @@ -203,7 +212,6 @@ const reservedClauses = expandPhrases([ 'REINDEX', 'RELEASE SAVEPOINT', 'RESET', - 'RETURNING', 'REVOKE', 'ROLLBACK', 'ROLLBACK PREPARED', @@ -219,9 +227,6 @@ const reservedClauses = expandPhrases([ 'START TRANSACTION', 'UNLISTEN', 'VACUUM', - // other - 'AFTER', - 'SET SCHEMA', ]); const reservedSetOperations = expandPhrases([ @@ -249,8 +254,8 @@ const reservedPhrases = expandPhrases([ export default class PostgreSqlFormatter extends Formatter { tokenizer() { return new Tokenizer({ - reservedClauses, reservedSelect, + reservedClauses: [...reservedClauses, ...onelineClauses], reservedSetOperations, reservedJoins, reservedPhrases, @@ -357,6 +362,9 @@ export default class PostgreSqlFormatter extends Formatter { } formatOptions(): DialectFormatOptions { - return { alwaysDenseOperators: ['::'] }; + return { + alwaysDenseOperators: ['::'], + onelineClauses, + }; } } diff --git a/src/languages/redshift/redshift.formatter.ts b/src/languages/redshift/redshift.formatter.ts index 21e29b49a..8d14294bc 100644 --- a/src/languages/redshift/redshift.formatter.ts +++ b/src/languages/redshift/redshift.formatter.ts @@ -23,15 +23,18 @@ const reservedClauses = expandPhrases([ 'INSERT INTO', 'VALUES', // - update: - 'UPDATE', 'SET', - // - delete: - 'DELETE [FROM]', - // - truncate: - 'TRUNCATE [TABLE]', // Data definition 'CREATE [OR REPLACE | MATERIALIZED] VIEW', 'CREATE [TEMPORARY | TEMP | LOCAL TEMPORARY | LOCAL TEMP] TABLE [IF NOT EXISTS]', +]); + +const onelineClauses = expandPhrases([ + // - update: + 'UPDATE', + // - delete: + 'DELETE [FROM]', + // - drop table: 'DROP TABLE [IF EXISTS]', // - alter table: 'ALTER TABLE', @@ -43,7 +46,8 @@ const reservedClauses = expandPhrases([ 'ALTER COLUMN', 'TYPE', // for alter column 'ENCODE', // for alter column - + // - truncate: + 'TRUNCATE [TABLE]', // https://docs.aws.amazon.com/redshift/latest/dg/c_SQL_commands.html 'ABORT', 'ALTER DATABASE', @@ -115,8 +119,6 @@ const reservedClauses = expandPhrases([ 'START TRANSACTION', 'UNLOAD', 'VACUUM', - // other - 'ALTER COLUMN', ]); const reservedSetOperations = expandPhrases(['UNION [ALL]', 'EXCEPT', 'INTERSECT', 'MINUS']); @@ -143,8 +145,8 @@ const reservedPhrases = expandPhrases([ export default class RedshiftFormatter extends Formatter { tokenizer() { return new Tokenizer({ - reservedClauses, reservedSelect, + reservedClauses: [...reservedClauses, ...onelineClauses], reservedSetOperations, reservedJoins, reservedPhrases, @@ -173,6 +175,9 @@ export default class RedshiftFormatter extends Formatter { } formatOptions(): DialectFormatOptions { - return { alwaysDenseOperators: ['::'] }; + return { + alwaysDenseOperators: ['::'], + onelineClauses, + }; } } diff --git a/src/languages/singlestoredb/singlestoredb.formatter.ts b/src/languages/singlestoredb/singlestoredb.formatter.ts index 173893a55..1c4bd3107 100644 --- a/src/languages/singlestoredb/singlestoredb.formatter.ts +++ b/src/languages/singlestoredb/singlestoredb.formatter.ts @@ -1,5 +1,6 @@ import { expandPhrases } from '../../expandPhrases.js'; import Formatter from '../../formatter/Formatter.js'; +import { DialectFormatOptions } from '../../formatter/ExpressionFormatter.js'; import Tokenizer from '../../lexer/Tokenizer.js'; import { EOF_TOKEN, isToken, Token, TokenType } from '../../lexer/token.js'; import { keywords } from './singlestoredb.keywords.js'; @@ -24,17 +25,20 @@ const reservedClauses = expandPhrases([ 'VALUES', 'REPLACE [INTO]', // - update: - 'UPDATE', 'SET', - // - delete: - 'DELETE [FROM]', - // - truncate: - 'TRUNCATE [TABLE]', // Data definition 'CREATE VIEW', 'CREATE [ROWSTORE] [REFERENCE | TEMPORARY | GLOBAL TEMPORARY] TABLE [IF NOT EXISTS]', 'CREATE [OR REPLACE] [TEMPORARY] PROCEDURE [IF NOT EXISTS]', 'CREATE [OR REPLACE] [EXTERNAL] FUNCTION', +]); + +const onelineClauses = expandPhrases([ + // - update: + 'UPDATE', + // - delete: + 'DELETE [FROM]', + // - drop table: 'DROP [TEMPORARY] TABLE [IF EXISTS]', // - alter table: 'ALTER [ONLINE] TABLE', @@ -44,7 +48,8 @@ const reservedClauses = expandPhrases([ 'MODIFY [COLUMN]', 'CHANGE', 'RENAME [TO | AS]', - + // - truncate: + 'TRUNCATE [TABLE]', // https://docs.singlestore.com/managed-service/en/reference/sql-reference.html 'ADD AGGREGATOR', 'ADD LEAF', @@ -193,7 +198,6 @@ const reservedClauses = expandPhrases([ 'STOP REPLICATING', 'STOP SLAVE', 'TEST PIPELINE', - 'TRUNCATE TABLE', 'UNLOCK INSTANCE', 'UNLOCK TABLES', 'USE', @@ -233,8 +237,8 @@ const reservedPhrases = expandPhrases([ export default class SingleStoreDbFormatter extends Formatter { tokenizer() { return new Tokenizer({ - reservedClauses, reservedSelect, + reservedClauses: [...reservedClauses, ...onelineClauses], reservedSetOperations, reservedJoins, reservedPhrases, @@ -257,6 +261,12 @@ export default class SingleStoreDbFormatter extends Formatter { postProcess, }); } + + formatOptions(): DialectFormatOptions { + return { + onelineClauses, + }; + } } function postProcess(tokens: Token[]) { diff --git a/src/languages/snowflake/snowflake.formatter.ts b/src/languages/snowflake/snowflake.formatter.ts index a5e125170..b0dbaaf87 100644 --- a/src/languages/snowflake/snowflake.formatter.ts +++ b/src/languages/snowflake/snowflake.formatter.ts @@ -7,17 +7,6 @@ import { keywords } from './snowflake.keywords.js'; const reservedSelect = expandPhrases(['SELECT [ALL | DISTINCT]']); -// https://docs.snowflake.com/en/sql-reference/sql-all.html -// -// 1. run in console on this page: $x('//tbody/tr/*[1]//a/span/text()').map(x => x.nodeValue) -// 2. delete all lines that contain a sting like '(.*)', they are already covered in the list -// 3. delete all lines that contain a sting like '<.*>', they are already covered in the list -// 4. delete all lines that contain '…', they are part of a regex statement that can't be covered here -// 5. Manually add 'COPY INTO' -// -// Steps 1-4 can be combined by the following script in the developer console: -// $x('//tbody/tr/*[1]//a/span/text()').map(x => x.nodeValue) // Step 1 -// filter(x => !x.match(/\(.*\)/) && !x.match(/…/) && !x.match(/<.*>/)) // Step 2-4 const reservedClauses = expandPhrases([ // queries 'WITH [RECURSIVE]', @@ -36,12 +25,7 @@ const reservedClauses = expandPhrases([ '{THEN | ELSE} INTO', 'VALUES', // - update: - 'UPDATE', 'SET', - // - delete: - 'DELETE FROM', - // - truncate: - 'TRUNCATE [TABLE] [IF EXISTS]', // Data definition // - view 'CREATE [OR REPLACE] [SECURE] [RECURSIVE] VIEW [IF NOT EXISTS]', @@ -56,6 +40,14 @@ const reservedClauses = expandPhrases([ 'WHEN MATCHED [AND]', 'THEN {UPDATE SET | DELETE}', 'WHEN NOT MATCHED THEN INSERT', +]); + +const onelineClauses = expandPhrases([ + // - update: + 'UPDATE', + // - delete: + 'DELETE FROM', + // - drop table: 'DROP TABLE [IF EXISTS]', // - alter table: 'ALTER TABLE [IF EXISTS]', @@ -70,7 +62,7 @@ const reservedClauses = expandPhrases([ '{ADD | ALTER | MODIFY | DROP} [CONSTRAINT]', 'RENAME CONSTRAINT', '{ADD | DROP} SEARCH OPTIMIZATION', - '{SET | UNSET} [TAG]', + '{SET | UNSET} TAG', // Actually TAG is optional, but that conflicts with UPDATE..SET statement '{ADD | DROP} ROW ACCESS POLICY', 'DROP ALL ROW ACCESS POLICIES', '{SET | DROP} DEFAULT', // for alter column @@ -78,7 +70,8 @@ const reservedClauses = expandPhrases([ '[SET DATA] TYPE', // for alter column '[UNSET] COMMENT', // for alter column '{SET | UNSET} MASKING POLICY', // for alter column - + // - truncate: + 'TRUNCATE [TABLE] [IF EXISTS]', // other // https://docs.snowflake.com/en/sql-reference/sql-all.html // @@ -87,16 +80,7 @@ const reservedClauses = expandPhrases([ // 3. delete all lines that contain a sting like '<.*>', they are already covered in the list // 4. delete all lines that contain '…', they are part of a regex statement that can't be covered here // 5. Manually add 'COPY INTO' - // 6. Remove all lines that are already in the above definitions: - // - ALTER TABLE - // - COMMENT - // - CREATE TABLE - // - CREATE VIEW - // - DROP TABLE - // - TRUNCATE TABLE - // - SELECT - // - UPDATE - // - SET + // 6. Remove all lines that are already in `reservedClauses` // // Steps 1-4 can be combined by the following script in the developer console: // $x('//tbody/tr/*[1]//a/span/text()').map(x => x.nodeValue) // Step 1 @@ -315,8 +299,8 @@ const reservedPhrases = expandPhrases([ export default class SnowflakeFormatter extends Formatter { tokenizer() { return new Tokenizer({ - reservedClauses, reservedSelect, + reservedClauses: [...reservedClauses, ...onelineClauses], reservedSetOperations, reservedJoins, reservedPhrases, @@ -349,6 +333,9 @@ export default class SnowflakeFormatter extends Formatter { } formatOptions(): DialectFormatOptions { - return { alwaysDenseOperators: [':', '::'] }; + return { + alwaysDenseOperators: [':', '::'], + onelineClauses, + }; } } diff --git a/src/languages/spark/spark.formatter.ts b/src/languages/spark/spark.formatter.ts index 4daebf884..22734370c 100644 --- a/src/languages/spark/spark.formatter.ts +++ b/src/languages/spark/spark.formatter.ts @@ -1,5 +1,6 @@ import { expandPhrases } from '../../expandPhrases.js'; import Formatter from '../../formatter/Formatter.js'; +import { DialectFormatOptions } from '../../formatter/ExpressionFormatter.js'; import Tokenizer from '../../lexer/Tokenizer.js'; import { EOF_TOKEN, isToken, Token, TokenType } from '../../lexer/token.js'; import { keywords } from './spark.keywords.js'; @@ -26,8 +27,6 @@ const reservedClauses = expandPhrases([ // - insert: 'INSERT [INTO | OVERWRITE] [TABLE]', 'VALUES', - // - truncate: - 'TRUNCATE TABLE', // - insert overwrite directory: // https://spark.apache.org/docs/latest/sql-ref-syntax-dml-insert-overwrite-directory.html 'INSERT OVERWRITE [LOCAL] DIRECTORY', @@ -38,6 +37,10 @@ const reservedClauses = expandPhrases([ // Data definition 'CREATE [OR REPLACE] [GLOBAL TEMPORARY | TEMPORARY] VIEW [IF NOT EXISTS]', 'CREATE [EXTERNAL] TABLE [IF NOT EXISTS]', +]); + +const onelineClauses = expandPhrases([ + // - drop table: 'DROP TABLE [IF EXISTS]', // - alter table: 'ALTER TABLE', @@ -46,7 +49,10 @@ const reservedClauses = expandPhrases([ 'RENAME TO', 'RENAME COLUMN', 'ALTER COLUMN', - + // - truncate: + 'TRUNCATE TABLE', + // other + 'LATERAL VIEW', 'ALTER DATABASE', 'ALTER VIEW', 'CREATE DATABASE', @@ -87,8 +93,6 @@ const reservedClauses = expandPhrases([ 'SHOW TBLPROPERTIES', 'SHOW VIEWS', 'UNCACHE TABLE', - // other - 'LATERAL VIEW', ]); const reservedSetOperations = expandPhrases([ @@ -119,8 +123,8 @@ const reservedPhrases = expandPhrases([ export default class SparkFormatter extends Formatter { tokenizer() { return new Tokenizer({ - reservedClauses, reservedSelect, + reservedClauses: [...reservedClauses, ...onelineClauses], reservedSetOperations, reservedJoins, reservedPhrases, @@ -140,6 +144,12 @@ export default class SparkFormatter extends Formatter { postProcess, }); } + + formatOptions(): DialectFormatOptions { + return { + onelineClauses, + }; + } } function postProcess(tokens: Token[]) { diff --git a/src/languages/sql/sql.formatter.ts b/src/languages/sql/sql.formatter.ts index 92b4d709c..51f31ddb7 100644 --- a/src/languages/sql/sql.formatter.ts +++ b/src/languages/sql/sql.formatter.ts @@ -1,5 +1,6 @@ import { expandPhrases } from '../../expandPhrases.js'; import Formatter from '../../formatter/Formatter.js'; +import { DialectFormatOptions } from '../../formatter/ExpressionFormatter.js'; import Tokenizer from '../../lexer/Tokenizer.js'; import { functions } from './sql.functions.js'; import { keywords } from './sql.keywords.js'; @@ -24,16 +25,19 @@ const reservedClauses = expandPhrases([ 'INSERT INTO', 'VALUES', // - update: - 'UPDATE', 'SET', - 'WHERE CURRENT OF', - // - delete: - 'DELETE FROM', - // - truncate: - 'TRUNCATE TABLE', // Data definition 'CREATE [RECURSIVE] VIEW', 'CREATE [GLOBAL TEMPORARY | LOCAL TEMPORARY] TABLE', +]); + +const onelineClauses = expandPhrases([ + // - update: + 'UPDATE', + 'WHERE CURRENT OF', + // - delete: + 'DELETE FROM', + // - drop table: 'DROP TABLE', // - alter table: 'ALTER TABLE', @@ -46,7 +50,8 @@ const reservedClauses = expandPhrases([ 'ADD SCOPE', // for alter column 'DROP SCOPE {CASCADE | RESTRICT}', // for alter column 'RESTART WITH', // for alter column - + // - truncate: + 'TRUNCATE TABLE', // other 'SET SCHEMA', ]); @@ -73,8 +78,8 @@ const reservedPhrases = expandPhrases([ export default class SqlFormatter extends Formatter { tokenizer() { return new Tokenizer({ - reservedClauses, reservedSelect, + reservedClauses: [...reservedClauses, ...onelineClauses], reservedSetOperations, reservedJoins, reservedPhrases, @@ -89,4 +94,10 @@ export default class SqlFormatter extends Formatter { operators: ['||'], }); } + + formatOptions(): DialectFormatOptions { + return { + onelineClauses, + }; + } } diff --git a/src/languages/sqlite/sqlite.formatter.ts b/src/languages/sqlite/sqlite.formatter.ts index 1bd948475..5c4c9efe9 100644 --- a/src/languages/sqlite/sqlite.formatter.ts +++ b/src/languages/sqlite/sqlite.formatter.ts @@ -1,5 +1,6 @@ import { expandPhrases } from '../../expandPhrases.js'; import Formatter from '../../formatter/Formatter.js'; +import { DialectFormatOptions } from '../../formatter/ExpressionFormatter.js'; import Tokenizer from '../../lexer/Tokenizer.js'; import { functions } from './sqlite.functions.js'; import { keywords } from './sqlite.keywords.js'; @@ -24,13 +25,18 @@ const reservedClauses = expandPhrases([ 'REPLACE INTO', 'VALUES', // - update: - 'UPDATE [OR ABORT | OR FAIL | OR IGNORE | OR REPLACE | OR ROLLBACK]', 'SET', - // - delete: - 'DELETE FROM', // Data definition 'CREATE [TEMPORARY | TEMP] VIEW [IF NOT EXISTS]', 'CREATE [TEMPORARY | TEMP] TABLE [IF NOT EXISTS]', +]); + +const onelineClauses = expandPhrases([ + // - update: + 'UPDATE [OR ABORT | OR FAIL | OR IGNORE | OR REPLACE | OR ROLLBACK]', + // - delete: + 'DELETE FROM', + // - drop table: 'DROP TABLE [IF EXISTS]', // - alter table: 'ALTER TABLE', @@ -38,8 +44,7 @@ const reservedClauses = expandPhrases([ 'DROP [COLUMN]', 'RENAME [COLUMN]', 'RENAME TO', - - // other + // - set schema 'SET SCHEMA', ]); @@ -62,8 +67,8 @@ const reservedPhrases = expandPhrases([ export default class SqliteFormatter extends Formatter { tokenizer() { return new Tokenizer({ - reservedClauses, reservedSelect, + reservedClauses: [...reservedClauses, ...onelineClauses], reservedSetOperations, reservedJoins, reservedPhrases, @@ -81,4 +86,10 @@ export default class SqliteFormatter extends Formatter { operators: ['%', '~', '&', '|', '<<', '>>', '==', '->', '->>', '||'], }); } + + formatOptions(): DialectFormatOptions { + return { + onelineClauses, + }; + } } diff --git a/src/languages/transactsql/transactsql.formatter.ts b/src/languages/transactsql/transactsql.formatter.ts index 609267365..cbda06b6f 100644 --- a/src/languages/transactsql/transactsql.formatter.ts +++ b/src/languages/transactsql/transactsql.formatter.ts @@ -24,13 +24,7 @@ const reservedClauses = expandPhrases([ 'INSERT [INTO]', 'VALUES', // - update: - 'UPDATE', 'SET', - 'WHERE CURRENT OF', - // - delete: - 'DELETE [FROM]', - // - truncate: - 'TRUNCATE TABLE', // - merge: 'MERGE [INTO]', 'WHEN [NOT] MATCHED [BY TARGET | BY SOURCE] [THEN]', @@ -38,13 +32,23 @@ const reservedClauses = expandPhrases([ // Data definition 'CREATE [OR ALTER] [MATERIALIZED] VIEW', 'CREATE TABLE', +]); + +const onelineClauses = expandPhrases([ + // - update: + 'UPDATE', + 'WHERE CURRENT OF', + // - delete: + 'DELETE [FROM]', + // - drop table: 'DROP TABLE [IF EXISTS]', // - alter table: 'ALTER TABLE', 'ADD', 'DROP COLUMN [IF EXISTS]', 'ALTER COLUMN', - + // - truncate: + 'TRUNCATE TABLE', // https://docs.microsoft.com/en-us/sql/t-sql/statements/statements?view=sql-server-ver15 'ADD SENSITIVITY CLASSIFICATION', 'ADD SIGNATURE', @@ -220,8 +224,8 @@ const reservedPhrases = expandPhrases([ export default class TransactSqlFormatter extends Formatter { tokenizer() { return new Tokenizer({ - reservedClauses, reservedSelect, + reservedClauses: [...reservedClauses, ...onelineClauses], reservedSetOperations, reservedJoins, reservedPhrases, @@ -255,6 +259,9 @@ export default class TransactSqlFormatter extends Formatter { } formatOptions(): DialectFormatOptions { - return { alwaysDenseOperators: ['::'] }; + return { + alwaysDenseOperators: ['::'], + onelineClauses, + }; } } diff --git a/src/languages/trino/trino.formatter.ts b/src/languages/trino/trino.formatter.ts index 2b0b7765d..3965dd901 100644 --- a/src/languages/trino/trino.formatter.ts +++ b/src/languages/trino/trino.formatter.ts @@ -1,5 +1,6 @@ import { expandPhrases } from '../../expandPhrases.js'; import Formatter from '../../formatter/Formatter.js'; +import { DialectFormatOptions } from '../../formatter/ExpressionFormatter.js'; import Tokenizer from '../../lexer/Tokenizer.js'; import { functions } from './trino.functions.js'; import { keywords } from './trino.keywords.js'; @@ -25,15 +26,27 @@ const reservedClauses = expandPhrases([ 'INSERT INTO', 'VALUES', // - update: - 'UPDATE', 'SET', - // - delete: - 'DELETE FROM', - // - truncate: - 'TRUNCATE TABLE', // Data definition 'CREATE [OR REPLACE] [MATERIALIZED] VIEW', 'CREATE TABLE [IF NOT EXISTS]', + // MATCH_RECOGNIZE + 'MATCH_RECOGNIZE', + 'MEASURES', + 'ONE ROW PER MATCH', + 'ALL ROWS PER MATCH', + 'AFTER MATCH', + 'PATTERN', + 'SUBSET', + 'DEFINE', +]); + +const onelineClauses = expandPhrases([ + // - update: + 'UPDATE', + // - delete: + 'DELETE FROM', + // - drop table: 'DROP TABLE [IF EXISTS]', // - alter table: 'ALTER TABLE [IF EXISTS]', @@ -44,14 +57,16 @@ const reservedClauses = expandPhrases([ 'SET AUTHORIZATION [USER | ROLE]', 'SET PROPERTIES', 'EXECUTE', + // - truncate: + 'TRUNCATE TABLE', + // other 'ALTER SCHEMA', 'ALTER MATERIALIZED VIEW', 'ALTER VIEW', 'CREATE SCHEMA', 'CREATE ROLE', 'DROP SCHEMA', - 'DROP COLUMN', 'DROP MATERIALIZED VIEW', 'DROP VIEW', 'DROP ROLE', @@ -88,16 +103,6 @@ const reservedClauses = expandPhrases([ 'SHOW ROLE GRANTS', 'SHOW FUNCTIONS', 'SHOW SESSION', - - // MATCH_RECOGNIZE - 'MATCH_RECOGNIZE', - 'MEASURES', - 'ONE ROW PER MATCH', - 'ALL ROWS PER MATCH', - 'AFTER MATCH', - 'PATTERN', - 'SUBSET', - 'DEFINE', ]); // https://github.com/trinodb/trino/blob/432d2897bdef99388c1a47188743a061c4ac1f34/core/trino-parser/src/main/antlr4/io/trino/sql/parser/SqlBase.g4#L231-L235 @@ -122,8 +127,8 @@ const reservedPhrases = expandPhrases(['{ROWS | RANGE | GROUPS} BETWEEN']); export default class TrinoFormatter extends Formatter { tokenizer() { return new Tokenizer({ - reservedClauses, reservedSelect, + reservedClauses: [...reservedClauses, ...onelineClauses], reservedSetOperations, reservedJoins, reservedPhrases, @@ -156,4 +161,10 @@ export default class TrinoFormatter extends Formatter { ], }); } + + formatOptions(): DialectFormatOptions { + return { + onelineClauses, + }; + } } diff --git a/src/lexer/TokenizerOptions.ts b/src/lexer/TokenizerOptions.ts index 35e0f6051..8bfc2dd6f 100644 --- a/src/lexer/TokenizerOptions.ts +++ b/src/lexer/TokenizerOptions.ts @@ -43,10 +43,10 @@ export interface ParamTypes { } export interface TokenizerOptions { - // Main clauses that start new block, like: WITH, FROM, WHERE, ORDER BY - reservedClauses: string[]; // SELECT clause and its variations reservedSelect: string[]; + // Main clauses that start new block, like: WITH, FROM, WHERE, ORDER BY + reservedClauses: string[]; // True to support XOR in addition to AND and OR supportsXor?: boolean; // Keywords that create newline but no indentaion of their body. diff --git a/src/utils.ts b/src/utils.ts index 25eef752f..546cebea3 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -30,3 +30,11 @@ export const isMultiline = (text: string): boolean => /\n/.test(text); // type Foo = { foo?: string, bar: number }; // export type Optional = Pick, K> & Omit; + +// Caches return value of a function in specified field in class object/function +export const cacheInClassField = (cls: any, fieldName: string, fn: () => T): T => { + if (!cls[fieldName]) { + cls[fieldName] = fn(); + } + return cls[fieldName]; +}; diff --git a/test/behavesLikeMariaDbFormatter.ts b/test/behavesLikeMariaDbFormatter.ts index 9824cef5b..f35ceff41 100644 --- a/test/behavesLikeMariaDbFormatter.ts +++ b/test/behavesLikeMariaDbFormatter.ts @@ -102,10 +102,8 @@ export default function behavesLikeMariaDbFormatter(format: FormatFn) { // Issue #181 it('does not wrap CHARACTER SET to multiple lines', () => { expect(format('ALTER TABLE t MODIFY col1 VARCHAR(50) CHARACTER SET greek')).toBe(dedent` - ALTER TABLE - t - MODIFY - col1 VARCHAR(50) CHARACTER SET greek + ALTER TABLE t + MODIFY col1 VARCHAR(50) CHARACTER SET greek `); }); diff --git a/test/bigquery.test.ts b/test/bigquery.test.ts index 20defcd56..9afe4d5be 100644 --- a/test/bigquery.test.ts +++ b/test/bigquery.test.ts @@ -1,7 +1,6 @@ import dedent from 'dedent-js'; import { format as originalFormat, FormatFn } from '../src/sqlFormatter.js'; -import { flatKeywordList } from '../src/utils.js'; import behavesLikeSqlFormatter from './behavesLikeSqlFormatter.js'; import supportsCreateTable from './features/createTable.js'; @@ -201,31 +200,15 @@ describe('BigQueryFormatter', () => { }); it('supports parameterised types', () => { - const result = format( - ` + const sql = dedent` DECLARE varString STRING(11) '11charswide'; DECLARE varBytes BYTES(8); - DECLARE varNumeric NUMERIC(1,1); - DECLARE varDecimal DECIMAL(1,1); - DECLARE varBignumeric BIGNUMERIC(1,1); - DECLARE varBigdecimal BIGDECIMAL(1,1); - `, - { linesBetweenQueries: 0 } - ); - expect(result).toBe(dedent` - DECLARE - varString STRING(11) '11charswide'; - DECLARE - varBytes BYTES(8); - DECLARE - varNumeric NUMERIC(1, 1); - DECLARE - varDecimal DECIMAL(1, 1); - DECLARE - varBignumeric BIGNUMERIC(1, 1); - DECLARE - varBigdecimal BIGDECIMAL(1, 1); - `); + DECLARE varNumeric NUMERIC(1, 1); + DECLARE varDecimal DECIMAL(1, 1); + DECLARE varBignumeric BIGNUMERIC(1, 1); + DECLARE varBigdecimal BIGDECIMAL(1, 1); + `; + expect(format(sql, { linesBetweenQueries: 0 })).toBe(sql); }); // Regression test for issue #243 @@ -268,8 +251,7 @@ describe('BigQueryFormatter', () => { FROM Produce PIVOT( sales - FOR - quarter IN (Q1, Q2, Q3, Q4) + FOR quarter IN (Q1, Q2, Q3, Q4) ); `); }); @@ -282,8 +264,7 @@ describe('BigQueryFormatter', () => { FROM Produce UNPIVOT( sales - FOR - quarter IN (Q1, Q2, Q3, Q4) + FOR quarter IN (Q1, Q2, Q3, Q4) ); `); }); @@ -299,91 +280,19 @@ describe('BigQueryFormatter', () => { }); describe('BigQuery DDL Create Statements', () => { - const createCmds = { - schema: ['CREATE SCHEMA', 'CREATE SCHEMA IF NOT EXISTS'], - function: ['CREATE FUNCTION', 'CREATE OR REPLACE FUNCTION', 'CREATE FUNCTION IF NOT EXISTS'], - tempFunction: [ - 'CREATE TEMP FUNCTION', - 'CREATE OR REPLACE TEMP FUNCTION', - 'CREATE TEMP FUNCTION IF NOT EXISTS', - 'CREATE TEMPORARY FUNCTION', - 'CREATE OR REPLACE TEMPORARY FUNCTION', - 'CREATE TEMPORARY FUNCTION IF NOT EXISTS', - ], - tableFunction: [ - 'CREATE TABLE FUNCTION', - 'CREATE OR REPLACE TABLE FUNCTION', - 'CREATE TABLE FUNCTION IF NOT EXISTS', - ], - procedure: [ - 'CREATE PROCEDURE', - 'CREATE OR REPLACE PROCEDURE', - 'CREATE PROCEDURE IF NOT EXISTS', - ], - rowAccessPolicy: [ - 'CREATE ROW ACCESS POLICY', - 'CREATE ROW ACCESS POLICY IF NOT EXISTS', - 'CREATE OR REPLACE ROW ACCESS POLICY', - ], - searchIndex: ['CREATE SEARCH INDEX', 'CREATE SEARCH INDEX IF NOT EXISTS'], - }; - - createCmds.schema.forEach(createSchema => { - it(`Supports ${createSchema}`, () => { - const input = ` - ${createSchema} mydataset - DEFAULT COLLATE 'und:ci' - OPTIONS( - location="us", labels=[("label1","value1"),("label2","value2")])`; - const expected = dedent` - ${createSchema} - mydataset - DEFAULT COLLATE - 'und:ci' OPTIONS( - location = "us", - labels = [("label1", "value1"), ("label2", "value2")] - ) - `; - expect(format(input)).toBe(expected); - }); - }); - - it(`Supports CREATE TABLE LIKE`, () => { - const input = ` - CREATE TABLE mydataset.newtable - LIKE mydataset.sourcetable - AS (SELECT * FROM mydataset.myothertable)`; - const expected = dedent` - CREATE TABLE - mydataset.newtable LIKE mydataset.sourcetable AS ( - SELECT - * - FROM - mydataset.myothertable - )`; - - expect(format(input)).toBe(expected); - }); - - it(`Supports CREATE TABLE COPY`, () => { + it(`Supports CREATE SCHEMA`, () => { const input = ` - CREATE TABLE mydataset.newtable - COPY mydataset.sourcetable`; + CREATE SCHEMA mydataset + DEFAULT COLLATE 'und:ci' + OPTIONS( + location="us", labels=[("label1","value1"),("label2","value2")])`; const expected = dedent` - CREATE TABLE - mydataset.newtable COPY mydataset.sourcetable`; - - expect(format(input)).toBe(expected); - }); - - it(`Supports CREATE TABLE CLONE`, () => { - const input = ` - CREATE TABLE mydataset.newtable - CLONE mydataset.sourcetable`; - const expected = dedent` - CREATE TABLE - mydataset.newtable CLONE mydataset.sourcetable`; - + CREATE SCHEMA mydataset + DEFAULT COLLATE 'und:ci' OPTIONS( + location = "us", + labels = [("label1", "value1"), ("label2", "value2")] + ) + `; expect(format(input)).toBe(expected); }); @@ -406,17 +315,14 @@ describe('BigQueryFormatter', () => { expect(format(input)).toBe(expected); }); - createCmds.function.concat(createCmds.tempFunction).forEach(createFunction => { - it(`Supports ${createFunction}`, () => { - const input = ` - ${createFunction} mydataset.myFunc(x FLOAT64, y FLOAT64) - RETURNS FLOAT64 - AS (x * y);`; - const expected = dedent` - ${createFunction} - mydataset.myFunc (x FLOAT64, y FLOAT64) RETURNS FLOAT64 AS (x * y);`; - expect(format(input)).toBe(expected); - }); + it(`Supports CREATE FUNCTION`, () => { + const input = ` + CREATE FUNCTION mydataset.myFunc(x FLOAT64, y FLOAT64) + RETURNS FLOAT64 + AS (x * y);`; + const expected = dedent` + CREATE FUNCTION mydataset.myFunc (x FLOAT64, y FLOAT64) RETURNS FLOAT64 AS (x * y);`; + expect(format(input)).toBe(expected); }); it(`Supports CREATE FUNCTION - LANGUAGE js`, () => { @@ -428,146 +334,100 @@ describe('BigQueryFormatter', () => { return x*y; """;`; const expected = dedent` - CREATE FUNCTION - myFunc (x FLOAT64, y FLOAT64) RETURNS FLOAT64 LANGUAGE js AS r""" + CREATE FUNCTION myFunc (x FLOAT64, y FLOAT64) RETURNS FLOAT64 LANGUAGE js AS r""" return x*y; """;`; expect(format(input)).toBe(expected); }); - createCmds.tableFunction.forEach(createTableFunc => { - it(`Supports ${createTableFunc}`, () => { - const input = ` - ${createTableFunc} mydataset.names_by_year(y INT64) - RETURNS TABLE - AS ( - SELECT year, name - FROM mydataset.mytable - WHERE year = y - )`; - - // TODO: formatting for can be improved - const expected = dedent` - ${createTableFunc} - mydataset.names_by_year (y INT64) - RETURNS TABLE - < name STRING, - year INT64 > AS ( - SELECT - year, - name - FROM - mydataset.mytable - WHERE - year = y - )`; - expect(format(input)).toBe(expected); - }); - }); + it(`Supports CREATE TABLE FUNCTION`, () => { + const input = ` + CREATE TABLE FUNCTION mydataset.names_by_year(y INT64) + RETURNS TABLE + AS ( + SELECT year, name + FROM mydataset.mytable + WHERE year = y + )`; - // not correctly supported yet - createCmds.procedure.forEach(createProcedure => { - it(`Supports ${createProcedure}`, () => { - const input = ` - ${createProcedure} myDataset.QueryTable() - BEGIN - SELECT * FROM anotherDataset.myTable; - END;`; - const expected = dedent` - ${createProcedure} - myDataset.QueryTable () - BEGIN + // TODO: formatting for can be improved + const expected = dedent` + CREATE TABLE FUNCTION mydataset.names_by_year (y INT64) RETURNS TABLE < name STRING, + year INT64 > AS ( SELECT - * + year, + name FROM - anotherDataset.myTable; - - END;`; - expect(format(input)).toBe(expected); - }); - }); - - createCmds.rowAccessPolicy.forEach(createRowAccessPolicy => { - it(`Supports ${createRowAccessPolicy}`, () => { - const input = ` - ${createRowAccessPolicy} us_filter - ON mydataset.table1 - GRANT TO ("group:abc@example.com", "user:hello@example.com") - FILTER USING (Region="US")`; - const expected = dedent` - ${createRowAccessPolicy} - us_filter ON mydataset.table1 - GRANT TO - ("group:abc@example.com", "user:hello@example.com") - FILTER USING - (Region = "US")`; - expect(format(input)).toBe(expected); - }); - }); - - it(`Supports CREATE CAPACITY`, () => { - const input = dedent` - CREATE CAPACITY admin_project.region-us.my-commitment - AS JSON """{ - "slot_count": 100, - "plan": "FLEX" - }"""`; - const expected = dedent` - CREATE CAPACITY - admin_project.region-us.my-commitment - AS JSON - """{ - "slot_count": 100, - "plan": "FLEX" - }"""`; + mydataset.mytable + WHERE + year = y + )`; expect(format(input)).toBe(expected); }); - it(`Supports CREATE RESERVATION`, () => { - const input = dedent` - CREATE RESERVATION admin_project.region-us.prod - AS JSON """{ - "slot_capacity": 100 - }"""`; + // not correctly supported yet + it(`Supports CREATE PROCEDURE`, () => { + const input = ` + CREATE PROCEDURE myDataset.QueryTable() + BEGIN + SELECT * FROM anotherDataset.myTable; + END;`; const expected = dedent` - CREATE RESERVATION - admin_project.region-us.prod - AS JSON - """{ - "slot_capacity": 100 - }"""`; + CREATE PROCEDURE myDataset.QueryTable () + BEGIN + SELECT + * + FROM + anotherDataset.myTable; + + END;`; expect(format(input)).toBe(expected); }); - it(`Supports CREATE ASSIGNMENT`, () => { - const input = dedent` - CREATE ASSIGNMENT admin_project.region-us.prod.my_assignment - AS JSON """{ - "assignee": "projects/my_project", - "job_type": "QUERY" - }"""`; + it(`Supports CREATE ROW ACCESS POLICY`, () => { + const input = ` + CREATE ROW ACCESS POLICY us_filter + ON mydataset.table1 + GRANT TO ("group:abc@example.com", "user:hello@example.com") + FILTER USING (Region="US")`; const expected = dedent` - CREATE ASSIGNMENT - admin_project.region-us.prod.my_assignment - AS JSON - """{ - "assignee": "projects/my_project", - "job_type": "QUERY" - }"""`; + CREATE ROW ACCESS POLICY us_filter ON mydataset.table1 + GRANT TO ("group:abc@example.com", "user:hello@example.com") + FILTER USING (Region = "US")`; expect(format(input)).toBe(expected); }); - createCmds.searchIndex.forEach(createSearchIndex => { - it(`Supports ${createSearchIndex}`, () => { - const input = ` - ${createSearchIndex} my_index - ON dataset.my_table(ALL COLUMNS);`; + [ + // Create statements using "AS JSON" + 'CREATE CAPACITY', + 'CREATE RESERVATION', + 'CREATE ASSIGNMENT', + ].forEach(create => { + it(`Supports ${create}`, () => { + const input = dedent` + ${create} admin_project.region-us.my-commitment + AS JSON """{ + "slot_count": 100, + "plan": "FLEX" + }"""`; const expected = dedent` - ${createSearchIndex} - my_index ON dataset.my_table (ALL COLUMNS);`; + ${create} admin_project.region-us.my-commitment + AS JSON """{ + "slot_count": 100, + "plan": "FLEX" + }"""`; expect(format(input)).toBe(expected); }); }); + + it(`Supports CREATE SEARCH INDEX`, () => { + const input = ` + CREATE SEARCH INDEX my_index + ON dataset.my_table(ALL COLUMNS);`; + const expected = dedent` + CREATE SEARCH INDEX my_index ON dataset.my_table (ALL COLUMNS);`; + expect(format(input)).toBe(expected); + }); }); describe('BigQuery DDL Alter Statements', () => { @@ -576,10 +436,8 @@ describe('BigQueryFormatter', () => { ALTER SCHEMA mydataset SET DEFAULT COLLATE 'und:ci'`; const expected = dedent` - ALTER SCHEMA - mydataset - SET DEFAULT COLLATE - 'und:ci'`; + ALTER SCHEMA mydataset + SET DEFAULT COLLATE 'und:ci'`; expect(format(input)).toBe(expected); }); @@ -590,10 +448,8 @@ describe('BigQueryFormatter', () => { default_table_expiration_days=3.75 )`; const expected = dedent` - ALTER SCHEMA - mydataset - SET OPTIONS - (default_table_expiration_days = 3.75)`; + ALTER SCHEMA mydataset + SET OPTIONS (default_table_expiration_days = 3.75)`; expect(format(input)).toBe(expected); }); @@ -604,12 +460,10 @@ describe('BigQueryFormatter', () => { expiration_timestamp=TIMESTAMP_ADD(CURRENT_TIMESTAMP(), INTERVAL 7 DAY) )`; const expected = dedent` - ALTER TABLE - mydataset.mytable - SET OPTIONS - ( - expiration_timestamp = TIMESTAMP_ADD(CURRENT_TIMESTAMP(), INTERVAL 7 DAY) - )`; + ALTER TABLE mydataset.mytable + SET OPTIONS ( + expiration_timestamp = TIMESTAMP_ADD(CURRENT_TIMESTAMP(), INTERVAL 7 DAY) + )`; expect(format(input)).toBe(expected); }); @@ -618,10 +472,8 @@ describe('BigQueryFormatter', () => { ALTER TABLE mydataset.mytable SET DEFAULT COLLATE 'und:ci'`; const expected = dedent` - ALTER TABLE - mydataset.mytable - SET DEFAULT COLLATE - 'und:ci'`; + ALTER TABLE mydataset.mytable + SET DEFAULT COLLATE 'und:ci'`; expect(format(input)).toBe(expected); }); @@ -633,12 +485,9 @@ describe('BigQueryFormatter', () => { description="Price per unit" )`; const expected = dedent` - ALTER TABLE - mydataset.mytable - ALTER COLUMN - price - SET OPTIONS - (description = "Price per unit")`; + ALTER TABLE mydataset.mytable + ALTER COLUMN price + SET OPTIONS (description = "Price per unit")`; expect(format(input)).toBe(expected); }); @@ -648,10 +497,8 @@ describe('BigQueryFormatter', () => { ALTER COLUMN price DROP NOT NULL`; const expected = dedent` - ALTER TABLE - mydataset.mytable - ALTER COLUMN - price + ALTER TABLE mydataset.mytable + ALTER COLUMN price DROP NOT NULL`; expect(format(input)).toBe(expected); }); @@ -662,12 +509,9 @@ describe('BigQueryFormatter', () => { ALTER COLUMN price SET DATA TYPE NUMERIC`; const expected = dedent` - ALTER TABLE - mydataset.mytable - ALTER COLUMN - price - SET DATA TYPE - NUMERIC`; + ALTER TABLE mydataset.mytable + ALTER COLUMN price + SET DATA TYPE NUMERIC`; expect(format(input)).toBe(expected); }); @@ -678,12 +522,10 @@ describe('BigQueryFormatter', () => { expiration_timestamp=TIMESTAMP_ADD(CURRENT_TIMESTAMP(), INTERVAL 7 DAY) )`; const expected = dedent` - ALTER VIEW - mydataset.myview - SET OPTIONS - ( - expiration_timestamp = TIMESTAMP_ADD(CURRENT_TIMESTAMP(), INTERVAL 7 DAY) - )`; + ALTER VIEW mydataset.myview + SET OPTIONS ( + expiration_timestamp = TIMESTAMP_ADD(CURRENT_TIMESTAMP(), INTERVAL 7 DAY) + )`; expect(format(input)).toBe(expected); }); @@ -694,92 +536,27 @@ describe('BigQueryFormatter', () => { size_gb = 250 )`; const expected = dedent` - ALTER BI_CAPACITY - my-project.region-us.default - SET OPTIONS - (size_gb = 250)`; + ALTER BI_CAPACITY my-project.region-us.default + SET OPTIONS (size_gb = 250)`; expect(format(input)).toBe(expected); }); }); describe('BigQuery DDL Drop Statements', () => { - const dropCmds = { - schema: ['DROP SCHEMA', 'DROP SCHEMA IF EXISTS'], - view: ['DROP VIEW', 'DROP VIEW IF EXISTS'], - materializedViews: ['DROP MATERIALIZED VIEW', 'DROP MATERIALIZED VIEW IF EXISTS'], - function: ['DROP FUNCTION', 'DROP FUNCTION IF EXISTS'], - tableFunction: ['DROP TABLE FUNCTION', 'DROP TABLE FUNCTION IF EXISTS'], - procedure: ['DROP PROCEDURE', 'DROP PROCEDURE IF EXISTS'], - reservation: ['DROP RESERVATION', 'DROP RESERVATION IF EXISTS'], - assignment: ['DROP ASSIGNMENT', 'DROP ASSIGNMENT IF EXISTS'], - }; - - flatKeywordList(dropCmds).forEach(drop => { - it(`Supports ${drop}`, () => { - const input = ` - ${drop} mydataset.name`; - const expected = dedent` - ${drop} - mydataset.name`; - expect(format(input)).toBe(expected); - }); - }); - - it(`Supports DROP SCHEMA - CASCADE`, () => { - const input = ` - DROP SCHEMA mydataset CASCADE`; - const expected = dedent` - DROP SCHEMA - mydataset CASCADE`; - expect(format(input)).toBe(expected); - }); - - it(`Supports DROP SCHEMA - RESTRICT`, () => { - const input = ` - DROP SCHEMA mydataset RESTRICT`; - const expected = dedent` - DROP SCHEMA - mydataset RESTRICT`; - expect(format(input)).toBe(expected); - }); - - const dropSearchIndices = ['DROP SEARCH INDEX', 'DROP SEARCH INDEX IF EXISTS']; - dropSearchIndices.forEach(dropSearchIndex => { - it(`Supports ${dropSearchIndex}`, () => { - const input = ` - ${dropSearchIndex} index2 ON mydataset.mytable`; - const expected = dedent` - ${dropSearchIndex} - index2 ON mydataset.mytable`; - expect(format(input)).toBe(expected); - }); - }); - - it(`Supports DROP ROW ACCESS POLICY`, () => { - const input = ` - DROP mypolicy ON mydataset.mytable`; - const expected = dedent` - DROP - mypolicy ON mydataset.mytable`; - expect(format(input)).toBe(expected); - }); - - it(`Supports DROP ROW ACCESS POLICY IF EXISTS`, () => { - const input = ` - DROP IF EXISTS mypolicy ON mydataset.mytable`; - const expected = dedent` - DROP IF EXISTS - mypolicy ON mydataset.mytable`; - expect(format(input)).toBe(expected); - }); - - it(`Supports DROP ALL ROW ACCESS POLICIES`, () => { - const input = ` - DROP ALL ROW ACCESS POLICIES ON table_name`; - const expected = dedent` - DROP ALL ROW ACCESS POLICIES - ON table_name`; - expect(format(input)).toBe(expected); + it(`Supports DROP clauses`, () => { + const input = dedent` + DROP SCHEMA mydataset.name; + DROP VIEW mydataset.name; + DROP FUNCTION mydataset.name; + DROP TABLE FUNCTION mydataset.name; + DROP PROCEDURE mydataset.name; + DROP RESERVATION mydataset.name; + DROP ASSIGNMENT mydataset.name; + DROP SEARCH INDEX index2 ON mydataset.mytable; + DROP mypolicy ON mydataset.mytable; + DROP ALL ROW ACCESS POLICIES ON table_name; + `; + expect(format(input, { linesBetweenQueries: 0 })).toBe(input); }); }); }); diff --git a/test/db2.test.ts b/test/db2.test.ts index 87480c832..ec028200c 100644 --- a/test/db2.test.ts +++ b/test/db2.test.ts @@ -103,8 +103,7 @@ describe('Db2Formatter', () => { it('supports WITH isolation level modifiers for UPDATE statement', () => { expect(format('UPDATE foo SET x = 10 WITH CS')).toBe(dedent` - UPDATE - foo + UPDATE foo SET x = 10 WITH CS @@ -118,17 +117,12 @@ describe('Db2Formatter', () => { 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 DATA TYPE VARCHAR; - ALTER TABLE - t - ALTER COLUMN - foo + ALTER TABLE t + ALTER COLUMN foo SET NOT NULL; `); }); diff --git a/test/features/alterTable.ts b/test/features/alterTable.ts index 6494a84a0..9f0feaa06 100644 --- a/test/features/alterTable.ts +++ b/test/features/alterTable.ts @@ -15,10 +15,8 @@ export default function supportsAlterTable(format: FormatFn, cfg: AlterTableConf it('formats ALTER TABLE ... ADD COLUMN query', () => { const result = format('ALTER TABLE supplier ADD COLUMN unit_price DECIMAL NOT NULL;'); expect(result).toBe(dedent` - ALTER TABLE - supplier - ADD COLUMN - unit_price DECIMAL NOT NULL; + ALTER TABLE supplier + ADD COLUMN unit_price DECIMAL NOT NULL; `); }); } @@ -27,10 +25,8 @@ export default function supportsAlterTable(format: FormatFn, cfg: AlterTableConf it('formats ALTER TABLE ... DROP COLUMN query', () => { const result = format('ALTER TABLE supplier DROP COLUMN unit_price;'); expect(result).toBe(dedent` - ALTER TABLE - supplier - DROP COLUMN - unit_price; + ALTER TABLE supplier + DROP COLUMN unit_price; `); }); } @@ -39,10 +35,8 @@ export default function supportsAlterTable(format: FormatFn, cfg: AlterTableConf it('formats ALTER TABLE ... MODIFY statement', () => { const result = format('ALTER TABLE supplier MODIFY supplier_id DECIMAL NULL;'); expect(result).toBe(dedent` - ALTER TABLE - supplier - MODIFY - supplier_id DECIMAL NULL; + ALTER TABLE supplier + MODIFY supplier_id DECIMAL NULL; `); }); } @@ -51,10 +45,8 @@ export default function supportsAlterTable(format: FormatFn, cfg: AlterTableConf it('formats ALTER TABLE ... RENAME TO statement', () => { const result = format('ALTER TABLE supplier RENAME TO the_one_who_supplies;'); expect(result).toBe(dedent` - ALTER TABLE - supplier - RENAME TO - the_one_who_supplies; + ALTER TABLE supplier + RENAME TO the_one_who_supplies; `); }); } @@ -63,10 +55,8 @@ export default function supportsAlterTable(format: FormatFn, cfg: AlterTableConf it('formats ALTER TABLE ... RENAME COLUMN statement', () => { const result = format('ALTER TABLE supplier RENAME COLUMN supplier_id TO id;'); expect(result).toBe(dedent` - ALTER TABLE - supplier - RENAME COLUMN - supplier_id TO id; + ALTER TABLE supplier + RENAME COLUMN supplier_id TO id; `); }); } diff --git a/test/features/deleteFrom.ts b/test/features/deleteFrom.ts index 542a2a4f4..2b26cc54a 100644 --- a/test/features/deleteFrom.ts +++ b/test/features/deleteFrom.ts @@ -13,8 +13,7 @@ export default function supportsDeleteFrom( it('formats DELETE FROM statement', () => { const result = format("DELETE FROM Customers WHERE CustomerName='Alfred' AND Phone=5002132;"); expect(result).toBe(dedent` - DELETE FROM - Customers + DELETE FROM Customers WHERE CustomerName = 'Alfred' AND Phone = 5002132; @@ -25,8 +24,7 @@ export default function supportsDeleteFrom( it('formats DELETE statement (without FROM)', () => { const result = format("DELETE Customers WHERE CustomerName='Alfred';"); expect(result).toBe(dedent` - DELETE - Customers + DELETE Customers WHERE CustomerName = 'Alfred'; `); diff --git a/test/features/dropTable.ts b/test/features/dropTable.ts index 25b44ed86..5802c0809 100644 --- a/test/features/dropTable.ts +++ b/test/features/dropTable.ts @@ -10,8 +10,7 @@ export default function supportsDropTable(format: FormatFn, { ifExists }: DropTa it('formats DROP TABLE statement', () => { const result = format('DROP TABLE admin_role;'); expect(result).toBe(dedent` - DROP TABLE - admin_role; + DROP TABLE admin_role; `); }); @@ -19,8 +18,7 @@ export default function supportsDropTable(format: FormatFn, { ifExists }: DropTa it('formats DROP TABLE IF EXISTS statement', () => { const result = format('DROP TABLE IF EXISTS admin_role;'); expect(result).toBe(dedent` - DROP TABLE IF EXISTS - admin_role; + DROP TABLE IF EXISTS admin_role; `); }); } diff --git a/test/features/schema.ts b/test/features/schema.ts index 9a5740ac1..b58ee525b 100644 --- a/test/features/schema.ts +++ b/test/features/schema.ts @@ -6,8 +6,7 @@ export default function supportsSchema(format: FormatFn) { it('formats simple SET SCHEMA statements', () => { const result = format('SET SCHEMA schema1;'); expect(result).toBe(dedent` - SET SCHEMA - schema1; + SET SCHEMA schema1; `); }); } diff --git a/test/features/truncateTable.ts b/test/features/truncateTable.ts index 31fb6ea0d..6aea06387 100644 --- a/test/features/truncateTable.ts +++ b/test/features/truncateTable.ts @@ -13,8 +13,7 @@ export default function supportsTruncateTable( it('formats TRUNCATE TABLE statement', () => { const result = format('TRUNCATE TABLE Customers;'); expect(result).toBe(dedent` - TRUNCATE TABLE - Customers; + TRUNCATE TABLE Customers; `); }); @@ -22,8 +21,7 @@ export default function supportsTruncateTable( it('formats TRUNCATE statement (without TABLE)', () => { const result = format('TRUNCATE Customers;'); expect(result).toBe(dedent` - TRUNCATE - Customers; + TRUNCATE Customers; `); }); } diff --git a/test/features/update.ts b/test/features/update.ts index 533e0f9f9..a412cba9a 100644 --- a/test/features/update.ts +++ b/test/features/update.ts @@ -12,8 +12,7 @@ export default function supportsUpdate(format: FormatFn, { whereCurrentOf }: Upd "UPDATE Customers SET ContactName='Alfred Schmidt', City='Hamburg' WHERE CustomerName='Alfreds Futterkiste';" ); expect(result).toBe(dedent` - UPDATE - Customers + UPDATE Customers SET ContactName = 'Alfred Schmidt', City = 'Hamburg' @@ -27,8 +26,7 @@ export default function supportsUpdate(format: FormatFn, { whereCurrentOf }: Upd 'UPDATE customers SET total_orders = order_summary.total FROM ( SELECT * FROM bank) AS order_summary' ); expect(result).toBe(dedent` - UPDATE - customers + UPDATE customers SET total_orders = order_summary.total FROM @@ -45,12 +43,10 @@ export default function supportsUpdate(format: FormatFn, { whereCurrentOf }: Upd it('formats UPDATE statement with cursor position', () => { const result = format("UPDATE Customers SET Name='John' WHERE CURRENT OF my_cursor;"); expect(result).toBe(dedent` - UPDATE - Customers + UPDATE Customers SET Name = 'John' - WHERE CURRENT OF - my_cursor; + WHERE CURRENT OF my_cursor; `); }); } diff --git a/test/hive.test.ts b/test/hive.test.ts index a05b9ee97..8327fe732 100644 --- a/test/hive.test.ts +++ b/test/hive.test.ts @@ -7,7 +7,6 @@ import behavesLikeSqlFormatter from './behavesLikeSqlFormatter.js'; import supportsCreateTable from './features/createTable.js'; import supportsDropTable from './features/dropTable.js'; import supportsAlterTable from './features/alterTable.js'; -import supportsSchema from './features/schema.js'; import supportsStrings from './features/strings.js'; import supportsBetween from './features/between.js'; import supportsJoin from './features/join.js'; @@ -41,7 +40,6 @@ describe('HiveFormatter', () => { supportsStrings(format, ['""-bs', "''-bs"]); supportsIdentifiers(format, ['``']); supportsBetween(format); - supportsSchema(format); supportsJoin(format, { without: ['NATURAL'], additionally: ['LEFT SEMI JOIN'], diff --git a/test/mariadb.test.ts b/test/mariadb.test.ts index 0a5b7411e..cc08418a6 100644 --- a/test/mariadb.test.ts +++ b/test/mariadb.test.ts @@ -77,17 +77,12 @@ describe('MariaDbFormatter', () => { ALTER TABLE t ALTER COLUMN foo DROP DEFAULT;` ) ).toBe(dedent` - ALTER TABLE - t - ALTER COLUMN - foo - SET DEFAULT - 10; + ALTER TABLE t + ALTER COLUMN foo + SET DEFAULT 10; - ALTER TABLE - t - ALTER COLUMN - foo + ALTER TABLE t + ALTER COLUMN foo DROP DEFAULT; `); }); diff --git a/test/mysql.test.ts b/test/mysql.test.ts index 2acbe3679..13dc1ff45 100644 --- a/test/mysql.test.ts +++ b/test/mysql.test.ts @@ -84,17 +84,12 @@ describe('MySqlFormatter', () => { ALTER TABLE t ALTER COLUMN foo DROP DEFAULT;` ) ).toBe(dedent` - ALTER TABLE - t - ALTER COLUMN - foo - SET DEFAULT - 10; + ALTER TABLE t + ALTER COLUMN foo + SET DEFAULT 10; - ALTER TABLE - t - ALTER COLUMN - foo + ALTER TABLE t + ALTER COLUMN foo DROP DEFAULT; `); }); diff --git a/test/n1ql.test.ts b/test/n1ql.test.ts index fe45b3494..2629e74e6 100644 --- a/test/n1ql.test.ts +++ b/test/n1ql.test.ts @@ -115,8 +115,7 @@ describe('N1qlFormatter', () => { * FROM usr - USE KEYS - 'Elinor_33313792' + USE KEYS 'Elinor_33313792' NEST orders_with_users orders ON KEYS ARRAY s.order_id FOR s IN usr.shipped_order_history END; `); @@ -126,20 +125,16 @@ describe('N1qlFormatter', () => { const result = format("EXPLAIN DELETE FROM tutorial t USE KEYS 'baldwin'"); expect(result).toBe(dedent` EXPLAIN - DELETE FROM - tutorial t - USE KEYS - 'baldwin' + DELETE FROM tutorial t + USE KEYS 'baldwin' `); }); it('formats UPDATE query with USE KEYS', () => { const result = format("UPDATE tutorial USE KEYS 'baldwin' SET type = 'actor'"); expect(result).toBe(dedent` - UPDATE - tutorial - USE KEYS - 'baldwin' + UPDATE tutorial + USE KEYS 'baldwin' SET type = 'actor' `); diff --git a/test/postgresql.test.ts b/test/postgresql.test.ts index 59d1a0bfa..8ba232ca1 100644 --- a/test/postgresql.test.ts +++ b/test/postgresql.test.ts @@ -237,36 +237,24 @@ describe('PostgreSqlFormatter', () => { ALTER TABLE t ALTER COLUMN foo DROP NOT NULL;` ) ).toBe(dedent` - ALTER TABLE - t - ALTER COLUMN - foo - SET DATA TYPE - VARCHAR; + ALTER TABLE t + ALTER COLUMN foo + SET DATA TYPE VARCHAR; - ALTER TABLE - t - ALTER COLUMN - foo - SET DEFAULT - 5; + ALTER TABLE t + ALTER COLUMN foo + SET DEFAULT 5; - ALTER TABLE - t - ALTER COLUMN - foo + ALTER TABLE t + ALTER COLUMN foo DROP DEFAULT; - ALTER TABLE - t - ALTER COLUMN - foo + ALTER TABLE t + ALTER COLUMN foo SET NOT NULL; - ALTER TABLE - t - ALTER COLUMN - foo + ALTER TABLE t + ALTER COLUMN foo DROP NOT NULL; `); }); diff --git a/test/redshift.test.ts b/test/redshift.test.ts index 6bdb52d03..6b5ff2090 100644 --- a/test/redshift.test.ts +++ b/test/redshift.test.ts @@ -121,8 +121,7 @@ describe('RedshiftFormatter', () => { REGION AS 'us-east-1' `) ).toBe(dedent` - COPY - schema.table + COPY schema.table FROM 's3://bucket/file.csv' IAM_ROLE 'arn:aws:iam::123456789:role/rolename' FORMAT AS CSV DELIMITER ',' QUOTE '"' REGION AS 'us-east-1' `); @@ -135,19 +134,13 @@ describe('RedshiftFormatter', () => { ALTER TABLE t ALTER COLUMN foo ENCODE my_encoding;` ) ).toBe(dedent` - ALTER TABLE - t - ALTER COLUMN - foo - TYPE - VARCHAR; + ALTER TABLE t + ALTER COLUMN foo + TYPE VARCHAR; - ALTER TABLE - t - ALTER COLUMN - foo - ENCODE - my_encoding; + ALTER TABLE t + ALTER COLUMN foo + ENCODE my_encoding; `); }); }); diff --git a/test/snowflake.test.ts b/test/snowflake.test.ts index cd9168cee..791a6b6cb 100644 --- a/test/snowflake.test.ts +++ b/test/snowflake.test.ts @@ -106,77 +106,49 @@ describe('SnowflakeFormatter', () => { ALTER TABLE t ALTER COLUMN foo UNSET TAG tname;` ) ).toBe(dedent` - ALTER TABLE - t - ALTER COLUMN - foo - SET DATA TYPE - VARCHAR; - - ALTER TABLE - t - ALTER COLUMN - foo - SET DEFAULT - 5; - - ALTER TABLE - t - ALTER COLUMN - foo + ALTER TABLE t + ALTER COLUMN foo + SET DATA TYPE VARCHAR; + + ALTER TABLE t + ALTER COLUMN foo + SET DEFAULT 5; + + ALTER TABLE t + ALTER COLUMN foo DROP DEFAULT; - ALTER TABLE - t - ALTER COLUMN - foo + ALTER TABLE t + ALTER COLUMN foo SET NOT NULL; - ALTER TABLE - t - ALTER COLUMN - foo + ALTER TABLE t + ALTER COLUMN foo DROP NOT NULL; - ALTER TABLE - t - ALTER COLUMN - foo - COMMENT - 'blah'; - - ALTER TABLE - t - ALTER COLUMN - foo + ALTER TABLE t + ALTER COLUMN foo + COMMENT 'blah'; + + ALTER TABLE t + ALTER COLUMN foo UNSET COMMENT; - ALTER TABLE - t - ALTER COLUMN - foo - SET MASKING POLICY - polis; - - ALTER TABLE - t - ALTER COLUMN - foo + ALTER TABLE t + ALTER COLUMN foo + SET MASKING POLICY polis; + + ALTER TABLE t + ALTER COLUMN foo UNSET MASKING POLICY; - ALTER TABLE - t - ALTER COLUMN - foo - SET TAG - tname = 10; - - ALTER TABLE - t - ALTER COLUMN - foo - UNSET TAG - tname; + ALTER TABLE t + ALTER COLUMN foo + SET TAG tname = 10; + + ALTER TABLE t + ALTER COLUMN foo + UNSET TAG tname; `); }); }); diff --git a/test/spark.test.ts b/test/spark.test.ts index 5bbb2eccd..447d6fed5 100644 --- a/test/spark.test.ts +++ b/test/spark.test.ts @@ -128,10 +128,8 @@ describe('SparkFormatter', () => { it('formats ALTER TABLE ... ALTER COLUMN', () => { expect(format(`ALTER TABLE StudentInfo ALTER COLUMN FirstName COMMENT "new comment";`)) .toBe(dedent` - ALTER TABLE - StudentInfo - ALTER COLUMN - FirstName COMMENT "new comment"; + ALTER TABLE StudentInfo + ALTER COLUMN FirstName COMMENT "new comment"; `); }); }); diff --git a/test/sql.test.ts b/test/sql.test.ts index 1cc78ea04..9a8bf5809 100644 --- a/test/sql.test.ts +++ b/test/sql.test.ts @@ -79,31 +79,21 @@ describe('SqlFormatter', () => { ALTER TABLE t ALTER COLUMN foo RESTART WITH 10;` ) ).toBe(dedent` - ALTER TABLE - t - ALTER COLUMN - foo - SET DEFAULT - 5; + ALTER TABLE t + ALTER COLUMN foo + SET DEFAULT 5; - ALTER TABLE - t - ALTER COLUMN - foo + ALTER TABLE t + ALTER COLUMN foo DROP DEFAULT; - ALTER TABLE - t - ALTER COLUMN - foo + ALTER TABLE t + ALTER COLUMN foo DROP SCOPE CASCADE; - ALTER TABLE - t - ALTER COLUMN - foo - RESTART WITH - 10; + ALTER TABLE t + ALTER COLUMN foo + RESTART WITH 10; `); }); }); diff --git a/test/transactsql.test.ts b/test/transactsql.test.ts index cdcfe4d08..83ea31426 100644 --- a/test/transactsql.test.ts +++ b/test/transactsql.test.ts @@ -113,10 +113,8 @@ describe('TransactSqlFormatter', () => { it('formats ALTER TABLE ... ALTER COLUMN', () => { expect(format(`ALTER TABLE t ALTER COLUMN foo INT NOT NULL DEFAULT 5;`)).toBe(dedent` - ALTER TABLE - t - ALTER COLUMN - foo INT NOT NULL DEFAULT 5; + ALTER TABLE t + ALTER COLUMN foo INT NOT NULL DEFAULT 5; `); }); }); diff --git a/test/trino.test.ts b/test/trino.test.ts index 34446a6bd..bc910184d 100644 --- a/test/trino.test.ts +++ b/test/trino.test.ts @@ -59,8 +59,7 @@ describe('TrinoFormatter', () => { it('formats SET SESSION', () => { const result = format('SET SESSION foo = 444;'); expect(result).toBe(dedent` - SET SESSION - foo = 444; + SET SESSION foo = 444; `); });