cipherstash_config/
dataset.rs

1use crate::{errors::ConfigError, list::UniqueList, TableConfig};
2use serde::{Deserialize, Serialize};
3
4/// Struct to manage the config for a given database.
5/// At connection time, the Driver will retrieve config from Vitur
6/// for the currently connected database
7#[derive(Debug, Clone, Serialize, Deserialize)]
8pub struct DatasetConfig {
9    pub tables: UniqueList<TableConfig>,
10}
11
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct DatasetConfigWithIndexRootKey {
14    /// The "root key" used for deriving ORE and bloom filter keys used in indexes
15    pub index_root_key: [u8; 32],
16    #[serde(flatten)]
17    pub config: DatasetConfig,
18}
19
20impl DatasetConfig {
21    pub fn init() -> Self {
22        Self {
23            tables: Default::default(),
24        }
25    }
26
27    pub fn add_table(mut self, config: TableConfig) -> Result<Self, ConfigError> {
28        self.tables.try_insert(config)?;
29
30        Ok(self)
31    }
32
33    /// Returns true if a table matches the given query
34    pub fn has_table<Q>(&self, query: &Q) -> bool
35    where
36        TableConfig: PartialEq<Q>,
37    {
38        self.get_table(query).is_some()
39    }
40
41    /// Finds a table that matches `query`
42    pub fn get_table<Q>(&self, query: &Q) -> Option<&TableConfig>
43    where
44        TableConfig: PartialEq<Q>,
45    {
46        self.tables.get(query)
47    }
48
49    /// Sorts all indexes by type for each field in each table. Indexes are sorted in place.
50    ///
51    /// This is useful for ensuring that iteration over indexes always occurs in order
52    /// by type (instead of the order that they appear in a config file or the order of
53    /// `ColumnConfig::add_index` calls).
54    pub fn sort_indexes_by_type(mut self) -> Self {
55        self.tables
56            .iter_mut()
57            .for_each(TableConfig::sort_indexes_by_type);
58
59        self
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    use indoc::indoc;
67    use std::error::Error;
68    use toml::to_string_pretty;
69
70    use crate::*;
71
72    #[test]
73    fn add_and_get_table() -> Result<(), Box<dyn Error>> {
74        let config = DatasetConfig::init().add_table(TableConfig::new("users")?)?;
75
76        assert!(matches!(
77            config.get_table(&"users"),
78            Some(TableConfig { path, .. }) if path.as_string() == "users"
79        ));
80
81        Ok(())
82    }
83
84    #[test]
85    fn add_and_get_table_with_schema() -> Result<(), Box<dyn Error>> {
86        let config = DatasetConfig::init().add_table(TableConfig::new("public.users")?)?;
87
88        assert!(matches!(
89            config.get_table(&"users"),
90            Some(TableConfig { path, .. }) if path.as_string() == "public.users"
91        ));
92
93        Ok(())
94    }
95
96    #[test]
97    fn test_serialise_to_toml_single_table() -> Result<(), Box<dyn Error>> {
98        let config = DatasetConfig::init().add_table(TableConfig::new("test")?)?;
99
100        assert_eq!(
101            to_string_pretty(&config)?,
102            indoc! { r#"
103                [[tables]]
104                path = "test"
105                fields = []
106            "#}
107        );
108
109        Ok(())
110    }
111
112    #[test]
113    fn test_serialise_to_toml_multiple_table() -> Result<(), Box<dyn Error>> {
114        let config = DatasetConfig::init()
115            .add_table(TableConfig::new("test")?)?
116            .add_table(
117                TableConfig::new("another")?.add_column(
118                    ColumnConfig::build("great-column")
119                        .add_index(column::Index::new_ore())
120                        .add_index(column::Index::new_match()),
121                )?,
122            )?;
123
124        assert_eq!(
125            to_string_pretty(&config)?,
126            indoc! { r#"
127                [[tables]]
128                path = "test"
129                fields = []
130
131                [[tables]]
132                path = "another"
133
134                [[tables.fields]]
135                name = "great-column"
136                in_place = false
137                cast_type = "utf8-str"
138                mode = "encrypted-duplicate"
139
140                [[tables.fields.indexes]]
141                version = 1
142                kind = "ore"
143
144                [[tables.fields.indexes]]
145                version = 1
146                kind = "match"
147                k = 6
148                m = 2048
149                include_original = true
150
151                [tables.fields.indexes.tokenizer]
152                kind = "ngram"
153                token_length = 3
154
155                [[tables.fields.indexes.token_filters]]
156                kind = "downcase"
157            "#}
158        );
159
160        Ok(())
161    }
162
163    #[test]
164    fn test_sort_indexes() -> Result<(), Box<dyn Error>> {
165        let config = DatasetConfig::init()
166            .add_table(
167                TableConfig::new("users")?.add_column(
168                    ColumnConfig::build("name")
169                        .add_index(column::Index::new_ore())
170                        .add_index(column::Index::new_unique())
171                        .add_index(column::Index::new_match()),
172                )?,
173            )?
174            .sort_indexes_by_type();
175
176        let index_types = &config.tables[0].fields[0]
177            .indexes
178            .iter()
179            .map(|index| index.as_str())
180            .collect::<Vec<_>>();
181
182        assert_eq!(index_types, &vec!["match", "ore", "unique"]);
183
184        Ok(())
185    }
186}