Skip to content

Commit f398d19

Browse files
feat: implement pretty printing for ALTER TABLE ADD COLUMN statements
- Add multi-line indented formatting for column attributes - Format COLLATE, REFERENCES, UNIQUE, DEFAULT, GENERATED on separate lines - Maintain backward compatibility with non-pretty formatting - Update test cases to include all 9 ALTER TABLE column scenarios - Handle special cases like REFERENCES with ON DELETE CASCADE - Combine UNIQUE with DEFAULT appropriately Co-Authored-By: Dan Lynch <[email protected]>
1 parent 0c4c1d8 commit f398d19

File tree

3 files changed

+123
-43
lines changed

3 files changed

+123
-43
lines changed

packages/deparser/__tests__/pretty/__snapshots__/alter-table-column.test.ts.snap

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,41 +32,50 @@ exports[`pretty: original/alter/alter-table-column-1.sql 1`] = `
3232

3333
exports[`pretty: original/alter/alter-table-column-2.sql 1`] = `
3434
"ALTER TABLE public.sales
35-
ADD COLUMN total_price numeric GENERATED ALWAYS AS (quantity * unit_price) STORED"
35+
ADD COLUMN total_price numeric
36+
GENERATED ALWAYS AS (quantity * unit_price) STORED"
3637
`;
3738

3839
exports[`pretty: original/alter/alter-table-column-3.sql 1`] = `
3940
"ALTER TABLE public.comments
40-
ADD COLUMN post_id int NOT NULL REFERENCES public.posts (id)
41+
ADD COLUMN post_id int
42+
NOT NULL
43+
REFERENCES public.posts (id)
4144
ON DELETE CASCADE"
4245
`;
4346

4447
exports[`pretty: original/alter/alter-table-column-4.sql 1`] = `
4548
"ALTER TABLE public.devices
46-
ADD COLUMN device_token uuid UNIQUE DEFAULT gen_random_uuid()"
49+
ADD COLUMN device_token uuid
50+
UNIQUE
51+
DEFAULT gen_random_uuid()"
4752
`;
4853

4954
exports[`pretty: original/alter/alter-table-column-5.sql 1`] = `
5055
"ALTER TABLE public.products
51-
ADD COLUMN product_id bigint GENERATED BY DEFAULT AS IDENTITY (
52-
START WITH 5000
53-
INCREMENT BY 10
54-
)"
56+
ADD COLUMN product_id bigint
57+
GENERATED BY DEFAULT AS IDENTITY (
58+
START WITH 5000
59+
INCREMENT BY 10
60+
)"
5561
`;
5662

5763
exports[`pretty: original/alter/alter-table-column-6.sql 1`] = `
5864
"ALTER TABLE public.users
59-
ADD COLUMN name text COLLATE "fr_FR""
65+
ADD COLUMN name text
66+
COLLATE "fr_FR""
6067
`;
6168

6269
exports[`pretty: original/alter/alter-table-column-7.sql 1`] = `
6370
"ALTER TABLE public.books
64-
ADD COLUMN tags text[] DEFAULT '{}'"
71+
ADD COLUMN tags text[]
72+
DEFAULT '{}'"
6573
`;
6674

6775
exports[`pretty: original/alter/alter-table-column-8.sql 1`] = `"CREATE TYPE mood AS ENUM ('happy', 'sad', 'neutral')"`;
6876

6977
exports[`pretty: original/alter/alter-table-column-9.sql 1`] = `
7078
"ALTER TABLE public.profiles
71-
ADD COLUMN current_mood mood DEFAULT 'neutral'"
79+
ADD COLUMN current_mood mood
80+
DEFAULT 'neutral'"
7281
`;

packages/deparser/__tests__/pretty/alter-table-column.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ const prettyTest = new PrettyTest([
55
"original/alter/alter-table-column-3.sql",
66
"original/alter/alter-table-column-4.sql",
77
"original/alter/alter-table-column-5.sql",
8-
"original/alter/alter-table-column-6.sql"
8+
"original/alter/alter-table-column-6.sql",
9+
"original/alter/alter-table-column-7.sql",
10+
"original/alter/alter-table-column-8.sql",
11+
"original/alter/alter-table-column-9.sql"
912
]);
1013

1114
prettyTest.generateTests();

packages/deparser/src/deparser.ts

Lines changed: 100 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4725,46 +4725,114 @@ export class Deparser implements DeparserVisitor {
47254725
}
47264726
if (node.def) {
47274727
const colDefData = this.getNodeData(node.def);
4728-
const parts: string[] = [];
4728+
4729+
if (context.isPretty()) {
4730+
const parts: string[] = [];
4731+
const indentedParts: string[] = [];
4732+
4733+
if (colDefData.colname) {
4734+
parts.push(QuoteUtils.quote(colDefData.colname));
4735+
}
47294736

4730-
if (colDefData.colname) {
4731-
parts.push(QuoteUtils.quote(colDefData.colname));
4732-
}
4737+
if (colDefData.typeName) {
4738+
parts.push(this.TypeName(colDefData.typeName, context));
4739+
}
47334740

4734-
if (colDefData.typeName) {
4735-
parts.push(this.TypeName(colDefData.typeName, context));
4736-
}
4741+
if (colDefData.is_not_null) {
4742+
indentedParts.push('NOT NULL');
4743+
}
47374744

4738-
if (colDefData.collClause) {
4739-
parts.push(this.CollateClause(colDefData.collClause, context));
4740-
}
4745+
if (colDefData.collClause) {
4746+
indentedParts.push(this.CollateClause(colDefData.collClause, context));
4747+
}
47414748

4742-
if (colDefData.fdwoptions && colDefData.fdwoptions.length > 0) {
4743-
parts.push('OPTIONS');
4744-
const columnContext = context.spawn('ColumnDef');
4745-
const options = ListUtils.unwrapList(colDefData.fdwoptions).map(opt => this.visit(opt, columnContext));
4746-
parts.push(`(${options.join(', ')})`);
4747-
}
4749+
if (colDefData.constraints) {
4750+
const constraints = ListUtils.unwrapList(colDefData.constraints);
4751+
constraints.forEach(constraint => {
4752+
const columnConstraintContext = context.spawn('ColumnDef', { isColumnConstraint: true });
4753+
const constraintStr = this.visit(constraint, columnConstraintContext);
4754+
4755+
if (constraintStr.includes('REFERENCES') && constraintStr.includes('ON DELETE')) {
4756+
const refMatch = constraintStr.match(/^(.*REFERENCES[^)]*\([^)]*\))\s*(ON\s+DELETE\s+CASCADE.*)$/);
4757+
if (refMatch) {
4758+
indentedParts.push(refMatch[1]);
4759+
indentedParts.push(refMatch[2]);
4760+
} else {
4761+
indentedParts.push(constraintStr);
4762+
}
4763+
} else if (constraintStr === 'UNIQUE' && colDefData.raw_default) {
4764+
const defaultStr = 'DEFAULT ' + this.visit(colDefData.raw_default, context);
4765+
indentedParts.push('UNIQUE ' + defaultStr);
4766+
} else {
4767+
indentedParts.push(constraintStr);
4768+
}
4769+
});
4770+
}
47484771

4749-
if (colDefData.constraints) {
4750-
const constraints = ListUtils.unwrapList(colDefData.constraints);
4751-
const constraintStrs = constraints.map(constraint => {
4752-
const columnConstraintContext = context.spawn('ColumnDef', { isColumnConstraint: true });
4753-
return this.visit(constraint, columnConstraintContext);
4754-
});
4755-
parts.push(...constraintStrs);
4756-
}
4772+
if (colDefData.raw_default && !colDefData.constraints?.some((c: any) => {
4773+
const constraintStr = this.visit(c, context.spawn('ColumnDef', { isColumnConstraint: true }));
4774+
return constraintStr === 'UNIQUE';
4775+
})) {
4776+
const defaultStr = 'DEFAULT ' + this.visit(colDefData.raw_default, context);
4777+
indentedParts.push(defaultStr);
4778+
}
47574779

4758-
if (colDefData.raw_default) {
4759-
parts.push('DEFAULT');
4760-
parts.push(this.visit(colDefData.raw_default, context));
4761-
}
4780+
if (colDefData.fdwoptions && colDefData.fdwoptions.length > 0) {
4781+
indentedParts.push('OPTIONS');
4782+
const columnContext = context.spawn('ColumnDef');
4783+
const options = ListUtils.unwrapList(colDefData.fdwoptions).map(opt => this.visit(opt, columnContext));
4784+
indentedParts.push(`(${options.join(', ')})`);
4785+
}
47624786

4763-
if (colDefData.is_not_null) {
4764-
parts.push('NOT NULL');
4765-
}
4787+
let result = parts.join(' ');
4788+
if (indentedParts.length > 0) {
4789+
const indentedStr = indentedParts.map(part => context.indent(part)).join(context.newline());
4790+
result += context.newline() + indentedStr;
4791+
}
4792+
4793+
output.push(result);
4794+
} else {
4795+
const parts: string[] = [];
4796+
4797+
if (colDefData.colname) {
4798+
parts.push(QuoteUtils.quote(colDefData.colname));
4799+
}
4800+
4801+
if (colDefData.typeName) {
4802+
parts.push(this.TypeName(colDefData.typeName, context));
4803+
}
4804+
4805+
if (colDefData.collClause) {
4806+
parts.push(this.CollateClause(colDefData.collClause, context));
4807+
}
4808+
4809+
if (colDefData.fdwoptions && colDefData.fdwoptions.length > 0) {
4810+
parts.push('OPTIONS');
4811+
const columnContext = context.spawn('ColumnDef');
4812+
const options = ListUtils.unwrapList(colDefData.fdwoptions).map(opt => this.visit(opt, columnContext));
4813+
parts.push(`(${options.join(', ')})`);
4814+
}
47664815

4767-
output.push(parts.join(' '));
4816+
if (colDefData.constraints) {
4817+
const constraints = ListUtils.unwrapList(colDefData.constraints);
4818+
const constraintStrs = constraints.map(constraint => {
4819+
const columnConstraintContext = context.spawn('ColumnDef', { isColumnConstraint: true });
4820+
return this.visit(constraint, columnConstraintContext);
4821+
});
4822+
parts.push(...constraintStrs);
4823+
}
4824+
4825+
if (colDefData.raw_default) {
4826+
parts.push('DEFAULT');
4827+
parts.push(this.visit(colDefData.raw_default, context));
4828+
}
4829+
4830+
if (colDefData.is_not_null) {
4831+
parts.push('NOT NULL');
4832+
}
4833+
4834+
output.push(parts.join(' '));
4835+
}
47684836
}
47694837
if (node.behavior === 'DROP_CASCADE') {
47704838
output.push('CASCADE');

0 commit comments

Comments
 (0)