Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Split clauses into two groups: multiline & single line #483

Merged
merged 32 commits into from Oct 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
c022749
Remove SET SCHEMA support from Hive
nene Oct 1, 2022
4efe16a
Implement single-line formatted clauses
nene Oct 1, 2022
5ef7143
Apply oneline formatting to TRUNCATE TABLE
nene Oct 1, 2022
8847bad
Use oneline format for many BigQuery clauses
nene Oct 1, 2022
f38641a
Format all extra (untested) clauses on single line
nene Oct 1, 2022
76db367
Optimize lookup of onelineClauses
nene Oct 1, 2022
36dd581
Ensure alwaysDenseOperators field is always an array
nene Oct 1, 2022
462112d
Extract utility for saving data to class fields
nene Oct 1, 2022
024492a
Format BigQuery CREATE SCHEMA on single line
nene Oct 2, 2022
0d76d38
Don't indent oneline clause children
nene Oct 2, 2022
697901d
Eliminate complex conditional logic in formatClause
nene Oct 2, 2022
7a113b2
Use oneline formatting for BigQuery procedure creation
nene Oct 2, 2022
6dd0283
Additional oneline clauses for BigQuery
nene Oct 2, 2022
ea9ecaa
Add BigQuery search index creation to oneline clauses
nene Oct 2, 2022
56a7049
Add BigQuery DROP clauses to online clauses list
nene Oct 2, 2022
dfc3d39
Drop excessive tests for BigQuery syntax variants
nene Oct 2, 2022
38fef82
Drop tests for CREATE TABLE LIKE/COPY/CLONE
nene Oct 2, 2022
b97e27d
GROUP tests for statements using AS JSON
nene Oct 2, 2022
5f6e88e
Delete DROP SCHEMA CASCADE/RESTRICT tests
nene Oct 2, 2022
1df0064
Remove test for DROP IF EXISTS variant
nene Oct 2, 2022
b94f81f
Group all DROP clauses to single test
nene Oct 2, 2022
fdf06b7
Avoid repeating the same string
nene Oct 2, 2022
f029c5b
More compact DROP clauses test
nene Oct 2, 2022
53a6af8
Merge pull request #485 from sql-formatter-org/bigquery-tests
nene Oct 3, 2022
0cfc6e8
Merge branch 'master' into oneline-clauses
nene Oct 3, 2022
cdfbf33
Merge remote-tracking branch 'refs/remotes/origin/oneline-clauses' in…
nene Oct 3, 2022
0cc7f0c
Format ALTER TABLE using oneline style
nene Oct 4, 2022
9d0fead
Use oneline style for DROP TABLE
nene Oct 4, 2022
b3a18aa
Use oneline formatting for DELETE FROM
nene Oct 4, 2022
80b8ef6
Format UPDATE statement using online style
nene Oct 4, 2022
5237348
Format N1QL USE KEYS clause in oneline style
nene Oct 4, 2022
4c60475
Merge PR #490: Format ALTER TABLE and some other statements using one…
nene Oct 4, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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 {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we not just use this as the default interface for DialectFormatOptions and add a wrapper function to transform the string[] into Record ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not only the onlineClauses field that differs. It's also the alwaysDenseOperators field which is originally optional, but then processed to be empty array if missing.

alwaysDenseOperators: string[];
onelineClauses: Record<string, boolean>;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be

Suggested change
onelineClauses: Record<string, boolean>;
onelineClauses: Record<string, true>;

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried that, but in this case TypeScript thinks that onlineClauses[anything] always evaluates to true.

}

/** 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,
};
}
}