uqa_sql/catalog/
relation_attributes.rs1use serde::{Deserialize, Serialize};
10use std::collections::BTreeSet;
11
12use super::type_metadata;
13use crate::ast::{AutoIncrementKind, ColumnDef, ColumnType};
14use crate::SQLError;
15
16pub const MAX_ATTRIBUTES: i16 = 1600;
18
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
21#[serde(deny_unknown_fields)]
22pub struct DroppedAttribute {
23 pub number: i16,
24 pub type_length: i64,
25 pub type_modifier: i64,
26 pub dimensions: i64,
27 pub by_value: bool,
28 pub alignment: String,
29 pub storage: String,
30 pub collation: i64,
31 pub identity: String,
32 pub is_local: bool,
33 pub inheritance_count: i64,
34}
35
36impl DroppedAttribute {
37 pub fn from_column(
38 column: &ColumnDef,
39 position: usize,
40 is_local: bool,
41 inheritance_count: i64,
42 ) -> Result<Self, SQLError> {
43 Ok(Self {
44 number: column_number(column, position)?,
45 type_length: type_metadata::pg_type_len(&column.ty),
46 type_modifier: type_metadata::pg_type_modifier(&column.ty),
47 dimensions: array_dimension_count(&column.ty),
48 by_value: type_metadata::pg_type_by_value(&column.ty),
49 alignment: type_metadata::pg_type_align(&column.ty).into(),
50 storage: type_metadata::pg_type_storage(&column.ty).into(),
51 collation: type_metadata::pg_type_collation_oid(&column.ty),
52 identity: match column.auto_increment.as_ref().map(|value| value.kind) {
53 Some(AutoIncrementKind::IdentityAlways) => "a",
54 Some(AutoIncrementKind::IdentityByDefault | AutoIncrementKind::Legacy) => "d",
55 Some(AutoIncrementKind::Serial) | None => "",
56 }
57 .into(),
58 is_local,
59 inheritance_count,
60 })
61 }
62
63 pub fn name(&self) -> String {
64 super::composite_type::StoredCompositeAttribute::dropped_name(self.number)
65 }
66}
67
68pub fn column_number(column: &ColumnDef, position: usize) -> Result<i16, SQLError> {
70 let number = match column.attribute_number {
71 Some(number) => number,
72 None => i16::try_from(position + 1)
73 .map_err(|_| SQLError::Internal("attribute number is out of range".into()))?,
74 };
75 if number <= 0 {
76 return Err(SQLError::Internal(
77 "attribute number must be positive".into(),
78 ));
79 }
80 Ok(number)
81}
82
83pub fn column_names(columns: &[ColumnDef]) -> Result<Vec<(i16, String)>, SQLError> {
84 columns
85 .iter()
86 .enumerate()
87 .map(|(index, column)| Ok((column_number(column, index)?, column.name.clone())))
88 .collect()
89}
90
91pub fn column_by_number(columns: &[ColumnDef], number: i64) -> Option<&ColumnDef> {
92 columns.iter().enumerate().find_map(|(index, column)| {
93 column_number(column, index)
94 .is_ok_and(|candidate| i64::from(candidate) == number)
95 .then_some(column)
96 })
97}
98
99pub fn consecutive_names(names: Vec<String>) -> Result<Vec<(i16, String)>, SQLError> {
100 names
101 .into_iter()
102 .enumerate()
103 .map(|(index, name)| {
104 Ok((
105 i16::try_from(index + 1)
106 .map_err(|_| SQLError::Internal("attribute number is out of range".into()))?,
107 name,
108 ))
109 })
110 .collect()
111}
112
113pub fn array_dimension_count(ty: &ColumnType) -> i64 {
114 let mut dimensions = 0;
115 let mut current = ty;
116 while let ColumnType::Array(element) = current {
117 dimensions += 1;
118 current = element;
119 }
120 dimensions
121}
122
123pub fn retain_numbers(previous: &[ColumnDef], candidate: &mut [ColumnDef]) {
125 for column in candidate {
126 column.attribute_number = previous
127 .iter()
128 .find(|prior| {
129 column
130 .object_id
131 .map_or(prior.name == column.name, |identity| {
132 prior.object_id == Some(identity)
133 })
134 })
135 .and_then(|prior| prior.attribute_number);
136 }
137}
138
139pub fn materialize(
141 columns: &mut [ColumnDef],
142 dropped: &[DroppedAttribute],
143) -> Result<bool, SQLError> {
144 let mut maximum = columns
145 .iter()
146 .filter_map(|column| column.attribute_number)
147 .chain(dropped.iter().map(|attribute| attribute.number))
148 .max()
149 .unwrap_or(0);
150 let mut numbers = Vec::with_capacity(columns.len());
151 let mut changed = false;
152 for column in columns.iter() {
153 numbers.push(if let Some(number) = column.attribute_number {
154 number
155 } else {
156 maximum = maximum.checked_add(1).ok_or_else(too_many_columns)?;
157 changed = true;
158 maximum
159 });
160 }
161 validate_numbers(&numbers, dropped)?;
162 for (column, number) in columns.iter_mut().zip(numbers) {
163 column.attribute_number = Some(number);
164 }
165 Ok(changed)
166}
167
168pub fn validate(columns: &[ColumnDef], dropped: &[DroppedAttribute]) -> Result<(), SQLError> {
170 let numbers = columns
171 .iter()
172 .map(|column| {
173 column.attribute_number.ok_or_else(|| {
174 SQLError::Internal("relation attributes require initial catalog migration".into())
175 })
176 })
177 .collect::<Result<Vec<_>, _>>()?;
178 validate_numbers(&numbers, dropped)
179}
180
181fn validate_numbers(numbers: &[i16], dropped: &[DroppedAttribute]) -> Result<(), SQLError> {
182 let all: BTreeSet<_> = numbers
183 .iter()
184 .copied()
185 .chain(dropped.iter().map(|attribute| attribute.number))
186 .collect();
187 if all.last().is_some_and(|number| *number > MAX_ATTRIBUTES) {
188 return Err(too_many_columns());
189 }
190 if all.len() != numbers.len() + dropped.len()
191 || all
192 .iter()
193 .copied()
194 .ne(1..=i16::try_from(all.len()).unwrap_or(i16::MAX))
195 || numbers.windows(2).any(|pair| pair[0] >= pair[1])
196 {
197 return Err(SQLError::Internal(
198 "invalid relation attribute slot layout".into(),
199 ));
200 }
201 Ok(())
202}
203
204fn too_many_columns() -> SQLError {
205 SQLError::Routine {
206 sqlstate: "54011".into(),
207 message: format!("tables can have at most {MAX_ATTRIBUTES} columns"),
208 }
209}
210
211#[cfg(test)]
212mod tests;