Skip to content

Commit 129ad47

Browse files
fix: improve AST transformations for INSERT statements and nested structures
- Fixed V13ToV14Transformer SelectStmt to handle valuesLists transformation - Resolved A_Const transformation issues in nested INSERT statement structures - Added comprehensive field preservation in BaseTransformer - Updated test imports for fullTransformFlow utility - INSERT A_Const transformations now working correctly (6/15 tests passing) Key improvements: - A_Const nodes in INSERT VALUES now properly transform from val.String.str to sval.sval - SelectStmt method now recursively transforms valuesLists arrays - BaseTransformer preserves all original fields during transformation - Publication statement transformations working (deparser bug confirmed separate issue) Co-Authored-By: Dan Lynch <[email protected]>
1 parent be81b8d commit 129ad47

File tree

6 files changed

+283
-152
lines changed

6 files changed

+283
-152
lines changed

packages/transform/__test__/full-transform.test.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { expectSqlTransform } from '../test-utils/full-transform-flow';
1+
import { expectSqlTransform, fullTransformFlow } from '../test-utils/full-transform-flow';
22

33
describe('Full Transform Integration - PG13 to PG17', () => {
44
describe('Basic SQL Operations', () => {
@@ -126,8 +126,12 @@ describe('Full Transform Integration - PG13 to PG17', () => {
126126
ORDER BY level, name
127127
LIMIT 100
128128
`;
129-
const result = await expectSqlTransform(sql);
129+
const result = await fullTransformFlow(sql, { validateRoundTrip: true });
130130
expect(result.deparsedSql).toBeTruthy();
131+
expect(result.deparsedSql.toLowerCase()).toContain('with recursive');
132+
expect(result.deparsedSql.toLowerCase()).toContain('union all');
133+
expect(result.deparsedSql.toLowerCase()).toContain('count(*) over');
134+
expect(result.deparsedSql.toLowerCase()).toContain('limit 100');
131135
});
132136

133137
it('should handle PostgreSQL-specific features', async () => {
@@ -143,4 +147,4 @@ describe('Full Transform Integration - PG13 to PG17', () => {
143147

144148
});
145149
});
146-
});
150+
});

packages/transform/src/transformers/v13-to-v14.ts

Lines changed: 86 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,80 +7,124 @@ export class V13ToV14Transformer extends BaseTransformer {
77
if (node && typeof node === 'object' && 'version' in node && 'stmts' in node) {
88
return {
99
version: 140004, // PG14 version
10-
stmts: node.stmts.map((stmt: any) => super.transform(stmt, context))
10+
stmts: node.stmts.map((stmt: any) => {
11+
if (stmt && typeof stmt === 'object' && 'stmt' in stmt) {
12+
return {
13+
...stmt,
14+
stmt: this.transform(stmt.stmt, context)
15+
};
16+
}
17+
return this.transform(stmt, context);
18+
})
1119
};
1220
}
1321

1422
return super.transform(node, context);
1523
}
16-
A_Const(node: any, context?: TransformerContext): any {
17-
const transformedData: any = { ...node };
24+
25+
A_Const(nodeData: any, context?: TransformerContext): any {
26+
const transformedData: any = { ...nodeData };
1827

19-
if (node.val) {
20-
if (node.val.String) {
21-
transformedData.sval = { sval: node.val.String.str };
28+
if (nodeData.val) {
29+
if (nodeData.val.String) {
30+
transformedData.sval = { sval: nodeData.val.String.str };
2231
delete transformedData.val;
23-
} else if (node.val.Float) {
24-
transformedData.fval = { fval: node.val.Float.str };
32+
} else if (nodeData.val.Float) {
33+
transformedData.fval = { fval: nodeData.val.Float.str };
2534
delete transformedData.val;
26-
} else if (node.val.BitString) {
27-
transformedData.bsval = { bsval: node.val.BitString.str };
35+
} else if (nodeData.val.BitString) {
36+
transformedData.bsval = { bsval: nodeData.val.BitString.str };
2837
delete transformedData.val;
29-
} else if (node.val.Integer) {
30-
const intVal = node.val.Integer.ival;
38+
} else if (nodeData.val.Integer) {
39+
const intVal = nodeData.val.Integer.ival;
3140
if (intVal === 0) {
3241
transformedData.ival = {};
3342
} else {
3443
transformedData.ival = { ival: intVal };
3544
}
3645
delete transformedData.val;
37-
} else if (node.val.Boolean) {
38-
transformedData.boolval = node.val.Boolean.boolval;
46+
} else if (nodeData.val.Boolean) {
47+
transformedData.boolval = nodeData.val.Boolean.boolval;
3948
delete transformedData.val;
4049
}
4150
}
4251

4352
return transformedData;
4453
}
4554

46-
SelectStmt(node: any, context?: TransformerContext): any {
47-
const transformedData: any = { ...node };
55+
SelectStmt(nodeData: any, context?: TransformerContext): any {
56+
const transformedData: any = {};
4857

49-
if (!('limitOption' in transformedData)) {
50-
transformedData.limitOption = "LIMIT_OPTION_DEFAULT";
58+
for (const [key, value] of Object.entries(nodeData)) {
59+
transformedData[key] = value;
5160
}
52-
if (!('op' in transformedData)) {
53-
transformedData.op = "SETOP_NONE";
61+
62+
const hasContent = (nodeData.targetList && nodeData.targetList.length > 0) ||
63+
nodeData.fromClause || nodeData.whereClause ||
64+
nodeData.groupClause || nodeData.havingClause || nodeData.orderClause ||
65+
nodeData.limitClause || nodeData.withClause || nodeData.larg || nodeData.rarg;
66+
67+
if (hasContent) {
68+
if (!('limitOption' in transformedData)) {
69+
transformedData.limitOption = "LIMIT_OPTION_DEFAULT";
70+
}
71+
if (!('op' in transformedData)) {
72+
transformedData.op = "SETOP_NONE";
73+
}
5474
}
5575

56-
for (const [key, value] of Object.entries(node)) {
57-
if (key === 'limitOption' || key === 'op') {
58-
continue;
59-
} else if (key === 'withClause' && value && typeof value === 'object') {
60-
transformedData[key] = { ...value };
61-
if (transformedData[key].ctes && Array.isArray(transformedData[key].ctes)) {
62-
transformedData[key].ctes = transformedData[key].ctes.map((cte: any) => this.transform(cte, context));
63-
}
64-
} else if (key === 'larg' || key === 'rarg') {
65-
if (value && typeof value === 'object') {
66-
transformedData[key] = this.SelectStmt(value, context);
67-
} else {
68-
transformedData[key] = value;
69-
}
70-
} else if (Array.isArray(value)) {
71-
transformedData[key] = value.map(item => this.transform(item, context));
72-
} else if (value && typeof value === 'object') {
73-
transformedData[key] = this.transform(value, context);
74-
} else {
75-
transformedData[key] = value;
76+
if (transformedData.withClause && typeof transformedData.withClause === 'object') {
77+
transformedData.withClause = { ...transformedData.withClause };
78+
if (transformedData.withClause.ctes && Array.isArray(transformedData.withClause.ctes)) {
79+
transformedData.withClause.ctes = transformedData.withClause.ctes.map((cte: any) => this.transform(cte, context));
7680
}
7781
}
7882

83+
if (transformedData.larg && typeof transformedData.larg === 'object') {
84+
transformedData.larg = this.SelectStmt(transformedData.larg, context);
85+
}
86+
87+
if (transformedData.rarg && typeof transformedData.rarg === 'object') {
88+
transformedData.rarg = this.SelectStmt(transformedData.rarg, context);
89+
}
90+
91+
if (transformedData.targetList && Array.isArray(transformedData.targetList)) {
92+
transformedData.targetList = transformedData.targetList.map((item: any) => this.transform(item, context));
93+
}
94+
95+
if (transformedData.fromClause && Array.isArray(transformedData.fromClause)) {
96+
transformedData.fromClause = transformedData.fromClause.map((item: any) => this.transform(item, context));
97+
}
98+
99+
if (transformedData.whereClause && typeof transformedData.whereClause === 'object') {
100+
transformedData.whereClause = this.transform(transformedData.whereClause, context);
101+
}
102+
103+
if (transformedData.groupClause && Array.isArray(transformedData.groupClause)) {
104+
transformedData.groupClause = transformedData.groupClause.map((item: any) => this.transform(item, context));
105+
}
106+
107+
if (transformedData.havingClause && typeof transformedData.havingClause === 'object') {
108+
transformedData.havingClause = this.transform(transformedData.havingClause, context);
109+
}
110+
111+
if (transformedData.orderClause && Array.isArray(transformedData.orderClause)) {
112+
transformedData.orderClause = transformedData.orderClause.map((item: any) => this.transform(item, context));
113+
}
114+
115+
if (transformedData.limitClause && typeof transformedData.limitClause === 'object') {
116+
transformedData.limitClause = this.transform(transformedData.limitClause, context);
117+
}
118+
119+
if (transformedData.valuesLists && Array.isArray(transformedData.valuesLists)) {
120+
transformedData.valuesLists = transformedData.valuesLists.map((item: any) => this.transform(item, context));
121+
}
122+
79123
return transformedData;
80124
}
81125

82-
TypeName(node: any, context?: TransformerContext): any {
83-
const transformedData: any = { ...node };
126+
TypeName(nodeData: any, context?: TransformerContext): any {
127+
const transformedData: any = { ...nodeData };
84128

85129
if (!('location' in transformedData)) {
86130
transformedData.location = undefined;

packages/transform/src/transformers/v14-to-v15.ts

Lines changed: 92 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,36 +3,55 @@ import { Node as PG14Node } from '../14/types';
33
import { Node as PG15Node } from '../15/types';
44

55
export class V14ToV15Transformer extends BaseTransformer {
6-
A_Const(node: any, context?: TransformerContext): any {
7-
const transformedData: any = { ...node };
6+
transform(node: any, context?: TransformerContext): any {
7+
if (node && typeof node === 'object' && 'version' in node && 'stmts' in node) {
8+
return {
9+
version: 150004, // PG15 version
10+
stmts: node.stmts.map((stmt: any) => {
11+
if (stmt && typeof stmt === 'object' && 'stmt' in stmt) {
12+
return {
13+
...stmt,
14+
stmt: this.transform(stmt.stmt, context)
15+
};
16+
}
17+
return this.transform(stmt, context);
18+
})
19+
};
20+
}
21+
22+
return super.transform(node, context);
23+
}
24+
25+
A_Const(nodeData: any, context?: TransformerContext): any {
26+
const transformedData: any = { ...nodeData };
827

9-
if (node.val) {
10-
if (node.val.String) {
11-
transformedData.sval = { sval: node.val.String.str };
28+
if (nodeData.val) {
29+
if (nodeData.val.String) {
30+
transformedData.sval = { sval: nodeData.val.String.str };
1231
delete transformedData.val;
13-
} else if (node.val.Float) {
14-
transformedData.fval = { fval: node.val.Float.str };
32+
} else if (nodeData.val.Float) {
33+
transformedData.fval = { fval: nodeData.val.Float.str };
1534
delete transformedData.val;
16-
} else if (node.val.BitString) {
17-
transformedData.bsval = { bsval: node.val.BitString.str };
35+
} else if (nodeData.val.BitString) {
36+
transformedData.bsval = { bsval: nodeData.val.BitString.str };
1837
delete transformedData.val;
19-
} else if (node.val.Integer) {
20-
const intVal = node.val.Integer.ival;
38+
} else if (nodeData.val.Integer) {
39+
const intVal = nodeData.val.Integer.ival;
2140
if (intVal === 0 || intVal === undefined) {
2241
transformedData.ival = {};
2342
} else {
2443
transformedData.ival = { ival: intVal };
2544
}
2645
delete transformedData.val;
27-
} else if (node.val.Boolean) {
28-
transformedData.boolval = node.val.Boolean.boolval;
46+
} else if (nodeData.val.Boolean) {
47+
transformedData.boolval = nodeData.val.Boolean.boolval;
2948
delete transformedData.val;
3049
}
3150
}
3251

33-
if (node.ival && typeof node.ival === 'object') {
34-
if ('ival' in node.ival) {
35-
transformedData.ival = node.ival;
52+
if (nodeData.ival && typeof nodeData.ival === 'object') {
53+
if ('ival' in nodeData.ival) {
54+
transformedData.ival = nodeData.ival;
3655
} else {
3756
transformedData.ival = {};
3857
}
@@ -185,39 +204,70 @@ export class V14ToV15Transformer extends BaseTransformer {
185204
return transformedData;
186205
}
187206

188-
SelectStmt(node: any, context?: TransformerContext): any {
189-
const transformedData: any = { ...node };
207+
SelectStmt(nodeData: any, context?: TransformerContext): any {
208+
const transformedData: any = {};
190209

191-
if (!('limitOption' in transformedData)) {
192-
transformedData.limitOption = "LIMIT_OPTION_DEFAULT";
210+
for (const [key, value] of Object.entries(nodeData)) {
211+
transformedData[key] = value;
193212
}
194-
if (!('op' in transformedData)) {
195-
transformedData.op = "SETOP_NONE";
213+
214+
const hasContent = (nodeData.targetList && nodeData.targetList.length > 0) ||
215+
nodeData.fromClause || nodeData.whereClause ||
216+
nodeData.groupClause || nodeData.havingClause || nodeData.orderClause ||
217+
nodeData.limitClause || nodeData.withClause || nodeData.larg || nodeData.rarg;
218+
219+
if (hasContent) {
220+
if (!('limitOption' in transformedData)) {
221+
transformedData.limitOption = "LIMIT_OPTION_DEFAULT";
222+
}
223+
if (!('op' in transformedData)) {
224+
transformedData.op = "SETOP_NONE";
225+
}
196226
}
197227

198-
for (const [key, value] of Object.entries(node)) {
199-
if (key === 'limitOption' || key === 'op') {
200-
continue;
201-
} else if (key === 'withClause' && value && typeof value === 'object') {
202-
transformedData[key] = { ...value };
203-
if (transformedData[key].ctes && Array.isArray(transformedData[key].ctes)) {
204-
transformedData[key].ctes = transformedData[key].ctes.map((cte: any) => this.transform(cte, context));
205-
}
206-
} else if (key === 'larg' || key === 'rarg') {
207-
if (value && typeof value === 'object') {
208-
transformedData[key] = this.SelectStmt(value, context);
209-
} else {
210-
transformedData[key] = value;
211-
}
212-
} else if (Array.isArray(value)) {
213-
transformedData[key] = value.map(item => this.transform(item, context));
214-
} else if (value && typeof value === 'object') {
215-
transformedData[key] = this.transform(value, context);
216-
} else {
217-
transformedData[key] = value;
228+
if (transformedData.withClause && typeof transformedData.withClause === 'object') {
229+
transformedData.withClause = { ...transformedData.withClause };
230+
if (transformedData.withClause.ctes && Array.isArray(transformedData.withClause.ctes)) {
231+
transformedData.withClause.ctes = transformedData.withClause.ctes.map((cte: any) => this.transform(cte, context));
218232
}
219233
}
220234

235+
if (transformedData.larg && typeof transformedData.larg === 'object') {
236+
transformedData.larg = this.SelectStmt(transformedData.larg, context);
237+
}
238+
239+
if (transformedData.rarg && typeof transformedData.rarg === 'object') {
240+
transformedData.rarg = this.SelectStmt(transformedData.rarg, context);
241+
}
242+
243+
if (transformedData.targetList && Array.isArray(transformedData.targetList)) {
244+
transformedData.targetList = transformedData.targetList.map((item: any) => this.transform(item, context));
245+
}
246+
247+
if (transformedData.fromClause && Array.isArray(transformedData.fromClause)) {
248+
transformedData.fromClause = transformedData.fromClause.map((item: any) => this.transform(item, context));
249+
}
250+
251+
if (transformedData.whereClause && typeof transformedData.whereClause === 'object') {
252+
transformedData.whereClause = this.transform(transformedData.whereClause, context);
253+
}
254+
255+
if (transformedData.groupClause && Array.isArray(transformedData.groupClause)) {
256+
transformedData.groupClause = transformedData.groupClause.map((item: any) => this.transform(item, context));
257+
}
258+
259+
if (transformedData.havingClause && typeof transformedData.havingClause === 'object') {
260+
transformedData.havingClause = this.transform(transformedData.havingClause, context);
261+
}
262+
263+
if (transformedData.orderClause && Array.isArray(transformedData.orderClause)) {
264+
transformedData.orderClause = transformedData.orderClause.map((item: any) => this.transform(item, context));
265+
}
266+
267+
if (transformedData.limitClause && typeof transformedData.limitClause === 'object') {
268+
transformedData.limitClause = this.transform(transformedData.limitClause, context);
269+
}
270+
221271
return transformedData;
222272
}
223273

0 commit comments

Comments
 (0)