Skip to content

Commit c4c40ad

Browse files
docs: update STATUS-15-16.md with comprehensive progress details
- Added detailed status showing 184/258 tests passing (71.3% success rate) - Documented current challenge with negative integer transformation - Listed debug tools created for systematic analysis - Outlined next steps for targeted A_Const fix - Added technical notes and test categorization Co-Authored-By: Dan Lynch <[email protected]>
1 parent 70f4c68 commit c4c40ad

File tree

5 files changed

+451
-1
lines changed

5 files changed

+451
-1
lines changed

packages/transform/STATUS-15-16.md

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,52 @@
1-
not started — see previous status docs for format style.
1+
# PostgreSQL v15-to-v16 AST Transformer Status
2+
3+
## Current Status: **IN PROGRESS** 🟡
4+
- **Test Pass Rate**: 184/258 tests passing (71.3% success rate)
5+
- **Branch**: `pg15-pg16-transformer`
6+
- **PR**: [#175](https://github.com/launchql/pgsql-parser/pull/175)
7+
8+
## Progress Summary
9+
Started from a basic skeleton transformer and systematically implemented node wrapping and transformation logic across all node types. Made significant progress improving test pass rate from initial ~30% to current 71.3%.
10+
11+
## Key Achievements
12+
- ✅ Implemented comprehensive node transformation methods for 100+ node types
13+
- ✅ Fixed node wrapping issues across SelectStmt, InsertStmt, UpdateStmt, DeleteStmt
14+
- ✅ Resolved PartitionSpec strategy mapping in CreateStmt method
15+
- ✅ Added proper Array handling to transform method for nested node processing
16+
- ✅ Established stable baseline of 184/258 tests passing locally
17+
18+
## Current Challenge: Negative Integer Transformation
19+
**Root Issue**: PG15 produces `"ival": {}` (empty objects) where PG16 expects `"ival": {"ival": -3}` for negative integers in A_Const nodes.
20+
21+
**Analysis Completed**:
22+
- Created detailed debug scripts to analyze transformation flow
23+
- Identified that A_Const method calls `this.transform()` on empty ival objects
24+
- Empty objects `{}` don't get routed to Integer method due to missing node wrapper structure
25+
- Need targeted fix that distinguishes between zero values (should remain empty) and negative values (need nested structure)
26+
27+
**Attempted Solutions**:
28+
- ❌ 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
31+
32+
## Debug Tools Created
33+
- `debug_transformation_flow_detailed.js` - Analyzes exact transformation flow for negative integers
34+
- `debug_insert_negative.js` - Tests specific INSERT statement with negative value
35+
- `debug_zero_vs_negative.js` - Compares zero vs negative value handling
36+
- `debug_context_analysis.js` - Analyzes context-dependent transformation patterns
37+
38+
## 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+
44+
## Test Categories
45+
- **Passing (184)**: Basic node transformations, most SQL constructs
46+
- **Failing (74)**: Primarily negative integer transformations, some complex nested structures
47+
48+
## Technical Notes
49+
- Following patterns from v13-to-v14 transformer as reference
50+
- Focus only on v15-to-v16 transformer per user instructions
51+
- Ignoring CI failures per user directive, focusing on local test improvements
52+
- Maintaining systematic approach to avoid regressions
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
const fs = require('fs');
2+
3+
function analyze15To16CIFailures() {
4+
const logFile = '/home/ubuntu/full_outputs/gh_run_view_log_fail_1751134577.6148772.txt';
5+
6+
try {
7+
const content = fs.readFileSync(logFile, 'utf8');
8+
9+
const lines = content.split('\n');
10+
const failures15to16 = [];
11+
let currentFailure = null;
12+
let isIn15to16 = false;
13+
14+
for (let i = 0; i < lines.length; i++) {
15+
const line = lines[i];
16+
17+
if (line.includes('FAIL __tests__/kitchen-sink/15-16/')) {
18+
isIn15to16 = true;
19+
if (currentFailure) {
20+
failures15to16.push(currentFailure);
21+
}
22+
currentFailure = {
23+
testFile: line.split('FAIL ')[1],
24+
errors: [],
25+
fullContext: []
26+
};
27+
} else if (line.includes('FAIL __tests__/kitchen-sink/') && !line.includes('15-16')) {
28+
isIn15to16 = false;
29+
if (currentFailure && isIn15to16) {
30+
failures15to16.push(currentFailure);
31+
currentFailure = null;
32+
}
33+
}
34+
35+
if (isIn15to16 && currentFailure) {
36+
currentFailure.fullContext.push(line);
37+
38+
if (line.includes('- Expected') || line.includes('+ Received') ||
39+
line.includes('ival') || line.includes('Integer')) {
40+
const errorContext = lines.slice(Math.max(0, i-3), i+7).join('\n');
41+
currentFailure.errors.push(errorContext);
42+
}
43+
}
44+
}
45+
46+
if (currentFailure && isIn15to16) {
47+
failures15to16.push(currentFailure);
48+
}
49+
50+
console.log('=== 15-16 CI FAILURE ANALYSIS ===');
51+
console.log(`Total 15-16 failures found: ${failures15to16.length}`);
52+
53+
const errorPatterns = {
54+
'ival_empty_to_nested': 0,
55+
'integer_wrapping': 0,
56+
'negative_integer': 0,
57+
'missing_node_wrapping': 0
58+
};
59+
60+
failures15to16.forEach(f => {
61+
f.errors.forEach(error => {
62+
if (error.includes('ival') && error.includes('{}') && error.includes('ival":')) {
63+
errorPatterns.ival_empty_to_nested++;
64+
}
65+
if (error.includes('Integer') && error.includes('+') && error.includes('-')) {
66+
errorPatterns.integer_wrapping++;
67+
}
68+
if (error.includes('-3') || error.includes('negative')) {
69+
errorPatterns.negative_integer++;
70+
}
71+
if (error.includes('+ ') && error.includes('Object {')) {
72+
errorPatterns.missing_node_wrapping++;
73+
}
74+
});
75+
});
76+
77+
console.log('\n=== 15-16 ERROR PATTERNS ===');
78+
Object.entries(errorPatterns).forEach(([pattern, count]) => {
79+
console.log(`${pattern}: ${count} occurrences`);
80+
});
81+
82+
console.log('\n=== DETAILED 15-16 FAILURES ===');
83+
failures15to16.slice(0, 3).forEach((f, i) => {
84+
console.log(`\n--- Failure ${i+1}: ${f.testFile} ---`);
85+
if (f.errors.length > 0) {
86+
console.log(f.errors[0].substring(0, 800));
87+
} else {
88+
console.log('No specific error patterns found, showing context:');
89+
console.log(f.fullContext.slice(-20).join('\n').substring(0, 800));
90+
}
91+
console.log('...\n');
92+
});
93+
94+
console.log('\n=== COMPARISON WITH LOCAL FAILURES ===');
95+
console.log('CI shows 148 failures in 15-16 transformer');
96+
console.log('Local shows 74 failures in 15-16 transformer');
97+
console.log('Difference suggests CI may be testing additional scenarios or environment differences');
98+
99+
} catch (error) {
100+
console.error('Error analyzing CI failures:', error.message);
101+
}
102+
}
103+
104+
analyze15To16CIFailures();
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+
class DebugTransformer extends V15ToV16Transformer {
5+
A_Const(node, context) {
6+
console.log('=== A_Const Debug ===');
7+
console.log('Input node:', JSON.stringify(node, null, 2));
8+
console.log('Context path:', context.path);
9+
console.log('Parent types:', context.parentNodeTypes);
10+
11+
const result = super.A_Const(node, context);
12+
console.log('A_Const result:', JSON.stringify(result, null, 2));
13+
console.log('=====================');
14+
15+
return result;
16+
}
17+
18+
Integer(node, context) {
19+
console.log('=== Integer Debug ===');
20+
console.log('Input node:', JSON.stringify(node, null, 2));
21+
console.log('Context path:', context.path);
22+
console.log('Parent types:', context.parentNodeTypes);
23+
console.log('Is empty?', Object.keys(node).length === 0);
24+
console.log('Has A_Const parent?', context.parentNodeTypes.includes('A_Const'));
25+
26+
const result = super.Integer(node, context);
27+
console.log('Integer result:', JSON.stringify(result, null, 2));
28+
console.log('===================');
29+
30+
return result;
31+
}
32+
}
33+
34+
async function debugCIFailure() {
35+
const parser15 = new Parser(15);
36+
const parser16 = new Parser(16);
37+
const transformer = new DebugTransformer();
38+
39+
const sql = "insert into atacc2 (test2) values (-3)";
40+
41+
console.log("=== DEBUGGING CI FAILURE ===");
42+
console.log("SQL:", sql);
43+
44+
try {
45+
const pg15Result = await parser15.parse(sql);
46+
const pg16Result = await parser16.parse(sql);
47+
48+
console.log("\n=== PG15 A_Const ival ===");
49+
const pg15AConst = findAConstInInsert(pg15Result);
50+
console.log(JSON.stringify(pg15AConst?.ival, null, 2));
51+
52+
console.log("\n=== PG16 A_Const ival ===");
53+
const pg16AConst = findAConstInInsert(pg16Result);
54+
console.log(JSON.stringify(pg16AConst?.ival, null, 2));
55+
56+
console.log("\n=== STARTING TRANSFORMATION ===");
57+
const astToTransform = JSON.parse(JSON.stringify(pg15Result));
58+
if (astToTransform.stmts && Array.isArray(astToTransform.stmts)) {
59+
astToTransform.stmts = astToTransform.stmts.map((stmtWrapper) => {
60+
if (stmtWrapper.stmt) {
61+
const transformedStmt = transformer.transform(stmtWrapper.stmt, { parentNodeTypes: [] });
62+
return { ...stmtWrapper, stmt: transformedStmt };
63+
}
64+
return stmtWrapper;
65+
});
66+
}
67+
68+
console.log("\n=== TRANSFORMED A_Const ival ===");
69+
const transformedAConst = findAConstInInsert(astToTransform);
70+
console.log(JSON.stringify(transformedAConst?.ival, null, 2));
71+
72+
console.log("\n=== COMPARISON ===");
73+
console.log("PG15 produces:", JSON.stringify(pg15AConst?.ival));
74+
console.log("PG16 expects:", JSON.stringify(pg16AConst?.ival));
75+
console.log("My transform produces:", JSON.stringify(transformedAConst?.ival));
76+
console.log("Match PG16?", JSON.stringify(transformedAConst?.ival) === JSON.stringify(pg16AConst?.ival));
77+
78+
} catch (error) {
79+
console.error("Error:", error.message);
80+
}
81+
}
82+
83+
function findAConstInInsert(obj) {
84+
if (!obj || typeof obj !== 'object') return null;
85+
86+
try {
87+
return obj.stmts[0].stmt.InsertStmt.selectStmt.SelectStmt.valuesLists[0].List.items[0].A_Const;
88+
} catch (e) {
89+
return null;
90+
}
91+
}
92+
93+
debugCIFailure();
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
const { Parser } = require('@pgsql/parser');
2+
const { V15ToV16Transformer } = require('./dist/transformers/v15-to-v16');
3+
4+
class ContextAnalysisTransformer extends V15ToV16Transformer {
5+
A_Const(node, context) {
6+
console.log('=== A_Const Context Analysis ===');
7+
console.log('Input node:', JSON.stringify(node, null, 2));
8+
console.log('Context path:', context.path);
9+
console.log('Parent types:', context.parentNodeTypes);
10+
console.log('Context keys:', Object.keys(context));
11+
12+
const hasInsertContext = context.parentNodeTypes.includes('InsertStmt');
13+
const hasConstraintContext = context.parentNodeTypes.includes('Constraint');
14+
const hasExprContext = context.parentNodeTypes.includes('A_Expr');
15+
16+
console.log('Insert context:', hasInsertContext);
17+
console.log('Constraint context:', hasConstraintContext);
18+
console.log('Expression context:', hasExprContext);
19+
20+
const result = super.A_Const(node, context);
21+
console.log('A_Const result:', JSON.stringify(result, null, 2));
22+
console.log('================================');
23+
24+
return result;
25+
}
26+
}
27+
28+
async function analyzeContext() {
29+
const parser15 = new Parser(15);
30+
const parser16 = new Parser(16);
31+
const transformer = new ContextAnalysisTransformer();
32+
33+
const testCases = [
34+
{
35+
name: "Negative integer in INSERT",
36+
sql: "insert into atacc2 (test2) values (-3)"
37+
},
38+
{
39+
name: "Zero in constraint",
40+
sql: "ALTER TABLE onek ADD CONSTRAINT onek_check_constraint CHECK (unique1 >= 0)"
41+
},
42+
{
43+
name: "Positive integer",
44+
sql: "insert into atacc2 (test2) values (5)"
45+
}
46+
];
47+
48+
console.log("=== CONTEXT ANALYSIS FOR A_CONST TRANSFORMATION ===");
49+
50+
for (const testCase of testCases) {
51+
console.log(`\n=== ${testCase.name}: ${testCase.sql} ===`);
52+
53+
try {
54+
const pg15Result = await parser15.parse(testCase.sql);
55+
const pg16Result = await parser16.parse(testCase.sql);
56+
57+
console.log("\n--- PG16 Expected Result ---");
58+
const pg16AConst = findAConstInAST(pg16Result);
59+
console.log("PG16 A_Const ival:", JSON.stringify(pg16AConst?.ival));
60+
61+
console.log("\n--- Transformation Analysis ---");
62+
const astToTransform = JSON.parse(JSON.stringify(pg15Result));
63+
if (astToTransform.stmts && Array.isArray(astToTransform.stmts)) {
64+
astToTransform.stmts = astToTransform.stmts.map((stmtWrapper) => {
65+
if (stmtWrapper.stmt) {
66+
const transformedStmt = transformer.transform(stmtWrapper.stmt, { parentNodeTypes: [] });
67+
return { ...stmtWrapper, stmt: transformedStmt };
68+
}
69+
return stmtWrapper;
70+
});
71+
}
72+
73+
} catch (error) {
74+
console.error("Error:", error.message);
75+
}
76+
}
77+
}
78+
79+
function findAConstInAST(obj) {
80+
if (!obj || typeof obj !== 'object') return null;
81+
82+
if (obj.A_Const) return obj.A_Const;
83+
84+
for (const key in obj) {
85+
if (typeof obj[key] === 'object') {
86+
const result = findAConstInAST(obj[key]);
87+
if (result) return result;
88+
}
89+
}
90+
91+
return null;
92+
}
93+
94+
analyzeContext();

0 commit comments

Comments
 (0)