|
1 | 1 | # AST Transformer Implementation |
2 | 2 |
|
| 3 | +IMPORANT: update, improve, and leverage `expectOldAstToTransformToNewAst` in packages/transform/test-utils/expect-ast.ts |
| 4 | + |
| 5 | + |
| 6 | +## Visitor Pattern Architecture |
| 7 | + |
| 8 | +The deparser in `packages/deparser` demonstrates an elegant visitor pattern that can be leveraged for building AST transformers. The core approach follows these principles: |
| 9 | + |
| 10 | +### Key Components |
| 11 | + |
| 12 | +1. **Main Visitor Class**: A central class that implements the visitor interface |
| 13 | +2. **Dynamic Method Dispatch**: Uses node type names to automatically route to handler methods |
| 14 | +3. **Context Propagation**: Passes context through the visitor tree for state management |
| 15 | +4. **Simple Method Signatures**: Each node handler follows a consistent `NodeType(node, context)` pattern |
| 16 | + |
| 17 | +### Core Pattern from Deparser |
| 18 | + |
| 19 | +```typescript |
| 20 | +// Main visitor method - routes to specific handlers |
| 21 | +visit(node: Node, context: Context): ResultType { |
| 22 | + const nodeType = this.getNodeType(node); |
| 23 | + const nodeData = this.getNodeData(node); |
| 24 | + |
| 25 | + const methodName = nodeType as keyof this; |
| 26 | + if (typeof this[methodName] === 'function') { |
| 27 | + return (this[methodName] as any)(nodeData, context); |
| 28 | + } |
| 29 | + |
| 30 | + throw new Error(`Handler not found for: ${nodeType}`); |
| 31 | +} |
| 32 | + |
| 33 | +// Individual node handlers - clean, focused methods |
| 34 | +SelectStmt(node: SelectStmt, context: Context): ResultType { |
| 35 | + // Transform logic here |
| 36 | + return transformedResult; |
| 37 | +} |
| 38 | + |
| 39 | +A_Const(node: A_Const, context: Context): ResultType { |
| 40 | + // Transform logic here |
| 41 | + return transformedResult; |
| 42 | +} |
| 43 | +``` |
| 44 | + |
| 45 | +### Benefits for AST Transformation |
| 46 | + |
| 47 | +- **Extensible**: Easy to add new node type handlers |
| 48 | +- **Maintainable**: Each transformation is isolated to its own method |
| 49 | +- **Debuggable**: Clear method names match AST node types exactly — method names match the Type. |
| 50 | +- **Composable**: Handlers can call `visit()` recursively for child nodes, OR directly call another vistor function directly if it's not a `Node` type and inlined. |
| 51 | + |
| 52 | +### Implementation Strategy |
| 53 | + |
| 54 | +Apply this same pattern to build transformers for PG version migrations (13→14, 14→15, etc.) where each transformer class handles the specific changes needed for that version transition. |
| 55 | + |
| 56 | +## Runtime Schema and Enum Tools |
| 57 | + |
| 58 | +Check the `src/13`, `src/14`, `src/15`, `src/16`, `src/16` directories for useful runtime schema utilities that may help with AST generation. |
| 59 | + |
| 60 | +The enum tooling is particularly helpful for earlier PostgreSQL versions (PG13 and PG14) that used numeric enums in their AST instead of strings. These directories contain: |
| 61 | + |
| 62 | +- Type definitions |
| 63 | +- Enum mappings |
| 64 | +- Enum conversion functions for translating between numeric and string representations |
| 65 | + |
| 66 | +### Scripts |
| 67 | + |
| 68 | +The `scripts/` directory contains several analysis tools for understanding AST differences and enum representations across PostgreSQL versions. These tools depend on fixtures generated from actual PostgreSQL ASTs. |
| 69 | + |
| 70 | +#### Core Scripts |
| 71 | + |
| 72 | +**1. `generate-ast-fixtures.js`** - Foundation for all analysis |
| 73 | +- **Purpose**: Generates AST fixtures for PostgreSQL versions 13-17 |
| 74 | +- **Output**: Creates `__fixtures__/transform/{version}/` directories with JSON files |
| 75 | +- **Query Coverage**: |
| 76 | + - Basic operations: SELECT, INSERT, UPDATE, DELETE |
| 77 | + - DDL: CREATE TABLE, ALTER TABLE |
| 78 | + - Advanced: Complex queries with CTEs, JOINs, window functions |
| 79 | +- **Usage**: `node packages/transform/scripts/generate-ast-fixtures.js` |
| 80 | +- **Note**: Must be run first to create the fixture data that other scripts depend on |
| 81 | + |
| 82 | +**2. `analyze-ast-differences.js`** - Version migration analysis |
| 83 | +- **Purpose**: Deep comparison of AST structures between PostgreSQL versions |
| 84 | +- **Capabilities**: |
| 85 | + - Detects field renames (e.g., `relkind` → `objtype`) |
| 86 | + - Identifies structure changes (e.g., A_Const flattening in v14→v15) |
| 87 | + - Finds enum value changes |
| 88 | + - Categorizes differences by type and affected nodes |
| 89 | +- **Output**: Console report + `ast-differences-analysis.json` |
| 90 | +- **Usage**: `node packages/transform/scripts/analyze-ast-differences.js` |
| 91 | + |
| 92 | +**3. `analyze-enum-representation.js`** - Enum format analysis |
| 93 | +- **Purpose**: Analyzes how enums are represented across versions (numeric vs string) |
| 94 | +- **Key Insights**: |
| 95 | + - PG13/14 use numeric enums in many cases |
| 96 | + - PG15+ moved to string-based enums |
| 97 | + - Identifies fields that need enum conversion during transformation |
| 98 | +- **Usage**: `node packages/transform/scripts/analyze-enum-representation.js` |
| 99 | + |
| 100 | +**4. `pg-proto-parser.ts`** - Type generation utility |
| 101 | +- **Purpose**: Generates TypeScript types and utilities from PostgreSQL protobuf definitions |
| 102 | +- **Output**: Creates `src/{version}/` directories with: |
| 103 | + - Type definitions |
| 104 | + - Enum mappings |
| 105 | + - Runtime schema information |
| 106 | + - Enum conversion utilities (`enum-to-int.ts`, `enum-to-str.ts`) |
| 107 | + |
| 108 | +#### Analysis Workflow |
| 109 | + |
| 110 | +1. **Generate Fixtures**: Run `generate-ast-fixtures.js` to create base data |
| 111 | +2. **Analyze Differences**: Use `analyze-ast-differences.js` to understand version changes |
| 112 | +3. **Study Enums**: Run `analyze-enum-representation.js` for enum conversion patterns |
| 113 | +4. **Generate Types**: Use `pg-proto-parser.ts` for type definitions and utilities |
| 114 | + |
| 115 | +#### Fixture Quality & Coverage |
| 116 | + |
| 117 | +**Current State**: The fixtures provide good coverage for common SQL operations but could benefit from expansion. |
| 118 | + |
| 119 | +**Improvement Areas**: |
| 120 | +- More complex DDL operations (CREATE INDEX, constraints, etc.) |
| 121 | +- Advanced SQL features (window functions, recursive CTEs) |
| 122 | +- PostgreSQL-specific syntax (arrays, JSON operations, etc.) |
| 123 | +- Edge cases and error conditions |
| 124 | + |
| 125 | +**Adding New Test Cases**: Modify the `queries` object in `generate-ast-fixtures.js`: |
| 126 | + |
| 127 | +```javascript |
| 128 | +const queries = { |
| 129 | + // ... existing queries ... |
| 130 | + 'new_feature': [ |
| 131 | + 'SELECT ARRAY[1,2,3]', |
| 132 | + 'SELECT jsonb_path_query(data, "$.key")' |
| 133 | + ] |
| 134 | +}; |
| 135 | +``` |
| 136 | + |
| 137 | +#### Script Limitations |
| 138 | + |
| 139 | +- **Robustness**: These are analysis tools, not production utilities |
| 140 | +- **Error Handling**: Limited error recovery for malformed ASTs |
| 141 | +- **Performance**: Not optimized for large-scale analysis |
| 142 | +- **Dependencies**: Require consistent fixture generation to be meaningful |
| 143 | + |
| 144 | +#### Practical Usage Tips |
| 145 | + |
| 146 | +- **Incremental Analysis**: Focus on specific version transitions (e.g., 14→15) |
| 147 | +- **Query Expansion**: Add representative queries for your specific use cases |
| 148 | +- **Pattern Recognition**: Look for consistent patterns across multiple query types |
| 149 | +- **Validation**: Cross-reference findings with PostgreSQL release notes |
| 150 | + |
3 | 151 | ### Transformers |
4 | 152 |
|
5 | 153 | #### v13 → v14 (`v13-to-v14.ts`) |
|
0 commit comments