diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/docs/migration_plan.md b/docs/migration_plan.md new file mode 100644 index 0000000..2355d85 --- /dev/null +++ b/docs/migration_plan.md @@ -0,0 +1,545 @@ +# Migration Plan: Iterative `raw_parse_iter.rs` — Test-Driven Approach + +## Strategy + +We migrate **test by test** — running a single test from simple to complex, adding +the missing node type to the iterative `Processor`, and moving on only after it passes. + +All tests live in `tests/raw_parse_iter/` and mirror `tests/raw_parse/` exactly, except +they call `parse_raw_iter` instead of `parse_raw`. Every test compares `parse_raw_iter` +output against `parse` (protobuf) output for structural equality. + +### How to run a single test + +```sh +cargo test --test raw_parse_iter_tests raw_parse_iter::basic::it_parses_simple_select -- --nocapture +``` + +### Workflow for each test + +1. Run the test +2. If it **passes** → move to the next test +3. If it **fails** → read the error, identify the missing/broken node conversion, + fix it in `raw_parse_iter.rs` (or `Processor`), re-run, repeat +4. After the fix passes, run all previously-passing tests as regression: + `cargo test --test raw_parse_iter_tests -- --nocapture` + +--- + +## How the Iterative Processor Works + +The `Processor` struct in `raw_parse_iter.rs` replaces recursive `convert_node()` calls +with an explicit stack. It has two stacks: + +- **`stack`** — `Vec` — work items (C node pointers + a `collect` flag) +- **`result_stack`** — `Vec` — completed protobuf nodes + +### Two-pass processing + +Every node is visited **twice**: + +1. **Queue pass** (`collect=false`): Push a collect marker for this node, then push + its children onto the stack. Children go on top, so they are processed first. +2. **Collect pass** (`collect=true`): All children have been processed and their results + sit on `result_stack`. Pop them, assemble the protobuf struct, push the result. + +``` +stack (LIFO): + ┌─────────────────┐ + │ child C │ ← processed first + │ child B │ + │ child A │ + │ COLLECT(parent) │ ← processed after all children + └─────────────────┘ +``` + +### Key helper methods + +| Method | Purpose | +|--------|---------| +| `queue_collect(ptr)` | Push a collect marker (same pointer, `collect=true`) | +| `queue_node(ptr)` | Push a child node for processing (`collect=false`); null ptrs are skipped | +| `queue_list_nodes(list)` | Push every element of a C `List*` as individual nodes | +| `single_result(ptr)` | Pop one result from `result_stack`; returns `None` if ptr was null | +| `single_result_box(ptr)` | Same but returns `Option>` | +| `fetch_list_results(list)` | Pop N results (where N = list length); returns `Vec` | +| `push_result(node)` | Push a completed protobuf node onto `result_stack` | + +### Symmetry rule + +**Every queued node produces exactly one result.** This means: + +- `queue_node(ptr)` must be balanced by `single_result(ptr)` or `single_result_box(ptr)` + using **the same pointer** so the null-check matches. +- `queue_list_nodes(list)` must be balanced by `fetch_list_results(list)` using + **the same list pointer**. +- The order must be the same: queue A then B → collect fetches A then B. + +--- + +## How to Migrate a Node Type + +### Step 1: Identify the category + +| Category | Description | Example | +|----------|-------------|---------| +| **Leaf** | No child nodes/lists that need conversion | `SQLValueFunction`, `SetToDefault` | +| **Simple** | Has child nodes and/or lists | `BoolExpr`, `NullTest`, `SubLink` | + +### Step 2: Add the match arm in `process()` + +In the `match node_tag { ... }` block, add an arm for the new `NodeTag`: + +```rust +bindings_raw::NodeTag_T_BoolExpr => { + let be = node_ptr as *const bindings_raw::BoolExpr; + if collect { + let node = self.collect_bool_expr(&*be); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_bool_expr(&*be); + } +} +``` + +For **leaf nodes** (no children), skip queue/collect entirely: + +```rust +bindings_raw::NodeTag_T_SQLValueFunction => { + let svf = node_ptr as *const bindings_raw::SQLValueFunction; + self.push_result(protobuf::node::Node::SqlvalueFunction(Box::new( + protobuf::SqlValueFunction { + xpr: None, + op: (*svf).op as i32 + 1, + r#type: (*svf).type_, + typmod: (*svf).typmod, + location: (*svf).location, + }, + ))); +} +``` + +### Step 3: Add `queue_*` and `collect_*` methods (non-leaf only) + +The **queue method** pushes children onto the stack. The **collect method** pops +results and assembles the protobuf struct. + +Look at the existing recursive `convert_*` function to see which fields need conversion. +Each call to `convert_node_boxed(field)` becomes a `queue_node(field)` / `single_result_box(field)` pair. +Each call to `convert_list_to_nodes(list)` becomes a `queue_list_nodes(list)` / `fetch_list_results(list)` pair. + +**Example — BoolExpr** (one list child): + +Recursive version (in `raw_parse.rs`): +```rust +unsafe fn convert_bool_expr(be: &bindings_raw::BoolExpr) -> protobuf::BoolExpr { + protobuf::BoolExpr { + xpr: None, + boolop: be.boolop as i32 + 1, + args: convert_list_to_nodes(be.args), // ← list child + location: be.location, + } +} +``` + +Iterative version (queue + collect methods on `Processor`): +```rust +unsafe fn queue_bool_expr(&mut self, be: &bindings_raw::BoolExpr) { + self.queue_list_nodes(be.args); // ← queue the list +} + +unsafe fn collect_bool_expr(&mut self, be: &bindings_raw::BoolExpr) -> protobuf::node::Node { + let args = self.fetch_list_results(be.args); // ← fetch matching results + protobuf::node::Node::BoolExpr(Box::new(protobuf::BoolExpr { + xpr: None, + boolop: be.boolop as i32 + 1, + args, + location: be.location, + })) +} +``` + +**Example — SubLink** (two node children + one list child): + +```rust +unsafe fn queue_sub_link(&mut self, sl: &bindings_raw::SubLink) { + self.queue_node(sl.testexpr); // node child 1 + self.queue_list_nodes(sl.operName); // list child + self.queue_node(sl.subselect); // node child 2 +} + +unsafe fn collect_sub_link(&mut self, sl: &bindings_raw::SubLink) -> protobuf::node::Node { + let testexpr = self.single_result_box(sl.testexpr); + let oper_name = self.fetch_list_results(sl.operName); + let subselect = self.single_result_box(sl.subselect); + protobuf::node::Node::SubLink(Box::new(protobuf::SubLink { + xpr: None, + sub_link_type: sl.subLinkType as i32 + 1, + sub_link_id: sl.subLinkId, + testexpr, + oper_name, + subselect, + location: sl.location, + })) +} +``` + +### Step 4: Add to `node_tag_name()` (for debug logging) + +```rust +bindings_raw::NodeTag_T_BoolExpr => "BoolExpr", +``` + +### Step 5: Handle non-Node helper structs + +Some PostgreSQL structs (like `Alias`, `IntoClause`, `OnConflictClause`) are embedded +inside other nodes but are not themselves `Node` types in the processor's match. These +are handled with dedicated `queue_*` / `fetch_*` helper pairs on the Processor, called +from the parent's queue/collect methods. See `queue_into_clause` / `fetch_into_clause` +and `queue_on_conflict_clause` / `fetch_on_conflict_clause` as examples. + +### Step 6: Run tests and verify + +```sh +# Run the specific test that needs this node type +cargo test --test raw_parse_iter_tests "raw_parse_iter::expressions::it_parses_null_tests" -- --nocapture + +# Run all tests as regression +cargo test --test raw_parse_iter_tests -- --nocapture +``` + +### Common pitfalls + +- **Queue/collect order mismatch**: The collect method must pop results in the **same + order** as the queue method pushed children. If queue does `A, B, C` then collect + must do `A, B, C` (not `C, B, A`). +- **Forgetting null checks**: `queue_node` with null is harmless (skipped in process), + but `single_result` must be called with **the same pointer** so it knows whether to + pop. If the pointer was null, `single_result` returns `None` without popping. +- **Missing list symmetry**: If you call `queue_list_nodes(some_list)`, you MUST call + `fetch_list_results(some_list)` with the **same list pointer** — not a different copy. +- **Wrong protobuf enum offset**: PostgreSQL C enums start at 0, protobuf enums reserve + 0 for `UNDEFINED`. Most fields need `as i32 + 1`. +- **Boxed vs unboxed**: Check the protobuf struct definition — some fields use + `Option>` (call `single_result_box`), others use `Vec` (call + `fetch_list_results`), and some are scalar (just copy directly). + +--- + +## Test Execution Order + +Tests are ordered from simplest SQL (fewest node types involved) to most complex +(deeply nested, many node types combined). Within each file, tests are listed in +the order they should be attempted. + +### Step 1 — `basic` (fundamentals) + +These tests exercise the core path: `RawStmt` → `SelectStmt` → `ResTarget` → `ColumnRef` / `AConst`. + +| # | Test name | What it exercises | +|---|-----------|-------------------| +| 1 | `basic::it_parses_simple_select` | `SELECT 1` — SelectStmt + ResTarget + AConst(int) | +| 2 | `basic::it_matches_parse_for_simple_select` | Same, with deparse check | +| 3 | `basic::it_handles_parse_errors` | Error path (no node conversion) | +| 4 | `basic::it_handles_empty_queries` | Empty parse tree | +| 5 | `basic::it_matches_parse_for_select_from_table` | `SELECT * FROM users` — adds RangeVar | +| 6 | `basic::it_deparses_parse_raw_iter_result` | Deparse round-trip | +| 7 | `basic::it_parses_multiple_statements` | Multiple RawStmt entries | +| 8 | `basic::it_returns_tables_like_parse` | JOIN + WHERE — adds JoinExpr, A_Expr, ColumnRef | +| 9 | `basic::it_returns_functions_like_parse` | FuncCall, `count(*)`, `sum()` | +| 10 | `basic::it_returns_statement_types_like_parse` | Mixed SELECT/INSERT/UPDATE/DELETE | +| 11 | `basic::it_deparse_raw_simple_select` | Deparse from parse_raw_iter result | +| 12 | `basic::it_deparse_raw_select_from_table` | Deparse with RangeVar | +| 13 | `basic::it_deparse_raw_complex_select` | Deparse with WHERE + ORDER BY | +| 14 | `basic::it_deparse_raw_insert` | InsertStmt deparse | +| 15 | `basic::it_deparse_raw_update` | UpdateStmt deparse | +| 16 | `basic::it_deparse_raw_delete` | DeleteStmt deparse | +| 17 | `basic::it_deparse_raw_multiple_statements` | Multi-statement deparse | +| 18 | `basic::it_deparse_raw_method_on_parse_result` | Method call variant | +| 19 | `basic::it_deparse_raw_method_on_protobuf_parse_result` | Method on protobuf struct | +| 20 | `basic::it_deparse_raw_method_on_node_ref` | NodeRef method | +| 21 | `basic::it_deparse_raw_matches_deparse` | Cross-check deparse vs deparse_raw | + +### Step 2 — `expressions` (literals & operators) + +Exercises leaf nodes and simple expression trees. + +| # | Test name | What it exercises | +|---|-----------|-------------------| +| 22 | `expressions::it_extracts_integer_const` | AConst(Ival) | +| 23 | `expressions::it_extracts_string_const` | AConst(Sval) | +| 24 | `expressions::it_extracts_float_const` | AConst(Fval) | +| 25 | `expressions::it_extracts_boolean_true_const` | AConst(Boolval) | +| 26 | `expressions::it_extracts_boolean_false_const` | AConst(Boolval) | +| 27 | `expressions::it_extracts_null_const` | AConst(isnull) | +| 28 | `expressions::it_extracts_negative_integer_const` | Unary minus → A_Expr or AConst | +| 29 | `expressions::it_parses_floats_with_leading_dot` | AConst(Fval) edge case | +| 30 | `expressions::it_extracts_bit_string_const` | AConst(Bsval) | +| 31 | `expressions::it_extracts_hex_bit_string_const` | AConst(Bsval) | +| 32 | `expressions::it_parses_null_tests` | NullTest node | +| 33 | `expressions::it_parses_is_distinct_from` | A_Expr(DISTINCT) | +| 34 | `expressions::it_parses_between` | A_Expr(BETWEEN) | +| 35 | `expressions::it_parses_like_ilike` | A_Expr(LIKE/ILIKE) + BoolExpr(OR) | +| 36 | `expressions::it_parses_similar_to` | A_Expr(SIMILAR TO) | +| 37 | `expressions::it_parses_complex_boolean` | BoolExpr(AND/OR/NOT) nesting | +| 38 | `expressions::it_parses_coalesce` | CoalesceExpr | +| 39 | `expressions::it_parses_nullif` | NullIfExpr (maps to OpExpr or special) | +| 40 | `expressions::it_parses_greatest_least` | MinMaxExpr | +| 41 | `expressions::it_parses_pg_type_cast` | TypeCast + TypeName | +| 42 | `expressions::it_parses_sql_cast` | TypeCast via CAST() | +| 43 | `expressions::it_parses_array_cast` | TypeCast with array type | +| 44 | `expressions::it_parses_array_constructor` | ArrayExpr | +| 45 | `expressions::it_parses_array_subscript` | A_Indirection + A_Indices | +| 46 | `expressions::it_parses_array_slice` | A_Indirection + A_Indices (slice) | +| 47 | `expressions::it_parses_unnest` | FuncCall | +| 48 | `expressions::it_parses_json_operators` | A_Expr with JSON ops | +| 49 | `expressions::it_parses_jsonb_containment` | A_Expr with @> | +| 50 | `expressions::it_parses_positional_params` | ParamRef | +| 51 | `expressions::it_parses_params_in_insert` | ParamRef inside InsertStmt | +| 52 | `expressions::it_parses_current_timestamp` | SQLValueFunction | +| 53 | `expressions::it_parses_sql_value_functions` | All SQLValueFunction variants | +| 54 | `expressions::it_parses_real_world_query` | Combined: JOIN + BETWEEN + A_Expr + ORDER BY | +| 55 | `expressions::it_parses_bit_strings_hex` | Full query with X'...' literal | + +### Step 3 — `select` (complex queries) + +Progressively harder SELECT features. Each test tends to add one new node type. + +| # | Test name | Key new node types | +|---|-----------|-------------------| +| 56 | `select::it_parses_join` | JoinExpr (INNER) | +| 57 | `select::it_parses_left_join` | JoinExpr (LEFT) | +| 58 | `select::it_parses_right_join` | JoinExpr (RIGHT) | +| 59 | `select::it_parses_full_outer_join` | JoinExpr (FULL) | +| 60 | `select::it_parses_cross_join` | JoinExpr (CROSS) | +| 61 | `select::it_parses_natural_join` | JoinExpr (NATURAL) | +| 62 | `select::it_parses_join_using` | JoinExpr + USING list | +| 63 | `select::it_parses_multiple_joins` | Nested JoinExpr | +| 64 | `select::it_parses_lateral_join` | RangeSubselect (LATERAL) | +| 65 | `select::it_parses_union` | SelectStmt with set_op | +| 66 | `select::it_parses_intersect` | INTERSECT | +| 67 | `select::it_parses_except` | EXCEPT | +| 68 | `select::it_parses_union_all` | UNION ALL | +| 69 | `select::it_parses_compound_set_operations` | Nested set ops | +| 70 | `select::it_parses_subquery` | SubLink (IN) | +| 71 | `select::it_parses_correlated_subquery` | SubLink (EXISTS) | +| 72 | `select::it_parses_not_exists_subquery` | BoolExpr(NOT) + SubLink | +| 73 | `select::it_parses_scalar_subquery` | SubLink (scalar) in target | +| 74 | `select::it_parses_derived_table` | RangeSubselect | +| 75 | `select::it_parses_any_subquery` | SubLink (ANY) | +| 76 | `select::it_parses_all_subquery` | SubLink (ALL) | +| 77 | `select::it_parses_case_expression` | CaseExpr + CaseWhen | +| 78 | `select::it_parses_aggregates` | FuncCall (aggregate) | +| 79 | `select::it_parses_window_function` | WindowFunc, WindowDef | +| 80 | `select::it_parses_window_function_partition` | PARTITION BY | +| 81 | `select::it_parses_window_function_frame` | Frame clause (ROWS) | +| 82 | `select::it_parses_named_window` | WINDOW clause | +| 83 | `select::it_parses_lag_lead` | LAG/LEAD window funcs | +| 84 | `select::it_parses_cte` | WithClause + CommonTableExpr | +| 85 | `select::it_parses_multiple_ctes` | Multiple CTEs | +| 86 | `select::it_parses_recursive_cte` | RECURSIVE + UNION ALL | +| 87 | `select::it_parses_cte_with_columns` | CTE column list | +| 88 | `select::it_parses_cte_materialized` | MATERIALIZED hint | +| 89 | `select::it_parses_cte_search_breadth_first` | SEARCH BREADTH FIRST | +| 90 | `select::it_parses_cte_search_depth_first` | SEARCH DEPTH FIRST | +| 91 | `select::it_parses_cte_cycle` | CYCLE detection | +| 92 | `select::it_parses_cte_search_and_cycle` | Combined SEARCH + CYCLE | +| 93 | `select::it_parses_group_by_rollup` | GroupingSet (ROLLUP) | +| 94 | `select::it_parses_group_by_cube` | GroupingSet (CUBE) | +| 95 | `select::it_parses_grouping_sets` | GroupingSet (SETS) | +| 96 | `select::it_parses_distinct_on` | DISTINCT ON | +| 97 | `select::it_parses_order_by_nulls` | SortBy (NULLS LAST) | +| 98 | `select::it_parses_fetch_first` | FETCH FIRST | +| 99 | `select::it_parses_offset_fetch` | OFFSET + FETCH | +| 100 | `select::it_parses_for_update` | LockingClause | +| 101 | `select::it_parses_for_share` | LockingClause (SHARE) | +| 102 | `select::it_parses_for_update_nowait` | LockingClause (NOWAIT) | +| 103 | `select::it_parses_for_update_skip_locked` | LockingClause (SKIP LOCKED) | +| 104 | `select::it_parses_complex_select` | All basic features combined | +| 105 | `select::it_parses_analytics_query` | Window + interval + aggregate | +| 106 | `select::it_parses_hierarchy_query` | Recursive CTE + ARRAY + path | +| 107 | `select::it_parses_complex_report_query` | CTE + JOIN + NULLIF + window | +| 108 | `select::it_parses_mixed_subqueries_and_ctes` | CTE + scalar sub + EXISTS + ORDER BY sub | +| 109 | `select::it_parses_column_with_collate` | CollateClause | +| 110 | `select::it_parses_partition_by_range` | PartitionSpec + PartitionElem | +| 111 | `select::it_parses_partition_by_list` | PARTITION BY LIST | +| 112 | `select::it_parses_partition_by_hash` | PARTITION BY HASH | +| 113 | `select::it_parses_partition_for_values_range` | PartitionBoundSpec (range) | +| 114 | `select::it_parses_partition_for_values_list` | PartitionBoundSpec (list) | +| 115 | `select::it_parses_partition_for_values_hash` | PartitionBoundSpec (hash) | +| 116 | `select::it_parses_partition_default` | PartitionBoundSpec (default) | + +### Step 4 — `dml` (INSERT / UPDATE / DELETE) + +| # | Test name | Key new node types | +|---|-----------|-------------------| +| 117 | `dml::it_parses_insert` | InsertStmt basic | +| 118 | `dml::it_parses_update` | UpdateStmt basic | +| 119 | `dml::it_parses_delete` | DeleteStmt basic | +| 120 | `dml::it_parses_insert_returning` | RETURNING clause | +| 121 | `dml::it_parses_insert_multiple_rows` | Multiple VALUES tuples | +| 122 | `dml::it_parses_insert_default_values` | SetToDefault node | +| 123 | `dml::it_parses_insert_select` | INSERT ... SELECT | +| 124 | `dml::it_parses_insert_select_complex` | INSERT ... SELECT with aggregates | +| 125 | `dml::it_parses_insert_on_conflict_do_nothing` | OnConflictClause (DO NOTHING) | +| 126 | `dml::it_parses_insert_on_conflict` | OnConflictClause (DO UPDATE) + InferClause | +| 127 | `dml::it_parses_insert_on_conflict_with_where` | ON CONFLICT with WHERE | +| 128 | `dml::it_parses_insert_on_conflict_multiple_columns` | Multi-column conflict | +| 129 | `dml::it_parses_insert_returning_multiple` | RETURNING multiple cols | +| 130 | `dml::it_parses_insert_with_subquery_value` | SubLink in VALUES | +| 131 | `dml::it_parses_insert_overriding` | OVERRIDING SYSTEM VALUE | +| 132 | `dml::it_parses_insert_with_cte` | INSERT with CTE | +| 133 | `dml::it_parses_update_multiple_columns` | Multiple SET clauses | +| 134 | `dml::it_parses_update_returning` | UPDATE RETURNING | +| 135 | `dml::it_parses_update_with_subquery_set` | SubLink in SET | +| 136 | `dml::it_parses_update_from` | FROM clause in UPDATE | +| 137 | `dml::it_parses_update_from_multiple_tables` | FROM + JOIN in UPDATE | +| 138 | `dml::it_parses_update_with_cte` | UPDATE with CTE | +| 139 | `dml::it_parses_update_complex_where` | Complex WHERE + NOT EXISTS | +| 140 | `dml::it_parses_update_row_comparison` | MultiAssignRef | +| 141 | `dml::it_parses_update_with_case` | CaseExpr in SET | +| 142 | `dml::it_parses_update_array` | FuncCall (array_append) | +| 143 | `dml::it_parses_delete_returning` | DELETE RETURNING | +| 144 | `dml::it_parses_delete_with_subquery` | SubLink in DELETE WHERE | +| 145 | `dml::it_parses_delete_using` | USING clause | +| 146 | `dml::it_parses_delete_using_multiple_tables` | USING + multiple tables | +| 147 | `dml::it_parses_delete_with_cte` | DELETE with CTE | +| 148 | `dml::it_parses_delete_with_exists` | NOT EXISTS in DELETE | +| 149 | `dml::it_parses_delete_complex_conditions` | Complex boolean WHERE | +| 150 | `dml::it_parses_delete_only` | DELETE FROM ONLY | +| 151 | `dml::it_parses_insert_cte_returning` | DML CTE (INSERT in CTE) | +| 152 | `dml::it_parses_update_cte_returning` | DML CTE (UPDATE in CTE) | +| 153 | `dml::it_parses_delete_cte_returning` | DML CTE (DELETE in CTE) | +| 154 | `dml::it_parses_chained_dml_ctes` | Multiple DML CTEs chained | + +### Step 5 — `ddl` (CREATE / ALTER / DROP) + +| # | Test name | Key new node types | +|---|-----------|-------------------| +| 155 | `ddl::it_parses_create_table` | CreateStmt, ColumnDef, TypeName | +| 156 | `ddl::it_parses_drop_table` | DropStmt | +| 157 | `ddl::it_parses_create_index` | IndexStmt | +| 158 | `ddl::it_parses_create_table_with_constraints` | Constraint nodes | +| 159 | `ddl::it_parses_create_table_as` | CreateTableAsStmt | +| 160 | `ddl::it_parses_create_view` | ViewStmt | +| 161 | `ddl::it_parses_create_materialized_view` | CreateTableAsStmt (matview) | +| 162 | `ddl::it_parses_alter_table_add_column` | AlterTableStmt + AlterTableCmd | +| 163 | `ddl::it_parses_alter_table_drop_column` | AlterTableCmd (DROP) | +| 164 | `ddl::it_parses_alter_table_add_constraint` | Constraint (FK) | +| 165 | `ddl::it_parses_alter_table_rename` | RenameStmt | +| 166 | `ddl::it_parses_alter_table_rename_column` | RenameStmt (column) | +| 167 | `ddl::it_parses_alter_owner` | AlterOwnerStmt | +| 168 | `ddl::it_parses_create_index_expression` | IndexElem with expr | +| 169 | `ddl::it_parses_partial_unique_index` | IndexStmt with WHERE | +| 170 | `ddl::it_parses_create_index_concurrently` | IndexStmt (CONCURRENTLY) | +| 171 | `ddl::it_parses_truncate` | TruncateStmt | +| 172 | `ddl::it_parses_create_sequence` | CreateSeqStmt | +| 173 | `ddl::it_parses_create_sequence_with_options` | DefElem options | +| 174 | `ddl::it_parses_create_sequence_if_not_exists` | IF NOT EXISTS | +| 175 | `ddl::it_parses_alter_sequence` | AlterSeqStmt | +| 176 | `ddl::it_parses_create_domain` | CreateDomainStmt | +| 177 | `ddl::it_parses_create_domain_not_null` | Domain + NOT NULL | +| 178 | `ddl::it_parses_create_domain_default` | Domain + DEFAULT | +| 179 | `ddl::it_parses_create_composite_type` | CompositeTypeStmt | +| 180 | `ddl::it_parses_create_enum_type` | CreateEnumStmt | +| 181 | `ddl::it_parses_create_extension` | CreateExtensionStmt | +| 182 | `ddl::it_parses_create_extension_with_schema` | WITH SCHEMA option | +| 183 | `ddl::it_parses_create_publication` | CreatePublicationStmt | +| 184 | `ddl::it_parses_create_publication_for_tables` | FOR TABLE | +| 185 | `ddl::it_parses_alter_publication` | AlterPublicationStmt | +| 186 | `ddl::it_parses_create_subscription` | CreateSubscriptionStmt | +| 187 | `ddl::it_parses_alter_subscription` | AlterSubscriptionStmt | +| 188 | `ddl::it_parses_create_trigger` | CreateTrigStmt | +| 189 | `ddl::it_parses_create_trigger_after_update` | Trigger with WHEN | +| 190 | `ddl::it_parses_create_constraint_trigger` | Constraint trigger | + +### Step 6 — `statements` (utility / session) + +| # | Test name | Key new node types | +|---|-----------|-------------------| +| 191 | `statements::it_parses_explain` | ExplainStmt | +| 192 | `statements::it_parses_explain_analyze` | ExplainStmt with options | +| 193 | `statements::it_parses_copy` | CopyStmt | +| 194 | `statements::it_parses_prepare` | PrepareStmt | +| 195 | `statements::it_parses_execute` | ExecuteStmt | +| 196 | `statements::it_parses_deallocate` | DeallocateStmt | +| 197 | `statements::it_parses_begin` | TransactionStmt | +| 198 | `statements::it_parses_begin_with_options` | Transaction options | +| 199 | `statements::it_parses_commit` | TransactionStmt (COMMIT) | +| 200 | `statements::it_parses_rollback` | TransactionStmt (ROLLBACK) | +| 201 | `statements::it_parses_start_transaction` | START TRANSACTION | +| 202 | `statements::it_parses_savepoint` | TransactionStmt (SAVEPOINT) | +| 203 | `statements::it_parses_rollback_to_savepoint` | ROLLBACK TO | +| 204 | `statements::it_parses_release_savepoint` | RELEASE | +| 205 | `statements::it_parses_vacuum` | VacuumStmt | +| 206 | `statements::it_parses_vacuum_table` | VacuumStmt + relation | +| 207 | `statements::it_parses_vacuum_analyze` | VACUUM ANALYZE | +| 208 | `statements::it_parses_vacuum_full` | VACUUM FULL | +| 209 | `statements::it_parses_analyze` | VacuumStmt (ANALYZE) | +| 210 | `statements::it_parses_analyze_table` | ANALYZE + relation | +| 211 | `statements::it_parses_analyze_columns` | ANALYZE + column list | +| 212 | `statements::it_parses_set` | VariableSetStmt | +| 213 | `statements::it_parses_set_equals` | SET with = | +| 214 | `statements::it_parses_set_local` | SET LOCAL | +| 215 | `statements::it_parses_set_session` | SET SESSION | +| 216 | `statements::it_parses_reset` | VariableSetStmt (RESET) | +| 217 | `statements::it_parses_reset_all` | RESET ALL | +| 218 | `statements::it_parses_show` | VariableShowStmt | +| 219 | `statements::it_parses_show_all` | SHOW ALL | +| 220 | `statements::it_parses_listen` | ListenStmt | +| 221 | `statements::it_parses_notify` | NotifyStmt | +| 222 | `statements::it_parses_notify_with_payload` | NotifyStmt + payload | +| 223 | `statements::it_parses_unlisten` | UnlistenStmt | +| 224 | `statements::it_parses_unlisten_all` | UNLISTEN * | +| 225 | `statements::it_parses_discard_all` | DiscardStmt | +| 226 | `statements::it_parses_discard_plans` | DISCARD PLANS | +| 227 | `statements::it_parses_discard_sequences` | DISCARD SEQUENCES | +| 228 | `statements::it_parses_discard_temp` | DISCARD TEMP | +| 229 | `statements::it_parses_lock_table` | LockStmt | +| 230 | `statements::it_parses_lock_multiple_tables` | LockStmt multi-table | +| 231 | `statements::it_parses_do_statement` | DoStmt | +| 232 | `statements::it_parses_do_with_language` | DoStmt + language | + +--- + +## Progress Tracking + +**232 / 232 tests passing** (100%) ✅ — last checked 2026-02-08 + +### Step 1 — `basic`: ✅ 21/21 +### Step 2 — `expressions`: ✅ 34/34 +### Step 3 — `select`: ✅ 61/61 +### Step 4 — `dml`: ✅ 38/38 +### Step 5 — `ddl`: ✅ 36/36 +### Step 6 — `statements`: ✅ 42/42 + +All 232 tests pass. The iterative migration is functionally complete. + +--- + +## After All Tests Pass + +1. **Run full regression**: `cargo test --test raw_parse_iter_tests` +2. **Run acceptance suite** against `parse_raw_iter` (port `parse_raw_acceptance.rs`) +3. **Run benchmarks**: `cargo test --test raw_parse_iter_tests benchmark -- --nocapture` (if added) +4. **Clean up dead code**: remove unused standalone `convert_*` functions, the legacy + `convert_node()` / `convert_node_boxed()` functions, `convert_list_to_nodes()`, + and the `stacker` dependency +5. **Swap entrypoint**: make `parse_raw` call the iterative implementation internally + +## General Rules + +- **No recursive fallback**: every node type that appears in a parse tree must have its + own match arm in `process()`. The `_ =>` catch-all panics to surface missing types early. +- **Queue and collect must be symmetric**: every node queued produces exactly one result. + Every `fetch_list_results` call must match a corresponding `queue_list_nodes` call with + the same list pointer. Every `single_result` must match a `queue_node`. +- **Null pointers produce no result**: `queue_node` with null is skipped in `process()`, + `single_result` checks the original pointer to decide whether to pop. +- **Null lists produce no results**: `queue_list_nodes` and `fetch_list_results` both + early-return on null. +- **Push order**: `queue_collect` first (bottom of stack), then children left-to-right. + Children are processed right-to-left (LIFO). Results accumulate in reverse. + `fetch_results` drains from the end and reverses. +- **One test at a time**: run, fix, verify, commit. diff --git a/docs/recursion_classification.md b/docs/recursion_classification.md new file mode 100644 index 0000000..28c70f3 --- /dev/null +++ b/docs/recursion_classification.md @@ -0,0 +1,343 @@ +# Recursion Classification of `raw_parse.rs` Convert Functions + +This document classifies every `convert_*` function in `raw_parse.rs` into two categories: + +1. **Recursive** — the function calls `convert_node`, `convert_node_boxed`, `convert_list_to_nodes`, + or transitively calls another function that does, meaning it re-enters the main `convert_node` + dispatch and can contribute to unbounded recursion depth. + +2. **Non-recursive (leaf)** — the function never re-enters `convert_node`. It only reads scalar + fields, calls `convert_c_string`, or calls other leaf functions. These are safe from stack + overflow regardless of input. + +--- + +## Transitive helper classification + +Before classifying the top-level converters, we need to know which "typed helper" converters +are themselves recursive. A helper is recursive if it calls `convert_node_boxed`, +`convert_list_to_nodes`, or another recursive helper. + +| Helper | Recursive? | Reason | +|---|---|---| +| `convert_c_string` | No | Pure C string → Rust string | +| `convert_function_parameter_mode` | No | Pure enum mapping | +| `convert_string` | No | Only calls `convert_c_string` | +| `convert_role_spec` | No | Only calls `convert_c_string` | +| `convert_json_format` | No | Only scalar fields | +| `convert_json_returning` | No | Only calls `convert_json_format` (leaf) | +| `convert_json_table_path` | No | Only calls `convert_c_string` | +| `convert_alias` | **Yes** | Calls `convert_list_to_nodes(alias.colnames)` | +| `convert_range_var` | **Yes** | Calls `convert_alias` (recursive) | +| `convert_type_name` | **Yes** | Calls `convert_list_to_nodes` (×3: names, typmods, arrayBounds) | +| `convert_object_with_args` | **Yes** | Calls `convert_list_to_nodes` (×3) | +| `convert_window_def` | **Yes** | Calls `convert_list_to_nodes`, `convert_node_boxed` | +| `convert_with_clause` | **Yes** | Calls `convert_list_to_nodes` | +| `convert_with_clause_opt` | **Yes** | Calls `convert_with_clause` | +| `convert_variable_set_stmt` | **Yes** | Calls `convert_list_to_nodes` | +| `convert_variable_set_stmt_opt` | **Yes** | Calls `convert_variable_set_stmt` | +| `convert_collate_clause` | **Yes** | Calls `convert_node_boxed`, `convert_list_to_nodes` | +| `convert_collate_clause_opt` | **Yes** | Calls `convert_collate_clause` | +| `convert_partition_spec` | **Yes** | Calls `convert_list_to_nodes` | +| `convert_partition_spec_opt` | **Yes** | Calls `convert_partition_spec` | +| `convert_partition_bound_spec` | **Yes** | Calls `convert_list_to_nodes` (×3) | +| `convert_partition_bound_spec_opt` | **Yes** | Calls `convert_partition_bound_spec` | +| `convert_cte_search_clause` | **Yes** | Calls `convert_list_to_nodes` | +| `convert_cte_search_clause_opt` | **Yes** | Calls `convert_cte_search_clause` | +| `convert_cte_cycle_clause` | **Yes** | Calls `convert_list_to_nodes`, `convert_node_boxed` | +| `convert_cte_cycle_clause_opt` | **Yes** | Calls `convert_cte_cycle_clause` | +| `convert_infer_clause` | **Yes** | Calls `convert_list_to_nodes`, `convert_node_boxed` | +| `convert_infer_clause_opt` | **Yes** | Calls `convert_list_to_nodes`, `convert_node_boxed` | +| `convert_on_conflict_clause` | **Yes** | Calls `convert_infer_clause`, `convert_list_to_nodes`, `convert_node_boxed` | +| `convert_into_clause` | **Yes** | Calls `convert_range_var`, `convert_list_to_nodes`, `convert_node_boxed` | +| `convert_json_output` | **Yes** | Calls `convert_type_name` (recursive) | +| `convert_json_value_expr` | **Yes** | Calls `convert_node_boxed` | +| `convert_json_behavior` | **Yes** | Calls `convert_node_boxed` | +| `convert_json_agg_constructor` | **Yes** | Calls `convert_node_boxed`, `convert_list_to_nodes`, `convert_window_def` | +| `convert_json_key_value` | **Yes** | Calls `convert_node_boxed`, `convert_json_value_expr` | +| `convert_func_call` | **Yes** | Calls `convert_list_to_nodes`, `convert_node_boxed`, `convert_window_def` | +| `convert_grant_stmt` | **Yes** | Calls `convert_list_to_nodes`, `convert_role_spec` | +| `convert_publication_table` | **Yes** | Calls `convert_range_var`, `convert_node_boxed`, `convert_list_to_nodes` | +| `convert_create_stmt` | **Yes** | Calls `convert_range_var`, `convert_list_to_nodes`, `convert_type_name`, etc. | + +--- + +## Non-Recursive (Leaf) Functions + +These functions **never** re-enter `convert_node` — not directly and not through any transitive +call chain. They are entirely safe from contributing to stack depth. + +| # | Function | Lines | What it calls | +|---|---|---|---| +| 1 | `convert_c_string` | L2177–2183 | — (raw pointer to String) | +| 2 | `convert_function_parameter_mode` | L1933–1943 | — (pure enum match) | +| 3 | `convert_string` | L1516–1518 | `convert_c_string` | +| 4 | `convert_a_const` | L1179–1217 | Reads union fields directly; no node calls | +| 5 | `convert_bit_string` | L2189–2191 | `convert_c_string` | +| 6 | `convert_role_spec` | L1600–1602 | `convert_c_string` | +| 7 | `convert_replica_identity_stmt` | L1520–1522 | `convert_c_string` | +| 8 | `convert_notify_stmt` | L1945–1947 | `convert_c_string` | +| 9 | `convert_listen_stmt` | L1949–1951 | `convert_c_string` | +| 10 | `convert_unlisten_stmt` | L1953–1955 | `convert_c_string` | +| 11 | `convert_discard_stmt` | L1957–1961 | Scalar only | +| 12 | `convert_set_to_default` | L1658–1666 | Scalar only | +| 13 | `convert_trigger_transition` | L2765–2767 | `convert_c_string` | +| 14 | `convert_variable_show_stmt` | L1836–1838 | `convert_c_string` | +| 15 | `convert_deallocate_stmt` | L1654–1656 | `convert_c_string`, scalars | +| 16 | `convert_close_portal_stmt` | L2217–2219 | `convert_c_string` | +| 17 | `convert_fetch_stmt` | L2221–2228 | `convert_c_string`, scalars | +| 18 | `convert_load_stmt` | L2651–2653 | `convert_c_string` | +| 19 | `convert_alter_database_refresh_coll_stmt` | L2622–2624 | `convert_c_string` | +| 20 | `convert_alter_event_trig_stmt` | L2314–2319 | `convert_c_string`, scalars | +| 21 | `convert_drop_table_space_stmt` | L2464–2466 | `convert_c_string`, scalars | +| 22 | `convert_drop_subscription_stmt` | L2666–2668 | `convert_c_string`, scalars | +| 23 | `convert_drop_user_mapping_stmt` | L2436–2442 | `convert_role_spec` (leaf), `convert_c_string` | +| 24 | `convert_json_format` | L2837–2839 | Scalar only | +| 25 | `convert_json_returning` | L2841–2847 | `convert_json_format` (leaf) | +| 26 | `convert_json_table_path` | L2907–2910 | `convert_c_string` | +| 27 | `convert_sql_value_function` | L2793–2795 | Scalar only | + +**Total: 27 non-recursive functions** + +--- + +## Recursive Functions + +These functions **do** re-enter `convert_node` (directly via `convert_node_boxed` / +`convert_list_to_nodes`, or transitively via a recursive helper like `convert_range_var`, +`convert_alias`, `convert_type_name`, etc.). They contribute to stack depth when processing +nested parse trees. + +| # | Function | Lines | Recursive calls (direct) | +|---|---|---|---| +| 1 | `convert_node` | L102–988 | Central dispatch — calls all `convert_*` functions | +| 2 | `convert_node_boxed` | L97–99 | `convert_node` | +| 3 | `convert_list` | L991–994 | `convert_list_to_nodes` | +| 4 | `convert_list_to_nodes` | L999–1020 | `convert_node` (per element) | +| 5 | `convert_list_to_raw_stmts` | L66–89 | `convert_raw_stmt` | +| 6 | `convert_raw_stmt` | L92–94 | `convert_node_boxed` | +| 7 | `convert_select_stmt` | L1026–1049 | `convert_list_to_nodes` (×8), `convert_node_boxed` (×4), `convert_into_clause`, `convert_with_clause_opt`, **self-recursive via larg/rarg** | +| 8 | `convert_insert_stmt` | L1051–1061 | `convert_range_var`, `convert_list_to_nodes` (×2), `convert_node_boxed`, `convert_on_conflict_clause`, `convert_with_clause_opt` | +| 9 | `convert_update_stmt` | L1063–1072 | `convert_range_var`, `convert_list_to_nodes` (×3), `convert_node_boxed`, `convert_with_clause_opt` | +| 10 | `convert_delete_stmt` | L1074–1082 | `convert_range_var`, `convert_list_to_nodes` (×2), `convert_node_boxed`, `convert_with_clause_opt` | +| 11 | `convert_create_stmt` | L1084–1099 | `convert_range_var`, `convert_list_to_nodes` (×4), `convert_type_name`, `convert_partition_bound_spec_opt`, `convert_partition_spec_opt` | +| 12 | `convert_drop_stmt` | L1101–1109 | `convert_list_to_nodes` | +| 13 | `convert_index_stmt` | L1111–1138 | `convert_range_var`, `convert_list_to_nodes` (×4), `convert_node_boxed` | +| 14 | `convert_range_var` | L1144–1154 | `convert_alias` → `convert_list_to_nodes` | +| 15 | `convert_column_ref` | L1156–1158 | `convert_list_to_nodes` | +| 16 | `convert_res_target` | L1160–1167 | `convert_list_to_nodes`, `convert_node_boxed` | +| 17 | `convert_a_expr` | L1169–1177 | `convert_list_to_nodes`, `convert_node_boxed` (×2) | +| 18 | `convert_func_call` | L1219–1233 | `convert_list_to_nodes` (×3), `convert_node_boxed`, `convert_window_def` | +| 19 | `convert_type_cast` | L1235–1241 | `convert_node_boxed`, `convert_type_name` | +| 20 | `convert_type_name` | L1243–1254 | `convert_list_to_nodes` (×3) | +| 21 | `convert_alias` | L1256–1258 | `convert_list_to_nodes` | +| 22 | `convert_join_expr` | L1260–1272 | `convert_node_boxed` (×3), `convert_list_to_nodes`, `convert_alias` (×2) | +| 23 | `convert_sort_by` | L1274–1282 | `convert_node_boxed`, `convert_list_to_nodes` | +| 24 | `convert_bool_expr` | L1284–1291 | `convert_list_to_nodes` | +| 25 | `convert_sub_link` | L1293–1303 | `convert_node_boxed` (×2), `convert_list_to_nodes` | +| 26 | `convert_null_test` | L1305–1313 | `convert_node_boxed` | +| 27 | `convert_case_expr` | L1315–1325 | `convert_node_boxed` (×2), `convert_list_to_nodes` | +| 28 | `convert_case_when` | L1327–1334 | `convert_node_boxed` (×2) | +| 29 | `convert_coalesce_expr` | L1336–1344 | `convert_list_to_nodes` | +| 30 | `convert_with_clause` | L1346–1348 | `convert_list_to_nodes` | +| 31 | `convert_with_clause_opt` | L1350–1356 | `convert_with_clause` | +| 32 | `convert_common_table_expr` | L1358–1374 | `convert_list_to_nodes` (×5), `convert_node_boxed`, `convert_cte_search_clause_opt`, `convert_cte_cycle_clause_opt` | +| 33 | `convert_window_def` | L1376–1387 | `convert_list_to_nodes` (×2), `convert_node_boxed` (×2) | +| 34 | `convert_into_clause` | L1389–1404 | `convert_range_var`, `convert_list_to_nodes` (×2), `convert_node_boxed` | +| 35 | `convert_infer_clause` | L1406–1417 | `convert_list_to_nodes`, `convert_node_boxed` | +| 36 | `convert_on_conflict_clause` | L1419–1431 | `convert_infer_clause`, `convert_list_to_nodes`, `convert_node_boxed` | +| 37 | `convert_column_def` | L1433–1455 | `convert_type_name`, `convert_node_boxed` (×2), `convert_range_var`, `convert_collate_clause_opt`, `convert_list_to_nodes` (×2) | +| 38 | `convert_constraint` | L1457–1491 | `convert_node_boxed` (×2), `convert_list_to_nodes` (×7), `convert_range_var` | +| 39 | `convert_index_elem` | L1493–1504 | `convert_node_boxed`, `convert_list_to_nodes` (×3) | +| 40 | `convert_def_elem` | L1506–1514 | `convert_node_boxed` | +| 41 | `convert_grouping_func` | L1524–1532 | `convert_list_to_nodes` (×2) | +| 42 | `convert_locking_clause` | L1534–1540 | `convert_list_to_nodes` | +| 43 | `convert_min_max_expr` | L1542–1552 | `convert_list_to_nodes` | +| 44 | `convert_grouping_set` | L1554–1556 | `convert_list_to_nodes` | +| 45 | `convert_range_subselect` | L1558–1564 | `convert_node_boxed`, `convert_alias` | +| 46 | `convert_a_array_expr` | L1566–1568 | `convert_list_to_nodes` | +| 47 | `convert_a_indirection` | L1570–1572 | `convert_node_boxed`, `convert_list_to_nodes` | +| 48 | `convert_a_indices` | L1574–1576 | `convert_node_boxed` (×2) | +| 49 | `convert_alter_table_stmt` | L1578–1585 | `convert_range_var`, `convert_list_to_nodes` | +| 50 | `convert_alter_table_cmd` | L1587–1598 | `convert_role_spec`, `convert_node_boxed` | +| 51 | `convert_copy_stmt` | L1604–1615 | `convert_range_var`, `convert_node_boxed` (×2), `convert_list_to_nodes` (×2) | +| 52 | `convert_truncate_stmt` | L1617–1619 | `convert_list_to_nodes` | +| 53 | `convert_view_stmt` | L1621–1630 | `convert_range_var`, `convert_list_to_nodes` (×2), `convert_node_boxed` | +| 54 | `convert_explain_stmt` | L1632–1634 | `convert_node_boxed`, `convert_list_to_nodes` | +| 55 | `convert_create_table_as_stmt` | L1636–1644 | `convert_node_boxed`, `convert_into_clause` | +| 56 | `convert_prepare_stmt` | L1646–1648 | `convert_list_to_nodes`, `convert_node_boxed` | +| 57 | `convert_execute_stmt` | L1650–1652 | `convert_list_to_nodes` | +| 58 | `convert_multi_assign_ref` | L1668–1670 | `convert_node_boxed` | +| 59 | `convert_row_expr` | L1672–1681 | `convert_list_to_nodes` (×2) | +| 60 | `convert_collate_clause` | L1683–1685 | `convert_node_boxed`, `convert_list_to_nodes` | +| 61 | `convert_collate_clause_opt` | L1687–1693 | `convert_collate_clause` | +| 62 | `convert_partition_spec` | L1695–1706 | `convert_list_to_nodes` | +| 63 | `convert_partition_spec_opt` | L1708–1714 | `convert_partition_spec` | +| 64 | `convert_partition_bound_spec` | L1716–1727 | `convert_list_to_nodes` (×3) | +| 65 | `convert_partition_bound_spec_opt` | L1729–1735 | `convert_partition_bound_spec` | +| 66 | `convert_partition_elem` | L1737–1745 | `convert_node_boxed`, `convert_list_to_nodes` (×2) | +| 67 | `convert_partition_range_datum` | L1747–1758 | `convert_node_boxed` | +| 68 | `convert_cte_search_clause` | L1760–1767 | `convert_list_to_nodes` | +| 69 | `convert_cte_search_clause_opt` | L1769–1775 | `convert_cte_search_clause` | +| 70 | `convert_cte_cycle_clause` | L1777–1790 | `convert_list_to_nodes`, `convert_node_boxed` (×2) | +| 71 | `convert_cte_cycle_clause_opt` | L1792–1798 | `convert_cte_cycle_clause` | +| 72 | `convert_transaction_stmt` | L1804–1813 | `convert_list_to_nodes` | +| 73 | `convert_vacuum_stmt` | L1815–1817 | `convert_list_to_nodes` (×2) | +| 74 | `convert_vacuum_relation` | L1819–1825 | `convert_range_var`, `convert_list_to_nodes` | +| 75 | `convert_variable_set_stmt` | L1827–1834 | `convert_list_to_nodes` | +| 76 | `convert_create_seq_stmt` | L1840–1848 | `convert_range_var`, `convert_list_to_nodes` | +| 77 | `convert_do_stmt` | L1850–1852 | `convert_list_to_nodes` | +| 78 | `convert_lock_stmt` | L1854–1856 | `convert_list_to_nodes` | +| 79 | `convert_create_schema_stmt` | L1858–1865 | `convert_role_spec`, `convert_list_to_nodes` | +| 80 | `convert_rename_stmt` | L1867–1878 | `convert_range_var`, `convert_node_boxed` | +| 81 | `convert_create_function_stmt` | L1880–1890 | `convert_list_to_nodes` (×3), `convert_type_name`, `convert_node_boxed` | +| 82 | `convert_alter_owner_stmt` | L1892–1899 | `convert_range_var`, `convert_node_boxed`, `convert_role_spec` | +| 83 | `convert_alter_seq_stmt` | L1901–1908 | `convert_range_var`, `convert_list_to_nodes` | +| 84 | `convert_create_enum_stmt` | L1910–1912 | `convert_list_to_nodes` (×2) | +| 85 | `convert_object_with_args` | L1914–1921 | `convert_list_to_nodes` (×3) | +| 86 | `convert_function_parameter` | L1923–1930 | `convert_type_name`, `convert_node_boxed` | +| 87 | `convert_coerce_to_domain` | L1963–1973 | `convert_node_boxed` | +| 88 | `convert_composite_type_stmt` | L1975–1980 | `convert_range_var`, `convert_list_to_nodes` | +| 89 | `convert_create_domain_stmt` | L1982–1989 | `convert_list_to_nodes` (×2), `convert_type_name`, `convert_collate_clause_opt` | +| 90 | `convert_create_extension_stmt` | L1991–1997 | `convert_list_to_nodes` | +| 91 | `convert_create_publication_stmt` | L1999–2006 | `convert_list_to_nodes` (×2) | +| 92 | `convert_alter_publication_stmt` | L2008–2016 | `convert_list_to_nodes` (×2) | +| 93 | `convert_create_subscription_stmt` | L2018–2025 | `convert_list_to_nodes` (×2) | +| 94 | `convert_alter_subscription_stmt` | L2027–2035 | `convert_list_to_nodes` (×2) | +| 95 | `convert_publication_obj_spec` | L2037–2040 | `convert_publication_table` (recursive) | +| 96 | `convert_publication_table` | L2042–2049 | `convert_range_var`, `convert_node_boxed`, `convert_list_to_nodes` | +| 97 | `convert_create_trig_stmt` | L2051–2069 | `convert_range_var` (×2), `convert_list_to_nodes` (×4), `convert_node_boxed` | +| 98 | `convert_call_stmt` | L2071–2077 | `convert_func_call`, `convert_list_to_nodes` | +| 99 | `convert_rule_stmt` | L2079–2089 | `convert_range_var`, `convert_node_boxed`, `convert_list_to_nodes` | +| 100 | `convert_grant_stmt` | L2091–2103 | `convert_list_to_nodes` (×3), `convert_role_spec` | +| 101 | `convert_grant_role_stmt` | L2105–2114 | `convert_list_to_nodes` (×3), `convert_role_spec` | +| 102 | `convert_refresh_mat_view_stmt` | L2116–2122 | `convert_range_var` (recursive via alias) | +| 103 | `convert_merge_stmt` | L2124–2133 | `convert_range_var`, `convert_node_boxed` (×2), `convert_list_to_nodes` (×2), `convert_with_clause_opt` | +| 104 | `convert_merge_action` | L2135–2144 | `convert_node_boxed`, `convert_list_to_nodes` (×2) | +| 105 | `convert_merge_when_clause` | L2146–2155 | `convert_node_boxed`, `convert_list_to_nodes` (×2) | +| 106 | `convert_range_function` | L2157–2166 | `convert_list_to_nodes` (×2), `convert_alias` | +| 107 | `convert_access_priv` | L2168–2170 | `convert_list_to_nodes` | +| 108 | `convert_boolean_test` | L2193–2200 | `convert_node_boxed` | +| 109 | `convert_create_range_stmt` | L2202–2204 | `convert_list_to_nodes` (×2) | +| 110 | `convert_alter_enum_stmt` | L2206–2215 | `convert_list_to_nodes` | +| 111 | `convert_declare_cursor_stmt` | L2230–2232 | `convert_node_boxed` | +| 112 | `convert_define_stmt` | L2234–2244 | `convert_list_to_nodes` (×3) | +| 113 | `convert_comment_stmt` | L2246–2248 | `convert_node_boxed` | +| 114 | `convert_sec_label_stmt` | L2250–2257 | `convert_node_boxed` | +| 115 | `convert_create_role_stmt` | L2259–2261 | `convert_list_to_nodes` | +| 116 | `convert_alter_role_stmt` | L2263–2269 | `convert_role_spec`, `convert_list_to_nodes` | +| 117 | `convert_alter_role_set_stmt` | L2271–2277 | `convert_role_spec`, `convert_variable_set_stmt_opt` (recursive) | +| 118 | `convert_drop_role_stmt` | L2279–2281 | `convert_list_to_nodes` | +| 119 | `convert_create_policy_stmt` | L2283–2293 | `convert_range_var`, `convert_list_to_nodes`, `convert_node_boxed` (×2) | +| 120 | `convert_alter_policy_stmt` | L2295–2303 | `convert_range_var`, `convert_list_to_nodes`, `convert_node_boxed` (×2) | +| 121 | `convert_create_event_trig_stmt` | L2305–2312 | `convert_list_to_nodes` (×2) | +| 122 | `convert_create_plang_stmt` | L2321–2330 | `convert_list_to_nodes` (×3) | +| 123 | `convert_create_am_stmt` | L2332–2338 | `convert_list_to_nodes` | +| 124 | `convert_create_op_class_stmt` | L2340–2349 | `convert_list_to_nodes` (×3), `convert_type_name` | +| 125 | `convert_create_op_class_item` | L2351–2360 | `convert_object_with_args`, `convert_list_to_nodes` (×2), `convert_type_name` | +| 126 | `convert_create_op_family_stmt` | L2362–2364 | `convert_list_to_nodes` | +| 127 | `convert_alter_op_family_stmt` | L2366–2373 | `convert_list_to_nodes` (×2) | +| 128 | `convert_create_fdw_stmt` | L2375–2381 | `convert_list_to_nodes` (×2) | +| 129 | `convert_alter_fdw_stmt` | L2383–2389 | `convert_list_to_nodes` (×2) | +| 130 | `convert_create_foreign_server_stmt` | L2391–2400 | `convert_list_to_nodes` | +| 131 | `convert_alter_foreign_server_stmt` | L2402–2409 | `convert_list_to_nodes` | +| 132 | `convert_create_foreign_table_stmt` | L2411–2417 | `convert_create_stmt` (recursive), `convert_list_to_nodes` | +| 133 | `convert_create_user_mapping_stmt` | L2419–2426 | `convert_role_spec`, `convert_list_to_nodes` | +| 134 | `convert_alter_user_mapping_stmt` | L2428–2434 | `convert_role_spec`, `convert_list_to_nodes` | +| 135 | `convert_import_foreign_schema_stmt` | L2444–2453 | `convert_list_to_nodes` (×2) | +| 136 | `convert_create_table_space_stmt` | L2455–2462 | `convert_role_spec`, `convert_list_to_nodes` | +| 137 | `convert_alter_table_space_options_stmt` | L2468–2474 | `convert_list_to_nodes` | +| 138 | `convert_alter_table_move_all_stmt` | L2476–2484 | `convert_list_to_nodes` | +| 139 | `convert_alter_extension_stmt` | L2486–2488 | `convert_list_to_nodes` | +| 140 | `convert_alter_extension_contents_stmt` | L2490–2497 | `convert_node_boxed` | +| 141 | `convert_alter_domain_stmt` | L2499–2508 | `convert_list_to_nodes`, `convert_node_boxed` | +| 142 | `convert_alter_function_stmt` | L2510–2516 | `convert_object_with_args`, `convert_list_to_nodes` | +| 143 | `convert_alter_operator_stmt` | L2518–2523 | `convert_object_with_args`, `convert_list_to_nodes` | +| 144 | `convert_alter_type_stmt` | L2525–2527 | `convert_list_to_nodes` (×2) | +| 145 | `convert_alter_object_schema_stmt` | L2529–2537 | `convert_range_var`, `convert_node_boxed` | +| 146 | `convert_alter_object_depends_stmt` | L2539–2547 | `convert_range_var`, `convert_node_boxed`, `convert_string` | +| 147 | `convert_alter_collation_stmt` | L2549–2551 | `convert_list_to_nodes` | +| 148 | `convert_alter_default_privileges_stmt` | L2553–2558 | `convert_list_to_nodes`, `convert_grant_stmt` (recursive) | +| 149 | `convert_create_cast_stmt` | L2560–2568 | `convert_type_name` (×2), `convert_object_with_args` | +| 150 | `convert_create_transform_stmt` | L2570–2578 | `convert_type_name`, `convert_object_with_args` (×2) | +| 151 | `convert_create_conversion_stmt` | L2580–2588 | `convert_list_to_nodes` (×2) | +| 152 | `convert_alter_ts_dictionary_stmt` | L2590–2592 | `convert_list_to_nodes` (×2) | +| 153 | `convert_alter_ts_configuration_stmt` | L2594–2604 | `convert_list_to_nodes` (×3) | +| 154 | `convert_createdb_stmt` | L2606–2608 | `convert_list_to_nodes` | +| 155 | `convert_dropdb_stmt` | L2610–2612 | `convert_list_to_nodes` | +| 156 | `convert_alter_database_stmt` | L2614–2616 | `convert_list_to_nodes` | +| 157 | `convert_alter_database_set_stmt` | L2618–2620 | `convert_variable_set_stmt_opt` (recursive) | +| 158 | `convert_alter_system_stmt` | L2626–2628 | `convert_variable_set_stmt_opt` (recursive) | +| 159 | `convert_cluster_stmt` | L2630–2636 | `convert_range_var`, `convert_list_to_nodes` | +| 160 | `convert_reindex_stmt` | L2638–2645 | `convert_range_var`, `convert_list_to_nodes` | +| 161 | `convert_constraints_set_stmt` | L2647–2649 | `convert_list_to_nodes` | +| 162 | `convert_drop_owned_stmt` | L2655–2657 | `convert_list_to_nodes` | +| 163 | `convert_reassign_owned_stmt` | L2659–2664 | `convert_list_to_nodes`, `convert_role_spec` | +| 164 | `convert_table_func` | L2670–2690 | `convert_list_to_nodes` (×11), `convert_node_boxed` (×3) | +| 165 | `convert_into_clause_node` | L2692–2703 | `convert_range_var`, `convert_list_to_nodes` (×2), `convert_node_boxed` | +| 166 | `convert_table_like_clause` | L2705–2711 | `convert_range_var` | +| 167 | `convert_range_table_func` | L2713–2723 | `convert_node_boxed` (×2), `convert_list_to_nodes` (×2), `convert_alias` | +| 168 | `convert_range_table_func_col` | L2725–2735 | `convert_type_name`, `convert_node_boxed` (×2) | +| 169 | `convert_range_table_sample` | L2737–2745 | `convert_node_boxed` (×2), `convert_list_to_nodes` (×2) | +| 170 | `convert_partition_cmd` | L2747–2753 | `convert_range_var`, `convert_partition_bound_spec_opt` | +| 171 | `convert_on_conflict_clause_node` | L2755–2763 | `convert_infer_clause_opt`, `convert_list_to_nodes`, `convert_node_boxed` | +| 172 | `convert_create_stats_stmt` | L2769–2779 | `convert_list_to_nodes` (×4) | +| 173 | `convert_alter_stats_stmt` | L2781–2787 | `convert_list_to_nodes`, `convert_node_boxed` | +| 174 | `convert_stats_elem` | L2789–2791 | `convert_node_boxed` | +| 175 | `convert_xml_expr` | L2797–2811 | `convert_list_to_nodes` (×3) | +| 176 | `convert_xml_serialize` | L2813–2821 | `convert_node_boxed`, `convert_type_name` | +| 177 | `convert_named_arg_expr` | L2823–2831 | `convert_node_boxed` | +| 178 | `convert_json_value_expr` | L2849–2855 | `convert_node_boxed` (×2), `convert_json_format` | +| 179 | `convert_json_constructor_expr` | L2857–2869 | `convert_list_to_nodes`, `convert_node_boxed` (×2), `convert_json_returning` | +| 180 | `convert_json_is_predicate` | L2871–2879 | `convert_node_boxed`, `convert_json_format` | +| 181 | `convert_json_behavior` | L2881–2883 | `convert_node_boxed` | +| 182 | `convert_json_expr` | L2885–2905 | `convert_node_boxed` (×2), `convert_json_format`, `convert_json_returning`, `convert_list_to_nodes` (×2), `convert_json_behavior` (×2) | +| 183 | `convert_json_table_path_scan` | L2912–2921 | `convert_node_boxed` (×2), `convert_json_table_path` | +| 184 | `convert_json_table_sibling_join` | L2923–2929 | `convert_node_boxed` (×3) | +| 185 | `convert_json_output` | L2931–2936 | `convert_type_name` (recursive), `convert_json_returning` | +| 186 | `convert_json_argument` | L2938–2943 | `convert_json_value_expr` (recursive) | +| 187 | `convert_json_func_expr` | L2945–2959 | `convert_json_value_expr`, `convert_node_boxed`, `convert_list_to_nodes`, `convert_json_output`, `convert_json_behavior` (×2) | +| 188 | `convert_json_table_path_spec` | L2961–2968 | `convert_node_boxed` | +| 189 | `convert_json_table` | L2970–2981 | `convert_json_value_expr`, `convert_json_table_path_spec`, `convert_list_to_nodes` (×2), `convert_json_behavior`, `convert_alias` | +| 190 | `convert_json_table_column` | L2983–2997 | `convert_type_name`, `convert_json_table_path_spec`, `convert_json_format`, `convert_list_to_nodes`, `convert_json_behavior` (×2) | +| 191 | `convert_json_key_value` | L2999–3004 | `convert_node_boxed`, `convert_json_value_expr` | +| 192 | `convert_json_parse_expr` | L3006–3013 | `convert_json_value_expr`, `convert_json_output` | +| 193 | `convert_json_scalar_expr` | L3015–3021 | `convert_node_boxed`, `convert_json_output` | +| 194 | `convert_json_serialize_expr` | L3023–3029 | `convert_json_value_expr`, `convert_json_output` | +| 195 | `convert_json_object_constructor` | L3031–3039 | `convert_list_to_nodes`, `convert_json_output` | +| 196 | `convert_json_array_constructor` | L3041–3048 | `convert_list_to_nodes`, `convert_json_output` | +| 197 | `convert_json_array_query_constructor` | L3050–3058 | `convert_node_boxed`, `convert_json_output`, `convert_json_format` | +| 198 | `convert_json_agg_constructor` | L3060–3068 | `convert_json_output`, `convert_node_boxed`, `convert_list_to_nodes`, `convert_window_def` | +| 199 | `convert_json_object_agg` | L3070–3077 | `convert_json_agg_constructor` (recursive), `convert_json_key_value` (recursive) | +| 200 | `convert_json_array_agg` | L3079–3085 | `convert_json_agg_constructor` (recursive), `convert_json_value_expr` (recursive) | +| 201 | `convert_variable_set_stmt_opt` | L3091–3097 | `convert_variable_set_stmt` (recursive) | +| 202 | `convert_infer_clause_opt` | L3099–3111 | `convert_list_to_nodes`, `convert_node_boxed` | + +**Total: 202 recursive functions** (including the 6 core dispatch/list functions) + +--- + +## Summary + +| Category | Count | Notes | +|---|---|---| +| **Non-recursive (leaf)** | 27 | Safe — never re-enter `convert_node` | +| **Recursive** | 202 | Re-enter `convert_node` directly or transitively | +| **Total** | 229 | All `convert_*` functions in `raw_parse.rs` | + +The overwhelming majority of functions are recursive because nearly every PostgreSQL node type +contains at least one `List` field (converted via `convert_list_to_nodes`) or a child `Node*` +pointer (converted via `convert_node_boxed`). Even seemingly simple types like `RangeVar` are +recursive because they contain an optional `Alias`, which in turn has a `colnames` list that +goes through `convert_list_to_nodes`. + +### Highest-risk recursion paths (unbounded depth) + +| Path | Cause | +|---|---| +| `convert_select_stmt` → `convert_select_stmt` (larg/rarg) | Long `UNION`/`INTERSECT`/`EXCEPT` chains | +| `convert_bool_expr` → `convert_list_to_nodes` → `convert_node` → `convert_bool_expr` | Deeply nested `AND`/`OR` | +| `convert_join_expr` → `convert_node_boxed` (larg/rarg) → `convert_node` → `convert_join_expr` | Many-way explicit `JOIN` | +| `convert_sub_link` → `convert_node_boxed` (subselect) → `convert_node` → `convert_select_stmt` → ... | Nested subqueries | +| `convert_a_expr` → `convert_node_boxed` (lexpr/rexpr) → `convert_node` → `convert_a_expr` | Deeply nested expressions | +| `convert_case_expr` → `convert_list_to_nodes` → ... → `convert_case_expr` | Nested `CASE` expressions | diff --git a/raw_parse_iter.md b/raw_parse_iter.md new file mode 100644 index 0000000..9dc8808 --- /dev/null +++ b/raw_parse_iter.md @@ -0,0 +1,15 @@ + Root + / | \ + left mid right + +1. Stack: Node(Root), result: [] +2. Stack: Collect(Root), Node(left), Node(mid), Node(right), result: [] +3. Stack: Collect(Root), Node(left), Node(mid), Collect(right), Node(right_1), Node(right_2), result: [] +4. Stack: Collect(Root), Node(left), Node(mid), Collect(right), Node(right_1), result: [right_2] +5. Stack: Collect(Root), Node(left), Node(mid), Collect(right), Collect(right_1), Node(right_1_1), Node(right_1_2), result: [right_2] +6. Stack: Collect(Root), Node(left), Node(mid), Collect(right), Collect(right_1), Node(right_1_1), result: [right_2, right_1_2] +7. Stack: Collect(Root), Node(left), Node(mid), Collect(right), Collect(right_1), result: [right_2, right_1_2, right_1_1] +8. Stack: Collect(Root), Node(left), Node(mid), Collect(right), result: [right_2, right_1] +9. Stack: Collect(Root), Node(left), Node(mid), result: [right] +10. Stack: Collect(Root), Node(left), result: [right, mid] +11. Stack: Collect(Root), Collect(left), Node(left_1), Node(left_2), result: [right, mid] diff --git a/src/lib.rs b/src/lib.rs index 2a12dfb..d816e58 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,8 +50,10 @@ mod parse_result; pub mod protobuf; mod query; mod raw_deparse; +mod raw_deparse_iter; mod raw_fingerprint; mod raw_parse; +mod raw_parse_iter; mod raw_scan; mod summary; mod summary_result; @@ -66,6 +68,8 @@ pub use query::*; pub use raw_deparse::{deparse_raw, deparse_raw_with_stack}; pub use raw_fingerprint::fingerprint_raw; pub use raw_parse::{parse_raw, parse_raw_with_stack}; +pub use raw_parse_iter::parse_raw_iter; +pub use raw_deparse_iter::deparse_raw_iter; pub use raw_scan::scan_raw; pub use summary::*; pub use summary_result::*; diff --git a/src/raw_deparse_iter.rs b/src/raw_deparse_iter.rs new file mode 100644 index 0000000..be56f37 --- /dev/null +++ b/src/raw_deparse_iter.rs @@ -0,0 +1,7 @@ +use crate::{deparse_raw, protobuf}; +use crate::{Error, Result}; + +pub fn deparse_raw_iter(protobuf: &protobuf::ParseResult) -> Result { + // TODO: Implement iterative deparsing + deparse_raw(protobuf) +} diff --git a/src/raw_parse_iter.rs b/src/raw_parse_iter.rs new file mode 100644 index 0000000..af268a8 --- /dev/null +++ b/src/raw_parse_iter.rs @@ -0,0 +1,3250 @@ +//! Direct parsing that bypasses protobuf serialization/deserialization. +//! +//! This module provides a faster alternative to the protobuf-based parsing by +//! directly reading PostgreSQL's internal parse tree structures and converting +//! them to Rust protobuf types. + +use crate::bindings; +use crate::bindings_raw; +use crate::parse_result::ParseResult; +use crate::protobuf; +use crate::{Error, Result}; +use std::ffi::{CStr, CString}; +use std::os::raw::c_char; + +/// Parses a SQL statement directly into protobuf types without going through protobuf serialization. +/// +/// This function is faster than `parse` because it skips the protobuf encode/decode step. +/// The parse tree is read directly from PostgreSQL's internal C structures. +/// +/// # Example +/// +/// ```rust +/// let result = pg_query::parse_raw("SELECT * FROM users").unwrap(); +/// assert_eq!(result.tables(), vec!["users"]); +/// ``` +pub fn parse_raw_iter(statement: &str) -> Result { + let input = CString::new(statement)?; + let result = unsafe { bindings_raw::pg_query_parse_raw(input.as_ptr()) }; + + let parse_result = if !result.error.is_null() { + let message = unsafe { CStr::from_ptr((*result.error).message) }.to_string_lossy().to_string(); + Err(Error::Parse(message)) + } else { + // Convert the C parse tree to protobuf types + let tree = result.tree; + let stmts = unsafe { convert_list_to_raw_stmts(tree) }; + let protobuf = protobuf::ParseResult { version: bindings::PG_VERSION_NUM as i32, stmts }; + Ok(ParseResult::new(protobuf, String::new())) + }; + + unsafe { bindings_raw::pg_query_free_raw_parse_result(result) }; + parse_result +} + +/// Converts a PostgreSQL List of RawStmt nodes to protobuf RawStmt vector. +unsafe fn convert_list_to_raw_stmts(list: *mut bindings_raw::List) -> Vec { + if list.is_null() { + return Vec::new(); + } + + let list_ref = &*list; + let length = list_ref.length as usize; + let mut stmts = Vec::with_capacity(length); + + for i in 0..length { + let cell = list_ref.elements.add(i); + let node_ptr = (*cell).ptr_value as *mut bindings_raw::Node; + + if !node_ptr.is_null() { + let node_tag = (*node_ptr).type_; + if node_tag == bindings_raw::NodeTag_T_RawStmt { + let raw_stmt = node_ptr as *mut bindings_raw::RawStmt; + stmts.push(convert_raw_stmt(&*raw_stmt)); + } + } + } + + stmts +} + +/// Converts a C RawStmt to a protobuf RawStmt. +unsafe fn convert_raw_stmt(raw_stmt: &bindings_raw::RawStmt) -> protobuf::RawStmt { + let mut processor = Processor::new(raw_stmt.stmt); + + processor.process(); + + protobuf::RawStmt { stmt: processor.get_result(), stmt_location: raw_stmt.stmt_location, stmt_len: raw_stmt.stmt_len } +} + +struct ProcessingNode { + collect: bool, + node: *const bindings_raw::Node, +} + +impl ProcessingNode { + #[inline] + fn with_node(node: *const bindings_raw::Node) -> Self { + Self { collect: false, node } + } + + #[inline] + fn with_collect(node: *const bindings_raw::Node) -> Self { + Self { collect: true, node } + } +} + +struct Processor { + stack: Vec, + result_stack: Vec, +} + +impl Processor { + fn new(root: *const bindings_raw::Node) -> Self { + Self { stack: vec![ProcessingNode { collect: false, node: root }], result_stack: Default::default() } + } + + fn get_result(mut self) -> Option> { + let result = self.result_stack.pop(); + + assert!(self.result_stack.is_empty(), "Result stack should be empty after processing, but has {} items left", self.result_stack.len()); + + result.map(Box::new) + } + + unsafe fn process(&mut self) { + while let Some(entry) = self.stack.pop() { + let collect = entry.collect; + let node_ptr = entry.node; + + if node_ptr.is_null() { + continue; + } + + let node_tag = (*node_ptr).type_; + + match node_tag { + // === SelectStmt (boxed) === + bindings_raw::NodeTag_T_SelectStmt => { + let stmt = node_ptr as *const bindings_raw::SelectStmt; + + if collect { + let node = self.collect_select_stmt(&*stmt); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_select_stmt(&*stmt); + } + } + + // === ResTarget (boxed) === + bindings_raw::NodeTag_T_ResTarget => { + let rt = node_ptr as *const bindings_raw::ResTarget; + + if collect { + let node = self.collect_res_target(&*rt); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_res_target(&*rt); + } + } + + // === ColumnRef (has fields list) === + bindings_raw::NodeTag_T_ColumnRef => { + let cr = node_ptr as *const bindings_raw::ColumnRef; + + if collect { + let node = self.collect_column_ref(&*cr); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_column_ref(&*cr); + } + } + + // === RangeVar (iterative — alias.colnames queued) === + bindings_raw::NodeTag_T_RangeVar => { + let rv = node_ptr as *const bindings_raw::RangeVar; + if collect { + let node = self.collect_range_var(&*rv); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_range_var(&*rv); + } + } + + // === JoinExpr (iterative) === + bindings_raw::NodeTag_T_JoinExpr => { + let je = node_ptr as *const bindings_raw::JoinExpr; + if collect { + let node = self.collect_join_expr(&*je); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_join_expr(&*je); + } + } + + // === A_Expr (iterative) === + bindings_raw::NodeTag_T_A_Expr => { + let expr = node_ptr as *const bindings_raw::A_Expr; + if collect { + let node = self.collect_a_expr(&*expr); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_a_expr(&*expr); + } + } + + // === FuncCall (iterative) === + bindings_raw::NodeTag_T_FuncCall => { + let fc = node_ptr as *const bindings_raw::FuncCall; + if collect { + let node = self.collect_func_call(&*fc); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_func_call(&*fc); + } + } + + // === WindowDef (iterative) === + bindings_raw::NodeTag_T_WindowDef => { + let wd = node_ptr as *const bindings_raw::WindowDef; + if collect { + let node = self.collect_window_def(&*wd); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_window_def(&*wd); + } + } + + // === InsertStmt (iterative) === + bindings_raw::NodeTag_T_InsertStmt => { + let stmt = node_ptr as *const bindings_raw::InsertStmt; + if collect { + let node = self.collect_insert_stmt(&*stmt); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_insert_stmt(&*stmt); + } + } + + // === UpdateStmt (iterative) === + bindings_raw::NodeTag_T_UpdateStmt => { + let stmt = node_ptr as *const bindings_raw::UpdateStmt; + if collect { + let node = self.collect_update_stmt(&*stmt); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_update_stmt(&*stmt); + } + } + + // === DeleteStmt (iterative) === + bindings_raw::NodeTag_T_DeleteStmt => { + let stmt = node_ptr as *const bindings_raw::DeleteStmt; + if collect { + let node = self.collect_delete_stmt(&*stmt); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_delete_stmt(&*stmt); + } + } + + // === List (iterative — items queued) === + bindings_raw::NodeTag_T_List => { + let list = node_ptr as *mut bindings_raw::List; + if collect { + let items = self.fetch_list_results(list); + self.push_result(protobuf::node::Node::List(protobuf::List { items })); + } else { + self.queue_collect(node_ptr); + self.queue_list_nodes(list); + } + } + + // === SortBy (iterative) === + bindings_raw::NodeTag_T_SortBy => { + let sb = node_ptr as *const bindings_raw::SortBy; + if collect { + let node = self.collect_sort_by(&*sb); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_sort_by(&*sb); + } + } + + // === TypeCast (iterative) === + bindings_raw::NodeTag_T_TypeCast => { + let tc = node_ptr as *const bindings_raw::TypeCast; + if collect { + let node = self.collect_type_cast(&*tc); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_type_cast(&*tc); + } + } + + // === TypeName (iterative) === + bindings_raw::NodeTag_T_TypeName => { + let tn = node_ptr as *const bindings_raw::TypeName; + if collect { + let node = self.collect_type_name(&*tn); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_type_name(&*tn); + } + } + + // === A_Const (leaf — no children) === + bindings_raw::NodeTag_T_A_Const => { + let aconst = node_ptr as *const bindings_raw::A_Const; + let converted = convert_a_const(&*aconst); + self.push_result(protobuf::node::Node::AConst(converted)); + } + + // === A_Star (leaf) === + bindings_raw::NodeTag_T_A_Star => { + self.push_result(protobuf::node::Node::AStar(protobuf::AStar {})); + } + + // === String (leaf) === + bindings_raw::NodeTag_T_String => { + let s = node_ptr as *const bindings_raw::String; + self.push_result(protobuf::node::Node::String(convert_string(&*s))); + } + + // === Integer (leaf) === + bindings_raw::NodeTag_T_Integer => { + let i = node_ptr as *const bindings_raw::Integer; + self.push_result(protobuf::node::Node::Integer(protobuf::Integer { ival: (*i).ival })); + } + + // === Float (leaf) === + bindings_raw::NodeTag_T_Float => { + let f = node_ptr as *const bindings_raw::Float; + let fval = if (*f).fval.is_null() { std::string::String::new() } else { CStr::from_ptr((*f).fval).to_string_lossy().to_string() }; + self.push_result(protobuf::node::Node::Float(protobuf::Float { fval })); + } + + // === Boolean (leaf) === + bindings_raw::NodeTag_T_Boolean => { + let b = node_ptr as *const bindings_raw::Boolean; + self.push_result(protobuf::node::Node::Boolean(protobuf::Boolean { boolval: (*b).boolval })); + } + + // === BitString (leaf) === + bindings_raw::NodeTag_T_BitString => { + let bs = node_ptr as *const bindings_raw::BitString; + self.push_result(protobuf::node::Node::BitString(convert_bit_string(&*bs))); + } + + // === ParamRef (leaf) === + bindings_raw::NodeTag_T_ParamRef => { + let pr = node_ptr as *const bindings_raw::ParamRef; + self.push_result(protobuf::node::Node::ParamRef(protobuf::ParamRef { number: (*pr).number, location: (*pr).location })); + } + + // === BoolExpr (iterative) === + bindings_raw::NodeTag_T_BoolExpr => { + let be = node_ptr as *const bindings_raw::BoolExpr; + if collect { + let node = self.collect_bool_expr(&*be); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_bool_expr(&*be); + } + } + + // === NullTest (iterative) === + bindings_raw::NodeTag_T_NullTest => { + let nt = node_ptr as *const bindings_raw::NullTest; + if collect { + let node = self.collect_null_test(&*nt); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_null_test(&*nt); + } + } + + // === SubLink (iterative) === + bindings_raw::NodeTag_T_SubLink => { + let sl = node_ptr as *const bindings_raw::SubLink; + if collect { + let node = self.collect_sub_link(&*sl); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_sub_link(&*sl); + } + } + + // === CaseExpr (iterative) === + bindings_raw::NodeTag_T_CaseExpr => { + let ce = node_ptr as *const bindings_raw::CaseExpr; + if collect { + let node = self.collect_case_expr(&*ce); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_case_expr(&*ce); + } + } + + // === CaseWhen (iterative) === + bindings_raw::NodeTag_T_CaseWhen => { + let cw = node_ptr as *const bindings_raw::CaseWhen; + if collect { + let node = self.collect_case_when(&*cw); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_case_when(&*cw); + } + } + + // === CoalesceExpr (iterative) === + bindings_raw::NodeTag_T_CoalesceExpr => { + let ce = node_ptr as *const bindings_raw::CoalesceExpr; + if collect { + let node = self.collect_coalesce_expr(&*ce); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_coalesce_expr(&*ce); + } + } + + // === MinMaxExpr (iterative) === + bindings_raw::NodeTag_T_MinMaxExpr => { + let mme = node_ptr as *const bindings_raw::MinMaxExpr; + if collect { + let node = self.collect_min_max_expr(&*mme); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_min_max_expr(&*mme); + } + } + + // === SQLValueFunction (leaf) === + bindings_raw::NodeTag_T_SQLValueFunction => { + let svf = node_ptr as *const bindings_raw::SQLValueFunction; + self.push_result(protobuf::node::Node::SqlvalueFunction(Box::new(protobuf::SqlValueFunction { + xpr: None, + op: (*svf).op as i32 + 1, + r#type: (*svf).type_, + typmod: (*svf).typmod, + location: (*svf).location, + }))); + } + + // === SetToDefault (leaf) === + bindings_raw::NodeTag_T_SetToDefault => { + let std_ = node_ptr as *const bindings_raw::SetToDefault; + self.push_result(protobuf::node::Node::SetToDefault(Box::new(protobuf::SetToDefault { + xpr: None, + type_id: (*std_).typeId, + type_mod: (*std_).typeMod, + collation: (*std_).collation, + location: (*std_).location, + }))); + } + + // === BooleanTest (iterative) === + bindings_raw::NodeTag_T_BooleanTest => { + let bt = node_ptr as *const bindings_raw::BooleanTest; + if collect { + let node = self.collect_boolean_test(&*bt); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_boolean_test(&*bt); + } + } + + // === A_ArrayExpr (iterative) === + bindings_raw::NodeTag_T_A_ArrayExpr => { + let ae = node_ptr as *const bindings_raw::A_ArrayExpr; + if collect { + let node = self.collect_a_array_expr(&*ae); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_a_array_expr(&*ae); + } + } + + // === A_Indirection (iterative) === + bindings_raw::NodeTag_T_A_Indirection => { + let ai = node_ptr as *const bindings_raw::A_Indirection; + if collect { + let node = self.collect_a_indirection(&*ai); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_a_indirection(&*ai); + } + } + + // === A_Indices (iterative) === + bindings_raw::NodeTag_T_A_Indices => { + let ai = node_ptr as *const bindings_raw::A_Indices; + if collect { + let node = self.collect_a_indices(&*ai); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_a_indices(&*ai); + } + } + + // === CollateClause (iterative) === + bindings_raw::NodeTag_T_CollateClause => { + let cc = node_ptr as *const bindings_raw::CollateClause; + if collect { + let node = self.collect_collate_clause(&*cc); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_collate_clause(&*cc); + } + } + + // === RangeSubselect (iterative) === + bindings_raw::NodeTag_T_RangeSubselect => { + let rs = node_ptr as *const bindings_raw::RangeSubselect; + if collect { + let node = self.collect_range_subselect(&*rs); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_range_subselect(&*rs); + } + } + + // === CommonTableExpr (iterative) === + bindings_raw::NodeTag_T_CommonTableExpr => { + let cte = node_ptr as *const bindings_raw::CommonTableExpr; + if collect { + let node = self.collect_common_table_expr(&*cte); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_common_table_expr(&*cte); + } + } + + // === GroupingSet (iterative) === + bindings_raw::NodeTag_T_GroupingSet => { + let gs = node_ptr as *const bindings_raw::GroupingSet; + if collect { + let node = self.collect_grouping_set(&*gs); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_grouping_set(&*gs); + } + } + + // === LockingClause (iterative) === + bindings_raw::NodeTag_T_LockingClause => { + let lc = node_ptr as *const bindings_raw::LockingClause; + if collect { + let node = self.collect_locking_clause(&*lc); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_locking_clause(&*lc); + } + } + + // === RowExpr (iterative) === + bindings_raw::NodeTag_T_RowExpr => { + let re = node_ptr as *const bindings_raw::RowExpr; + if collect { + let node = self.collect_row_expr(&*re); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_row_expr(&*re); + } + } + + // === MultiAssignRef (iterative) === + bindings_raw::NodeTag_T_MultiAssignRef => { + let mar = node_ptr as *const bindings_raw::MultiAssignRef; + if collect { + let node = self.collect_multi_assign_ref(&*mar); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_multi_assign_ref(&*mar); + } + } + + // === CTESearchClause (iterative) === + bindings_raw::NodeTag_T_CTESearchClause => { + let csc = node_ptr as *const bindings_raw::CTESearchClause; + if collect { + let node = self.collect_cte_search_clause(&*csc); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_cte_search_clause(&*csc); + } + } + + // === CTECycleClause (iterative) === + bindings_raw::NodeTag_T_CTECycleClause => { + let ccc = node_ptr as *const bindings_raw::CTECycleClause; + if collect { + let node = self.collect_cte_cycle_clause(&*ccc); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_cte_cycle_clause(&*ccc); + } + } + + // === Alias (iterative) === + bindings_raw::NodeTag_T_Alias => { + let alias = node_ptr as *const bindings_raw::Alias; + if collect { + let node = self.collect_alias(&*alias); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_alias(&*alias); + } + } + + // === GroupingFunc (iterative) === + bindings_raw::NodeTag_T_GroupingFunc => { + let gf = node_ptr as *const bindings_raw::GroupingFunc; + if collect { + let node = self.collect_grouping_func(&*gf); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_grouping_func(&*gf); + } + } + + // === IndexElem (iterative) === + bindings_raw::NodeTag_T_IndexElem => { + let ie = node_ptr as *const bindings_raw::IndexElem; + if collect { + let node = self.collect_index_elem(&*ie); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_index_elem(&*ie); + } + } + + // === DefElem (iterative) === + bindings_raw::NodeTag_T_DefElem => { + let de = node_ptr as *const bindings_raw::DefElem; + if collect { + let node = self.collect_def_elem(&*de); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_def_elem(&*de); + } + } + + // === ColumnDef (iterative) === + bindings_raw::NodeTag_T_ColumnDef => { + let cd = node_ptr as *const bindings_raw::ColumnDef; + if collect { + let node = self.collect_column_def(&*cd); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_column_def(&*cd); + } + } + + // === Constraint (iterative) === + bindings_raw::NodeTag_T_Constraint => { + let c = node_ptr as *const bindings_raw::Constraint; + if collect { + let node = self.collect_constraint(&*c); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_constraint(&*c); + } + } + + // === RoleSpec (leaf-like) === + bindings_raw::NodeTag_T_RoleSpec => { + let rs = node_ptr as *const bindings_raw::RoleSpec; + self.push_result(protobuf::node::Node::RoleSpec(protobuf::RoleSpec { + roletype: (*rs).roletype as i32 + 1, + rolename: convert_c_string((*rs).rolename), + location: (*rs).location, + })); + } + + // === CreateStmt (iterative) === + bindings_raw::NodeTag_T_CreateStmt => { + let cs = node_ptr as *const bindings_raw::CreateStmt; + if collect { + let node = self.collect_create_stmt(&*cs); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_create_stmt(&*cs); + } + } + + // === DropStmt (iterative) === + bindings_raw::NodeTag_T_DropStmt => { + let ds = node_ptr as *const bindings_raw::DropStmt; + if collect { + let node = self.collect_drop_stmt(&*ds); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_drop_stmt(&*ds); + } + } + + // === IndexStmt (iterative) === + bindings_raw::NodeTag_T_IndexStmt => { + let is_ = node_ptr as *const bindings_raw::IndexStmt; + if collect { + let node = self.collect_index_stmt(&*is_); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_index_stmt(&*is_); + } + } + + // === AlterTableStmt (iterative) === + bindings_raw::NodeTag_T_AlterTableStmt => { + let ats = node_ptr as *const bindings_raw::AlterTableStmt; + if collect { + let node = self.collect_alter_table_stmt(&*ats); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_alter_table_stmt(&*ats); + } + } + + // === AlterTableCmd (iterative) === + bindings_raw::NodeTag_T_AlterTableCmd => { + let atc = node_ptr as *const bindings_raw::AlterTableCmd; + if collect { + let node = self.collect_alter_table_cmd(&*atc); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_alter_table_cmd(&*atc); + } + } + + // === RenameStmt (iterative) === + bindings_raw::NodeTag_T_RenameStmt => { + let rs = node_ptr as *const bindings_raw::RenameStmt; + if collect { + let node = self.collect_rename_stmt(&*rs); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_rename_stmt(&*rs); + } + } + + // === ViewStmt (iterative) === + bindings_raw::NodeTag_T_ViewStmt => { + let vs = node_ptr as *const bindings_raw::ViewStmt; + if collect { + let node = self.collect_view_stmt(&*vs); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_view_stmt(&*vs); + } + } + + // === CreateTableAsStmt (iterative) === + bindings_raw::NodeTag_T_CreateTableAsStmt => { + let ctas = node_ptr as *const bindings_raw::CreateTableAsStmt; + if collect { + let node = self.collect_create_table_as_stmt(&*ctas); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_create_table_as_stmt(&*ctas); + } + } + + // === TruncateStmt (iterative) === + bindings_raw::NodeTag_T_TruncateStmt => { + let ts = node_ptr as *const bindings_raw::TruncateStmt; + if collect { + let node = self.collect_truncate_stmt(&*ts); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_truncate_stmt(&*ts); + } + } + + // === AlterOwnerStmt (iterative) === + bindings_raw::NodeTag_T_AlterOwnerStmt => { + let aos = node_ptr as *const bindings_raw::AlterOwnerStmt; + if collect { + let node = self.collect_alter_owner_stmt(&*aos); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_alter_owner_stmt(&*aos); + } + } + + // === CreateSeqStmt (iterative) === + bindings_raw::NodeTag_T_CreateSeqStmt => { + let css = node_ptr as *const bindings_raw::CreateSeqStmt; + if collect { + let node = self.collect_create_seq_stmt(&*css); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_create_seq_stmt(&*css); + } + } + + // === AlterSeqStmt (iterative) === + bindings_raw::NodeTag_T_AlterSeqStmt => { + let ass_ = node_ptr as *const bindings_raw::AlterSeqStmt; + if collect { + let node = self.collect_alter_seq_stmt(&*ass_); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_alter_seq_stmt(&*ass_); + } + } + + // === CreateDomainStmt (iterative) === + bindings_raw::NodeTag_T_CreateDomainStmt => { + let cds = node_ptr as *const bindings_raw::CreateDomainStmt; + if collect { + let node = self.collect_create_domain_stmt(&*cds); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_create_domain_stmt(&*cds); + } + } + + // === CompositeTypeStmt (iterative) === + bindings_raw::NodeTag_T_CompositeTypeStmt => { + let cts = node_ptr as *const bindings_raw::CompositeTypeStmt; + if collect { + let node = self.collect_composite_type_stmt(&*cts); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_composite_type_stmt(&*cts); + } + } + + // === CreateEnumStmt (iterative) === + bindings_raw::NodeTag_T_CreateEnumStmt => { + let ces = node_ptr as *const bindings_raw::CreateEnumStmt; + if collect { + let node = self.collect_create_enum_stmt(&*ces); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_create_enum_stmt(&*ces); + } + } + + // === CreateExtensionStmt (iterative) === + bindings_raw::NodeTag_T_CreateExtensionStmt => { + let ces = node_ptr as *const bindings_raw::CreateExtensionStmt; + if collect { + let node = self.collect_create_extension_stmt(&*ces); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_create_extension_stmt(&*ces); + } + } + + // === CreatePublicationStmt (iterative) === + bindings_raw::NodeTag_T_CreatePublicationStmt => { + let cps = node_ptr as *const bindings_raw::CreatePublicationStmt; + if collect { + let node = self.collect_create_publication_stmt(&*cps); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_create_publication_stmt(&*cps); + } + } + + // === AlterPublicationStmt (iterative) === + bindings_raw::NodeTag_T_AlterPublicationStmt => { + let aps = node_ptr as *const bindings_raw::AlterPublicationStmt; + if collect { + let node = self.collect_alter_publication_stmt(&*aps); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_alter_publication_stmt(&*aps); + } + } + + // === CreateSubscriptionStmt (iterative) === + bindings_raw::NodeTag_T_CreateSubscriptionStmt => { + let css = node_ptr as *const bindings_raw::CreateSubscriptionStmt; + if collect { + let node = self.collect_create_subscription_stmt(&*css); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_create_subscription_stmt(&*css); + } + } + + // === AlterSubscriptionStmt (iterative) === + bindings_raw::NodeTag_T_AlterSubscriptionStmt => { + let ass_ = node_ptr as *const bindings_raw::AlterSubscriptionStmt; + if collect { + let node = self.collect_alter_subscription_stmt(&*ass_); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_alter_subscription_stmt(&*ass_); + } + } + + // === CreateTrigStmt (iterative) === + bindings_raw::NodeTag_T_CreateTrigStmt => { + let cts = node_ptr as *const bindings_raw::CreateTrigStmt; + if collect { + let node = self.collect_create_trig_stmt(&*cts); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_create_trig_stmt(&*cts); + } + } + + // === PublicationObjSpec (iterative) === + bindings_raw::NodeTag_T_PublicationObjSpec => { + let pos = node_ptr as *const bindings_raw::PublicationObjSpec; + if collect { + let node = self.collect_publication_obj_spec(&*pos); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_publication_obj_spec(&*pos); + } + } + + // === TriggerTransition (leaf) === + bindings_raw::NodeTag_T_TriggerTransition => { + let tt = node_ptr as *const bindings_raw::TriggerTransition; + self.push_result(protobuf::node::Node::TriggerTransition(protobuf::TriggerTransition { + name: convert_c_string((*tt).name), + is_new: (*tt).isNew, + is_table: (*tt).isTable, + })); + } + + // === PartitionElem (iterative) === + bindings_raw::NodeTag_T_PartitionElem => { + let pe = node_ptr as *const bindings_raw::PartitionElem; + if collect { + let node = self.collect_partition_elem(&*pe); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_partition_elem(&*pe); + } + } + + // === PartitionSpec (iterative) === + bindings_raw::NodeTag_T_PartitionSpec => { + let ps = node_ptr as *const bindings_raw::PartitionSpec; + if collect { + let node = self.collect_partition_spec(&*ps); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_partition_spec(&*ps); + } + } + + // === PartitionBoundSpec (iterative) === + bindings_raw::NodeTag_T_PartitionBoundSpec => { + let pbs = node_ptr as *const bindings_raw::PartitionBoundSpec; + if collect { + let node = self.collect_partition_bound_spec(&*pbs); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_partition_bound_spec(&*pbs); + } + } + + // === PartitionRangeDatum (iterative) === + bindings_raw::NodeTag_T_PartitionRangeDatum => { + let prd = node_ptr as *const bindings_raw::PartitionRangeDatum; + if collect { + let node = self.collect_partition_range_datum(&*prd); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_partition_range_datum(&*prd); + } + } + + // === ExplainStmt (iterative) === + bindings_raw::NodeTag_T_ExplainStmt => { + let es = node_ptr as *const bindings_raw::ExplainStmt; + if collect { + let node = self.collect_explain_stmt(&*es); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_explain_stmt(&*es); + } + } + + // === CopyStmt (iterative) === + bindings_raw::NodeTag_T_CopyStmt => { + let cs = node_ptr as *const bindings_raw::CopyStmt; + if collect { + let node = self.collect_copy_stmt(&*cs); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_copy_stmt(&*cs); + } + } + + // === PrepareStmt (iterative) === + bindings_raw::NodeTag_T_PrepareStmt => { + let ps = node_ptr as *const bindings_raw::PrepareStmt; + if collect { + let node = self.collect_prepare_stmt(&*ps); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_prepare_stmt(&*ps); + } + } + + // === ExecuteStmt (iterative) === + bindings_raw::NodeTag_T_ExecuteStmt => { + let es = node_ptr as *const bindings_raw::ExecuteStmt; + if collect { + let node = self.collect_execute_stmt(&*es); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_execute_stmt(&*es); + } + } + + // === DeallocateStmt (leaf) === + bindings_raw::NodeTag_T_DeallocateStmt => { + let ds = node_ptr as *const bindings_raw::DeallocateStmt; + self.push_result(protobuf::node::Node::DeallocateStmt(protobuf::DeallocateStmt { + name: convert_c_string((*ds).name), + isall: (*ds).isall, + location: (*ds).location, + })); + } + + // === TransactionStmt (iterative) === + bindings_raw::NodeTag_T_TransactionStmt => { + let ts = node_ptr as *const bindings_raw::TransactionStmt; + if collect { + let node = self.collect_transaction_stmt(&*ts); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_transaction_stmt(&*ts); + } + } + + // === VacuumStmt (iterative) === + bindings_raw::NodeTag_T_VacuumStmt => { + let vs = node_ptr as *const bindings_raw::VacuumStmt; + if collect { + let node = self.collect_vacuum_stmt(&*vs); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_vacuum_stmt(&*vs); + } + } + + // === VacuumRelation (iterative) === + bindings_raw::NodeTag_T_VacuumRelation => { + let vr = node_ptr as *const bindings_raw::VacuumRelation; + if collect { + let node = self.collect_vacuum_relation(&*vr); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_vacuum_relation(&*vr); + } + } + + // === VariableSetStmt (iterative) === + bindings_raw::NodeTag_T_VariableSetStmt => { + let vss = node_ptr as *const bindings_raw::VariableSetStmt; + if collect { + let node = self.collect_variable_set_stmt(&*vss); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_variable_set_stmt(&*vss); + } + } + + // === VariableShowStmt (leaf) === + bindings_raw::NodeTag_T_VariableShowStmt => { + let vss = node_ptr as *const bindings_raw::VariableShowStmt; + self.push_result(protobuf::node::Node::VariableShowStmt(protobuf::VariableShowStmt { name: convert_c_string((*vss).name) })); + } + + // === NotifyStmt (leaf) === + bindings_raw::NodeTag_T_NotifyStmt => { + let ns = node_ptr as *const bindings_raw::NotifyStmt; + self.push_result(protobuf::node::Node::NotifyStmt(protobuf::NotifyStmt { + conditionname: convert_c_string((*ns).conditionname), + payload: convert_c_string((*ns).payload), + })); + } + + // === ListenStmt (leaf) === + bindings_raw::NodeTag_T_ListenStmt => { + let ls = node_ptr as *const bindings_raw::ListenStmt; + self.push_result(protobuf::node::Node::ListenStmt(protobuf::ListenStmt { conditionname: convert_c_string((*ls).conditionname) })); + } + + // === UnlistenStmt (leaf) === + bindings_raw::NodeTag_T_UnlistenStmt => { + let us = node_ptr as *const bindings_raw::UnlistenStmt; + self.push_result(protobuf::node::Node::UnlistenStmt(protobuf::UnlistenStmt { + conditionname: convert_c_string((*us).conditionname), + })); + } + + // === DiscardStmt (leaf) === + bindings_raw::NodeTag_T_DiscardStmt => { + let ds = node_ptr as *const bindings_raw::DiscardStmt; + self.push_result(protobuf::node::Node::DiscardStmt(protobuf::DiscardStmt { target: (*ds).target as i32 + 1 })); + } + + // === LockStmt (iterative) === + bindings_raw::NodeTag_T_LockStmt => { + let ls = node_ptr as *const bindings_raw::LockStmt; + if collect { + let node = self.collect_lock_stmt(&*ls); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_lock_stmt(&*ls); + } + } + + // === DoStmt (iterative) === + bindings_raw::NodeTag_T_DoStmt => { + let ds = node_ptr as *const bindings_raw::DoStmt; + if collect { + let node = self.collect_do_stmt(&*ds); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_do_stmt(&*ds); + } + } + + // === ObjectWithArgs (iterative) === + bindings_raw::NodeTag_T_ObjectWithArgs => { + let owa = node_ptr as *const bindings_raw::ObjectWithArgs; + if collect { + let node = self.collect_object_with_args(&*owa); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_object_with_args(&*owa); + } + } + + // === CoerceToDomain (iterative) === + bindings_raw::NodeTag_T_CoerceToDomain => { + let ctd = node_ptr as *const bindings_raw::CoerceToDomain; + if collect { + let node = self.collect_coerce_to_domain(&*ctd); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_coerce_to_domain(&*ctd); + } + } + + // === FunctionParameter (iterative) === + bindings_raw::NodeTag_T_FunctionParameter => { + let fp = node_ptr as *const bindings_raw::FunctionParameter; + if collect { + let node = self.collect_function_parameter(&*fp); + self.push_result(node); + } else { + self.queue_collect(node_ptr); + self.queue_function_parameter(&*fp); + } + } + + _ => { + panic!("[ERROR] Unhandled node tag={} in iterative processor. This type needs to be migrated.", node_tag,); + } + } + } + } + + fn queue_node(&mut self, node: *const bindings_raw::Node) { + self.stack.push(ProcessingNode::with_node(node)); + } + + fn queue_collect(&mut self, node: *const bindings_raw::Node) { + self.stack.push(ProcessingNode::with_collect(node)) + } + + fn push_result(&mut self, node: protobuf::node::Node) { + self.result_stack.push(protobuf::Node { node: Some(node) }); + } + + fn single_result(&mut self, node: *const bindings_raw::Node) -> Option { + if node.is_null() { + return None; + } + + let result = self.result_stack.pop().expect("result stack should not be empty while processing"); + Some(result) + } + + fn single_result_box(&mut self, node: *const bindings_raw::Node) -> Option> { + self.single_result(node).map(Box::new) + } + + fn fetch_results(&mut self, count: usize) -> Vec { + if count > self.result_stack.len() { + panic!( + "fetch_results: count ({}) > result_stack.len ({}) — stack contents: {:?}", + count, + self.result_stack.len(), + self.result_stack.iter().map(|n| format!("{:?}", n.node.as_ref().map(|n| std::mem::discriminant(n)))).collect::>(), + ); + } + let start = self.result_stack.len() - count; + self.result_stack.drain(start..).rev().collect() + } + + unsafe fn queue_list_nodes(&mut self, list: *mut bindings_raw::List) { + if list.is_null() { + return; + } + let list = &*list; + let length = list.length as usize; + + self.stack.reserve(length); + + for i in 0..length { + let cell = list.elements.add(i); + let node = (*cell).ptr_value as *const bindings_raw::Node; + if !node.is_null() { + } else { + } + self.stack.push(ProcessingNode::with_node(node)); + } + } + + unsafe fn fetch_list_results(&mut self, list: *const bindings_raw::List) -> Vec { + if list.is_null() { + return Vec::new(); + } + let list = &*list; + let length = list.length as usize; + + self.fetch_results(length) + } + + unsafe fn queue_into_clause(&mut self, ic: *const bindings_raw::IntoClause) { + if ic.is_null() { + return; + } + let ic_ref = &*ic; + if !ic_ref.rel.is_null() { + self.queue_node(ic_ref.rel as *const bindings_raw::Node); + } + self.queue_list_nodes(ic_ref.colNames); + self.queue_list_nodes(ic_ref.options); + self.queue_node(ic_ref.viewQuery); + } + + unsafe fn fetch_into_clause(&mut self, ic: *const bindings_raw::IntoClause) -> Option> { + if ic.is_null() { + return None; + } + let ic_ref = &*ic; + let rel = if ic_ref.rel.is_null() { None } else { self.pop_range_var() }; + let col_names = self.fetch_list_results(ic_ref.colNames); + let options = self.fetch_list_results(ic_ref.options); + let view_query = self.single_result_box(ic_ref.viewQuery); + Some(Box::new(protobuf::IntoClause { + rel, + col_names, + access_method: convert_c_string(ic_ref.accessMethod), + options, + on_commit: ic_ref.onCommit as i32 + 1, + table_space_name: convert_c_string(ic_ref.tableSpaceName), + view_query, + skip_data: ic_ref.skipData, + })) + } + + unsafe fn queue_with_clause(&mut self, wc: *mut bindings_raw::WithClause) { + if wc.is_null() { + return; + } + + let wc = &*wc; + + self.queue_list_nodes(wc.ctes); + } + + unsafe fn fetch_with_clause(&mut self, wc: *mut bindings_raw::WithClause) -> Option { + if wc.is_null() { + return None; + } + + let wc = &*wc; + + Some(protobuf::WithClause { ctes: self.fetch_list_results(wc.ctes), recursive: wc.recursive, location: wc.location }) + } + + unsafe fn queue_select_stmt(&mut self, stmt: &bindings_raw::SelectStmt) { + self.queue_list_nodes(stmt.distinctClause); + self.queue_into_clause(stmt.intoClause); + self.queue_list_nodes(stmt.targetList); + self.queue_list_nodes(stmt.fromClause); + self.queue_node(stmt.whereClause); + self.queue_list_nodes(stmt.groupClause); + self.queue_node(stmt.havingClause); + self.queue_list_nodes(stmt.windowClause); + self.queue_list_nodes(stmt.valuesLists); + self.queue_list_nodes(stmt.sortClause); + self.queue_node(stmt.limitOffset); + self.queue_node(stmt.limitCount); + self.queue_list_nodes(stmt.lockingClause); + self.queue_with_clause(stmt.withClause); + + if !stmt.larg.is_null() { + self.queue_node(stmt.larg as *const bindings_raw::Node); + } + + if !stmt.rarg.is_null() { + self.queue_node(stmt.rarg as *const bindings_raw::Node); + } + } + + fn pop_select_stmt(&mut self) -> Option> { + self.result_stack.pop().and_then(|n| match n.node { + Some(protobuf::node::Node::SelectStmt(s)) => Some(s), + _ => None, + }) + } + + unsafe fn collect_select_stmt(&mut self, stmt: &bindings_raw::SelectStmt) -> protobuf::node::Node { + let a = protobuf::SelectStmt { + distinct_clause: self.fetch_list_results(stmt.distinctClause), + into_clause: self.fetch_into_clause(stmt.intoClause), + target_list: self.fetch_list_results(stmt.targetList), + from_clause: self.fetch_list_results(stmt.fromClause), + where_clause: self.single_result_box(stmt.whereClause), + group_clause: self.fetch_list_results(stmt.groupClause), + group_distinct: stmt.groupDistinct, + having_clause: self.single_result_box(stmt.havingClause), + window_clause: self.fetch_list_results(stmt.windowClause), + values_lists: self.fetch_list_results(stmt.valuesLists), + sort_clause: self.fetch_list_results(stmt.sortClause), + limit_offset: self.single_result_box(stmt.limitOffset), + limit_count: self.single_result_box(stmt.limitCount), + limit_option: stmt.limitOption as i32 + 1, // Protobuf enums have UNDEFINED=0, so C values need +1 + locking_clause: self.fetch_list_results(stmt.lockingClause), + with_clause: self.fetch_with_clause(stmt.withClause), + op: stmt.op as i32 + 1, // Protobuf SetOperation has UNDEFINED=0, so C values need +1 + all: stmt.all, + larg: if stmt.larg.is_null() { None } else { self.pop_select_stmt() }, + rarg: if stmt.rarg.is_null() { None } else { self.pop_select_stmt() }, + }; + + protobuf::node::Node::SelectStmt(Box::new(a)) + } + + // ==================================================================== + // ResTarget + // ==================================================================== + + unsafe fn queue_res_target(&mut self, rt: &bindings_raw::ResTarget) { + // Queue children that need recursive processing: + // indirection (list of nodes) and val (single node) + self.queue_list_nodes(rt.indirection); + self.queue_node(rt.val); + } + + unsafe fn collect_res_target(&mut self, rt: &bindings_raw::ResTarget) -> protobuf::node::Node { + let indirection = self.fetch_list_results(rt.indirection); + let val = self.single_result_box(rt.val); + + let res = protobuf::ResTarget { name: convert_c_string(rt.name), indirection, val, location: rt.location }; + protobuf::node::Node::ResTarget(Box::new(res)) + } + + // ==================================================================== + // ColumnRef + // ==================================================================== + + unsafe fn queue_column_ref(&mut self, cr: &bindings_raw::ColumnRef) { + self.queue_list_nodes(cr.fields); + } + + unsafe fn collect_column_ref(&mut self, cr: &bindings_raw::ColumnRef) -> protobuf::node::Node { + let fields = self.fetch_list_results(cr.fields); + protobuf::node::Node::ColumnRef(protobuf::ColumnRef { fields, location: cr.location }) + } + + // ==================================================================== + // RangeVar (iterative — queues alias.colnames) + // ==================================================================== + + unsafe fn queue_range_var(&mut self, rv: &bindings_raw::RangeVar) { + if !rv.alias.is_null() { + self.queue_list_nodes((*rv.alias).colnames); + } + } + + unsafe fn collect_range_var(&mut self, rv: &bindings_raw::RangeVar) -> protobuf::node::Node { + let alias = if rv.alias.is_null() { + None + } else { + let alias = &*rv.alias; + Some(protobuf::Alias { aliasname: convert_c_string(alias.aliasname), colnames: self.fetch_list_results(alias.colnames) }) + }; + protobuf::node::Node::RangeVar(protobuf::RangeVar { + catalogname: convert_c_string(rv.catalogname), + schemaname: convert_c_string(rv.schemaname), + relname: convert_c_string(rv.relname), + inh: rv.inh, + relpersistence: std::string::String::from_utf8_lossy(&[rv.relpersistence as u8]).to_string(), + alias, + location: rv.location, + }) + } + + fn pop_range_var(&mut self) -> Option { + self.result_stack.pop().and_then(|n| match n.node { + Some(protobuf::node::Node::RangeVar(rv)) => Some(rv), + other => panic!("[ERROR] Expected RangeVar on result stack, got {:?}", other.as_ref().map(|n| std::mem::discriminant(n))), + }) + } + + // ==================================================================== + // JoinExpr + // ==================================================================== + + unsafe fn queue_join_expr(&mut self, je: &bindings_raw::JoinExpr) { + self.queue_list_nodes(je.usingClause); + self.queue_node(je.larg); + self.queue_node(je.rarg); + self.queue_node(je.quals); + if !je.alias.is_null() { + self.queue_list_nodes((*je.alias).colnames); + } + if !je.join_using_alias.is_null() { + self.queue_list_nodes((*je.join_using_alias).colnames); + } + } + + unsafe fn collect_join_expr(&mut self, je: &bindings_raw::JoinExpr) -> protobuf::node::Node { + let using_clause = self.fetch_list_results(je.usingClause); + let larg = self.single_result_box(je.larg); + let rarg = self.single_result_box(je.rarg); + let quals = self.single_result_box(je.quals); + let alias = if je.alias.is_null() { + None + } else { + Some(protobuf::Alias { aliasname: convert_c_string((*je.alias).aliasname), colnames: self.fetch_list_results((*je.alias).colnames) }) + }; + let join_using_alias = if je.join_using_alias.is_null() { + None + } else { + Some(protobuf::Alias { + aliasname: convert_c_string((*je.join_using_alias).aliasname), + colnames: self.fetch_list_results((*je.join_using_alias).colnames), + }) + }; + protobuf::node::Node::JoinExpr(Box::new(protobuf::JoinExpr { + jointype: je.jointype as i32 + 1, + is_natural: je.isNatural, + larg, + rarg, + using_clause, + join_using_alias, + quals, + alias, + rtindex: je.rtindex, + })) + } + + // ==================================================================== + // A_Expr + // ==================================================================== + + unsafe fn queue_a_expr(&mut self, expr: &bindings_raw::A_Expr) { + self.queue_list_nodes(expr.name); + self.queue_node(expr.lexpr); + self.queue_node(expr.rexpr); + } + + unsafe fn collect_a_expr(&mut self, expr: &bindings_raw::A_Expr) -> protobuf::node::Node { + let name = self.fetch_list_results(expr.name); + let lexpr = self.single_result_box(expr.lexpr); + let rexpr = self.single_result_box(expr.rexpr); + protobuf::node::Node::AExpr(Box::new(protobuf::AExpr { kind: expr.kind as i32 + 1, name, lexpr, rexpr, location: expr.location })) + } + + // ==================================================================== + // FuncCall + // ==================================================================== + + unsafe fn queue_func_call(&mut self, fc: &bindings_raw::FuncCall) { + self.queue_list_nodes(fc.funcname); + self.queue_list_nodes(fc.args); + self.queue_list_nodes(fc.agg_order); + self.queue_node(fc.agg_filter); + if !fc.over.is_null() { + self.queue_node(fc.over as *const bindings_raw::Node); + } + } + + unsafe fn collect_func_call(&mut self, fc: &bindings_raw::FuncCall) -> protobuf::node::Node { + let funcname = self.fetch_list_results(fc.funcname); + let args = self.fetch_list_results(fc.args); + let agg_order = self.fetch_list_results(fc.agg_order); + let agg_filter = self.single_result_box(fc.agg_filter); + let over = if fc.over.is_null() { + None + } else { + self.result_stack.pop().and_then(|n| match n.node { + Some(protobuf::node::Node::WindowDef(wd)) => Some(wd), + _ => None, + }) + }; + protobuf::node::Node::FuncCall(Box::new(protobuf::FuncCall { + funcname, + args, + agg_order, + agg_filter, + over, + agg_within_group: fc.agg_within_group, + agg_star: fc.agg_star, + agg_distinct: fc.agg_distinct, + func_variadic: fc.func_variadic, + funcformat: fc.funcformat as i32 + 1, + location: fc.location, + })) + } + + // ==================================================================== + // WindowDef + // ==================================================================== + + unsafe fn queue_window_def(&mut self, wd: &bindings_raw::WindowDef) { + self.queue_list_nodes(wd.partitionClause); + self.queue_list_nodes(wd.orderClause); + self.queue_node(wd.startOffset); + self.queue_node(wd.endOffset); + } + + unsafe fn collect_window_def(&mut self, wd: &bindings_raw::WindowDef) -> protobuf::node::Node { + let partition_clause = self.fetch_list_results(wd.partitionClause); + let order_clause = self.fetch_list_results(wd.orderClause); + let start_offset = self.single_result_box(wd.startOffset); + let end_offset = self.single_result_box(wd.endOffset); + protobuf::node::Node::WindowDef(Box::new(protobuf::WindowDef { + name: convert_c_string(wd.name), + refname: convert_c_string(wd.refname), + partition_clause, + order_clause, + frame_options: wd.frameOptions, + start_offset, + end_offset, + location: wd.location, + })) + } + + // ==================================================================== + // InsertStmt + // ==================================================================== + + unsafe fn queue_insert_stmt(&mut self, stmt: &bindings_raw::InsertStmt) { + if !stmt.relation.is_null() { + self.queue_node(stmt.relation as *const bindings_raw::Node); + } + self.queue_list_nodes(stmt.cols); + self.queue_node(stmt.selectStmt); + self.queue_on_conflict_clause(stmt.onConflictClause); + self.queue_list_nodes(stmt.returningList); + self.queue_with_clause(stmt.withClause); + } + + unsafe fn collect_insert_stmt(&mut self, stmt: &bindings_raw::InsertStmt) -> protobuf::node::Node { + let relation = if stmt.relation.is_null() { None } else { self.pop_range_var() }; + let cols = self.fetch_list_results(stmt.cols); + let select_stmt = self.single_result_box(stmt.selectStmt); + let on_conflict_clause = self.fetch_on_conflict_clause(stmt.onConflictClause); + let returning_list = self.fetch_list_results(stmt.returningList); + let with_clause = self.fetch_with_clause(stmt.withClause); + protobuf::node::Node::InsertStmt(Box::new(protobuf::InsertStmt { + relation, + cols, + select_stmt, + on_conflict_clause, + returning_list, + with_clause, + r#override: stmt.override_ as i32 + 1, + })) + } + + // ==================================================================== + // UpdateStmt + // ==================================================================== + + unsafe fn queue_update_stmt(&mut self, stmt: &bindings_raw::UpdateStmt) { + if !stmt.relation.is_null() { + self.queue_node(stmt.relation as *const bindings_raw::Node); + } + self.queue_list_nodes(stmt.targetList); + self.queue_node(stmt.whereClause); + self.queue_list_nodes(stmt.fromClause); + self.queue_list_nodes(stmt.returningList); + self.queue_with_clause(stmt.withClause); + } + + unsafe fn collect_update_stmt(&mut self, stmt: &bindings_raw::UpdateStmt) -> protobuf::node::Node { + let relation = if stmt.relation.is_null() { None } else { self.pop_range_var() }; + let target_list = self.fetch_list_results(stmt.targetList); + let where_clause = self.single_result_box(stmt.whereClause); + let from_clause = self.fetch_list_results(stmt.fromClause); + let returning_list = self.fetch_list_results(stmt.returningList); + let with_clause = self.fetch_with_clause(stmt.withClause); + protobuf::node::Node::UpdateStmt(Box::new(protobuf::UpdateStmt { + relation, + target_list, + where_clause, + from_clause, + returning_list, + with_clause, + })) + } + + // ==================================================================== + // DeleteStmt + // ==================================================================== + + unsafe fn queue_delete_stmt(&mut self, stmt: &bindings_raw::DeleteStmt) { + if !stmt.relation.is_null() { + self.queue_node(stmt.relation as *const bindings_raw::Node); + } + self.queue_list_nodes(stmt.usingClause); + self.queue_node(stmt.whereClause); + self.queue_list_nodes(stmt.returningList); + self.queue_with_clause(stmt.withClause); + } + + unsafe fn collect_delete_stmt(&mut self, stmt: &bindings_raw::DeleteStmt) -> protobuf::node::Node { + let relation = if stmt.relation.is_null() { None } else { self.pop_range_var() }; + let using_clause = self.fetch_list_results(stmt.usingClause); + let where_clause = self.single_result_box(stmt.whereClause); + let returning_list = self.fetch_list_results(stmt.returningList); + let with_clause = self.fetch_with_clause(stmt.withClause); + protobuf::node::Node::DeleteStmt(Box::new(protobuf::DeleteStmt { relation, using_clause, where_clause, returning_list, with_clause })) + } + + // ==================================================================== + // SortBy + // ==================================================================== + + unsafe fn queue_sort_by(&mut self, sb: &bindings_raw::SortBy) { + self.queue_node(sb.node); + self.queue_list_nodes(sb.useOp); + } + + unsafe fn collect_sort_by(&mut self, sb: &bindings_raw::SortBy) -> protobuf::node::Node { + let node = self.single_result_box(sb.node); + let use_op = self.fetch_list_results(sb.useOp); + protobuf::node::Node::SortBy(Box::new(protobuf::SortBy { + node, + sortby_dir: sb.sortby_dir as i32 + 1, + sortby_nulls: sb.sortby_nulls as i32 + 1, + use_op, + location: sb.location, + })) + } + + // ==================================================================== + // TypeCast + // ==================================================================== + + unsafe fn queue_type_cast(&mut self, tc: &bindings_raw::TypeCast) { + self.queue_node(tc.arg); + if !tc.typeName.is_null() { + self.queue_node(tc.typeName as *const bindings_raw::Node); + } + } + + unsafe fn collect_type_cast(&mut self, tc: &bindings_raw::TypeCast) -> protobuf::node::Node { + let arg = self.single_result_box(tc.arg); + let type_name = if tc.typeName.is_null() { + None + } else { + self.result_stack.pop().and_then(|n| match n.node { + Some(protobuf::node::Node::TypeName(tn)) => Some(tn), + _ => None, + }) + }; + protobuf::node::Node::TypeCast(Box::new(protobuf::TypeCast { arg, type_name, location: tc.location })) + } + + // ==================================================================== + // TypeName + // ==================================================================== + + unsafe fn queue_type_name(&mut self, tn: &bindings_raw::TypeName) { + self.queue_list_nodes(tn.names); + self.queue_list_nodes(tn.typmods); + self.queue_list_nodes(tn.arrayBounds); + } + + unsafe fn collect_type_name(&mut self, tn: &bindings_raw::TypeName) -> protobuf::node::Node { + let names = self.fetch_list_results(tn.names); + let typmods = self.fetch_list_results(tn.typmods); + let array_bounds = self.fetch_list_results(tn.arrayBounds); + protobuf::node::Node::TypeName(protobuf::TypeName { + names, + type_oid: tn.typeOid, + setof: tn.setof, + pct_type: tn.pct_type, + typmods, + typemod: tn.typemod, + array_bounds, + location: tn.location, + }) + } + + // ==================================================================== + // OnConflictClause (helper struct — not a Node) + // ==================================================================== + + unsafe fn queue_on_conflict_clause(&mut self, oc: *mut bindings_raw::OnConflictClause) { + if oc.is_null() { + return; + } + let oc = &*oc; + self.queue_infer_clause(oc.infer); + self.queue_list_nodes(oc.targetList); + self.queue_node(oc.whereClause); + } + + unsafe fn fetch_on_conflict_clause(&mut self, oc: *mut bindings_raw::OnConflictClause) -> Option> { + if oc.is_null() { + return None; + } + let oc = &*oc; + let infer = self.fetch_infer_clause(oc.infer); + let target_list = self.fetch_list_results(oc.targetList); + let where_clause = self.single_result_box(oc.whereClause); + Some(Box::new(protobuf::OnConflictClause { action: oc.action as i32 + 1, infer, target_list, where_clause, location: oc.location })) + } + + // ==================================================================== + // InferClause (helper struct — not a Node) + // ==================================================================== + + unsafe fn queue_infer_clause(&mut self, ic: *mut bindings_raw::InferClause) { + if ic.is_null() { + return; + } + let ic = &*ic; + self.queue_list_nodes(ic.indexElems); + self.queue_node(ic.whereClause); + } + + unsafe fn fetch_infer_clause(&mut self, ic: *mut bindings_raw::InferClause) -> Option> { + if ic.is_null() { + return None; + } + let ic = &*ic; + let index_elems = self.fetch_list_results(ic.indexElems); + let where_clause = self.single_result_box(ic.whereClause); + Some(Box::new(protobuf::InferClause { index_elems, where_clause, conname: convert_c_string(ic.conname), location: ic.location })) + } + + // ==================================================================== + // BoolExpr + // ==================================================================== + + unsafe fn queue_bool_expr(&mut self, be: &bindings_raw::BoolExpr) { + self.queue_list_nodes(be.args); + } + + unsafe fn collect_bool_expr(&mut self, be: &bindings_raw::BoolExpr) -> protobuf::node::Node { + let args = self.fetch_list_results(be.args); + protobuf::node::Node::BoolExpr(Box::new(protobuf::BoolExpr { xpr: None, boolop: be.boolop as i32 + 1, args, location: be.location })) + } + + // ==================================================================== + // NullTest + // ==================================================================== + + unsafe fn queue_null_test(&mut self, nt: &bindings_raw::NullTest) { + self.queue_node(nt.arg as *const bindings_raw::Node); + } + + unsafe fn collect_null_test(&mut self, nt: &bindings_raw::NullTest) -> protobuf::node::Node { + let arg = self.single_result_box(nt.arg as *const bindings_raw::Node); + protobuf::node::Node::NullTest(Box::new(protobuf::NullTest { + xpr: None, + arg, + nulltesttype: nt.nulltesttype as i32 + 1, + argisrow: nt.argisrow, + location: nt.location, + })) + } + + // ==================================================================== + // SubLink + // ==================================================================== + + unsafe fn queue_sub_link(&mut self, sl: &bindings_raw::SubLink) { + self.queue_node(sl.testexpr); + self.queue_list_nodes(sl.operName); + self.queue_node(sl.subselect); + } + + unsafe fn collect_sub_link(&mut self, sl: &bindings_raw::SubLink) -> protobuf::node::Node { + let testexpr = self.single_result_box(sl.testexpr); + let oper_name = self.fetch_list_results(sl.operName); + let subselect = self.single_result_box(sl.subselect); + protobuf::node::Node::SubLink(Box::new(protobuf::SubLink { + xpr: None, + sub_link_type: sl.subLinkType as i32 + 1, + sub_link_id: sl.subLinkId, + testexpr, + oper_name, + subselect, + location: sl.location, + })) + } + + // ==================================================================== + // CaseExpr + // ==================================================================== + + unsafe fn queue_case_expr(&mut self, ce: &bindings_raw::CaseExpr) { + self.queue_node(ce.arg as *const bindings_raw::Node); + self.queue_list_nodes(ce.args); + self.queue_node(ce.defresult as *const bindings_raw::Node); + } + + unsafe fn collect_case_expr(&mut self, ce: &bindings_raw::CaseExpr) -> protobuf::node::Node { + let arg = self.single_result_box(ce.arg as *const bindings_raw::Node); + let args = self.fetch_list_results(ce.args); + let defresult = self.single_result_box(ce.defresult as *const bindings_raw::Node); + protobuf::node::Node::CaseExpr(Box::new(protobuf::CaseExpr { + xpr: None, + casetype: ce.casetype, + casecollid: ce.casecollid, + arg, + args, + defresult, + location: ce.location, + })) + } + + // ==================================================================== + // CaseWhen + // ==================================================================== + + unsafe fn queue_case_when(&mut self, cw: &bindings_raw::CaseWhen) { + self.queue_node(cw.expr as *const bindings_raw::Node); + self.queue_node(cw.result as *const bindings_raw::Node); + } + + unsafe fn collect_case_when(&mut self, cw: &bindings_raw::CaseWhen) -> protobuf::node::Node { + let expr = self.single_result_box(cw.expr as *const bindings_raw::Node); + let result = self.single_result_box(cw.result as *const bindings_raw::Node); + protobuf::node::Node::CaseWhen(Box::new(protobuf::CaseWhen { xpr: None, expr, result, location: cw.location })) + } + + // ==================================================================== + // CoalesceExpr + // ==================================================================== + + unsafe fn queue_coalesce_expr(&mut self, ce: &bindings_raw::CoalesceExpr) { + self.queue_list_nodes(ce.args); + } + + unsafe fn collect_coalesce_expr(&mut self, ce: &bindings_raw::CoalesceExpr) -> protobuf::node::Node { + let args = self.fetch_list_results(ce.args); + protobuf::node::Node::CoalesceExpr(Box::new(protobuf::CoalesceExpr { + xpr: None, + coalescetype: ce.coalescetype, + coalescecollid: ce.coalescecollid, + args, + location: ce.location, + })) + } + + // ==================================================================== + // MinMaxExpr + // ==================================================================== + + unsafe fn queue_min_max_expr(&mut self, mme: &bindings_raw::MinMaxExpr) { + self.queue_list_nodes(mme.args); + } + + unsafe fn collect_min_max_expr(&mut self, mme: &bindings_raw::MinMaxExpr) -> protobuf::node::Node { + let args = self.fetch_list_results(mme.args); + protobuf::node::Node::MinMaxExpr(Box::new(protobuf::MinMaxExpr { + xpr: None, + minmaxtype: mme.minmaxtype, + minmaxcollid: mme.minmaxcollid, + inputcollid: mme.inputcollid, + op: mme.op as i32 + 1, + args, + location: mme.location, + })) + } + + // ==================================================================== + // BooleanTest + // ==================================================================== + + unsafe fn queue_boolean_test(&mut self, bt: &bindings_raw::BooleanTest) { + self.queue_node(bt.arg as *const bindings_raw::Node); + } + + unsafe fn collect_boolean_test(&mut self, bt: &bindings_raw::BooleanTest) -> protobuf::node::Node { + let arg = self.single_result_box(bt.arg as *const bindings_raw::Node); + protobuf::node::Node::BooleanTest(Box::new(protobuf::BooleanTest { + xpr: None, + arg, + booltesttype: bt.booltesttype as i32 + 1, + location: bt.location, + })) + } + + // ==================================================================== + // A_ArrayExpr + // ==================================================================== + + unsafe fn queue_a_array_expr(&mut self, ae: &bindings_raw::A_ArrayExpr) { + self.queue_list_nodes(ae.elements); + } + + unsafe fn collect_a_array_expr(&mut self, ae: &bindings_raw::A_ArrayExpr) -> protobuf::node::Node { + let elements = self.fetch_list_results(ae.elements); + protobuf::node::Node::AArrayExpr(protobuf::AArrayExpr { elements, location: ae.location }) + } + + // ==================================================================== + // A_Indirection + // ==================================================================== + + unsafe fn queue_a_indirection(&mut self, ai: &bindings_raw::A_Indirection) { + self.queue_node(ai.arg); + self.queue_list_nodes(ai.indirection); + } + + unsafe fn collect_a_indirection(&mut self, ai: &bindings_raw::A_Indirection) -> protobuf::node::Node { + let arg = self.single_result_box(ai.arg); + let indirection = self.fetch_list_results(ai.indirection); + protobuf::node::Node::AIndirection(Box::new(protobuf::AIndirection { arg, indirection })) + } + + // ==================================================================== + // A_Indices + // ==================================================================== + + unsafe fn queue_a_indices(&mut self, ai: &bindings_raw::A_Indices) { + self.queue_node(ai.lidx); + self.queue_node(ai.uidx); + } + + unsafe fn collect_a_indices(&mut self, ai: &bindings_raw::A_Indices) -> protobuf::node::Node { + let lidx = self.single_result_box(ai.lidx); + let uidx = self.single_result_box(ai.uidx); + protobuf::node::Node::AIndices(Box::new(protobuf::AIndices { is_slice: ai.is_slice, lidx, uidx })) + } + + // ==================================================================== + // CollateClause + // ==================================================================== + + unsafe fn queue_collate_clause(&mut self, cc: &bindings_raw::CollateClause) { + self.queue_node(cc.arg); + self.queue_list_nodes(cc.collname); + } + + unsafe fn collect_collate_clause(&mut self, cc: &bindings_raw::CollateClause) -> protobuf::node::Node { + let arg = self.single_result_box(cc.arg); + let collname = self.fetch_list_results(cc.collname); + protobuf::node::Node::CollateClause(Box::new(protobuf::CollateClause { arg, collname, location: cc.location })) + } + + // ==================================================================== + // RangeSubselect + // ==================================================================== + + unsafe fn queue_range_subselect(&mut self, rs: &bindings_raw::RangeSubselect) { + self.queue_node(rs.subquery); + if !rs.alias.is_null() { + self.queue_node(rs.alias as *const bindings_raw::Node); + } + } + + unsafe fn collect_range_subselect(&mut self, rs: &bindings_raw::RangeSubselect) -> protobuf::node::Node { + let subquery = self.single_result_box(rs.subquery); + let alias = if rs.alias.is_null() { + None + } else { + self.result_stack.pop().and_then(|n| match n.node { + Some(protobuf::node::Node::Alias(a)) => Some(a), + _ => None, + }) + }; + protobuf::node::Node::RangeSubselect(Box::new(protobuf::RangeSubselect { lateral: rs.lateral, subquery, alias })) + } + + // ==================================================================== + // CommonTableExpr + // ==================================================================== + + unsafe fn queue_common_table_expr(&mut self, cte: &bindings_raw::CommonTableExpr) { + self.queue_list_nodes(cte.aliascolnames); + self.queue_node(cte.ctequery); + if !cte.search_clause.is_null() { + self.queue_node(cte.search_clause as *const bindings_raw::Node); + } + if !cte.cycle_clause.is_null() { + self.queue_node(cte.cycle_clause as *const bindings_raw::Node); + } + self.queue_list_nodes(cte.ctecolnames); + self.queue_list_nodes(cte.ctecoltypes); + self.queue_list_nodes(cte.ctecoltypmods); + self.queue_list_nodes(cte.ctecolcollations); + } + + unsafe fn collect_common_table_expr(&mut self, cte: &bindings_raw::CommonTableExpr) -> protobuf::node::Node { + let aliascolnames = self.fetch_list_results(cte.aliascolnames); + let ctequery = self.single_result_box(cte.ctequery); + let search_clause = if cte.search_clause.is_null() { + None + } else { + self.result_stack.pop().and_then(|n| match n.node { + Some(protobuf::node::Node::CtesearchClause(sc)) => Some(sc), + _ => None, + }) + }; + let cycle_clause = if cte.cycle_clause.is_null() { + None + } else { + self.result_stack.pop().and_then(|n| match n.node { + Some(protobuf::node::Node::CtecycleClause(cc)) => Some(cc), + _ => None, + }) + }; + let ctecolnames = self.fetch_list_results(cte.ctecolnames); + let ctecoltypes = self.fetch_list_results(cte.ctecoltypes); + let ctecoltypmods = self.fetch_list_results(cte.ctecoltypmods); + let ctecolcollations = self.fetch_list_results(cte.ctecolcollations); + protobuf::node::Node::CommonTableExpr(Box::new(protobuf::CommonTableExpr { + ctename: convert_c_string(cte.ctename), + aliascolnames, + ctematerialized: cte.ctematerialized as i32 + 1, + ctequery, + search_clause, + cycle_clause, + location: cte.location, + cterecursive: cte.cterecursive, + cterefcount: cte.cterefcount, + ctecolnames, + ctecoltypes, + ctecoltypmods, + ctecolcollations, + })) + } + + // ==================================================================== + // CTESearchClause + // ==================================================================== + + unsafe fn queue_cte_search_clause(&mut self, csc: &bindings_raw::CTESearchClause) { + self.queue_list_nodes(csc.search_col_list); + } + + unsafe fn collect_cte_search_clause(&mut self, csc: &bindings_raw::CTESearchClause) -> protobuf::node::Node { + let search_col_list = self.fetch_list_results(csc.search_col_list); + protobuf::node::Node::CtesearchClause(protobuf::CteSearchClause { + search_col_list, + search_breadth_first: csc.search_breadth_first, + search_seq_column: convert_c_string(csc.search_seq_column), + location: csc.location, + }) + } + + // ==================================================================== + // CTECycleClause + // ==================================================================== + + unsafe fn queue_cte_cycle_clause(&mut self, ccc: &bindings_raw::CTECycleClause) { + self.queue_list_nodes(ccc.cycle_col_list); + self.queue_node(ccc.cycle_mark_value); + self.queue_node(ccc.cycle_mark_default); + } + + unsafe fn collect_cte_cycle_clause(&mut self, ccc: &bindings_raw::CTECycleClause) -> protobuf::node::Node { + let cycle_col_list = self.fetch_list_results(ccc.cycle_col_list); + let cycle_mark_value = self.single_result_box(ccc.cycle_mark_value); + let cycle_mark_default = self.single_result_box(ccc.cycle_mark_default); + protobuf::node::Node::CtecycleClause(Box::new(protobuf::CteCycleClause { + cycle_col_list, + cycle_mark_column: convert_c_string(ccc.cycle_mark_column), + cycle_mark_value, + cycle_mark_default, + cycle_path_column: convert_c_string(ccc.cycle_path_column), + location: ccc.location, + cycle_mark_type: ccc.cycle_mark_type, + cycle_mark_typmod: ccc.cycle_mark_typmod, + cycle_mark_collation: ccc.cycle_mark_collation, + cycle_mark_neop: ccc.cycle_mark_neop, + })) + } + + // ==================================================================== + // GroupingSet + // ==================================================================== + + unsafe fn queue_grouping_set(&mut self, gs: &bindings_raw::GroupingSet) { + self.queue_list_nodes(gs.content); + } + + unsafe fn collect_grouping_set(&mut self, gs: &bindings_raw::GroupingSet) -> protobuf::node::Node { + let content = self.fetch_list_results(gs.content); + protobuf::node::Node::GroupingSet(protobuf::GroupingSet { kind: gs.kind as i32 + 1, content, location: gs.location }) + } + + // ==================================================================== + // LockingClause + // ==================================================================== + + unsafe fn queue_locking_clause(&mut self, lc: &bindings_raw::LockingClause) { + self.queue_list_nodes(lc.lockedRels); + } + + unsafe fn collect_locking_clause(&mut self, lc: &bindings_raw::LockingClause) -> protobuf::node::Node { + let locked_rels = self.fetch_list_results(lc.lockedRels); + protobuf::node::Node::LockingClause(protobuf::LockingClause { + locked_rels, + strength: lc.strength as i32 + 1, + wait_policy: lc.waitPolicy as i32 + 1, + }) + } + + // ==================================================================== + // RowExpr + // ==================================================================== + + unsafe fn queue_row_expr(&mut self, re: &bindings_raw::RowExpr) { + self.queue_list_nodes(re.args); + self.queue_list_nodes(re.colnames); + } + + unsafe fn collect_row_expr(&mut self, re: &bindings_raw::RowExpr) -> protobuf::node::Node { + let args = self.fetch_list_results(re.args); + let colnames = self.fetch_list_results(re.colnames); + protobuf::node::Node::RowExpr(Box::new(protobuf::RowExpr { + xpr: None, + args, + row_typeid: re.row_typeid, + row_format: re.row_format as i32 + 1, + colnames, + location: re.location, + })) + } + + // ==================================================================== + // MultiAssignRef + // ==================================================================== + + unsafe fn queue_multi_assign_ref(&mut self, mar: &bindings_raw::MultiAssignRef) { + self.queue_node(mar.source); + } + + unsafe fn collect_multi_assign_ref(&mut self, mar: &bindings_raw::MultiAssignRef) -> protobuf::node::Node { + let source = self.single_result_box(mar.source); + protobuf::node::Node::MultiAssignRef(Box::new(protobuf::MultiAssignRef { source, colno: mar.colno, ncolumns: mar.ncolumns })) + } + + // ==================================================================== + // Alias (as a standalone node) + // ==================================================================== + + unsafe fn queue_alias(&mut self, alias: &bindings_raw::Alias) { + self.queue_list_nodes(alias.colnames); + } + + unsafe fn collect_alias(&mut self, alias: &bindings_raw::Alias) -> protobuf::node::Node { + let colnames = self.fetch_list_results(alias.colnames); + protobuf::node::Node::Alias(protobuf::Alias { aliasname: convert_c_string(alias.aliasname), colnames }) + } + + // ==================================================================== + // GroupingFunc + // ==================================================================== + + unsafe fn queue_grouping_func(&mut self, gf: &bindings_raw::GroupingFunc) { + self.queue_list_nodes(gf.args); + self.queue_list_nodes(gf.refs); + } + + unsafe fn collect_grouping_func(&mut self, gf: &bindings_raw::GroupingFunc) -> protobuf::node::Node { + let args = self.fetch_list_results(gf.args); + let refs = self.fetch_list_results(gf.refs); + protobuf::node::Node::GroupingFunc(Box::new(protobuf::GroupingFunc { + xpr: None, + args, + refs, + agglevelsup: gf.agglevelsup, + location: gf.location, + })) + } + + // ==================================================================== + // IndexElem + // ==================================================================== + unsafe fn queue_index_elem(&mut self, ie: &bindings_raw::IndexElem) { + self.queue_node(ie.expr); + self.queue_list_nodes(ie.collation); + self.queue_list_nodes(ie.opclass); + self.queue_list_nodes(ie.opclassopts); + } + unsafe fn collect_index_elem(&mut self, ie: &bindings_raw::IndexElem) -> protobuf::node::Node { + let expr = self.single_result_box(ie.expr); + let collation = self.fetch_list_results(ie.collation); + let opclass = self.fetch_list_results(ie.opclass); + let opclassopts = self.fetch_list_results(ie.opclassopts); + protobuf::node::Node::IndexElem(Box::new(protobuf::IndexElem { + name: convert_c_string(ie.name), + expr, + indexcolname: convert_c_string(ie.indexcolname), + collation, + opclass, + opclassopts, + ordering: ie.ordering as i32 + 1, + nulls_ordering: ie.nulls_ordering as i32 + 1, + })) + } + + // ==================================================================== + // DefElem + // ==================================================================== + unsafe fn queue_def_elem(&mut self, de: &bindings_raw::DefElem) { + self.queue_node(de.arg); + } + unsafe fn collect_def_elem(&mut self, de: &bindings_raw::DefElem) -> protobuf::node::Node { + let arg = self.single_result_box(de.arg); + protobuf::node::Node::DefElem(Box::new(protobuf::DefElem { + defnamespace: convert_c_string(de.defnamespace), + defname: convert_c_string(de.defname), + arg, + defaction: de.defaction as i32 + 1, + location: de.location, + })) + } + + // ==================================================================== + // ColumnDef + // ==================================================================== + unsafe fn queue_column_def(&mut self, cd: &bindings_raw::ColumnDef) { + if !cd.typeName.is_null() { + self.queue_node(cd.typeName as *const bindings_raw::Node); + } + self.queue_node(cd.raw_default); + self.queue_node(cd.cooked_default); + if !cd.collClause.is_null() { + self.queue_node(cd.collClause as *const bindings_raw::Node); + } + self.queue_list_nodes(cd.constraints); + self.queue_list_nodes(cd.fdwoptions); + } + unsafe fn collect_column_def(&mut self, cd: &bindings_raw::ColumnDef) -> protobuf::node::Node { + let type_name = if cd.typeName.is_null() { + None + } else { + self.result_stack.pop().and_then(|n| match n.node { + Some(protobuf::node::Node::TypeName(tn)) => Some(tn), + _ => None, + }) + }; + let raw_default = self.single_result_box(cd.raw_default); + let cooked_default = self.single_result_box(cd.cooked_default); + let coll_clause = if cd.collClause.is_null() { + None + } else { + self.result_stack.pop().and_then(|n| match n.node { + Some(protobuf::node::Node::CollateClause(cc)) => Some(cc), + _ => None, + }) + }; + let constraints = self.fetch_list_results(cd.constraints); + let fdwoptions = self.fetch_list_results(cd.fdwoptions); + protobuf::node::Node::ColumnDef(Box::new(protobuf::ColumnDef { + colname: convert_c_string(cd.colname), + type_name, + compression: convert_c_string(cd.compression), + inhcount: cd.inhcount, + is_local: cd.is_local, + is_not_null: cd.is_not_null, + is_from_type: cd.is_from_type, + storage: if cd.storage == 0 { String::new() } else { String::from_utf8_lossy(&[cd.storage as u8]).to_string() }, + storage_name: convert_c_string(cd.storage_name), + raw_default, + cooked_default, + identity: if cd.identity == 0 { String::new() } else { String::from_utf8_lossy(&[cd.identity as u8]).to_string() }, + identity_sequence: None, // post-analysis + generated: if cd.generated == 0 { String::new() } else { String::from_utf8_lossy(&[cd.generated as u8]).to_string() }, + coll_clause, + coll_oid: cd.collOid, + constraints, + fdwoptions, + location: cd.location, + })) + } + + // ==================================================================== + // Constraint + // ==================================================================== + unsafe fn queue_constraint(&mut self, c: &bindings_raw::Constraint) { + self.queue_node(c.raw_expr); + self.queue_list_nodes(c.keys); + self.queue_list_nodes(c.including); + self.queue_list_nodes(c.exclusions); + self.queue_list_nodes(c.options); + self.queue_node(c.where_clause); + if !c.pktable.is_null() { + self.queue_node(c.pktable as *const bindings_raw::Node); + } + self.queue_list_nodes(c.fk_attrs); + self.queue_list_nodes(c.pk_attrs); + self.queue_list_nodes(c.fk_del_set_cols); + self.queue_list_nodes(c.old_conpfeqop); + } + unsafe fn collect_constraint(&mut self, c: &bindings_raw::Constraint) -> protobuf::node::Node { + let raw_expr = self.single_result_box(c.raw_expr); + let keys = self.fetch_list_results(c.keys); + let including = self.fetch_list_results(c.including); + let exclusions = self.fetch_list_results(c.exclusions); + let options = self.fetch_list_results(c.options); + let where_clause = self.single_result_box(c.where_clause); + let pktable = if c.pktable.is_null() { None } else { self.pop_range_var() }; + let fk_attrs = self.fetch_list_results(c.fk_attrs); + let pk_attrs = self.fetch_list_results(c.pk_attrs); + let fk_del_set_cols = self.fetch_list_results(c.fk_del_set_cols); + let old_conpfeqop = self.fetch_list_results(c.old_conpfeqop); + protobuf::node::Node::Constraint(Box::new(protobuf::Constraint { + contype: c.contype as i32 + 1, + conname: convert_c_string(c.conname), + deferrable: c.deferrable, + initdeferred: c.initdeferred, + location: c.location, + is_no_inherit: c.is_no_inherit, + raw_expr, + cooked_expr: convert_c_string(c.cooked_expr), + generated_when: if c.generated_when == 0 { String::new() } else { String::from_utf8_lossy(&[c.generated_when as u8]).to_string() }, + inhcount: c.inhcount, + nulls_not_distinct: c.nulls_not_distinct, + keys, + including, + exclusions, + options, + indexname: convert_c_string(c.indexname), + indexspace: convert_c_string(c.indexspace), + reset_default_tblspc: c.reset_default_tblspc, + access_method: convert_c_string(c.access_method), + where_clause, + pktable, + fk_attrs, + pk_attrs, + fk_matchtype: if c.fk_matchtype == 0 { String::new() } else { String::from_utf8_lossy(&[c.fk_matchtype as u8]).to_string() }, + fk_upd_action: if c.fk_upd_action == 0 { String::new() } else { String::from_utf8_lossy(&[c.fk_upd_action as u8]).to_string() }, + fk_del_action: if c.fk_del_action == 0 { String::new() } else { String::from_utf8_lossy(&[c.fk_del_action as u8]).to_string() }, + fk_del_set_cols, + old_conpfeqop, + old_pktable_oid: c.old_pktable_oid, + skip_validation: c.skip_validation, + initially_valid: c.initially_valid, + })) + } + + // ==================================================================== + // CreateStmt + // ==================================================================== + unsafe fn queue_create_stmt(&mut self, cs: &bindings_raw::CreateStmt) { + if !cs.relation.is_null() { + self.queue_node(cs.relation as *const bindings_raw::Node); + } + self.queue_list_nodes(cs.tableElts); + self.queue_list_nodes(cs.inhRelations); + if !cs.partbound.is_null() { + self.queue_node(cs.partbound as *const bindings_raw::Node); + } + if !cs.partspec.is_null() { + self.queue_node(cs.partspec as *const bindings_raw::Node); + } + if !cs.ofTypename.is_null() { + self.queue_node(cs.ofTypename as *const bindings_raw::Node); + } + self.queue_list_nodes(cs.constraints); + self.queue_list_nodes(cs.options); + } + unsafe fn collect_create_stmt(&mut self, cs: &bindings_raw::CreateStmt) -> protobuf::node::Node { + let relation = if cs.relation.is_null() { None } else { self.pop_range_var() }; + let table_elts = self.fetch_list_results(cs.tableElts); + let inh_relations = self.fetch_list_results(cs.inhRelations); + let partbound = if cs.partbound.is_null() { + None + } else { + self.result_stack.pop().and_then(|n| match n.node { + Some(protobuf::node::Node::PartitionBoundSpec(pbs)) => Some(pbs), + _ => None, + }) + }; + let partspec = if cs.partspec.is_null() { + None + } else { + self.result_stack.pop().and_then(|n| match n.node { + Some(protobuf::node::Node::PartitionSpec(ps)) => Some(ps), + _ => None, + }) + }; + let of_typename = if cs.ofTypename.is_null() { + None + } else { + self.result_stack.pop().and_then(|n| match n.node { + Some(protobuf::node::Node::TypeName(tn)) => Some(tn), + _ => None, + }) + }; + let constraints = self.fetch_list_results(cs.constraints); + let options = self.fetch_list_results(cs.options); + protobuf::node::Node::CreateStmt(protobuf::CreateStmt { + relation, + table_elts, + inh_relations, + partbound, + partspec, + of_typename, + constraints, + options, + oncommit: cs.oncommit as i32 + 1, + tablespacename: convert_c_string(cs.tablespacename), + access_method: convert_c_string(cs.accessMethod), + if_not_exists: cs.if_not_exists, + }) + } + + // ==================================================================== + // DropStmt + // ==================================================================== + unsafe fn queue_drop_stmt(&mut self, ds: &bindings_raw::DropStmt) { + self.queue_list_nodes(ds.objects); + } + unsafe fn collect_drop_stmt(&mut self, ds: &bindings_raw::DropStmt) -> protobuf::node::Node { + let objects = self.fetch_list_results(ds.objects); + protobuf::node::Node::DropStmt(protobuf::DropStmt { + objects, + remove_type: ds.removeType as i32 + 1, + behavior: ds.behavior as i32 + 1, + missing_ok: ds.missing_ok, + concurrent: ds.concurrent, + }) + } + + // ==================================================================== + // IndexStmt + // ==================================================================== + unsafe fn queue_index_stmt(&mut self, is_: &bindings_raw::IndexStmt) { + if !is_.relation.is_null() { + self.queue_node(is_.relation as *const bindings_raw::Node); + } + self.queue_list_nodes(is_.indexParams); + self.queue_list_nodes(is_.indexIncludingParams); + self.queue_list_nodes(is_.options); + self.queue_node(is_.whereClause); + self.queue_list_nodes(is_.excludeOpNames); + } + unsafe fn collect_index_stmt(&mut self, is_: &bindings_raw::IndexStmt) -> protobuf::node::Node { + let relation = if is_.relation.is_null() { None } else { self.pop_range_var() }; + let index_params = self.fetch_list_results(is_.indexParams); + let index_including_params = self.fetch_list_results(is_.indexIncludingParams); + let options = self.fetch_list_results(is_.options); + let where_clause = self.single_result_box(is_.whereClause); + let exclude_op_names = self.fetch_list_results(is_.excludeOpNames); + protobuf::node::Node::IndexStmt(Box::new(protobuf::IndexStmt { + idxname: convert_c_string(is_.idxname), + relation, + access_method: convert_c_string(is_.accessMethod), + table_space: convert_c_string(is_.tableSpace), + index_params, + index_including_params, + options, + where_clause, + exclude_op_names, + idxcomment: convert_c_string(is_.idxcomment), + index_oid: is_.indexOid, + old_number: is_.oldNumber, + old_create_subid: is_.oldCreateSubid, + old_first_relfilelocator_subid: is_.oldFirstRelfilelocatorSubid, + unique: is_.unique, + nulls_not_distinct: is_.nulls_not_distinct, + primary: is_.primary, + isconstraint: is_.isconstraint, + deferrable: is_.deferrable, + initdeferred: is_.initdeferred, + transformed: is_.transformed, + concurrent: is_.concurrent, + if_not_exists: is_.if_not_exists, + reset_default_tblspc: is_.reset_default_tblspc, + })) + } + + // ==================================================================== + // AlterTableStmt + // ==================================================================== + unsafe fn queue_alter_table_stmt(&mut self, ats: &bindings_raw::AlterTableStmt) { + if !ats.relation.is_null() { + self.queue_node(ats.relation as *const bindings_raw::Node); + } + self.queue_list_nodes(ats.cmds); + } + unsafe fn collect_alter_table_stmt(&mut self, ats: &bindings_raw::AlterTableStmt) -> protobuf::node::Node { + let relation = if ats.relation.is_null() { None } else { self.pop_range_var() }; + let cmds = self.fetch_list_results(ats.cmds); + protobuf::node::Node::AlterTableStmt(protobuf::AlterTableStmt { relation, cmds, objtype: ats.objtype as i32 + 1, missing_ok: ats.missing_ok }) + } + + // ==================================================================== + // AlterTableCmd + // ==================================================================== + unsafe fn queue_alter_table_cmd(&mut self, atc: &bindings_raw::AlterTableCmd) { + self.queue_node(atc.def); + } + unsafe fn collect_alter_table_cmd(&mut self, atc: &bindings_raw::AlterTableCmd) -> protobuf::node::Node { + let def = self.single_result_box(atc.def); + let newowner = if atc.newowner.is_null() { + None + } else { + Some(protobuf::RoleSpec { + roletype: (*atc.newowner).roletype as i32 + 1, + rolename: convert_c_string((*atc.newowner).rolename), + location: (*atc.newowner).location, + }) + }; + protobuf::node::Node::AlterTableCmd(Box::new(protobuf::AlterTableCmd { + subtype: atc.subtype as i32 + 1, + name: convert_c_string(atc.name), + num: atc.num as i32, + newowner, + def, + behavior: atc.behavior as i32 + 1, + missing_ok: atc.missing_ok, + recurse: atc.recurse, + })) + } + + // ==================================================================== + // RenameStmt + // ==================================================================== + unsafe fn queue_rename_stmt(&mut self, rs: &bindings_raw::RenameStmt) { + if !rs.relation.is_null() { + self.queue_node(rs.relation as *const bindings_raw::Node); + } + self.queue_node(rs.object); + } + unsafe fn collect_rename_stmt(&mut self, rs: &bindings_raw::RenameStmt) -> protobuf::node::Node { + let relation = if rs.relation.is_null() { None } else { self.pop_range_var() }; + let object = self.single_result_box(rs.object); + protobuf::node::Node::RenameStmt(Box::new(protobuf::RenameStmt { + rename_type: rs.renameType as i32 + 1, + relation_type: rs.relationType as i32 + 1, + relation, + object, + subname: convert_c_string(rs.subname), + newname: convert_c_string(rs.newname), + behavior: rs.behavior as i32 + 1, + missing_ok: rs.missing_ok, + })) + } + + // ==================================================================== + // ViewStmt + // ==================================================================== + unsafe fn queue_view_stmt(&mut self, vs: &bindings_raw::ViewStmt) { + if !vs.view.is_null() { + self.queue_node(vs.view as *const bindings_raw::Node); + } + self.queue_list_nodes(vs.aliases); + self.queue_node(vs.query); + self.queue_list_nodes(vs.options); + } + unsafe fn collect_view_stmt(&mut self, vs: &bindings_raw::ViewStmt) -> protobuf::node::Node { + let view = if vs.view.is_null() { None } else { self.pop_range_var() }; + let aliases = self.fetch_list_results(vs.aliases); + let query = self.single_result_box(vs.query); + let options = self.fetch_list_results(vs.options); + protobuf::node::Node::ViewStmt(Box::new(protobuf::ViewStmt { + view, + aliases, + query, + replace: vs.replace, + options, + with_check_option: vs.withCheckOption as i32 + 1, + })) + } + + // ==================================================================== + // CreateTableAsStmt + // ==================================================================== + unsafe fn queue_create_table_as_stmt(&mut self, ctas: &bindings_raw::CreateTableAsStmt) { + self.queue_node(ctas.query); + self.queue_into_clause(ctas.into); + } + unsafe fn collect_create_table_as_stmt(&mut self, ctas: &bindings_raw::CreateTableAsStmt) -> protobuf::node::Node { + let query = self.single_result_box(ctas.query); + let into = self.fetch_into_clause(ctas.into); + protobuf::node::Node::CreateTableAsStmt(Box::new(protobuf::CreateTableAsStmt { + query, + into, + objtype: ctas.objtype as i32 + 1, + is_select_into: ctas.is_select_into, + if_not_exists: ctas.if_not_exists, + })) + } + + // ==================================================================== + // TruncateStmt + // ==================================================================== + unsafe fn queue_truncate_stmt(&mut self, ts: &bindings_raw::TruncateStmt) { + self.queue_list_nodes(ts.relations); + } + unsafe fn collect_truncate_stmt(&mut self, ts: &bindings_raw::TruncateStmt) -> protobuf::node::Node { + let relations = self.fetch_list_results(ts.relations); + protobuf::node::Node::TruncateStmt(protobuf::TruncateStmt { relations, restart_seqs: ts.restart_seqs, behavior: ts.behavior as i32 + 1 }) + } + + // ==================================================================== + // AlterOwnerStmt + // ==================================================================== + unsafe fn queue_alter_owner_stmt(&mut self, aos: &bindings_raw::AlterOwnerStmt) { + if !aos.relation.is_null() { + self.queue_node(aos.relation as *const bindings_raw::Node); + } + self.queue_node(aos.object); + } + unsafe fn collect_alter_owner_stmt(&mut self, aos: &bindings_raw::AlterOwnerStmt) -> protobuf::node::Node { + let relation = if aos.relation.is_null() { None } else { self.pop_range_var() }; + let object = self.single_result_box(aos.object); + let newowner = if aos.newowner.is_null() { + None + } else { + Some(protobuf::RoleSpec { + roletype: (*aos.newowner).roletype as i32 + 1, + rolename: convert_c_string((*aos.newowner).rolename), + location: (*aos.newowner).location, + }) + }; + protobuf::node::Node::AlterOwnerStmt(Box::new(protobuf::AlterOwnerStmt { + object_type: aos.objectType as i32 + 1, + relation, + object, + newowner, + })) + } + + // ==================================================================== + // CreateSeqStmt + // ==================================================================== + unsafe fn queue_create_seq_stmt(&mut self, css: &bindings_raw::CreateSeqStmt) { + if !css.sequence.is_null() { + self.queue_node(css.sequence as *const bindings_raw::Node); + } + self.queue_list_nodes(css.options); + } + unsafe fn collect_create_seq_stmt(&mut self, css: &bindings_raw::CreateSeqStmt) -> protobuf::node::Node { + let sequence = if css.sequence.is_null() { None } else { self.pop_range_var() }; + let options = self.fetch_list_results(css.options); + protobuf::node::Node::CreateSeqStmt(protobuf::CreateSeqStmt { + sequence, + options, + owner_id: css.ownerId, + for_identity: css.for_identity, + if_not_exists: css.if_not_exists, + }) + } + + // ==================================================================== + // AlterSeqStmt + // ==================================================================== + unsafe fn queue_alter_seq_stmt(&mut self, ass_: &bindings_raw::AlterSeqStmt) { + if !ass_.sequence.is_null() { + self.queue_node(ass_.sequence as *const bindings_raw::Node); + } + self.queue_list_nodes(ass_.options); + } + unsafe fn collect_alter_seq_stmt(&mut self, ass_: &bindings_raw::AlterSeqStmt) -> protobuf::node::Node { + let sequence = if ass_.sequence.is_null() { None } else { self.pop_range_var() }; + let options = self.fetch_list_results(ass_.options); + protobuf::node::Node::AlterSeqStmt(protobuf::AlterSeqStmt { sequence, options, for_identity: ass_.for_identity, missing_ok: ass_.missing_ok }) + } + + // ==================================================================== + // CreateDomainStmt + // ==================================================================== + unsafe fn queue_create_domain_stmt(&mut self, cds: &bindings_raw::CreateDomainStmt) { + self.queue_list_nodes(cds.domainname); + if !cds.typeName.is_null() { + self.queue_node(cds.typeName as *const bindings_raw::Node); + } + if !cds.collClause.is_null() { + self.queue_node(cds.collClause as *const bindings_raw::Node); + } + self.queue_list_nodes(cds.constraints); + } + unsafe fn collect_create_domain_stmt(&mut self, cds: &bindings_raw::CreateDomainStmt) -> protobuf::node::Node { + let domainname = self.fetch_list_results(cds.domainname); + let type_name = if cds.typeName.is_null() { + None + } else { + self.result_stack.pop().and_then(|n| match n.node { + Some(protobuf::node::Node::TypeName(tn)) => Some(tn), + _ => None, + }) + }; + let coll_clause = if cds.collClause.is_null() { + None + } else { + self.result_stack.pop().and_then(|n| match n.node { + Some(protobuf::node::Node::CollateClause(cc)) => Some(cc), + _ => None, + }) + }; + let constraints = self.fetch_list_results(cds.constraints); + protobuf::node::Node::CreateDomainStmt(Box::new(protobuf::CreateDomainStmt { domainname, type_name, coll_clause, constraints })) + } + + // ==================================================================== + // CompositeTypeStmt + // ==================================================================== + unsafe fn queue_composite_type_stmt(&mut self, cts: &bindings_raw::CompositeTypeStmt) { + if !cts.typevar.is_null() { + self.queue_node(cts.typevar as *const bindings_raw::Node); + } + self.queue_list_nodes(cts.coldeflist); + } + unsafe fn collect_composite_type_stmt(&mut self, cts: &bindings_raw::CompositeTypeStmt) -> protobuf::node::Node { + let typevar = if cts.typevar.is_null() { None } else { self.pop_range_var() }; + let coldeflist = self.fetch_list_results(cts.coldeflist); + protobuf::node::Node::CompositeTypeStmt(protobuf::CompositeTypeStmt { typevar, coldeflist }) + } + + // ==================================================================== + // CreateEnumStmt + // ==================================================================== + unsafe fn queue_create_enum_stmt(&mut self, ces: &bindings_raw::CreateEnumStmt) { + self.queue_list_nodes(ces.typeName); + self.queue_list_nodes(ces.vals); + } + unsafe fn collect_create_enum_stmt(&mut self, ces: &bindings_raw::CreateEnumStmt) -> protobuf::node::Node { + let type_name = self.fetch_list_results(ces.typeName); + let vals = self.fetch_list_results(ces.vals); + protobuf::node::Node::CreateEnumStmt(protobuf::CreateEnumStmt { type_name, vals }) + } + + // ==================================================================== + // CreateExtensionStmt + // ==================================================================== + unsafe fn queue_create_extension_stmt(&mut self, ces: &bindings_raw::CreateExtensionStmt) { + self.queue_list_nodes(ces.options); + } + unsafe fn collect_create_extension_stmt(&mut self, ces: &bindings_raw::CreateExtensionStmt) -> protobuf::node::Node { + let options = self.fetch_list_results(ces.options); + protobuf::node::Node::CreateExtensionStmt(protobuf::CreateExtensionStmt { + extname: convert_c_string(ces.extname), + if_not_exists: ces.if_not_exists, + options, + }) + } + + // ==================================================================== + // Publication / Subscription / Trigger stmts + // ==================================================================== + unsafe fn queue_create_publication_stmt(&mut self, cps: &bindings_raw::CreatePublicationStmt) { + self.queue_list_nodes(cps.options); + self.queue_list_nodes(cps.pubobjects); + } + unsafe fn collect_create_publication_stmt(&mut self, cps: &bindings_raw::CreatePublicationStmt) -> protobuf::node::Node { + let options = self.fetch_list_results(cps.options); + let pubobjects = self.fetch_list_results(cps.pubobjects); + protobuf::node::Node::CreatePublicationStmt(protobuf::CreatePublicationStmt { + pubname: convert_c_string(cps.pubname), + options, + pubobjects, + for_all_tables: cps.for_all_tables, + }) + } + unsafe fn queue_alter_publication_stmt(&mut self, aps: &bindings_raw::AlterPublicationStmt) { + self.queue_list_nodes(aps.options); + self.queue_list_nodes(aps.pubobjects); + } + unsafe fn collect_alter_publication_stmt(&mut self, aps: &bindings_raw::AlterPublicationStmt) -> protobuf::node::Node { + let options = self.fetch_list_results(aps.options); + let pubobjects = self.fetch_list_results(aps.pubobjects); + protobuf::node::Node::AlterPublicationStmt(protobuf::AlterPublicationStmt { + pubname: convert_c_string(aps.pubname), + options, + pubobjects, + for_all_tables: aps.for_all_tables, + action: aps.action as i32 + 1, + }) + } + unsafe fn queue_create_subscription_stmt(&mut self, css: &bindings_raw::CreateSubscriptionStmt) { + self.queue_list_nodes(css.publication); + self.queue_list_nodes(css.options); + } + unsafe fn collect_create_subscription_stmt(&mut self, css: &bindings_raw::CreateSubscriptionStmt) -> protobuf::node::Node { + let publication = self.fetch_list_results(css.publication); + let options = self.fetch_list_results(css.options); + protobuf::node::Node::CreateSubscriptionStmt(protobuf::CreateSubscriptionStmt { + subname: convert_c_string(css.subname), + conninfo: convert_c_string(css.conninfo), + publication, + options, + }) + } + unsafe fn queue_alter_subscription_stmt(&mut self, ass_: &bindings_raw::AlterSubscriptionStmt) { + self.queue_list_nodes(ass_.publication); + self.queue_list_nodes(ass_.options); + } + unsafe fn collect_alter_subscription_stmt(&mut self, ass_: &bindings_raw::AlterSubscriptionStmt) -> protobuf::node::Node { + let publication = self.fetch_list_results(ass_.publication); + let options = self.fetch_list_results(ass_.options); + protobuf::node::Node::AlterSubscriptionStmt(protobuf::AlterSubscriptionStmt { + kind: ass_.kind as i32 + 1, + subname: convert_c_string(ass_.subname), + conninfo: convert_c_string(ass_.conninfo), + publication, + options, + }) + } + unsafe fn queue_create_trig_stmt(&mut self, cts: &bindings_raw::CreateTrigStmt) { + if !cts.relation.is_null() { + self.queue_node(cts.relation as *const bindings_raw::Node); + } + self.queue_list_nodes(cts.funcname); + self.queue_list_nodes(cts.args); + self.queue_list_nodes(cts.columns); + self.queue_node(cts.whenClause); + self.queue_list_nodes(cts.transitionRels); + if !cts.constrrel.is_null() { + self.queue_node(cts.constrrel as *const bindings_raw::Node); + } + } + unsafe fn collect_create_trig_stmt(&mut self, cts: &bindings_raw::CreateTrigStmt) -> protobuf::node::Node { + let relation = if cts.relation.is_null() { None } else { self.pop_range_var() }; + let funcname = self.fetch_list_results(cts.funcname); + let args = self.fetch_list_results(cts.args); + let columns = self.fetch_list_results(cts.columns); + let when_clause = self.single_result_box(cts.whenClause); + let transition_rels = self.fetch_list_results(cts.transitionRels); + let constrrel = if cts.constrrel.is_null() { None } else { self.pop_range_var() }; + protobuf::node::Node::CreateTrigStmt(Box::new(protobuf::CreateTrigStmt { + replace: cts.replace, + isconstraint: cts.isconstraint, + trigname: convert_c_string(cts.trigname), + relation, + funcname, + args, + row: cts.row, + timing: cts.timing as i32, + events: cts.events as i32, + columns, + when_clause, + transition_rels, + deferrable: cts.deferrable, + initdeferred: cts.initdeferred, + constrrel, + })) + } + unsafe fn queue_publication_obj_spec(&mut self, pos: &bindings_raw::PublicationObjSpec) { + if !pos.pubtable.is_null() { + let pt = &*pos.pubtable; + if !pt.relation.is_null() { + self.queue_node(pt.relation as *const bindings_raw::Node); + } + self.queue_node(pt.whereClause as *const bindings_raw::Node); + self.queue_list_nodes(pt.columns); + } + } + unsafe fn collect_publication_obj_spec(&mut self, pos: &bindings_raw::PublicationObjSpec) -> protobuf::node::Node { + let pubtable = if pos.pubtable.is_null() { + None + } else { + let pt = &*pos.pubtable; + let relation = if pt.relation.is_null() { None } else { self.pop_range_var() }; + let where_clause = self.single_result_box(pt.whereClause as *const bindings_raw::Node); + let columns = self.fetch_list_results(pt.columns); + Some(Box::new(protobuf::PublicationTable { relation, where_clause, columns })) + }; + protobuf::node::Node::PublicationObjSpec(Box::new(protobuf::PublicationObjSpec { + pubobjtype: pos.pubobjtype as i32 + 1, + name: convert_c_string(pos.name), + pubtable, + location: pos.location, + })) + } + + // ==================================================================== + // Partition nodes + // ==================================================================== + unsafe fn queue_partition_elem(&mut self, pe: &bindings_raw::PartitionElem) { + self.queue_node(pe.expr); + self.queue_list_nodes(pe.collation); + self.queue_list_nodes(pe.opclass); + } + unsafe fn collect_partition_elem(&mut self, pe: &bindings_raw::PartitionElem) -> protobuf::node::Node { + let expr = self.single_result_box(pe.expr); + let collation = self.fetch_list_results(pe.collation); + let opclass = self.fetch_list_results(pe.opclass); + protobuf::node::Node::PartitionElem(Box::new(protobuf::PartitionElem { + name: convert_c_string(pe.name), + expr, + collation, + opclass, + location: pe.location, + })) + } + unsafe fn queue_partition_spec(&mut self, ps: &bindings_raw::PartitionSpec) { + self.queue_list_nodes(ps.partParams); + } + unsafe fn collect_partition_spec(&mut self, ps: &bindings_raw::PartitionSpec) -> protobuf::node::Node { + let part_params = self.fetch_list_results(ps.partParams); + let strategy = match ps.strategy as u8 as char { + 'l' => 1, + 'r' => 2, + 'h' => 3, + _ => 0, + }; + protobuf::node::Node::PartitionSpec(protobuf::PartitionSpec { strategy, part_params, location: ps.location }) + } + unsafe fn queue_partition_bound_spec(&mut self, pbs: &bindings_raw::PartitionBoundSpec) { + self.queue_list_nodes(pbs.listdatums); + self.queue_list_nodes(pbs.lowerdatums); + self.queue_list_nodes(pbs.upperdatums); + } + unsafe fn collect_partition_bound_spec(&mut self, pbs: &bindings_raw::PartitionBoundSpec) -> protobuf::node::Node { + let listdatums = self.fetch_list_results(pbs.listdatums); + let lowerdatums = self.fetch_list_results(pbs.lowerdatums); + let upperdatums = self.fetch_list_results(pbs.upperdatums); + protobuf::node::Node::PartitionBoundSpec(protobuf::PartitionBoundSpec { + strategy: if pbs.strategy == 0 { String::new() } else { String::from_utf8_lossy(&[pbs.strategy as u8]).to_string() }, + is_default: pbs.is_default, + modulus: pbs.modulus, + remainder: pbs.remainder, + listdatums, + lowerdatums, + upperdatums, + location: pbs.location, + }) + } + unsafe fn queue_partition_range_datum(&mut self, prd: &bindings_raw::PartitionRangeDatum) { + self.queue_node(prd.value); + } + unsafe fn collect_partition_range_datum(&mut self, prd: &bindings_raw::PartitionRangeDatum) -> protobuf::node::Node { + let value = self.single_result_box(prd.value); + let kind = match prd.kind { + bindings_raw::PartitionRangeDatumKind_PARTITION_RANGE_DATUM_MINVALUE => 1, + bindings_raw::PartitionRangeDatumKind_PARTITION_RANGE_DATUM_VALUE => 2, + bindings_raw::PartitionRangeDatumKind_PARTITION_RANGE_DATUM_MAXVALUE => 3, + _ => 0, + }; + protobuf::node::Node::PartitionRangeDatum(Box::new(protobuf::PartitionRangeDatum { kind, value, location: prd.location })) + } + + // ==================================================================== + // Statement types (utility / session) + // ==================================================================== + unsafe fn queue_explain_stmt(&mut self, es: &bindings_raw::ExplainStmt) { + self.queue_node(es.query); + self.queue_list_nodes(es.options); + } + unsafe fn collect_explain_stmt(&mut self, es: &bindings_raw::ExplainStmt) -> protobuf::node::Node { + let query = self.single_result_box(es.query); + let options = self.fetch_list_results(es.options); + protobuf::node::Node::ExplainStmt(Box::new(protobuf::ExplainStmt { query, options })) + } + unsafe fn queue_copy_stmt(&mut self, cs: &bindings_raw::CopyStmt) { + if !cs.relation.is_null() { + self.queue_node(cs.relation as *const bindings_raw::Node); + } + self.queue_node(cs.query); + self.queue_list_nodes(cs.attlist); + self.queue_list_nodes(cs.options); + self.queue_node(cs.whereClause); + } + unsafe fn collect_copy_stmt(&mut self, cs: &bindings_raw::CopyStmt) -> protobuf::node::Node { + let relation = if cs.relation.is_null() { None } else { self.pop_range_var() }; + let query = self.single_result_box(cs.query); + let attlist = self.fetch_list_results(cs.attlist); + let options = self.fetch_list_results(cs.options); + let where_clause = self.single_result_box(cs.whereClause); + protobuf::node::Node::CopyStmt(Box::new(protobuf::CopyStmt { + relation, + query, + attlist, + is_from: cs.is_from, + is_program: cs.is_program, + filename: convert_c_string(cs.filename), + options, + where_clause, + })) + } + unsafe fn queue_prepare_stmt(&mut self, ps: &bindings_raw::PrepareStmt) { + self.queue_list_nodes(ps.argtypes); + self.queue_node(ps.query); + } + unsafe fn collect_prepare_stmt(&mut self, ps: &bindings_raw::PrepareStmt) -> protobuf::node::Node { + let argtypes = self.fetch_list_results(ps.argtypes); + let query = self.single_result_box(ps.query); + protobuf::node::Node::PrepareStmt(Box::new(protobuf::PrepareStmt { name: convert_c_string(ps.name), argtypes, query })) + } + unsafe fn queue_execute_stmt(&mut self, es: &bindings_raw::ExecuteStmt) { + self.queue_list_nodes(es.params); + } + unsafe fn collect_execute_stmt(&mut self, es: &bindings_raw::ExecuteStmt) -> protobuf::node::Node { + let params = self.fetch_list_results(es.params); + protobuf::node::Node::ExecuteStmt(protobuf::ExecuteStmt { name: convert_c_string(es.name), params }) + } + unsafe fn queue_transaction_stmt(&mut self, ts: &bindings_raw::TransactionStmt) { + self.queue_list_nodes(ts.options); + } + unsafe fn collect_transaction_stmt(&mut self, ts: &bindings_raw::TransactionStmt) -> protobuf::node::Node { + let options = self.fetch_list_results(ts.options); + protobuf::node::Node::TransactionStmt(protobuf::TransactionStmt { + kind: ts.kind as i32 + 1, + options, + savepoint_name: convert_c_string(ts.savepoint_name), + gid: convert_c_string(ts.gid), + chain: ts.chain, + location: ts.location, + }) + } + unsafe fn queue_vacuum_stmt(&mut self, vs: &bindings_raw::VacuumStmt) { + self.queue_list_nodes(vs.options); + self.queue_list_nodes(vs.rels); + } + unsafe fn collect_vacuum_stmt(&mut self, vs: &bindings_raw::VacuumStmt) -> protobuf::node::Node { + let options = self.fetch_list_results(vs.options); + let rels = self.fetch_list_results(vs.rels); + protobuf::node::Node::VacuumStmt(protobuf::VacuumStmt { options, rels, is_vacuumcmd: vs.is_vacuumcmd }) + } + unsafe fn queue_vacuum_relation(&mut self, vr: &bindings_raw::VacuumRelation) { + if !vr.relation.is_null() { + self.queue_node(vr.relation as *const bindings_raw::Node); + } + self.queue_list_nodes(vr.va_cols); + } + unsafe fn collect_vacuum_relation(&mut self, vr: &bindings_raw::VacuumRelation) -> protobuf::node::Node { + let relation = if vr.relation.is_null() { None } else { self.pop_range_var() }; + let va_cols = self.fetch_list_results(vr.va_cols); + protobuf::node::Node::VacuumRelation(protobuf::VacuumRelation { relation, oid: vr.oid, va_cols }) + } + unsafe fn queue_variable_set_stmt(&mut self, vss: &bindings_raw::VariableSetStmt) { + self.queue_list_nodes(vss.args); + } + unsafe fn collect_variable_set_stmt(&mut self, vss: &bindings_raw::VariableSetStmt) -> protobuf::node::Node { + let args = self.fetch_list_results(vss.args); + protobuf::node::Node::VariableSetStmt(protobuf::VariableSetStmt { + kind: vss.kind as i32 + 1, + name: convert_c_string(vss.name), + args, + is_local: vss.is_local, + }) + } + unsafe fn queue_lock_stmt(&mut self, ls: &bindings_raw::LockStmt) { + self.queue_list_nodes(ls.relations); + } + unsafe fn collect_lock_stmt(&mut self, ls: &bindings_raw::LockStmt) -> protobuf::node::Node { + let relations = self.fetch_list_results(ls.relations); + protobuf::node::Node::LockStmt(protobuf::LockStmt { relations, mode: ls.mode, nowait: ls.nowait }) + } + unsafe fn queue_do_stmt(&mut self, ds: &bindings_raw::DoStmt) { + self.queue_list_nodes(ds.args); + } + unsafe fn collect_do_stmt(&mut self, ds: &bindings_raw::DoStmt) -> protobuf::node::Node { + let args = self.fetch_list_results(ds.args); + protobuf::node::Node::DoStmt(protobuf::DoStmt { args }) + } + unsafe fn queue_object_with_args(&mut self, owa: &bindings_raw::ObjectWithArgs) { + self.queue_list_nodes(owa.objname); + self.queue_list_nodes(owa.objargs); + self.queue_list_nodes(owa.objfuncargs); + } + unsafe fn collect_object_with_args(&mut self, owa: &bindings_raw::ObjectWithArgs) -> protobuf::node::Node { + let objname = self.fetch_list_results(owa.objname); + let objargs = self.fetch_list_results(owa.objargs); + let objfuncargs = self.fetch_list_results(owa.objfuncargs); + protobuf::node::Node::ObjectWithArgs(protobuf::ObjectWithArgs { objname, objargs, objfuncargs, args_unspecified: owa.args_unspecified }) + } + unsafe fn queue_coerce_to_domain(&mut self, ctd: &bindings_raw::CoerceToDomain) { + self.queue_node(ctd.arg as *const bindings_raw::Node); + } + unsafe fn collect_coerce_to_domain(&mut self, ctd: &bindings_raw::CoerceToDomain) -> protobuf::node::Node { + let arg = self.single_result_box(ctd.arg as *const bindings_raw::Node); + protobuf::node::Node::CoerceToDomain(Box::new(protobuf::CoerceToDomain { + xpr: None, + arg, + resulttype: ctd.resulttype, + resulttypmod: ctd.resulttypmod, + resultcollid: ctd.resultcollid, + coercionformat: ctd.coercionformat as i32 + 1, + location: ctd.location, + })) + } + unsafe fn queue_function_parameter(&mut self, fp: &bindings_raw::FunctionParameter) { + if !fp.argType.is_null() { + self.queue_node(fp.argType as *const bindings_raw::Node); + } + self.queue_node(fp.defexpr); + } + unsafe fn collect_function_parameter(&mut self, fp: &bindings_raw::FunctionParameter) -> protobuf::node::Node { + let arg_type = if fp.argType.is_null() { + None + } else { + self.result_stack.pop().and_then(|n| match n.node { + Some(protobuf::node::Node::TypeName(tn)) => Some(tn), + _ => None, + }) + }; + let defexpr = self.single_result_box(fp.defexpr); + let mode = match fp.mode { + bindings_raw::FunctionParameterMode_FUNC_PARAM_IN => protobuf::FunctionParameterMode::FuncParamIn as i32, + bindings_raw::FunctionParameterMode_FUNC_PARAM_OUT => protobuf::FunctionParameterMode::FuncParamOut as i32, + bindings_raw::FunctionParameterMode_FUNC_PARAM_INOUT => protobuf::FunctionParameterMode::FuncParamInout as i32, + bindings_raw::FunctionParameterMode_FUNC_PARAM_VARIADIC => protobuf::FunctionParameterMode::FuncParamVariadic as i32, + bindings_raw::FunctionParameterMode_FUNC_PARAM_TABLE => protobuf::FunctionParameterMode::FuncParamTable as i32, + bindings_raw::FunctionParameterMode_FUNC_PARAM_DEFAULT => protobuf::FunctionParameterMode::FuncParamDefault as i32, + _ => 0, + }; + protobuf::node::Node::FunctionParameter(Box::new(protobuf::FunctionParameter { name: convert_c_string(fp.name), arg_type, mode, defexpr })) + } +} + +unsafe fn convert_a_const(aconst: &bindings_raw::A_Const) -> protobuf::AConst { + let val = if aconst.isnull { + None + } else { + // Check the node tag in the val union to determine the type + let node_tag = aconst.val.node.type_; + match node_tag { + bindings_raw::NodeTag_T_Integer => Some(protobuf::a_const::Val::Ival(protobuf::Integer { ival: aconst.val.ival.ival })), + bindings_raw::NodeTag_T_Float => { + let fval = if aconst.val.fval.fval.is_null() { + std::string::String::new() + } else { + CStr::from_ptr(aconst.val.fval.fval).to_string_lossy().to_string() + }; + Some(protobuf::a_const::Val::Fval(protobuf::Float { fval })) + } + bindings_raw::NodeTag_T_Boolean => Some(protobuf::a_const::Val::Boolval(protobuf::Boolean { boolval: aconst.val.boolval.boolval })), + bindings_raw::NodeTag_T_String => { + let sval = if aconst.val.sval.sval.is_null() { + std::string::String::new() + } else { + CStr::from_ptr(aconst.val.sval.sval).to_string_lossy().to_string() + }; + Some(protobuf::a_const::Val::Sval(protobuf::String { sval })) + } + bindings_raw::NodeTag_T_BitString => { + let bsval = if aconst.val.bsval.bsval.is_null() { + std::string::String::new() + } else { + CStr::from_ptr(aconst.val.bsval.bsval).to_string_lossy().to_string() + }; + Some(protobuf::a_const::Val::Bsval(protobuf::BitString { bsval })) + } + _ => None, + } + }; + + protobuf::AConst { isnull: aconst.isnull, val, location: aconst.location } +} + +/// Converts a C string pointer to a Rust String. +unsafe fn convert_c_string(ptr: *const c_char) -> std::string::String { + if ptr.is_null() { + std::string::String::new() + } else { + CStr::from_ptr(ptr).to_string_lossy().to_string() + } +} + +unsafe fn convert_string(s: &bindings_raw::String) -> protobuf::String { + protobuf::String { sval: convert_c_string(s.sval) } +} + +unsafe fn convert_bit_string(bs: &bindings_raw::BitString) -> protobuf::BitString { + protobuf::BitString { bsval: convert_c_string(bs.bsval) } +} diff --git a/tests/raw_parse_iter/basic.rs b/tests/raw_parse_iter/basic.rs new file mode 100644 index 0000000..f06b961 --- /dev/null +++ b/tests/raw_parse_iter/basic.rs @@ -0,0 +1,277 @@ +//! Basic parsing tests for parse_raw_iter. +//! +//! These tests verify fundamental parsing behavior including: +//! - Simple SELECT queries +//! - Error handling +//! - Multiple statements +//! - Empty queries + +use super::*; + +/// Test that parse_raw_iter results can be deparsed back to SQL +#[test] +fn it_deparses_parse_raw_iter_result() { + let query = "SELECT * FROM users"; + let result = parse_raw_iter(query).unwrap(); + + let deparsed = result.deparse().unwrap(); + assert_eq!(deparsed, query); +} + +/// Test that parse_raw_iter successfully parses a simple SELECT query +#[test] +fn it_parses_simple_select() { + let query = "SELECT 1"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf.stmts.len(), 1); + assert_eq!(raw_result.protobuf, proto_result.protobuf); + // Verify deparse produces original query + assert_eq!(deparse_raw(&raw_result.protobuf).unwrap(), query); +} + +/// Test that parse_raw_iter handles syntax errors +#[test] +fn it_handles_parse_errors() { + let query = "SELECT * FORM users"; + let raw_error = parse_raw_iter(query).err().unwrap(); + let proto_error = parse(query).err().unwrap(); + + assert!(matches!(raw_error, Error::Parse(_))); + assert!(matches!(proto_error, Error::Parse(_))); +} + +/// Test that parse_raw_iter and parse produce equivalent results for simple SELECT +#[test] +fn it_matches_parse_for_simple_select() { + let query = "SELECT 1"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); + assert_eq!(deparse_raw(&raw_result.protobuf).unwrap(), query); +} + +/// Test that parse_raw_iter and parse produce equivalent results for SELECT with table +#[test] +fn it_matches_parse_for_select_from_table() { + let query = "SELECT * FROM users"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); + assert_eq!(deparse_raw(&raw_result.protobuf).unwrap(), query); + + let mut raw_tables = raw_result.tables(); + let mut proto_tables = proto_result.tables(); + raw_tables.sort(); + proto_tables.sort(); + assert_eq!(raw_tables, proto_tables); + assert_eq!(raw_tables, vec!["users"]); +} + +/// Test that parse_raw_iter handles empty queries (comments only) +#[test] +fn it_handles_empty_queries() { + let query = "-- just a comment"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf.stmts.len(), 0); + assert_eq!(raw_result.protobuf, proto_result.protobuf); + // Empty queries deparse to empty string (comments are stripped) + assert_eq!(deparse_raw(&raw_result.protobuf).unwrap(), ""); +} + +/// Test that parse_raw_iter parses multiple statements +#[test] +fn it_parses_multiple_statements() { + let query = "SELECT 1; SELECT 2; SELECT 3"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf.stmts.len(), 3); + assert_eq!(raw_result.protobuf, proto_result.protobuf); + assert_eq!(deparse_raw(&raw_result.protobuf).unwrap(), query); +} + +/// Test that tables() returns the same results for both parsers +#[test] +fn it_returns_tables_like_parse() { + let query = "SELECT u.*, o.* FROM users u JOIN orders o ON u.id = o.user_id WHERE o.status = 'active'"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); + assert_eq!(deparse_raw(&raw_result.protobuf).unwrap(), query); + + let mut raw_tables = raw_result.tables(); + let mut proto_tables = proto_result.tables(); + raw_tables.sort(); + proto_tables.sort(); + assert_eq!(raw_tables, proto_tables); + assert_eq!(raw_tables, vec!["orders", "users"]); +} + +/// Test that functions() returns the same results for both parsers +#[test] +fn it_returns_functions_like_parse() { + let query = "SELECT count(*), sum(amount) FROM orders"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); + assert_eq!(deparse_raw(&raw_result.protobuf).unwrap(), query); + + let mut raw_funcs = raw_result.functions(); + let mut proto_funcs = proto_result.functions(); + raw_funcs.sort(); + proto_funcs.sort(); + assert_eq!(raw_funcs, proto_funcs); + assert_eq!(raw_funcs, vec!["count", "sum"]); +} + +/// Test that statement_types() returns the same results for both parsers +#[test] +fn it_returns_statement_types_like_parse() { + let query = "SELECT 1; INSERT INTO t VALUES (1); UPDATE t SET x = 1; DELETE FROM t"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); + assert_eq!(deparse_raw(&raw_result.protobuf).unwrap(), query); + + assert_eq!(raw_result.statement_types(), proto_result.statement_types()); + assert_eq!(raw_result.statement_types(), vec!["SelectStmt", "InsertStmt", "UpdateStmt", "DeleteStmt"]); +} + +// ============================================================================ +// deparse_raw tests +// ============================================================================ + +/// Test that deparse_raw successfully roundtrips a simple SELECT +#[test] +fn it_deparse_raw_simple_select() { + let query = "SELECT 1"; + let result = parse_raw_iter(query).unwrap(); + let deparsed = deparse_raw(&result.protobuf).unwrap(); + assert_eq!(deparsed, query); +} + +/// Test that deparse_raw successfully roundtrips SELECT FROM table +#[test] +fn it_deparse_raw_select_from_table() { + let query = "SELECT * FROM users"; + let result = parse_raw_iter(query).unwrap(); + let deparsed = deparse_raw(&result.protobuf).unwrap(); + assert_eq!(deparsed, query); +} + +/// Test that deparse_raw handles complex queries +#[test] +fn it_deparse_raw_complex_select() { + let query = "SELECT u.id, u.name FROM users u WHERE u.active = true ORDER BY u.name"; + let result = parse_raw_iter(query).unwrap(); + let deparsed = deparse_raw(&result.protobuf).unwrap(); + assert_eq!(deparsed, query); +} + +/// Test that deparse_raw handles INSERT statements +#[test] +fn it_deparse_raw_insert() { + let query = "INSERT INTO users (name, email) VALUES ('John', 'john@example.com')"; + let result = parse_raw_iter(query).unwrap(); + let deparsed = deparse_raw(&result.protobuf).unwrap(); + assert_eq!(deparsed, query); +} + +/// Test that deparse_raw handles UPDATE statements +#[test] +fn it_deparse_raw_update() { + let query = "UPDATE users SET name = 'Jane' WHERE id = 1"; + let result = parse_raw_iter(query).unwrap(); + let deparsed = deparse_raw(&result.protobuf).unwrap(); + assert_eq!(deparsed, query); +} + +/// Test that deparse_raw handles DELETE statements +#[test] +fn it_deparse_raw_delete() { + let query = "DELETE FROM users WHERE id = 1"; + let result = parse_raw_iter(query).unwrap(); + let deparsed = deparse_raw(&result.protobuf).unwrap(); + assert_eq!(deparsed, query); +} + +/// Test that deparse_raw handles multiple statements +#[test] +fn it_deparse_raw_multiple_statements() { + let query = "SELECT 1; SELECT 2; SELECT 3"; + let result = parse_raw_iter(query).unwrap(); + let deparsed = deparse_raw(&result.protobuf).unwrap(); + assert_eq!(deparsed, query); +} + +// ============================================================================ +// deparse_raw method tests (on structs) +// ============================================================================ + +/// Test that ParseResult.deparse_raw() method works +#[test] +fn it_deparse_raw_method_on_parse_result() { + let query = "SELECT * FROM users WHERE id = 1"; + let result = parse_raw_iter(query).unwrap(); + // Test the method on ParseResult + let deparsed = result.deparse_raw().unwrap(); + assert_eq!(deparsed, query); +} + +/// Test that protobuf::ParseResult.deparse_raw() method works +#[test] +fn it_deparse_raw_method_on_protobuf_parse_result() { + let query = "SELECT a, b, c FROM table1 JOIN table2 ON table1.id = table2.id"; + let result = parse_raw_iter(query).unwrap(); + // Test the method on protobuf::ParseResult + let deparsed = result.protobuf.deparse_raw().unwrap(); + assert_eq!(deparsed, query); +} + +/// Test that NodeRef.deparse_raw() method works +#[test] +fn it_deparse_raw_method_on_node_ref() { + let query = "SELECT * FROM users"; + let result = parse_raw_iter(query).unwrap(); + // Get the first node (SelectStmt) + let nodes = result.protobuf.nodes(); + assert!(!nodes.is_empty()); + // Find the SelectStmt node + for (node, _depth, _context, _has_filter) in nodes { + if let pg_query::NodeRef::SelectStmt(_) = node { + let deparsed = node.deparse_raw().unwrap(); + assert_eq!(deparsed, query); + return; + } + } + panic!("SelectStmt node not found"); +} + +/// Test that deparse_raw method produces same result as deparse method +#[test] +fn it_deparse_raw_matches_deparse() { + let queries = vec![ + "SELECT 1", + "SELECT * FROM users", + "INSERT INTO t (a) VALUES (1)", + "UPDATE t SET a = 1 WHERE b = 2", + "DELETE FROM t WHERE id = 1", + "SELECT a, b FROM t1 JOIN t2 ON t1.id = t2.id WHERE t1.x > 5 ORDER BY a", + ]; + + for query in queries { + let result = parse_raw_iter(query).unwrap(); + let deparse_result = result.deparse().unwrap(); + let deparse_raw_result = result.deparse_raw().unwrap(); + assert_eq!(deparse_result, deparse_raw_result); + } +} diff --git a/tests/raw_parse_iter/ddl.rs b/tests/raw_parse_iter/ddl.rs new file mode 100644 index 0000000..12d2fb4 --- /dev/null +++ b/tests/raw_parse_iter/ddl.rs @@ -0,0 +1,423 @@ +//! DDL statement tests (CREATE, ALTER, DROP, etc.). +//! +//! These tests verify parse_raw_iter_iter correctly handles data definition language statements. + +use super::*; + +// ============================================================================ +// Basic DDL tests +// ============================================================================ + +/// Test parsing CREATE TABLE +#[test] +fn it_parses_create_table() { + let query = "CREATE TABLE test (id int, name text)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); + assert_eq!(raw_result.statement_types(), proto_result.statement_types()); + assert_eq!(raw_result.statement_types(), vec!["CreateStmt"]); +} + +/// Test parsing DROP TABLE +#[test] +fn it_parses_drop_table() { + let query = "DROP TABLE users"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); + + let mut raw_tables = raw_result.ddl_tables(); + let mut proto_tables = proto_result.ddl_tables(); + raw_tables.sort(); + proto_tables.sort(); + assert_eq!(raw_tables, proto_tables); + assert_eq!(raw_tables, vec!["users"]); +} + +/// Test parsing CREATE INDEX +#[test] +fn it_parses_create_index() { + let query = "CREATE INDEX idx_users_name ON users (name)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); + assert_eq!(raw_result.statement_types(), proto_result.statement_types()); + assert_eq!(raw_result.statement_types(), vec!["IndexStmt"]); +} + +/// Test CREATE TABLE with constraints +#[test] +fn it_parses_create_table_with_constraints() { + let query = "CREATE TABLE orders ( + id SERIAL PRIMARY KEY, + user_id INTEGER NOT NULL REFERENCES users(id), + amount DECIMAL(10, 2) CHECK (amount > 0), + status TEXT DEFAULT 'pending', + created_at TIMESTAMP DEFAULT NOW(), + UNIQUE (user_id, created_at) + )"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test CREATE TABLE AS +#[test] +fn it_parses_create_table_as() { + let query = "CREATE TABLE active_users AS SELECT * FROM users WHERE active = true"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test CREATE VIEW +#[test] +fn it_parses_create_view() { + let query = "CREATE VIEW active_users AS SELECT id, name FROM users WHERE active = true"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test CREATE MATERIALIZED VIEW +#[test] +fn it_parses_create_materialized_view() { + let query = "CREATE MATERIALIZED VIEW monthly_sales AS SELECT date_trunc('month', created_at) AS month, SUM(amount) FROM orders GROUP BY 1"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +// ============================================================================ +// ALTER TABLE tests +// ============================================================================ + +/// Test ALTER TABLE ADD COLUMN +#[test] +fn it_parses_alter_table_add_column() { + let query = "ALTER TABLE users ADD COLUMN email TEXT NOT NULL"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test ALTER TABLE DROP COLUMN +#[test] +fn it_parses_alter_table_drop_column() { + let query = "ALTER TABLE users DROP COLUMN deprecated_field"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test ALTER TABLE ADD CONSTRAINT +#[test] +fn it_parses_alter_table_add_constraint() { + let query = "ALTER TABLE orders ADD CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test ALTER TABLE RENAME +#[test] +fn it_parses_alter_table_rename() { + let query = "ALTER TABLE users RENAME TO customers"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test ALTER TABLE RENAME COLUMN +#[test] +fn it_parses_alter_table_rename_column() { + let query = "ALTER TABLE users RENAME COLUMN name TO full_name"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test ALTER TABLE OWNER +#[test] +fn it_parses_alter_owner() { + let query = "ALTER TABLE users OWNER TO postgres"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +// ============================================================================ +// INDEX tests +// ============================================================================ + +/// Test CREATE INDEX with expression +#[test] +fn it_parses_create_index_expression() { + let query = "CREATE INDEX idx_lower_email ON users (lower(email))"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test CREATE UNIQUE INDEX with WHERE +#[test] +fn it_parses_partial_unique_index() { + let query = "CREATE UNIQUE INDEX idx_active_email ON users (email) WHERE active = true"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test CREATE INDEX CONCURRENTLY +#[test] +fn it_parses_create_index_concurrently() { + let query = "CREATE INDEX CONCURRENTLY idx_name ON users (name)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +// ============================================================================ +// TRUNCATE test +// ============================================================================ + +/// Test TRUNCATE +#[test] +fn it_parses_truncate() { + let query = "TRUNCATE TABLE logs, audit_logs RESTART IDENTITY CASCADE"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +// ============================================================================ +// Sequence tests +// ============================================================================ + +/// Test CREATE SEQUENCE +#[test] +fn it_parses_create_sequence() { + let query = "CREATE SEQUENCE my_seq"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test CREATE SEQUENCE with options +#[test] +fn it_parses_create_sequence_with_options() { + let query = "CREATE SEQUENCE my_seq START WITH 100 INCREMENT BY 10 MINVALUE 1 MAXVALUE 1000 CYCLE"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test CREATE SEQUENCE IF NOT EXISTS +#[test] +fn it_parses_create_sequence_if_not_exists() { + let query = "CREATE SEQUENCE IF NOT EXISTS my_seq"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test ALTER SEQUENCE +#[test] +fn it_parses_alter_sequence() { + let query = "ALTER SEQUENCE my_seq RESTART WITH 1"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +// ============================================================================ +// Domain tests +// ============================================================================ + +/// Test CREATE DOMAIN +#[test] +fn it_parses_create_domain() { + let query = "CREATE DOMAIN positive_int AS INTEGER CHECK (VALUE > 0)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test CREATE DOMAIN with NOT NULL +#[test] +fn it_parses_create_domain_not_null() { + let query = "CREATE DOMAIN non_empty_text AS TEXT NOT NULL CHECK (VALUE <> '')"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test CREATE DOMAIN with DEFAULT +#[test] +fn it_parses_create_domain_default() { + let query = "CREATE DOMAIN my_text AS TEXT DEFAULT 'unknown'"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +// ============================================================================ +// Type tests +// ============================================================================ + +/// Test CREATE TYPE AS composite +#[test] +fn it_parses_create_composite_type() { + let query = "CREATE TYPE address AS (street TEXT, city TEXT, zip TEXT)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test CREATE TYPE AS ENUM +#[test] +fn it_parses_create_enum_type() { + let query = "CREATE TYPE status AS ENUM ('pending', 'approved', 'rejected')"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +// ============================================================================ +// Extension tests +// ============================================================================ + +/// Test CREATE EXTENSION +#[test] +fn it_parses_create_extension() { + let query = "CREATE EXTENSION IF NOT EXISTS pg_stat_statements"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test CREATE EXTENSION with schema +#[test] +fn it_parses_create_extension_with_schema() { + let query = "CREATE EXTENSION hstore WITH SCHEMA public"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +// ============================================================================ +// Publication and Subscription tests +// ============================================================================ + +/// Test CREATE PUBLICATION +#[test] +fn it_parses_create_publication() { + let query = "CREATE PUBLICATION my_pub FOR ALL TABLES"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test CREATE PUBLICATION for specific tables +#[test] +fn it_parses_create_publication_for_tables() { + let query = "CREATE PUBLICATION my_pub FOR TABLE users, orders"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test ALTER PUBLICATION +#[test] +fn it_parses_alter_publication() { + let query = "ALTER PUBLICATION my_pub ADD TABLE products"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test CREATE SUBSCRIPTION +#[test] +fn it_parses_create_subscription() { + let query = "CREATE SUBSCRIPTION my_sub CONNECTION 'host=localhost dbname=mydb' PUBLICATION my_pub"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test ALTER SUBSCRIPTION +#[test] +fn it_parses_alter_subscription() { + let query = "ALTER SUBSCRIPTION my_sub DISABLE"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +// ============================================================================ +// Trigger tests +// ============================================================================ + +/// Test CREATE TRIGGER +#[test] +fn it_parses_create_trigger() { + let query = "CREATE TRIGGER my_trigger BEFORE INSERT ON users FOR EACH ROW EXECUTE FUNCTION my_func()"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test CREATE TRIGGER AFTER UPDATE +#[test] +fn it_parses_create_trigger_after_update() { + let query = "CREATE TRIGGER audit_trigger AFTER UPDATE ON users FOR EACH ROW WHEN (OLD.* IS DISTINCT FROM NEW.*) EXECUTE FUNCTION audit_log()"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test CREATE CONSTRAINT TRIGGER +#[test] +fn it_parses_create_constraint_trigger() { + let query = "CREATE CONSTRAINT TRIGGER check_balance AFTER INSERT OR UPDATE ON accounts DEFERRABLE INITIALLY DEFERRED FOR EACH ROW EXECUTE FUNCTION check_balance()"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} diff --git a/tests/raw_parse_iter/dml.rs b/tests/raw_parse_iter/dml.rs new file mode 100644 index 0000000..d0bad70 --- /dev/null +++ b/tests/raw_parse_iter/dml.rs @@ -0,0 +1,500 @@ +//! DML statement tests (INSERT, UPDATE, DELETE). +//! +//! These tests verify parse_raw_iter_iter correctly handles data manipulation language statements. + +use super::*; + +// ============================================================================ +// Basic DML tests +// ============================================================================ + +/// Test parsing INSERT statement +#[test] +fn it_parses_insert() { + let query = "INSERT INTO users (name) VALUES ('test')"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); + assert_eq!(deparse_raw_iter(&raw_result.protobuf).unwrap(), query); + + let mut raw_tables = raw_result.dml_tables(); + let mut proto_tables = proto_result.dml_tables(); + raw_tables.sort(); + proto_tables.sort(); + assert_eq!(raw_tables, proto_tables); + assert_eq!(raw_tables, vec!["users"]); +} + +/// Test parsing UPDATE statement +#[test] +fn it_parses_update() { + let query = "UPDATE users SET name = 'bob' WHERE id = 1"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); + assert_eq!(deparse_raw_iter(&raw_result.protobuf).unwrap(), query); + + let mut raw_tables = raw_result.dml_tables(); + let mut proto_tables = proto_result.dml_tables(); + raw_tables.sort(); + proto_tables.sort(); + assert_eq!(raw_tables, proto_tables); + assert_eq!(raw_tables, vec!["users"]); +} + +/// Test parsing DELETE statement +#[test] +fn it_parses_delete() { + let query = "DELETE FROM users WHERE id = 1"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); + assert_eq!(deparse_raw_iter(&raw_result.protobuf).unwrap(), query); + + let mut raw_tables = raw_result.dml_tables(); + let mut proto_tables = proto_result.dml_tables(); + raw_tables.sort(); + proto_tables.sort(); + assert_eq!(raw_tables, proto_tables); + assert_eq!(raw_tables, vec!["users"]); +} + +// ============================================================================ +// INSERT variations +// ============================================================================ + +/// Test parsing INSERT with ON CONFLICT +#[test] +fn it_parses_insert_on_conflict() { + let query = "INSERT INTO users (id, name) VALUES (1, 'test') ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); + // PostgreSQL's deparser normalizes EXCLUDED to lowercase, so compare case-insensitively + assert_eq!(deparse_raw_iter(&raw_result.protobuf).unwrap().to_lowercase(), query.to_lowercase()); + + let raw_tables = raw_result.dml_tables(); + let proto_tables = proto_result.dml_tables(); + assert_eq!(raw_tables, proto_tables); + assert_eq!(raw_tables, vec!["users"]); +} + +/// Test parsing INSERT with RETURNING +#[test] +fn it_parses_insert_returning() { + let query = "INSERT INTO users (name) VALUES ('test') RETURNING id"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); + assert_eq!(deparse_raw_iter(&raw_result.protobuf).unwrap(), query); +} + +/// Test INSERT with multiple tuples +#[test] +fn it_parses_insert_multiple_rows() { + let query = "INSERT INTO users (name, email, age) VALUES ('Alice', 'alice@example.com', 25), ('Bob', 'bob@example.com', 30), ('Charlie', 'charlie@example.com', 35)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); + assert_eq!(deparse_raw_iter(&raw_result.protobuf).unwrap(), query); +} + +/// Test INSERT ... SELECT +#[test] +fn it_parses_insert_select() { + let query = "INSERT INTO archived_users (id, name, email) SELECT id, name, email FROM users WHERE deleted_at IS NOT NULL"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test INSERT ... SELECT with complex query +#[test] +fn it_parses_insert_select_complex() { + let query = "INSERT INTO monthly_stats (month, user_count, order_count, total_revenue) + SELECT date_trunc('month', created_at) AS month, + COUNT(DISTINCT user_id), + COUNT(*), + SUM(amount) + FROM orders + WHERE created_at >= '2023-01-01' + GROUP BY date_trunc('month', created_at)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test INSERT with CTE +#[test] +fn it_parses_insert_with_cte() { + let query = "WITH new_data AS ( + SELECT name, email FROM temp_imports WHERE valid = true + ) + INSERT INTO users (name, email) SELECT name, email FROM new_data"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test INSERT with DEFAULT values +#[test] +fn it_parses_insert_default_values() { + let query = "INSERT INTO users (name, created_at) VALUES ('test', DEFAULT)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test INSERT with ON CONFLICT DO NOTHING +#[test] +fn it_parses_insert_on_conflict_do_nothing() { + let query = "INSERT INTO users (id, name) VALUES (1, 'test') ON CONFLICT (id) DO NOTHING"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test INSERT with ON CONFLICT with WHERE clause +#[test] +fn it_parses_insert_on_conflict_with_where() { + let query = "INSERT INTO users (id, name, updated_at) VALUES (1, 'test', NOW()) + ON CONFLICT (id) DO UPDATE SET name = EXCLUDED.name, updated_at = EXCLUDED.updated_at + WHERE users.updated_at < EXCLUDED.updated_at"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test INSERT with multiple columns in ON CONFLICT +#[test] +fn it_parses_insert_on_conflict_multiple_columns() { + let query = "INSERT INTO user_settings (user_id, key, value) VALUES (1, 'theme', 'dark') + ON CONFLICT (user_id, key) DO UPDATE SET value = EXCLUDED.value"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test INSERT with RETURNING multiple columns +#[test] +fn it_parses_insert_returning_multiple() { + let query = "INSERT INTO users (name, email) VALUES ('test', 'test@example.com') RETURNING id, created_at, name"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test INSERT with subquery in VALUES +#[test] +fn it_parses_insert_with_subquery_value() { + let query = "INSERT INTO orders (user_id, total) VALUES ((SELECT id FROM users WHERE email = 'test@example.com'), 100.00)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test INSERT with OVERRIDING +#[test] +fn it_parses_insert_overriding() { + let query = "INSERT INTO users (id, name) OVERRIDING SYSTEM VALUE VALUES (1, 'test')"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +// ============================================================================ +// Complex UPDATE tests +// ============================================================================ + +/// Test UPDATE with multiple columns +#[test] +fn it_parses_update_multiple_columns() { + let query = "UPDATE users SET name = 'new_name', email = 'new@example.com', updated_at = NOW() WHERE id = 1"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test UPDATE with subquery in SET +#[test] +fn it_parses_update_with_subquery_set() { + let query = "UPDATE orders SET total = (SELECT SUM(price * quantity) FROM order_items WHERE order_id = orders.id) WHERE id = 1"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test UPDATE with FROM clause (PostgreSQL-specific JOIN update) +#[test] +fn it_parses_update_from() { + let query = "UPDATE orders o SET status = 'shipped', shipped_at = NOW() + FROM shipments s + WHERE o.id = s.order_id AND s.status = 'delivered'"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test UPDATE with FROM and multiple tables +#[test] +fn it_parses_update_from_multiple_tables() { + let query = "UPDATE products p SET price = p.price * (1 + d.percentage / 100) + FROM discounts d + JOIN categories c ON d.category_id = c.id + WHERE p.category_id = c.id AND d.active = true"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test UPDATE with CTE +#[test] +fn it_parses_update_with_cte() { + let query = "WITH inactive_users AS ( + SELECT id FROM users WHERE last_login < NOW() - INTERVAL '1 year' + ) + UPDATE users SET status = 'inactive' WHERE id IN (SELECT id FROM inactive_users)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test UPDATE with RETURNING +#[test] +fn it_parses_update_returning() { + let query = "UPDATE users SET name = 'updated' WHERE id = 1 RETURNING id, name, updated_at"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test UPDATE with complex WHERE clause +#[test] +fn it_parses_update_complex_where() { + let query = "UPDATE orders SET status = 'cancelled' + WHERE created_at < NOW() - INTERVAL '30 days' + AND status = 'pending' + AND NOT EXISTS (SELECT 1 FROM payments WHERE payments.order_id = orders.id)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test UPDATE with row value comparison +#[test] +fn it_parses_update_row_comparison() { + let query = "UPDATE users SET (name, email) = ('new_name', 'new@example.com') WHERE id = 1"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test UPDATE with CASE expression +#[test] +fn it_parses_update_with_case() { + let query = "UPDATE products SET price = CASE + WHEN category = 'electronics' THEN price * 0.9 + WHEN category = 'clothing' THEN price * 0.8 + ELSE price * 0.95 + END + WHERE sale_active = true"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test UPDATE with array operations +#[test] +fn it_parses_update_array() { + let query = "UPDATE users SET tags = array_append(tags, 'verified') WHERE id = 1"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +// ============================================================================ +// Complex DELETE tests +// ============================================================================ + +/// Test DELETE with subquery in WHERE +#[test] +fn it_parses_delete_with_subquery() { + let query = "DELETE FROM orders WHERE user_id IN (SELECT id FROM users WHERE status = 'deleted')"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test DELETE with USING clause (PostgreSQL-specific JOIN delete) +#[test] +fn it_parses_delete_using() { + let query = "DELETE FROM order_items oi USING orders o + WHERE oi.order_id = o.id AND o.status = 'cancelled'"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test DELETE with USING and multiple tables +#[test] +fn it_parses_delete_using_multiple_tables() { + let query = "DELETE FROM notifications n + USING users u, user_settings s + WHERE n.user_id = u.id + AND u.id = s.user_id + AND s.key = 'email_notifications' + AND s.value = 'false'"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test DELETE with CTE +#[test] +fn it_parses_delete_with_cte() { + let query = "WITH old_orders AS ( + SELECT id FROM orders WHERE created_at < NOW() - INTERVAL '5 years' + ) + DELETE FROM order_items WHERE order_id IN (SELECT id FROM old_orders)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test DELETE with RETURNING +#[test] +fn it_parses_delete_returning() { + let query = "DELETE FROM users WHERE id = 1 RETURNING id, name, email"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test DELETE with EXISTS +#[test] +fn it_parses_delete_with_exists() { + let query = "DELETE FROM products p + WHERE NOT EXISTS (SELECT 1 FROM order_items oi WHERE oi.product_id = p.id) + AND p.created_at < NOW() - INTERVAL '1 year'"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test DELETE with complex boolean conditions +#[test] +fn it_parses_delete_complex_conditions() { + let query = "DELETE FROM logs + WHERE (level = 'debug' AND created_at < NOW() - INTERVAL '7 days') + OR (level = 'info' AND created_at < NOW() - INTERVAL '30 days') + OR (level IN ('warning', 'error') AND created_at < NOW() - INTERVAL '90 days')"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test DELETE with ONLY +#[test] +fn it_parses_delete_only() { + let query = "DELETE FROM ONLY parent_table WHERE id = 1"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +// ============================================================================ +// Combined DML with CTEs +// ============================================================================ + +/// Test data modification CTE (INSERT in CTE) +#[test] +fn it_parses_insert_cte_returning() { + let query = "WITH inserted AS ( + INSERT INTO users (name, email) VALUES ('test', 'test@example.com') RETURNING id, name + ) + SELECT * FROM inserted"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test UPDATE in CTE with final SELECT +#[test] +fn it_parses_update_cte_returning() { + let query = "WITH updated AS ( + UPDATE users SET last_login = NOW() WHERE id = 1 RETURNING id, name, last_login + ) + SELECT * FROM updated"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test DELETE in CTE with final SELECT +#[test] +fn it_parses_delete_cte_returning() { + let query = "WITH deleted AS ( + DELETE FROM expired_sessions WHERE expires_at < NOW() RETURNING user_id + ) + SELECT COUNT(*) FROM deleted"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test chained CTEs with multiple DML operations +#[test] +fn it_parses_chained_dml_ctes() { + let query = "WITH + to_archive AS ( + SELECT id FROM users WHERE last_login < NOW() - INTERVAL '2 years' + ), + archived AS ( + INSERT INTO archived_users SELECT * FROM users WHERE id IN (SELECT id FROM to_archive) RETURNING id + ), + deleted AS ( + DELETE FROM users WHERE id IN (SELECT id FROM archived) RETURNING id + ) + SELECT COUNT(*) as archived_count FROM deleted"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} diff --git a/tests/raw_parse_iter/expressions.rs b/tests/raw_parse_iter/expressions.rs new file mode 100644 index 0000000..f0f9b25 --- /dev/null +++ b/tests/raw_parse_iter/expressions.rs @@ -0,0 +1,494 @@ +//! Expression tests: literals, type casts, arrays, JSON, operators. +//! +//! These tests verify parse_raw_iter_iter correctly handles various expressions. + +use super::*; + +// ============================================================================ +// Literal value tests +// ============================================================================ + +/// Test parsing float with leading dot +#[test] +fn it_parses_floats_with_leading_dot() { + let query = "SELECT .1"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + // Full structural equality check + assert_eq!(raw_result.protobuf, proto_result.protobuf); + + // Verify the float value + let raw_const = get_first_const(&raw_result.protobuf).expect("should have const"); + let proto_const = get_first_const(&proto_result.protobuf).expect("should have const"); + assert_eq!(raw_const, proto_const); +} + +/// Test parsing bit string in hex notation +#[test] +fn it_parses_bit_strings_hex() { + let query = "SELECT X'EFFF'"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + // Full structural equality check + assert_eq!(raw_result.protobuf, proto_result.protobuf); + + // Verify the bit string value + let raw_const = get_first_const(&raw_result.protobuf).expect("should have const"); + let proto_const = get_first_const(&proto_result.protobuf).expect("should have const"); + assert_eq!(raw_const, proto_const); +} + +/// Test parsing real-world query with multiple joins +#[test] +fn it_parses_real_world_query() { + let query = " + SELECT memory_total_bytes, memory_free_bytes, memory_pagecache_bytes, + (memory_swap_total_bytes - memory_swap_free_bytes) AS swap + FROM snapshots s JOIN system_snapshots ON (snapshot_id = s.id) + WHERE s.database_id = 1 AND s.collected_at BETWEEN '2021-01-01' AND '2021-12-31' + ORDER BY collected_at"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + // Full structural equality check + assert_eq!(raw_result.protobuf, proto_result.protobuf); + + // Verify tables + let mut raw_tables = raw_result.tables(); + let mut proto_tables = proto_result.tables(); + raw_tables.sort(); + proto_tables.sort(); + assert_eq!(raw_tables, proto_tables); + assert_eq!(raw_tables, vec!["snapshots", "system_snapshots"]); +} +// ============================================================================ +// A_Const value extraction tests +// ============================================================================ + +/// Test that parse_raw extracts integer values correctly and matches parse +#[test] +fn it_extracts_integer_const() { + let query = "SELECT 42"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + // Full structural equality check + assert_eq!(raw_result.protobuf, proto_result.protobuf); + + let raw_const = get_first_const(&raw_result.protobuf).expect("Should have A_Const"); + let proto_const = get_first_const(&proto_result.protobuf).expect("Should have A_Const"); + + assert_eq!(raw_const, proto_const); + assert!(!raw_const.isnull); + match &raw_const.val { + Some(a_const::Val::Ival(int_val)) => { + assert_eq!(int_val.ival, 42); + } + other => panic!("Expected Ival, got {:?}", other), + } +} + +/// Test that parse_raw extracts negative integer values correctly +#[test] +fn it_extracts_negative_integer_const() { + let query = "SELECT -123"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + // Full structural equality check + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test that parse_raw extracts string values correctly and matches parse +#[test] +fn it_extracts_string_const() { + let query = "SELECT 'hello world'"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + // Full structural equality check + assert_eq!(raw_result.protobuf, proto_result.protobuf); + + let raw_const = get_first_const(&raw_result.protobuf).expect("Should have A_Const"); + let proto_const = get_first_const(&proto_result.protobuf).expect("Should have A_Const"); + + assert_eq!(raw_const, proto_const); + assert!(!raw_const.isnull); + match &raw_const.val { + Some(a_const::Val::Sval(str_val)) => { + assert_eq!(str_val.sval, "hello world"); + } + other => panic!("Expected Sval, got {:?}", other), + } +} + +/// Test that parse_raw extracts float values correctly and matches parse +#[test] +fn it_extracts_float_const() { + let query = "SELECT 3.14159"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + // Full structural equality check + assert_eq!(raw_result.protobuf, proto_result.protobuf); + + let raw_const = get_first_const(&raw_result.protobuf).expect("Should have A_Const"); + let proto_const = get_first_const(&proto_result.protobuf).expect("Should have A_Const"); + + assert_eq!(raw_const, proto_const); + assert!(!raw_const.isnull); + match &raw_const.val { + Some(a_const::Val::Fval(float_val)) => { + assert_eq!(float_val.fval, "3.14159"); + } + other => panic!("Expected Fval, got {:?}", other), + } +} + +/// Test that parse_raw extracts boolean TRUE correctly and matches parse +#[test] +fn it_extracts_boolean_true_const() { + let query = "SELECT TRUE"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + // Full structural equality check + assert_eq!(raw_result.protobuf, proto_result.protobuf); + + let raw_const = get_first_const(&raw_result.protobuf).expect("Should have A_Const"); + let proto_const = get_first_const(&proto_result.protobuf).expect("Should have A_Const"); + + assert_eq!(raw_const, proto_const); + assert!(!raw_const.isnull); + match &raw_const.val { + Some(a_const::Val::Boolval(bool_val)) => { + assert!(bool_val.boolval); + } + other => panic!("Expected Boolval(true), got {:?}", other), + } +} + +/// Test that parse_raw extracts boolean FALSE correctly and matches parse +#[test] +fn it_extracts_boolean_false_const() { + let query = "SELECT FALSE"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + // Full structural equality check + assert_eq!(raw_result.protobuf, proto_result.protobuf); + + let raw_const = get_first_const(&raw_result.protobuf).expect("Should have A_Const"); + let proto_const = get_first_const(&proto_result.protobuf).expect("Should have A_Const"); + + assert_eq!(raw_const, proto_const); + assert!(!raw_const.isnull); + match &raw_const.val { + Some(a_const::Val::Boolval(bool_val)) => { + assert!(!bool_val.boolval); + } + other => panic!("Expected Boolval(false), got {:?}", other), + } +} + +/// Test that parse_raw extracts NULL correctly and matches parse +#[test] +fn it_extracts_null_const() { + let query = "SELECT NULL"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + // Full structural equality check + assert_eq!(raw_result.protobuf, proto_result.protobuf); + + let raw_const = get_first_const(&raw_result.protobuf).expect("Should have A_Const"); + let proto_const = get_first_const(&proto_result.protobuf).expect("Should have A_Const"); + + assert_eq!(raw_const, proto_const); + assert!(raw_const.isnull); + assert!(raw_const.val.is_none()); +} + +/// Test that parse_raw extracts bit string values correctly and matches parse +#[test] +fn it_extracts_bit_string_const() { + let query = "SELECT B'1010'"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + // Full structural equality check + assert_eq!(raw_result.protobuf, proto_result.protobuf); + + let raw_const = get_first_const(&raw_result.protobuf).expect("Should have A_Const"); + let proto_const = get_first_const(&proto_result.protobuf).expect("Should have A_Const"); + + assert_eq!(raw_const, proto_const); + assert!(!raw_const.isnull); + match &raw_const.val { + Some(a_const::Val::Bsval(bit_val)) => { + assert_eq!(bit_val.bsval, "b1010"); + } + other => panic!("Expected Bsval, got {:?}", other), + } +} + +/// Test that parse_raw extracts hex bit string correctly and matches parse +#[test] +fn it_extracts_hex_bit_string_const() { + let query = "SELECT X'FF'"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + // Full structural equality check + assert_eq!(raw_result.protobuf, proto_result.protobuf); + + let raw_const = get_first_const(&raw_result.protobuf).expect("Should have A_Const"); + let proto_const = get_first_const(&proto_result.protobuf).expect("Should have A_Const"); + + assert_eq!(raw_const, proto_const); + assert!(!raw_const.isnull); + match &raw_const.val { + Some(a_const::Val::Bsval(bit_val)) => { + assert_eq!(bit_val.bsval, "xFF"); + } + other => panic!("Expected Bsval, got {:?}", other), + } +} +// ============================================================================ +// Expression tests +// ============================================================================ + +/// Test COALESCE +#[test] +fn it_parses_coalesce() { + let query = "SELECT COALESCE(nickname, name, 'Unknown') FROM users"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test NULLIF +#[test] +fn it_parses_nullif() { + let query = "SELECT NULLIF(status, 'deleted') FROM records"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test GREATEST and LEAST +#[test] +fn it_parses_greatest_least() { + let query = "SELECT GREATEST(a, b, c), LEAST(x, y, z) FROM t"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test IS NULL and IS NOT NULL +#[test] +fn it_parses_null_tests() { + let query = "SELECT * FROM users WHERE deleted_at IS NULL AND email IS NOT NULL"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test IS DISTINCT FROM +#[test] +fn it_parses_is_distinct_from() { + let query = "SELECT * FROM t WHERE a IS DISTINCT FROM b"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test BETWEEN +#[test] +fn it_parses_between() { + let query = "SELECT * FROM events WHERE created_at BETWEEN '2023-01-01' AND '2023-12-31'"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test LIKE and ILIKE +#[test] +fn it_parses_like_ilike() { + let query = "SELECT * FROM users WHERE name LIKE 'John%' OR email ILIKE '%@EXAMPLE.COM'"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test SIMILAR TO +#[test] +fn it_parses_similar_to() { + let query = "SELECT * FROM products WHERE name SIMILAR TO '%(phone|tablet)%'"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test complex boolean expressions +#[test] +fn it_parses_complex_boolean() { + let query = "SELECT * FROM users WHERE (active = true AND verified = true) OR (role = 'admin' AND NOT suspended)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} +// ============================================================================ +// Type cast tests +// ============================================================================ + +/// Test PostgreSQL-style type cast +#[test] +fn it_parses_pg_type_cast() { + let query = "SELECT '123'::integer, '2023-01-01'::date, 'true'::boolean"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test SQL-style CAST +#[test] +fn it_parses_sql_cast() { + let query = "SELECT CAST('123' AS integer), CAST(created_at AS date) FROM t"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test array type cast +#[test] +fn it_parses_array_cast() { + let query = "SELECT ARRAY[1, 2, 3]::text[]"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} +// ============================================================================ +// Array and JSON tests +// ============================================================================ + +/// Test array constructor +#[test] +fn it_parses_array_constructor() { + let query = "SELECT ARRAY[1, 2, 3], ARRAY['a', 'b', 'c']"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test array subscript +#[test] +fn it_parses_array_subscript() { + let query = "SELECT tags[1], matrix[1][2] FROM t"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test array slice +#[test] +fn it_parses_array_slice() { + let query = "SELECT arr[2:4], arr[:3], arr[2:] FROM t"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test unnest +#[test] +fn it_parses_unnest() { + let query = "SELECT unnest(ARRAY[1, 2, 3])"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test JSON operators +#[test] +fn it_parses_json_operators() { + let query = "SELECT data->'name', data->>'email', data#>'{address,city}' FROM users"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test JSONB containment +#[test] +fn it_parses_jsonb_containment() { + let query = "SELECT * FROM products WHERE metadata @> '{\"featured\": true}'"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} +// ============================================================================ +// Parameter placeholder tests +// ============================================================================ + +/// Test positional parameters +#[test] +fn it_parses_positional_params() { + let query = "SELECT * FROM users WHERE id = $1 AND status = $2"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test parameters in INSERT +#[test] +fn it_parses_params_in_insert() { + let query = "INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +// ============================================================================ +// SQL Value Function tests +// ============================================================================ + +/// Test CURRENT_TIMESTAMP (was causing infinite recursion) +#[test] +fn it_parses_current_timestamp() { + let query = "INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES (6, 1, 37553, -2309, CURRENT_TIMESTAMP)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test other SQL value functions +#[test] +fn it_parses_sql_value_functions() { + let query = "SELECT CURRENT_DATE, CURRENT_TIME, CURRENT_TIMESTAMP, LOCALTIME, LOCALTIMESTAMP, CURRENT_USER, CURRENT_CATALOG, CURRENT_SCHEMA"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} diff --git a/tests/raw_parse_iter/mod.rs b/tests/raw_parse_iter/mod.rs new file mode 100644 index 0000000..88c1903 --- /dev/null +++ b/tests/raw_parse_iter/mod.rs @@ -0,0 +1,57 @@ +//! Raw parse iter tests split into multiple modules for maintainability. +//! +//! This module contains tests that verify parse_raw_iter produces equivalent +//! results to parse (protobuf-based parsing). + +pub use pg_query::protobuf::{a_const, node, ParseResult as ProtobufParseResult}; +pub use pg_query::{deparse_raw, parse, parse_raw_iter, deparse_raw_iter, Error}; + +/// Helper to extract AConst from a SELECT statement's first target +pub fn get_first_const(result: &ProtobufParseResult) -> Option<&pg_query::protobuf::AConst> { + let stmt = result.stmts.first()?; + let raw_stmt = stmt.stmt.as_ref()?; + let node = raw_stmt.node.as_ref()?; + + if let node::Node::SelectStmt(select) = node { + let target = select.target_list.first()?; + if let Some(node::Node::ResTarget(res_target)) = target.node.as_ref() { + if let Some(val_node) = res_target.val.as_ref() { + if let Some(node::Node::AConst(aconst)) = val_node.node.as_ref() { + return Some(aconst); + } + } + } + } + None +} + +/// Helper macro for simple parse comparison tests with deparse verification +#[macro_export] +macro_rules! parse_iter_test { + ($query:expr) => {{ + let raw_result = parse_raw_iter($query).unwrap(); + let proto_result = parse($query).unwrap(); + assert_eq!(raw_result.protobuf, proto_result.protobuf); + // Verify that deparse_raw produces the original query + let deparsed = deparse_raw(&raw_result.protobuf).unwrap(); + assert_eq!(deparsed, $query); + }}; +} + +/// Helper macro for parse tests where the deparsed output may differ from input +/// (e.g., when PostgreSQL normalizes the SQL syntax) +#[macro_export] +macro_rules! parse_iter_test_no_deparse_check { + ($query:expr) => {{ + let raw_result = parse_raw_iter($query).unwrap(); + let proto_result = parse($query).unwrap(); + assert_eq!(raw_result.protobuf, proto_result.protobuf); + }}; +} + +pub mod basic; +pub mod ddl; +pub mod dml; +pub mod expressions; +pub mod select; +pub mod statements; diff --git a/tests/raw_parse_iter/select.rs b/tests/raw_parse_iter/select.rs new file mode 100644 index 0000000..5de4ba7 --- /dev/null +++ b/tests/raw_parse_iter/select.rs @@ -0,0 +1,862 @@ +//! Complex SELECT tests: JOINs, subqueries, CTEs, window functions, set operations. +//! +//! These tests verify parse_raw_iter_iter correctly handles complex SELECT statements. + +use super::*; + +// ============================================================================ +// JOIN and complex SELECT tests +// ============================================================================ + +/// Test parsing SELECT with JOIN +#[test] +fn it_parses_join() { + let query = "SELECT * FROM users u JOIN orders o ON u.id = o.user_id"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + // Full structural equality check + assert_eq!(raw_result.protobuf, proto_result.protobuf); + // Verify deparse produces original query + assert_eq!(deparse_raw_iter(&raw_result.protobuf).unwrap(), query); + + // Verify tables are extracted correctly + let mut raw_tables = raw_result.tables(); + let mut proto_tables = proto_result.tables(); + raw_tables.sort(); + proto_tables.sort(); + assert_eq!(raw_tables, proto_tables); + assert_eq!(raw_tables, vec!["orders", "users"]); +} + +/// Test parsing UNION query +#[test] +fn it_parses_union() { + let query = "SELECT id FROM users UNION SELECT id FROM admins"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + // Full structural equality check + assert_eq!(raw_result.protobuf, proto_result.protobuf); + assert_eq!(deparse_raw_iter(&raw_result.protobuf).unwrap(), query); + + // Verify tables from both sides of UNION + let mut raw_tables = raw_result.tables(); + let mut proto_tables = proto_result.tables(); + raw_tables.sort(); + proto_tables.sort(); + assert_eq!(raw_tables, proto_tables); + assert_eq!(raw_tables, vec!["admins", "users"]); +} + +/// Test parsing WITH clause (CTE) +#[test] +fn it_parses_cte() { + let query = "WITH active_users AS (SELECT * FROM users WHERE active = true) SELECT * FROM active_users"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + // Full structural equality check + assert_eq!(raw_result.protobuf, proto_result.protobuf); + assert_eq!(deparse_raw_iter(&raw_result.protobuf).unwrap(), query); + + // Verify CTE names match + assert_eq!(raw_result.cte_names, proto_result.cte_names); + assert!(raw_result.cte_names.contains(&"active_users".to_string())); + + // Verify tables (should only include actual tables, not CTEs) + let mut raw_tables = raw_result.tables(); + let mut proto_tables = proto_result.tables(); + raw_tables.sort(); + proto_tables.sort(); + assert_eq!(raw_tables, proto_tables); + assert_eq!(raw_tables, vec!["users"]); +} + +/// Test parsing subquery in SELECT +#[test] +fn it_parses_subquery() { + let query = "SELECT * FROM users WHERE id IN (SELECT user_id FROM orders)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + // Full structural equality check + assert_eq!(raw_result.protobuf, proto_result.protobuf); + assert_eq!(deparse_raw_iter(&raw_result.protobuf).unwrap(), query); + + // Verify all tables are found + let mut raw_tables = raw_result.tables(); + let mut proto_tables = proto_result.tables(); + raw_tables.sort(); + proto_tables.sort(); + assert_eq!(raw_tables, proto_tables); + assert_eq!(raw_tables, vec!["orders", "users"]); +} + +/// Test parsing aggregate functions +#[test] +fn it_parses_aggregates() { + let query = "SELECT count(*), sum(amount), avg(price) FROM orders"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + // Full structural equality check + assert_eq!(raw_result.protobuf, proto_result.protobuf); + assert_eq!(deparse_raw_iter(&raw_result.protobuf).unwrap(), query); + + // Verify functions are extracted correctly + let mut raw_funcs = raw_result.functions(); + let mut proto_funcs = proto_result.functions(); + raw_funcs.sort(); + proto_funcs.sort(); + assert_eq!(raw_funcs, proto_funcs); + assert!(raw_funcs.contains(&"count".to_string())); + assert!(raw_funcs.contains(&"sum".to_string())); + assert!(raw_funcs.contains(&"avg".to_string())); +} + +/// Test parsing CASE expression +#[test] +fn it_parses_case_expression() { + let query = "SELECT CASE WHEN x > 0 THEN 'positive' ELSE 'non-positive' END FROM t"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + // Full structural equality check + assert_eq!(raw_result.protobuf, proto_result.protobuf); + assert_eq!(deparse_raw_iter(&raw_result.protobuf).unwrap(), query); + + // Verify table is found + let raw_tables = raw_result.tables(); + let proto_tables = proto_result.tables(); + assert_eq!(raw_tables, proto_tables); + assert_eq!(raw_tables, vec!["t"]); +} + +/// Test parsing complex SELECT with multiple clauses +#[test] +fn it_parses_complex_select() { + let query = "SELECT u.id, u.name, count(*) AS order_count FROM users u LEFT JOIN orders o ON u.id = o.user_id WHERE u.active = true GROUP BY u.id, u.name HAVING count(*) > 0 ORDER BY order_count DESC LIMIT 10"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + // Full structural equality check + assert_eq!(raw_result.protobuf, proto_result.protobuf); + + // Verify tables + let mut raw_tables = raw_result.tables(); + let mut proto_tables = proto_result.tables(); + raw_tables.sort(); + proto_tables.sort(); + assert_eq!(raw_tables, proto_tables); + assert_eq!(raw_tables, vec!["orders", "users"]); + + // Verify functions + let mut raw_funcs = raw_result.functions(); + let mut proto_funcs = proto_result.functions(); + raw_funcs.sort(); + proto_funcs.sort(); + assert_eq!(raw_funcs, proto_funcs); + assert!(raw_funcs.contains(&"count".to_string())); +} + +// ============================================================================ +// Advanced JOIN tests +// ============================================================================ + +/// Test LEFT JOIN +#[test] +fn it_parses_left_join() { + let query = "SELECT * FROM users u LEFT JOIN orders o ON u.id = o.user_id"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); + + let mut raw_tables = raw_result.tables(); + raw_tables.sort(); + assert_eq!(raw_tables, vec!["orders", "users"]); +} + +/// Test RIGHT JOIN +#[test] +fn it_parses_right_join() { + let query = "SELECT * FROM users u RIGHT JOIN orders o ON u.id = o.user_id"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test FULL OUTER JOIN +#[test] +fn it_parses_full_outer_join() { + let query = "SELECT * FROM users u FULL OUTER JOIN orders o ON u.id = o.user_id"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test CROSS JOIN +#[test] +fn it_parses_cross_join() { + let query = "SELECT * FROM users CROSS JOIN products"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); + + let mut raw_tables = raw_result.tables(); + raw_tables.sort(); + assert_eq!(raw_tables, vec!["products", "users"]); +} + +/// Test NATURAL JOIN +#[test] +fn it_parses_natural_join() { + let query = "SELECT * FROM users NATURAL JOIN user_profiles"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test multiple JOINs +#[test] +fn it_parses_multiple_joins() { + let query = "SELECT u.name, o.id, p.name FROM users u + JOIN orders o ON u.id = o.user_id + JOIN order_items oi ON o.id = oi.order_id + JOIN products p ON oi.product_id = p.id"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); + + let mut raw_tables = raw_result.tables(); + raw_tables.sort(); + assert_eq!(raw_tables, vec!["order_items", "orders", "products", "users"]); +} + +/// Test JOIN with USING clause +#[test] +fn it_parses_join_using() { + let query = "SELECT * FROM users u JOIN user_profiles p USING (user_id)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test LATERAL JOIN +#[test] +fn it_parses_lateral_join() { + let query = "SELECT * FROM users u, LATERAL (SELECT * FROM orders o WHERE o.user_id = u.id LIMIT 3) AS recent_orders"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); + + let mut raw_tables = raw_result.tables(); + raw_tables.sort(); + assert_eq!(raw_tables, vec!["orders", "users"]); +} +// ============================================================================ +// Advanced subquery tests +// ============================================================================ + +/// Test correlated subquery +#[test] +fn it_parses_correlated_subquery() { + let query = "SELECT * FROM users u WHERE EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); + + let mut raw_tables = raw_result.tables(); + raw_tables.sort(); + assert_eq!(raw_tables, vec!["orders", "users"]); +} + +/// Test NOT EXISTS subquery +#[test] +fn it_parses_not_exists_subquery() { + let query = "SELECT * FROM users u WHERE NOT EXISTS (SELECT 1 FROM banned b WHERE b.user_id = u.id)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test scalar subquery in SELECT +#[test] +fn it_parses_scalar_subquery() { + let query = "SELECT u.name, (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) AS order_count FROM users u"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test subquery in FROM clause +#[test] +fn it_parses_derived_table() { + let query = "SELECT * FROM (SELECT id, name FROM users WHERE active = true) AS active_users"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test ANY/SOME subquery +#[test] +fn it_parses_any_subquery() { + let query = "SELECT * FROM products WHERE price > ANY (SELECT avg_price FROM categories)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test ALL subquery +#[test] +fn it_parses_all_subquery() { + let query = "SELECT * FROM products WHERE price > ALL (SELECT price FROM discounted_products)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} +// ============================================================================ +// Window function tests +// ============================================================================ + +/// Test basic window function +#[test] +fn it_parses_window_function() { + let query = "SELECT name, salary, ROW_NUMBER() OVER (ORDER BY salary DESC) AS rank FROM employees"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test window function with PARTITION BY +#[test] +fn it_parses_window_function_partition() { + let query = "SELECT department, name, salary, RANK() OVER (PARTITION BY department ORDER BY salary DESC) AS dept_rank FROM employees"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test window function with frame clause +#[test] +fn it_parses_window_function_frame() { + let query = "SELECT date, amount, SUM(amount) OVER (ORDER BY date ROWS BETWEEN 2 PRECEDING AND CURRENT ROW) AS moving_sum FROM transactions"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test named window +#[test] +fn it_parses_named_window() { + let query = "SELECT name, salary, SUM(salary) OVER w, AVG(salary) OVER w FROM employees WINDOW w AS (PARTITION BY department ORDER BY salary)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test LAG and LEAD functions +#[test] +fn it_parses_lag_lead() { + let query = + "SELECT date, price, LAG(price, 1) OVER (ORDER BY date) AS prev_price, LEAD(price, 1) OVER (ORDER BY date) AS next_price FROM stock_prices"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} +// ============================================================================ +// CTE variations +// ============================================================================ + +/// Test multiple CTEs +#[test] +fn it_parses_multiple_ctes() { + let query = "WITH + active_users AS (SELECT * FROM users WHERE active = true), + premium_users AS (SELECT * FROM active_users WHERE plan = 'premium') + SELECT * FROM premium_users"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); + assert!(raw_result.cte_names.contains(&"active_users".to_string())); + assert!(raw_result.cte_names.contains(&"premium_users".to_string())); +} + +/// Test recursive CTE +#[test] +fn it_parses_recursive_cte() { + let query = "WITH RECURSIVE subordinates AS ( + SELECT id, name, manager_id FROM employees WHERE id = 1 + UNION ALL + SELECT e.id, e.name, e.manager_id FROM employees e INNER JOIN subordinates s ON e.manager_id = s.id + ) SELECT * FROM subordinates"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test CTE with column list +#[test] +fn it_parses_cte_with_columns() { + let query = "WITH regional_sales(region, total) AS (SELECT region, SUM(amount) FROM orders GROUP BY region) SELECT * FROM regional_sales"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test CTE with MATERIALIZED +#[test] +fn it_parses_cte_materialized() { + let query = "WITH t AS MATERIALIZED (SELECT * FROM large_table WHERE x > 100) SELECT * FROM t"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} +// ============================================================================ +// Set operations +// ============================================================================ + +/// Test INTERSECT +#[test] +fn it_parses_intersect() { + let query = "SELECT id FROM users INTERSECT SELECT user_id FROM orders"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test EXCEPT +#[test] +fn it_parses_except() { + let query = "SELECT id FROM users EXCEPT SELECT user_id FROM banned_users"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test UNION ALL +#[test] +fn it_parses_union_all() { + let query = "SELECT name FROM users UNION ALL SELECT name FROM admins"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test compound set operations +#[test] +fn it_parses_compound_set_operations() { + let query = "(SELECT id FROM a UNION SELECT id FROM b) INTERSECT SELECT id FROM c"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} +// ============================================================================ +// GROUP BY variations +// ============================================================================ + +/// Test GROUP BY ROLLUP +#[test] +fn it_parses_group_by_rollup() { + let query = "SELECT region, product, SUM(sales) FROM sales_data GROUP BY ROLLUP(region, product)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test GROUP BY CUBE +#[test] +fn it_parses_group_by_cube() { + let query = "SELECT region, product, SUM(sales) FROM sales_data GROUP BY CUBE(region, product)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test GROUP BY GROUPING SETS +#[test] +fn it_parses_grouping_sets() { + let query = "SELECT region, product, SUM(sales) FROM sales_data GROUP BY GROUPING SETS ((region), (product), ())"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} +// ============================================================================ +// DISTINCT and ORDER BY variations +// ============================================================================ + +/// Test DISTINCT ON +#[test] +fn it_parses_distinct_on() { + let query = "SELECT DISTINCT ON (user_id) * FROM orders ORDER BY user_id, created_at DESC"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test ORDER BY with NULLS FIRST/LAST +#[test] +fn it_parses_order_by_nulls() { + let query = "SELECT * FROM users ORDER BY last_login DESC NULLS LAST"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test FETCH FIRST +#[test] +fn it_parses_fetch_first() { + let query = "SELECT * FROM users ORDER BY id FETCH FIRST 10 ROWS ONLY"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test OFFSET with FETCH +#[test] +fn it_parses_offset_fetch() { + let query = "SELECT * FROM users ORDER BY id OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} +// ============================================================================ +// Locking clauses +// ============================================================================ + +/// Test FOR UPDATE +#[test] +fn it_parses_for_update() { + let query = "SELECT * FROM users WHERE id = 1 FOR UPDATE"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test FOR SHARE +#[test] +fn it_parses_for_share() { + let query = "SELECT * FROM users WHERE id = 1 FOR SHARE"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test FOR UPDATE NOWAIT +#[test] +fn it_parses_for_update_nowait() { + let query = "SELECT * FROM users WHERE id = 1 FOR UPDATE NOWAIT"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test FOR UPDATE SKIP LOCKED +#[test] +fn it_parses_for_update_skip_locked() { + let query = "SELECT * FROM jobs WHERE status = 'pending' LIMIT 1 FOR UPDATE SKIP LOCKED"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} +// ============================================================================ +// Complex real-world queries +// ============================================================================ + +/// Test analytics query with window functions +#[test] +fn it_parses_analytics_query() { + let query = " + SELECT + date_trunc('day', created_at) AS day, + COUNT(*) AS daily_orders, + SUM(amount) AS daily_revenue, + AVG(amount) OVER (ORDER BY date_trunc('day', created_at) ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) AS weekly_avg + FROM orders + WHERE created_at >= NOW() - INTERVAL '30 days' + GROUP BY date_trunc('day', created_at) + ORDER BY day"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test hierarchical query with recursive CTE +#[test] +fn it_parses_hierarchy_query() { + let query = " + WITH RECURSIVE category_tree AS ( + SELECT id, name, parent_id, 0 AS level, ARRAY[id] AS path + FROM categories + WHERE parent_id IS NULL + UNION ALL + SELECT c.id, c.name, c.parent_id, ct.level + 1, ct.path || c.id + FROM categories c + JOIN category_tree ct ON c.parent_id = ct.id + ) + SELECT * FROM category_tree ORDER BY path"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test complex report query +#[test] +fn it_parses_complex_report_query() { + let query = " + WITH monthly_data AS ( + SELECT + date_trunc('month', o.created_at) AS month, + u.region, + p.category, + SUM(oi.quantity * oi.unit_price) AS revenue, + COUNT(DISTINCT o.id) AS order_count, + COUNT(DISTINCT o.user_id) AS customer_count + FROM orders o + JOIN users u ON o.user_id = u.id + JOIN order_items oi ON o.id = oi.order_id + JOIN products p ON oi.product_id = p.id + WHERE o.created_at >= '2023-01-01' AND o.status = 'completed' + GROUP BY 1, 2, 3 + ) + SELECT + month, + region, + category, + revenue, + order_count, + customer_count, + revenue / NULLIF(order_count, 0) AS avg_order_value, + SUM(revenue) OVER (PARTITION BY region ORDER BY month) AS cumulative_revenue + FROM monthly_data + ORDER BY month DESC, region, revenue DESC"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test query with multiple subqueries and CTEs +#[test] +fn it_parses_mixed_subqueries_and_ctes() { + let query = " + WITH high_value_customers AS ( + SELECT user_id FROM orders GROUP BY user_id HAVING SUM(amount) > 1000 + ) + SELECT u.*, + (SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) AS total_orders, + (SELECT MAX(created_at) FROM orders o WHERE o.user_id = u.id) AS last_order + FROM users u + WHERE u.id IN (SELECT user_id FROM high_value_customers) + AND EXISTS (SELECT 1 FROM orders o WHERE o.user_id = u.id AND o.created_at > NOW() - INTERVAL '90 days') + ORDER BY (SELECT SUM(amount) FROM orders o WHERE o.user_id = u.id) DESC + LIMIT 100"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} +// Tests for previously stubbed fields +// ============================================================================ + +/// Test column with COLLATE clause +#[test] +fn it_parses_column_with_collate() { + let query = "CREATE TABLE test_collate ( + name TEXT COLLATE \"C\", + description VARCHAR(255) COLLATE \"en_US.UTF-8\" + )"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test partitioned table with PARTITION BY RANGE +#[test] +fn it_parses_partition_by_range() { + let query = "CREATE TABLE measurements ( + id SERIAL, + logdate DATE NOT NULL, + peaktemp INT + ) PARTITION BY RANGE (logdate)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test partitioned table with PARTITION BY LIST +#[test] +fn it_parses_partition_by_list() { + let query = "CREATE TABLE orders ( + id SERIAL, + region TEXT NOT NULL, + order_date DATE + ) PARTITION BY LIST (region)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test partitioned table with PARTITION BY HASH +#[test] +fn it_parses_partition_by_hash() { + let query = "CREATE TABLE users_partitioned ( + id SERIAL, + username TEXT + ) PARTITION BY HASH (id)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test partition with FOR VALUES (range) +#[test] +fn it_parses_partition_for_values_range() { + let query = "CREATE TABLE measurements_2023 PARTITION OF measurements + FOR VALUES FROM ('2023-01-01') TO ('2024-01-01')"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test partition with FOR VALUES (list) +#[test] +fn it_parses_partition_for_values_list() { + let query = "CREATE TABLE orders_west PARTITION OF orders + FOR VALUES IN ('west', 'northwest', 'southwest')"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test partition with FOR VALUES (hash) +#[test] +fn it_parses_partition_for_values_hash() { + let query = "CREATE TABLE users_part_0 PARTITION OF users_partitioned + FOR VALUES WITH (MODULUS 4, REMAINDER 0)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test partition with DEFAULT +#[test] +fn it_parses_partition_default() { + let query = "CREATE TABLE orders_other PARTITION OF orders DEFAULT"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test recursive CTE with SEARCH BREADTH FIRST +#[test] +fn it_parses_cte_search_breadth_first() { + let query = "WITH RECURSIVE search_tree(id, parent_id, data, depth) AS ( + SELECT id, parent_id, data, 0 FROM tree WHERE parent_id IS NULL + UNION ALL + SELECT t.id, t.parent_id, t.data, st.depth + 1 + FROM tree t, search_tree st WHERE t.parent_id = st.id + ) SEARCH BREADTH FIRST BY id SET ordercol + SELECT * FROM search_tree ORDER BY ordercol"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test recursive CTE with SEARCH DEPTH FIRST +#[test] +fn it_parses_cte_search_depth_first() { + let query = "WITH RECURSIVE search_tree(id, parent_id, data) AS ( + SELECT id, parent_id, data FROM tree WHERE parent_id IS NULL + UNION ALL + SELECT t.id, t.parent_id, t.data + FROM tree t, search_tree st WHERE t.parent_id = st.id + ) SEARCH DEPTH FIRST BY id SET ordercol + SELECT * FROM search_tree ORDER BY ordercol"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test recursive CTE with CYCLE detection +#[test] +fn it_parses_cte_cycle() { + let query = "WITH RECURSIVE search_graph(id, link, data, depth) AS ( + SELECT g.id, g.link, g.data, 0 FROM graph g + UNION ALL + SELECT g.id, g.link, g.data, sg.depth + 1 + FROM graph g, search_graph sg WHERE g.id = sg.link + ) CYCLE id SET is_cycle USING path + SELECT * FROM search_graph"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test recursive CTE with both SEARCH and CYCLE +#[test] +fn it_parses_cte_search_and_cycle() { + let query = "WITH RECURSIVE search_graph(id, link, data, depth) AS ( + SELECT g.id, g.link, g.data, 0 FROM graph g WHERE id = 1 + UNION ALL + SELECT g.id, g.link, g.data, sg.depth + 1 + FROM graph g, search_graph sg WHERE g.id = sg.link + ) SEARCH DEPTH FIRST BY id SET ordercol + CYCLE id SET is_cycle USING path + SELECT * FROM search_graph"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} diff --git a/tests/raw_parse_iter/statements.rs b/tests/raw_parse_iter/statements.rs new file mode 100644 index 0000000..9a324c8 --- /dev/null +++ b/tests/raw_parse_iter/statements.rs @@ -0,0 +1,450 @@ +//! Utility statement tests: transactions, VACUUM, SET/SHOW, LOCK, DO, LISTEN, etc. +//! +//! These tests verify parse_raw_iter_iter correctly handles utility statements. + +use super::*; + +// ============================================================================ +// Transaction and utility statements +// ============================================================================ + +/// Test EXPLAIN +#[test] +fn it_parses_explain() { + let query = "EXPLAIN SELECT * FROM users WHERE id = 1"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test EXPLAIN ANALYZE +#[test] +fn it_parses_explain_analyze() { + let query = "EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) SELECT * FROM users"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test COPY +#[test] +fn it_parses_copy() { + let query = "COPY users (id, name, email) FROM STDIN WITH (FORMAT csv, HEADER true)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test PREPARE +#[test] +fn it_parses_prepare() { + let query = "PREPARE user_by_id (int) AS SELECT * FROM users WHERE id = $1"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test EXECUTE +#[test] +fn it_parses_execute() { + let query = "EXECUTE user_by_id(42)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test DEALLOCATE +#[test] +fn it_parses_deallocate() { + let query = "DEALLOCATE user_by_id"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} +// ============================================================================ +// Transaction statement tests +// ============================================================================ + +/// Test BEGIN transaction +#[test] +fn it_parses_begin() { + let query = "BEGIN"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test BEGIN with options +#[test] +fn it_parses_begin_with_options() { + let query = "BEGIN ISOLATION LEVEL SERIALIZABLE READ ONLY"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test COMMIT transaction +#[test] +fn it_parses_commit() { + let query = "COMMIT"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test ROLLBACK transaction +#[test] +fn it_parses_rollback() { + let query = "ROLLBACK"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test START TRANSACTION +#[test] +fn it_parses_start_transaction() { + let query = "START TRANSACTION"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test SAVEPOINT +#[test] +fn it_parses_savepoint() { + let query = "SAVEPOINT my_savepoint"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test ROLLBACK TO SAVEPOINT +#[test] +fn it_parses_rollback_to_savepoint() { + let query = "ROLLBACK TO SAVEPOINT my_savepoint"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test RELEASE SAVEPOINT +#[test] +fn it_parses_release_savepoint() { + let query = "RELEASE SAVEPOINT my_savepoint"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} +// ============================================================================ +// VACUUM and ANALYZE statement tests +// ============================================================================ + +/// Test VACUUM +#[test] +fn it_parses_vacuum() { + let query = "VACUUM"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test VACUUM with table +#[test] +fn it_parses_vacuum_table() { + let query = "VACUUM users"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test VACUUM ANALYZE +#[test] +fn it_parses_vacuum_analyze() { + let query = "VACUUM ANALYZE users"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test VACUUM FULL +#[test] +fn it_parses_vacuum_full() { + let query = "VACUUM FULL users"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test ANALYZE +#[test] +fn it_parses_analyze() { + let query = "ANALYZE"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test ANALYZE with table +#[test] +fn it_parses_analyze_table() { + let query = "ANALYZE users"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test ANALYZE with column list +#[test] +fn it_parses_analyze_columns() { + let query = "ANALYZE users (id, name)"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} +// ============================================================================ +// SET and SHOW statement tests +// ============================================================================ + +/// Test SET statement +#[test] +fn it_parses_set() { + let query = "SET search_path TO public"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test SET with equals +#[test] +fn it_parses_set_equals() { + let query = "SET statement_timeout = 5000"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test SET LOCAL +#[test] +fn it_parses_set_local() { + let query = "SET LOCAL search_path TO myschema"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test SET SESSION +#[test] +fn it_parses_set_session() { + let query = "SET SESSION timezone = 'UTC'"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test RESET +#[test] +fn it_parses_reset() { + let query = "RESET search_path"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test RESET ALL +#[test] +fn it_parses_reset_all() { + let query = "RESET ALL"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test SHOW statement +#[test] +fn it_parses_show() { + let query = "SHOW search_path"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test SHOW ALL +#[test] +fn it_parses_show_all() { + let query = "SHOW ALL"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} +// ============================================================================ +// LISTEN, NOTIFY, UNLISTEN statement tests +// ============================================================================ + +/// Test LISTEN statement +#[test] +fn it_parses_listen() { + let query = "LISTEN my_channel"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test NOTIFY statement +#[test] +fn it_parses_notify() { + let query = "NOTIFY my_channel"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test NOTIFY with payload +#[test] +fn it_parses_notify_with_payload() { + let query = "NOTIFY my_channel, 'hello world'"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test UNLISTEN statement +#[test] +fn it_parses_unlisten() { + let query = "UNLISTEN my_channel"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test UNLISTEN * +#[test] +fn it_parses_unlisten_all() { + let query = "UNLISTEN *"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} +// ============================================================================ +// DISCARD statement tests +// ============================================================================ + +/// Test DISCARD ALL +#[test] +fn it_parses_discard_all() { + let query = "DISCARD ALL"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test DISCARD PLANS +#[test] +fn it_parses_discard_plans() { + let query = "DISCARD PLANS"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test DISCARD SEQUENCES +#[test] +fn it_parses_discard_sequences() { + let query = "DISCARD SEQUENCES"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test DISCARD TEMP +#[test] +fn it_parses_discard_temp() { + let query = "DISCARD TEMP"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} +// ============================================================================ +// LOCK statement tests +// ============================================================================ + +/// Test LOCK TABLE +#[test] +fn it_parses_lock_table() { + let query = "LOCK TABLE users IN ACCESS EXCLUSIVE MODE"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test LOCK multiple tables +#[test] +fn it_parses_lock_multiple_tables() { + let query = "LOCK TABLE users, orders IN SHARE MODE"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} +// ============================================================================ +// DO statement tests +// ============================================================================ + +/// Test DO statement +#[test] +fn it_parses_do_statement() { + let query = "DO $$ BEGIN RAISE NOTICE 'Hello'; END $$"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} + +/// Test DO statement with language +#[test] +fn it_parses_do_with_language() { + let query = "DO LANGUAGE plpgsql $$ BEGIN NULL; END $$"; + let raw_result = parse_raw_iter(query).unwrap(); + let proto_result = parse(query).unwrap(); + + assert_eq!(raw_result.protobuf, proto_result.protobuf); +} diff --git a/tests/raw_parse_iter_tests.rs b/tests/raw_parse_iter_tests.rs new file mode 100644 index 0000000..a33a027 --- /dev/null +++ b/tests/raw_parse_iter_tests.rs @@ -0,0 +1,15 @@ +//! Tests for parse_raw_iter functionality. +//! +//! These tests verify that parse_raw_iter produces equivalent results to parse. +//! Tests are split into modules for maintainability. +//! +//! Run tests one at a time from simple to complex: +//! cargo test --test raw_parse_iter_tests raw_parse_iter::basic::it_parses_simple_select + +#![allow(non_snake_case)] +#![cfg(test)] + +#[macro_use] +mod support; + +mod raw_parse_iter; diff --git a/tests/raw_parse_tests.rs b/tests/raw_parse_tests.rs index 397500a..d1279a6 100644 --- a/tests/raw_parse_tests.rs +++ b/tests/raw_parse_tests.rs @@ -12,7 +12,7 @@ mod support; mod raw_parse; // Re-export the benchmark test at the top level -use pg_query::{deparse, deparse_raw, parse, parse_raw}; +use pg_query::{deparse, deparse_raw, parse, parse_raw, parse_raw_iter}; use std::time::{Duration, Instant}; /// Benchmark comparing parse_raw vs parse performance @@ -87,12 +87,13 @@ fn benchmark_parse_raw_vs_parse() { // Warm up for _ in 0..10 { + let _ = parse_raw_iter(query).unwrap(); let _ = parse_raw(query).unwrap(); let _ = parse(query).unwrap(); } // Run for a fixed duration to get stable measurements - let target_duration = Duration::from_secs(2); + let target_duration = Duration::from_secs(5); // Benchmark parse_raw let mut raw_iterations = 0u64; @@ -106,6 +107,18 @@ fn benchmark_parse_raw_vs_parse() { let raw_elapsed = raw_start.elapsed(); let raw_ns_per_iter = raw_elapsed.as_nanos() as f64 / raw_iterations as f64; + // Benchmark parse_raw_iter + let mut raw_iter_iterations = 0u64; + let raw_start = Instant::now(); + while raw_start.elapsed() < target_duration { + for _ in 0..100 { + let _ = parse_raw_iter(query).unwrap(); + raw_iter_iterations += 1; + } + } + let raw_iter_elapsed = raw_start.elapsed(); + let raw_iter_ns_per_iter = raw_iter_elapsed.as_nanos() as f64 / raw_iter_iterations as f64; + // Benchmark parse (protobuf) let mut proto_iterations = 0u64; let proto_start = Instant::now(); @@ -123,7 +136,13 @@ fn benchmark_parse_raw_vs_parse() { let time_saved_ns = proto_ns_per_iter - raw_ns_per_iter; let time_saved_us = time_saved_ns / 1000.0; + // Calculate speedup and time saved + let speedup_iter = raw_ns_per_iter / raw_iter_ns_per_iter; + let time_saved_ns_iter = raw_ns_per_iter - raw_iter_ns_per_iter; + let time_saved_us_iter = time_saved_ns_iter / 1000.0; + // Calculate throughput (queries per second) + let raw_iter_qps = 1_000_000_000.0 / raw_iter_ns_per_iter; let raw_qps = 1_000_000_000.0 / raw_ns_per_iter; let proto_qps = 1_000_000_000.0 / proto_ns_per_iter; @@ -140,17 +159,24 @@ fn benchmark_parse_raw_vs_parse() { println!("│ Iterations: {:>10} │", raw_iterations); println!("│ Total time: {:>10.2?} │", raw_elapsed); println!("│ Per iteration: {:>10.2} μs │", raw_ns_per_iter / 1000.0); - println!("│ Throughput: {:>10.0} queries/sec │", raw_qps); + println!("│ Throughput: {:>10.0} queries/sec │", raw_qps); + println!("├─────────────────────────────────────────────────────────┤"); + println!("│ parse_raw_iter (direct C struct reading): │"); + println!("│ Iterations: {:>10} │", raw_iter_iterations); + println!("│ Total time: {:>10.2?} │", raw_iter_elapsed); + println!("│ Per iteration: {:>10.2} μs │", raw_iter_ns_per_iter / 1000.0); + println!("│ Throughput: {:>10.0} queries/sec │", raw_iter_qps); println!("├─────────────────────────────────────────────────────────┤"); println!("│ parse (protobuf serialization): │"); println!("│ Iterations: {:>10} │", proto_iterations); println!("│ Total time: {:>10.2?} │", proto_elapsed); println!("│ Per iteration: {:>10.2} μs │", proto_ns_per_iter / 1000.0); - println!("│ Throughput: {:>10.0} queries/sec │", proto_qps); + println!("│ Throughput: {:>10.0} queries/sec │", proto_qps); println!("├─────────────────────────────────────────────────────────┤"); println!("│ COMPARISON │"); - println!("│ Speedup: {:>10.2}x faster │", speedup); - println!("│ Time saved: {:>10.2} μs per parse │", time_saved_us); + println!("│ Speedup: {:>10.2}x faster │", speedup); + println!("│ Speedup iter: {:>10.2}x faster │", speedup_iter); + println!("│ Time saved: {:>10.2} μs per parse │", time_saved_us); println!("│ Extra queries: {:>10.0} more queries/sec │", raw_qps - proto_qps); println!("└─────────────────────────────────────────────────────────┘"); println!();