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 }
105 if column.not_null_no_inherit {
106 column.not_null = false;
107 column.not_null_explicit = false;
108 column.not_null_name = None;
109 column.not_null_no_inherit = false;
110 column.not_null_validated = true;
111 }
112 column.not_null_is_local = !column.not_null;
113 column.check = None;
115 column.check_name = None;
116 column.check_object_id = None;
117 column.check_catalog_oid = None;
118 column.check_is_local = true;
119 column.check_enforced = true;
120 column.check_validated = true;
121 column.check_no_inherit = false;
122 }
123 if !is_partition {
124 for column in &mut columns {
126 column.references = None;
127 if column
128 .auto_increment
129 .as_ref()
130 .is_some_and(crate::ast::AutoIncrement::is_identity)
131 {
132 column.auto_increment = None;
133 }
134 }
135 }
136 merge_columns(&mut inherited_columns, columns)?;
137 let constraints = context
138 .catalog
139 .declared_constraints(&parent)
140 .map_err(|error| SQLError::Internal(format!("read inherited constraints: {error}")))?;
141 for mut check in context
142 .catalog
143 .check_definitions(&parent)
144 .map_err(|error| SQLError::Internal(format!("read inherited CHECKs: {error}")))?
145 .into_iter()
146 .filter(|check| !check.no_inherit)
147 {
148 super::check_inheritance::bind_parent_check_columns(&parent, &mut check.expr)?;
149 check.is_local = false;
150 check.object_id = None;
151 check.catalog_oid = None;
152 check.validated = check.enforced;
153 inherited_checks.push(check);
154 }
155 if is_partition {
156 inherited_foreign_keys.extend(constraints.foreign_keys.into_iter().map(|mut key| {
157 key.catalog_identity = None;
158 key
159 }));
160 inherited_keys.extend(constraints.key_constraints.into_iter().map(|mut key| {
161 key.name = None;
162 key.catalog_identity = None;
163 key
164 }));
165 }
166 canonical_parents.push(parent);
167 }
168 merge_columns(&mut inherited_columns, std::mem::take(&mut table.columns))?;
169 table.columns = inherited_columns;
170 inherited_checks.append(&mut table.checks);
171 table.checks = inherited_checks;
172 if is_partition {
173 inherited_foreign_keys.append(&mut table.foreign_keys);
174 inherited_keys.append(&mut table.key_constraints);
175 table.foreign_keys = inherited_foreign_keys;
176 table.key_constraints = inherited_keys;
177 }
178 table.hierarchy.parents = canonical_parents;
179 validate_partition_keys(context, table)?;
180 if let (Some(parent), Some(bound)) = (
181 table.hierarchy.parents.first(),
182 table.hierarchy.partition_bound.as_ref(),
183 ) {
184 validate_new_partition_bound(&context.partitions, parent, bound)?;
185 }
186 Ok(())
187}
188
189fn validate_partition_bound_strategy(
190 strategy: crate::ast::PartitionStrategy,
191 bound: &crate::ast::PartitionBound,
192) -> Result<(), SQLError> {
193 use crate::ast::{PartitionBound, PartitionStrategy};
194 if matches!(
195 (strategy, bound),
196 (PartitionStrategy::Hash, PartitionBound::Default)
197 ) {
198 return Err(SQLError::Routine {
199 sqlstate: "42P16".into(),
200 message: "a hash-partitioned table may not have a default partition".into(),
201 });
202 }
203 let matches = matches!(bound, PartitionBound::Default)
204 || matches!(
205 (strategy, bound),
206 (PartitionStrategy::List, PartitionBound::List(_))
207 | (PartitionStrategy::Range, PartitionBound::Range { .. })
208 | (PartitionStrategy::Hash, PartitionBound::Hash { .. })
209 );
210 if matches {
211 Ok(())
212 } else {
213 Err(SQLError::Internal(
214 "partition bound strategy differs from its parent".into(),
215 ))
216 }
217}
218
219fn merge_columns(
220 merged: &mut Vec<crate::ast::ColumnDef>,
221 incoming: Vec<crate::ast::ColumnDef>,
222) -> Result<(), SQLError> {
223 for column in incoming {
224 if let Some(existing) = merged.iter_mut().find(|item| item.name == column.name) {
225 merge_same_column(existing, column)?;
226 } else {
227 merged.push(column);
228 }
229 }
230 Ok(())
231}
232
233pub fn merge_same_column(
234 inherited: &mut crate::ast::ColumnDef,
235 declared: crate::ast::ColumnDef,
236) -> Result<(), SQLError> {
237 if inherited.ty != declared.ty {
238 return Err(SQLError::Routine {
239 sqlstate: "42804".into(),
240 message: format!(
241 "inherited column \"{}\" has a type conflict",
242 inherited.name
243 ),
244 });
245 }
246 if inherited.generated.is_some() != declared.generated.is_some() {
247 return Err(SQLError::Routine {
248 sqlstate: "42P17".into(),
249 message: format!(
250 "inherited column \"{}\" has a generation conflict",
251 inherited.name
252 ),
253 });
254 }
255 let not_null_is_local = (inherited.not_null && inherited.not_null_is_local)
256 || (declared.not_null && declared.not_null_is_local);
257 if declared.not_null && (!inherited.not_null || declared.not_null_is_local) {
258 inherited.not_null_name.clone_from(&declared.not_null_name);
259 inherited.not_null_identity = declared.not_null_identity;
260 inherited.not_null_validated = declared.not_null_validated;
261 inherited.not_null_no_inherit = declared.not_null_no_inherit;
262 }
263 inherited.not_null |= declared.not_null;
264 inherited.not_null_is_local = !inherited.not_null || not_null_is_local;
265 inherited.not_null_explicit |= declared.not_null_explicit;
266 inherited.primary_key |= declared.primary_key;
267 inherited.unique |= declared.unique;
268 if declared.auto_increment.is_some() {
269 inherited.auto_increment = declared.auto_increment;
270 }
271 if declared.default.is_some() {
272 inherited.default = declared.default;
273 }
274 if declared.generated.is_some() {
275 inherited.generated = declared.generated;
276 }
277 if declared.check.is_some() {
278 inherited.check = declared.check;
279 inherited.check_name = declared.check_name;
280 inherited.check_enforced = declared.check_enforced;
281 inherited.check_validated = declared.check_validated;
282 inherited.check_no_inherit = declared.check_no_inherit;
283 inherited.check_is_local = declared.check_is_local;
284 inherited.check_object_id = declared.check_object_id;
285 }
286 if declared.references.is_some() {
287 inherited.references = declared.references;
288 }
289 Ok(())
290}
291
292fn validate_partition_keys(
293 context: &InheritanceContext<'_>,
294 table: &CreateTable,
295) -> Result<(), SQLError> {
296 let Some(spec) = table.hierarchy.partition_spec.as_ref() else {
297 return Ok(());
298 };
299 let column_names = table
300 .columns
301 .iter()
302 .map(|column| column.name.as_str())
303 .collect::<BTreeSet<_>>();
304 for key in &spec.keys {
305 let scalar = crate::plan::ExpressionPlan::lower(key.clone()).scalar;
306 let mut referenced_columns = BTreeSet::new();
307 scalar.collect_columns(&mut referenced_columns);
308 for column in referenced_columns {
309 if !column_names.contains(column.as_str()) {
310 return Err(SQLError::Routine {
311 sqlstate: "42703".into(),
312 message: format!("column \"{column}\" named in partition key does not exist"),
313 });
314 }
315 }
316 }
317 validate_hash_partition_spec(&context.partitions, spec, &table.columns)?;
318 for key in &spec.keys {
319 crate::catalog::regrole_dependencies::reject_stored_regrole_constants(
320 context.roles,
321 key,
322 None,
323 )?;
324 }
325 Ok(())
326}
327
328pub mod alter;
329
330pub mod detachment;
331pub mod origins;
332pub mod restoration;