cipherstash_config/column/
config.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
use std::collections::HashSet;

use super::{index::Index, IndexType, TokenFilter};
use crate::list::ListEntry;
use crate::operator::Operator;
use serde::{Deserialize, Serialize};

// All types should be handled here I guess
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Eq, PartialEq, Hash)]
#[serde(rename_all = "kebab-case")]
pub enum ColumnType {
    BigInt,
    BigUInt,
    Boolean,
    Date,
    Decimal,
    Float,
    Int,
    SmallInt,
    Timestamp,
    Utf8Str,
    #[serde(rename = "jsonb")]
    JsonB,
    // TODO: What else do we need to add here?
}

impl std::fmt::Display for ColumnType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let text = match self {
            ColumnType::BigInt => "BigInt",
            ColumnType::BigUInt => "BigUInt",
            ColumnType::Boolean => "Boolean",
            ColumnType::Date => "Date",
            ColumnType::Decimal => "Decimal",
            ColumnType::Float => "Float",
            ColumnType::Int => "Int",
            ColumnType::SmallInt => "SmallInt",
            ColumnType::Timestamp => "Timestamp",
            ColumnType::Utf8Str => "Utf8Str",
            ColumnType::JsonB => "JSONB",
        };

        write!(f, "{text}")
    }
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "kebab-case")]
pub enum ColumnMode {
    /// Store both the plaintext and encrypted data - all operations will continue to be performed
    /// against the plaintext data. This mode should be used while migrating existing data.
    PlaintextDuplicate = 1,
    /// Store both the plaintext and encrypted data, but all operations will be mapped to encrypted
    /// data. In this mode the plaintext is just a backup.
    EncryptedDuplicate = 2,
    /// Only store the encrypted data. This mode should be used once migration is complete so
    /// columns get the maximum protection.
    Encrypted = 3,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ColumnConfig {
    pub name: String,
    pub in_place: bool,
    pub cast_type: ColumnType,
    pub indexes: Vec<Index>,
    pub mode: ColumnMode,
}

impl ListEntry for ColumnConfig {}

// Configs must be unique by name
impl PartialEq for ColumnConfig {
    fn eq(&self, other: &Self) -> bool {
        self.name == other.name
    }
}

// Compare a string to a Config based on its column name
impl PartialEq<String> for ColumnConfig {
    fn eq(&self, other: &String) -> bool {
        self.name == *other
    }
}

impl ColumnConfig {
    /// Builds a field with the following defaults:
    ///
    /// Type: Utf8Str,
    /// Mode: EncryptedDuplicate
    /// In Place: false
    pub fn build(name: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            in_place: false,
            cast_type: ColumnType::Utf8Str,
            indexes: Default::default(),
            mode: ColumnMode::EncryptedDuplicate,
        }
    }

    /// Consumes self and sets the field_type to the given
    /// value
    pub fn casts_as(mut self, field_type: ColumnType) -> Self {
        self.cast_type = field_type;
        self
    }

    /// Consumes self and adds the given index to the list
    /// of indexes
    pub fn add_index(mut self, index: Index) -> Self {
        // TODO: Not all indexes are allowed on all types
        // check first
        self.indexes.push(index);
        self
    }

    pub fn mode(mut self, mode: ColumnMode) -> Self {
        self.mode = mode;
        self
    }

    pub fn supports_operator(&self, op: &Operator) -> bool {
        self.index_for_operator(op).is_some()
    }

    pub fn supported_operations(&self) -> Vec<Operator> {
        let hash: HashSet<Operator> = self
            .indexes
            .iter()
            .flat_map(|i| i.index_type.supported_operations(&self.cast_type))
            .collect();

        hash.into_iter().collect()
    }

    pub fn index_for_operator(&self, op: &Operator) -> Option<&Index> {
        self.indexes
            .iter()
            .find(|i| i.supports(op, &self.cast_type))
    }

    pub fn index_for_sort(&self) -> Option<&Index> {
        self.indexes.iter().find(|i| i.is_orderable())
    }

    /// Sorts indexes by type. Indexes are sorted in place.
    pub fn sort_indexes_by_type(&mut self) {
        self.indexes
            .sort_by(|a, b| a.index_type.as_str().cmp(b.index_type.as_str()));
    }

    pub fn has_unique_index_with_downcase(&self) -> bool {
        self.indexes.iter().any(|index| {
            if let IndexType::Unique { token_filters } = &index.index_type {
                token_filters
                    .iter()
                    .any(|filter| matches!(filter, TokenFilter::Downcase))
            } else {
                false
            }
        })
    }
}