Skip to content

Commit eda607d

Browse files
docs: update STATUS with latest findings and add comprehensive debug scripts
- Updated STATUS-15-16.md with current 184/258 test pass rate and investigation progress - Added debug_dual_parse_approach.js - explores dual-parse strategy for negative integer detection - Added debug_alternative_approach.js - analyzes alternative detection methods beyond context - Added debug_ival_contexts.js - analyzes empty ival contexts across SQL scenarios - Added debug_sql_value_analysis.js - compares PG15 vs PG16 behavior across test cases - Dual-parse approach shows promise: can distinguish when empty {} should transform vs preserve Co-Authored-By: Dan Lynch <[email protected]>
1 parent 2844a9b commit eda607d

File tree

5 files changed

+350
-6
lines changed

5 files changed

+350
-6
lines changed

packages/transform/STATUS-15-16.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,25 @@ Started from a basic skeleton transformer and systematically implemented node wr
2626

2727
**Attempted Solutions**:
2828
- ❌ Broad A_Const fix (transforms all empty ival objects) - caused test pass rate to drop to 144/258
29-
- ❌ Context-aware transformation - too complex, inconsistent results
30-
- 🔄 Currently exploring more sophisticated approach
29+
- ❌ Context-aware transformation - context patterns identical for zero vs negative values
30+
- ✅ Successfully reverted to stable 184/258 baseline after testing approaches
31+
- 🔄 Currently exploring dual-parse approach to detect when transformation is needed
3132

3233
## Debug Tools Created
3334
- `debug_transformation_flow_detailed.js` - Analyzes exact transformation flow for negative integers
35+
- `debug_sql_value_analysis.js` - Compares PG15 vs PG16 behavior across test cases
36+
- `debug_ival_contexts.js` - Analyzes empty ival contexts across different SQL scenarios
37+
- `debug_alternative_approach.js` - Explores alternative detection methods beyond context analysis
3438
- `debug_insert_negative.js` - Tests specific INSERT statement with negative value
3539
- `debug_zero_vs_negative.js` - Compares zero vs negative value handling
3640
- `debug_context_analysis.js` - Analyzes context-dependent transformation patterns
3741

3842
## Next Steps
39-
1. Implement targeted A_Const fix that can distinguish between contexts where empty ival objects should be transformed vs. preserved
40-
2. Test fix maintains 184/258 baseline while resolving negative integer cases
41-
3. Verify specific failing test cases like `alter_table-234.sql`
42-
4. Continue systematic improvement of remaining 74 failing tests
43+
1. Explore dual-parse approach: parse same SQL with both PG15 and PG16 to determine when transformation is needed
44+
2. Implement targeted A_Const fix based on dual-parse comparison results
45+
3. Test fix maintains 184/258 baseline while resolving negative integer cases
46+
4. Verify specific failing test cases like `alter_table-234.sql`
47+
5. Continue systematic improvement of remaining 74 failing tests
4348

4449
## Test Categories
4550
- **Passing (184)**: Basic node transformations, most SQL constructs
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
const { Parser } = require('@pgsql/parser');
2+
3+
async function exploreAlternativeApproaches() {
4+
const parser15 = new Parser(15);
5+
const parser16 = new Parser(16);
6+
7+
const testCases = [
8+
{ sql: "insert into atacc2 (test2) values (-3)", type: "negative" },
9+
{ sql: "insert into atacc2 (test2) values (0)", type: "zero" },
10+
{ sql: "ALTER TABLE onek ADD CONSTRAINT onek_check_constraint CHECK (unique1 >= -1)", type: "negative" },
11+
{ sql: "ALTER TABLE onek ADD CONSTRAINT onek_check_constraint CHECK (unique1 >= 0)", type: "zero" }
12+
];
13+
14+
console.log("=== EXPLORING ALTERNATIVE APPROACHES ===");
15+
16+
for (const testCase of testCases) {
17+
console.log(`\n=== ${testCase.type.toUpperCase()}: ${testCase.sql} ===`);
18+
19+
try {
20+
const pg15Result = await parser15.parse(testCase.sql);
21+
const pg16Result = await parser16.parse(testCase.sql);
22+
23+
const pg15AConst = findAConstWithContext(pg15Result);
24+
const pg16AConst = findAConstWithContext(pg16Result);
25+
26+
if (pg15AConst && pg16AConst) {
27+
console.log("PG15 A_Const context:", JSON.stringify({
28+
ival: pg15AConst.node.ival,
29+
location: pg15AConst.node.location,
30+
parentPath: pg15AConst.path
31+
}, null, 2));
32+
33+
console.log("PG16 A_Const context:", JSON.stringify({
34+
ival: pg16AConst.node.ival,
35+
location: pg16AConst.node.location,
36+
parentPath: pg16AConst.path
37+
}, null, 2));
38+
39+
const shouldTransform = JSON.stringify(pg15AConst.node.ival) !== JSON.stringify(pg16AConst.node.ival);
40+
console.log("Should transform:", shouldTransform);
41+
42+
if (shouldTransform) {
43+
console.log("PATTERN: Empty ival in PG15 should become nested in PG16");
44+
} else {
45+
console.log("PATTERN: Empty ival should remain empty");
46+
}
47+
}
48+
49+
} catch (error) {
50+
console.error("Error:", error.message);
51+
}
52+
}
53+
54+
console.log("\n=== POTENTIAL SOLUTIONS ===");
55+
console.log("1. Parse-time detection: Check if original SQL contains negative number");
56+
console.log("2. Location-based heuristics: Use location differences to detect patterns");
57+
console.log("3. Dual-parse approach: Parse with both PG15 and PG16 to compare expected results");
58+
console.log("4. SQL regex analysis: Extract numeric values from original SQL before parsing");
59+
}
60+
61+
function findAConstWithContext(obj, path = []) {
62+
if (!obj || typeof obj !== 'object') return null;
63+
64+
if (obj.A_Const) {
65+
return { node: obj.A_Const, path: [...path, 'A_Const'] };
66+
}
67+
68+
for (const key in obj) {
69+
if (typeof obj[key] === 'object') {
70+
const result = findAConstWithContext(obj[key], [...path, key]);
71+
if (result) return result;
72+
}
73+
}
74+
75+
return null;
76+
}
77+
78+
exploreAlternativeApproaches();
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
const { Parser } = require('@pgsql/parser');
2+
3+
async function exploreDualParseApproach() {
4+
const parser15 = new Parser(15);
5+
const parser16 = new Parser(16);
6+
7+
const testCases = [
8+
{ sql: "insert into atacc2 (test2) values (-3)", expected: "negative" },
9+
{ sql: "insert into atacc2 (test2) values (0)", expected: "zero" },
10+
{ sql: "insert into atacc2 (test2) values (5)", expected: "positive" },
11+
{ sql: "ALTER TABLE onek ADD CONSTRAINT onek_check_constraint CHECK (unique1 >= -1)", expected: "negative" },
12+
{ sql: "ALTER TABLE onek ADD CONSTRAINT onek_check_constraint CHECK (unique1 >= 0)", expected: "zero" }
13+
];
14+
15+
console.log("=== DUAL-PARSE APPROACH EXPLORATION ===");
16+
console.log("Strategy: Parse same SQL with both PG15 and PG16, compare A_Const ival structures");
17+
console.log("If PG15 has empty {} but PG16 has nested {ival: X}, then transform is needed\n");
18+
19+
const transformationRules = [];
20+
21+
for (const testCase of testCases) {
22+
console.log(`=== ${testCase.expected.toUpperCase()}: ${testCase.sql} ===`);
23+
24+
try {
25+
const pg15Result = await parser15.parse(testCase.sql);
26+
const pg16Result = await parser16.parse(testCase.sql);
27+
28+
const pg15AConst = findAConstInAST(pg15Result);
29+
const pg16AConst = findAConstInAST(pg16Result);
30+
31+
if (pg15AConst && pg16AConst) {
32+
const pg15IsEmpty = pg15AConst.ival && Object.keys(pg15AConst.ival).length === 0;
33+
const pg16HasNested = pg16AConst.ival && typeof pg16AConst.ival.ival === 'number';
34+
const shouldTransform = pg15IsEmpty && pg16HasNested;
35+
36+
console.log("PG15 ival:", JSON.stringify(pg15AConst.ival));
37+
console.log("PG16 ival:", JSON.stringify(pg16AConst.ival));
38+
console.log("PG15 is empty:", pg15IsEmpty);
39+
console.log("PG16 has nested:", pg16HasNested);
40+
console.log("Should transform:", shouldTransform);
41+
42+
if (shouldTransform) {
43+
const targetValue = pg16AConst.ival.ival;
44+
console.log(`RULE: Transform empty {} to {ival: ${targetValue}}`);
45+
transformationRules.push({
46+
sql: testCase.sql,
47+
targetValue: targetValue,
48+
type: testCase.expected
49+
});
50+
} else {
51+
console.log("RULE: Keep empty {} as is");
52+
}
53+
54+
console.log("");
55+
}
56+
57+
} catch (error) {
58+
console.error("Error:", error.message);
59+
}
60+
}
61+
62+
console.log("=== TRANSFORMATION RULES DISCOVERED ===");
63+
transformationRules.forEach((rule, i) => {
64+
console.log(`${i + 1}. ${rule.type} integers: Transform {} to {ival: ${rule.targetValue}}`);
65+
});
66+
67+
console.log("\n=== DUAL-PARSE IMPLEMENTATION STRATEGY ===");
68+
console.log("1. In A_Const method, when encountering empty ival object:");
69+
console.log("2. Extract the original SQL from the current transformation context");
70+
console.log("3. Parse the SQL with both PG15 and PG16");
71+
console.log("4. Compare the A_Const ival structures");
72+
console.log("5. If PG16 expects nested structure, transform; otherwise keep empty");
73+
console.log("6. Cache results to avoid re-parsing the same SQL multiple times");
74+
75+
console.log("\n=== FEASIBILITY ASSESSMENT ===");
76+
console.log("✅ Technically feasible - can access both parsers in transformer");
77+
console.log("✅ Accurate - directly compares what PG16 expects vs PG15 produces");
78+
console.log("⚠️ Performance - may be slower due to dual parsing");
79+
console.log("⚠️ Complexity - need to extract original SQL from transformation context");
80+
console.log("✅ Reliable - eliminates guesswork about when to transform");
81+
}
82+
83+
function findAConstInAST(obj) {
84+
if (!obj || typeof obj !== 'object') return null;
85+
86+
if (obj.A_Const) return obj.A_Const;
87+
88+
for (const key in obj) {
89+
if (typeof obj[key] === 'object') {
90+
const result = findAConstInAST(obj[key]);
91+
if (result) return result;
92+
}
93+
}
94+
95+
return null;
96+
}
97+
98+
exploreDualParseApproach();
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
const { Parser } = require('@pgsql/parser');
2+
const { V15ToV16Transformer } = require('./dist/transformers/v15-to-v16');
3+
4+
class IvalContextAnalyzer extends V15ToV16Transformer {
5+
A_Const(node, context) {
6+
if (node.ival !== undefined && typeof node.ival === 'object' && Object.keys(node.ival).length === 0) {
7+
console.log('=== EMPTY IVAL CONTEXT ===');
8+
console.log('Context path:', context.path);
9+
console.log('Parent types:', context.parentNodeTypes);
10+
console.log('Full node:', JSON.stringify(node, null, 2));
11+
console.log('========================');
12+
}
13+
14+
return super.A_Const(node, context);
15+
}
16+
}
17+
18+
async function analyzeIvalContexts() {
19+
const parser15 = new Parser(15);
20+
const parser16 = new Parser(16);
21+
const analyzer = new IvalContextAnalyzer();
22+
23+
const testCases = [
24+
{
25+
name: "Negative integer in INSERT",
26+
sql: "insert into atacc2 (test2) values (-3)"
27+
},
28+
{
29+
name: "Zero in constraint",
30+
sql: "ALTER TABLE onek ADD CONSTRAINT onek_check_constraint CHECK (unique1 >= 0)"
31+
},
32+
{
33+
name: "Positive integer",
34+
sql: "insert into atacc2 (test2) values (5)"
35+
},
36+
{
37+
name: "Zero in INSERT",
38+
sql: "insert into atacc2 (test2) values (0)"
39+
},
40+
{
41+
name: "Negative in constraint",
42+
sql: "ALTER TABLE onek ADD CONSTRAINT onek_check_constraint CHECK (unique1 >= -1)"
43+
}
44+
];
45+
46+
console.log("=== ANALYZING EMPTY IVAL CONTEXTS ===");
47+
48+
for (const testCase of testCases) {
49+
console.log(`\n=== ${testCase.name}: ${testCase.sql} ===`);
50+
51+
try {
52+
const pg15Result = await parser15.parse(testCase.sql);
53+
const pg16Result = await parser16.parse(testCase.sql);
54+
55+
console.log("\n--- PG15 vs PG16 Comparison ---");
56+
const pg15AConst = findAConstInAST(pg15Result);
57+
const pg16AConst = findAConstInAST(pg16Result);
58+
59+
if (pg15AConst && pg16AConst) {
60+
console.log("PG15 ival:", JSON.stringify(pg15AConst.ival));
61+
console.log("PG16 ival:", JSON.stringify(pg16AConst.ival));
62+
console.log("Should transform?", JSON.stringify(pg15AConst.ival) !== JSON.stringify(pg16AConst.ival));
63+
}
64+
65+
console.log("\n--- Transformation Analysis ---");
66+
const astToTransform = JSON.parse(JSON.stringify(pg15Result));
67+
if (astToTransform.stmts && Array.isArray(astToTransform.stmts)) {
68+
astToTransform.stmts = astToTransform.stmts.map((stmtWrapper) => {
69+
if (stmtWrapper.stmt) {
70+
const transformedStmt = analyzer.transform(stmtWrapper.stmt, { parentNodeTypes: [] });
71+
return { ...stmtWrapper, stmt: transformedStmt };
72+
}
73+
return stmtWrapper;
74+
});
75+
}
76+
77+
} catch (error) {
78+
console.error("Error:", error.message);
79+
}
80+
}
81+
}
82+
83+
function findAConstInAST(obj) {
84+
if (!obj || typeof obj !== 'object') return null;
85+
86+
if (obj.A_Const) return obj.A_Const;
87+
88+
for (const key in obj) {
89+
if (typeof obj[key] === 'object') {
90+
const result = findAConstInAST(obj[key]);
91+
if (result) return result;
92+
}
93+
}
94+
95+
return null;
96+
}
97+
98+
analyzeIvalContexts();
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
const { Parser } = require('@pgsql/parser');
2+
3+
async function analyzeSQLValues() {
4+
const parser15 = new Parser(15);
5+
const parser16 = new Parser(16);
6+
7+
const testCases = [
8+
{ sql: "insert into atacc2 (test2) values (-3)", expectedValue: -3 },
9+
{ sql: "insert into atacc2 (test2) values (0)", expectedValue: 0 },
10+
{ sql: "insert into atacc2 (test2) values (5)", expectedValue: 5 },
11+
{ sql: "ALTER TABLE onek ADD CONSTRAINT onek_check_constraint CHECK (unique1 >= 0)", expectedValue: 0 },
12+
{ sql: "ALTER TABLE onek ADD CONSTRAINT onek_check_constraint CHECK (unique1 >= -1)", expectedValue: -1 },
13+
{ sql: "ALTER TABLE onek ADD CONSTRAINT onek_check_constraint CHECK (unique1 >= -5)", expectedValue: -5 },
14+
];
15+
16+
console.log("=== SQL VALUE ANALYSIS ===");
17+
18+
for (const testCase of testCases) {
19+
console.log(`\n=== SQL: ${testCase.sql} ===`);
20+
console.log(`Expected value: ${testCase.expectedValue}`);
21+
22+
try {
23+
const pg15Result = await parser15.parse(testCase.sql);
24+
const pg16Result = await parser16.parse(testCase.sql);
25+
26+
const pg15AConst = findAConstInAST(pg15Result);
27+
const pg16AConst = findAConstInAST(pg16Result);
28+
29+
console.log("PG15 ival:", JSON.stringify(pg15AConst?.ival));
30+
console.log("PG16 ival:", JSON.stringify(pg16AConst?.ival));
31+
32+
const pg15IsEmpty = pg15AConst?.ival && Object.keys(pg15AConst.ival).length === 0;
33+
const pg16HasValue = pg16AConst?.ival && typeof pg16AConst.ival.ival === 'number';
34+
35+
console.log("PG15 has empty ival:", pg15IsEmpty);
36+
console.log("PG16 has ival value:", pg16HasValue);
37+
console.log("Should transform:", pg15IsEmpty && pg16HasValue);
38+
39+
if (pg16HasValue) {
40+
console.log("PG16 actual value:", pg16AConst.ival.ival);
41+
console.log("Matches expected:", pg16AConst.ival.ival === testCase.expectedValue);
42+
}
43+
44+
} catch (error) {
45+
console.error("Error:", error.message);
46+
}
47+
}
48+
}
49+
50+
function findAConstInAST(obj) {
51+
if (!obj || typeof obj !== 'object') return null;
52+
53+
if (obj.A_Const) return obj.A_Const;
54+
55+
for (const key in obj) {
56+
if (typeof obj[key] === 'object') {
57+
const result = findAConstInAST(obj[key]);
58+
if (result) return result;
59+
}
60+
}
61+
62+
return null;
63+
}
64+
65+
analyzeSQLValues();

0 commit comments

Comments
 (0)