constraints: CHECK-violation friendly error + typing-surface matrix (ADR-0029 §10)
Completes ADR-0029's implementation: the friendly-error layer now names the rule a CHECK violation broke, and the typing-surface matrix covers the whole constraint grammar. CHECK-violation friendly error (ADR-0029 §10): - enrich_dsl_failure gains a CHECK branch — it reads the column from the engine's `CHECK constraint failed: <column>` message, then resolves the table, the offending value, and the column's compiled CHECK expression. - FailureContext / TranslateContext carry the resolved check_rule; translate_check renders "the value <v> breaks the rule `<rule>`" when it is known, falling back to the plain hint otherwise. Typing-surface matrix: a new `constraints` submodule, 14 cells covering the create-table / add-column constraint suffix and the add-constraint / drop-constraint commands (174 → 188). 16 tests added (1 translate unit, 1 enrichment integration, 14 matrix cells).
This commit is contained in:
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
+68
-21
@@ -145,6 +145,10 @@ pub struct FailureContext {
|
||||
/// §7. Rendered through the bordered diagnostic-table
|
||||
/// renderer when present.
|
||||
pub diagnostic_table: Option<DiagnosticTable>,
|
||||
/// 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<String>,
|
||||
}
|
||||
|
||||
/// 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<DiagnosticTable>,
|
||||
/// 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<String>,
|
||||
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]
|
||||
|
||||
@@ -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: <column>` — 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,
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
+63
@@ -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",
|
||||
),
|
||||
}
|
||||
+45
@@ -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",
|
||||
),
|
||||
}
|
||||
+55
@@ -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)",
|
||||
),
|
||||
}
|
||||
+63
@@ -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)",
|
||||
),
|
||||
}
|
||||
+47
@@ -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)",
|
||||
),
|
||||
}
|
||||
+63
@@ -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)",
|
||||
),
|
||||
}
|
||||
+71
@@ -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)",
|
||||
),
|
||||
}
|
||||
+19
@@ -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",
|
||||
),
|
||||
}
|
||||
+19
@@ -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",
|
||||
),
|
||||
}
|
||||
+19
@@ -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",
|
||||
),
|
||||
}
|
||||
+19
@@ -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",
|
||||
),
|
||||
}
|
||||
+19
@@ -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",
|
||||
),
|
||||
}
|
||||
+19
@@ -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",
|
||||
),
|
||||
}
|
||||
+63
@@ -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)",
|
||||
),
|
||||
}
|
||||
Reference in New Issue
Block a user