cipherstash_config/column/
config.rs

1use std::collections::HashSet;
2
3use super::{index::Index, IndexType, TokenFilter};
4use crate::list::ListEntry;
5use crate::operator::Operator;
6use serde::{Deserialize, Serialize};
7
8// All types should be handled here I guess
9#[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    Utf8Str,
22    #[serde(rename = "jsonb")]
23    JsonB,
24    // TODO: What else do we need to add here?
25}
26
27impl std::fmt::Display for ColumnType {
28    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
29        let text = match self {
30            ColumnType::BigInt => "BigInt",
31            ColumnType::BigUInt => "BigUInt",
32            ColumnType::Boolean => "Boolean",
33            ColumnType::Date => "Date",
34            ColumnType::Decimal => "Decimal",
35            ColumnType::Float => "Float",
36            ColumnType::Int => "Int",
37            ColumnType::SmallInt => "SmallInt",
38            ColumnType::Timestamp => "Timestamp",
39            ColumnType::Utf8Str => "Utf8Str",
40            ColumnType::JsonB => "JSONB",
41        };
42
43        write!(f, "{text}")
44    }
45}
46
47#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
48#[serde(rename_all = "kebab-case")]
49pub enum ColumnMode {
50    /// Store both the plaintext and encrypted data - all operations will continue to be performed
51    /// against the plaintext data. This mode should be used while migrating existing data.
52    PlaintextDuplicate = 1,
53    /// Store both the plaintext and encrypted data, but all operations will be mapped to encrypted
54    /// data. In this mode the plaintext is just a backup.
55    EncryptedDuplicate = 2,
56    /// Only store the encrypted data. This mode should be used once migration is complete so
57    /// columns get the maximum protection.
58    Encrypted = 3,
59}
60
61#[derive(Debug, Clone, Serialize, Deserialize)]
62pub struct ColumnConfig {
63    pub name: String,
64    pub in_place: bool,
65    pub cast_type: ColumnType,
66    pub indexes: Vec<Index>,
67    pub mode: ColumnMode,
68}
69
70impl ListEntry for ColumnConfig {}
71
72// Configs must be unique by name
73impl PartialEq for ColumnConfig {
74    fn eq(&self, other: &Self) -> bool {
75        self.name == other.name
76    }
77}
78
79// Compare a string to a Config based on its column name
80impl PartialEq<String> for ColumnConfig {
81    fn eq(&self, other: &String) -> bool {
82        self.name == *other
83    }
84}
85
86impl ColumnConfig {
87    /// Builds a field with the following defaults:
88    ///
89    /// Type: Utf8Str,
90    /// Mode: EncryptedDuplicate
91    /// In Place: false
92    pub fn build(name: impl Into<String>) -> Self {
93        Self {
94            name: name.into(),
95            in_place: false,
96            cast_type: ColumnType::Utf8Str,
97            indexes: Default::default(),
98            mode: ColumnMode::EncryptedDuplicate,
99        }
100    }
101
102    /// Consumes self and sets the field_type to the given
103    /// value
104    pub fn casts_as(mut self, field_type: ColumnType) -> Self {
105        self.cast_type = field_type;
106        self
107    }
108
109    /// Consumes self and adds the given index to the list
110    /// of indexes
111    pub fn add_index(mut self, index: Index) -> Self {
112        // TODO: Not all indexes are allowed on all types
113        // check first
114        self.indexes.push(index);
115        self
116    }
117
118    pub fn mode(mut self, mode: ColumnMode) -> Self {
119        self.mode = mode;
120        self
121    }
122
123    pub fn supports_operator(&self, op: &Operator) -> bool {
124        self.index_for_operator(op).is_some()
125    }
126
127    pub fn supported_operations(&self) -> Vec<Operator> {
128        let hash: HashSet<Operator> = self
129            .indexes
130            .iter()
131            .flat_map(|i| i.index_type.supported_operations(&self.cast_type))
132            .collect();
133
134        hash.into_iter().collect()
135    }
136
137    pub fn index_for_operator(&self, op: &Operator) -> Option<&Index> {
138        self.indexes
139            .iter()
140            .find(|i| i.supports(op, &self.cast_type))
141    }
142
143    pub fn index_for_sort(&self) -> Option<&Index> {
144        self.indexes.iter().find(|i| i.is_orderable())
145    }
146
147    /// Sorts indexes by type. Indexes are sorted in place.
148    pub fn sort_indexes_by_type(&mut self) {
149        self.indexes
150            .sort_by(|a, b| a.index_type.as_str().cmp(b.index_type.as_str()));
151    }
152
153    pub fn has_unique_index_with_downcase(&self) -> bool {
154        self.indexes.iter().any(|index| {
155            if let IndexType::Unique { token_filters } = &index.index_type {
156                token_filters
157                    .iter()
158                    .any(|filter| matches!(filter, TokenFilter::Downcase))
159            } else {
160                false
161            }
162        })
163    }
164
165    pub fn into_match_index(self) -> Option<Index> {
166        self.indexes.into_iter().find(|i| i.is_match())
167    }
168
169    pub fn into_ore_index(self) -> Option<Index> {
170        self.indexes.into_iter().find(|i| i.is_ore())
171    }
172
173    pub fn into_unique_index(self) -> Option<Index> {
174        self.indexes.into_iter().find(|i| i.is_unique())
175    }
176
177    pub fn into_ste_vec_index(self) -> Option<Index> {
178        self.indexes.into_iter().find(|i| i.is_ste_vec())
179    }
180}