diff --git a/crates/pgls_pretty_print/src/nodes/a_const.rs b/crates/pgls_pretty_print/src/nodes/a_const.rs index 16c265f00..497c8f2dd 100644 --- a/crates/pgls_pretty_print/src/nodes/a_const.rs +++ b/crates/pgls_pretty_print/src/nodes/a_const.rs @@ -10,8 +10,6 @@ pub(super) fn emit_a_const(e: &mut EventEmitter, n: &AConst) { e.token(TokenKind::NULL_KW); } else if let Some(ref val) = n.val { emit_val(e, val); - } else { - unreachable!("AConst must have either isnull=true or a val"); } e.group_end(); diff --git a/crates/pgls_pretty_print/src/nodes/alter_fdw_stmt.rs b/crates/pgls_pretty_print/src/nodes/alter_fdw_stmt.rs index b2ac08ceb..ff447aa0c 100644 --- a/crates/pgls_pretty_print/src/nodes/alter_fdw_stmt.rs +++ b/crates/pgls_pretty_print/src/nodes/alter_fdw_stmt.rs @@ -23,7 +23,10 @@ pub(super) fn emit_alter_fdw_stmt(e: &mut EventEmitter, n: &AlterFdwStmt) { e.indent_start(); for opt in n.func_options.iter() { e.line(LineType::SoftOrSpace); - let def_elem = assert_node_variant!(DefElem, opt); + let Some(NodeEnum::DefElem(def_elem)) = opt.node.as_ref() else { + emit_node(e, opt); + continue; + }; match def_elem.defname.as_str() { "handler" => { diff --git a/crates/pgls_pretty_print/src/nodes/alter_function_stmt.rs b/crates/pgls_pretty_print/src/nodes/alter_function_stmt.rs index 3921b31fa..6f9cfcf46 100644 --- a/crates/pgls_pretty_print/src/nodes/alter_function_stmt.rs +++ b/crates/pgls_pretty_print/src/nodes/alter_function_stmt.rs @@ -22,7 +22,10 @@ pub(super) fn emit_alter_function_stmt(e: &mut EventEmitter, n: &AlterFunctionSt if !n.actions.is_empty() { e.indent_start(); for action in sort_function_options(&n.actions) { - let def_elem = assert_node_variant!(DefElem, &action); + let Some(NodeEnum::DefElem(def_elem)) = action.node.as_ref() else { + emit_node(e, &action); + continue; + }; e.line(LineType::SoftOrSpace); format_function_option(e, def_elem); } diff --git a/crates/pgls_pretty_print/src/nodes/alter_table_cmd.rs b/crates/pgls_pretty_print/src/nodes/alter_table_cmd.rs index cfb5b6e17..9136a953f 100644 --- a/crates/pgls_pretty_print/src/nodes/alter_table_cmd.rs +++ b/crates/pgls_pretty_print/src/nodes/alter_table_cmd.rs @@ -74,7 +74,11 @@ pub(super) fn emit_alter_table_cmd_of( e.token(TokenKind::TYPE_KW); if let Some(ref def) = cmd.def { // Extract ColumnDef from Node to emit only type-related attributes - let column_def = assert_node_variant!(ColumnDef, def); + let Some(NodeEnum::ColumnDef(column_def)) = def.node.as_ref() else { + e.space(); + emit_node(e, def); + return; + }; // Emit type name (no space before - TYPE keyword already emitted) if let Some(ref typename) = column_def.type_name { diff --git a/crates/pgls_pretty_print/src/nodes/alter_table_move_all_stmt.rs b/crates/pgls_pretty_print/src/nodes/alter_table_move_all_stmt.rs index 6afa75240..6fab0f900 100644 --- a/crates/pgls_pretty_print/src/nodes/alter_table_move_all_stmt.rs +++ b/crates/pgls_pretty_print/src/nodes/alter_table_move_all_stmt.rs @@ -28,7 +28,10 @@ pub(super) fn emit_alter_table_move_all_stmt(e: &mut EventEmitter, n: &AlterTabl e.token(TokenKind::BY_KW); e.space(); emit_comma_separated_list(e, &n.roles, |e, node| { - let role_spec = assert_node_variant!(RoleSpec, node); + let Some(NodeEnum::RoleSpec(role_spec)) = node.node.as_ref() else { + emit_node(e, node); + return; + }; super::emit_role_spec(e, role_spec); }); } diff --git a/crates/pgls_pretty_print/src/nodes/alter_table_stmt.rs b/crates/pgls_pretty_print/src/nodes/alter_table_stmt.rs index c14db9a38..dfd1b5494 100644 --- a/crates/pgls_pretty_print/src/nodes/alter_table_stmt.rs +++ b/crates/pgls_pretty_print/src/nodes/alter_table_stmt.rs @@ -39,7 +39,10 @@ pub(super) fn emit_alter_table_stmt(e: &mut EventEmitter, n: &AlterTableStmt) { } // Extract AlterTableCmd from Node - let cmd = assert_node_variant!(AlterTableCmd, cmd_node); + let Some(NodeEnum::AlterTableCmd(cmd)) = cmd_node.node.as_ref() else { + emit_node(e, cmd_node); + continue; + }; super::emit_alter_table_cmd_of(e, cmd, n.objtype()); } diff --git a/crates/pgls_pretty_print/src/nodes/bool_expr.rs b/crates/pgls_pretty_print/src/nodes/bool_expr.rs index 0e69fa708..d09a9310d 100644 --- a/crates/pgls_pretty_print/src/nodes/bool_expr.rs +++ b/crates/pgls_pretty_print/src/nodes/bool_expr.rs @@ -33,7 +33,7 @@ pub(super) fn emit_bool_expr_as(e: &mut EventEmitter, n: &BoolExpr, group: Opera BoolExprType::AndExpr => emit_variadic_bool_expr(e, n, TokenKind::AND_KW), BoolExprType::OrExpr => emit_variadic_bool_expr(e, n, TokenKind::OR_KW), BoolExprType::NotExpr => emit_not_expr(e, n), - BoolExprType::Undefined => unreachable!("Undefined BoolExprType"), + BoolExprType::Undefined => {} } e.expression_group_end(); @@ -108,13 +108,6 @@ fn emit_variadic_operands( fn emit_not_expr(e: &mut EventEmitter, n: &BoolExpr) { e.token(TokenKind::NOT_KW); - if n.args.len() != 1 { - panic!( - "NOT expressions should have exactly one argument, got {}", - n.args.len() - ); - } - if let Some(arg) = n.args.first() { e.space(); emit_bool_operand(e, arg, BoolExprType::NotExpr); diff --git a/crates/pgls_pretty_print/src/nodes/clauses/options.rs b/crates/pgls_pretty_print/src/nodes/clauses/options.rs index b87a0164a..e7ea87f6a 100644 --- a/crates/pgls_pretty_print/src/nodes/clauses/options.rs +++ b/crates/pgls_pretty_print/src/nodes/clauses/options.rs @@ -17,7 +17,10 @@ pub(crate) fn emit_options_clause(e: &mut EventEmitter, options: &[Node]) { e.token(TokenKind::OPTIONS_KW); e.space(); emit_parenthesized_list(e, options, |e, n| { - let def_elem = assert_node_variant!(DefElem, n); + let Some(NodeEnum::DefElem(def_elem)) = n.node.as_ref() else { + emit_node(e, n); + return; + }; emit_options_def_elem(e, def_elem); }); } diff --git a/crates/pgls_pretty_print/src/nodes/clauses/set_clause.rs b/crates/pgls_pretty_print/src/nodes/clauses/set_clause.rs index 84e84ec2a..0005e4d58 100644 --- a/crates/pgls_pretty_print/src/nodes/clauses/set_clause.rs +++ b/crates/pgls_pretty_print/src/nodes/clauses/set_clause.rs @@ -36,12 +36,22 @@ pub(crate) fn emit_set_clause_list(e: &mut EventEmitter, nodes: &[pgls_query::No } let node = &nodes[index]; - let target = assert_node_variant!(ResTarget, node); + let Some(NodeEnum::ResTarget(target)) = node.node.as_ref() else { + emit_node(e, node); + index += 1; + continue; + }; let consumed = if let Some(ref val) = target.val { match val.node.as_ref() { Some(NodeEnum::MultiAssignRef(multi)) if multi.colno == 1 => { - emit_multi_assign_clause(e, nodes, index, multi) + match multi.source.as_deref() { + Some(source) => emit_multi_assign_clause(e, nodes, index, multi, source), + None => { + emit_set_clause(e, target); + 1 + } + } } _ => { emit_set_clause(e, target); @@ -62,20 +72,14 @@ fn emit_multi_assign_clause( nodes: &[pgls_query::Node], start: usize, multi: &MultiAssignRef, + source_node: &Node, ) -> usize { let total = multi.ncolumns.max(1) as usize; debug_assert_eq!(multi.colno, 1, "MultiAssignRef should start at colno 1"); - let end = start + total; - debug_assert!( - end <= nodes.len(), - "MultiAssignRef spans beyond target list" - ); - - let source_node = multi - .source - .as_ref() - .expect("MultiAssignRef source missing row expression"); + // The columns of `(a, b) = ...` are the next `ncolumns` targets. A source spanning past the + // list writes the targets there are, and the AST check refuses the output. + let end = (start + total).min(nodes.len()); // Check if source is a RowExpr - if so, we may expand it; otherwise emit as-is let maybe_row_expr = if let Some(pgls_query::NodeEnum::RowExpr(r)) = source_node.node.as_ref() { @@ -104,8 +108,10 @@ fn emit_multi_assign_clause( e.line(LineType::SoftOrSpace); } - let target = assert_node_variant!(ResTarget, node); - emit_column_name_with_indirection(e, target); + match node.node.as_ref() { + Some(NodeEnum::ResTarget(target)) => emit_column_name_with_indirection(e, target), + _ => emit_node(e, node), + } } e.indent_end(); e.line(LineType::Soft); diff --git a/crates/pgls_pretty_print/src/nodes/clauses/window_frame.rs b/crates/pgls_pretty_print/src/nodes/clauses/window_frame.rs index 4c66c4bbe..4de5d4c45 100644 --- a/crates/pgls_pretty_print/src/nodes/clauses/window_frame.rs +++ b/crates/pgls_pretty_print/src/nodes/clauses/window_frame.rs @@ -117,16 +117,16 @@ fn emit_frame_bound( e.space(); e.token(TokenKind::ROW_KW); } else if options & FRAMEOPTION_START_OFFSET_PRECEDING != 0 { - let offset_node = - offset.expect("FRAMEOPTION_START_OFFSET_PRECEDING requires start_offset"); - emit_node(e, offset_node); - e.space(); + if let Some(offset) = offset { + emit_node(e, offset); + e.space(); + } emit_keyword(e, "PRECEDING"); } else if options & FRAMEOPTION_START_OFFSET_FOLLOWING != 0 { - let offset_node = - offset.expect("FRAMEOPTION_START_OFFSET_FOLLOWING requires start_offset"); - emit_node(e, offset_node); - e.space(); + if let Some(offset) = offset { + emit_node(e, offset); + e.space(); + } emit_keyword(e, "FOLLOWING"); } else { debug_assert!(false, "unhandled window frame start options: {options:#x}"); @@ -150,16 +150,16 @@ fn emit_frame_bound( e.space(); e.token(TokenKind::ROW_KW); } else if options & FRAMEOPTION_END_OFFSET_PRECEDING != 0 { - let offset_node = - offset.expect("FRAMEOPTION_END_OFFSET_PRECEDING requires end_offset"); - emit_node(e, offset_node); - e.space(); + if let Some(offset) = offset { + emit_node(e, offset); + e.space(); + } emit_keyword(e, "PRECEDING"); } else if options & FRAMEOPTION_END_OFFSET_FOLLOWING != 0 { - let offset_node = - offset.expect("FRAMEOPTION_END_OFFSET_FOLLOWING requires end_offset"); - emit_node(e, offset_node); - e.space(); + if let Some(offset) = offset { + emit_node(e, offset); + e.space(); + } emit_keyword(e, "FOLLOWING"); } else { debug_assert!(false, "unhandled window frame end options: {options:#x}"); diff --git a/crates/pgls_pretty_print/src/nodes/column_def.rs b/crates/pgls_pretty_print/src/nodes/column_def.rs index 58bfd837a..e62132f69 100644 --- a/crates/pgls_pretty_print/src/nodes/column_def.rs +++ b/crates/pgls_pretty_print/src/nodes/column_def.rs @@ -68,7 +68,10 @@ pub(super) fn emit_column_def(e: &mut EventEmitter, n: &ColumnDef) { &n.fdwoptions, ListSeparatorSpacing::Space, |e, opt| { - let def_elem = assert_node_variant!(DefElem, opt); + let Some(NodeEnum::DefElem(def_elem)) = opt.node.as_ref() else { + emit_node(e, opt); + return; + }; emit_options_def_elem(e, def_elem); }, ); diff --git a/crates/pgls_pretty_print/src/nodes/constraint.rs b/crates/pgls_pretty_print/src/nodes/constraint.rs index a9de5208f..b4f901fbc 100644 --- a/crates/pgls_pretty_print/src/nodes/constraint.rs +++ b/crates/pgls_pretty_print/src/nodes/constraint.rs @@ -484,7 +484,10 @@ fn emit_deferrable(e: &mut EventEmitter, n: &Constraint, continued: &mut bool) { /// Emits one element of an exclusion constraint: `column WITH operator`. fn emit_exclusion(e: &mut EventEmitter, exclusion: &pgls_query::Node) { - let exclusion_list = assert_node_variant!(List, exclusion); + let Some(NodeEnum::List(exclusion_list)) = exclusion.node.as_ref() else { + emit_node(e, exclusion); + return; + }; debug_assert!(exclusion_list.items.len() >= 2); if let Some(index_elem) = exclusion_list.items.first() { diff --git a/crates/pgls_pretty_print/src/nodes/copy_stmt.rs b/crates/pgls_pretty_print/src/nodes/copy_stmt.rs index d9e20f731..9d06b356e 100644 --- a/crates/pgls_pretty_print/src/nodes/copy_stmt.rs +++ b/crates/pgls_pretty_print/src/nodes/copy_stmt.rs @@ -62,7 +62,10 @@ pub(super) fn emit_copy_stmt(e: &mut EventEmitter, n: &CopyStmt) { e.line(LineType::Soft); e.indent_start(); emit_comma_separated_list(e, &n.options, |e, n| { - let def_elem = assert_node_variant!(DefElem, n); + let Some(NodeEnum::DefElem(def_elem)) = n.node.as_ref() else { + emit_node(e, n); + return; + }; emit_copy_option(e, def_elem); }); e.indent_end(); diff --git a/crates/pgls_pretty_print/src/nodes/create_enum_stmt.rs b/crates/pgls_pretty_print/src/nodes/create_enum_stmt.rs index ce5673323..a6797accf 100644 --- a/crates/pgls_pretty_print/src/nodes/create_enum_stmt.rs +++ b/crates/pgls_pretty_print/src/nodes/create_enum_stmt.rs @@ -24,7 +24,10 @@ pub(super) fn emit_create_enum_stmt(e: &mut EventEmitter, n: &CreateEnumStmt) { if !n.vals.is_empty() { e.space(); emit_parenthesized_list(e, &n.vals, |e, n| { - let s = assert_node_variant!(String, n); + let Some(NodeEnum::String(s)) = n.node.as_ref() else { + emit_node(e, n); + return; + }; emit_string_literal(e, s); }); } diff --git a/crates/pgls_pretty_print/src/nodes/create_extension_stmt.rs b/crates/pgls_pretty_print/src/nodes/create_extension_stmt.rs index 166275e15..abe38d1a7 100644 --- a/crates/pgls_pretty_print/src/nodes/create_extension_stmt.rs +++ b/crates/pgls_pretty_print/src/nodes/create_extension_stmt.rs @@ -22,7 +22,10 @@ pub(super) fn emit_create_extension_stmt(e: &mut EventEmitter, n: &CreateExtensi e.token(TokenKind::WITH_KW); for opt in &n.options { - let def = assert_node_variant!(DefElem, opt); + let Some(NodeEnum::DefElem(def)) = opt.node.as_ref() else { + emit_node(e, opt); + continue; + }; e.space(); emit_extension_option(e, def); } diff --git a/crates/pgls_pretty_print/src/nodes/create_fdw_stmt.rs b/crates/pgls_pretty_print/src/nodes/create_fdw_stmt.rs index 4dcbe894e..4e0458da8 100644 --- a/crates/pgls_pretty_print/src/nodes/create_fdw_stmt.rs +++ b/crates/pgls_pretty_print/src/nodes/create_fdw_stmt.rs @@ -20,7 +20,10 @@ pub(super) fn emit_create_fdw_stmt(e: &mut EventEmitter, n: &CreateFdwStmt) { e.line(LineType::SoftOrSpace); e.indent_start(); for opt in &n.func_options { - let def_elem = assert_node_variant!(DefElem, opt); + let Some(NodeEnum::DefElem(def_elem)) = opt.node.as_ref() else { + emit_node(e, opt); + continue; + }; match def_elem.defname.as_str() { "handler" => { diff --git a/crates/pgls_pretty_print/src/nodes/create_table_as_stmt.rs b/crates/pgls_pretty_print/src/nodes/create_table_as_stmt.rs index 657d2c4b0..6dc9b2ff6 100644 --- a/crates/pgls_pretty_print/src/nodes/create_table_as_stmt.rs +++ b/crates/pgls_pretty_print/src/nodes/create_table_as_stmt.rs @@ -49,7 +49,10 @@ pub(super) fn emit_create_table_as_stmt(e: &mut EventEmitter, n: &CreateTableAsS if !into.col_names.is_empty() { e.space(); emit_parenthesized_list(e, &into.col_names, |e, node| { - let string = assert_node_variant!(String, node); + let Some(NodeEnum::String(string)) = node.node.as_ref() else { + emit_node(e, node); + return; + }; emit_string(e, string); }); } diff --git a/crates/pgls_pretty_print/src/nodes/create_trig_stmt.rs b/crates/pgls_pretty_print/src/nodes/create_trig_stmt.rs index fc362d11a..cd469d548 100644 --- a/crates/pgls_pretty_print/src/nodes/create_trig_stmt.rs +++ b/crates/pgls_pretty_print/src/nodes/create_trig_stmt.rs @@ -156,7 +156,10 @@ pub(super) fn emit_create_trig_stmt(e: &mut EventEmitter, n: &CreateTrigStmt) { fn emit_trigger_transitions(e: &mut EventEmitter, rels: &[pgls_query::Node]) { emit_space_separated_list(e, rels, |e, rel| { - let transition = assert_node_variant!(TriggerTransition, rel); + let Some(NodeEnum::TriggerTransition(transition)) = rel.node.as_ref() else { + emit_node(e, rel); + return; + }; if transition.is_new { e.token(TokenKind::NEW_KW); diff --git a/crates/pgls_pretty_print/src/nodes/func_call.rs b/crates/pgls_pretty_print/src/nodes/func_call.rs index 708a9ba1e..e380d6390 100644 --- a/crates/pgls_pretty_print/src/nodes/func_call.rs +++ b/crates/pgls_pretty_print/src/nodes/func_call.rs @@ -168,9 +168,11 @@ fn emit_standard_function(e: &mut EventEmitter, n: &FuncCall) { e.token(TokenKind::IDENT("*".to_string())); } else if !n.args.is_empty() { // Handle VARIADIC - if func_variadic is true, prepend VARIADIC to the last argument - if n.func_variadic && !n.args.is_empty() { + if n.func_variadic + && let Some((variadic, leading)) = n.args.split_last() + { // Emit non-variadic args first - for (idx, arg) in n.args.iter().take(n.args.len() - 1).enumerate() { + for (idx, arg) in leading.iter().enumerate() { if idx > 0 { e.token(TokenKind::COMMA); e.line(LineType::SoftOrSpace); @@ -178,7 +180,7 @@ fn emit_standard_function(e: &mut EventEmitter, n: &FuncCall) { emit_node(e, arg); } - if n.args.len() > 1 { + if !leading.is_empty() { e.token(TokenKind::COMMA); e.line(LineType::SoftOrSpace); } @@ -186,7 +188,7 @@ fn emit_standard_function(e: &mut EventEmitter, n: &FuncCall) { // Emit VARIADIC with the last argument e.token(TokenKind::VARIADIC_KW); e.space(); - emit_node(e, n.args.last().unwrap()); + emit_node(e, variadic); } else if let Some(group_size) = configured_argument_group_size(e, n) { emit_grouped_arguments(e, &n.args, group_size); } else { diff --git a/crates/pgls_pretty_print/src/nodes/into_clause.rs b/crates/pgls_pretty_print/src/nodes/into_clause.rs index b90d2d968..9b982a07a 100644 --- a/crates/pgls_pretty_print/src/nodes/into_clause.rs +++ b/crates/pgls_pretty_print/src/nodes/into_clause.rs @@ -37,7 +37,10 @@ pub(super) fn emit_into_clause(e: &mut EventEmitter, n: &IntoClause) { e.line(LineType::SoftOrSpace); } emit_comma_separated_list(e, &n.col_names, |e, node| { - let ident = assert_node_variant!(String, node); + let Some(NodeEnum::String(ident)) = node.node.as_ref() else { + emit_node(e, node); + return; + }; emit_string(e, ident); }); if n.col_names.len() > 1 { diff --git a/crates/pgls_pretty_print/src/nodes/json_table.rs b/crates/pgls_pretty_print/src/nodes/json_table.rs index cfcbe8718..4ba84f4bd 100644 --- a/crates/pgls_pretty_print/src/nodes/json_table.rs +++ b/crates/pgls_pretty_print/src/nodes/json_table.rs @@ -30,7 +30,10 @@ pub(super) fn emit_json_table(e: &mut EventEmitter, n: &JsonTable) { e.token(TokenKind::PASSING_KW); e.space(); emit_comma_separated_list(e, &n.passing, |e, node| { - let argument = assert_node_variant!(JsonArgument, node); + let Some(NodeEnum::JsonArgument(argument)) = node.node.as_ref() else { + emit_node(e, node); + return; + }; super::emit_json_argument(e, argument); }); } diff --git a/crates/pgls_pretty_print/src/nodes/merge_stmt.rs b/crates/pgls_pretty_print/src/nodes/merge_stmt.rs index 97ed6eef3..9a80f898d 100644 --- a/crates/pgls_pretty_print/src/nodes/merge_stmt.rs +++ b/crates/pgls_pretty_print/src/nodes/merge_stmt.rs @@ -39,7 +39,10 @@ pub(super) fn emit_merge_stmt(e: &mut EventEmitter, n: &MergeStmt) { // WHEN clauses for when_clause_node in &n.merge_when_clauses { - let when_clause = assert_node_variant!(MergeWhenClause, when_clause_node); + let Some(NodeEnum::MergeWhenClause(when_clause)) = when_clause_node.node.as_ref() else { + emit_node(e, when_clause_node); + continue; + }; e.line(LineType::SoftOrSpace); emit_merge_when_clause(e, when_clause); } diff --git a/crates/pgls_pretty_print/src/nodes/merge_when_clause.rs b/crates/pgls_pretty_print/src/nodes/merge_when_clause.rs index 67d42ef75..b5a8462b4 100644 --- a/crates/pgls_pretty_print/src/nodes/merge_when_clause.rs +++ b/crates/pgls_pretty_print/src/nodes/merge_when_clause.rs @@ -64,7 +64,10 @@ pub(super) fn emit_merge_when_clause(e: &mut EventEmitter, clause: &MergeWhenCla e.line(LineType::Soft); e.indent_start(); emit_comma_separated_list(e, &clause.target_list, |emitter, node| { - let target = assert_node_variant!(ResTarget, node); + let Some(NodeEnum::ResTarget(target)) = node.node.as_ref() else { + emit_node(emitter, node); + return; + }; if !target.name.is_empty() { emit_identifier_maybe_quoted(emitter, &target.name); } diff --git a/crates/pgls_pretty_print/src/nodes/mod.rs b/crates/pgls_pretty_print/src/nodes/mod.rs index 5177e5180..6c469d97a 100644 --- a/crates/pgls_pretty_print/src/nodes/mod.rs +++ b/crates/pgls_pretty_print/src/nodes/mod.rs @@ -1,12 +1,3 @@ -macro_rules! assert_node_variant { - ($variant:ident, $expr:expr) => { - match $expr.node.as_ref() { - Some(pgls_query::NodeEnum::$variant(inner)) => inner, - other => panic!("Expected {}, got {:?}", stringify!($variant), other), - } - }; -} - pub(crate) mod clauses; mod prelude; mod utils; diff --git a/crates/pgls_pretty_print/src/nodes/on_conflict_clause.rs b/crates/pgls_pretty_print/src/nodes/on_conflict_clause.rs index 56806c5ca..1e6363f5b 100644 --- a/crates/pgls_pretty_print/src/nodes/on_conflict_clause.rs +++ b/crates/pgls_pretty_print/src/nodes/on_conflict_clause.rs @@ -40,9 +40,7 @@ pub(super) fn emit_on_conflict_clause(e: &mut EventEmitter, n: &OnConflictClause emit_clause_condition(e, where_clause); } } - OnConflictAction::OnconflictNone | OnConflictAction::Undefined => { - unreachable!("unexpected OnConflictAction: {:?}", n.action()); - } + OnConflictAction::OnconflictNone | OnConflictAction::Undefined => {} } e.group_end(); diff --git a/crates/pgls_pretty_print/src/nodes/select_stmt.rs b/crates/pgls_pretty_print/src/nodes/select_stmt.rs index 47753aeb4..3b6465816 100644 --- a/crates/pgls_pretty_print/src/nodes/select_stmt.rs +++ b/crates/pgls_pretty_print/src/nodes/select_stmt.rs @@ -37,7 +37,7 @@ pub(super) fn emit_select_stmt(e: &mut EventEmitter, n: &SelectStmt) { SetOperation::SetopUnion => e.token(TokenKind::UNION_KW), SetOperation::SetopIntersect => e.token(TokenKind::INTERSECT_KW), SetOperation::SetopExcept => e.token(TokenKind::EXCEPT_KW), - _ => unreachable!(), + SetOperation::SetopNone | SetOperation::Undefined => {} } // Emit ALL keyword if present diff --git a/crates/pgls_pretty_print/src/nodes/set_operation_stmt.rs b/crates/pgls_pretty_print/src/nodes/set_operation_stmt.rs index f39d5d82d..261fce06c 100644 --- a/crates/pgls_pretty_print/src/nodes/set_operation_stmt.rs +++ b/crates/pgls_pretty_print/src/nodes/set_operation_stmt.rs @@ -35,9 +35,8 @@ pub(super) fn emit_set_operation_stmt(e: &mut EventEmitter, n: &SetOperationStmt e.token(TokenKind::ALL_KW); } } - SetOperation::SetopNone | SetOperation::Undefined => { - unreachable!("unexpected SetOperation: {:?}", n.op()); - } + // Not a set operation: there is no keyword to write, and the AST check refuses the output. + SetOperation::SetopNone | SetOperation::Undefined => {} } // Emit right operand diff --git a/crates/pgls_pretty_print/src/nodes/transaction_stmt.rs b/crates/pgls_pretty_print/src/nodes/transaction_stmt.rs index 23d968714..76acd5b2f 100644 --- a/crates/pgls_pretty_print/src/nodes/transaction_stmt.rs +++ b/crates/pgls_pretty_print/src/nodes/transaction_stmt.rs @@ -108,7 +108,11 @@ fn emit_transaction_options(e: &mut EventEmitter, n: &TransactionStmt) { e.line(LineType::SoftOrSpace); } - let def_elem = assert_node_variant!(DefElem, option); + let Some(NodeEnum::DefElem(def_elem)) = option.node.as_ref() else { + emit_node(e, option); + + continue; + }; emit_transaction_option(e, def_elem); } } diff --git a/crates/pgls_pretty_print/src/nodes/view_stmt.rs b/crates/pgls_pretty_print/src/nodes/view_stmt.rs index 7b090cbb3..52a6a57db 100644 --- a/crates/pgls_pretty_print/src/nodes/view_stmt.rs +++ b/crates/pgls_pretty_print/src/nodes/view_stmt.rs @@ -41,7 +41,10 @@ pub(super) fn emit_view_stmt(e: &mut EventEmitter, n: &ViewStmt) { e.line(LineType::Soft); e.indent_start(); emit_comma_separated_list(e, &n.aliases, |emitter, alias_node| { - let alias = assert_node_variant!(String, alias_node); + let Some(NodeEnum::String(alias)) = alias_node.node.as_ref() else { + emit_node(emitter, alias_node); + return; + }; emit_identifier_maybe_quoted(emitter, &alias.sval); }); e.indent_end(); diff --git a/crates/pgls_pretty_print/tests/malformed_ast.rs b/crates/pgls_pretty_print/tests/malformed_ast.rs new file mode 100644 index 000000000..0b94f230c --- /dev/null +++ b/crates/pgls_pretty_print/tests/malformed_ast.rs @@ -0,0 +1,101 @@ +//! The printer writes whatever AST it gets without panicking. An AST the parser wouldn't build +//! gives output with a different AST, which `format_statement` refuses. + +use pgls_pretty_print::{FormatConfig, format_statement_unchecked}; +use pgls_query::NodeEnum; +use pgls_query::protobuf::{Integer, Node, String as PgString}; + +fn parse(sql: &str) -> NodeEnum { + pgls_query::parse(sql) + .expect("parse") + .into_root() + .expect("root") +} + +fn print(ast: &NodeEnum) -> String { + format_statement_unchecked(ast, &FormatConfig::default()).expect("printed") +} + +fn integer(ival: i32) -> Node { + Node { + node: Some(NodeEnum::Integer(Integer { ival })), + } +} + +fn string(sval: &str) -> Node { + Node { + node: Some(NodeEnum::String(PgString { + sval: sval.to_string(), + })), + } +} + +#[test] +fn an_unexpected_node_in_a_list_is_printed_as_itself() { + let mut ast = parse("CREATE TYPE mood AS ENUM ('happy')"); + let NodeEnum::CreateEnumStmt(stmt) = &mut ast else { + panic!("a CREATE TYPE ... AS ENUM"); + }; + stmt.vals[0] = integer(1); + + assert!(print(&ast).contains("enum (1)")); +} + +#[test] +fn an_unexpected_alter_table_command_is_printed_as_itself() { + let mut ast = parse("ALTER TABLE t ADD COLUMN a int"); + let NodeEnum::AlterTableStmt(stmt) = &mut ast else { + panic!("an ALTER TABLE"); + }; + stmt.cmds.push(string("b")); + + assert!(print(&ast).contains("b")); +} + +/// The value of the first select item of `ast`. +fn first_select_item(ast: &mut NodeEnum) -> &mut NodeEnum { + let NodeEnum::SelectStmt(select) = ast else { + panic!("a SELECT"); + }; + let Some(NodeEnum::ResTarget(target)) = select.target_list[0].node.as_mut() else { + panic!("a select item"); + }; + target + .val + .as_mut() + .and_then(|value| value.node.as_mut()) + .expect("a value") +} + +#[test] +fn a_not_with_two_arguments_is_printed() { + let mut ast = parse("SELECT NOT a FROM t"); + let NodeEnum::BoolExpr(expr) = first_select_item(&mut ast) else { + panic!("a NOT"); + }; + expr.args.push(expr.args[0].clone()); + + print(&ast); +} + +#[test] +fn a_constant_without_a_value_is_printed() { + let mut ast = parse("SELECT 1"); + let NodeEnum::AConst(constant) = first_select_item(&mut ast) else { + panic!("a constant"); + }; + constant.val = None; + + print(&ast); +} + +#[test] +fn a_window_frame_offset_that_is_missing_is_skipped() { + let mut ast = parse("SELECT count(*) OVER (ROWS 1 PRECEDING) FROM t"); + let NodeEnum::FuncCall(call) = first_select_item(&mut ast) else { + panic!("a call"); + }; + call.over.as_mut().expect("a window").start_offset = None; + + assert!(print(&ast).contains("rows preceding")); +}