diff --git a/src/app.rs b/src/app.rs index 1726e04..5007823 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1473,6 +1473,7 @@ impl App { target_type: None, value: facts.value, diagnostic_table: facts.diagnostic_table, + check_rule: facts.check_rule, verbosity: self.messages_verbosity, } } diff --git a/src/friendly/keys.rs b/src/friendly/keys.rs index 96011b4..16baf3a 100644 --- a/src/friendly/keys.rs +++ b/src/friendly/keys.rs @@ -50,8 +50,16 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[ // ---- CHECK violations ---- ("error.check.insert.headline", &["table", "column"]), ("error.check.insert.hint", &["column"]), + ( + "error.check.insert.hint_with_rule", + &["value", "rule", "column"], + ), ("error.check.update.headline", &["table", "column"]), ("error.check.update.hint", &["column"]), + ( + "error.check.update.hint_with_rule", + &["value", "rule", "column"], + ), // ---- FK violations (anchor: "referenced by") ---- ( "error.foreign_key.child_side.insert.headline", diff --git a/src/friendly/strings/en-US.yaml b/src/friendly/strings/en-US.yaml index 6c6d3f4..bf63298 100644 --- a/src/friendly/strings/en-US.yaml +++ b/src/friendly/strings/en-US.yaml @@ -85,17 +85,20 @@ error: headline: "`{table}.{column}` cannot be null." hint: "The `{column}` column is required — pick a non-null value, or do not include `{column}` in your `set` list." - # CHECK constraint violations. Placeholder coverage — - # the playground does not emit CHECK constraints today - # (track C3), but the catalog is wired so the wording - # is ready when constraint-management lands. + # CHECK constraint violations (ADR-0029 §10). When the + # runtime resolves the column's compiled CHECK expression, + # `hint_with_rule` names both the offending value and the + # rule; the plain `hint` is the fallback when enrichment + # could not resolve the rule. check: insert: headline: "check constraint refused `{table}.{column}`." hint: "A check constraint requires `{column}` to satisfy a rule the inserted value did not." + hint_with_rule: "The value {value} breaks the rule `{rule}` — `{column}` must satisfy it." update: headline: "check constraint refused `{table}.{column}`." hint: "A check constraint requires `{column}` to satisfy a rule the new value did not." + hint_with_rule: "The new value {value} breaks the rule `{rule}` — `{column}` must satisfy it." # Type mismatch — engine-side STRICT refusal of a wrong-shape # value. Mostly the `change column ... --dont-convert` path diff --git a/src/friendly/translate.rs b/src/friendly/translate.rs index 3679f7e..e618cd8 100644 --- a/src/friendly/translate.rs +++ b/src/friendly/translate.rs @@ -145,6 +145,10 @@ pub struct FailureContext { /// §7. Rendered through the bordered diagnostic-table /// renderer when present. pub diagnostic_table: Option, + /// For a `CHECK` violation: the column's compiled `CHECK` + /// expression, resolved from the schema (ADR-0029 §10). Lets + /// the friendly error name the rule the value broke. + pub check_rule: Option, } /// Context the translator uses to pick catalog keys and fill @@ -174,6 +178,10 @@ pub struct TranslateContext { /// Pinpointed offending row(s); rendered onto the /// `FriendlyError::diagnostic_table` field when present. pub diagnostic_table: Option, + /// For a `CHECK` violation: the column's compiled `CHECK` + /// expression (ADR-0029 §10). When present, the friendly + /// error names the rule the value broke. + pub check_rule: Option, pub verbosity: Verbosity, } @@ -207,6 +215,7 @@ impl TranslateContext { target_type: None, value: facts.value, diagnostic_table: facts.diagnostic_table, + check_rule: facts.check_rule, verbosity, } } @@ -489,30 +498,47 @@ fn translate_not_null(message: &str, ctx: &TranslateContext) -> FriendlyError { // ---- CHECK ----------------------------------------------------- fn translate_check(_message: &str, ctx: &TranslateContext) -> FriendlyError { - // The engine reports CHECK constraint failures by constraint - // name, not by column. We don't have user-named CHECK - // constraints today, so the message is rarely informative. - // Surface what we have via context. + // The engine reports a `CHECK` failure by the column the + // constraint sits on; the runtime's enrichment resolves the + // table, the offending value, and — the teaching moment — + // the rule itself (ADR-0029 §10). When the rule could not + // be resolved, the plain hint stands on its own. let table = ctx_table(ctx); let column = ctx_column(ctx); - match ctx.operation { - Some(Operation::Update) => fe( - t!( - "error.check.update.headline", - table = table, - column = column - ), - verbose_hint(ctx, t!("error.check.update.hint", column = column)), - ), - _ => fe( - t!( - "error.check.insert.headline", - table = table, - column = column - ), - verbose_hint(ctx, t!("error.check.insert.hint", column = column)), - ), - } + let is_update = matches!(ctx.operation, Some(Operation::Update)); + let headline = if is_update { + t!("error.check.update.headline", table = table, column = column) + } else { + t!("error.check.insert.headline", table = table, column = column) + }; + let hint = ctx.check_rule.as_ref().map_or_else( + || { + if is_update { + t!("error.check.update.hint", column = column) + } else { + t!("error.check.insert.hint", column = column) + } + }, + |rule| { + let value = ctx_value(ctx); + if is_update { + t!( + "error.check.update.hint_with_rule", + value = value, + rule = rule, + column = column + ) + } else { + t!( + "error.check.insert.hint_with_rule", + value = value, + rule = rule, + column = column + ) + } + }, + ); + fe(headline, verbose_hint(ctx, hint)) } // ---- not_found / already_exists -------------------------------- @@ -857,6 +883,27 @@ mod tests { assert!(f.headline.contains("age")); } + #[test] + fn check_with_a_resolved_rule_names_the_rule_and_value() { + // ADR-0029 §10: when enrichment resolves the column's + // compiled CHECK expression and the offending value, + // the hint names both. + let err = sqlite( + "CHECK constraint failed: score", + SqliteErrorKind::UniqueViolation, + ); + let mut ctx = ctx_with(Operation::Insert); + ctx.table = Some("T".to_string()); + ctx.column = Some("score".to_string()); + ctx.value = Some("-5".to_string()); + ctx.check_rule = Some("\"score\" >= 0".to_string()); + let f = translate(&err, &ctx); + assert!(f.headline.contains("check constraint refused")); + let hint = f.hint.expect("a verbose hint"); + assert!(hint.contains("-5"), "the offending value: {hint}"); + assert!(hint.contains("\"score\" >= 0"), "the rule: {hint}"); + } + // ---- not_found ---- #[test] diff --git a/src/runtime.rs b/src/runtime.rs index f954d18..099cdc3 100644 --- a/src/runtime.rs +++ b/src/runtime.rs @@ -1248,6 +1248,8 @@ pub async fn enrich_dsl_failure( enrich_unique_violation(database, command, message).await } else if lower.contains("not null constraint failed") { enrich_not_null_violation(command, message) + } else if lower.contains("check constraint failed") { + enrich_check_violation(database, command, message).await } else if lower.contains("foreign key constraint failed") { enrich_fk_violation(database, command).await } else { @@ -1255,6 +1257,41 @@ pub async fn enrich_dsl_failure( } } +/// Enrich a `CHECK` violation (ADR-0029 §10). The engine +/// reports `CHECK constraint failed: ` — the column the +/// constraint sits on, unqualified. We pair that with the +/// command's table, the value the user supplied for the column, +/// and the column's compiled `CHECK` expression so the friendly +/// error can name the rule that was broken. +async fn enrich_check_violation( + database: &Database, + command: &Command, + message: &str, +) -> crate::friendly::FailureContext { + let mut facts = crate::friendly::FailureContext::default(); + let Some((_, after)) = message.split_once(':') else { + return facts; + }; + let column = after.trim(); + let table = command.target_table(); + if column.is_empty() || table.is_empty() { + return facts; + } + facts.column = Some(column.to_string()); + facts.table = Some(table.to_string()); + // The value the user supplied for the constrained column. + facts.value = user_value_for_column_with_schema(database, command, table, column) + .await + .map(|v| v.to_string()); + // The rule itself — the column's compiled CHECK expression. + if let Ok(desc) = database.describe_table(table.to_string(), None).await + && let Some(col) = desc.columns.iter().find(|c| c.name == column) + { + facts.check_rule.clone_from(&col.check); + } + facts +} + async fn enrich_unique_violation( database: &Database, command: &Command, diff --git a/tests/friendly_enrichment.rs b/tests/friendly_enrichment.rs index c303b23..c4e6b5a 100644 --- a/tests/friendly_enrichment.rs +++ b/tests/friendly_enrichment.rs @@ -21,6 +21,7 @@ use rdbms_playground::db::{Database, DbError, SqliteErrorKind}; use rdbms_playground::dsl::{ action::ReferentialAction, ColumnSpec, Command, RowFilter, Type, Value, }; +use rdbms_playground::dsl::parser::parse_command; use rdbms_playground::runtime::enrich_dsl_failure; fn rt() -> Runtime { @@ -475,6 +476,64 @@ fn enrich_fk_delete_resolves_child_table() { }); } +// ---- CHECK (ADR-0029 §10) --------------------------------------- + +#[test] +fn enrich_check_insert_resolves_table_column_value_and_rule() { + let db = db(); + rt().block_on(async { + // `Scores(id serial pk)` plus a non-PK `score` column + // carrying `CHECK (score >= 0)`. + db.create_table( + "Scores".to_string(), + vec![ColumnSpec::new("id".to_string(), Type::Serial)], + vec!["id".to_string()], + None, + ) + .await + .unwrap(); + let score_spec = match parse_command( + "create table __probe with pk score(int) check (score >= 0)", + ) + .expect("probe create parses") + { + Command::CreateTable { columns, .. } => { + columns.into_iter().next().expect("one column") + } + other => panic!("expected CreateTable, got {other:?}"), + }; + db.add_column("Scores".to_string(), score_spec, None) + .await + .unwrap(); + + // An insert that violates the CHECK. + let cmd = Command::Insert { + table: "Scores".to_string(), + columns: Some(vec!["score".to_string()]), + values: vec![Value::Number("-5".to_string())], + }; + let err = db + .insert( + "Scores".to_string(), + Some(vec!["score".to_string()]), + vec![Value::Number("-5".to_string())], + None, + ) + .await + .unwrap_err(); + + let facts = enrich_dsl_failure(&db, &cmd, &err).await; + assert_eq!(facts.table.as_deref(), Some("Scores")); + assert_eq!(facts.column.as_deref(), Some("score")); + assert_eq!(facts.value.as_deref(), Some("-5")); + let rule = facts.check_rule.expect("the CHECK rule is resolved"); + assert!( + rule.contains("score"), + "the resolved rule names the column: {rule}", + ); + }); +} + // ---- non-engine error → empty enrichment ------------------------ #[test] diff --git a/tests/typing_surface/constraints.rs b/tests/typing_surface/constraints.rs new file mode 100644 index 0000000..8790f44 --- /dev/null +++ b/tests/typing_surface/constraints.rs @@ -0,0 +1,149 @@ +//! Matrix coverage for ADR-0029 column constraints — the +//! `not null` / `unique` / `default` / `check` suffix on +//! `create table` & `add column`, and the `add constraint` / +//! `drop constraint` commands (ADR-0029 §2). + +use crate::typing_surface::*; +use rdbms_playground::input_render::InputState; + +// --- create table / add column constraint suffix -------------- + +#[test] +fn create_table_default_suffix_parses() { + let a = assess_at_end( + "create table Books with pk isbn(text) default '000'", + &schema_empty(), + ); + assert!(matches!(a.state, InputState::Valid)); + assert_eq!(a.parse_result.as_deref(), Ok("CreateTable")); + crate::snap!("create_table_default_suffix", a); +} + +#[test] +fn create_table_check_suffix_parses() { + let a = assess_at_end( + "create table Ages with pk age(int) check (age >= 0)", + &schema_empty(), + ); + assert!(matches!(a.state, InputState::Valid)); + assert_eq!(a.parse_result.as_deref(), Ok("CreateTable")); + crate::snap!("create_table_check_suffix", a); +} + +#[test] +fn add_column_check_suffix_parses() { + let a = assess_at_end( + "add column to Customers: note (text) check (note like 'A%')", + &schema_multi_table(), + ); + assert!(matches!(a.state, InputState::Valid)); + assert_eq!(a.parse_result.as_deref(), Ok("AddColumn")); + crate::snap!("add_column_check_suffix", a); +} + +#[test] +fn add_column_after_type_offers_constraint_keywords() { + let a = assess_at_end( + "add column to Customers: note (text) ", + &schema_multi_table(), + ); + assert_candidate_present(&a, &["not", "unique", "default", "check"]); + crate::snap!("add_column_constraint_suffix", a); +} + +// --- add constraint ------------------------------------------- + +#[test] +fn after_add_offers_constraint_branch() { + let a = assess_at_end("add ", &schema_multi_table()); + assert!(matches!(a.state, InputState::IncompleteAtEof)); + assert_candidate_present(&a, &["constraint"]); + crate::snap!("after_add_constraint_branch", a); +} + +#[test] +fn add_constraint_offers_the_four_kinds() { + let a = assess_at_end("add constraint ", &schema_multi_table()); + assert!(matches!(a.state, InputState::IncompleteAtEof)); + assert_candidate_present(&a, &["not", "unique", "default", "check"]); + crate::snap!("add_constraint_kinds", a); +} + +#[test] +fn add_constraint_to_offers_table_names() { + let a = assess_at_end("add constraint not null to ", &schema_multi_table()); + assert!(matches!(a.state, InputState::IncompleteAtEof)); + assert_candidate_present(&a, &["Customers", "Orders"]); + crate::snap!("add_constraint_to_tables", a); +} + +#[test] +fn add_constraint_dot_narrows_to_table_columns() { + let a = assess_at_end("add constraint unique to Orders.", &schema_multi_table()); + assert!(matches!(a.state, InputState::IncompleteAtEof)); + assert_candidate_present(&a, &["OrderId", "CustId", "Total"]); + assert_no_candidate_named(&a, &["id", "Name"]); + crate::snap!("add_constraint_dot_columns", a); +} + +#[test] +fn complete_add_constraint_not_null_parses() { + let a = assess_at_end( + "add constraint not null to Customers.Name", + &schema_multi_table(), + ); + assert!(matches!(a.state, InputState::Valid)); + assert_eq!(a.parse_result.as_deref(), Ok("AddConstraint")); + crate::snap!("add_constraint_not_null_complete", a); +} + +#[test] +fn complete_add_constraint_default_parses() { + let a = assess_at_end( + "add constraint default 0 to Orders.Total", + &schema_multi_table(), + ); + assert!(matches!(a.state, InputState::Valid)); + assert_eq!(a.parse_result.as_deref(), Ok("AddConstraint")); + crate::snap!("add_constraint_default_complete", a); +} + +#[test] +fn complete_add_constraint_check_parses() { + let a = assess_at_end( + "add constraint check (Total >= 0) to Orders.Total", + &schema_multi_table(), + ); + assert!(matches!(a.state, InputState::Valid)); + assert_eq!(a.parse_result.as_deref(), Ok("AddConstraint")); + crate::snap!("add_constraint_check_complete", a); +} + +// --- drop constraint ------------------------------------------ + +#[test] +fn after_drop_offers_constraint_branch() { + let a = assess_at_end("drop ", &schema_multi_table()); + assert!(matches!(a.state, InputState::IncompleteAtEof)); + assert_candidate_present(&a, &["constraint"]); + crate::snap!("after_drop_constraint_branch", a); +} + +#[test] +fn drop_constraint_offers_the_four_kinds() { + let a = assess_at_end("drop constraint ", &schema_multi_table()); + assert!(matches!(a.state, InputState::IncompleteAtEof)); + assert_candidate_present(&a, &["not", "unique", "default", "check"]); + crate::snap!("drop_constraint_kinds", a); +} + +#[test] +fn complete_drop_constraint_parses() { + let a = assess_at_end( + "drop constraint unique from Customers.Name", + &schema_multi_table(), + ); + assert!(matches!(a.state, InputState::Valid)); + assert_eq!(a.parse_result.as_deref(), Ok("DropConstraint")); + crate::snap!("drop_constraint_complete", a); +} diff --git a/tests/typing_surface/mod.rs b/tests/typing_surface/mod.rs index fd3ec7c..1364fc9 100644 --- a/tests/typing_surface/mod.rs +++ b/tests/typing_surface/mod.rs @@ -35,6 +35,7 @@ pub mod drop_column; pub mod drop_relationship; pub mod add_relationship; pub mod index_ops; +pub mod constraints; pub mod rename_change_column; pub mod app_commands; pub mod candidate_ordering; diff --git a/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__add_column_after_type_offers_constraint_keywords@add_column_constraint_suffix.snap b/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__add_column_after_type_offers_constraint_keywords@add_column_constraint_suffix.snap new file mode 100644 index 0000000..73faabf --- /dev/null +++ b/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__add_column_after_type_offers_constraint_keywords@add_column_constraint_suffix.snap @@ -0,0 +1,63 @@ +--- +source: tests/typing_surface/constraints.rs +description: "input=\"add column to Customers: note (text) \" cursor=37" +expression: "& a" +--- +Assessment { + input: "add column to Customers: note (text) ", + cursor: 37, + state: Valid, + hint: Some( + Candidates { + items: [ + Candidate { + text: "not", + kind: Keyword, + }, + Candidate { + text: "unique", + kind: Keyword, + }, + Candidate { + text: "default", + kind: Keyword, + }, + Candidate { + text: "check", + kind: Keyword, + }, + ], + selected: None, + }, + ), + completion: Some( + Completion { + replaced_range: ( + 37, + 37, + ), + partial_prefix: "", + candidates: [ + Candidate { + text: "not", + kind: Keyword, + }, + Candidate { + text: "unique", + kind: Keyword, + }, + Candidate { + text: "default", + kind: Keyword, + }, + Candidate { + text: "check", + kind: Keyword, + }, + ], + }, + ), + parse_result: Ok( + "AddColumn", + ), +} diff --git a/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__add_column_check_suffix_parses@add_column_check_suffix.snap b/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__add_column_check_suffix_parses@add_column_check_suffix.snap new file mode 100644 index 0000000..03c7a36 --- /dev/null +++ b/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__add_column_check_suffix_parses@add_column_check_suffix.snap @@ -0,0 +1,45 @@ +--- +source: tests/typing_surface/constraints.rs +description: "input=\"add column to Customers: note (text) check (note like 'A%')\" cursor=59" +expression: "& a" +--- +Assessment { + input: "add column to Customers: note (text) check (note like 'A%')", + cursor: 59, + state: Valid, + hint: Some( + Prose( + "no such column `note` on table `Customers`", + ), + ), + completion: Some( + Completion { + replaced_range: ( + 59, + 59, + ), + partial_prefix: "", + candidates: [ + Candidate { + text: "not", + kind: Keyword, + }, + Candidate { + text: "unique", + kind: Keyword, + }, + Candidate { + text: "default", + kind: Keyword, + }, + Candidate { + text: "check", + kind: Keyword, + }, + ], + }, + ), + parse_result: Ok( + "AddColumn", + ), +} diff --git a/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__add_constraint_dot_narrows_to_table_columns@add_constraint_dot_columns.snap b/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__add_constraint_dot_narrows_to_table_columns@add_constraint_dot_columns.snap new file mode 100644 index 0000000..143e988 --- /dev/null +++ b/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__add_constraint_dot_narrows_to_table_columns@add_constraint_dot_columns.snap @@ -0,0 +1,55 @@ +--- +source: tests/typing_surface/constraints.rs +description: "input=\"add constraint unique to Orders.\" cursor=32" +expression: "& a" +--- +Assessment { + input: "add constraint unique to Orders.", + cursor: 32, + state: IncompleteAtEof, + hint: Some( + Candidates { + items: [ + Candidate { + text: "CustId", + kind: Identifier, + }, + Candidate { + text: "OrderId", + kind: Identifier, + }, + Candidate { + text: "Total", + kind: Identifier, + }, + ], + selected: None, + }, + ), + completion: Some( + Completion { + replaced_range: ( + 32, + 32, + ), + partial_prefix: "", + candidates: [ + Candidate { + text: "CustId", + kind: Identifier, + }, + Candidate { + text: "OrderId", + kind: Identifier, + }, + Candidate { + text: "Total", + kind: Identifier, + }, + ], + }, + ), + parse_result: Err( + "Invalid(at_eof)", + ), +} diff --git a/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__add_constraint_offers_the_four_kinds@add_constraint_kinds.snap b/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__add_constraint_offers_the_four_kinds@add_constraint_kinds.snap new file mode 100644 index 0000000..e9437d1 --- /dev/null +++ b/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__add_constraint_offers_the_four_kinds@add_constraint_kinds.snap @@ -0,0 +1,63 @@ +--- +source: tests/typing_surface/constraints.rs +description: "input=\"add constraint \" cursor=15" +expression: "& a" +--- +Assessment { + input: "add constraint ", + cursor: 15, + state: IncompleteAtEof, + hint: Some( + Candidates { + items: [ + Candidate { + text: "not", + kind: Keyword, + }, + Candidate { + text: "unique", + kind: Keyword, + }, + Candidate { + text: "default", + kind: Keyword, + }, + Candidate { + text: "check", + kind: Keyword, + }, + ], + selected: None, + }, + ), + completion: Some( + Completion { + replaced_range: ( + 15, + 15, + ), + partial_prefix: "", + candidates: [ + Candidate { + text: "not", + kind: Keyword, + }, + Candidate { + text: "unique", + kind: Keyword, + }, + Candidate { + text: "default", + kind: Keyword, + }, + Candidate { + text: "check", + kind: Keyword, + }, + ], + }, + ), + parse_result: Err( + "Invalid(at_eof)", + ), +} diff --git a/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__add_constraint_to_offers_table_names@add_constraint_to_tables.snap b/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__add_constraint_to_offers_table_names@add_constraint_to_tables.snap new file mode 100644 index 0000000..468c90a --- /dev/null +++ b/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__add_constraint_to_offers_table_names@add_constraint_to_tables.snap @@ -0,0 +1,47 @@ +--- +source: tests/typing_surface/constraints.rs +description: "input=\"add constraint not null to \" cursor=27" +expression: "& a" +--- +Assessment { + input: "add constraint not null to ", + cursor: 27, + state: IncompleteAtEof, + hint: Some( + Candidates { + items: [ + Candidate { + text: "Customers", + kind: Identifier, + }, + Candidate { + text: "Orders", + kind: Identifier, + }, + ], + selected: None, + }, + ), + completion: Some( + Completion { + replaced_range: ( + 27, + 27, + ), + partial_prefix: "", + candidates: [ + Candidate { + text: "Customers", + kind: Identifier, + }, + Candidate { + text: "Orders", + kind: Identifier, + }, + ], + }, + ), + parse_result: Err( + "Invalid(at_eof)", + ), +} diff --git a/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__after_add_offers_constraint_branch@after_add_constraint_branch.snap b/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__after_add_offers_constraint_branch@after_add_constraint_branch.snap new file mode 100644 index 0000000..4e9f69b --- /dev/null +++ b/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__after_add_offers_constraint_branch@after_add_constraint_branch.snap @@ -0,0 +1,63 @@ +--- +source: tests/typing_surface/constraints.rs +description: "input=\"add \" cursor=4" +expression: "& a" +--- +Assessment { + input: "add ", + cursor: 4, + state: IncompleteAtEof, + hint: Some( + Candidates { + items: [ + Candidate { + text: "column", + kind: Keyword, + }, + Candidate { + text: "index", + kind: Keyword, + }, + Candidate { + text: "constraint", + kind: Keyword, + }, + Candidate { + text: "1:n", + kind: Keyword, + }, + ], + selected: None, + }, + ), + completion: Some( + Completion { + replaced_range: ( + 4, + 4, + ), + partial_prefix: "", + candidates: [ + Candidate { + text: "column", + kind: Keyword, + }, + Candidate { + text: "index", + kind: Keyword, + }, + Candidate { + text: "constraint", + kind: Keyword, + }, + Candidate { + text: "1:n", + kind: Keyword, + }, + ], + }, + ), + parse_result: Err( + "Invalid(at_eof)", + ), +} diff --git a/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__after_drop_offers_constraint_branch@after_drop_constraint_branch.snap b/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__after_drop_offers_constraint_branch@after_drop_constraint_branch.snap new file mode 100644 index 0000000..3ac9bc2 --- /dev/null +++ b/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__after_drop_offers_constraint_branch@after_drop_constraint_branch.snap @@ -0,0 +1,71 @@ +--- +source: tests/typing_surface/constraints.rs +description: "input=\"drop \" cursor=5" +expression: "& a" +--- +Assessment { + input: "drop ", + cursor: 5, + state: IncompleteAtEof, + hint: Some( + Candidates { + items: [ + Candidate { + text: "column", + kind: Keyword, + }, + Candidate { + text: "relationship", + kind: Keyword, + }, + Candidate { + text: "table", + kind: Keyword, + }, + Candidate { + text: "index", + kind: Keyword, + }, + Candidate { + text: "constraint", + kind: Keyword, + }, + ], + selected: None, + }, + ), + completion: Some( + Completion { + replaced_range: ( + 5, + 5, + ), + partial_prefix: "", + candidates: [ + Candidate { + text: "column", + kind: Keyword, + }, + Candidate { + text: "relationship", + kind: Keyword, + }, + Candidate { + text: "table", + kind: Keyword, + }, + Candidate { + text: "index", + kind: Keyword, + }, + Candidate { + text: "constraint", + kind: Keyword, + }, + ], + }, + ), + parse_result: Err( + "Invalid(at_eof)", + ), +} diff --git a/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__complete_add_constraint_check_parses@add_constraint_check_complete.snap b/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__complete_add_constraint_check_parses@add_constraint_check_complete.snap new file mode 100644 index 0000000..f99827d --- /dev/null +++ b/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__complete_add_constraint_check_parses@add_constraint_check_complete.snap @@ -0,0 +1,19 @@ +--- +source: tests/typing_surface/constraints.rs +description: "input=\"add constraint check (Total >= 0) to Orders.Total\" cursor=49" +expression: "& a" +--- +Assessment { + input: "add constraint check (Total >= 0) to Orders.Total", + cursor: 49, + state: Valid, + hint: Some( + Prose( + "Submit with Enter", + ), + ), + completion: None, + parse_result: Ok( + "AddConstraint", + ), +} diff --git a/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__complete_add_constraint_default_parses@add_constraint_default_complete.snap b/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__complete_add_constraint_default_parses@add_constraint_default_complete.snap new file mode 100644 index 0000000..04a1815 --- /dev/null +++ b/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__complete_add_constraint_default_parses@add_constraint_default_complete.snap @@ -0,0 +1,19 @@ +--- +source: tests/typing_surface/constraints.rs +description: "input=\"add constraint default 0 to Orders.Total\" cursor=40" +expression: "& a" +--- +Assessment { + input: "add constraint default 0 to Orders.Total", + cursor: 40, + state: Valid, + hint: Some( + Prose( + "Submit with Enter", + ), + ), + completion: None, + parse_result: Ok( + "AddConstraint", + ), +} diff --git a/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__complete_add_constraint_not_null_parses@add_constraint_not_null_complete.snap b/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__complete_add_constraint_not_null_parses@add_constraint_not_null_complete.snap new file mode 100644 index 0000000..dd6a171 --- /dev/null +++ b/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__complete_add_constraint_not_null_parses@add_constraint_not_null_complete.snap @@ -0,0 +1,19 @@ +--- +source: tests/typing_surface/constraints.rs +description: "input=\"add constraint not null to Customers.Name\" cursor=41" +expression: "& a" +--- +Assessment { + input: "add constraint not null to Customers.Name", + cursor: 41, + state: Valid, + hint: Some( + Prose( + "Submit with Enter", + ), + ), + completion: None, + parse_result: Ok( + "AddConstraint", + ), +} diff --git a/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__complete_drop_constraint_parses@drop_constraint_complete.snap b/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__complete_drop_constraint_parses@drop_constraint_complete.snap new file mode 100644 index 0000000..65d07ea --- /dev/null +++ b/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__complete_drop_constraint_parses@drop_constraint_complete.snap @@ -0,0 +1,19 @@ +--- +source: tests/typing_surface/constraints.rs +description: "input=\"drop constraint unique from Customers.Name\" cursor=42" +expression: "& a" +--- +Assessment { + input: "drop constraint unique from Customers.Name", + cursor: 42, + state: Valid, + hint: Some( + Prose( + "Submit with Enter", + ), + ), + completion: None, + parse_result: Ok( + "DropConstraint", + ), +} diff --git a/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__create_table_check_suffix_parses@create_table_check_suffix.snap b/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__create_table_check_suffix_parses@create_table_check_suffix.snap new file mode 100644 index 0000000..fab04e4 --- /dev/null +++ b/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__create_table_check_suffix_parses@create_table_check_suffix.snap @@ -0,0 +1,19 @@ +--- +source: tests/typing_surface/constraints.rs +description: "input=\"create table Ages with pk age(int) check (age >= 0)\" cursor=51" +expression: "& a" +--- +Assessment { + input: "create table Ages with pk age(int) check (age >= 0)", + cursor: 51, + state: Valid, + hint: Some( + Prose( + "Submit with Enter", + ), + ), + completion: None, + parse_result: Ok( + "CreateTable", + ), +} diff --git a/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__create_table_default_suffix_parses@create_table_default_suffix.snap b/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__create_table_default_suffix_parses@create_table_default_suffix.snap new file mode 100644 index 0000000..589f459 --- /dev/null +++ b/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__create_table_default_suffix_parses@create_table_default_suffix.snap @@ -0,0 +1,19 @@ +--- +source: tests/typing_surface/constraints.rs +description: "input=\"create table Books with pk isbn(text) default '000'\" cursor=51" +expression: "& a" +--- +Assessment { + input: "create table Books with pk isbn(text) default '000'", + cursor: 51, + state: Valid, + hint: Some( + Prose( + "Submit with Enter", + ), + ), + completion: None, + parse_result: Ok( + "CreateTable", + ), +} diff --git a/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__drop_constraint_offers_the_four_kinds@drop_constraint_kinds.snap b/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__drop_constraint_offers_the_four_kinds@drop_constraint_kinds.snap new file mode 100644 index 0000000..4f91b13 --- /dev/null +++ b/tests/typing_surface/snapshots/typing_surface_matrix__typing_surface__constraints__drop_constraint_offers_the_four_kinds@drop_constraint_kinds.snap @@ -0,0 +1,63 @@ +--- +source: tests/typing_surface/constraints.rs +description: "input=\"drop constraint \" cursor=16" +expression: "& a" +--- +Assessment { + input: "drop constraint ", + cursor: 16, + state: IncompleteAtEof, + hint: Some( + Candidates { + items: [ + Candidate { + text: "not", + kind: Keyword, + }, + Candidate { + text: "unique", + kind: Keyword, + }, + Candidate { + text: "default", + kind: Keyword, + }, + Candidate { + text: "check", + kind: Keyword, + }, + ], + selected: None, + }, + ), + completion: Some( + Completion { + replaced_range: ( + 16, + 16, + ), + partial_prefix: "", + candidates: [ + Candidate { + text: "not", + kind: Keyword, + }, + Candidate { + text: "unique", + kind: Keyword, + }, + Candidate { + text: "default", + kind: Keyword, + }, + Candidate { + text: "check", + kind: Keyword, + }, + ], + }, + ), + parse_result: Err( + "Invalid(at_eof)", + ), +}