Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions src/dialect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,15 +34,17 @@ export const createDialect = (options: DialectOptions): Dialect => {

const dialectFromOptions = (dialectOptions: DialectOptions): Dialect => ({
tokenizer: new Tokenizer(dialectOptions.tokenizerOptions, dialectOptions.name),
formatOptions: processDialectFormatOptions(dialectOptions.formatOptions),
formatOptions: processDialectFormatOptions(dialectOptions),
});

const processDialectFormatOptions = (
options: DialectFormatOptions
): ProcessedDialectFormatOptions => ({
const processDialectFormatOptions = ({
tokenizerOptions,
formatOptions: options,
}: DialectOptions): ProcessedDialectFormatOptions => ({
alwaysDenseOperators: options.alwaysDenseOperators || [],
onelineClauses: Object.fromEntries(options.onelineClauses.map(name => [name, true])),
tabularOnelineClauses: Object.fromEntries(
(options.tabularOnelineClauses ?? options.onelineClauses).map(name => [name, true])
),
identifierDashes: Boolean(tokenizerOptions.identChars?.dashes),
});
11 changes: 10 additions & 1 deletion src/formatter/ExpressionFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ export interface ProcessedDialectFormatOptions {
alwaysDenseOperators: string[];
onelineClauses: Record<string, boolean>;
tabularOnelineClauses: Record<string, boolean>;
// True when the dialect allows dashes inside identifiers (e.g. BigQuery).
// In such dialects the "-" operator must keep its surrounding spaces,
// otherwise "a - b" densed to "a-b" would re-parse as a single identifier.
identifierDashes: boolean;
}

/** Formats a generic SQL expression */
Expand Down Expand Up @@ -330,7 +334,12 @@ export default class ExpressionFormatter {
}

private formatOperator({ text }: OperatorNode) {
if (this.cfg.denseOperators || this.dialectCfg.alwaysDenseOperators.includes(text)) {
// In dialects that allow dashes inside identifiers (e.g. BigQuery) the "-"
// operator must keep its surrounding spaces. Densing "a - b" into "a-b"
// would otherwise re-parse as a single dashed identifier.
if (text === '-' && this.dialectCfg.identifierDashes) {
this.layout.add(text, WS.SPACE);
} else 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
18 changes: 17 additions & 1 deletion test/bigquery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ describe('BigQueryFormatter', () => {
'EXCEPT DISTINCT',
'INTERSECT DISTINCT',
]);
supportsOperators(format, ['&', '|', '^', '~', '>>', '<<', '||', '=>'], { any: true });
supportsOperators(format, ['&', '|', '^', '~', '>>', '<<', '||', '=>'], {
any: true,
identifierDashes: true,
});
supportsIsDistinctFrom(format);
supportsParams(format, { positional: true, named: ['@'], quoted: ['@``'] });
supportsWindow(format);
Expand All @@ -80,6 +83,19 @@ describe('BigQueryFormatter', () => {
`);
});

// Because dashes are allowed inside identifiers, densing the "-" operator
// would glue its operands into a single identifier ("a - b" -> "a-b"),
// changing the meaning of the query. So denseOperators must keep it spaced.
it('keeps spaces around the - operator in dense mode', () => {
expect(format('SELECT a - b, x - foo(y)\nFROM t', { denseOperators: true })).toBe(dedent`
SELECT
a - b,
x - foo (y)
FROM
t
`);
});

it('supports @@variables', () => {
expect(format('SELECT @@error.message, @@time_zone')).toBe(dedent`
SELECT
Expand Down
11 changes: 10 additions & 1 deletion test/features/operators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { FormatFn } from '../../src/sqlFormatter.js';
type OperatorsConfig = {
logicalOperators?: string[];
any?: boolean;
// True for dialects that allow dashes inside identifiers (e.g. BigQuery),
// where the "-" operator must keep its surrounding spaces even in dense mode.
identifierDashes?: boolean;
};

export default function supportsOperators(
Expand All @@ -27,7 +30,13 @@ export default function supportsOperators(

operators.forEach(op => {
it(`supports ${op} operator in dense mode`, () => {
expect(format(`foo ${op} bar`, { denseOperators: true })).toBe(`foo${op}bar`);
// In dialects with dashed identifiers, "foo-bar" would re-parse as a
// single identifier, so the "-" operator keeps its surrounding spaces.
if (op === '-' && cfg.identifierDashes) {
expect(format(`foo ${op} bar`, { denseOperators: true })).toBe(`foo ${op} bar`);
} else {
expect(format(`foo ${op} bar`, { denseOperators: true })).toBe(`foo${op}bar`);
}
});
});

Expand Down
Loading