uqa_sql/schema/
inheritance.rs1use crate::semantics::partition::{
10 validate_hash_partition_spec, validate_new_partition_bound, PartitionContext,
11};
12use crate::{
13 ast::{CreateTable, TableCheck, TableConstraintSet},
14 SQLError,
15};
16pub trait InheritanceCatalog {
18 fn resolve_parent(&self, name: &str) -> Result<String, SQLError>;
19 fn declared_constraints(&self, table: &str) -> Result<TableConstraintSet, String>;
20 fn check_definitions(&self, table: &str) -> Result<Vec<TableCheck>, String>;
21}
22pub struct InheritanceContext<'a> {
23 pub catalog: &'a dyn InheritanceCatalog,
24 pub partitions: PartitionContext<'a>,
25 pub roles: &'a dyn crate::expr::EngineHook,
26}
27use std::collections::BTreeSet;
28
29#[expect(
30 clippy::too_many_lines,
31 reason = "preserves DDL dependency and action order"
32)]
33pub fn prepare_create_table_hierarchy(
34 context: &InheritanceContext<'_>,
35 table: &mut CreateTable,
36) -> Result<(), SQLError> {
37 table.hierarchy.local_columns = table
38 .columns
39 .iter()
40 .map(|column| column.name.clone())
41 .collect();
42 if table.hierarchy.parents.is_empty() {
43 if table.hierarchy.partition_bound.is_some() {
44 return Err(SQLError::Internal(
45 "partition bound has no parent relation".into(),
46 ));
47 }
48 validate_partition_keys(context, table)?;
49 return Ok(());
50 }
51 let is_partition = table.hierarchy.partition_bound.is_some();
52 if is_partition && table.hierarchy.parents.len() != 1 {
53 return Err(SQLError::Internal(
54 "a partition must have exactly one parent".into(),
55 ));
56 }
57 let mut canonical_parents = Vec::with_capacity(table.hierarchy.parents.len());
58 let mut inherited_columns = Vec::new();
59 let mut inherited_checks = Vec::new();
60 let mut inherited_foreign_keys = Vec::new();
61 let mut inherited_keys = Vec::new();
62 for requested_parent in &table.hierarchy.parents {
63 let parent = context.catalog.resolve_parent(requested_parent)?;
64 if parent == table.name {
65 return Err(SQLError::Routine {
66 sqlstate: "42P17".into(),
67 message: "circular inheritance not allowed".into(),
68 });
69 }
70 let parent_hierarchy = context
71 .partitions
72 .catalog
73 .try_table_hierarchy(&parent)
74 .map_err(|error| SQLError::Internal(format!("read parent hierarchy: {error}")))?;
75 if is_partition {
76 let Some(parent_spec) = parent_hierarchy.partition_spec.as_ref() else {
77 return Err(SQLError::Routine {
78 sqlstate: "42809".into(),
79 message: format!("relation \"{requested_parent}\" is not partitioned"),
80 });
81 };
82 validate_partition_bound_strategy(
83 parent_spec.strategy,
84 table.hierarchy.partition_bound.as_ref().ok_or_else(|| {
85 SQLError::Internal("partition lost its bound during validation".into())
86 })?,
87 )?;
88 } else if parent_hierarchy.partition_spec.is_some() {
89 return Err(SQLError::Routine {
90 sqlstate: "42809".into(),
91 message: format!("cannot inherit from partitioned table \"{requested_parent}\""),
92 });
93 }
94 let mut columns = context
95 .partitions
96 .catalog
97 .try_describe_table(&parent)
98 .map_err(|error| SQLError::Internal(format!("read inherited row type: {error}")))?
99 .ok_or_else(|| SQLError::UnknownTable(parent.clone()))?;
100 for column in &mut columns {
101 column.not_null_identity = None;
102 if let Some(reference) = &mut column.references {
103 reference.catalog_identity = None;
104 reference.referenced_partitions.clear();
105 }
106 if column.not_null_no_inherit {
107 column.not_null = false;
108 column.not_null_explicit = false;
109 column.not_null_name = None;
110 column.not_null_no_inherit = false;
111 column.not_null_validated = true;
112 }
113 column.not_null_is_local = !column.not_null;
114 column.check = None;
116 column.check_name = None;
117 column.check_object_id = None;
118 column.check_catalog_oid = None;
119 column.check_is_local = true;
120 column.check_enforced = true;
121 column.check_validated = true;
122 column.check_no_inherit = false;
123 }
124 if !is_partition {
125 for column in &mut columns {
127 column.references = None;
128 if column
129 .auto_increment
130 .as_ref()
131 .is_some_and(crate::ast::AutoIncrement::is_identity)
132 {
133 column.auto_increment = None;
134 }
135 }
136 }
137 merge_columns(&mut inherited_columns, columns)?;
138 let constraints = context
139 .catalog
140 .declared_constraints(&parent)
141 .map_err(|error| SQLError::Internal(format!("read inherited constraints: {error}")))?;
142 for mut check in context
143 .catalog
144 .check_definitions(&parent)
145 .map_err(|error| SQLError::Internal(format!("read inherited CHECKs: {error}")))?
146 .into_iter()
147 .filter(|check| !check.no_inherit)
148 {
149 super::check_inheritance::bind_parent_check_columns(&parent, &mut check.expr)?;
150 check.is_local = false;
151 check.object_id = None;
152 check.catalog_oid = None;
153 check.validated = check.enforced;
154 inherited_checks.push(check);
155 }
156 if is_partition {
157 inherited_foreign_keys.extend(constraints.foreign_keys.into_iter().map(|mut key| {
158 key.catalog_identity = None;
159 key.referenced_partitions.clear();
160 key
161 }));
162 inherited_keys.extend(constraints.key_constraints.into_iter().map(|mut key| {
163 key.name = None;
164 key.catalog_identity = None;
165 key
166 }));
167 }
168 canonical_parents.push(parent);
169 }
170 merge_columns(&mut inherited_columns, std::mem::take(&mut table.columns))?;
171 table.columns = inherited_columns;
172 inherited_checks.append(&mut table.checks);
173 table.checks = inherited_checks;
174 if is_partition {
175 inherited_foreign_keys.append(&mut table.foreign_keys);
176 inherited_keys.append(&mut table.key_constraints);
177 table.foreign_keys = inherited_foreign_keys;
178 table.key_constraints = inherited_keys;
179 }
180 table.hierarchy.parents = canonical_parents;
181 validate_partition_keys(context, table)?;
182 if let (Some(parent), Some(bound)) = (
183 table.hierarchy.parents.first(),
184 table.hierarchy.partition_bound.as_ref(),
185 ) {
186 validate_new_partition_bound(&context.partitions, parent, bound)?;
187 }
188 Ok(())
189}
190
191fn validate_partition_bound_strategy(
192 strategy: crate::ast::PartitionStrategy,
193 bound: &crate::ast::PartitionBound,
194) -> Result<(), SQLError> {
195 use crate::ast::{PartitionBound, PartitionStrategy};
196 if matches!(
197 (strategy, bound),
198 (PartitionStrategy::Hash, PartitionBound::Default)
199 ) {
200 return Err(SQLError::Routine {
201 sqlstate: "42P16".into(),
202 message: "a hash-partitioned table may not have a default partition".into(),
203 });
204 }
205 let matches = matches!(bound, PartitionBound::Default)
206 || matches!(
207 (strategy, bound),
208 (PartitionStrategy::List, PartitionBound::List(_))
209 | (PartitionStrategy::Range, PartitionBound::Range { .. })
210 | (PartitionStrategy::Hash, PartitionBound::Hash { .. })
211 );
212 if matches {
213 Ok(())
214 } else {
215 Err(SQLError::Internal(
216 "partition bound strategy differs from its parent".into(),
217 ))
218 }
219}
220
221fn merge_columns(
222 merged: &mut Vec<crate::ast::ColumnDef>,
223 incoming: Vec<crate::ast::ColumnDef>,
224) -> Result<(), SQLError> {
225 for column in incoming {
226 if let Some(existing) = merged.iter_mut().find(|item| item.name == column.name) {
227 merge_same_column(existing, column)?;
228 } else {
229 merged.push(column);
230 }
231 }
232 Ok(())
233}
234
235pub fn merge_same_column(
236 inherited: &mut crate::ast::ColumnDef,
237 declared: crate::ast::ColumnDef,
238) -> Result<(), SQLError> {
239 if inherited.ty != declared.ty {
240 return Err(SQLError::Routine {
241 sqlstate: "42804".into(),
242 message: format!(
243 "inherited column \"{}\" has a type conflict",
244 inherited.name
245 ),
246 });
247 }
248 if inherited.generated.is_some() != declared.generated.is_some() {
249 return Err(SQLError::Routine {
250 sqlstate: "42P17".into(),
251 message: format!(
252 "inherited column \"{}\" has a generation conflict",
253 inherited.name
254 ),
255 });
256 }
257 let not_null_is_local = (inherited.not_null && inherited.not_null_is_local)
258 || (declared.not_null && declared.not_null_is_local);
259 if declared.not_null && (!inherited.not_null || declared.not_null_is_local) {
260 inherited.not_null_name.clone_from(&declared.not_null_name);
261 inherited.not_null_identity = declared.not_null_identity;
262 inherited.not_null_validated = declared.not_null_validated;
263 inherited.not_null_no_inherit = declared.not_null_no_inherit;
264 }
265 inherited.not_null |= declared.not_null;
266 inherited.not_null_is_local = !inherited.not_null || not_null_is_local;
267 inherited.not_null_explicit |= declared.not_null_explicit;
268 inherited.primary_key |= declared.primary_key;
269 inherited.unique |= declared.unique;
270 if declared.auto_increment.is_some() {
271 inherited.auto_increment = declared.auto_increment;
272 }
273 if declared.default.is_some() {
274 inherited.default = declared.default;
275 }
276 if declared.generated.is_some() {
277 inherited.generated = declared.generated;
278 }
279 if declared.check.is_some() {
280 inherited.check = declared.check;
281 inherited.check_name = declared.check_name;
282 inherited.check_enforced = declared.check_enforced;
283 inherited.check_validated = declared.check_validated;
284 inherited.check_no_inherit = declared.check_no_inherit;
285 inherited.check_is_local = declared.check_is_local;
286 inherited.check_object_id = declared.check_object_id;
287 }
288 if declared.references.is_some() {
289 inherited.references = declared.references;
290 }
291 Ok(())
292}
293
294fn validate_partition_keys(
295 context: &InheritanceContext<'_>,
296 table: &CreateTable,
297) -> Result<(), SQLError> {
298 let Some(spec) = table.hierarchy.partition_spec.as_ref() else {
299 return Ok(());
300 };
301 let column_names = table
302 .columns
303 .iter()
304 .map(|column| column.name.as_str())
305 .collect::<BTreeSet<_>>();
306 for key in &spec.keys {
307 let scalar = crate::plan::ExpressionPlan::lower(key.clone()).scalar;
308 let mut referenced_columns = BTreeSet::new();
309 scalar.collect_columns(&mut referenced_columns);
310 for column in referenced_columns {
311 if !column_names.contains(column.as_str()) {
312 return Err(SQLError::Routine {
313 sqlstate: "42703".into(),
314 message: format!("column \"{column}\" named in partition key does not exist"),
315 });
316 }
317 }
318 }
319 validate_hash_partition_spec(&context.partitions, spec, &table.columns)?;
320 for key in &spec.keys {
321 crate::catalog::regrole_dependencies::reject_stored_regrole_constants(
322 context.roles,
323 key,
324 None,
325 )?;
326 }
327 Ok(())
328}
329
330pub mod alter;
331
332pub mod detachment;
333pub mod foreign_keys;
334pub mod origins;
335pub mod restoration;