Skip to main content

uqa_sql/schema/table_creation/
column_declarations.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! The checks `transformColumnDefinition` makes of a column definition once `transformCreateStmt` or `transformAlterTableStmt` has found the relation: an array of SERIAL before the column's type is looked up, then the placement of constraint attributes, as `transformConstraintAttrs` checks it, and each clause against the clauses written before it.
8
9use crate::ast::{ColumnClause, ColumnClauseKind, ColumnDeclaration};
10use crate::SQLError;
11
12/// The relation a column definition belongs to.
13#[derive(Clone, Copy)]
14pub struct ColumnDeclarationTarget<'a> {
15    /// The relation's name as the statement writes it.
16    pub table: &'a str,
17    /// The relation is partitioned.
18    pub partitioned: bool,
19    /// The relation is a partition.
20    pub partition: bool,
21}
22
23/// `array of serial is not implemented`, which `transformColumnDefinition` reports before it looks up the column's type.
24pub fn check_serial_array(declaration: &ColumnDeclaration) -> Result<(), SQLError> {
25    if declaration.serial_array {
26        return Err(error("0A000", "array of serial is not implemented".into()));
27    }
28    Ok(())
29}
30
31/// The attribute placement and clause conflicts `transformColumnDefinition` reports for `column` after it looks up the column's type. A PRIMARY KEY or UNIQUE clause made DEFERRABLE or INITIALLY DEFERRED is reported as unsupported after every other check of the statement passes, so the result says whether the column writes one.
32pub fn check_column_declaration(
33    declaration: &ColumnDeclaration,
34    column: &str,
35    target: ColumnDeclarationTarget<'_>,
36) -> Result<bool, SQLError> {
37    let deferrable_key = check_constraint_attributes(&declaration.clauses)?;
38    ClauseConflicts::new(declaration, column, target, false).check()?;
39    Ok(deferrable_key)
40}
41
42/// The same ordered checks for a foreign-table column. Unsupported keys are reported at their clause, after attribute placement and any preceding conflicts.
43pub fn check_foreign_column_declaration(
44    declaration: &ColumnDeclaration,
45    column: &str,
46    table: &str,
47) -> Result<(), SQLError> {
48    check_constraint_attributes(&declaration.clauses)?;
49    ClauseConflicts::new(
50        declaration,
51        column,
52        ColumnDeclarationTarget {
53            table,
54            partitioned: false,
55            partition: false,
56        },
57        true,
58    )
59    .check()
60}
61
62pub(crate) fn foreign_table_constraint_error(kind: &str) -> SQLError {
63    SQLError::Unsupported(format!(
64        "{kind} constraints are not supported on foreign tables"
65    ))
66}
67
68/// `transformConstraintAttrs`: DEFERRABLE, NOT DEFERRABLE, INITIALLY DEFERRED and INITIALLY IMMEDIATE follow a PRIMARY KEY, UNIQUE or REFERENCES clause, ENFORCED and NOT ENFORCED a CHECK or REFERENCES clause, each at most once.
69fn check_constraint_attributes(clauses: &[ColumnClause]) -> Result<bool, SQLError> {
70    use ColumnClauseKind::{
71        Check, Deferrable, Enforced, ForeignKey, InitiallyDeferred, InitiallyImmediate,
72        NotDeferrable, NotEnforced, PrimaryKey, Unique,
73    };
74    let mut last = None;
75    let mut deferrable = false;
76    let mut initially_deferred = false;
77    let mut saw_deferrability = false;
78    let mut saw_initially = false;
79    let mut saw_enforced = false;
80    let mut deferrable_key = false;
81    let syntax = |message: &str| Err(error("42601", message.into()));
82    for clause in clauses {
83        let supports_timing = matches!(last, Some(PrimaryKey | Unique | ForeignKey));
84        match clause.kind {
85            Deferrable | NotDeferrable => {
86                let written = if clause.kind == Deferrable {
87                    "DEFERRABLE"
88                } else {
89                    "NOT DEFERRABLE"
90                };
91                if !supports_timing {
92                    return syntax(&format!("misplaced {written} clause"));
93                }
94                if saw_deferrability {
95                    return syntax("multiple DEFERRABLE/NOT DEFERRABLE clauses not allowed");
96                }
97                saw_deferrability = true;
98                deferrable = clause.kind == Deferrable;
99                if !deferrable && saw_initially && initially_deferred {
100                    return syntax("constraint declared INITIALLY DEFERRED must be DEFERRABLE");
101                }
102            }
103            InitiallyDeferred | InitiallyImmediate => {
104                let written = if clause.kind == InitiallyDeferred {
105                    "INITIALLY DEFERRED"
106                } else {
107                    "INITIALLY IMMEDIATE"
108                };
109                if !supports_timing {
110                    return syntax(&format!("misplaced {written} clause"));
111                }
112                if saw_initially {
113                    return syntax("multiple INITIALLY IMMEDIATE/DEFERRED clauses not allowed");
114                }
115                saw_initially = true;
116                initially_deferred = clause.kind == InitiallyDeferred;
117                if initially_deferred {
118                    // INITIALLY DEFERRED alone makes the constraint DEFERRABLE.
119                    if !saw_deferrability {
120                        deferrable = true;
121                    } else if !deferrable {
122                        return syntax("constraint declared INITIALLY DEFERRED must be DEFERRABLE");
123                    }
124                }
125            }
126            Enforced | NotEnforced => {
127                let written = if clause.kind == Enforced {
128                    "ENFORCED"
129                } else {
130                    "NOT ENFORCED"
131                };
132                if !matches!(last, Some(Check | ForeignKey)) {
133                    return syntax(&format!("misplaced {written} clause"));
134                }
135                if saw_enforced {
136                    return syntax("multiple ENFORCED/NOT ENFORCED clauses not allowed");
137                }
138                saw_enforced = true;
139            }
140            kind => {
141                last = Some(kind);
142                deferrable = false;
143                initially_deferred = false;
144                saw_deferrability = false;
145                saw_initially = false;
146                saw_enforced = false;
147            }
148        }
149        deferrable_key |= deferrable && matches!(last, Some(PrimaryKey | Unique));
150    }
151    Ok(deferrable_key)
152}
153
154/// The nextval default that SERIAL adds after the written clauses.
155static SERIAL_DEFAULT: ColumnClause = ColumnClause {
156    kind: ColumnClauseKind::Default,
157    name: None,
158    no_inherit: false,
159};
160
161/// What a column's clauses have said about its nullability so far.
162#[derive(Clone, Copy, PartialEq, Eq)]
163enum Nullability {
164    Unspecified,
165    Null,
166    NotNull,
167}
168
169/// The clauses that `transformColumnDefinition` allows once each and not together.
170#[derive(Default)]
171struct SeenClauses {
172    default: bool,
173    identity: bool,
174    generated: bool,
175}
176
177struct SeenNotNull<'a> {
178    name: Option<&'a str>,
179    no_inherit: bool,
180}
181
182/// The state `transformColumnDefinition` keeps while it reads a column's clauses.
183struct ClauseConflicts<'a> {
184    declaration: &'a ColumnDeclaration,
185    column: &'a str,
186    target: ColumnDeclarationTarget<'a>,
187    foreign: bool,
188    need_not_null: bool,
189    disallow_no_inherit: bool,
190    nullability: Nullability,
191    not_null: Option<SeenNotNull<'a>>,
192    seen: SeenClauses,
193}
194
195impl<'a> ClauseConflicts<'a> {
196    fn new(
197        declaration: &'a ColumnDeclaration,
198        column: &'a str,
199        target: ColumnDeclarationTarget<'a>,
200        foreign: bool,
201    ) -> Self {
202        // A SERIAL column and a column with an identity or a primary key need a not-null constraint that NO INHERIT cannot describe.
203        let disallow_no_inherit = declaration.serial
204            || declaration.clauses.iter().any(|clause| {
205                matches!(
206                    clause.kind,
207                    ColumnClauseKind::Identity | ColumnClauseKind::PrimaryKey
208                )
209            });
210        Self {
211            declaration,
212            column,
213            target,
214            foreign,
215            need_not_null: declaration.serial,
216            disallow_no_inherit,
217            nullability: Nullability::Unspecified,
218            not_null: None,
219            seen: SeenClauses::default(),
220        }
221    }
222
223    fn check(mut self) -> Result<(), SQLError> {
224        let serial = self.declaration.serial.then_some(&SERIAL_DEFAULT);
225        let declaration = self.declaration;
226        for clause in declaration.clauses.iter().chain(serial) {
227            self.read(clause)?;
228            self.check_combinations()?;
229        }
230        Ok(())
231    }
232
233    fn read(&mut self, clause: &'a ColumnClause) -> Result<(), SQLError> {
234        match clause.kind {
235            ColumnClauseKind::Null => {
236                if self.nullability == Nullability::NotNull || self.need_not_null {
237                    return Err(self.conflicting_nullability());
238                }
239                self.nullability = Nullability::Null;
240            }
241            ColumnClauseKind::NotNull => self.read_not_null(clause)?,
242            ColumnClauseKind::Default => {
243                if self.seen.default {
244                    return Err(self.column_error("multiple default values specified"));
245                }
246                self.seen.default = true;
247            }
248            ColumnClauseKind::Identity => {
249                if self.target.partition {
250                    return Err(error(
251                        "0A000",
252                        "identity columns are not supported on partitions".into(),
253                    ));
254                }
255                if self.seen.identity {
256                    return Err(self.column_error("multiple identity specifications"));
257                }
258                self.seen.identity = true;
259                match self.nullability {
260                    Nullability::Unspecified => self.need_not_null = true,
261                    Nullability::Null => return Err(self.conflicting_nullability()),
262                    Nullability::NotNull => {}
263                }
264            }
265            ColumnClauseKind::Generated => {
266                if self.seen.generated {
267                    return Err(self.column_error("multiple generation clauses specified"));
268                }
269                self.seen.generated = true;
270            }
271            ColumnClauseKind::PrimaryKey => {
272                if self.nullability == Nullability::Null {
273                    return Err(self.conflicting_nullability());
274                }
275                if self.foreign {
276                    return Err(foreign_table_constraint_error("primary key"));
277                }
278                self.need_not_null = true;
279            }
280            ColumnClauseKind::Unique if self.foreign => {
281                return Err(foreign_table_constraint_error("unique"));
282            }
283            ColumnClauseKind::ForeignKey if self.foreign => {
284                return Err(foreign_table_constraint_error("foreign key"));
285            }
286            _ => {}
287        }
288        Ok(())
289    }
290
291    fn read_not_null(&mut self, clause: &'a ColumnClause) -> Result<(), SQLError> {
292        if self.target.partitioned && clause.no_inherit {
293            return Err(error(
294                "0A000",
295                "not-null constraints on partitioned tables cannot be NO INHERIT".into(),
296            ));
297        }
298        if self.nullability == Nullability::Null {
299            return Err(self.conflicting_nullability());
300        }
301        if self.disallow_no_inherit && clause.no_inherit {
302            return Err(self.conflicting_no_inherit());
303        }
304        if self.nullability != Nullability::NotNull {
305            self.nullability = Nullability::NotNull;
306            self.need_not_null = false;
307            self.not_null = Some(SeenNotNull {
308                name: clause.name.as_deref(),
309                no_inherit: clause.no_inherit,
310            });
311        } else if let Some(previous) = &mut self.not_null {
312            if let (Some(previous_name), Some(name)) = (previous.name, clause.name.as_deref()) {
313                if previous_name != name {
314                    return Err(error(
315                        "XX000",
316                        format!(
317                            "conflicting not-null constraint names \"{previous_name}\" and \"{name}\""
318                        ),
319                    ));
320                }
321            }
322            if previous.no_inherit != clause.no_inherit {
323                return Err(self.conflicting_no_inherit());
324            }
325            if previous.name.is_none() {
326                previous.name = clause.name.as_deref();
327            }
328        }
329        Ok(())
330    }
331
332    fn check_combinations(&self) -> Result<(), SQLError> {
333        if self.seen.default && self.seen.identity {
334            return Err(self.column_error("both default and identity specified"));
335        }
336        if self.seen.default && self.seen.generated {
337            return Err(self.column_error("both default and generation expression specified"));
338        }
339        if self.seen.identity && self.seen.generated {
340            return Err(self.column_error("both identity and generation expression specified"));
341        }
342        Ok(())
343    }
344
345    fn column_error(&self, what: &str) -> SQLError {
346        error(
347            "42601",
348            format!(
349                "{what} for column \"{}\" of table \"{}\"",
350                self.column, self.target.table
351            ),
352        )
353    }
354
355    fn conflicting_nullability(&self) -> SQLError {
356        error(
357            "42601",
358            format!(
359                "conflicting NULL/NOT NULL declarations for column \"{}\" of table \"{}\"",
360                self.column, self.target.table
361            ),
362        )
363    }
364
365    fn conflicting_no_inherit(&self) -> SQLError {
366        error(
367            "42601",
368            format!(
369                "conflicting NO INHERIT declarations for not-null constraints on column \"{}\"",
370                self.column
371            ),
372        )
373    }
374}
375
376fn error(sqlstate: &str, message: String) -> SQLError {
377    SQLError::Routine {
378        sqlstate: sqlstate.into(),
379        message,
380    }
381}