1
//! This module contains datastructures and logic for comparing database privileges,
2
//! generating, validating and reducing diffs between two sets of database privileges.
3

            
4
use super::base::{DatabasePrivilegeRow, db_priv_field_human_readable_name};
5
use crate::core::types::{MySQLDatabase, MySQLUser};
6
use prettytable::Table;
7
use serde::{Deserialize, Serialize};
8
use std::{
9
    collections::{BTreeSet, HashMap, hash_map::Entry},
10
    fmt,
11
};
12

            
13
/// This enum represents a change for a single privilege.
14
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
15
pub enum DatabasePrivilegeChange {
16
    YesToNo,
17
    NoToYes,
18
}
19

            
20
impl DatabasePrivilegeChange {
21
26
    pub fn new(p1: bool, p2: bool) -> Option<DatabasePrivilegeChange> {
22
26
        match (p1, p2) {
23
4
            (true, false) => Some(DatabasePrivilegeChange::YesToNo),
24
3
            (false, true) => Some(DatabasePrivilegeChange::NoToYes),
25
19
            _ => None,
26
        }
27
26
    }
28
}
29

            
30
/// This struct encapsulates the before and after states of the
31
/// access privileges for a single user on a single database.
32
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord, Default)]
33
pub struct DatabasePrivilegeRowDiff {
34
    // TODO: don't store the db and user here, let the type be stored in a mapping
35
    pub db: MySQLDatabase,
36
    pub user: MySQLUser,
37
    pub select_priv: Option<DatabasePrivilegeChange>,
38
    pub insert_priv: Option<DatabasePrivilegeChange>,
39
    pub update_priv: Option<DatabasePrivilegeChange>,
40
    pub delete_priv: Option<DatabasePrivilegeChange>,
41
    pub create_priv: Option<DatabasePrivilegeChange>,
42
    pub drop_priv: Option<DatabasePrivilegeChange>,
43
    pub alter_priv: Option<DatabasePrivilegeChange>,
44
    pub index_priv: Option<DatabasePrivilegeChange>,
45
    pub create_tmp_table_priv: Option<DatabasePrivilegeChange>,
46
    pub lock_tables_priv: Option<DatabasePrivilegeChange>,
47
    pub references_priv: Option<DatabasePrivilegeChange>,
48
}
49

            
50
impl DatabasePrivilegeRowDiff {
51
    /// Calculates the difference between two [`DatabasePrivilegeRow`] instances.
52
2
    pub fn from_rows(
53
2
        row1: &DatabasePrivilegeRow,
54
2
        row2: &DatabasePrivilegeRow,
55
2
    ) -> DatabasePrivilegeRowDiff {
56
2
        debug_assert!(row1.db == row2.db && row1.user == row2.user);
57

            
58
2
        DatabasePrivilegeRowDiff {
59
2
            db: row1.db.to_owned(),
60
2
            user: row1.user.to_owned(),
61
2
            select_priv: DatabasePrivilegeChange::new(row1.select_priv, row2.select_priv),
62
2
            insert_priv: DatabasePrivilegeChange::new(row1.insert_priv, row2.insert_priv),
63
2
            update_priv: DatabasePrivilegeChange::new(row1.update_priv, row2.update_priv),
64
2
            delete_priv: DatabasePrivilegeChange::new(row1.delete_priv, row2.delete_priv),
65
2
            create_priv: DatabasePrivilegeChange::new(row1.create_priv, row2.create_priv),
66
2
            drop_priv: DatabasePrivilegeChange::new(row1.drop_priv, row2.drop_priv),
67
2
            alter_priv: DatabasePrivilegeChange::new(row1.alter_priv, row2.alter_priv),
68
2
            index_priv: DatabasePrivilegeChange::new(row1.index_priv, row2.index_priv),
69
2
            create_tmp_table_priv: DatabasePrivilegeChange::new(
70
2
                row1.create_tmp_table_priv,
71
2
                row2.create_tmp_table_priv,
72
2
            ),
73
2
            lock_tables_priv: DatabasePrivilegeChange::new(
74
2
                row1.lock_tables_priv,
75
2
                row2.lock_tables_priv,
76
2
            ),
77
2
            references_priv: DatabasePrivilegeChange::new(
78
2
                row1.references_priv,
79
2
                row2.references_priv,
80
2
            ),
81
2
        }
82
2
    }
83

            
84
    /// Returns true if there are no changes in this diff.
85
3
    pub fn is_empty(&self) -> bool {
86
3
        self.select_priv.is_none()
87
1
            && self.insert_priv.is_none()
88
1
            && self.update_priv.is_none()
89
1
            && self.delete_priv.is_none()
90
1
            && self.create_priv.is_none()
91
1
            && self.drop_priv.is_none()
92
1
            && self.alter_priv.is_none()
93
1
            && self.index_priv.is_none()
94
1
            && self.create_tmp_table_priv.is_none()
95
1
            && self.lock_tables_priv.is_none()
96
1
            && self.references_priv.is_none()
97
3
    }
98

            
99
    /// Retrieves the privilege change for a given privilege name.
100
    pub fn get_privilege_change_by_name(
101
        &self,
102
        privilege_name: &str,
103
    ) -> anyhow::Result<Option<DatabasePrivilegeChange>> {
104
        match privilege_name {
105
            "select_priv" => Ok(self.select_priv),
106
            "insert_priv" => Ok(self.insert_priv),
107
            "update_priv" => Ok(self.update_priv),
108
            "delete_priv" => Ok(self.delete_priv),
109
            "create_priv" => Ok(self.create_priv),
110
            "drop_priv" => Ok(self.drop_priv),
111
            "alter_priv" => Ok(self.alter_priv),
112
            "index_priv" => Ok(self.index_priv),
113
            "create_tmp_table_priv" => Ok(self.create_tmp_table_priv),
114
            "lock_tables_priv" => Ok(self.lock_tables_priv),
115
            "references_priv" => Ok(self.references_priv),
116
            _ => anyhow::bail!("Unknown privilege name: {}", privilege_name),
117
        }
118
    }
119

            
120
    /// Merges another diff into this one, combining them in a sequential manner.
121
    fn mappend(&mut self, other: &DatabasePrivilegeRowDiff) {
122
        debug_assert!(self.db == other.db && self.user == other.user);
123

            
124
        if other.select_priv.is_some() {
125
            self.select_priv = other.select_priv;
126
        }
127
        if other.insert_priv.is_some() {
128
            self.insert_priv = other.insert_priv;
129
        }
130
        if other.update_priv.is_some() {
131
            self.update_priv = other.update_priv;
132
        }
133
        if other.delete_priv.is_some() {
134
            self.delete_priv = other.delete_priv;
135
        }
136
        if other.create_priv.is_some() {
137
            self.create_priv = other.create_priv;
138
        }
139
        if other.drop_priv.is_some() {
140
            self.drop_priv = other.drop_priv;
141
        }
142
        if other.alter_priv.is_some() {
143
            self.alter_priv = other.alter_priv;
144
        }
145
        if other.index_priv.is_some() {
146
            self.index_priv = other.index_priv;
147
        }
148
        if other.create_tmp_table_priv.is_some() {
149
            self.create_tmp_table_priv = other.create_tmp_table_priv;
150
        }
151
        if other.lock_tables_priv.is_some() {
152
            self.lock_tables_priv = other.lock_tables_priv;
153
        }
154
        if other.references_priv.is_some() {
155
            self.references_priv = other.references_priv;
156
        }
157
    }
158

            
159
    /// Removes any no-op changes from the diff, based on the original privilege row.
160
    fn remove_noops(&mut self, from: &DatabasePrivilegeRow) {
161
        fn new_value(
162
            change: &Option<DatabasePrivilegeChange>,
163
            from_value: bool,
164
        ) -> Option<DatabasePrivilegeChange> {
165
            change.as_ref().and_then(|c| match c {
166
                DatabasePrivilegeChange::YesToNo if from_value => {
167
                    Some(DatabasePrivilegeChange::YesToNo)
168
                }
169
                DatabasePrivilegeChange::NoToYes if !from_value => {
170
                    Some(DatabasePrivilegeChange::NoToYes)
171
                }
172
                _ => None,
173
            })
174
        }
175

            
176
        self.select_priv = new_value(&self.select_priv, from.select_priv);
177
        self.insert_priv = new_value(&self.insert_priv, from.insert_priv);
178
        self.update_priv = new_value(&self.update_priv, from.update_priv);
179
        self.delete_priv = new_value(&self.delete_priv, from.delete_priv);
180
        self.create_priv = new_value(&self.create_priv, from.create_priv);
181
        self.drop_priv = new_value(&self.drop_priv, from.drop_priv);
182
        self.alter_priv = new_value(&self.alter_priv, from.alter_priv);
183
        self.index_priv = new_value(&self.index_priv, from.index_priv);
184
        self.create_tmp_table_priv =
185
            new_value(&self.create_tmp_table_priv, from.create_tmp_table_priv);
186
        self.lock_tables_priv = new_value(&self.lock_tables_priv, from.lock_tables_priv);
187
        self.references_priv = new_value(&self.references_priv, from.references_priv);
188
    }
189

            
190
    fn apply(&self, base: &mut DatabasePrivilegeRow) {
191
        fn apply_change(change: &Option<DatabasePrivilegeChange>, target: &mut bool) {
192
            match change {
193
                Some(DatabasePrivilegeChange::YesToNo) => *target = false,
194
                Some(DatabasePrivilegeChange::NoToYes) => *target = true,
195
                None => {}
196
            }
197
        }
198

            
199
        apply_change(&self.select_priv, &mut base.select_priv);
200
        apply_change(&self.insert_priv, &mut base.insert_priv);
201
        apply_change(&self.update_priv, &mut base.update_priv);
202
        apply_change(&self.delete_priv, &mut base.delete_priv);
203
        apply_change(&self.create_priv, &mut base.create_priv);
204
        apply_change(&self.drop_priv, &mut base.drop_priv);
205
        apply_change(&self.alter_priv, &mut base.alter_priv);
206
        apply_change(&self.index_priv, &mut base.index_priv);
207
        apply_change(&self.create_tmp_table_priv, &mut base.create_tmp_table_priv);
208
        apply_change(&self.lock_tables_priv, &mut base.lock_tables_priv);
209
        apply_change(&self.references_priv, &mut base.references_priv);
210
    }
211
}
212

            
213
impl fmt::Display for DatabasePrivilegeRowDiff {
214
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215
        fn format_change(
216
            f: &mut fmt::Formatter<'_>,
217
            change: &Option<DatabasePrivilegeChange>,
218
            field_name: &str,
219
        ) -> fmt::Result {
220
            if let Some(change) = change {
221
                match change {
222
                    DatabasePrivilegeChange::YesToNo => f.write_fmt(format_args!(
223
                        "{}: Y -> N\n",
224
                        db_priv_field_human_readable_name(field_name)
225
                    )),
226
                    DatabasePrivilegeChange::NoToYes => f.write_fmt(format_args!(
227
                        "{}: N -> Y\n",
228
                        db_priv_field_human_readable_name(field_name)
229
                    )),
230
                }
231
            } else {
232
                Ok(())
233
            }
234
        }
235

            
236
        format_change(f, &self.select_priv, "select_priv")?;
237
        format_change(f, &self.insert_priv, "insert_priv")?;
238
        format_change(f, &self.update_priv, "update_priv")?;
239
        format_change(f, &self.delete_priv, "delete_priv")?;
240
        format_change(f, &self.create_priv, "create_priv")?;
241
        format_change(f, &self.drop_priv, "drop_priv")?;
242
        format_change(f, &self.alter_priv, "alter_priv")?;
243
        format_change(f, &self.index_priv, "index_priv")?;
244
        format_change(f, &self.create_tmp_table_priv, "create_tmp_table_priv")?;
245
        format_change(f, &self.lock_tables_priv, "lock_tables_priv")?;
246
        format_change(f, &self.references_priv, "references_priv")?;
247

            
248
        Ok(())
249
    }
250
}
251

            
252
/// This enum encapsulates whether a [`DatabasePrivilegeRow`] was introduced, modified or deleted.
253
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
254
pub enum DatabasePrivilegesDiff {
255
    New(DatabasePrivilegeRow),
256
    Modified(DatabasePrivilegeRowDiff),
257
    Deleted(DatabasePrivilegeRow),
258
    Noop { db: MySQLDatabase, user: MySQLUser },
259
}
260

            
261
impl DatabasePrivilegesDiff {
262
    pub fn get_database_name(&self) -> &MySQLDatabase {
263
        match self {
264
            DatabasePrivilegesDiff::New(p) => &p.db,
265
            DatabasePrivilegesDiff::Modified(p) => &p.db,
266
            DatabasePrivilegesDiff::Deleted(p) => &p.db,
267
            DatabasePrivilegesDiff::Noop { db, .. } => db,
268
        }
269
    }
270

            
271
    pub fn get_user_name(&self) -> &MySQLUser {
272
        match self {
273
            DatabasePrivilegesDiff::New(p) => &p.user,
274
            DatabasePrivilegesDiff::Modified(p) => &p.user,
275
            DatabasePrivilegesDiff::Deleted(p) => &p.user,
276
            DatabasePrivilegesDiff::Noop { user, .. } => user,
277
        }
278
    }
279

            
280
    /// Merges another [`DatabasePrivilegesDiff`] into this one, combining them in a sequential manner.
281
    /// For example, if this diff represents a creation and the other represents a modification,
282
    /// the result will be a creation with the modifications applied.
283
    pub fn mappend(&mut self, other: &DatabasePrivilegesDiff) -> anyhow::Result<()> {
284
        debug_assert!(
285
            self.get_database_name() == other.get_database_name()
286
                && self.get_user_name() == other.get_user_name()
287
        );
288

            
289
        if matches!(self, DatabasePrivilegesDiff::Deleted(_))
290
            && (matches!(other, DatabasePrivilegesDiff::Modified(_)))
291
        {
292
            anyhow::bail!("Cannot modify a deleted database privilege row");
293
        }
294

            
295
        if matches!(self, DatabasePrivilegesDiff::New(_))
296
            && (matches!(other, DatabasePrivilegesDiff::New(_)))
297
        {
298
            anyhow::bail!("Cannot create an already existing database privilege row");
299
        }
300

            
301
        if matches!(self, DatabasePrivilegesDiff::Modified(_))
302
            && (matches!(other, DatabasePrivilegesDiff::New(_)))
303
        {
304
            anyhow::bail!("Cannot create an already existing database privilege row");
305
        }
306

            
307
        if matches!(self, DatabasePrivilegesDiff::Noop { .. }) {
308
            *self = other.to_owned();
309
            return Ok(());
310
        } else if matches!(other, DatabasePrivilegesDiff::Noop { .. }) {
311
            return Ok(());
312
        }
313

            
314
        match (&self, other) {
315
            (DatabasePrivilegesDiff::New(_), DatabasePrivilegesDiff::Modified(modified)) => {
316
                let inner_row = match self {
317
                    DatabasePrivilegesDiff::New(r) => r,
318
                    _ => unreachable!(),
319
                };
320
                modified.apply(inner_row);
321
            }
322
            (DatabasePrivilegesDiff::Modified(_), DatabasePrivilegesDiff::Modified(modified)) => {
323
                let inner_diff = match self {
324
                    DatabasePrivilegesDiff::Modified(r) => r,
325
                    _ => unreachable!(),
326
                };
327
                inner_diff.mappend(modified);
328

            
329
                if inner_diff.is_empty() {
330
                    let db = inner_diff.db.to_owned();
331
                    let user = inner_diff.user.to_owned();
332
                    *self = DatabasePrivilegesDiff::Noop { db, user };
333
                }
334
            }
335
            (DatabasePrivilegesDiff::Modified(_), DatabasePrivilegesDiff::Deleted(deleted)) => {
336
                *self = DatabasePrivilegesDiff::Deleted(deleted.to_owned());
337
            }
338
            (DatabasePrivilegesDiff::New(_), DatabasePrivilegesDiff::Deleted(_)) => {
339
                let db = self.get_database_name().to_owned();
340
                let user = self.get_user_name().to_owned();
341
                *self = DatabasePrivilegesDiff::Noop { db, user };
342
            }
343
            _ => {}
344
        }
345

            
346
        Ok(())
347
    }
348
}
349

            
350
pub type DatabasePrivilegeState<'a> = &'a [DatabasePrivilegeRow];
351

            
352
/// This function calculates the differences between two sets of database privileges.
353
/// It returns a set of [`DatabasePrivilegesDiff`] that can be used to display or
354
/// apply a set of privilege modifications to the database.
355
1
pub fn diff_privileges(
356
1
    from: DatabasePrivilegeState<'_>,
357
1
    to: &[DatabasePrivilegeRow],
358
1
) -> BTreeSet<DatabasePrivilegesDiff> {
359
1
    let from_lookup_table: HashMap<(MySQLDatabase, MySQLUser), DatabasePrivilegeRow> =
360
1
        HashMap::from_iter(
361
1
            from.iter()
362
1
                .cloned()
363
2
                .map(|p| ((p.db.to_owned(), p.user.to_owned()), p)),
364
        );
365

            
366
1
    let to_lookup_table: HashMap<(MySQLDatabase, MySQLUser), DatabasePrivilegeRow> =
367
1
        HashMap::from_iter(
368
1
            to.iter()
369
1
                .cloned()
370
2
                .map(|p| ((p.db.to_owned(), p.user.to_owned()), p)),
371
        );
372

            
373
1
    let mut result = BTreeSet::new();
374

            
375
2
    for p in to {
376
2
        if let Some(old_p) = from_lookup_table.get(&(p.db.to_owned(), p.user.to_owned())) {
377
1
            let diff = DatabasePrivilegeRowDiff::from_rows(old_p, p);
378
1
            if !diff.is_empty() {
379
1
                result.insert(DatabasePrivilegesDiff::Modified(diff));
380
1
            }
381
1
        } else {
382
1
            result.insert(DatabasePrivilegesDiff::New(p.to_owned()));
383
1
        }
384
    }
385

            
386
2
    for p in from {
387
2
        if !to_lookup_table.contains_key(&(p.db.to_owned(), p.user.to_owned())) {
388
1
            result.insert(DatabasePrivilegesDiff::Deleted(p.to_owned()));
389
1
        }
390
    }
391

            
392
1
    result
393
1
}
394

            
395
/// Converts a set of [`DatabasePrivilegeRowDiff`] into a set of [`DatabasePrivilegesDiff`],
396
/// representing either creating new privilege rows, or modifying the existing ones.
397
///
398
/// This is particularly useful for processing CLI arguments.
399
pub fn create_or_modify_privilege_rows(
400
    from: DatabasePrivilegeState<'_>,
401
    to: &BTreeSet<DatabasePrivilegeRowDiff>,
402
) -> anyhow::Result<BTreeSet<DatabasePrivilegesDiff>> {
403
    let from_lookup_table: HashMap<(MySQLDatabase, MySQLUser), DatabasePrivilegeRow> =
404
        HashMap::from_iter(
405
            from.iter()
406
                .cloned()
407
                .map(|p| ((p.db.to_owned(), p.user.to_owned()), p)),
408
        );
409

            
410
    let mut result = BTreeSet::new();
411

            
412
    for diff in to {
413
        if let Some(old_p) = from_lookup_table.get(&(diff.db.to_owned(), diff.user.to_owned())) {
414
            let mut modified_diff = diff.to_owned();
415
            modified_diff.remove_noops(old_p);
416
            if !modified_diff.is_empty() {
417
                result.insert(DatabasePrivilegesDiff::Modified(modified_diff));
418
            }
419
        } else {
420
            let mut new_row = DatabasePrivilegeRow {
421
                db: diff.db.to_owned(),
422
                user: diff.user.to_owned(),
423
                select_priv: false,
424
                insert_priv: false,
425
                update_priv: false,
426
                delete_priv: false,
427
                create_priv: false,
428
                drop_priv: false,
429
                alter_priv: false,
430
                index_priv: false,
431
                create_tmp_table_priv: false,
432
                lock_tables_priv: false,
433
                references_priv: false,
434
            };
435
            diff.apply(&mut new_row);
436
            result.insert(DatabasePrivilegesDiff::New(new_row));
437
        }
438
    }
439

            
440
    Ok(result)
441
}
442

            
443
/// Reduces a set of [`DatabasePrivilegesDiff`] by removing any modifications that would be no-ops.
444
/// For example, if a privilege is changed from Yes to No, but it was already No, that change
445
/// is removed from the diff.
446
///
447
/// The `from` parameter is used to determine the current state of the privileges.
448
/// The `to` parameter is the set of diffs to be reduced.
449
pub fn reduce_privilege_diffs(
450
    from: DatabasePrivilegeState<'_>,
451
    to: BTreeSet<DatabasePrivilegesDiff>,
452
) -> anyhow::Result<BTreeSet<DatabasePrivilegesDiff>> {
453
    let from_lookup_table: HashMap<(MySQLDatabase, MySQLUser), DatabasePrivilegeRow> =
454
        HashMap::from_iter(
455
            from.iter()
456
                .cloned()
457
                .map(|p| ((p.db.to_owned(), p.user.to_owned()), p)),
458
        );
459

            
460
    let mut result: HashMap<(MySQLDatabase, MySQLUser), DatabasePrivilegesDiff> = from_lookup_table
461
        .iter()
462
        .map(|((db, user), _)| {
463
            (
464
                (db.to_owned(), user.to_owned()),
465
                DatabasePrivilegesDiff::Noop {
466
                    db: db.to_owned(),
467
                    user: user.to_owned(),
468
                },
469
            )
470
        })
471
        .collect();
472

            
473
    for diff in to {
474
        let entry = result.entry((
475
            diff.get_database_name().to_owned(),
476
            diff.get_user_name().to_owned(),
477
        ));
478
        match entry {
479
            Entry::Occupied(mut occupied_entry) => {
480
                let existing_diff = occupied_entry.get_mut();
481
                existing_diff.mappend(&diff)?;
482
            }
483
            Entry::Vacant(vacant_entry) => {
484
                vacant_entry.insert(diff.to_owned());
485
            }
486
        }
487
    }
488

            
489
    for (key, diff) in result.iter_mut() {
490
        if let Some(from_row) = from_lookup_table.get(key)
491
            && let DatabasePrivilegesDiff::Modified(modified_diff) = diff
492
        {
493
            modified_diff.remove_noops(from_row);
494
            if modified_diff.is_empty() {
495
                let db = modified_diff.db.to_owned();
496
                let user = modified_diff.user.to_owned();
497
                *diff = DatabasePrivilegesDiff::Noop { db, user };
498
            }
499
        }
500
    }
501

            
502
    Ok(result
503
        .into_values()
504
        .filter(|diff| !matches!(diff, DatabasePrivilegesDiff::Noop { .. }))
505
        .collect::<BTreeSet<DatabasePrivilegesDiff>>())
506
}
507

            
508
/// Renders a set of [`DatabasePrivilegesDiff`] into a human-readable formatted table.
509
pub fn display_privilege_diffs(diffs: &BTreeSet<DatabasePrivilegesDiff>) -> String {
510
    let mut table = Table::new();
511
    table.set_titles(row!["Database", "User", "Privilege diff",]);
512
    for row in diffs {
513
        match row {
514
            DatabasePrivilegesDiff::New(p) => {
515
                table.add_row(row![
516
                    p.db,
517
                    p.user,
518
                    "(Previously unprivileged)\n".to_string() + &p.to_string()
519
                ]);
520
            }
521
            DatabasePrivilegesDiff::Modified(p) => {
522
                table.add_row(row![p.db, p.user, p.to_string(),]);
523
            }
524
            DatabasePrivilegesDiff::Deleted(p) => {
525
                table.add_row(row![p.db, p.user, "Removed".to_string()]);
526
            }
527
            DatabasePrivilegesDiff::Noop { db, user } => {
528
                table.add_row(row![db, user, "No changes".to_string()]);
529
            }
530
        }
531
    }
532

            
533
    table.to_string()
534
}
535

            
536
#[cfg(test)]
537
mod tests {
538
    use super::*;
539

            
540
    #[test]
541
1
    fn test_database_privilege_change_creation() {
542
1
        assert_eq!(
543
1
            DatabasePrivilegeChange::new(true, false),
544
            Some(DatabasePrivilegeChange::YesToNo),
545
        );
546
1
        assert_eq!(
547
1
            DatabasePrivilegeChange::new(false, true),
548
            Some(DatabasePrivilegeChange::NoToYes),
549
        );
550
1
        assert_eq!(DatabasePrivilegeChange::new(true, true), None);
551
1
        assert_eq!(DatabasePrivilegeChange::new(false, false), None);
552
1
    }
553

            
554
    #[test]
555
1
    fn test_database_privilege_row_diff_from_rows() {
556
1
        let row1 = DatabasePrivilegeRow {
557
1
            db: "db".into(),
558
1
            user: "user".into(),
559
1

            
560
1
            select_priv: true,
561
1
            insert_priv: false,
562
1
            update_priv: true,
563
1
            delete_priv: false,
564
1

            
565
1
            create_priv: false,
566
1
            drop_priv: false,
567
1
            alter_priv: false,
568
1
            index_priv: false,
569
1
            create_tmp_table_priv: false,
570
1
            lock_tables_priv: false,
571
1
            references_priv: false,
572
1
        };
573
1
        let row2 = DatabasePrivilegeRow {
574
1
            db: "db".into(),
575
1
            user: "user".into(),
576
1

            
577
1
            select_priv: true,
578
1
            insert_priv: true,
579
1
            update_priv: false,
580
1
            delete_priv: false,
581
1

            
582
1
            create_priv: false,
583
1
            drop_priv: false,
584
1
            alter_priv: false,
585
1
            index_priv: false,
586
1
            create_tmp_table_priv: false,
587
1
            lock_tables_priv: false,
588
1
            references_priv: false,
589
1
        };
590

            
591
1
        let diff = DatabasePrivilegeRowDiff::from_rows(&row1, &row2);
592
1
        assert_eq!(
593
            diff,
594
1
            DatabasePrivilegeRowDiff {
595
1
                db: "db".into(),
596
1
                user: "user".into(),
597
1
                select_priv: None,
598
1
                insert_priv: Some(DatabasePrivilegeChange::NoToYes),
599
1
                update_priv: Some(DatabasePrivilegeChange::YesToNo),
600
1
                delete_priv: None,
601
1
                ..Default::default()
602
1
            },
603
        );
604
1
    }
605

            
606
    #[test]
607
1
    fn test_database_privilege_row_diff_is_empty() {
608
1
        let empty_diff = DatabasePrivilegeRowDiff {
609
1
            db: "db".into(),
610
1
            user: "user".into(),
611
1
            ..Default::default()
612
1
        };
613

            
614
1
        assert!(empty_diff.is_empty());
615

            
616
1
        let non_empty_diff = DatabasePrivilegeRowDiff {
617
1
            db: "db".into(),
618
1
            user: "user".into(),
619
1
            select_priv: Some(DatabasePrivilegeChange::YesToNo),
620
1
            ..Default::default()
621
1
        };
622

            
623
1
        assert!(!non_empty_diff.is_empty());
624
1
    }
625

            
626
    // TODO: test in isolation:
627
    // DatabasePrivilegeRowDiff::mappend
628
    // DatabasePrivilegeRowDiff::remove_noops
629
    // DatabasePrivilegeRowDiff::apply
630
    //
631
    // DatabasePrivilegesDiff::mappend
632
    //
633
    // reduce_privilege_diffs
634

            
635
    #[test]
636
1
    fn test_diff_privileges() {
637
1
        let row_to_be_modified = DatabasePrivilegeRow {
638
1
            db: "db".into(),
639
1
            user: "user".into(),
640
1
            select_priv: true,
641
1
            insert_priv: true,
642
1
            update_priv: true,
643
1
            delete_priv: true,
644
1
            create_priv: true,
645
1
            drop_priv: true,
646
1
            alter_priv: true,
647
1
            index_priv: false,
648
1
            create_tmp_table_priv: true,
649
1
            lock_tables_priv: true,
650
1
            references_priv: false,
651
1
        };
652

            
653
1
        let mut row_to_be_deleted = row_to_be_modified.to_owned();
654
1
        "user2".clone_into(&mut row_to_be_deleted.user);
655

            
656
1
        let from = vec![row_to_be_modified.to_owned(), row_to_be_deleted.to_owned()];
657

            
658
1
        let mut modified_row = row_to_be_modified.to_owned();
659
1
        modified_row.select_priv = false;
660
1
        modified_row.insert_priv = false;
661
1
        modified_row.index_priv = true;
662

            
663
1
        let mut new_row = row_to_be_modified.to_owned();
664
1
        "user3".clone_into(&mut new_row.user);
665

            
666
1
        let to = vec![modified_row.to_owned(), new_row.to_owned()];
667

            
668
1
        let diffs = diff_privileges(&from, &to);
669

            
670
1
        assert_eq!(
671
            diffs,
672
1
            BTreeSet::from_iter(vec![
673
1
                DatabasePrivilegesDiff::Deleted(row_to_be_deleted),
674
1
                DatabasePrivilegesDiff::Modified(DatabasePrivilegeRowDiff {
675
1
                    db: "db".into(),
676
1
                    user: "user".into(),
677
1
                    select_priv: Some(DatabasePrivilegeChange::YesToNo),
678
1
                    insert_priv: Some(DatabasePrivilegeChange::YesToNo),
679
1
                    index_priv: Some(DatabasePrivilegeChange::NoToYes),
680
1
                    ..Default::default()
681
1
                }),
682
1
                DatabasePrivilegesDiff::New(new_row),
683
            ])
684
        );
685
1
    }
686
}