///|
// All actions must be validations: a mixed ALTER TABLE can require a stronger
// lock, so never discard the remainder or infer its semantics from one action.
fn classify_validate_constraint(words : ArrayView[String]) -> Command {
let mut i = 0
while i < words.length() {
if words.get(i) != Some("VALIDATE") ||
words.get(i + 1) != Some("CONSTRAINT") {
return Unsupported
}
if i + 2 >= words.length() || !index_identifier(words[i + 2]) {
return Unsupported
}
i += 3
if i == words.length() {
return ValidateConstraint
}
if words.get(i) != Some(",") {
return Unsupported
}
i += 1
}
Unsupported
}
///|
fn constraint_validation_findings(
span : Span,
diagnostics : Array[Diagnostic],
) -> Unit {
diagnostics.push(
finding(
"ML180",
Warning,
span,
"Constraint validation may scan existing rows under SHARE UPDATE EXCLUSIVE locks",
"Plan scan work and maintenance/schema contention; ordinary updates can continue. Locks persist until transaction end. lock_timeout bounds individual lock waits, not the scan duration. Keep ADD CONSTRAINT and validation in separately reviewed phases.",
),
)
diagnostics.push(
finding(
"ML181",
Warning,
span,
"Constraint validation requires catalog and existing-data checks",
"Verify the named FOREIGN KEY, CHECK or NOT NULL constraint exists and is enforced; already-valid constraints need no validation work. Foreign keys also require ROW SHARE locks on referenced tables. Review violations, privileges, ONLY/descendant scope and partition behavior; offline review does not prove data satisfies the constraint.",
),
)
}