cipherstash_config/column/
config.rs1use std::collections::HashSet;
2
3use super::{index::Index, IndexType, TokenFilter};
4use crate::list::ListEntry;
5use crate::operator::Operator;
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq, Hash)]
10#[serde(rename_all = "kebab-case")]
11pub enum ColumnType {
12 BigInt,
13 BigUInt,
14 Boolean,
15 Date,
16 Decimal,
17 Float,
18 Int,
19 SmallInt,
20 Timestamp,
21 #[serde(alias = "utf8-str")]
22 Text,
23 #[serde(rename = "json", alias = "jsonb")]
24 Json,
25 }
27
28impl std::fmt::Display for ColumnType {
29 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30 let text = match self {
31 ColumnType::BigInt => "BigInt",
32 ColumnType::BigUInt => "BigUInt",
33 ColumnType::Boolean => "Boolean",
34 ColumnType::Date => "Date",
35 ColumnType::Decimal => "Decimal",
36 ColumnType::Float => "Float",
37 ColumnType::Int => "Int",
38 ColumnType::SmallInt => "SmallInt",
39 ColumnType::Timestamp => "Timestamp",
40 ColumnType::Text => "Text",
41 ColumnType::Json => "Json",
42 };
43
44 write!(f, "{text}")
45 }
46}
47
48#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
49#[serde(rename_all = "kebab-case")]
50pub enum ColumnMode {
51 PlaintextDuplicate = 1,
54 EncryptedDuplicate = 2,
57 Encrypted = 3,
60}
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
63pub struct ColumnConfig {
64 pub name: String,
65 pub in_place: bool,
66 pub cast_type: ColumnType,
67 pub indexes: Vec<Index>,
68 pub mode: ColumnMode,
69}
70
71impl ListEntry for ColumnConfig {}
72
73impl PartialEq for ColumnConfig {
75 fn eq(&self, other: &Self) -> bool {
76 self.name == other.name
77 }
78}
79
80impl PartialEq<String> for ColumnConfig {
82 fn eq(&self, other: &String) -> bool {
83 self.name == *other
84 }
85}
86
87impl ColumnConfig {
88 pub fn build(name: impl Into<String>) -> Self {
94 Self {
95 name: name.into(),
96 in_place: false,
97 cast_type: ColumnType::Text,
98 indexes: Default::default(),
99 mode: ColumnMode::EncryptedDuplicate,
100 }
101 }
102
103 pub fn casts_as(mut self, field_type: ColumnType) -> Self {
106 self.cast_type = field_type;
107 self
108 }
109
110 pub fn add_index(mut self, index: Index) -> Self {
113 self.indexes.push(index);
116 self
117 }
118
119 pub fn mode(mut self, mode: ColumnMode) -> Self {
120 self.mode = mode;
121 self
122 }
123
124 pub fn supports_operator(&self, op: &Operator) -> bool {
125 self.index_for_operator(op).is_some()
126 }
127
128 pub fn supported_operations(&self) -> Vec<Operator> {
129 let hash: HashSet<Operator> = self
130 .indexes
131 .iter()
132 .flat_map(|i| i.index_type.supported_operations(&self.cast_type))
133 .collect();
134
135 hash.into_iter().collect()
136 }
137
138 pub fn index_for_operator(&self, op: &Operator) -> Option<&Index> {
139 self.indexes
140 .iter()
141 .find(|i| i.supports(op, &self.cast_type))
142 }
143
144 pub fn index_for_sort(&self) -> Option<&Index> {
145 self.indexes.iter().find(|i| i.is_orderable())
146 }
147
148 pub fn sort_indexes_by_type(&mut self) {
150 self.indexes
151 .sort_by(|a, b| a.index_type.as_str().cmp(b.index_type.as_str()));
152 }
153
154 pub fn has_unique_index_with_downcase(&self) -> bool {
155 self.indexes.iter().any(|index| {
156 if let IndexType::Unique { token_filters } = &index.index_type {
157 token_filters
158 .iter()
159 .any(|filter| matches!(filter, TokenFilter::Downcase))
160 } else {
161 false
162 }
163 })
164 }
165
166 pub fn into_match_index(self) -> Option<Index> {
167 self.indexes.into_iter().find(|i| i.is_match())
168 }
169
170 pub fn into_ore_index(self) -> Option<Index> {
171 self.indexes.into_iter().find(|i| i.is_ore())
172 }
173
174 pub fn into_unique_index(self) -> Option<Index> {
175 self.indexes.into_iter().find(|i| i.is_unique())
176 }
177
178 pub fn into_ste_vec_index(self) -> Option<Index> {
179 self.indexes.into_iter().find(|i| i.is_ste_vec())
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn text_serializes_as_text_in_kebab_case() {
189 let json = serde_json::to_string(&ColumnType::Text).unwrap();
190 assert_eq!(json, "\"text\"");
191 }
192
193 #[test]
194 fn text_deserializes_from_text() {
195 let result: ColumnType = serde_json::from_str("\"text\"").unwrap();
196 assert_eq!(result, ColumnType::Text);
197 }
198
199 #[test]
200 fn text_deserializes_from_legacy_utf8_str() {
201 let result: ColumnType = serde_json::from_str("\"utf8-str\"").unwrap();
202 assert_eq!(result, ColumnType::Text);
203 }
204
205 #[test]
206 fn text_display_shows_text() {
207 assert_eq!(format!("{}", ColumnType::Text), "Text");
208 }
209
210 #[test]
211 fn build_defaults_to_text_type() {
212 let config = ColumnConfig::build("test_column");
213 assert_eq!(config.cast_type, ColumnType::Text);
214 }
215}