Skip to content
Merged
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
18 changes: 18 additions & 0 deletions docs/guide/formatting-recipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const { formattedSql, params } = formatter.format(query);
| `parenthesesOneLine`, `betweenOneLine`, `valuesOneLine`, `joinOneLine`, `caseOneLine`, `subqueryOneLine` | `true` / `false` | `false` for each | Opt-in switches that keep the corresponding construct on a single line even if other break settings would expand it. |
| `exportComment` | `true` / `false` | `false` | Emits comments collected by the parser. Turn it on when you want annotations preserved. |
| `castStyle` | 'standard', 'postgres' | From preset or 'standard' | Chooses how CAST expressions are printed. 'standard' emits ANSI `CAST(expr AS type)` while 'postgres' emits `expr::type`. See "Controlling CAST style" below for usage notes and examples. |
| `constraintStyle` | `'postgres'`, `'mysql'` | From preset or `'postgres'` | Shapes constraint output in DDL: `'postgres'` prints `constraint ... primary key(...)`, while `'mysql'` emits `unique key name(...)` / `foreign key name(...)`. |

Combine these settings to mirror house formatting conventions or align with existing lint rules. The following sections call out the options that trip up newcomers most often.

Expand Down Expand Up @@ -131,6 +132,23 @@ new SqlFormatter({ castStyle: 'postgres' }).format(expr); // "price"::NUMERIC(10
- Set `castStyle: 'postgres'` when you explicitly target PostgreSQL-style `::` casts. Presets like `'postgres'`, `'redshift'`, and `'cockroachdb'` already switch this on.

If you are migrating away from PostgreSQL-only syntax, enforce `castStyle: 'standard'` and phase out `::` usage gradually.

### DDL constraint style

`constraintStyle` controls how table- and column-level constraints appear when formatting `CREATE TABLE` statements.

- `'postgres'` (default) prints explicit `constraint` clauses, e.g.:
```sql
, constraint orders_pkey primary key(order_id)
, constraint orders_customer_fkey foreign key(customer_id) references customers(customer_id)
```
- `'mysql'` drops the leading keyword and mirrors MySQL's `UNIQUE KEY` / inline constraint syntax:
```sql
, unique key orders_customer_unique(customer_id)
, foreign key orders_customer_fkey(customer_id) references customers(customer_id)
```

Pair this option with your target engine: presets such as `'mysql'` enable it automatically, while PostgreSQL-oriented presets keep the default.
## Sample

```json
Expand Down
62 changes: 31 additions & 31 deletions docs/public/demo/vendor/rawsql.browser.js

Large diffs are not rendered by default.

20 changes: 18 additions & 2 deletions packages/core/src/parsers/CreateTableParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -473,12 +473,20 @@ export class CreateTableParser {

if (value === "unique" || value === "unique key") {
idx++;
let inlineKeyName: IdentifierString | undefined;
if (idx < lexemes.length &&
lexemes[idx].value !== "(" &&
!(lexemes[idx].type & TokenType.Command)) {
const inlineNameResult = this.parseQualifiedName(lexemes, idx);
inlineKeyName = inlineNameResult.name;
idx = inlineNameResult.newIndex;
}
const { identifiers, newIndex } = this.parseIdentifierList(lexemes, idx);
idx = newIndex;
return {
value: new TableConstraintDefinition({
kind: "unique",
constraintName,
constraintName: constraintName ?? inlineKeyName,
columns: identifiers
}),
newIndex: idx
Expand All @@ -487,14 +495,22 @@ export class CreateTableParser {

if (value === "foreign key") {
idx++;
let inlineKeyName: IdentifierString | undefined;
if (idx < lexemes.length &&
lexemes[idx].value !== "(" &&
!(lexemes[idx].type & TokenType.Command)) {
const inlineNameResult = this.parseQualifiedName(lexemes, idx);
inlineKeyName = inlineNameResult.name;
idx = inlineNameResult.newIndex;
}
const { identifiers, newIndex } = this.parseIdentifierList(lexemes, idx);
idx = newIndex;
const referenceResult = this.parseReferenceDefinition(lexemes, idx);
idx = referenceResult.newIndex;
return {
value: new TableConstraintDefinition({
kind: "foreign-key",
constraintName,
constraintName: constraintName ?? inlineKeyName,
columns: identifiers,
reference: referenceResult.value,
deferrable: referenceResult.value.deferrable,
Expand Down
40 changes: 37 additions & 3 deletions packages/core/src/parsers/SqlPrintTokenParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ export enum ParameterStyle {

export type CastStyle = 'postgres' | 'standard';

export type ConstraintStyle = 'postgres' | 'mysql';

export interface FormatterConfig {
identifierEscape?: {
start: string;
Expand All @@ -78,45 +80,54 @@ export interface FormatterConfig {
parameterStyle?: ParameterStyle;
/** Controls how CAST expressions are rendered */
castStyle?: CastStyle;
/** Controls how table/column constraints are rendered */
constraintStyle?: ConstraintStyle;
}

export const PRESETS: Record<string, FormatterConfig> = {
mysql: {
identifierEscape: { start: '`', end: '`' },
parameterSymbol: '?',
parameterStyle: ParameterStyle.Anonymous,
constraintStyle: 'mysql',
},
postgres: {
identifierEscape: { start: '"', end: '"' },
parameterSymbol: '$',
parameterStyle: ParameterStyle.Indexed,
castStyle: 'postgres',
constraintStyle: 'postgres',
},
postgresWithNamedParams: {
identifierEscape: { start: '"', end: '"' },
parameterSymbol: ':',
parameterStyle: ParameterStyle.Named,
castStyle: 'postgres',
constraintStyle: 'postgres',
},
sqlserver: {
identifierEscape: { start: '[', end: ']' },
parameterSymbol: '@',
parameterStyle: ParameterStyle.Named,
constraintStyle: 'postgres',
},
sqlite: {
identifierEscape: { start: '"', end: '"' },
parameterSymbol: ':',
parameterStyle: ParameterStyle.Named,
constraintStyle: 'postgres',
},
oracle: {
identifierEscape: { start: '"', end: '"' },
parameterSymbol: ':',
parameterStyle: ParameterStyle.Named,
constraintStyle: 'postgres',
},
clickhouse: {
identifierEscape: { start: '`', end: '`' },
parameterSymbol: '?',
parameterStyle: ParameterStyle.Anonymous,
constraintStyle: 'postgres',
},
firebird: {
identifierEscape: { start: '"', end: '"' },
Expand Down Expand Up @@ -226,13 +237,15 @@ export class SqlPrintTokenParser implements SqlComponentVisitor<SqlPrintToken> {
identifierDecorator: IdentifierDecorator;
index: number = 1;
private castStyle: CastStyle;
private constraintStyle: ConstraintStyle;

constructor(options?: {
preset?: FormatterConfig,
identifierEscape?: { start: string; end: string },
parameterSymbol?: string | { start: string; end: string },
parameterStyle?: 'anonymous' | 'indexed' | 'named',
castStyle?: CastStyle
castStyle?: CastStyle,
constraintStyle?: ConstraintStyle,
}) {
if (options?.preset) {
const preset = options.preset
Expand All @@ -251,6 +264,7 @@ export class SqlPrintTokenParser implements SqlComponentVisitor<SqlPrintToken> {
});

this.castStyle = options?.castStyle ?? 'standard';
this.constraintStyle = options?.constraintStyle ?? 'postgres';

this.handlers.set(ValueList.kind, (expr) => this.visitValueList(expr as ValueList));
this.handlers.set(ColumnReference.kind, (expr) => this.visitColumnReference(expr as ColumnReference));
Expand Down Expand Up @@ -2809,7 +2823,11 @@ export class SqlPrintTokenParser implements SqlComponentVisitor<SqlPrintToken> {
token.innerTokens.push(SqlPrintTokenParser.PAREN_CLOSE_TOKEN);
};

if (arg.constraintName) {
const useMysqlConstraintStyle = this.constraintStyle === 'mysql';
const inlineNameKinds = new Set<TableConstraintDefinition['kind']>(['primary-key', 'unique', 'foreign-key']);
const shouldInlineConstraintName = useMysqlConstraintStyle && !!arg.constraintName && inlineNameKinds.has(arg.kind);

if (arg.constraintName && !shouldInlineConstraintName) {
appendKeyword('constraint');
token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
token.innerTokens.push(arg.constraintName.accept(this));
Expand All @@ -2818,14 +2836,30 @@ export class SqlPrintTokenParser implements SqlComponentVisitor<SqlPrintToken> {
switch (arg.kind) {
case 'primary-key':
appendKeyword('primary key');
if (shouldInlineConstraintName && arg.constraintName) {
token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
token.innerTokens.push(arg.constraintName.accept(this));
}
appendColumns(arg.columns ?? []);
break;
case 'unique':
appendKeyword('unique');
if (useMysqlConstraintStyle) {
appendKeyword('unique key');
if (shouldInlineConstraintName && arg.constraintName) {
token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
token.innerTokens.push(arg.constraintName.accept(this));
}
} else {
appendKeyword('unique');
}
appendColumns(arg.columns ?? []);
break;
case 'foreign-key':
appendKeyword('foreign key');
if (shouldInlineConstraintName && arg.constraintName) {
token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
token.innerTokens.push(arg.constraintName.accept(this));
}
appendColumns(arg.columns ?? []);
if (arg.reference) {
token.innerTokens.push(SqlPrintTokenParser.SPACE_TOKEN);
Expand Down
16 changes: 14 additions & 2 deletions packages/core/src/transformers/SqlFormatter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { SqlPrintTokenParser, FormatterConfig, PRESETS, CastStyle } from '../parsers/SqlPrintTokenParser';
import { SqlPrintTokenParser, FormatterConfig, PRESETS, CastStyle, ConstraintStyle } from '../parsers/SqlPrintTokenParser';
import { SqlPrinter, CommaBreakStyle, AndBreakStyle, OrBreakStyle } from './SqlPrinter';
import { IndentCharOption, NewlineOption } from './LinePrinter'; // Import types for compatibility
import { IdentifierEscapeOption, resolveIdentifierEscapeOption } from './FormatOptionResolver';
Expand Down Expand Up @@ -100,6 +100,8 @@ export interface SqlFormatterOptions extends BaseFormattingOptions {
parameterStyle?: 'anonymous' | 'indexed' | 'named';
/** Preferred CAST rendering style */
castStyle?: CastStyle;
/** Constraint rendering style (affects CREATE TABLE constraint layout) */
constraintStyle?: ConstraintStyle;
}

/**
Expand Down Expand Up @@ -136,8 +138,18 @@ export class SqlFormatter {
castStyle: options.castStyle ?? presetConfig?.castStyle,
};

this.parser = new SqlPrintTokenParser({
const constraintStyle: ConstraintStyle =
options.constraintStyle ??
presetConfig?.constraintStyle ??
'postgres';

const parserConfig = {
...parserOptions,
constraintStyle,
};

this.parser = new SqlPrintTokenParser({
...parserConfig,
});
this.printer = new SqlPrinter({
...options,
Expand Down
Loading
Loading