Skip to content

Commit a3c2fc1

Browse files
feat: maintain stable 184/258 test baseline and add dual-parse exploration
- Reverted context-based shouldTransformEmptyIval to return null - Maintains stable 184/258 test pass rate (71.3% success) - Added comprehensive debug scripts for dual-parse approach exploration - Identified that PG15 produces ival: {} but PG16 expects ival: {ival: -3} for negatives - Context patterns identical for zero vs negative values, need sophisticated detection - Dual-parse approach most promising but requires implementation complexity Co-Authored-By: Dan Lynch <[email protected]>
1 parent eda607d commit a3c2fc1

File tree

4 files changed

+228
-1
lines changed

4 files changed

+228
-1
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
const { Parser } = require('@pgsql/parser');
2+
3+
async function testDualParseImplementation() {
4+
const parser15 = new Parser(15);
5+
const parser16 = new Parser(16);
6+
7+
console.log("=== DUAL-PARSE IMPLEMENTATION TEST ===");
8+
console.log("Testing the approach for detecting when empty ival objects need transformation\n");
9+
10+
const testCases = [
11+
{ sql: "insert into atacc2 (test2) values (-3)", expected: "should transform" },
12+
{ sql: "insert into atacc2 (test2) values (0)", expected: "should NOT transform" },
13+
{ sql: "ALTER TABLE onek ADD CONSTRAINT onek_check_constraint CHECK (unique1 >= -1)", expected: "should transform" },
14+
{ sql: "ALTER TABLE onek ADD CONSTRAINT onek_check_constraint CHECK (unique1 >= 0)", expected: "should NOT transform" }
15+
];
16+
17+
for (const testCase of testCases) {
18+
console.log(`=== SQL: ${testCase.sql} ===`);
19+
console.log(`Expected: ${testCase.expected}`);
20+
21+
try {
22+
const pg15Result = await parser15.parse(testCase.sql);
23+
const pg16Result = await parser16.parse(testCase.sql);
24+
25+
const pg15AConst = findAConstInAST(pg15Result);
26+
const pg16AConst = findAConstInAST(pg16Result);
27+
28+
if (pg15AConst && pg16AConst) {
29+
const pg15IsEmpty = pg15AConst.ival && Object.keys(pg15AConst.ival).length === 0;
30+
const pg16HasNested = pg16AConst.ival && typeof pg16AConst.ival.ival === 'number';
31+
32+
console.log("PG15 ival:", JSON.stringify(pg15AConst.ival));
33+
console.log("PG16 ival:", JSON.stringify(pg16AConst.ival));
34+
console.log("PG15 is empty:", pg15IsEmpty);
35+
console.log("PG16 has nested:", pg16HasNested);
36+
37+
if (pg15IsEmpty && pg16HasNested) {
38+
console.log(`✅ TRANSFORM NEEDED: {} -> {ival: ${pg16AConst.ival.ival}}`);
39+
} else if (pg15IsEmpty && !pg16HasNested) {
40+
console.log("✅ KEEP EMPTY: {} -> {}");
41+
} else {
42+
console.log("ℹ️ No transformation needed (not empty ival case)");
43+
}
44+
45+
console.log("");
46+
}
47+
48+
} catch (error) {
49+
console.error("Error:", error.message);
50+
}
51+
}
52+
53+
console.log("=== IMPLEMENTATION STRATEGY ===");
54+
console.log("1. Create helper method shouldTransformEmptyIval(sql) that:");
55+
console.log(" - Parses SQL with both PG15 and PG16");
56+
console.log(" - Compares A_Const ival structures");
57+
console.log(" - Returns transformation target if needed, null otherwise");
58+
console.log("2. Use this in A_Const method when encountering empty ival objects");
59+
console.log("3. Cache results to avoid re-parsing same SQL multiple times");
60+
}
61+
62+
function findAConstInAST(obj) {
63+
if (!obj || typeof obj !== 'object') return null;
64+
65+
if (obj.A_Const) return obj.A_Const;
66+
67+
for (const key in obj) {
68+
if (typeof obj[key] === 'object') {
69+
const result = findAConstInAST(obj[key]);
70+
if (result) return result;
71+
}
72+
}
73+
74+
return null;
75+
}
76+
77+
testDualParseImplementation();
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
const { Parser } = require('@pgsql/parser');
2+
const { V15ToV16Transformer } = require('./dist/transformers/v15-to-v16');
3+
4+
async function debugEmptyIvalDetection() {
5+
const parser15 = new Parser(15);
6+
const parser16 = new Parser(16);
7+
8+
console.log("=== EMPTY IVAL DETECTION STRATEGY ===");
9+
console.log("Goal: Detect when empty {} should become {ival: X} vs remain empty\n");
10+
11+
const testCases = [
12+
{ sql: "insert into atacc2 (test2) values (-3)", expected: "transform", desc: "negative integer" },
13+
{ sql: "insert into atacc2 (test2) values (0)", expected: "keep empty", desc: "zero value" },
14+
{ sql: "ALTER TABLE onek ADD CONSTRAINT onek_check_constraint CHECK (unique1 >= -1)", expected: "transform", desc: "negative in constraint" },
15+
{ sql: "ALTER TABLE onek ADD CONSTRAINT onek_check_constraint CHECK (unique1 >= 0)", expected: "keep empty", desc: "zero in constraint" }
16+
];
17+
18+
const transformationRules = [];
19+
20+
for (const testCase of testCases) {
21+
console.log(`=== ${testCase.desc.toUpperCase()}: ${testCase.sql} ===`);
22+
23+
try {
24+
const pg15Result = await parser15.parse(testCase.sql);
25+
const pg16Result = await parser16.parse(testCase.sql);
26+
27+
const pg15AConst = findAConstInAST(pg15Result);
28+
const pg16AConst = findAConstInAST(pg16Result);
29+
30+
if (pg15AConst && pg16AConst) {
31+
const pg15IsEmpty = pg15AConst.ival && Object.keys(pg15AConst.ival).length === 0;
32+
const pg16HasNested = pg16AConst.ival && typeof pg16AConst.ival.ival === 'number';
33+
34+
console.log("PG15 ival:", JSON.stringify(pg15AConst.ival));
35+
console.log("PG16 ival:", JSON.stringify(pg16AConst.ival));
36+
console.log("Should transform:", pg15IsEmpty && pg16HasNested);
37+
38+
if (pg15IsEmpty && pg16HasNested) {
39+
const targetValue = pg16AConst.ival.ival;
40+
transformationRules.push({
41+
sql: testCase.sql,
42+
targetValue: targetValue,
43+
pattern: `Transform {} to {ival: ${targetValue}}`
44+
});
45+
console.log(`✅ RULE: ${transformationRules[transformationRules.length - 1].pattern}`);
46+
} else if (pg15IsEmpty && !pg16HasNested) {
47+
console.log("✅ RULE: Keep {} as empty");
48+
}
49+
50+
console.log("");
51+
}
52+
53+
} catch (error) {
54+
console.error("Error:", error.message);
55+
}
56+
}
57+
58+
console.log("=== IMPLEMENTATION STRATEGY ===");
59+
console.log("Since we can't easily implement async dual-parse in transformer:");
60+
console.log("1. Create a synchronous helper that uses SQL pattern matching");
61+
console.log("2. Extract numeric patterns from context or SQL fragments");
62+
console.log("3. Use heuristics based on common negative integer patterns");
63+
console.log("4. Implement conservative transformation that only handles clear cases");
64+
65+
console.log("\n=== DISCOVERED TRANSFORMATION RULES ===");
66+
transformationRules.forEach((rule, i) => {
67+
console.log(`${i + 1}. ${rule.pattern} (from: ${rule.sql.substring(0, 50)}...)`);
68+
});
69+
70+
console.log("\n=== NEXT STEPS ===");
71+
console.log("Implement targeted A_Const fix that:");
72+
console.log("- Detects empty ival objects");
73+
console.log("- Uses context clues to determine if transformation needed");
74+
console.log("- Only transforms when confident it's a negative integer case");
75+
console.log("- Preserves zero values as empty objects");
76+
}
77+
78+
function findAConstInAST(obj) {
79+
if (!obj || typeof obj !== 'object') return null;
80+
81+
if (obj.A_Const) return obj.A_Const;
82+
83+
for (const key in obj) {
84+
if (typeof obj[key] === 'object') {
85+
const result = findAConstInAST(obj[key]);
86+
if (result) return result;
87+
}
88+
}
89+
90+
return null;
91+
}
92+
93+
debugEmptyIvalDetection();
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
const { V15ToV16Transformer } = require('./dist/transformers/v15-to-v16');
2+
3+
function debugTargetedFix() {
4+
console.log("=== TARGETED A_CONST FIX APPROACH ===");
5+
console.log("Strategy: Only transform empty ival objects in specific contexts where confident it's negative\n");
6+
7+
const transformer = new V15ToV16Transformer();
8+
9+
const testNode = {
10+
A_Const: {
11+
ival: {}, // Empty object that PG15 produces for negative integers
12+
location: 63
13+
}
14+
};
15+
16+
console.log("=== TEST CASE: Empty ival object ===");
17+
console.log("Input node:", JSON.stringify(testNode, null, 2));
18+
19+
const context = { parentNodeTypes: ['TypeName', 'ColumnDef', 'CreateStmt'] };
20+
const result = transformer.transform(testNode, context);
21+
22+
console.log("Transformed result:", JSON.stringify(result, null, 2));
23+
console.log("Expected PG16 result: { A_Const: { ival: { ival: -3 }, location: 63 } }");
24+
25+
console.log("\n=== ANALYSIS ===");
26+
const hasEmptyIval = result.A_Const && result.A_Const.ival &&
27+
typeof result.A_Const.ival === 'object' &&
28+
Object.keys(result.A_Const.ival).length === 0;
29+
30+
console.log("Result has empty ival:", hasEmptyIval);
31+
console.log("Transformation needed:", hasEmptyIval ? "YES" : "NO");
32+
33+
if (hasEmptyIval) {
34+
console.log("\n=== PROPOSED FIX ===");
35+
console.log("Detect empty ival objects in A_Const and transform to nested structure");
36+
console.log("Use context clues or heuristics to determine appropriate negative value");
37+
console.log("Conservative approach: only transform when confident it's a negative integer case");
38+
}
39+
}
40+
41+
debugTargetedFix();

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

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
11
import * as PG15 from '../15/types';
22
import { TransformerContext } from './context';
3+
import { Parser } from '@pgsql/parser';
34

45
/**
56
* V15 to V16 AST Transformer
67
* Transforms PostgreSQL v15 AST nodes to v16 format
78
*/
89
export class V15ToV16Transformer {
10+
private parser15 = new Parser(15);
11+
private parser16 = new Parser(16);
12+
private transformationCache = new Map<string, any>();
13+
14+
private shouldTransformEmptyIval(context: TransformerContext): { ival: number } | null {
15+
return null;
16+
}
917

1018
transform(node: PG15.Node, context: TransformerContext = { parentNodeTypes: [] }): any {
1119
if (node == null) {
@@ -530,7 +538,15 @@ export class V15ToV16Transformer {
530538
}
531539

532540
if (result.ival !== undefined) {
533-
result.ival = this.transform(result.ival as any, context);
541+
// Handle case where PG15 produces empty ival objects for negative integers
542+
if (typeof result.ival === 'object' && Object.keys(result.ival).length === 0) {
543+
const transformedIval = this.shouldTransformEmptyIval(context);
544+
if (transformedIval) {
545+
result.ival = transformedIval;
546+
}
547+
} else {
548+
result.ival = this.transform(result.ival as any, context);
549+
}
534550
}
535551

536552
if (result.fval !== undefined) {

0 commit comments

Comments
 (0)