//! Matrix coverage for `insert into T values (vals)` (Form B — //! no column list; the dispatcher auto-fills column names per //! the schema, *skipping auto-generated columns* per ADR-0018 //! §3). //! //! The handoff §B fix this session was that Form B's //! `column_value_list` mirrors `do_insert`'s `user_cols` //! contract — so the slot list excludes serial/shortid columns //! and the hint prose at the first value position names the //! first non-auto-gen column, not the (skipped) `id`. use crate::typing_surface::*; use rdbms_playground::input_render::InputState; // ========================================================= // Entry positions overlap with Form A — covered there. This // file picks up from the `values` keyword. // ========================================================= #[test] fn after_values_space_offers_open_paren() { let schema = schema_serial_pk(); let a = assess_at_end("insert into Customers values ", &schema); assert!(matches!(a.state, InputState::IncompleteAtEof)); assert_candidate_present(&a, &["("]); crate::snap!("after_values_space", a); } // ========================================================= // First value position. The key §B regression: for a table // whose first column is `id:serial`, Form B's slot list // excludes `id`, so the prose must name the *first non-auto* // column — `Name` for schema_serial_pk. // ========================================================= #[test] fn form_b_first_value_skips_serial_column() { let schema = schema_serial_pk(); let a = assess_at_end("insert into Customers values (", &schema); let prose = hint_prose(&a).unwrap_or_else(|| { panic!("expected Prose at first Form B value slot, got {:?}", a.hint) }); // The value slot itself must be keyed on `Name` — the first // non-auto column — not on the skipped `id`. assert!( prose.starts_with("for `Name`"), "Form B's first value slot should be for `Name`, got prose: {prose:?}", ); // `id` appears only inside the trailing pedagogical note // (`id` auto-generated — skipped here …), never as the slot // the user is being prompted to fill. assert!( !prose.starts_with("for `id`"), "Form B must not prompt for the auto-gen `id` as a value slot, got prose: {prose:?}", ); crate::snap!("form_b_first_value_serial_pk", a); } #[test] fn form_b_first_value_text_pk_names_first_column() { let schema = schema_text_pk(); let a = assess_at_end("insert into Items values (", &schema); let prose = hint_prose(&a).unwrap_or_else(|| { panic!("expected Prose, got {:?}", a.hint) }); assert!( prose.contains("Code"), "Form B should name the PK column `Code`, got prose: {prose:?}", ); crate::snap!("form_b_first_value_text_pk", a); } #[test] fn form_b_first_value_every_type_first_column_is_int() { // The first column in schema_every_type is `k:int`. Prose // must say `integer` and name `k`. let schema = schema_every_type(); let a = assess_at_end("insert into Things values (", &schema); let prose = hint_prose(&a).unwrap_or_else(|| { panic!("expected Prose, got {:?}", a.hint) }); assert!( prose.contains("k"), "should name column `k`, got prose: {prose:?}", ); assert!( prose.contains("integer"), "should say `integer`, got prose: {prose:?}", ); crate::snap!("form_b_first_value_every_type", a); } // ========================================================= // Second-value position — the slot list advances to the next // non-auto column. For schema_serial_pk this is `Email`. // ========================================================= #[test] fn form_b_after_first_value_advances_to_next_column() { let schema = schema_serial_pk(); let a = assess_at_end( "insert into Customers values ('Alice', ", &schema, ); let prose = hint_prose(&a).unwrap_or_else(|| { panic!("expected Prose at second slot, got {:?}", a.hint) }); assert!( prose.contains("Email"), "second slot should name `Email`, got prose: {prose:?}", ); crate::snap!("form_b_second_value", a); } // ========================================================= // In-progress Form B values — must classify as // IncompleteAtEof. Regression for the matrix-found bug in // walk_repeated (fixed alongside this matrix). // ========================================================= #[test] fn form_b_in_progress_after_comma_is_incomplete() { let schema = schema_serial_pk(); let a = assess_at_end( "insert into Customers values ('Alice', ", &schema, ); assert!( matches!(a.state, InputState::IncompleteAtEof), "in-progress Form B should be Incomplete, got {:?}", a.state, ); crate::snap!("form_b_in_progress_after_comma", a); } #[test] fn form_b_in_progress_without_closing_paren_is_incomplete() { let schema = schema_serial_pk(); let a = assess_at_end( "insert into Customers values ('Alice', 'a@b.c'", &schema, ); assert!(matches!(a.state, InputState::IncompleteAtEof)); crate::snap!("form_b_in_progress_no_close_paren", a); } // ========================================================= // Form B with the wrong number of values is rejected. // schema_serial_pk has Customers(id:serial, Name:text, // Email:text). Form B excludes id → exactly 2 values // required. // ========================================================= #[test] fn form_b_with_too_few_values_is_invalid_at_close_paren() { let schema = schema_serial_pk(); let a = assess_at_end("insert into Customers values ('Alice')", &schema); // Only one value supplied; Form B for Customers needs two. // The grammar's typed slot list expects another `,` // before the `)`. Classify as DefiniteError or Incomplete // (which one depends on whether the closing `)` is past // the missing slot). assert!( !matches!(a.state, InputState::Valid), "input with too-few values must NOT be Valid, got {:?}", a.state, ); crate::snap!("form_b_too_few_values", a); } #[test] fn form_b_with_extra_value_for_serial_column_is_invalid() { // Form B excludes serial. Supplying a value for `id` here // (treating it as the first slot) means an extra value // overall — Customers has 3 columns but Form B accepts 2. let schema = schema_serial_pk(); let a = assess_at_end( "insert into Customers values (1, 'Alice', 'a@b.c')", &schema, ); assert!( !matches!(a.state, InputState::Valid), "Form B with a value-for-serial must be invalid, got {:?}", a.state, ); crate::snap!("form_b_extra_serial_value", a); } // ========================================================= // Form B happy path: correct number of values parses to // Insert. // ========================================================= #[test] fn form_b_with_correct_values_parses() { let schema = schema_serial_pk(); let a = assess_at_end( "insert into Customers values ('Alice', 'a@b.c')", &schema, ); assert!(matches!(a.state, InputState::Valid)); assert_eq!(a.parse_result.as_deref(), Ok("Insert")); crate::snap!("form_b_valid", a); } #[test] fn form_b_text_pk_with_correct_values_parses() { let schema = schema_text_pk(); let a = assess_at_end( "insert into Items values ('SKU-1', 'Widget')", &schema, ); assert!(matches!(a.state, InputState::Valid)); assert_eq!(a.parse_result.as_deref(), Ok("Insert")); crate::snap!("form_b_text_pk_valid", a); } // ========================================================= // Date / DateTime / Bool slot prose: schema_every_type // exercises each Type variant against a Form B values // position. // ========================================================= // ========================================================= // Pedagogical Form-A pointer (handoff-12 §2.2). // // At the FIRST value slot of a Form B insert whose table has // auto-generated columns, the hint must mention the skipped // column(s) and point at the explicit-column form. // ========================================================= #[test] fn form_b_first_slot_mentions_skipped_serial_column() { let schema = schema_serial_pk(); let a = assess_at_end("insert into Customers values (", &schema); let prose = hint_prose(&a).unwrap_or_else(|| { panic!("expected Prose at first Form B slot, got {:?}", a.hint) }); // Names the skipped auto-gen column. assert!( prose.contains("`id`"), "first-slot hint should mention skipped `id`, got: {prose:?}", ); // Points at the explicit-column escape hatch. assert!( prose.contains("auto-generated") && prose.contains("list columns"), "first-slot hint should explain the Form-A escape, got: {prose:?}", ); crate::snap!("form_b_first_slot_skip_note", a); } #[test] fn form_b_second_slot_omits_skip_note() { // The note fires once, at the first slot only — not at // every comma. let schema = schema_serial_pk(); let a = assess_at_end( "insert into Customers values ('Alice', ", &schema, ); let prose = hint_prose(&a).unwrap_or_else(|| { panic!("expected Prose at second slot, got {:?}", a.hint) }); assert!( !prose.contains("auto-generated"), "second-slot hint must NOT repeat the skip note, got: {prose:?}", ); crate::snap!("form_b_second_slot_no_skip_note", a); } #[test] fn form_b_text_pk_has_no_skip_note() { // No auto-gen columns → no skip note. let schema = schema_text_pk(); let a = assess_at_end("insert into Items values (", &schema); let prose = hint_prose(&a).unwrap_or_else(|| { panic!("expected Prose, got {:?}", a.hint) }); assert!( !prose.contains("auto-generated"), "text-PK table has no auto-gen column — no skip note expected, got: {prose:?}", ); crate::snap!("form_b_text_pk_no_skip_note", a); } #[test] fn form_a_first_slot_has_no_skip_note() { // Form A lists columns explicitly — the user is in control, // no pedagogical pointer needed. let schema = schema_serial_pk(); let a = assess_at_end( "insert into Customers (Name) values (", &schema, ); let prose = hint_prose(&a).unwrap_or_else(|| { panic!("expected Prose, got {:?}", a.hint) }); assert!( !prose.contains("auto-generated"), "Form A must not show the Form-B skip note, got: {prose:?}", ); crate::snap!("form_a_no_skip_note", a); } #[test] fn form_b_advances_through_every_type_first_to_real() { // Things' second column is `r:real`. After typing the // first value, prose must name `r` and say `number`. let schema = schema_every_type(); let a = assess_at_end("insert into Things values (1, ", &schema); let prose = hint_prose(&a).unwrap_or_else(|| { panic!("expected Prose at 2nd slot, got {:?}", a.hint) }); assert!(prose.contains("r"), "should name `r`, got prose: {prose:?}"); assert!( prose.contains("number"), "real-slot prose should say `number`, got prose: {prose:?}", ); crate::snap!("form_b_every_type_second_slot", a); }