1use crate::ast::{
10 ColumnDef, ColumnType, GeneratedColumnKind, PartitionSpec, TableKeyConstraint,
11 TableKeyConstraintKind,
12};
13use crate::schema::columns::POSTGRES_SYSTEM_COLUMNS;
14use crate::SQLError;
15
16pub fn missing_key_column(column: &str) -> SQLError {
18 SQLError::Routine {
19 sqlstate: "42703".into(),
20 message: format!("column \"{column}\" named in key does not exist"),
21 }
22}
23
24pub fn repeated_key_column(kind: TableKeyConstraintKind, column: &str) -> SQLError {
26 let constraint = match kind {
27 TableKeyConstraintKind::PrimaryKey => "primary key",
28 TableKeyConstraintKind::Unique => "unique",
29 };
30 SQLError::Routine {
31 sqlstate: "42701".into(),
32 message: format!("column \"{column}\" appears twice in {constraint} constraint"),
33 }
34}
35
36pub fn multiple_primary_keys(table: &str) -> SQLError {
38 SQLError::Routine {
39 sqlstate: "42P16".into(),
40 message: format!("multiple primary keys for table \"{table}\" are not allowed"),
41 }
42}
43
44pub fn validate_overlaps_column(column: &str, ty: Option<&ColumnType>) -> Result<(), SQLError> {
46 if matches!(ty, Some(ColumnType::Range(_) | ColumnType::Multirange(_))) {
47 return Ok(());
48 }
49 Err(SQLError::Routine {
50 sqlstate: "42804".into(),
51 message: format!(
52 "column \"{column}\" in WITHOUT OVERLAPS is not a range or multirange type"
53 ),
54 })
55}
56
57pub fn validate_overlaps_key_length(key: &TableKeyConstraint) -> Result<(), SQLError> {
59 if !key.without_overlaps || key.columns.len() >= 2 {
60 return Ok(());
61 }
62 Err(SQLError::Routine {
63 sqlstate: "42601".into(),
64 message: "constraint using WITHOUT OVERLAPS needs at least two columns".into(),
65 })
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub enum IndexOwner {
71 Key(TableKeyConstraintKind),
72 Index,
73}
74
75pub fn is_system_column(name: &str) -> bool {
77 POSTGRES_SYSTEM_COLUMNS.contains(&name)
78}
79
80fn missing_operator_class(type_name: &str, access_method: &str) -> SQLError {
82 SQLError::Diagnostic {
83 sqlstate: "42704".into(),
84 message: format!(
85 "data type {type_name} has no default operator class for access method \"{access_method}\""
86 ),
87 detail: None,
88 hint: Some(
89 "You must specify an operator class for the index or define a default operator class for the data type."
90 .into(),
91 ),
92 }
93}
94
95pub fn resolve_system_key_attribute(name: &str, access_method: &str) -> Result<(), SQLError> {
97 let type_name = match name {
98 "xmin" | "xmax" => "xid",
99 "cmin" | "cmax" => "cid",
100 "ctid" => "tid",
101 _ => return Ok(()),
102 };
103 let ordered = match access_method {
104 "hash" => true,
105 "" | "btree" => type_name == "tid",
106 _ => false,
107 };
108 if ordered {
109 Ok(())
110 } else {
111 Err(missing_operator_class(
112 type_name,
113 if access_method.is_empty() {
114 "btree"
115 } else {
116 access_method
117 },
118 ))
119 }
120}
121
122pub fn validate_index_attribute(
124 columns: &[ColumnDef],
125 name: &str,
126 owner: IndexOwner,
127) -> Result<(), SQLError> {
128 let Some(column) = columns.iter().find(|column| column.name == name) else {
129 if is_system_column(name) {
130 return Err(system_column_index());
131 }
132 return Err(SQLError::Internal(format!(
133 "index attribute `{name}` is not a column"
134 )));
135 };
136 if column
137 .generated
138 .as_ref()
139 .is_some_and(|generated| generated.kind == GeneratedColumnKind::Virtual)
140 {
141 let message = match owner {
142 IndexOwner::Key(TableKeyConstraintKind::PrimaryKey) => {
143 "primary keys on virtual generated columns are not supported"
144 }
145 IndexOwner::Key(TableKeyConstraintKind::Unique) => {
146 "unique constraints on virtual generated columns are not supported"
147 }
148 IndexOwner::Index => "indexes on virtual generated columns are not supported",
149 };
150 return Err(SQLError::Routine {
151 sqlstate: "0A000".into(),
152 message: message.into(),
153 });
154 }
155 Ok(())
156}
157
158pub fn system_column_index() -> SQLError {
160 SQLError::Routine {
161 sqlstate: "0A000".into(),
162 message: "index creation on system columns is not supported".into(),
163 }
164}
165
166pub struct KeyRelation<'a> {
168 pub table: &'a str,
170 pub columns: &'a [ColumnDef],
171 pub partition: Option<&'a PartitionSpec>,
173 pub has_primary_key: bool,
175}
176
177pub fn validate_key_definition(
179 relation: &KeyRelation<'_>,
180 key: &TableKeyConstraint,
181) -> Result<(), SQLError> {
182 let declared = |name: &str| relation.columns.iter().any(|column| column.name == name);
183 let access_method = if key.without_overlaps {
184 "gist"
185 } else {
186 "btree"
187 };
188 for name in &key.columns {
189 if declared(name) {
190 continue;
191 }
192 if !is_system_column(name) {
193 return Err(missing_key_column(name));
194 }
195 resolve_system_key_attribute(name, access_method)?;
196 }
197 if let Some(name) = key
198 .included_columns
199 .iter()
200 .find(|name| !declared(name) && !is_system_column(name))
201 {
202 return Err(missing_key_column(name));
203 }
204 if key.kind == TableKeyConstraintKind::PrimaryKey && relation.has_primary_key {
205 let local = uqa_core::RelationIdentity::from_legacy_name(relation.table)
206 .map_err(SQLError::Internal)?;
207 return Err(multiple_primary_keys(&local.name));
208 }
209 if let Some(partition) = relation.partition {
210 crate::schema::indexes::unique::validate_partitioned_key_constraint(
211 relation.table,
212 key,
213 partition,
214 )?;
215 }
216 for name in key.columns.iter().chain(&key.included_columns) {
217 validate_index_attribute(relation.columns, name, IndexOwner::Key(key.kind))?;
218 }
219 Ok(())
220}
221
222pub struct KeyPartition<'a> {
224 pub table: &'a str,
225 pub partition: Option<&'a PartitionSpec>,
227 pub keys: &'a [TableKeyConstraint],
229}
230
231#[derive(Debug, Clone, Copy, PartialEq, Eq)]
233pub enum PartitionKeyIndex {
234 Adopted,
236 Created,
238}
239
240pub fn define_partition_key(
242 partition: &KeyPartition<'_>,
243 key: &TableKeyConstraint,
244) -> Result<PartitionKeyIndex, SQLError> {
245 if partition
246 .keys
247 .iter()
248 .any(|existing| crate::schema::inheritance::alter::key_equivalent(existing, key))
249 {
250 return Ok(PartitionKeyIndex::Adopted);
251 }
252 if key.kind == TableKeyConstraintKind::PrimaryKey
253 && partition
254 .keys
255 .iter()
256 .any(|existing| existing.kind == TableKeyConstraintKind::PrimaryKey)
257 {
258 let local = uqa_core::RelationIdentity::from_legacy_name(partition.table)
259 .map_err(SQLError::Internal)?;
260 return Err(multiple_primary_keys(&local.name));
261 }
262 if let Some(spec) = partition.partition {
263 crate::schema::indexes::unique::validate_partitioned_key_constraint(
264 partition.table,
265 key,
266 spec,
267 )?;
268 }
269 Ok(PartitionKeyIndex::Created)
270}
271
272pub fn index_order(mut keys: Vec<TableKeyConstraint>) -> Vec<TableKeyConstraint> {
274 let mut ordered = Vec::with_capacity(keys.len());
275 if let Some(position) = keys
276 .iter()
277 .position(|key| key.kind == TableKeyConstraintKind::PrimaryKey)
278 {
279 ordered.push(keys.remove(position));
280 }
281 for key in keys {
282 match ordered
283 .iter_mut()
284 .find(|prior: &&mut TableKeyConstraint| same_index(prior, &key))
285 {
286 Some(prior) => {
287 if prior.name.is_none() {
288 prior.name = key.name;
289 }
290 }
291 None => ordered.push(key),
292 }
293 }
294 ordered
295}
296
297fn same_index(left: &TableKeyConstraint, right: &TableKeyConstraint) -> bool {
299 left.columns == right.columns
300 && left.included_columns == right.included_columns
301 && left.nulls_not_distinct == right.nulls_not_distinct
302 && left.without_overlaps == right.without_overlaps
303}