razeri_plugin_contract/constraint_enforcement.rs
1// Copyright (C) 2026 Gabriel Shea
2// Licensed under either of the Apache License, Version 2.0 or the MIT
3// license, at your option.
4
5//! **Whether this engine actually checks the constraints it accepts** — the
6//! dialect's answer, and the one place the rendered DDL and the docs read it
7//! from.
8//!
9//! A `PRIMARY KEY` that is enforced and a `PRIMARY KEY` that is decoration look
10//! identical in a `CREATE TABLE`. On two of the engines Razeri renders for they
11//! are the second thing, and the dialect is where that fact is stated:
12//!
13//! - **`BigQuery`** has the words for it. `PRIMARY KEY (x) NOT ENFORCED` is the
14//! only legal spelling — a bare `PRIMARY KEY` is refused — so the engine
15//! itself makes the declaration honest.
16//! - **`Redshift`** has no such words. It accepts `PRIMARY KEY`, `FOREIGN KEY`
17//! and `UNIQUE`, enforces none of them, and there is no keyword to say so. A
18//! reader of that DDL has no way to tell it apart from `PostgreSQL`'s, which
19//! is the near-neighbour trap in its purest form.
20//!
21//! So the seam carries two things: the engine's own **keyword**, where it has
22//! one, and a **note** the renderer writes above the constraint list, where it
23//! does not. Both are the dialect's words.
24
25/// Whether the engine checks the key, unique and check constraints it accepts.
26///
27/// `#[non_exhaustive]`, on this module's whole family's rule: Razeri adds an
28/// arm when it has measured one.
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
30#[non_exhaustive]
31pub enum ConstraintEnforcement {
32 /// The engine rejects a row that violates a declared constraint. Eight of
33 /// Razeri's registered platforms, and the default: claiming an engine
34 /// checks nothing is the unusual assertion, and it is made deliberately,
35 /// one dialect at a time.
36 #[default]
37 Enforced,
38 /// **The engine accepts the declaration and checks nothing.** The
39 /// constraint is metadata — an optimizer hint, or documentation — and a
40 /// violating row is stored without complaint.
41 DeclaredOnly(DeclaredOnly),
42}
43
44impl ConstraintEnforcement {
45 /// The words appended to a rendered key constraint, or `""`.
46 ///
47 /// This is what [`crate::renderer::DdlRenderer::key_constraint_suffix`]
48 /// answers by default, so a dialect states enforcement once and the suffix
49 /// follows. Empty on an enforcing engine, and empty on a declaring engine
50 /// that has no keyword for it.
51 #[must_use]
52 pub const fn keyword(self) -> &'static str {
53 match self {
54 ConstraintEnforcement::DeclaredOnly(declared) => declared.keyword,
55 ConstraintEnforcement::Enforced => "",
56 }
57 }
58
59 /// The sentence the rendered DDL carries above a table's constraints, or
60 /// `None` where the engine enforces them.
61 ///
62 /// `None` on an engine whose *keyword* already says it: `NOT ENFORCED`
63 /// written on the constraint line is more precise than a comment above it,
64 /// and printing both would say the same thing twice in one statement.
65 #[must_use]
66 pub const fn ddl_note(self) -> Option<&'static str> {
67 match self {
68 ConstraintEnforcement::DeclaredOnly(declared) if declared.keyword.is_empty() => {
69 Some(declared.note)
70 }
71 _ => None,
72 }
73 }
74
75 /// Whether the engine leaves declared constraints unchecked — the fact the
76 /// platform docs and the conformance report read.
77 #[must_use]
78 pub const fn is_declared_only(self) -> bool {
79 matches!(self, ConstraintEnforcement::DeclaredOnly(_))
80 }
81}
82
83/// What a declaring-but-not-enforcing engine says about its own constraints.
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub struct DeclaredOnly {
86 /// **The engine's own words, appended to every key constraint it renders**
87 /// — `BigQuery`'s `" NOT ENFORCED"`, leading space included, because the
88 /// suffix is concatenated onto the constraint text.
89 ///
90 /// `""` where the engine has no such keyword. That is not a lesser answer:
91 /// it is the honest one for `Redshift`, and it is what routes the fact into
92 /// [`ConstraintEnforcement::ddl_note`] instead.
93 pub keyword: &'static str,
94 /// **One sentence saying what this engine does with a declared
95 /// constraint**, written for whoever is reading the rendered DDL.
96 ///
97 /// Say what is and is not guaranteed, and where the guarantee has to come
98 /// from instead — a reader who learns only *"not enforced"* still does not
99 /// know whether their data is safe. It is rendered inside a SQL comment,
100 /// so keep it to prose and never spell a statement.
101 ///
102 /// Required even when `keyword` is non-empty: the docs read it in both
103 /// cases, and only the DDL note is skipped where the keyword already
104 /// speaks.
105 pub note: &'static str,
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 /// The default is the enforcing engine, which is eight of Razeri's ten and
113 /// the safe thing to assume of a dialect that has not said.
114 #[test]
115 fn the_default_is_an_enforcing_engine() {
116 assert_eq!(ConstraintEnforcement::default(), ConstraintEnforcement::Enforced);
117 assert_eq!(ConstraintEnforcement::default().keyword(), "");
118 assert_eq!(ConstraintEnforcement::default().ddl_note(), None);
119 assert!(!ConstraintEnforcement::default().is_declared_only());
120 }
121
122 /// **The two shapes of "declared only", and the split between them is the
123 /// point of the type.** An engine with a keyword writes it on the
124 /// constraint; an engine without one gets the comment, and neither gets
125 /// both.
126 #[test]
127 fn a_keyword_speaks_on_the_constraint_and_a_note_speaks_above_it() {
128 let with_keyword = ConstraintEnforcement::DeclaredOnly(DeclaredOnly {
129 keyword: " NOT ENFORCED",
130 note: "keys here are optimizer metadata.",
131 });
132 assert_eq!(with_keyword.keyword(), " NOT ENFORCED");
133 assert_eq!(
134 with_keyword.ddl_note(),
135 None,
136 "the keyword is on the constraint line already: a comment saying it again is one \
137 statement making the same claim twice"
138 );
139 assert!(with_keyword.is_declared_only());
140
141 let without_keyword = ConstraintEnforcement::DeclaredOnly(DeclaredOnly {
142 keyword: "",
143 note: "this engine accepts the declaration and checks nothing.",
144 });
145 assert_eq!(without_keyword.keyword(), "");
146 assert_eq!(
147 without_keyword.ddl_note(),
148 Some("this engine accepts the declaration and checks nothing."),
149 "with no keyword the comment is the only place a reader of this DDL can learn it"
150 );
151 assert!(without_keyword.is_declared_only());
152 }
153}