Skip to content

Commit

Permalink
Merge PR #483: Split clauses into two groups: multiline & single …
Browse files Browse the repository at this point in the history
…line
  • Loading branch information
nene committed Oct 5, 2022
2 parents e33a226 + 4c60475 commit 274fb17
Show file tree
Hide file tree
Showing 40 changed files with 639 additions and 780 deletions.
45 changes: 36 additions & 9 deletions src/formatter/ExpressionFormatter.ts
Expand Up @@ -36,7 +36,7 @@ import InlineLayout, { InlineLayoutError } from './InlineLayout.js';

interface ExpressionFormatterParams {
cfg: FormatOptions;
dialectCfg: DialectFormatOptions;
dialectCfg: ProcessedDialectFormatOptions;
params: Params;
layout: Layout;
inline?: boolean;
Expand All @@ -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<string, boolean>;
}

/** Formats a generic SQL expression */
export default class ExpressionFormatter {
private cfg: FormatOptions;
private dialectCfg: DialectFormatOptions;
private dialectCfg: ProcessedDialectFormatOptions;
private params: Params;
private layout: Layout;

Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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);
Expand Down
28 changes: 19 additions & 9 deletions src/formatter/Formatter.ts
Expand Up @@ -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';

Expand All @@ -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])),
};
});
}

/**
Expand Down Expand Up @@ -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);
Expand Down
56 changes: 39 additions & 17 deletions 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';
Expand Down Expand Up @@ -26,19 +27,28 @@ 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]',
'UPDATE SET',
// 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]',
Expand All @@ -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]',
Expand Down Expand Up @@ -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,
Expand All @@ -169,6 +185,12 @@ export default class BigQueryFormatter extends Formatter {
postProcess,
});
}

formatOptions(): DialectFormatOptions {
return {
onelineClauses,
};
}
}

function postProcess(tokens: Token[]): Token[] {
Expand Down
38 changes: 24 additions & 14 deletions 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';
Expand All @@ -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]',
Expand All @@ -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',
Expand All @@ -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',
Expand Down Expand Up @@ -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]']);
Expand 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,
Expand All @@ -194,4 +198,10 @@ export default class Db2Formatter extends Formatter {
operators: ['**', '¬=', '¬>', '¬<', '!>', '!<', '||'],
});
}

formatOptions(): DialectFormatOptions {
return {
onelineClauses,
};
}
}

0 comments on commit 274fb17

Please sign in to comment.