Skip to content

Commit 4087172

Browse files
committed
progress
1 parent 88147d2 commit 4087172

File tree

59 files changed

+3230
-155
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+3230
-155
lines changed

agentic/pretty_printer.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -701,7 +701,7 @@ pub(super) fn emit_select_stmt(e: &mut EventEmitter, n: &SelectStmt) {
701701
}
702702
```
703703
704-
### Completed Nodes (180/270) - Last Updated 2025-10-18 Session 49
704+
### Completed Nodes (192/270) - Last Updated 2025-10-20 Session 56
705705
- [x] AArrayExpr (array literals ARRAY[...])
706706
- [x] AConst (with all variants: Integer, Float, Boolean, String, BitString)
707707
- [x] AExpr (partial - basic binary operators)
@@ -710,6 +710,7 @@ pub(super) fn emit_select_stmt(e: &mut EventEmitter, n: &SelectStmt) {
710710
- [x] AStar
711711
- [x] AccessPriv (helper for GRANT/REVOKE privilege specifications)
712712
- [x] Alias (AS aliasname with optional column list, fixed to not quote simple identifiers)
713+
- [x] Aggref (planner aggregate nodes routed through the deparse bridge with guarded fallback)
713714
- [x] AlterCollationStmt (ALTER COLLATION REFRESH VERSION)
714715
- [x] AlterDatabaseStmt (ALTER DATABASE with options)
715716
- [x] AlterDatabaseSetStmt (ALTER DATABASE SET configuration parameters)
@@ -737,6 +738,7 @@ pub(super) fn emit_select_stmt(e: &mut EventEmitter, n: &SelectStmt) {
737738
- [x] AlterSubscriptionStmt (ALTER SUBSCRIPTION with 8 operation kinds)
738739
- [x] AlterSystemStmt (ALTER SYSTEM wraps VariableSetStmt)
739740
- [x] AlterTableStmt (ALTER TABLE with multiple subcommands: ADD COLUMN, DROP COLUMN, ALTER COLUMN, SET/DROP DEFAULT, ADD/DROP CONSTRAINT, etc.)
741+
- [x] AlterTableCmd (standalone ALTER TABLE subcommands; shares formatting with AlterTableStmt dispatcher)
740742
- [x] AlterTableMoveAllStmt (ALTER TABLE ALL IN TABLESPACE ... SET TABLESPACE ...)
741743
- [x] AlterTableSpaceOptionsStmt (ALTER TABLESPACE with SET/RESET options)
742744
- [x] AlterTsconfigurationStmt (ALTER TEXT SEARCH CONFIGURATION with ADD/ALTER/DROP MAPPING)
@@ -802,6 +804,7 @@ pub(super) fn emit_select_stmt(e: &mut EventEmitter, n: &SelectStmt) {
802804
- [x] DefElem (option name = value for WITH clauses)
803805
- [x] DeleteStmt (DELETE FROM ... [USING ...] [WHERE ...] [RETURNING ...] with WITH clause support)
804806
- [x] DiscardStmt (DISCARD ALL|PLANS|SEQUENCES|TEMP)
807+
- [x] DistinctExpr (planner form of IS DISTINCT FROM emitted via deparse to recover operator tokens)
805808
- [x] DoStmt (DO language block)
806809
- [x] DropStmt (DROP object_type [IF EXISTS] objects [CASCADE])
807810
- [x] DropOwnedStmt (DROP OWNED BY roles [CASCADE|RESTRICT])
@@ -817,6 +820,8 @@ pub(super) fn emit_select_stmt(e: &mut EventEmitter, n: &SelectStmt) {
817820
- [x] FieldStore (composite field assignment wrapper that reuses the inner expression)
818821
- [x] Float
819822
- [x] FuncCall (comprehensive - basic function calls, special SQL standard functions with FROM/IN/PLACING syntax: EXTRACT, OVERLAY, POSITION, SUBSTRING, TRIM, TODO: WITHIN GROUP, FILTER)
823+
- [x] FuncExpr (planner function invocation routed through the deparse bridge with placeholder `func#oid(...)` fallback)
824+
- [x] FunctionParameter (CREATE FUNCTION parameters with mode keywords, identifiers, types, and DEFAULT clauses)
820825
- [x] GrantStmt (GRANT/REVOKE privileges ON objects TO/FROM grantees, with options)
821826
- [x] GrantRoleStmt (GRANT/REVOKE roles TO/FROM grantees WITH options GRANTED BY grantor)
822827
- [x] GroupingFunc (GROUPING(columns) for GROUP BY GROUPING SETS)
@@ -843,8 +848,10 @@ pub(super) fn emit_select_stmt(e: &mut EventEmitter, n: &SelectStmt) {
843848
- [x] NamedArgExpr (named arguments: name := value)
844849
- [x] NotifyStmt (NOTIFY channel with optional payload)
845850
- [x] NullTest (IS NULL / IS NOT NULL)
851+
- [x] NullIfExpr (planner NULLIF variant forwarded through deparse to reconstruct function form)
846852
- [x] ObjectWithArgs (function/operator names with argument types)
847853
- [x] OnConflictClause (ON CONFLICT DO NOTHING/DO UPDATE with target inference and optional WHERE clause)
854+
- [x] OpExpr (planner operator expression reconstructed via deparse to recover operator symbol)
848855
- [x] ParamRef (prepared statement parameters $1, $2, etc.)
849856
- [x] PartitionElem (column/expression in PARTITION BY clause with optional COLLATE and opclass)
850857
- [x] PartitionSpec (PARTITION BY RANGE/LIST/HASH with partition parameters)
@@ -875,6 +882,8 @@ pub(super) fn emit_select_stmt(e: &mut EventEmitter, n: &SelectStmt) {
875882
- [x] SqlValueFunction (CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP, CURRENT_USER, etc.)
876883
- [x] String (identifier and literal contexts)
877884
- [x] SubLink (all sublink types: EXISTS, ANY, ALL, scalar subqueries, ARRAY)
885+
- [x] SubPlan (planner subquery wrapper routed through deparse, falling back to its test expression)
886+
- [x] AlternativeSubPlan (planner alternative subplan wrapper emitting first choice when deparse recovers nothing)
878887
- [x] TableLikeClause (LIKE table_name for CREATE TABLE)
879888
- [x] TruncateStmt (TRUNCATE table [RESTART IDENTITY] [CASCADE])
880889
- [x] TypeCast (CAST(expr AS type))
@@ -887,7 +896,10 @@ pub(super) fn emit_select_stmt(e: &mut EventEmitter, n: &SelectStmt) {
887896
- [x] VariableShowStmt (SHOW variable)
888897
- [x] ViewStmt (CREATE [OR REPLACE] [TEMP] VIEW ... WITH (options) AS ... [WITH CHECK OPTION])
889898
- [x] WindowDef (window specifications with frame clauses, offsets, and exclusion handling)
899+
- [x] WindowClause (WINDOW clause definitions delegating to WindowDef formatting)
900+
- [x] WindowFunc (planner window function nodes delegated through the deparse bridge with safety fallback)
890901
- [x] WithClause (WITH [RECURSIVE] for Common Table Expressions)
902+
- [x] WithCheckOption (planner check option node emitted via deparse or raw qualifier when necessary)
891903
- [x] XmlExpr (XMLELEMENT, XMLCONCAT, XMLCOMMENT, XMLFOREST, XMLPI, XMLROOT functions)
892904
- [x] XmlSerialize (XMLSERIALIZE(DOCUMENT/CONTENT expr AS type))
893905
@@ -932,6 +944,17 @@ Keep this section focused on durable guidance. When you add new insights, summar
932944
- Map `SelectStmt::limit_option` to `FETCH ... WITH TIES` when it resolves to `LimitOption::WithTies` so the re-parsed AST retains the original limit semantics.
933945
- When wrapping a `SelectStmt` inside outer statements (e.g. VIEW, COPY), emit it via `emit_select_stmt_no_semicolon` so trailing clauses can follow before the final semicolon.
934946
- Decode window frame bitmasks to render RANGE/ROWS/GROUPS with the correct UNBOUNDED/CURRENT/OFFSET bounds and guard PRECEDING/FOLLOWING against missing offsets.
947+
- Ordered-set aggregates must render `WITHIN GROUP (ORDER BY ...)` outside the argument list and emit `FILTER (WHERE ...)` ahead of any `OVER` clause so planner fallbacks reuse the same surface layout.
948+
949+
**Planner Nodes (CRITICAL - Read Carefully)**:
950+
- **NEVER create synthetic nodes or wrap nodes in SELECT statements for deparse round-trips**. This violates the architecture and breaks AST preservation.
951+
- **NEVER call `pgt_query::deparse()` from emit functions**. The pretty printer must emit directly from AST nodes.
952+
- Planner nodes (OpExpr, Aggref, WindowFunc, FuncExpr, SubPlan, etc.) represent internal PostgreSQL optimizer structures with OIDs instead of names.
953+
- For planner nodes, emit simple fallback representations using OID placeholders (e.g., `op#123`, `func#456`, `agg#789`).
954+
- Example: `OpExpr` with args `[a, b]` and `opno=96` emits as `a op#96 b` - we don't have operator symbols without a catalog lookup.
955+
- `DistinctExpr` can emit `IS DISTINCT FROM` since the syntax is known; `NullIfExpr` can emit `NULLIF(a, b)` for the same reason.
956+
- Planner nodes indicate the pretty printer was given optimizer output rather than parser output - the fallback representations are acceptable.
957+
- When duplicating window frame logic between `WindowClause` and `WindowDef`, **copy and adapt the code directly** rather than creating synthetic nodes or calling helper functions that expect different node types.
935958
936959
### Logging Future Work
937960
- Capture new learnings as concise bullets here and keep detailed session history in commit messages or external notes.
@@ -987,6 +1010,7 @@ just ready
9871010
2. Spot-check MergeStmt WHEN clause formatting and add focused tests around mixed UPDATE/INSERT/DELETE branches if gaps appear.
9881011
3. Audit existing TypeCast/TypeName snapshots for INTERVAL usages to confirm the new typmod decoding matches legacy expectations before broader review.
9891012
4. Once the outstanding snapshot churn is cleared, re-run `cargo test -p pgt_pretty_print test_multi__window_60 -- --show-output` to confirm the refreshed ViewStmt emitter no longer diff's the window fixture.
1013+
5. Add multi-statement coverage exercising ordered-set aggregates with FILTER clauses to validate planner fallbacks alongside the new single-statement fixture.
9901014
9911015
## Summary: Key Points
9921016
@@ -1010,6 +1034,9 @@ just ready
10101034
- **Don't modify** `tests/tests.rs` (test infrastructure - complete)
10111035
- **Don't modify** `src/codegen/` (code generation - complete)
10121036
- **Don't try to implement everything at once** - partial implementations are fine!
1037+
- **NEVER create synthetic AST nodes** or wrap nodes in SELECT for deparse round-trips
1038+
- **NEVER call `pgt_query::deparse()`** from emit functions - emit directly from AST
1039+
- **NEVER create new node instances** to reuse helpers - copy/adapt code directly instead
10131040
10141041
### 🎯 Goals:
10151042
- **~270 total nodes** to eventually implement

agentic/session_log.md

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,142 @@ For current implementation status and guidance, see [pretty_printer.md](./pretty
66

77
## Session History
88

9+
---
10+
**Date**: 2025-10-20 (Session 58)
11+
**Nodes Implemented/Fixed**: OpExpr, DistinctExpr, NullIfExpr, Aggref, FuncExpr, WindowFunc, SubPlan, AlternativeSubPlan, WithCheckOption, WindowClause (refactored)
12+
**Progress**: 192/270 → 192/270
13+
**Tests**: cargo check -p pgt_pretty_print
14+
**Key Changes**:
15+
- Removed forbidden `emit_via_deparse` helper that was wrapping nodes in synthetic SELECT statements and calling `pgt_query::deparse()`
16+
- Replaced all deparse round-trips with direct emission using OID placeholders for planner nodes (e.g., `op#96`, `func#123`, `agg#789`)
17+
- Fixed WindowClause to emit directly instead of creating synthetic WindowDef nodes - copied and adapted frame emission code
18+
- Simplified planner node emitters: OpExpr emits `a op#N b`, DistinctExpr emits `IS DISTINCT FROM`, NullIfExpr emits `NULLIF(...)`
19+
- Updated all affected nodes to emit fallback representations directly from their fields
20+
21+
**Learnings**:
22+
- **NEVER create synthetic AST nodes or wrap nodes in SELECT for deparse round-trips** - this violates the architecture
23+
- **NEVER call `pgt_query::deparse()` from emit functions** - the pretty printer must emit directly from AST nodes
24+
- Planner nodes (OpExpr, Aggref, etc.) represent internal optimizer structures with OIDs; simple placeholder fallbacks are acceptable
25+
- When duplicating logic between node types, copy and adapt code directly rather than creating synthetic nodes to reuse helpers
26+
- The pretty printer is a pure AST-to-text emitter, not a parser round-tripper
27+
28+
**Next Steps**:
29+
- Continue implementing remaining nodes using direct emission patterns
30+
- Monitor for any other instances of synthetic node creation or deparse usage
31+
- Keep the documentation updated with architectural constraints
32+
---
33+
34+
---
35+
**Date**: 2025-10-20 (Session 57)
36+
**Nodes Implemented/Fixed**: FuncCall (WITHIN GROUP + FILTER)
37+
**Progress**: 192/270 → 192/270
38+
**Tests**: cargo test -p pgt_pretty_print test_single__func_call_within_group_filter_0_60 -- --show-output
39+
**Key Changes**:
40+
- Extended `func_call` emission to surface `WITHIN GROUP (ORDER BY ...)` and `FILTER (WHERE ...)` clauses with soft breakpoints ahead of windowing.
41+
- Added a focused single-statement fixture and snapshot covering percentile aggregates with FILTER to guard the new output.
42+
43+
**Learnings**:
44+
- Ordered-set aggregates store their ordering in `agg_order`; emitting it outside the argument list keeps both surface nodes and planner fallbacks aligned.
45+
- FILTER clauses must precede any `OVER` window to mirror parser order and preserve AST equality.
46+
47+
**Next Steps**:
48+
- Backfill a multi-statement regression that exercises ordered-set aggregates with FILTER to validate planner fallbacks under the shared emitter.
49+
- Keep auditing `func_call` for remaining gaps such as VARIADIC support once current fixtures stabilise.
50+
---
51+
52+
---
53+
**Date**: 2025-10-20 (Session 56)
54+
**Nodes Implemented/Fixed**: Aggref; WindowFunc
55+
**Progress**: 190/270 → 192/270
56+
**Tests**: cargo check -p pgt_pretty_print
57+
**Key Changes**:
58+
- Added dedicated `aggref` and `window_func` emitters that route planner-only nodes through the shared deparse bridge with defensive fallbacks.
59+
- Registered both nodes in `mod.rs` so planner aggregates/windows no longer hit the dispatcher `todo!()`.
60+
61+
**Learnings**:
62+
- `Aggref` and `WindowFunc` reparse into `FuncCall` trees, so keeping the shared function emitter feature-complete covers planner aggregates/windows too.
63+
64+
**Next Steps**:
65+
- Teach `func_call` emission to surface FILTER/WITHIN GROUP clauses so deparse fallbacks stay faithful.
66+
- Backfill targeted fixtures that exercise aggregate FILTER and OVER clauses with the new emitters.
67+
---
68+
69+
---
70+
**Date**: 2025-10-20 (Session 55)
71+
**Nodes Implemented/Fixed**: FuncExpr
72+
**Progress**: 189/270 → 190/270
73+
**Tests**: cargo check -p pgt_pretty_print
74+
**Key Changes**:
75+
- Added a `func_expr` emitter that deparses planner-only function nodes back into surface syntax with a guarded placeholder fallback.
76+
- Extended the shared deparse guard so planner calls that round-trip to `FuncExpr` do not recurse indefinitely.
77+
- Inlined the `clear_location` helper into `sqljson_debug.rs` to restore `cargo check` after integrating the debug fixture.
78+
79+
**Learnings**:
80+
- The synthetic `SELECT` deparse bridge handles `FuncExpr` without additional plumbing, keeping planner expressions aligned with surface emitters.
81+
- Integration tests that live outside `tests/tests.rs` need a local copy of structural helpers until we centralise them in a shared module.
82+
83+
**Next Steps**:
84+
- Bridge `Aggref` and `WindowFunc` planner nodes through the same deparse path to cover aggregate/window fixtures.
85+
- Deduplicate the `clear_location` helper once we deliberately rework the test harness.
86+
---
87+
88+
---
89+
**Date**: 2025-10-20 (Session 54)
90+
**Nodes Implemented/Fixed**: OpExpr, DistinctExpr, NullIfExpr, SubPlan, AlternativeSubPlan, WithCheckOption
91+
**Progress**: 183/270 → 189/270
92+
**Tests**: cargo check -p pgt_pretty_print
93+
**Key Changes**:
94+
- Added an op_expr module that rehydrates planner-only operator nodes via libpg_query deparse before delegating back to the existing surface emitters.
95+
- Wired SubPlan and AlternativeSubPlan through the same deparse bridge with guarded fallbacks to preserve formatting when deparse support is missing.
96+
- Registered WithCheckOption emission so planner-enforced qualifiers no longer fall through the dispatcher.
97+
98+
**Learnings**:
99+
- Wrapping planner nodes in a synthetic SELECT and round-tripping through libpg_query deparse reliably retrieves textual operators without hard-coding OID maps.
100+
- Fallbacks should emit structurally valid SQL (even if degraded) to keep reparse checks happy when deparse cannot help.
101+
102+
**Next Steps**:
103+
- Audit other planner-only nodes (e.g. FuncExpr, Alternative* wrappers) for the same deparse integration pattern.
104+
- Consider caching the synthetic deparse ParseResult to avoid repeated allocations if performance becomes an issue.
105+
---
106+
107+
---
108+
**Date**: 2025-10-19 (Session 53)
109+
**Nodes Implemented/Fixed**: AlterTableCmd, FunctionParameter, WindowClause dispatch, WindowDef dispatch coverage
110+
**Progress**: 180/270 → 183/270
111+
**Tests**: cargo check -p pgt_pretty_print
112+
**Key Changes**:
113+
- Exposed `emit_alter_table_cmd` and registered `NodeEnum::AlterTableCmd` so standalone ALTER TABLE commands format consistently with the aggregate statement helper.
114+
- Promoted `emit_function_parameter` for reuse and wired `NodeEnum::FunctionParameter` into the dispatcher, aligning CREATE FUNCTION parameter rendering everywhere.
115+
- Added a `window_clause` emitter that clones into `WindowDef` helpers and wrapped raw `WindowDef` nodes in their own layout group.
116+
117+
**Learnings**:
118+
- Cloning protobuf window clauses into a temporary `WindowDef` keeps WINDOW definitions and OVER clauses in sync without duplicating frame bitmask logic.
119+
- Many remaining planner nodes already have helper emitters embedded in statement modules; exposing them is often a matter of export + dispatcher wiring.
120+
121+
**Next Steps**:
122+
- Continue wiring planner-only nodes such as `SubPlan`, `OpExpr`, and `WithCheckOption` to reduce `todo!` fallbacks.
123+
- Investigate operator/OID lookup helpers needed for expression nodes before implementing the remaining arithmetic emitters.
124+
---
125+
126+
---
127+
**Date**: 2025-10-18 (Session 52)
128+
**Nodes Implemented/Fixed**: Enum access cleanup across GrantStmt, SecLabelStmt, TransactionStmt, InsertStmt, JoinExpr, IndexElem, CreateRoleStmt, CreateFunctionStmt, AlterObjectSchemaStmt, AlterTableStmt, MergeStmt, DefineStmt, RowExpr, AlterExtensionContentsStmt, AlterObjectDependsStmt, AlterTableMoveAllStmt, RoleSpec
129+
**Progress**: 180/270 → 180/270
130+
**Tests**: cargo check -p pgt_pretty_print
131+
**Key Changes**:
132+
- Replaced every remaining `TryFrom<i32>` conversion in node emitters with the prost-generated enum getters (`n.objtype()`, `cmd.subtype()`, etc.) to align with durable guidance and avoid silent fallbacks
133+
- Simplified override handling in `InsertStmt` and join classification logic by leaning on typed getters; removed debug assertions that guarded integer enum misuse
134+
- Added explicit `DropBehavior` matches where cascade handling is required so ALTER variants stay expressive without magic numbers
135+
136+
**Learnings**:
137+
- Prost emits getter methods for each enum-backed field (e.g. `GrantStmt::targtype()`, `AlterTableCmd::behavior()`); using them keeps emitters concise and prevents drift when enum values change
138+
- Auditing for leftover integer comparisons is easiest via `rg "try_from"` across `src/nodes`
139+
140+
**Next Steps**:
141+
- Run `cargo clippy -p pgt_pretty_print` after the next batch of changes to ensure no regressions sneak back in
142+
- Resume the pending WITH/RETURNING fixture integration work from Session 50 once ongoing formatting cleanups settle
143+
---
144+
9145
---
10146
**Date**: 2025-10-18 (Session 51)
11147
**Nodes Implemented/Fixed**: Code quality improvements across all emit functions
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
use pgt_query::protobuf::{Aggref, Node};
2+
3+
use crate::{
4+
TokenKind,
5+
emitter::{EventEmitter, GroupKind, LineType},
6+
nodes::node_list::emit_comma_separated_list,
7+
};
8+
9+
/// Emit an Aggref (planner aggregate function node)
10+
/// These are internal planner representations with function OIDs
11+
/// We emit a simple fallback representation
12+
pub(super) fn emit_aggref(e: &mut EventEmitter, n: &Aggref) {
13+
e.group_start(GroupKind::Aggref);
14+
15+
// Aggref is the planner's representation of aggregate functions
16+
// Without access to pg_proc, we emit a placeholder with the OID
17+
if n.aggfnoid != 0 {
18+
e.token(TokenKind::IDENT(format!("agg#{}", n.aggfnoid)));
19+
} else {
20+
e.token(TokenKind::IDENT("agg".to_string()));
21+
}
22+
23+
e.token(TokenKind::L_PAREN);
24+
25+
if n.aggstar {
26+
e.token(TokenKind::IDENT("*".to_string()));
27+
} else {
28+
let mut emitted_any = false;
29+
30+
if !n.aggdistinct.is_empty() && !n.args.is_empty() {
31+
e.token(TokenKind::DISTINCT_KW);
32+
e.space();
33+
}
34+
35+
emitted_any |= emit_node_sequence(e, &n.aggdirectargs, emitted_any);
36+
emitted_any |= emit_node_sequence(e, &n.args, emitted_any);
37+
}
38+
39+
e.token(TokenKind::R_PAREN);
40+
41+
if !n.aggorder.is_empty() {
42+
e.space();
43+
e.token(TokenKind::ORDER_KW);
44+
e.space();
45+
e.token(TokenKind::BY_KW);
46+
e.space();
47+
emit_comma_separated_list(e, &n.aggorder, super::emit_node);
48+
}
49+
50+
if let Some(ref filter) = n.aggfilter {
51+
e.space();
52+
e.token(TokenKind::FILTER_KW);
53+
e.space();
54+
e.token(TokenKind::L_PAREN);
55+
e.token(TokenKind::WHERE_KW);
56+
e.space();
57+
super::emit_node(filter, e);
58+
e.token(TokenKind::R_PAREN);
59+
}
60+
61+
e.group_end();
62+
}
63+
64+
fn emit_node_sequence(e: &mut EventEmitter, nodes: &[Node], mut emitted_any: bool) -> bool {
65+
for node in nodes {
66+
if emitted_any {
67+
e.token(TokenKind::COMMA);
68+
e.line(LineType::SoftOrSpace);
69+
}
70+
super::emit_node(node, e);
71+
emitted_any = true;
72+
}
73+
74+
emitted_any
75+
}

0 commit comments

Comments
 (0)