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