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: constraint.columns.clone(),
82 key_types,
83 unique: true,
84 nulls_not_distinct: constraint.nulls_not_distinct,
85 ..Self::default()
86 })
87 }
88}
89
90pub fn can_attach_index(
92 parent: &IndexAttachmentShape<'_>,
93 child: &IndexAttachmentShape<'_>,
94) -> bool {
95 parent
96 .constraint_kind
97 .is_none_or(|kind| child.constraint_kind == Some(kind))
98 && parent.method.eq_ignore_ascii_case(child.method)
99 && parent.keys == child.keys
100 && parent.definition.unique == child.definition.unique
101 && parent.definition.nulls_not_distinct == child.definition.nulls_not_distinct
102 && parent.definition.included_columns == child.definition.included_columns
103 && parent.definition.predicate == child.definition.predicate
104 && (0..parent.keys.len()).all(|position| {
105 parent
106 .definition
107 .column_order
108 .get(position)
109 .copied()
110 .unwrap_or_default()
111 == child
112 .definition
113 .column_order
114 .get(position)
115 .copied()
116 .unwrap_or_default()
117 })
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn constraint_ownership_controls_attachment_in_only_the_parent_direction() {
126 let definition = IndexDefinition {
127 unique: true,
128 ..IndexDefinition::default()
129 };
130 let keys = [IndexKey::Column("k".into())];
131 let shape = |constraint_kind| IndexAttachmentShape {
132 method: "btree",
133 keys: &keys,
134 definition: &definition,
135 constraint_kind,
136 };
137 use TableKeyConstraintKind::{PrimaryKey, Unique};
138 for parent in [None, Some(PrimaryKey), Some(Unique)] {
139 for child in [None, Some(PrimaryKey), Some(Unique)] {
140 assert_eq!(
141 can_attach_index(&shape(parent), &shape(child)),
142 parent.is_none() || parent == child,
143 "parent {parent:?}, child {child:?}"
144 );
145 }
146 }
147 let other = [IndexKey::Column("other".into())];
148 let mut child = shape(None);
149 child.keys = &other;
150 assert!(!can_attach_index(&shape(None), &child));
151 }
152
153 #[test]
154 fn index_edges_reject_zero_and_self_references_and_remain_independent() {
155 let valid = IndexRelationships {
156 owning_constraint: Some([1; 16]),
157 parent_index: Some([2; 16]),
158 };
159 valid.validate([3; 16]).unwrap();
160 for invalid in [[0; 16], [3; 16]] {
161 for relationships in [
162 IndexRelationships {
163 owning_constraint: Some(invalid),
164 ..valid.clone()
165 },
166 IndexRelationships {
167 parent_index: Some(invalid),
168 ..valid.clone()
169 },
170 ] {
171 assert!(relationships.validate([3; 16]).is_err());
172 }
173 }
174 let detached = IndexRelationships {
175 parent_index: None,
176 ..valid
177 };
178 assert_eq!(detached.owning_constraint, Some([1; 16]));
179 assert!(!detached.is_empty());
180 }
181}