Skip to main content

mz_repr/adt/
mz_acl_item.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! A Materialize specific access control list abstract data type.
11
12use std::collections::BTreeMap;
13use std::fmt;
14use std::mem::size_of;
15use std::ops::BitOrAssign;
16use std::str::FromStr;
17
18use crate::adt::system::Oid;
19use anyhow::{Error, anyhow};
20use bitflags::bitflags;
21use mz_ore::soft_assert_no_log;
22use mz_ore::str::StrExt;
23use mz_persist_types::columnar::FixedSizeCodec;
24use mz_proto::{RustType, TryFromProtoError};
25#[cfg(any(test, feature = "proptest"))]
26use proptest::arbitrary::Arbitrary;
27#[cfg(any(test, feature = "proptest"))]
28use proptest::prelude::*;
29#[cfg(any(test, feature = "proptest"))]
30use proptest::strategy::{BoxedStrategy, Strategy};
31#[cfg(any(test, feature = "proptest"))]
32use proptest_derive::Arbitrary;
33use serde::{Deserialize, Serialize};
34
35use crate::role_id::RoleId;
36
37include!(concat!(env!("OUT_DIR"), "/mz_repr.adt.mz_acl_item.rs"));
38
39// Append
40const INSERT_CHAR: char = 'a';
41// Read
42const SELECT_CHAR: char = 'r';
43// Write
44const UPDATE_CHAR: char = 'w';
45// Delete
46const DELETE_CHAR: char = 'd';
47// Usage
48const USAGE_CHAR: char = 'U';
49// Create
50const CREATE_CHAR: char = 'C';
51// Role
52const CREATE_ROLE_CHAR: char = 'R';
53// dataBase
54const CREATE_DB_CHAR: char = 'B';
55// compute Node
56const CREATE_CLUSTER_CHAR: char = 'N';
57// compute network Policy
58const CREATE_NETWORK_POLICY_CHAR: char = 'P';
59
60const INSERT_STR: &str = "INSERT";
61const SELECT_STR: &str = "SELECT";
62const UPDATE_STR: &str = "UPDATE";
63const DELETE_STR: &str = "DELETE";
64const USAGE_STR: &str = "USAGE";
65const CREATE_STR: &str = "CREATE";
66const CREATE_ROLE_STR: &str = "CREATEROLE";
67const CREATE_DB_STR: &str = "CREATEDB";
68const CREATE_CLUSTER_STR: &str = "CREATECLUSTER";
69const CREATE_NETWORK_POLICY_STR: &str = "CREATENETWORKPOLICY";
70
71/// The OID used to represent the PUBLIC role. See:
72/// <https://github.com/postgres/postgres/blob/29a0ccbce97978e5d65b8f96c85a00611bb403c4/src/include/utils/acl.h#L46>
73pub const PUBLIC_ROLE_OID: Oid = Oid(0);
74
75/// A bit flag representing all the privileges that can be granted to a role.
76///
77/// Modeled after:
78/// <https://github.com/postgres/postgres/blob/7f5b19817eaf38e70ad1153db4e644ee9456853e/src/include/nodes/parsenodes.h#L74-L101>
79///
80/// The lower 32 bits are used for different privilege types.
81///
82/// The upper 32 bits indicate a grant option on the privilege for the current bit shifted
83/// right by 32 bits (Currently unimplemented in Materialize).
84///
85/// Privileges that exist in Materialize but not PostgreSQL start at the highest available bit
86/// and move down towards the PostgreSQL compatible bits. This is try to avoid collisions with
87/// privileges that PostgreSQL may add in the future.
88#[derive(
89    Clone,
90    Copy,
91    PartialEq,
92    Eq,
93    PartialOrd,
94    Ord,
95    Hash,
96    Serialize,
97    Deserialize
98)]
99#[serde(from = "AclModeSerde", into = "AclModeSerde")]
100pub struct AclMode(u64);
101
102bitflags! {
103    impl AclMode: u64 {
104        // PostgreSQL compatible privileges.
105        const INSERT = 1 << 0;
106        const SELECT = 1 << 1;
107        const UPDATE = 1 << 2;
108        const DELETE = 1 << 3;
109        const USAGE = 1 << 8;
110        const CREATE = 1 << 9;
111
112        // Materialize custom privileges.
113        const CREATE_CLUSTER = 1 << 29;
114        const CREATE_DB = 1 << 30;
115        const CREATE_ROLE = 1 << 31;
116        const CREATE_NETWORK_POLICY = 1 << 32;
117
118        // No additional privileges should be defined at a bit larger than 1 << 31. Those bits are
119        // reserved for grant options.
120    }
121}
122
123/// The serde representation of [`AclMode`], `{"bits": <u64>}`, as derived by bitflags 1.
124#[derive(Serialize, Deserialize)]
125struct AclModeSerde {
126    bits: u64,
127}
128
129impl From<AclModeSerde> for AclMode {
130    fn from(AclModeSerde { bits }: AclModeSerde) -> Self {
131        AclMode::from_bits_retain(bits)
132    }
133}
134
135impl From<AclMode> for AclModeSerde {
136    fn from(acl_mode: AclMode) -> Self {
137        AclModeSerde {
138            bits: acl_mode.bits(),
139        }
140    }
141}
142
143// Matches the `Debug` output that bitflags 1 derived, for example `INSERT | SELECT`.
144impl fmt::Debug for AclMode {
145    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
146        if self.is_empty() {
147            f.write_str("(empty)")
148        } else {
149            bitflags::parser::to_writer(self, f)
150        }
151    }
152}
153
154impl AclMode {
155    pub fn parse_single_privilege(s: &str) -> Result<Self, Error> {
156        match s.trim().to_uppercase().as_str() {
157            INSERT_STR => Ok(AclMode::INSERT),
158            SELECT_STR => Ok(AclMode::SELECT),
159            UPDATE_STR => Ok(AclMode::UPDATE),
160            DELETE_STR => Ok(AclMode::DELETE),
161            USAGE_STR => Ok(AclMode::USAGE),
162            CREATE_STR => Ok(AclMode::CREATE),
163            CREATE_ROLE_STR => Ok(AclMode::CREATE_ROLE),
164            CREATE_DB_STR => Ok(AclMode::CREATE_DB),
165            CREATE_CLUSTER_STR => Ok(AclMode::CREATE_CLUSTER),
166            CREATE_NETWORK_POLICY_STR => Ok(AclMode::CREATE_NETWORK_POLICY),
167            _ => Err(anyhow!("{}", s.quoted())),
168        }
169    }
170
171    pub fn parse_multiple_privileges(s: &str) -> Result<Self, Error> {
172        let mut acl_mode = AclMode::empty();
173        for privilege in s.split(',') {
174            let privilege = AclMode::parse_single_privilege(privilege)?;
175            acl_mode.bitor_assign(privilege);
176        }
177        Ok(acl_mode)
178    }
179
180    pub fn to_error_string(&self) -> String {
181        self.explode().join(", ")
182    }
183
184    pub fn explode(&self) -> Vec<&'static str> {
185        let mut privileges = Vec::new();
186        if self.contains(AclMode::SELECT) {
187            privileges.push(SELECT_STR);
188        }
189        if self.contains(AclMode::INSERT) {
190            privileges.push(INSERT_STR);
191        }
192        if self.contains(AclMode::UPDATE) {
193            privileges.push(UPDATE_STR);
194        }
195        if self.contains(AclMode::DELETE) {
196            privileges.push(DELETE_STR);
197        }
198        if self.contains(AclMode::USAGE) {
199            privileges.push(USAGE_STR);
200        }
201        if self.contains(AclMode::CREATE) {
202            privileges.push(CREATE_STR);
203        }
204        if self.contains(AclMode::CREATE_ROLE) {
205            privileges.push(CREATE_ROLE_STR);
206        }
207        if self.contains(AclMode::CREATE_DB) {
208            privileges.push(CREATE_DB_STR);
209        }
210        if self.contains(AclMode::CREATE_CLUSTER) {
211            privileges.push(CREATE_CLUSTER_STR);
212        }
213        if self.contains(AclMode::CREATE_NETWORK_POLICY) {
214            privileges.push(CREATE_NETWORK_POLICY_STR);
215        }
216        privileges
217    }
218}
219
220impl FromStr for AclMode {
221    type Err = Error;
222
223    fn from_str(s: &str) -> Result<Self, Self::Err> {
224        let mut acl_mode = AclMode::empty();
225        for c in s.chars() {
226            match c {
227                INSERT_CHAR => acl_mode.bitor_assign(AclMode::INSERT),
228                SELECT_CHAR => acl_mode.bitor_assign(AclMode::SELECT),
229                UPDATE_CHAR => acl_mode.bitor_assign(AclMode::UPDATE),
230                DELETE_CHAR => acl_mode.bitor_assign(AclMode::DELETE),
231                USAGE_CHAR => acl_mode.bitor_assign(AclMode::USAGE),
232                CREATE_CHAR => acl_mode.bitor_assign(AclMode::CREATE),
233                CREATE_ROLE_CHAR => acl_mode.bitor_assign(AclMode::CREATE_ROLE),
234                CREATE_DB_CHAR => acl_mode.bitor_assign(AclMode::CREATE_DB),
235                CREATE_CLUSTER_CHAR => acl_mode.bitor_assign(AclMode::CREATE_CLUSTER),
236                CREATE_NETWORK_POLICY_CHAR => acl_mode.bitor_assign(AclMode::CREATE_NETWORK_POLICY),
237                _ => return Err(anyhow!("invalid privilege '{c}' in acl mode '{s}'")),
238            }
239        }
240        Ok(acl_mode)
241    }
242}
243
244impl fmt::Display for AclMode {
245    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
246        // The order of each flag matches PostgreSQL, which uses the same order that they're
247        // defined in.
248        if self.contains(AclMode::INSERT) {
249            write!(f, "{INSERT_CHAR}")?;
250        }
251        if self.contains(AclMode::SELECT) {
252            write!(f, "{SELECT_CHAR}")?;
253        }
254        if self.contains(AclMode::UPDATE) {
255            write!(f, "{UPDATE_CHAR}")?;
256        }
257        if self.contains(AclMode::DELETE) {
258            write!(f, "{DELETE_CHAR}")?;
259        }
260        if self.contains(AclMode::USAGE) {
261            write!(f, "{USAGE_CHAR}")?;
262        }
263        if self.contains(AclMode::CREATE) {
264            write!(f, "{CREATE_CHAR}")?;
265        }
266        if self.contains(AclMode::CREATE_ROLE) {
267            write!(f, "{CREATE_ROLE_CHAR}")?;
268        }
269        if self.contains(AclMode::CREATE_DB) {
270            write!(f, "{CREATE_DB_CHAR}")?;
271        }
272        if self.contains(AclMode::CREATE_CLUSTER) {
273            write!(f, "{CREATE_CLUSTER_CHAR}")?;
274        }
275        if self.contains(AclMode::CREATE_NETWORK_POLICY) {
276            write!(f, "{CREATE_NETWORK_POLICY_CHAR}")?;
277        }
278        Ok(())
279    }
280}
281
282impl RustType<ProtoAclMode> for AclMode {
283    fn into_proto(&self) -> ProtoAclMode {
284        ProtoAclMode {
285            acl_mode: self.bits(),
286        }
287    }
288
289    fn from_proto(proto: ProtoAclMode) -> Result<Self, TryFromProtoError> {
290        Ok(AclMode::from_bits_retain(proto.acl_mode))
291    }
292}
293
294#[cfg(any(test, feature = "proptest"))]
295impl Arbitrary for AclMode {
296    type Parameters = ();
297    type Strategy = BoxedStrategy<AclMode>;
298
299    fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
300        proptest::bits::BitSetStrategy::masked(AclMode::all().bits())
301            .prop_map(|bits| AclMode::from_bits(bits).expect("invalid proptest implementation"))
302            .boxed()
303    }
304}
305
306/// A list of privileges granted to a role in Materialize.
307///
308/// This is modelled after the AclItem type in PostgreSQL, but the OIDs are replaced with RoleIds
309/// because we don't use OID as persistent identifiers for roles.
310///
311/// See: <https://github.com/postgres/postgres/blob/7f5b19817eaf38e70ad1153db4e644ee9456853e/src/include/utils/acl.h#L48-L59>
312#[derive(
313    Debug,
314    Clone,
315    Copy,
316    PartialEq,
317    Eq,
318    PartialOrd,
319    Ord,
320    Serialize,
321    Hash,
322    Deserialize
323)]
324#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))]
325pub struct MzAclItem {
326    /// Role that this item grants privileges to.
327    pub grantee: RoleId,
328    /// Grantor of privileges.
329    pub grantor: RoleId,
330    /// Privileges bit flag.
331    pub acl_mode: AclMode,
332}
333
334impl MzAclItem {
335    pub fn empty(grantee: RoleId, grantor: RoleId) -> MzAclItem {
336        MzAclItem {
337            grantee,
338            grantor,
339            acl_mode: AclMode::empty(),
340        }
341    }
342
343    pub fn encode_binary(&self) -> Vec<u8> {
344        let mut res = Vec::with_capacity(Self::binary_size());
345        res.extend_from_slice(&self.grantee.encode_binary());
346        res.extend_from_slice(&self.grantor.encode_binary());
347        res.extend_from_slice(&self.acl_mode.bits().to_le_bytes());
348        res
349    }
350
351    pub fn decode_binary(raw: &[u8]) -> Result<MzAclItem, Error> {
352        if raw.len() != MzAclItem::binary_size() {
353            return Err(anyhow!(
354                "invalid binary size, expecting {}, found {}",
355                MzAclItem::binary_size(),
356                raw.len()
357            ));
358        }
359
360        let role_id_size = RoleId::binary_size();
361
362        let grantee = RoleId::decode_binary(&raw[0..role_id_size])?;
363        let raw = &raw[role_id_size..];
364        let grantor = RoleId::decode_binary(&raw[0..role_id_size])?;
365        let raw = &raw[role_id_size..];
366        let acl_mode = u64::from_le_bytes(raw.try_into()?);
367
368        Ok(MzAclItem {
369            grantee,
370            grantor,
371            acl_mode: AclMode::from_bits_retain(acl_mode),
372        })
373    }
374
375    pub const fn binary_size() -> usize {
376        RoleId::binary_size() + RoleId::binary_size() + size_of::<u64>()
377    }
378}
379
380impl FromStr for MzAclItem {
381    type Err = Error;
382
383    fn from_str(s: &str) -> Result<Self, Self::Err> {
384        let parts: Vec<_> = s.split('=').collect();
385        let &[grantee, rest] = parts.as_slice() else {
386            return Err(anyhow!("invalid mz_aclitem '{s}'"));
387        };
388
389        let parts: Vec<_> = rest.split('/').collect();
390        let &[acl_mode, grantor] = parts.as_slice() else {
391            return Err(anyhow!("invalid mz_aclitem '{s}'"));
392        };
393
394        let grantee: RoleId = if grantee.is_empty() {
395            RoleId::Public
396        } else {
397            grantee.parse()?
398        };
399        let acl_mode: AclMode = acl_mode.parse()?;
400        let grantor: RoleId = grantor.parse()?;
401
402        Ok(MzAclItem {
403            grantee,
404            grantor,
405            acl_mode,
406        })
407    }
408}
409
410impl fmt::Display for MzAclItem {
411    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
412        if !self.grantee.is_public() {
413            write!(f, "{}", self.grantee)?;
414        }
415        write!(f, "={}/{}", self.acl_mode, self.grantor)
416    }
417}
418
419impl RustType<ProtoMzAclItem> for MzAclItem {
420    fn into_proto(&self) -> ProtoMzAclItem {
421        ProtoMzAclItem {
422            grantee: Some(self.grantee.into_proto()),
423            grantor: Some(self.grantor.into_proto()),
424            acl_mode: Some(self.acl_mode.into_proto()),
425        }
426    }
427
428    fn from_proto(proto: ProtoMzAclItem) -> Result<Self, TryFromProtoError> {
429        match (proto.grantee, proto.grantor, proto.acl_mode) {
430            (Some(grantee), Some(grantor), Some(acl_mode)) => Ok(MzAclItem {
431                grantee: RoleId::from_proto(grantee)?,
432                grantor: RoleId::from_proto(grantor)?,
433                acl_mode: AclMode::from_proto(acl_mode)?,
434            }),
435            (None, _, _) => Err(TryFromProtoError::missing_field("ProtoMzAclItem::grantee")),
436            (_, None, _) => Err(TryFromProtoError::missing_field("ProtoMzAclItem::grantor")),
437            (_, _, None) => Err(TryFromProtoError::missing_field("ProtoMzAclItem::acl_mode")),
438        }
439    }
440}
441
442/// An encoded packed variant of [`MzAclItem`].
443///
444/// We uphold the variant that [`PackedMzAclItem`] sorts the same as [`MzAclItem`].
445#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
446pub struct PackedMzAclItem([u8; Self::SIZE]);
447
448impl PackedMzAclItem {
449    // Note: It's critical to the sort order guarantee that these tags sort the same as the
450    // variants of RoleId. We leave space between each tag for future variants.
451    //
452    // Note: An alternative would be to use a `u8` for the tag, while this is more memory
453    // efficient micro benchmarks show throughput increases by ~40% for the encode path and ~130%
454    // for the decode path when using a `u32`. Looking at the generated assembly there are more
455    // load and store instructions when using a `u8` and unaligned reads.
456
457    pub const SYSTEM_TAG: u32 = 100;
458    pub const PREDEFINED_TAG: u32 = 200;
459    pub const USER_TAG: u32 = 300;
460    pub const PUBLIC_TAG: u32 = 400;
461
462    #[inline]
463    fn encode_role(buf: &mut [u8], role: RoleId) {
464        soft_assert_no_log!(buf.len() == 12);
465
466        match role {
467            RoleId::System(val) => {
468                buf[..4].copy_from_slice(&Self::SYSTEM_TAG.to_be_bytes());
469                buf[4..].copy_from_slice(&val.to_be_bytes());
470            }
471            RoleId::Predefined(val) => {
472                buf[..4].copy_from_slice(&Self::PREDEFINED_TAG.to_be_bytes());
473                buf[4..].copy_from_slice(&val.to_be_bytes());
474            }
475            RoleId::User(val) => {
476                buf[..4].copy_from_slice(&Self::USER_TAG.to_be_bytes());
477                buf[4..].copy_from_slice(&val.to_be_bytes());
478            }
479            RoleId::Public => {
480                buf[..4].copy_from_slice(&Self::PUBLIC_TAG.to_be_bytes());
481            }
482        }
483    }
484
485    #[inline]
486    fn decode_role(buf: &[u8]) -> RoleId {
487        soft_assert_no_log!(buf.len() == 12);
488
489        let tag: [u8; 4] = buf[..4]
490            .try_into()
491            .expect("PackedMzAclItem should roundtrip");
492        let tag = u32::from_be_bytes(tag);
493
494        let val: [u8; 8] = buf[4..]
495            .try_into()
496            .expect("PackedMzAclItem should roundtrip");
497        let val = u64::from_be_bytes(val);
498
499        match tag {
500            Self::SYSTEM_TAG => RoleId::System(val),
501            Self::PREDEFINED_TAG => RoleId::Predefined(val),
502            Self::USER_TAG => RoleId::User(val),
503            Self::PUBLIC_TAG => RoleId::Public,
504            x => panic!("unrecognized tag {x}"),
505        }
506    }
507}
508
509impl FixedSizeCodec<MzAclItem> for PackedMzAclItem {
510    const SIZE: usize = 32;
511
512    fn as_bytes(&self) -> &[u8] {
513        &self.0
514    }
515
516    fn from_bytes(val: &[u8]) -> Result<Self, String>
517    where
518        Self: Sized,
519    {
520        let buf: [u8; Self::SIZE] = val.try_into().map_err(|_| {
521            format!(
522                "size for PackedMzAclItem is {} bytes, got {}",
523                Self::SIZE,
524                val.len()
525            )
526        })?;
527
528        Ok(PackedMzAclItem(buf))
529    }
530
531    #[inline]
532    fn from_value(value: MzAclItem) -> Self {
533        let mut buf = [0u8; 32];
534
535        Self::encode_role(&mut buf[..12], value.grantee);
536        Self::encode_role(&mut buf[12..24], value.grantor);
537        buf[24..].copy_from_slice(&value.acl_mode.bits().to_be_bytes());
538
539        PackedMzAclItem(buf)
540    }
541
542    #[inline]
543    fn into_value(self) -> MzAclItem {
544        let grantee = PackedMzAclItem::decode_role(&self.0[..12]);
545        let grantor = PackedMzAclItem::decode_role(&self.0[12..24]);
546
547        let acl_mode: [u8; 8] = self.0[24..]
548            .try_into()
549            .expect("PackedMzAclItem should roundtrip");
550        let acl_mode = AclMode::from_bits(u64::from_be_bytes(acl_mode))
551            .expect("PackedMzAclItem should roundtrip");
552
553        MzAclItem {
554            grantee,
555            grantor,
556            acl_mode,
557        }
558    }
559}
560
561/// A list of privileges granted to a role.
562///
563/// This is primarily used for compatibility in PostgreSQL.
564///
565/// See: <https://github.com/postgres/postgres/blob/7f5b19817eaf38e70ad1153db4e644ee9456853e/src/include/utils/acl.h#L48-L59>
566#[derive(
567    Debug,
568    Clone,
569    Copy,
570    PartialEq,
571    Eq,
572    PartialOrd,
573    Ord,
574    Serialize,
575    Hash,
576    Deserialize
577)]
578#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))]
579pub struct AclItem {
580    /// Role that this item grants privileges to.
581    pub grantee: Oid,
582    /// Grantor of privileges.
583    pub grantor: Oid,
584    /// Privileges bit flag.
585    pub acl_mode: AclMode,
586}
587
588impl AclItem {
589    pub fn empty(grantee: Oid, grantor: Oid) -> AclItem {
590        AclItem {
591            grantee,
592            grantor,
593            acl_mode: AclMode::empty(),
594        }
595    }
596}
597
598// PostgreSQL has no stable binary encoding for AclItems. However, we need to be able to convert
599// types to and from binary, to pack a Datum in a row, so we invent our own encoding. The encoding
600// matches the in memory format that PostgreSQL uses, which is (Oid, Oid, AclMode).
601impl AclItem {
602    pub fn encode_binary(&self) -> Vec<u8> {
603        let mut res = Vec::with_capacity(Self::binary_size());
604        res.extend_from_slice(&self.grantee.0.to_le_bytes());
605        res.extend_from_slice(&self.grantor.0.to_le_bytes());
606        res.extend_from_slice(&self.acl_mode.bits().to_le_bytes());
607        res
608    }
609
610    pub fn decode_binary(raw: &[u8]) -> Result<AclItem, Error> {
611        if raw.len() != AclItem::binary_size() {
612            return Err(anyhow!(
613                "invalid binary size, expecting {}, found {}",
614                AclItem::binary_size(),
615                raw.len()
616            ));
617        }
618
619        let oid_size = size_of::<u32>();
620
621        let grantee = Oid(u32::from_le_bytes(raw[0..oid_size].try_into()?));
622        let raw = &raw[oid_size..];
623        let grantor = Oid(u32::from_le_bytes(raw[0..oid_size].try_into()?));
624        let raw = &raw[oid_size..];
625        let acl_mode = u64::from_le_bytes(raw.try_into()?);
626
627        Ok(AclItem {
628            grantee,
629            grantor,
630            acl_mode: AclMode::from_bits_retain(acl_mode),
631        })
632    }
633
634    pub const fn binary_size() -> usize {
635        size_of::<u32>() + size_of::<u32>() + size_of::<u64>()
636    }
637}
638
639impl FromStr for AclItem {
640    type Err = Error;
641
642    /// For the PostgreSQL implementation see:
643    ///   * <https://github.com/postgres/postgres/blob/9089287aa037fdecb5a52cec1926e5ae9569e9f9/src/backend/utils/adt/acl.c#L580-L609>
644    ///   * <https://github.com/postgres/postgres/blob/9089287aa037fdecb5a52cec1926e5ae9569e9f9/src/backend/utils/adt/acl.c#L223-L390>
645    ///   * <https://github.com/postgres/postgres/blob/9089287aa037fdecb5a52cec1926e5ae9569e9f9/src/backend/utils/adt/acl.c#L127-L187>
646    ///
647    /// PostgreSQL is able to look up OIDs in the catalog while parsing, so it accepts aclitems that
648    /// contain role OIDs or role names. We have no way to access the catalog here, so we can only
649    /// accept oids. Therefore our implementation is much simpler than PostgreSQL's.
650    fn from_str(s: &str) -> Result<Self, Self::Err> {
651        let parts: Vec<_> = s.split('=').collect();
652        let &[grantee, rest] = parts.as_slice() else {
653            return Err(anyhow!("invalid aclitem '{s}'"));
654        };
655
656        let parts: Vec<_> = rest.split('/').collect();
657        let &[acl_mode, grantor] = parts.as_slice() else {
658            return Err(anyhow!("invalid mz_aclitem '{s}'"));
659        };
660
661        let grantee: Oid = if grantee.is_empty() {
662            PUBLIC_ROLE_OID
663        } else {
664            Oid(grantee.parse()?)
665        };
666        let acl_mode: AclMode = acl_mode.parse()?;
667        let grantor: Oid = Oid(grantor.parse()?);
668
669        Ok(AclItem {
670            grantee,
671            grantor,
672            acl_mode,
673        })
674    }
675}
676
677impl fmt::Display for AclItem {
678    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
679        if self.grantee != PUBLIC_ROLE_OID {
680            write!(f, "{}", self.grantee.0)?;
681        }
682        write!(f, "={}/{}", self.acl_mode, self.grantor.0)
683    }
684}
685
686impl RustType<ProtoAclItem> for AclItem {
687    fn into_proto(&self) -> ProtoAclItem {
688        ProtoAclItem {
689            grantee: self.grantee.0,
690            grantor: self.grantor.0,
691            acl_mode: Some(self.acl_mode.into_proto()),
692        }
693    }
694
695    fn from_proto(proto: ProtoAclItem) -> Result<Self, TryFromProtoError> {
696        match proto.acl_mode {
697            Some(acl_mode) => Ok(AclItem {
698                grantee: Oid(proto.grantee),
699                grantor: Oid(proto.grantor),
700                acl_mode: AclMode::from_proto(acl_mode)?,
701            }),
702            None => Err(TryFromProtoError::missing_field("ProtoMzAclItem::acl_mode")),
703        }
704    }
705}
706
707/// An encoded packed variant of [`AclItem`].
708///
709/// We uphold the variant that [`PackedAclItem`] sorts the same as [`AclItem`].
710#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
711pub struct PackedAclItem([u8; Self::SIZE]);
712
713impl FixedSizeCodec<AclItem> for PackedAclItem {
714    const SIZE: usize = 16;
715
716    fn as_bytes(&self) -> &[u8] {
717        &self.0
718    }
719
720    fn from_bytes(slice: &[u8]) -> Result<Self, String> {
721        let buf: [u8; Self::SIZE] = slice.try_into().map_err(|_| {
722            format!(
723                "size for PackedAclItem is {} bytes, got {}",
724                Self::SIZE,
725                slice.len()
726            )
727        })?;
728        Ok(PackedAclItem(buf))
729    }
730
731    #[inline]
732    fn from_value(value: AclItem) -> Self {
733        let mut buf = [0u8; 16];
734
735        buf[..4].copy_from_slice(&value.grantee.0.to_be_bytes());
736        buf[4..8].copy_from_slice(&value.grantor.0.to_be_bytes());
737        buf[8..].copy_from_slice(&value.acl_mode.bits().to_be_bytes());
738
739        PackedAclItem(buf)
740    }
741
742    #[inline]
743    fn into_value(self) -> AclItem {
744        let mut grantee = [0; 4];
745        grantee.copy_from_slice(&self.0[..4]);
746
747        let mut grantor = [0; 4];
748        grantor.copy_from_slice(&self.0[4..8]);
749
750        let mut acl_mode = [0; 8];
751        acl_mode.copy_from_slice(&self.0[8..]);
752        let acl_mode = AclMode::from_bits(u64::from_be_bytes(acl_mode))
753            .expect("PackedAclItem should roundtrip");
754
755        AclItem {
756            grantee: Oid(u32::from_be_bytes(grantee)),
757            grantor: Oid(u32::from_be_bytes(grantor)),
758            acl_mode,
759        }
760    }
761}
762
763/// A container of [`MzAclItem`]s that is optimized to look up an [`MzAclItem`] by the grantee.
764#[derive(
765    Debug,
766    Clone,
767    PartialEq,
768    Eq,
769    PartialOrd,
770    Ord,
771    Hash,
772    Deserialize,
773    Serialize
774)]
775pub struct PrivilegeMap(
776    #[serde(serialize_with = "mz_ore::serde::map_key_to_string")] BTreeMap<RoleId, Vec<MzAclItem>>,
777);
778
779impl PrivilegeMap {
780    /// Creates a new empty `PrivilegeMap`.
781    pub fn new() -> PrivilegeMap {
782        PrivilegeMap(BTreeMap::new())
783    }
784
785    /// Creates a new `PrivilegeMap` from a collection of [`MzAclItem`]s.
786    pub fn from_mz_acl_items(items: impl IntoIterator<Item = MzAclItem>) -> PrivilegeMap {
787        let mut map = PrivilegeMap::new();
788        map.grant_all(items);
789        map
790    }
791
792    /// Get the acl item granted to `grantee` by `grantor`.
793    pub fn get_acl_item(&self, grantee: &RoleId, grantor: &RoleId) -> Option<&MzAclItem> {
794        self.0.get(grantee).and_then(|privileges| {
795            privileges
796                .into_iter()
797                .find(|mz_acl_item| &mz_acl_item.grantor == grantor)
798        })
799    }
800
801    /// Get all acl items granted to `grantee`.
802    pub fn get_acl_items_for_grantee(&self, grantee: &RoleId) -> impl Iterator<Item = &MzAclItem> {
803        self.0
804            .get(grantee)
805            .into_iter()
806            .flat_map(|privileges| privileges.into_iter())
807    }
808
809    /// Returns references to all contained [`MzAclItem`].
810    pub fn all_values(&self) -> impl Iterator<Item = &MzAclItem> {
811        self.0
812            .values()
813            .flat_map(|privileges| privileges.into_iter())
814    }
815
816    /// Returns clones of all contained [`MzAclItem`].
817    pub fn all_values_owned(&self) -> impl Iterator<Item = MzAclItem> + '_ {
818        self.all_values().cloned()
819    }
820
821    /// Consumes self and returns all contained [`MzAclItem`].
822    pub fn into_all_values(self) -> impl Iterator<Item = MzAclItem> {
823        self.0
824            .into_values()
825            .flat_map(|privileges| privileges.into_iter())
826    }
827
828    /// Adds an [`MzAclItem`] to this map.
829    pub fn grant(&mut self, privilege: MzAclItem) {
830        let grantee_privileges = self.0.entry(privilege.grantee).or_default();
831        if let Some(existing_privilege) = grantee_privileges
832            .iter_mut()
833            .find(|cur_privilege| cur_privilege.grantor == privilege.grantor)
834        {
835            // sanity check that the key is consistent.
836            assert_eq!(
837                privilege.grantee, existing_privilege.grantee,
838                "PrivilegeMap out of sync"
839            );
840            existing_privilege.acl_mode = existing_privilege.acl_mode.union(privilege.acl_mode);
841        } else {
842            grantee_privileges.push(privilege);
843        }
844    }
845
846    /// Grant multiple [`MzAclItem`]s to this map.
847    pub fn grant_all(&mut self, mz_acl_items: impl IntoIterator<Item = MzAclItem>) {
848        for mz_acl_item in mz_acl_items {
849            self.grant(mz_acl_item);
850        }
851    }
852
853    /// Removes an [`MzAclItem`] from this map.
854    pub fn revoke(&mut self, privilege: &MzAclItem) {
855        let grantee_privileges = self.0.entry(privilege.grantee).or_default();
856        if let Some(existing_privilege) = grantee_privileges
857            .iter_mut()
858            .find(|cur_privilege| cur_privilege.grantor == privilege.grantor)
859        {
860            // sanity check that the key is consistent.
861            assert_eq!(
862                privilege.grantee, existing_privilege.grantee,
863                "PrivilegeMap out of sync"
864            );
865            existing_privilege.acl_mode =
866                existing_privilege.acl_mode.difference(privilege.acl_mode);
867        }
868
869        // Remove empty privileges.
870        grantee_privileges.retain(|privilege| !privilege.acl_mode.is_empty());
871        if grantee_privileges.is_empty() {
872            self.0.remove(&privilege.grantee);
873        }
874    }
875
876    /// Returns a `PrivilegeMap` formatted as a `serde_json::Value` that is suitable for debugging. For
877    /// example `CatalogState::dump`.
878    pub fn debug_json(&self) -> serde_json::Value {
879        let privileges_by_str: BTreeMap<String, _> = self
880            .0
881            .iter()
882            .map(|(key, value)| (key.to_string(), value))
883            .collect();
884        serde_json::json!(privileges_by_str)
885    }
886}
887
888impl Default for PrivilegeMap {
889    fn default() -> PrivilegeMap {
890        PrivilegeMap::new()
891    }
892}
893
894/// Combines all [`MzAclItem`]s that have the same grantee and grantor.
895pub fn merge_mz_acl_items(
896    mz_acl_items: impl Iterator<Item = MzAclItem>,
897) -> impl Iterator<Item = MzAclItem> {
898    mz_acl_items
899        .fold(BTreeMap::new(), |mut accum, mz_acl_item| {
900            let item = accum
901                .entry((mz_acl_item.grantee, mz_acl_item.grantor))
902                .or_insert_with(|| MzAclItem::empty(mz_acl_item.grantee, mz_acl_item.grantor));
903            item.acl_mode |= mz_acl_item.acl_mode;
904            accum
905        })
906        .into_values()
907}
908
909#[mz_ore::test]
910fn test_mz_acl_parsing() {
911    let s = "u42=rw/s666";
912    let mz_acl: MzAclItem = s.parse().unwrap();
913    assert_eq!(RoleId::User(42), mz_acl.grantee);
914    assert_eq!(RoleId::System(666), mz_acl.grantor);
915    assert!(!mz_acl.acl_mode.contains(AclMode::INSERT));
916    assert!(mz_acl.acl_mode.contains(AclMode::SELECT));
917    assert!(mz_acl.acl_mode.contains(AclMode::UPDATE));
918    assert!(!mz_acl.acl_mode.contains(AclMode::DELETE));
919    assert!(!mz_acl.acl_mode.contains(AclMode::USAGE));
920    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE));
921    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_ROLE));
922    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_DB));
923    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_CLUSTER));
924    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_NETWORK_POLICY));
925    assert_eq!(s, mz_acl.to_string());
926
927    let s = "=UC/u4";
928    let mz_acl: MzAclItem = s.parse().unwrap();
929    assert_eq!(RoleId::Public, mz_acl.grantee);
930    assert_eq!(RoleId::User(4), mz_acl.grantor);
931    assert!(!mz_acl.acl_mode.contains(AclMode::INSERT));
932    assert!(!mz_acl.acl_mode.contains(AclMode::SELECT));
933    assert!(!mz_acl.acl_mode.contains(AclMode::UPDATE));
934    assert!(!mz_acl.acl_mode.contains(AclMode::DELETE));
935    assert!(mz_acl.acl_mode.contains(AclMode::USAGE));
936    assert!(mz_acl.acl_mode.contains(AclMode::CREATE));
937    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_ROLE));
938    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_DB));
939    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_CLUSTER));
940    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_NETWORK_POLICY));
941    assert_eq!(s, mz_acl.to_string());
942
943    let s = "s7=/s12";
944    let mz_acl: MzAclItem = s.parse().unwrap();
945    assert_eq!(RoleId::System(7), mz_acl.grantee);
946    assert_eq!(RoleId::System(12), mz_acl.grantor);
947    assert!(!mz_acl.acl_mode.contains(AclMode::INSERT));
948    assert!(!mz_acl.acl_mode.contains(AclMode::SELECT));
949    assert!(!mz_acl.acl_mode.contains(AclMode::UPDATE));
950    assert!(!mz_acl.acl_mode.contains(AclMode::DELETE));
951    assert!(!mz_acl.acl_mode.contains(AclMode::USAGE));
952    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE));
953    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_ROLE));
954    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_DB));
955    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_CLUSTER));
956    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_NETWORK_POLICY));
957    assert_eq!(s, mz_acl.to_string());
958
959    let s = "=/u100";
960    let mz_acl: MzAclItem = s.parse().unwrap();
961    assert_eq!(RoleId::Public, mz_acl.grantee);
962    assert_eq!(RoleId::User(100), mz_acl.grantor);
963    assert!(!mz_acl.acl_mode.contains(AclMode::INSERT));
964    assert!(!mz_acl.acl_mode.contains(AclMode::SELECT));
965    assert!(!mz_acl.acl_mode.contains(AclMode::UPDATE));
966    assert!(!mz_acl.acl_mode.contains(AclMode::DELETE));
967    assert!(!mz_acl.acl_mode.contains(AclMode::USAGE));
968    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE));
969    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_ROLE));
970    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_DB));
971    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_CLUSTER));
972    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE_NETWORK_POLICY));
973    assert_eq!(s, mz_acl.to_string());
974
975    let s = "u1=RBNP/u2";
976    let mz_acl: MzAclItem = s.parse().unwrap();
977    assert_eq!(RoleId::User(1), mz_acl.grantee);
978    assert_eq!(RoleId::User(2), mz_acl.grantor);
979    assert!(!mz_acl.acl_mode.contains(AclMode::INSERT));
980    assert!(!mz_acl.acl_mode.contains(AclMode::SELECT));
981    assert!(!mz_acl.acl_mode.contains(AclMode::UPDATE));
982    assert!(!mz_acl.acl_mode.contains(AclMode::DELETE));
983    assert!(!mz_acl.acl_mode.contains(AclMode::USAGE));
984    assert!(!mz_acl.acl_mode.contains(AclMode::CREATE));
985    assert!(mz_acl.acl_mode.contains(AclMode::CREATE_ROLE));
986    assert!(mz_acl.acl_mode.contains(AclMode::CREATE_DB));
987    assert!(mz_acl.acl_mode.contains(AclMode::CREATE_CLUSTER));
988    assert!(mz_acl.acl_mode.contains(AclMode::CREATE_NETWORK_POLICY));
989    assert_eq!(s, mz_acl.to_string());
990
991    mz_ore::assert_err!("u42/rw=u666".parse::<MzAclItem>());
992    mz_ore::assert_err!("u32=C/".parse::<MzAclItem>());
993    mz_ore::assert_err!("=/".parse::<MzAclItem>());
994    mz_ore::assert_err!("f62hfiuew827fhh".parse::<MzAclItem>());
995    mz_ore::assert_err!("u2=rw/s66=CU/u33".parse::<MzAclItem>());
996}
997
998#[mz_ore::test]
999fn test_mz_acl_item_binary() {
1000    use std::ops::BitAnd;
1001
1002    let mz_acl_item = MzAclItem {
1003        grantee: RoleId::User(42),
1004        grantor: RoleId::System(666),
1005        acl_mode: AclMode::empty()
1006            .bitand(AclMode::SELECT)
1007            .bitand(AclMode::UPDATE),
1008    };
1009    assert_eq!(
1010        mz_acl_item,
1011        MzAclItem::decode_binary(&mz_acl_item.encode_binary()).unwrap()
1012    );
1013
1014    let mz_acl_item = MzAclItem {
1015        grantee: RoleId::Public,
1016        grantor: RoleId::User(4),
1017        acl_mode: AclMode::empty()
1018            .bitand(AclMode::USAGE)
1019            .bitand(AclMode::CREATE),
1020    };
1021    assert_eq!(
1022        mz_acl_item,
1023        MzAclItem::decode_binary(&mz_acl_item.encode_binary()).unwrap()
1024    );
1025
1026    let mz_acl_item = MzAclItem {
1027        grantee: RoleId::System(7),
1028        grantor: RoleId::System(12),
1029        acl_mode: AclMode::empty(),
1030    };
1031    assert_eq!(
1032        mz_acl_item,
1033        MzAclItem::decode_binary(&mz_acl_item.encode_binary()).unwrap()
1034    );
1035
1036    let mz_acl_item = MzAclItem {
1037        grantee: RoleId::Public,
1038        grantor: RoleId::User(100),
1039        acl_mode: AclMode::empty(),
1040    };
1041    assert_eq!(
1042        mz_acl_item,
1043        MzAclItem::decode_binary(&mz_acl_item.encode_binary()).unwrap()
1044    );
1045
1046    mz_ore::assert_err!(MzAclItem::decode_binary(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]))
1047}
1048
1049#[mz_ore::test]
1050fn test_mz_acl_item_binary_size() {
1051    assert_eq!(26, MzAclItem::binary_size());
1052}
1053
1054#[mz_ore::test]
1055fn test_acl_mode_serde_and_debug() {
1056    let acl_mode = AclMode::INSERT | AclMode::SELECT;
1057    let json = serde_json::to_string(&acl_mode).unwrap();
1058    assert_eq!(json, r#"{"bits":3}"#);
1059    assert_eq!(serde_json::from_str::<AclMode>(&json).unwrap(), acl_mode);
1060
1061    assert_eq!(format!("{acl_mode:?}"), "INSERT | SELECT");
1062    assert_eq!(format!("{:?}", AclMode::empty()), "(empty)");
1063}
1064
1065#[mz_ore::test]
1066fn test_acl_parsing() {
1067    let s = "42=rw/666";
1068    let acl: AclItem = s.parse().unwrap();
1069    assert_eq!(42, acl.grantee.0);
1070    assert_eq!(666, acl.grantor.0);
1071    assert!(!acl.acl_mode.contains(AclMode::INSERT));
1072    assert!(acl.acl_mode.contains(AclMode::SELECT));
1073    assert!(acl.acl_mode.contains(AclMode::UPDATE));
1074    assert!(!acl.acl_mode.contains(AclMode::DELETE));
1075    assert!(!acl.acl_mode.contains(AclMode::USAGE));
1076    assert!(!acl.acl_mode.contains(AclMode::CREATE));
1077    assert!(!acl.acl_mode.contains(AclMode::CREATE_ROLE));
1078    assert!(!acl.acl_mode.contains(AclMode::CREATE_DB));
1079    assert!(!acl.acl_mode.contains(AclMode::CREATE_CLUSTER));
1080    assert!(!acl.acl_mode.contains(AclMode::CREATE_NETWORK_POLICY));
1081    assert_eq!(s, acl.to_string());
1082
1083    let s = "=UC/4";
1084    let acl: AclItem = s.parse().unwrap();
1085    assert_eq!(PUBLIC_ROLE_OID, acl.grantee);
1086    assert_eq!(4, acl.grantor.0);
1087    assert!(!acl.acl_mode.contains(AclMode::INSERT));
1088    assert!(!acl.acl_mode.contains(AclMode::SELECT));
1089    assert!(!acl.acl_mode.contains(AclMode::UPDATE));
1090    assert!(!acl.acl_mode.contains(AclMode::DELETE));
1091    assert!(acl.acl_mode.contains(AclMode::USAGE));
1092    assert!(acl.acl_mode.contains(AclMode::CREATE));
1093    assert!(!acl.acl_mode.contains(AclMode::CREATE_ROLE));
1094    assert!(!acl.acl_mode.contains(AclMode::CREATE_DB));
1095    assert!(!acl.acl_mode.contains(AclMode::CREATE_CLUSTER));
1096    assert!(!acl.acl_mode.contains(AclMode::CREATE_NETWORK_POLICY));
1097    assert_eq!(s, acl.to_string());
1098
1099    let s = "7=/12";
1100    let acl: AclItem = s.parse().unwrap();
1101    assert_eq!(7, acl.grantee.0);
1102    assert_eq!(12, acl.grantor.0);
1103    assert!(!acl.acl_mode.contains(AclMode::INSERT));
1104    assert!(!acl.acl_mode.contains(AclMode::SELECT));
1105    assert!(!acl.acl_mode.contains(AclMode::UPDATE));
1106    assert!(!acl.acl_mode.contains(AclMode::DELETE));
1107    assert!(!acl.acl_mode.contains(AclMode::USAGE));
1108    assert!(!acl.acl_mode.contains(AclMode::CREATE));
1109    assert!(!acl.acl_mode.contains(AclMode::CREATE_ROLE));
1110    assert!(!acl.acl_mode.contains(AclMode::CREATE_DB));
1111    assert!(!acl.acl_mode.contains(AclMode::CREATE_CLUSTER));
1112    assert!(!acl.acl_mode.contains(AclMode::CREATE_NETWORK_POLICY));
1113    assert_eq!(s, acl.to_string());
1114
1115    let s = "=/100";
1116    let acl: AclItem = s.parse().unwrap();
1117    assert_eq!(PUBLIC_ROLE_OID, acl.grantee);
1118    assert_eq!(100, acl.grantor.0);
1119    assert!(!acl.acl_mode.contains(AclMode::INSERT));
1120    assert!(!acl.acl_mode.contains(AclMode::SELECT));
1121    assert!(!acl.acl_mode.contains(AclMode::UPDATE));
1122    assert!(!acl.acl_mode.contains(AclMode::DELETE));
1123    assert!(!acl.acl_mode.contains(AclMode::USAGE));
1124    assert!(!acl.acl_mode.contains(AclMode::CREATE));
1125    assert!(!acl.acl_mode.contains(AclMode::CREATE_ROLE));
1126    assert!(!acl.acl_mode.contains(AclMode::CREATE_DB));
1127    assert!(!acl.acl_mode.contains(AclMode::CREATE_CLUSTER));
1128    assert!(!acl.acl_mode.contains(AclMode::CREATE_NETWORK_POLICY));
1129    assert_eq!(s, acl.to_string());
1130
1131    let s = "1=RBNP/2";
1132    let acl: AclItem = s.parse().unwrap();
1133    assert_eq!(1, acl.grantee.0);
1134    assert_eq!(2, acl.grantor.0);
1135    assert!(!acl.acl_mode.contains(AclMode::INSERT));
1136    assert!(!acl.acl_mode.contains(AclMode::SELECT));
1137    assert!(!acl.acl_mode.contains(AclMode::UPDATE));
1138    assert!(!acl.acl_mode.contains(AclMode::DELETE));
1139    assert!(!acl.acl_mode.contains(AclMode::USAGE));
1140    assert!(!acl.acl_mode.contains(AclMode::CREATE));
1141    assert!(acl.acl_mode.contains(AclMode::CREATE_ROLE));
1142    assert!(acl.acl_mode.contains(AclMode::CREATE_DB));
1143    assert!(acl.acl_mode.contains(AclMode::CREATE_CLUSTER));
1144    assert!(acl.acl_mode.contains(AclMode::CREATE_NETWORK_POLICY));
1145    assert_eq!(s, acl.to_string());
1146
1147    mz_ore::assert_err!("42/rw=666".parse::<AclItem>());
1148    mz_ore::assert_err!("u42=rw/u666".parse::<AclItem>());
1149    mz_ore::assert_err!("s42=rw/s666".parse::<AclItem>());
1150    mz_ore::assert_err!("u32=C/".parse::<AclItem>());
1151    mz_ore::assert_err!("=/".parse::<AclItem>());
1152    mz_ore::assert_err!("f62hfiuew827fhh".parse::<AclItem>());
1153    mz_ore::assert_err!("u2=rw/s66=CU/u33".parse::<AclItem>());
1154}
1155
1156#[mz_ore::test]
1157fn test_acl_item_binary() {
1158    use std::ops::BitAnd;
1159
1160    let acl_item = AclItem {
1161        grantee: Oid(42),
1162        grantor: Oid(666),
1163        acl_mode: AclMode::empty()
1164            .bitand(AclMode::SELECT)
1165            .bitand(AclMode::UPDATE),
1166    };
1167    assert_eq!(
1168        acl_item,
1169        AclItem::decode_binary(&acl_item.encode_binary()).unwrap()
1170    );
1171
1172    let acl_item = AclItem {
1173        grantee: PUBLIC_ROLE_OID,
1174        grantor: Oid(4),
1175        acl_mode: AclMode::empty()
1176            .bitand(AclMode::USAGE)
1177            .bitand(AclMode::CREATE),
1178    };
1179    assert_eq!(
1180        acl_item,
1181        AclItem::decode_binary(&acl_item.encode_binary()).unwrap()
1182    );
1183
1184    let acl_item = AclItem {
1185        grantee: Oid(7),
1186        grantor: Oid(12),
1187        acl_mode: AclMode::empty(),
1188    };
1189    assert_eq!(
1190        acl_item,
1191        AclItem::decode_binary(&acl_item.encode_binary()).unwrap()
1192    );
1193
1194    let acl_item = AclItem {
1195        grantee: PUBLIC_ROLE_OID,
1196        grantor: Oid(100),
1197        acl_mode: AclMode::empty(),
1198    };
1199    assert_eq!(
1200        acl_item,
1201        AclItem::decode_binary(&acl_item.encode_binary()).unwrap()
1202    );
1203
1204    mz_ore::assert_err!(AclItem::decode_binary(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]))
1205}
1206
1207#[mz_ore::test]
1208fn test_acl_item_binary_size() {
1209    assert_eq!(16, AclItem::binary_size());
1210}
1211
1212#[cfg(test)]
1213proptest! {
1214  #[mz_ore::test]
1215  #[cfg_attr(miri, ignore)] // slow
1216  fn proptest_acl_item_binary_encoding_roundtrip(acl_item: AclItem) {
1217      let encoded = acl_item.encode_binary();
1218      let decoded = AclItem::decode_binary(&encoded).unwrap();
1219      assert_eq!(acl_item, decoded);
1220  }
1221
1222  #[mz_ore::test]
1223  #[cfg_attr(miri, ignore)] // slow
1224  fn proptest_valid_acl_item_str(acl_item: AclItem) {
1225      let encoded = acl_item.to_string();
1226      let decoded = AclItem::from_str(&encoded).unwrap();
1227      assert_eq!(acl_item, decoded);
1228  }
1229}
1230
1231#[mz_ore::test]
1232#[cfg(any(test, feature = "proptest"))]
1233fn proptest_packed_acl_item_roundtrips() {
1234    fn roundtrip_acl_item(og: AclItem) {
1235        let packed = PackedAclItem::from_value(og);
1236        let rnd = packed.into_value();
1237        assert_eq!(og, rnd);
1238    }
1239
1240    proptest!(|(acl_item in proptest::arbitrary::any::<AclItem>())| {
1241        roundtrip_acl_item(acl_item);
1242    })
1243}
1244
1245#[mz_ore::test]
1246#[cfg_attr(miri, ignore)] // slow
1247#[cfg(any(test, feature = "proptest"))]
1248fn proptest_packed_acl_item_sorts() {
1249    fn sort_acl_items(mut og: Vec<AclItem>) {
1250        let mut packed: Vec<_> = og.iter().copied().map(PackedAclItem::from_value).collect();
1251
1252        og.sort();
1253        packed.sort();
1254
1255        let rnd: Vec<_> = packed.into_iter().map(PackedAclItem::into_value).collect();
1256        assert_eq!(og, rnd);
1257    }
1258
1259    proptest!(|(acl_items in proptest::collection::vec(any::<AclItem>(), 0..64))| {
1260        sort_acl_items(acl_items);
1261    });
1262}
1263
1264#[mz_ore::test]
1265#[cfg(any(test, feature = "proptest"))]
1266fn proptest_packed_mz_acl_item_roundtrips() {
1267    fn roundtrip_mz_acl_item(og: MzAclItem) {
1268        let packed = PackedMzAclItem::from_value(og);
1269        let rnd = packed.into_value();
1270        assert_eq!(og, rnd);
1271    }
1272
1273    proptest!(|(acl_item in proptest::arbitrary::any::<MzAclItem>())| {
1274        roundtrip_mz_acl_item(acl_item);
1275    })
1276}
1277
1278#[mz_ore::test]
1279#[cfg_attr(miri, ignore)] // slow
1280#[cfg(any(test, feature = "proptest"))]
1281fn proptest_packed_mz_acl_item_sorts() {
1282    fn sort_mz_acl_items(mut og: Vec<MzAclItem>) {
1283        let mut packed: Vec<_> = og
1284            .iter()
1285            .copied()
1286            .map(PackedMzAclItem::from_value)
1287            .collect();
1288
1289        og.sort();
1290        packed.sort();
1291
1292        let rnd: Vec<_> = packed
1293            .into_iter()
1294            .map(PackedMzAclItem::into_value)
1295            .collect();
1296        assert_eq!(og, rnd);
1297    }
1298
1299    proptest!(|(acl_items in proptest::collection::vec(any::<MzAclItem>(), 0..64))| {
1300        sort_mz_acl_items(acl_items);
1301    });
1302}