uqa_sql/schema/table_creation/
not_nulls.rs1use crate::ast::{ColumnDef, CreateTable, NotNullDeclaration};
10use crate::schema::columns::POSTGRES_SYSTEM_COLUMNS;
11use crate::schema::constraint_metadata::{
12 assign_constraint_name, identity::materialize_not_null_identity, CatalogIdentityAllocator,
13 CatalogOidClass, ConstraintMetadataError,
14};
15use crate::SQLError;
16use std::collections::BTreeSet;
17
18use super::declaration::{CreateTableAnalysisContext, InheritedDefinitions};
19
20#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct InheritedNotNull {
23 pub column: String,
24 pub name: Option<String>,
25 pub parents: usize,
26}
27
28impl InheritedNotNull {
29 pub fn record(inherited: &mut Vec<Self>, column: &str, name: Option<&str>) {
31 if let Some(existing) = inherited
32 .iter_mut()
33 .find(|existing| existing.column == column)
34 {
35 if existing.name.is_none() {
36 existing.name = name.map(str::to_owned);
37 }
38 existing.parents += 1;
39 return;
40 }
41 inherited.push(Self {
42 column: column.to_owned(),
43 name: name.map(str::to_owned),
44 parents: 1,
45 });
46 }
47}
48
49pub fn define_not_null_constraints(
51 context: &CreateTableAnalysisContext<'_>,
52 table: &mut CreateTable,
53 inherited: &InheritedDefinitions,
54 relation_oid: u32,
55 allocate: &mut CatalogIdentityAllocator<'_>,
56) -> Result<(), SQLError> {
57 let relation = uqa_core::RelationIdentity::from_legacy_name(&table.name)
58 .map_err(SQLError::Internal)?
59 .name;
60 let declared = merge_declarations(
61 &table.columns,
62 &relation,
63 std::mem::take(&mut table.not_null_declarations),
64 )?;
65 let mut names = Names {
66 relation_oid,
67 held: held_constraint_names(table, inherited),
68 used: context
69 .index_names
70 .automatic_constraint_names(&table.name)?,
71 chosen: BTreeSet::new(),
72 relation,
73 };
74 names.used.extend(names.held.iter().cloned());
75 let mut constrained = BTreeSet::new();
76 for (index, declaration) in declared {
77 let column = &mut table.columns[index];
78 let parents_give = inherited
79 .not_nulls
80 .iter()
81 .any(|constraint| constraint.column == column.name);
82 if parents_give && declaration.no_inherit {
83 return Err(SQLError::Diagnostic {
84 sqlstate: "42804".into(),
85 message: format!(
86 "cannot define not-null constraint with NO INHERIT on column \"{}\"",
87 column.name
88 ),
89 detail: Some("The column has an inherited not-null constraint.".into()),
90 hint: None,
91 });
92 }
93 let name = match declaration.name {
94 Some(name) => names.given(name, allocate)?,
95 None => names.choose(&column.name)?,
96 };
97 constrained.insert(column.name.clone());
98 store(
99 column,
100 name,
101 Locality {
102 is_local: true,
103 no_inherit: declaration.no_inherit,
104 explicit: declaration.explicit,
105 },
106 allocate,
107 )?;
108 }
109 for constraint in &inherited.not_nulls {
110 if constrained.contains(&constraint.column) {
111 continue;
112 }
113 let Some(column) = table
114 .columns
115 .iter_mut()
116 .find(|column| column.name == constraint.column)
117 else {
118 return Err(SQLError::Internal(format!(
119 "inherited NOT NULL constraint of column `{}` has no column",
120 constraint.column
121 )));
122 };
123 let name = names.inherited(constraint.name.as_deref(), &column.name)?;
124 constrained.insert(column.name.clone());
125 store(
126 column,
127 name,
128 Locality {
129 is_local: false,
130 no_inherit: false,
131 explicit: false,
132 },
133 allocate,
134 )?;
135 }
136 if let Some(column) = table
137 .columns
138 .iter()
139 .find(|column| column.not_null && !constrained.contains(&column.name))
140 {
141 return Err(SQLError::Internal(format!(
142 "NOT NULL constraint of column `{}` was not declared",
143 column.name
144 )));
145 }
146 Ok(())
147}
148
149fn merge_declarations(
151 columns: &[ColumnDef],
152 relation: &str,
153 declarations: Vec<NotNullDeclaration>,
154) -> Result<Vec<(usize, NotNullDeclaration)>, SQLError> {
155 let mut kept: Vec<(usize, NotNullDeclaration)> = Vec::new();
156 for declaration in declarations {
157 let Some(index) = columns
158 .iter()
159 .position(|column| column.name == declaration.column)
160 else {
161 if POSTGRES_SYSTEM_COLUMNS.contains(&declaration.column.as_str()) {
162 return Err(error(
163 "0A000",
164 format!(
165 "cannot add not-null constraint on system column \"{}\"",
166 declaration.column
167 ),
168 ));
169 }
170 return Err(error(
171 "42703",
172 format!(
173 "column \"{}\" of relation \"{relation}\" does not exist",
174 declaration.column
175 ),
176 ));
177 };
178 if let Some((_, existing)) = kept.iter_mut().find(|(existing, _)| *existing == index) {
179 if existing.no_inherit != declaration.no_inherit {
180 return Err(error(
181 "42601",
182 format!(
183 "conflicting NO INHERIT declaration for not-null constraint on column \"{}\"",
184 declaration.column
185 ),
186 ));
187 }
188 match (&existing.name, &declaration.name) {
189 (Some(first), Some(second)) if first != second => {
190 return Err(error(
191 "42601",
192 format!(
193 "conflicting not-null constraint names \"{first}\" and \"{second}\""
194 ),
195 ));
196 }
197 (None, Some(name)) => existing.name = Some(name.clone()),
198 _ => {}
199 }
200 existing.explicit |= declaration.explicit;
201 continue;
202 }
203 kept.push((index, declaration));
204 }
205 Ok(kept)
206}
207
208pub struct ForeignNotNullContext<'a> {
210 pub relation: &'a uqa_core::RelationIdentity,
211 pub relation_oid: u32,
212 pub checks: &'a [crate::ast::TableCheck],
213 pub names: &'a crate::schema::constraint_metadata::ConstraintNameScope,
214}
215
216pub fn define_foreign_not_null_constraints(
218 context: ForeignNotNullContext<'_>,
219 columns: &mut [ColumnDef],
220 declarations: Vec<NotNullDeclaration>,
221 allocate: &mut CatalogIdentityAllocator<'_>,
222) -> Result<(), SQLError> {
223 let declared = merge_declarations(columns, &context.relation.name, declarations)?;
224 let held = columns
225 .iter()
226 .filter_map(|column| column.check_name.clone().filter(|_| column.check.is_some()))
227 .chain(context.checks.iter().filter_map(|check| check.name.clone()))
228 .chain(context.names.events.iter().cloned())
229 .collect();
230 let mut names = Names {
231 relation: context.relation.name.clone(),
232 relation_oid: context.relation_oid,
233 held,
234 used: context.names.schema.clone(),
235 chosen: BTreeSet::new(),
236 };
237 names.used.extend(names.held.iter().cloned());
238 for (index, declaration) in declared {
239 let name = match declaration.name {
240 Some(name) => names.given(name, allocate)?,
241 None => names.choose(&declaration.column)?,
242 };
243 store(
244 &mut columns[index],
245 name,
246 Locality {
247 is_local: true,
248 no_inherit: declaration.no_inherit,
249 explicit: declaration.explicit,
250 },
251 allocate,
252 )?;
253 }
254 Ok(())
255}
256
257fn held_constraint_names(
259 table: &CreateTable,
260 inherited: &InheritedDefinitions,
261) -> BTreeSet<String> {
262 let mut held = super::declaration::cloned_constraint_names(table, inherited);
263 held.extend(table.checks.iter().filter_map(|check| check.name.clone()));
264 held.extend(
265 table
266 .columns
267 .iter()
268 .filter(|column| column.check.is_some())
269 .filter_map(|column| column.check_name.clone()),
270 );
271 held
272}
273
274struct Names {
276 relation: String,
277 relation_oid: u32,
278 held: BTreeSet<String>,
279 used: BTreeSet<String>,
280 chosen: BTreeSet<String>,
281}
282
283impl Names {
284 fn given(
286 &mut self,
287 name: String,
288 allocate: &mut CatalogIdentityAllocator<'_>,
289 ) -> Result<String, SQLError> {
290 if self.chosen.contains(&name) {
291 return Err(crate::schema::check_inheritance::duplicate_check(
292 &self.relation,
293 &name,
294 ));
295 }
296 if self.held.contains(&name) {
297 let object_id = allocate
298 .allocate_object_id("NOT NULL constraint")
299 .map_err(ConstraintMetadataError::into_sql_error)?;
300 allocate
301 .allocate_catalog_oid(CatalogOidClass::Constraint, &object_id)
302 .map_err(ConstraintMetadataError::into_sql_error)?;
303 return Err(SQLError::Diagnostic {
304 sqlstate: "23505".into(),
305 message: "duplicate key value violates unique constraint \"pg_constraint_conrelid_contypid_conname_index\"".into(),
306 detail: Some(format!(
307 "Key (conrelid, contypid, conname)=({}, 0, {name}) already exists.",
308 self.relation_oid
309 )),
310 hint: None,
311 });
312 }
313 Ok(self.take(name))
314 }
315
316 fn inherited(&mut self, preferred: Option<&str>, column: &str) -> Result<String, SQLError> {
318 match preferred {
319 Some(name) if !self.chosen.contains(name) && !self.held.contains(name) => {
320 Ok(self.take(name.to_owned()))
321 }
322 _ => self.choose(column),
323 }
324 }
325
326 fn choose(&mut self, column: &str) -> Result<String, SQLError> {
328 let mut target = None;
329 assign_constraint_name(
330 &mut target,
331 (&self.relation, column, "not_null"),
332 &mut self.used,
333 )
334 .map_err(ConstraintMetadataError::into_sql_error)?;
335 let name = target
336 .ok_or_else(|| SQLError::Internal("NOT NULL constraint name was not chosen".into()))?;
337 self.chosen.insert(name.clone());
338 Ok(name)
339 }
340
341 fn take(&mut self, name: String) -> String {
342 self.used.insert(name.clone());
343 self.chosen.insert(name.clone());
344 name
345 }
346}
347
348struct Locality {
350 is_local: bool,
351 no_inherit: bool,
352 explicit: bool,
353}
354
355fn store(
357 column: &mut ColumnDef,
358 name: String,
359 locality: Locality,
360 allocate: &mut CatalogIdentityAllocator<'_>,
361) -> Result<(), SQLError> {
362 column.not_null = true;
363 column.not_null_explicit = locality.explicit;
364 column.not_null_name = Some(name);
365 column.not_null_no_inherit = locality.no_inherit;
366 column.not_null_validated = true;
367 column.not_null_is_local = locality.is_local;
368 column.not_null_identity = None;
369 materialize_not_null_identity(column, allocate)
370 .map_err(ConstraintMetadataError::into_sql_error)?;
371 Ok(())
372}
373
374fn error(sqlstate: &str, message: String) -> SQLError {
375 SQLError::Routine {
376 sqlstate: sqlstate.into(),
377 message,
378 }
379}
380
381#[cfg(test)]
382mod tests;