1use super::base::{DatabasePrivilegeRow, db_priv_field_human_readable_name};
5use crate::core::types::{MySQLDatabase, MySQLUser};
6use prettytable::Table;
7use serde::{Deserialize, Serialize};
8use std::{
9 collections::{BTreeSet, HashMap, hash_map::Entry},
10 fmt,
11};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
15pub enum DatabasePrivilegeChange {
16 YesToNo,
17 NoToYes,
18}
19
20impl DatabasePrivilegeChange {
21 pub fn new(p1: bool, p2: bool) -> Option<DatabasePrivilegeChange> {
22 match (p1, p2) {
23 (true, false) => Some(DatabasePrivilegeChange::YesToNo),
24 (false, true) => Some(DatabasePrivilegeChange::NoToYes),
25 _ => None,
26 }
27 }
28}
29
30#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord, Default)]
33pub struct DatabasePrivilegeRowDiff {
34 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
50impl DatabasePrivilegeRowDiff {
51 pub fn from_rows(
53 row1: &DatabasePrivilegeRow,
54 row2: &DatabasePrivilegeRow,
55 ) -> DatabasePrivilegeRowDiff {
56 debug_assert!(row1.db == row2.db && row1.user == row2.user);
57
58 DatabasePrivilegeRowDiff {
59 db: row1.db.to_owned(),
60 user: row1.user.to_owned(),
61 select_priv: DatabasePrivilegeChange::new(row1.select_priv, row2.select_priv),
62 insert_priv: DatabasePrivilegeChange::new(row1.insert_priv, row2.insert_priv),
63 update_priv: DatabasePrivilegeChange::new(row1.update_priv, row2.update_priv),
64 delete_priv: DatabasePrivilegeChange::new(row1.delete_priv, row2.delete_priv),
65 create_priv: DatabasePrivilegeChange::new(row1.create_priv, row2.create_priv),
66 drop_priv: DatabasePrivilegeChange::new(row1.drop_priv, row2.drop_priv),
67 alter_priv: DatabasePrivilegeChange::new(row1.alter_priv, row2.alter_priv),
68 index_priv: DatabasePrivilegeChange::new(row1.index_priv, row2.index_priv),
69 create_tmp_table_priv: DatabasePrivilegeChange::new(
70 row1.create_tmp_table_priv,
71 row2.create_tmp_table_priv,
72 ),
73 lock_tables_priv: DatabasePrivilegeChange::new(
74 row1.lock_tables_priv,
75 row2.lock_tables_priv,
76 ),
77 references_priv: DatabasePrivilegeChange::new(
78 row1.references_priv,
79 row2.references_priv,
80 ),
81 }
82 }
83
84 pub fn is_empty(&self) -> bool {
86 self.select_priv.is_none()
87 && self.insert_priv.is_none()
88 && self.update_priv.is_none()
89 && self.delete_priv.is_none()
90 && self.create_priv.is_none()
91 && self.drop_priv.is_none()
92 && self.alter_priv.is_none()
93 && self.index_priv.is_none()
94 && self.create_tmp_table_priv.is_none()
95 && self.lock_tables_priv.is_none()
96 && self.references_priv.is_none()
97 }
98
99 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 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 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
213impl 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#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
254pub enum DatabasePrivilegesDiff {
255 New(DatabasePrivilegeRow),
256 Modified(DatabasePrivilegeRowDiff),
257 Deleted(DatabasePrivilegeRow),
258 Noop { db: MySQLDatabase, user: MySQLUser },
259}
260
261impl 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 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
350pub type DatabasePrivilegeState<'a> = &'a [DatabasePrivilegeRow];
351
352pub fn diff_privileges(
356 from: DatabasePrivilegeState<'_>,
357 to: &[DatabasePrivilegeRow],
358) -> BTreeSet<DatabasePrivilegesDiff> {
359 let from_lookup_table: HashMap<(MySQLDatabase, MySQLUser), DatabasePrivilegeRow> =
360 HashMap::from_iter(
361 from.iter()
362 .cloned()
363 .map(|p| ((p.db.to_owned(), p.user.to_owned()), p)),
364 );
365
366 let to_lookup_table: HashMap<(MySQLDatabase, MySQLUser), DatabasePrivilegeRow> =
367 HashMap::from_iter(
368 to.iter()
369 .cloned()
370 .map(|p| ((p.db.to_owned(), p.user.to_owned()), p)),
371 );
372
373 let mut result = BTreeSet::new();
374
375 for p in to {
376 if let Some(old_p) = from_lookup_table.get(&(p.db.to_owned(), p.user.to_owned())) {
377 let diff = DatabasePrivilegeRowDiff::from_rows(old_p, p);
378 if !diff.is_empty() {
379 result.insert(DatabasePrivilegesDiff::Modified(diff));
380 }
381 } else {
382 result.insert(DatabasePrivilegesDiff::New(p.to_owned()));
383 }
384 }
385
386 for p in from {
387 if !to_lookup_table.contains_key(&(p.db.to_owned(), p.user.to_owned())) {
388 result.insert(DatabasePrivilegesDiff::Deleted(p.to_owned()));
389 }
390 }
391
392 result
393}
394
395pub 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
443pub 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
508pub 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)]
537mod tests {
538 use super::*;
539
540 #[test]
541 fn test_database_privilege_change_creation() {
542 assert_eq!(
543 DatabasePrivilegeChange::new(true, false),
544 Some(DatabasePrivilegeChange::YesToNo),
545 );
546 assert_eq!(
547 DatabasePrivilegeChange::new(false, true),
548 Some(DatabasePrivilegeChange::NoToYes),
549 );
550 assert_eq!(DatabasePrivilegeChange::new(true, true), None);
551 assert_eq!(DatabasePrivilegeChange::new(false, false), None);
552 }
553
554 #[test]
555 fn test_database_privilege_row_diff_from_rows() {
556 let row1 = DatabasePrivilegeRow {
557 db: "db".into(),
558 user: "user".into(),
559
560 select_priv: true,
561 insert_priv: false,
562 update_priv: true,
563 delete_priv: false,
564
565 create_priv: false,
566 drop_priv: false,
567 alter_priv: false,
568 index_priv: false,
569 create_tmp_table_priv: false,
570 lock_tables_priv: false,
571 references_priv: false,
572 };
573 let row2 = DatabasePrivilegeRow {
574 db: "db".into(),
575 user: "user".into(),
576
577 select_priv: true,
578 insert_priv: true,
579 update_priv: false,
580 delete_priv: false,
581
582 create_priv: false,
583 drop_priv: false,
584 alter_priv: false,
585 index_priv: false,
586 create_tmp_table_priv: false,
587 lock_tables_priv: false,
588 references_priv: false,
589 };
590
591 let diff = DatabasePrivilegeRowDiff::from_rows(&row1, &row2);
592 assert_eq!(
593 diff,
594 DatabasePrivilegeRowDiff {
595 db: "db".into(),
596 user: "user".into(),
597 select_priv: None,
598 insert_priv: Some(DatabasePrivilegeChange::NoToYes),
599 update_priv: Some(DatabasePrivilegeChange::YesToNo),
600 delete_priv: None,
601 ..Default::default()
602 },
603 );
604 }
605
606 #[test]
607 fn test_database_privilege_row_diff_is_empty() {
608 let empty_diff = DatabasePrivilegeRowDiff {
609 db: "db".into(),
610 user: "user".into(),
611 ..Default::default()
612 };
613
614 assert!(empty_diff.is_empty());
615
616 let non_empty_diff = DatabasePrivilegeRowDiff {
617 db: "db".into(),
618 user: "user".into(),
619 select_priv: Some(DatabasePrivilegeChange::YesToNo),
620 ..Default::default()
621 };
622
623 assert!(!non_empty_diff.is_empty());
624 }
625
626 #[test]
636 fn test_diff_privileges() {
637 let row_to_be_modified = DatabasePrivilegeRow {
638 db: "db".into(),
639 user: "user".into(),
640 select_priv: true,
641 insert_priv: true,
642 update_priv: true,
643 delete_priv: true,
644 create_priv: true,
645 drop_priv: true,
646 alter_priv: true,
647 index_priv: false,
648 create_tmp_table_priv: true,
649 lock_tables_priv: true,
650 references_priv: false,
651 };
652
653 let mut row_to_be_deleted = row_to_be_modified.to_owned();
654 "user2".clone_into(&mut row_to_be_deleted.user);
655
656 let from = vec![row_to_be_modified.to_owned(), row_to_be_deleted.to_owned()];
657
658 let mut modified_row = row_to_be_modified.to_owned();
659 modified_row.select_priv = false;
660 modified_row.insert_priv = false;
661 modified_row.index_priv = true;
662
663 let mut new_row = row_to_be_modified.to_owned();
664 "user3".clone_into(&mut new_row.user);
665
666 let to = vec![modified_row.to_owned(), new_row.to_owned()];
667
668 let diffs = diff_privileges(&from, &to);
669
670 assert_eq!(
671 diffs,
672 BTreeSet::from_iter(vec![
673 DatabasePrivilegesDiff::Deleted(row_to_be_deleted),
674 DatabasePrivilegesDiff::Modified(DatabasePrivilegeRowDiff {
675 db: "db".into(),
676 user: "user".into(),
677 select_priv: Some(DatabasePrivilegeChange::YesToNo),
678 insert_priv: Some(DatabasePrivilegeChange::YesToNo),
679 index_priv: Some(DatabasePrivilegeChange::NoToYes),
680 ..Default::default()
681 }),
682 DatabasePrivilegesDiff::New(new_row),
683 ])
684 );
685 }
686}