///|
// Every action must be SET NOT NULL; mixed commands can invalidate the CHECK
// proof that PostgreSQL uses to avoid a scan, so their suffix is never dropped.
fn classify_set_not_null(words : ArrayView[String]) -> Command {
  let mut i = 0
  while i < words.length() {
    if words.get(i) != Some("ALTER") {
      return Unsupported
    }
    i += 1
    if words.get(i) == Some("COLUMN") {
      i += 1
    }
    if i >= words.length() || !index_identifier(words[i]) {
      return Unsupported
    }
    i += 1
    if words.get(i) != Some("SET") ||
      words.get(i + 1) != Some("NOT") ||
      words.get(i + 2) != Some("NULL") {
      return Unsupported
    }
    i += 3
    if i == words.length() {
      return SetNotNull
    }
    if words.get(i) != Some(",") {
      return Unsupported
    }
    i += 1
  }
  Unsupported
}

///|
fn not_null_findings(span : Span, diagnostics : Array[Diagnostic]) -> Unit {
  diagnostics.push(
    finding(
      "ML220",
      Warning,
      span,
      "SET NOT NULL requires an ACCESS EXCLUSIVE table lock",
      "Budget blocked reads/writes and lock acquisition; a scan-elision proof does not remove the table lock. Hold the transaction briefly. lock_timeout bounds individual lock waits, not the operation duration.",
    ),
  )
  diagnostics.push(
    finding(
      "ML221",
      Warning,
      span,
      "SET NOT NULL requires null-free data and may scan the table",
      "A valid CHECK proving null absence can avoid the scan if it is not dropped by the same command. Existing invalid not-null constraints are validated. Verify current data, proof eligibility, privileges and ONLY/descendant scope; offline SQL review does not establish catalog facts or scan avoidance.",
    ),
  )
}