uqa_sql/catalog/index/
relationships.rs1use super::IndexDefinition;
10use crate::ast::{ColumnDef, IndexKey, TableKeyConstraint, TableKeyConstraintKind};
11use crate::schema::constraint_metadata::{ConstraintMetadataError, ConstraintMetadataResult};
12
13#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
14#[serde(deny_unknown_fields)]
15pub struct IndexRelationships {
16 #[serde(default, skip_serializing_if = "Option::is_none")]
17 pub owning_constraint: Option<[u8; 16]>,
18 #[serde(default, skip_serializing_if = "Option::is_none")]
19 pub parent_index: Option<[u8; 16]>,
20}
21
22impl IndexRelationships {
23 pub fn is_empty(&self) -> bool {
24 self.owning_constraint.is_none() && self.parent_index.is_none()
25 }
26
27 pub fn validate(&self, index: [u8; 16]) -> ConstraintMetadataResult<()> {
28 if [self.owning_constraint, self.parent_index]
29 .into_iter()
30 .flatten()
31 .any(|target| target == [0; 16] || target == index)
32 {
33 return Err(ConstraintMetadataError::Invalid(
34 "invalid index ownership or parent identity".into(),
35 ));
36 }
37 Ok(())
38 }
39}
40
41pub struct IndexAttachmentShape<'a> {
42 pub method: &'a str,
43 pub keys: &'a [IndexKey],
44 pub definition: &'a IndexDefinition,
45 pub constraint_kind: Option<TableKeyConstraintKind>,
46}
47
48impl IndexDefinition {
49 pub fn for_constraint(
50 constraint: &TableKeyConstraint,
51 columns: &[ColumnDef],
52 ) -> ConstraintMetadataResult<Self> {
53 let owner = constraint.catalog_identity.ok_or_else(|| {
54 ConstraintMetadataError::Invalid("index owner has no constraint identity".into())
55 })?;
56 if !owner.is_valid() {
57 return Err(ConstraintMetadataError::Invalid(
58 "index owner has an invalid identity".into(),
59 ));
60 }
61 let key_types = constraint
62 .columns
63 .iter()
64 .map(|name| {
65 columns
66 .iter()
67 .find(|column| column.name == *name)
68 .map(|column| column.ty.clone())
69 .ok_or_else(|| {
70 ConstraintMetadataError::Invalid(format!(
71 "constraint index references missing column `{name}`"
72 ))
73 })
74 })
75 .collect::<ConstraintMetadataResult<_>>()?;
76 Ok(Self {
77 relationships: IndexRelationships {
78 owning_constraint: Some(owner.object_id),
79 parent_index: None,
80 },
81 key_names: crate::schema::indexes::keys::key_names(
82 &crate::schema::indexes::names::constraint_index_attributes(constraint),
83 ),
84 key_types,
85 included_columns: constraint.included_columns.clone(),
86 unique: true,
87 nulls_not_distinct: constraint.nulls_not_distinct,
88 ..Self::default()
89 })
90 }
91}
92
93pub fn can_attach_index(
95 parent: &IndexAttachmentShape<'_>,
96 child: &IndexAttachmentShape<'_>,
97) -> bool {
98 parent
99 .constraint_kind
100 .is_none_or(|kind| child.constraint_kind == Some(kind))
101 && parent.method.eq_ignore_ascii_case(child.method)
102 && parent.keys == child.keys
103 && parent.definition.unique == child.definition.unique
104 && parent.definition.nulls_not_distinct == child.definition.nulls_not_distinct
105 && parent.definition.included_columns == child.definition.included_columns
106 && parent.definition.predicate == child.definition.predicate
107 && (0..parent.keys.len()).all(|position| {
108 parent
109 .definition
110 .column_order
111 .get(position)
112 .copied()
113 .unwrap_or_default()
114 == child
115 .definition
116 .column_order
117 .get(position)
118 .copied()
119 .unwrap_or_default()
120 })
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn constraint_ownership_controls_attachment_in_only_the_parent_direction() {
129 let definition = IndexDefinition {
130 unique: true,
131 ..IndexDefinition::default()
132 };
133 let keys = [IndexKey::Column("k".into())];
134 let shape = |constraint_kind| IndexAttachmentShape {
135 method: "btree",
136 keys: &keys,
137 definition: &definition,
138 constraint_kind,
139 };
140 use TableKeyConstraintKind::{PrimaryKey, Unique};
141 for parent in [None, Some(PrimaryKey), Some(Unique)] {
142 for child in [None, Some(PrimaryKey), Some(Unique)] {
143 assert_eq!(
144 can_attach_index(&shape(parent), &shape(child)),
145 parent.is_none() || parent == child,
146 "parent {parent:?}, child {child:?}"
147 );
148 }
149 }
150 let other = [IndexKey::Column("other".into())];
151 let mut child = shape(None);
152 child.keys = &other;
153 assert!(!can_attach_index(&shape(None), &child));
154 }
155
156 #[test]
157 fn index_edges_reject_zero_and_self_references_and_remain_independent() {
158 let valid = IndexRelationships {
159 owning_constraint: Some([1; 16]),
160 parent_index: Some([2; 16]),
161 };
162 valid.validate([3; 16]).unwrap();
163 for invalid in [[0; 16], [3; 16]] {
164 for relationships in [
165 IndexRelationships {
166 owning_constraint: Some(invalid),
167 ..valid.clone()
168 },
169 IndexRelationships {
170 parent_index: Some(invalid),
171 ..valid.clone()
172 },
173 ] {
174 assert!(relationships.validate([3; 16]).is_err());
175 }
176 }
177 let detached = IndexRelationships {
178 parent_index: None,
179 ..valid
180 };
181 assert_eq!(detached.owning_constraint, Some([1; 16]));
182 assert!(!detached.is_empty());
183 }
184}