uqa_sql/schema/indexes/
unique.rs1use crate::{
9 ast::{CreateIndex, PartitionSpec, TableHierarchy, TableKeyConstraint, TableKeyConstraintKind},
10 SQLError,
11};
12pub fn validate_unique_index_method(statement: &CreateIndex) -> Result<(), SQLError> {
13 if !matches!(statement.access_method.as_str(), "" | "btree") {
14 return Err(SQLError::Routine {
15 sqlstate: "0A000".into(),
16 message: format!(
17 "access method \"{}\" does not support unique indexes",
18 statement.access_method
19 ),
20 });
21 }
22 Ok(())
23}
24pub fn validate_unique_partition_columns(
25 statement: &CreateIndex,
26 hierarchy: &TableHierarchy,
27) -> Result<(), SQLError> {
28 match &hierarchy.partition_spec {
29 Some(partition) => validate_unique_index_partition_key(statement, partition),
30 None => Ok(()),
31 }
32}
33
34pub fn validate_unique_index_partition_key(
36 statement: &CreateIndex,
37 partition: &PartitionSpec,
38) -> Result<(), SQLError> {
39 let columns = statement
40 .columns
41 .iter()
42 .map(crate::ast::IndexKey::column)
43 .collect::<Vec<_>>();
44 validate_partitioned_unique_key(
45 &statement.table,
46 &PartitionedUniqueKey {
47 constraint_type: TableKeyConstraintKind::Unique,
48 columns: &columns,
49 without_overlaps: false,
50 },
51 partition,
52 )
53}
54
55pub fn validate_partitioned_key_constraint(
57 table: &str,
58 constraint: &TableKeyConstraint,
59 partition: &PartitionSpec,
60) -> Result<(), SQLError> {
61 let columns = constraint
62 .columns
63 .iter()
64 .map(|column| Some(column.as_str()))
65 .collect::<Vec<_>>();
66 validate_partitioned_unique_key(
67 table,
68 &PartitionedUniqueKey {
69 constraint_type: constraint.kind,
70 columns: &columns,
71 without_overlaps: constraint.without_overlaps,
72 },
73 partition,
74 )
75}
76
77pub struct PartitionedUniqueKey<'a> {
79 pub constraint_type: TableKeyConstraintKind,
81 pub columns: &'a [Option<&'a str>],
83 pub without_overlaps: bool,
85}
86
87pub fn validate_partitioned_unique_key(
89 table: &str,
90 key: &PartitionedUniqueKey<'_>,
91 partition: &PartitionSpec,
92) -> Result<(), SQLError> {
93 let constraint_type = key.constraint_type.sql_label();
94 for partition_key in &partition.keys {
95 let crate::ast::Expr::Column(column) = partition_key else {
96 return Err(SQLError::Diagnostic {
97 sqlstate: "0A000".into(),
98 message: format!(
99 "unsupported {constraint_type} constraint with partition key definition"
100 ),
101 detail: Some(format!(
102 "{constraint_type} constraints cannot be used when partition keys include expressions."
103 )),
104 hint: None,
105 });
106 };
107 match key
108 .columns
109 .iter()
110 .position(|candidate| *candidate == Some(column.as_str()))
111 {
112 Some(position) if key.without_overlaps && position + 1 == key.columns.len() => {
113 return Err(SQLError::Routine {
114 sqlstate: "0A000".into(),
115 message: format!(
116 "cannot match partition key to index on column \"{column}\" using non-equal operator \"&&\""
117 ),
118 });
119 }
120 Some(_) => {}
121 None => {
122 let relation = uqa_core::RelationIdentity::from_legacy_name(table)
123 .map_err(SQLError::Internal)?;
124 return Err(SQLError::Diagnostic {
125 sqlstate: "0A000".into(),
126 message: "unique constraint on partitioned table must include all partitioning columns".into(),
127 detail: Some(format!(
128 "{constraint_type} constraint on table \"{}\" lacks column \"{column}\" which is part of the partition key.",
129 relation.name
130 )),
131 hint: None,
132 });
133 }
134 }
135 }
136 Ok(())
137}