1use anyhow::Result;
3use sqlx::postgres::PgConnection;
4use tracing::info;
5
6use super::id::{DbObjectId, DependsOn};
7use super::target::AttrTarget;
8
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum GranteeType {
11 Role(String),
12 Public,
13}
14
15#[derive(Debug, Clone)]
16pub struct Grant {
17 pub grantee: GranteeType,
18 pub target: AttrTarget,
19 pub privileges: Vec<String>, pub with_grant_option: bool,
21 pub depends_on: Vec<DbObjectId>,
22 pub object_owner: String, pub is_default_acl: bool,
27}
28
29pub fn target_key(target: &AttrTarget) -> String {
32 if let Some(column) = target.column_name() {
33 let (schema, relation) = target.schema_and_name();
34 return format!("column:{}.{}.{}", schema, relation, column);
35 }
36 match &target.object {
37 DbObjectId::Table { schema, name } => format!("table:{}.{}", schema, name),
38 DbObjectId::View { schema, name } => format!("view:{}.{}", schema, name),
39 DbObjectId::Schema { name } => format!("schema:{}", name),
40 DbObjectId::Function {
41 schema,
42 name,
43 arguments,
44 } => format!("function:{}.{}({})", schema, name, arguments),
45 DbObjectId::Procedure {
46 schema,
47 name,
48 arguments,
49 } => format!("procedure:{}.{}({})", schema, name, arguments),
50 DbObjectId::Aggregate {
51 schema,
52 name,
53 arguments,
54 } => format!("aggregate:{}.{}({})", schema, name, arguments),
55 DbObjectId::Sequence { schema, name } => format!("sequence:{}.{}", schema, name),
56 DbObjectId::Type { schema, name } => format!("type:{}.{}", schema, name),
57 DbObjectId::Domain { schema, name } => format!("domain:{}.{}", schema, name),
58 other => other.to_string(),
60 }
61}
62
63impl Grant {
64 pub fn id(&self) -> String {
65 let grantee_str = match &self.grantee {
66 GranteeType::Role(name) => name.clone(),
67 GranteeType::Public => "public".to_string(),
68 };
69 format!("{}@{}", grantee_str, target_key(&self.target))
70 }
71}
72
73impl DependsOn for Grant {
74 fn id(&self) -> DbObjectId {
75 DbObjectId::Grant { id: self.id() }
76 }
77
78 fn depends_on(&self) -> &[DbObjectId] {
79 &self.depends_on
80 }
81}
82
83pub async fn fetch(conn: &mut PgConnection) -> Result<Vec<Grant>> {
84 let mut grants = Vec::new();
85
86 info!("Fetching table grants...");
88 grants.extend(fetch_table_privileges(&mut *conn).await?);
89
90 info!("Fetching view grants...");
92 grants.extend(fetch_view_privileges(&mut *conn).await?);
93
94 info!("Fetching column grants...");
96 grants.extend(fetch_column_privileges(&mut *conn).await?);
97
98 info!("Fetching schema grants...");
100 grants.extend(fetch_schema_privileges(&mut *conn).await?);
101
102 info!("Fetching function grants...");
104 grants.extend(fetch_function_privileges(&mut *conn).await?);
105
106 info!("Fetching sequence grants...");
108 grants.extend(fetch_sequence_privileges(&mut *conn).await?);
109
110 info!("Fetching type grants...");
112 grants.extend(fetch_type_privileges(&mut *conn).await?);
113
114 Ok(grants)
115}
116
117async fn fetch_table_privileges(conn: &mut PgConnection) -> Result<Vec<Grant>> {
118 let rows = sqlx::query!(
119 r#"
120 SELECT
121 n.nspname as "schema_name!",
122 c.relname as "table_name!",
123 CASE
124 WHEN acl.grantee = 0 THEN 'PUBLIC'
125 ELSE r.rolname
126 END as "grantee!",
127 acl.privilege_type as "privilege_type!",
128 CASE WHEN acl.is_grantable THEN 'YES' ELSE 'NO' END as "is_grantable!",
129 owner_role.rolname as "object_owner!",
130 CASE WHEN c.relacl IS NULL THEN true ELSE false END as "is_default_acl!"
131 FROM pg_class c
132 JOIN pg_namespace n ON c.relnamespace = n.oid
133 JOIN pg_roles owner_role ON c.relowner = owner_role.oid,
134 LATERAL aclexplode(COALESCE(c.relacl, acldefault('r', c.relowner))) AS acl
135 LEFT JOIN pg_roles r ON r.oid = acl.grantee
136 WHERE n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
137 AND c.relkind = 'r' -- tables only (views handled separately)
138 -- Exclude tables that belong to extensions
139 AND NOT EXISTS (
140 SELECT 1 FROM pg_depend dep
141 WHERE dep.objid = c.oid
142 AND dep.deptype = 'e'
143 )
144 ORDER BY n.nspname, c.relname, CASE WHEN acl.grantee = 0 THEN 'PUBLIC' ELSE r.rolname END, acl.privilege_type
145 "#
146 )
147 .fetch_all(&mut *conn)
148 .await?;
149
150 let mut result = Vec::new();
151 let mut current_grant: Option<Grant> = None;
152
153 for row in rows {
154 let grantee = if row.grantee == "PUBLIC" {
155 GranteeType::Public
156 } else {
157 GranteeType::Role(row.grantee.clone())
158 };
159
160 let target = AttrTarget::object(DbObjectId::Table {
161 schema: row.schema_name.clone(),
162 name: row.table_name.clone(),
163 });
164
165 let with_grant_option = row.is_grantable == "YES";
166
167 match &mut current_grant {
169 Some(grant)
170 if grant.grantee == grantee
171 && grant.target == target
172 && grant.with_grant_option == with_grant_option =>
173 {
174 grant.privileges.push(row.privilege_type);
175 }
176 _ => {
177 if let Some(grant) = current_grant.take() {
178 result.push(grant);
179 }
180
181 let depends_on = vec![target.db_object_id()];
184
185 current_grant = Some(Grant {
186 grantee,
187 target,
188 privileges: vec![row.privilege_type],
189 with_grant_option,
190 depends_on,
191 object_owner: row.object_owner.clone(),
192 is_default_acl: row.is_default_acl,
193 });
194 }
195 }
196 }
197
198 if let Some(grant) = current_grant {
199 result.push(grant);
200 }
201
202 Ok(result)
203}
204
205async fn fetch_view_privileges(conn: &mut PgConnection) -> Result<Vec<Grant>> {
206 let rows = sqlx::query!(
207 r#"
208 SELECT
209 n.nspname as "schema_name!",
210 c.relname as "view_name!",
211 CASE
212 WHEN acl.grantee = 0 THEN 'PUBLIC'
213 ELSE r.rolname
214 END as "grantee!",
215 acl.privilege_type as "privilege_type!",
216 CASE WHEN acl.is_grantable THEN 'YES' ELSE 'NO' END as "is_grantable!",
217 owner_role.rolname as "object_owner!",
218 CASE WHEN c.relacl IS NULL THEN true ELSE false END as "is_default_acl!"
219 FROM pg_class c
220 JOIN pg_namespace n ON c.relnamespace = n.oid
221 JOIN pg_roles owner_role ON c.relowner = owner_role.oid,
222 LATERAL aclexplode(COALESCE(c.relacl, acldefault('r', c.relowner))) AS acl
223 LEFT JOIN pg_roles r ON r.oid = acl.grantee
224 WHERE n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
225 AND c.relkind IN ('v', 'm') -- views and materialized views
226 -- Exclude views that belong to extensions
227 AND NOT EXISTS (
228 SELECT 1 FROM pg_depend dep
229 WHERE dep.objid = c.oid
230 AND dep.deptype = 'e'
231 )
232 ORDER BY n.nspname, c.relname, CASE WHEN acl.grantee = 0 THEN 'PUBLIC' ELSE r.rolname END, acl.privilege_type
233 "#
234 )
235 .fetch_all(&mut *conn)
236 .await?;
237
238 let mut result = Vec::new();
239 let mut current_grant: Option<Grant> = None;
240
241 for row in rows {
242 let grantee = if row.grantee == "PUBLIC" {
243 GranteeType::Public
244 } else {
245 GranteeType::Role(row.grantee.clone())
246 };
247
248 let target = AttrTarget::object(DbObjectId::View {
249 schema: row.schema_name.clone(),
250 name: row.view_name.clone(),
251 });
252
253 let with_grant_option = row.is_grantable == "YES";
254
255 match &mut current_grant {
257 Some(grant)
258 if grant.grantee == grantee
259 && grant.target == target
260 && grant.with_grant_option == with_grant_option =>
261 {
262 grant.privileges.push(row.privilege_type);
263 }
264 _ => {
265 if let Some(grant) = current_grant.take() {
266 result.push(grant);
267 }
268
269 let depends_on = vec![target.db_object_id()];
272
273 current_grant = Some(Grant {
274 grantee,
275 target,
276 privileges: vec![row.privilege_type],
277 with_grant_option,
278 depends_on,
279 object_owner: row.object_owner.clone(),
280 is_default_acl: row.is_default_acl,
281 });
282 }
283 }
284 }
285
286 if let Some(grant) = current_grant {
287 result.push(grant);
288 }
289
290 Ok(result)
291}
292
293async fn fetch_column_privileges(conn: &mut PgConnection) -> Result<Vec<Grant>> {
294 let rows = sqlx::query!(
300 r#"
301 SELECT
302 n.nspname as "schema_name!",
303 c.relname as "table_name!",
304 a.attname as "column_name!",
305 c.relkind::text as "relkind!",
306 CASE
307 WHEN acl.grantee = 0 THEN 'PUBLIC'
308 ELSE r.rolname
309 END as "grantee!",
310 acl.privilege_type as "privilege_type!",
311 CASE WHEN acl.is_grantable THEN 'YES' ELSE 'NO' END as "is_grantable!",
312 owner_role.rolname as "object_owner!"
313 FROM pg_class c
314 JOIN pg_namespace n ON c.relnamespace = n.oid
315 JOIN pg_roles owner_role ON c.relowner = owner_role.oid
316 JOIN pg_attribute a ON a.attrelid = c.oid
317 CROSS JOIN LATERAL aclexplode(a.attacl) AS acl
318 LEFT JOIN pg_roles r ON r.oid = acl.grantee
319 WHERE n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
320 AND c.relkind IN ('r', 'v', 'm', 'p') -- tables, views, matviews, partitioned tables
321 AND a.attnum > 0
322 AND NOT a.attisdropped
323 AND a.attacl IS NOT NULL
324 -- Exclude relations that belong to extensions
325 AND NOT EXISTS (
326 SELECT 1 FROM pg_depend dep
327 WHERE dep.objid = c.oid
328 AND dep.deptype = 'e'
329 )
330 ORDER BY n.nspname, c.relname, a.attname,
331 CASE WHEN acl.grantee = 0 THEN 'PUBLIC' ELSE r.rolname END,
332 acl.privilege_type
333 "#
334 )
335 .fetch_all(&mut *conn)
336 .await?;
337
338 let mut result = Vec::new();
339 let mut current_grant: Option<Grant> = None;
340
341 for row in rows {
342 let grantee = if row.grantee == "PUBLIC" {
343 GranteeType::Public
344 } else {
345 GranteeType::Role(row.grantee.clone())
346 };
347
348 let parent = if row.relkind == "v" || row.relkind == "m" {
351 DbObjectId::View {
352 schema: row.schema_name.clone(),
353 name: row.table_name.clone(),
354 }
355 } else {
356 DbObjectId::Table {
357 schema: row.schema_name.clone(),
358 name: row.table_name.clone(),
359 }
360 };
361
362 let target = AttrTarget::column(parent.clone(), row.column_name.clone());
363
364 let with_grant_option = row.is_grantable == "YES";
365
366 match &mut current_grant {
367 Some(grant)
368 if grant.grantee == grantee
369 && grant.target == target
370 && grant.with_grant_option == with_grant_option =>
371 {
372 grant.privileges.push(row.privilege_type);
373 }
374 _ => {
375 if let Some(grant) = current_grant.take() {
376 result.push(grant);
377 }
378
379 current_grant = Some(Grant {
380 grantee,
381 target,
382 privileges: vec![row.privilege_type],
383 with_grant_option,
384 depends_on: vec![parent],
385 object_owner: row.object_owner.clone(),
386 is_default_acl: false,
389 });
390 }
391 }
392 }
393
394 if let Some(grant) = current_grant {
395 result.push(grant);
396 }
397
398 Ok(result)
399}
400
401async fn fetch_schema_privileges(conn: &mut PgConnection) -> Result<Vec<Grant>> {
402 let rows = sqlx::query!(
403 r#"
404 SELECT
405 n.nspname as "schema_name!",
406 CASE
407 WHEN acl.grantee = 0 THEN 'PUBLIC'
408 ELSE r.rolname
409 END as "grantee!",
410 acl.privilege_type as "privilege_type!",
411 CASE WHEN acl.is_grantable THEN 'YES' ELSE 'NO' END as "is_grantable!",
412 owner_role.rolname as "object_owner!",
413 CASE WHEN n.nspacl IS NULL THEN true ELSE false END as "is_default_acl!"
414 FROM pg_namespace n
415 JOIN pg_roles owner_role ON n.nspowner = owner_role.oid,
416 LATERAL aclexplode(COALESCE(n.nspacl, acldefault('n', n.nspowner))) AS acl
417 LEFT JOIN pg_roles r ON r.oid = acl.grantee
418 WHERE n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast', 'public')
419 AND NOT n.nspname LIKE 'pg_temp_%'
420 AND NOT n.nspname LIKE 'pg_toast_temp_%'
421 ORDER BY n.nspname, CASE WHEN acl.grantee = 0 THEN 'PUBLIC' ELSE r.rolname END, acl.privilege_type
422 "#
423 )
424 .fetch_all(&mut *conn)
425 .await?;
426
427 let mut result = Vec::new();
428 let mut current_grant: Option<Grant> = None;
429
430 for row in rows {
431 let grantee = if row.grantee == "PUBLIC" {
432 GranteeType::Public
433 } else {
434 GranteeType::Role(row.grantee.clone())
435 };
436
437 let target = AttrTarget::object(DbObjectId::Schema {
438 name: row.schema_name.clone(),
439 });
440
441 let with_grant_option = row.is_grantable == "YES";
442
443 match &mut current_grant {
444 Some(grant)
445 if grant.grantee == grantee
446 && grant.target == target
447 && grant.with_grant_option == with_grant_option =>
448 {
449 grant.privileges.push(row.privilege_type);
450 }
451 _ => {
452 if let Some(grant) = current_grant.take() {
453 result.push(grant);
454 }
455
456 let depends_on = vec![target.db_object_id()];
459
460 current_grant = Some(Grant {
461 grantee,
462 target,
463 privileges: vec![row.privilege_type],
464 with_grant_option,
465 depends_on,
466 object_owner: row.object_owner.clone(),
467 is_default_acl: row.is_default_acl,
468 });
469 }
470 }
471 }
472
473 if let Some(grant) = current_grant {
474 result.push(grant);
475 }
476
477 Ok(result)
478}
479
480async fn fetch_function_privileges(conn: &mut PgConnection) -> Result<Vec<Grant>> {
481 let rows = sqlx::query!(
482 r#"
483 SELECT
484 n.nspname as "schema_name!",
485 p.proname as "function_name!",
486 p.prokind::text as "prokind!",
487 pg_get_function_identity_arguments(p.oid) as "arguments!",
488 CASE
489 WHEN acl.grantee = 0 THEN 'PUBLIC'
490 ELSE r.rolname
491 END as "grantee!",
492 acl.privilege_type as "privilege_type!",
493 CASE WHEN acl.is_grantable THEN 'YES' ELSE 'NO' END as "is_grantable!",
494 owner_role.rolname as "object_owner!",
495 CASE WHEN p.proacl IS NULL THEN true ELSE false END as "is_default_acl!"
496 FROM pg_proc p
497 JOIN pg_namespace n ON p.pronamespace = n.oid
498 JOIN pg_roles owner_role ON p.proowner = owner_role.oid,
499 LATERAL aclexplode(COALESCE(p.proacl, acldefault('f', p.proowner))) AS acl
500 LEFT JOIN pg_roles r ON r.oid = acl.grantee
501 WHERE n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
502 -- Exclude functions that belong to extensions
503 AND NOT EXISTS (
504 SELECT 1 FROM pg_depend dep
505 WHERE dep.objid = p.oid
506 AND dep.deptype = 'e'
507 )
508 ORDER BY n.nspname, p.proname, pg_get_function_identity_arguments(p.oid), CASE WHEN acl.grantee = 0 THEN 'PUBLIC' ELSE r.rolname END, acl.privilege_type
509 "#
510 )
511 .fetch_all(&mut *conn)
512 .await?;
513
514 let mut result = Vec::new();
515 let mut current_grant: Option<Grant> = None;
516
517 for row in rows {
518 let grantee = if row.grantee == "PUBLIC" {
519 GranteeType::Public
520 } else {
521 GranteeType::Role(row.grantee.clone())
522 };
523
524 let target = AttrTarget::object(match row.prokind.as_str() {
527 "a" => DbObjectId::Aggregate {
528 schema: row.schema_name.clone(),
529 name: row.function_name.clone(),
530 arguments: row.arguments.clone(),
531 },
532 "p" => DbObjectId::Procedure {
533 schema: row.schema_name.clone(),
534 name: row.function_name.clone(),
535 arguments: row.arguments.clone(),
536 },
537 _ => DbObjectId::Function {
538 schema: row.schema_name.clone(),
539 name: row.function_name.clone(),
540 arguments: row.arguments.clone(),
541 },
542 });
543
544 let with_grant_option = row.is_grantable == "YES";
545
546 match &mut current_grant {
547 Some(grant)
548 if grant.grantee == grantee
549 && grant.target == target
550 && grant.with_grant_option == with_grant_option =>
551 {
552 grant.privileges.push(row.privilege_type);
553 }
554 _ => {
555 if let Some(grant) = current_grant.take() {
556 result.push(grant);
557 }
558
559 let depends_on = vec![target.db_object_id()];
562
563 current_grant = Some(Grant {
564 grantee,
565 target,
566 privileges: vec![row.privilege_type],
567 with_grant_option,
568 depends_on,
569 object_owner: row.object_owner.clone(),
570 is_default_acl: row.is_default_acl,
571 });
572 }
573 }
574 }
575
576 if let Some(grant) = current_grant {
577 result.push(grant);
578 }
579
580 Ok(result)
581}
582
583async fn fetch_sequence_privileges(conn: &mut PgConnection) -> Result<Vec<Grant>> {
584 let rows = sqlx::query!(
585 r#"
586 SELECT
587 n.nspname as "schema_name!",
588 c.relname as "sequence_name!",
589 CASE
590 WHEN acl.grantee = 0 THEN 'PUBLIC'
591 ELSE r.rolname
592 END as "grantee!",
593 acl.privilege_type as "privilege_type!",
594 CASE WHEN acl.is_grantable THEN 'YES' ELSE 'NO' END as "is_grantable!",
595 CASE WHEN c.relacl IS NULL THEN true ELSE false END as "is_default_acl!",
596 owner_role.rolname as "object_owner!"
597 FROM pg_class c
598 JOIN pg_namespace n ON c.relnamespace = n.oid
599 JOIN pg_roles owner_role ON c.relowner = owner_role.oid,
600 LATERAL aclexplode(COALESCE(c.relacl, acldefault('S', c.relowner))) AS acl
601 LEFT JOIN pg_roles r ON r.oid = acl.grantee
602 WHERE n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
603 AND c.relkind = 'S' -- sequences only
604 -- Exclude sequences that belong to extensions
605 AND NOT EXISTS (
606 SELECT 1 FROM pg_depend dep
607 WHERE dep.objid = c.oid
608 AND dep.deptype = 'e'
609 )
610 ORDER BY n.nspname, c.relname, CASE WHEN acl.grantee = 0 THEN 'PUBLIC' ELSE r.rolname END, acl.privilege_type
611 "#
612 )
613 .fetch_all(&mut *conn)
614 .await?;
615
616 let mut result = Vec::new();
617 let mut current_grant: Option<Grant> = None;
618
619 for row in rows {
620 let grantee = if row.grantee == "PUBLIC" {
621 GranteeType::Public
622 } else {
623 GranteeType::Role(row.grantee.clone())
624 };
625
626 let target = AttrTarget::object(DbObjectId::Sequence {
627 schema: row.schema_name.clone(),
628 name: row.sequence_name.clone(),
629 });
630
631 let with_grant_option = row.is_grantable == "YES";
632
633 match &mut current_grant {
634 Some(grant)
635 if grant.grantee == grantee
636 && grant.target == target
637 && grant.with_grant_option == with_grant_option =>
638 {
639 grant.privileges.push(row.privilege_type);
640 }
641 _ => {
642 if let Some(grant) = current_grant.take() {
643 result.push(grant);
644 }
645
646 let depends_on = vec![target.db_object_id()];
649
650 current_grant = Some(Grant {
651 grantee,
652 target,
653 privileges: vec![row.privilege_type],
654 with_grant_option,
655 depends_on,
656 object_owner: row.object_owner.clone(),
657 is_default_acl: row.is_default_acl,
658 });
659 }
660 }
661 }
662
663 if let Some(grant) = current_grant {
664 result.push(grant);
665 }
666
667 Ok(result)
668}
669
670async fn fetch_type_privileges(conn: &mut PgConnection) -> Result<Vec<Grant>> {
671 let rows = sqlx::query!(
672 r#"
673 SELECT
674 n.nspname as "schema_name!",
675 t.typname as "type_name!",
676 t.typtype as "type_kind!",
677 CASE
678 WHEN acl.grantee = 0 THEN 'PUBLIC'
679 ELSE r.rolname
680 END as "grantee!",
681 acl.privilege_type as "privilege_type!",
682 CASE WHEN acl.is_grantable THEN 'YES' ELSE 'NO' END as "is_grantable!",
683 owner_role.rolname as "object_owner!",
684 CASE WHEN t.typacl IS NULL THEN true ELSE false END as "is_default_acl!"
685 FROM pg_type t
686 JOIN pg_namespace n ON t.typnamespace = n.oid
687 JOIN pg_roles owner_role ON t.typowner = owner_role.oid,
688 LATERAL aclexplode(COALESCE(t.typacl, acldefault('T', t.typowner))) AS acl
689 LEFT JOIN pg_roles r ON r.oid = acl.grantee
690 WHERE n.nspname NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
691 AND t.typtype IN ('e', 'd', 'c') -- Only enums, domains, and composite types
692 AND NOT EXISTS (
693 -- Exclude composite types that are automatically created for tables
694 SELECT 1 FROM pg_class c
695 WHERE c.relname = t.typname
696 AND c.relnamespace = t.typnamespace
697 AND c.relkind IN ('r', 'v', 'm', 'S')
698 )
699 AND NOT t.typname LIKE '\_%' -- Exclude array types (they start with underscore)
700 -- Exclude types that belong to extensions
701 AND NOT EXISTS (
702 SELECT 1 FROM pg_depend dep
703 WHERE dep.objid = t.oid
704 AND dep.deptype = 'e'
705 )
706 ORDER BY n.nspname, t.typname, CASE WHEN acl.grantee = 0 THEN 'PUBLIC' ELSE r.rolname END, acl.privilege_type
707 "#
708 )
709 .fetch_all(&mut *conn)
710 .await?;
711
712 let mut result = Vec::new();
713 let mut current_grant: Option<Grant> = None;
714
715 for row in rows {
716 let grantee = if row.grantee == "PUBLIC" {
717 GranteeType::Public
718 } else {
719 GranteeType::Role(row.grantee.clone())
720 };
721
722 let target = AttrTarget::object(if row.type_kind == b'd' as i8 {
724 DbObjectId::Domain {
725 schema: row.schema_name.clone(),
726 name: row.type_name.clone(),
727 }
728 } else {
729 DbObjectId::Type {
730 schema: row.schema_name.clone(),
731 name: row.type_name.clone(),
732 }
733 });
734
735 let with_grant_option = row.is_grantable == "YES";
736
737 match &mut current_grant {
738 Some(grant)
739 if grant.grantee == grantee
740 && grant.target == target
741 && grant.with_grant_option == with_grant_option =>
742 {
743 grant.privileges.push(row.privilege_type);
744 }
745 _ => {
746 if let Some(grant) = current_grant.take() {
747 result.push(grant);
748 }
749
750 let depends_on = vec![target.db_object_id()];
753
754 current_grant = Some(Grant {
755 grantee,
756 target,
757 privileges: vec![row.privilege_type],
758 with_grant_option,
759 depends_on,
760 object_owner: row.object_owner.clone(),
761 is_default_acl: row.is_default_acl,
762 });
763 }
764 }
765 }
766
767 if let Some(grant) = current_grant {
768 result.push(grant);
769 }
770
771 Ok(result)
772}