radixdb-catalog 1.1.0

Generational logical catalog owner for RadixDB
Documentation
use crate::payload::common::{validate_flags, validate_version};
use crate::{CatalogError, CatalogResult, ObjectId};

pub const MAX_PLANNER_SUPPORT_LOCAL_ID_BYTES: usize = 255;
pub const MAX_PLANNER_SUPPORT_SPANS: u32 = 4096;
pub const MAX_PLANNER_SUPPORT_OUTPUT_BYTES: u32 = 16 * 1024 * 1024;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u16)]
pub enum PlannerRecheckPolicy {
    Exact = 1,
    Always = 2,
}

impl PlannerRecheckPolicy {
    pub const fn tag(self) -> u16 {
        self as u16
    }
}

impl TryFrom<u16> for PlannerRecheckPolicy {
    type Error = CatalogError;

    fn try_from(tag: u16) -> CatalogResult<Self> {
        match tag {
            1 => Ok(Self::Exact),
            2 => Ok(Self::Always),
            _ => Err(CatalogError::UnknownPayloadEnumTag {
                owner: "planner support recheck policy",
                tag,
            }),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PlannerSupportPayload {
    extension_binding_id: ObjectId,
    local_id: String,
    semantic_revision: u32,
    target_function_id: Option<ObjectId>,
    target_operator_class_id: Option<ObjectId>,
    max_spans: u32,
    max_output_bytes: u32,
    recheck_policy: PlannerRecheckPolicy,
    fingerprint: [u8; 32],
}

impl PlannerSupportPayload {
    #[allow(clippy::too_many_arguments)]
    pub fn new(
        extension_binding_id: ObjectId,
        local_id: impl Into<String>,
        semantic_revision: u32,
        target_function_id: Option<ObjectId>,
        target_operator_class_id: Option<ObjectId>,
        max_spans: u32,
        max_output_bytes: u32,
        recheck_policy: PlannerRecheckPolicy,
        fingerprint: [u8; 32],
    ) -> CatalogResult<Self> {
        Self::from_fields(
            super::PAYLOAD_VERSION,
            0,
            extension_binding_id,
            local_id.into(),
            semantic_revision,
            target_function_id,
            target_operator_class_id,
            max_spans,
            max_output_bytes,
            recheck_policy,
            fingerprint,
        )
    }

    #[allow(clippy::too_many_arguments)]
    pub fn from_fields(
        version: u16,
        flags: u64,
        extension_binding_id: ObjectId,
        local_id: String,
        semantic_revision: u32,
        target_function_id: Option<ObjectId>,
        target_operator_class_id: Option<ObjectId>,
        max_spans: u32,
        max_output_bytes: u32,
        recheck_policy: PlannerRecheckPolicy,
        fingerprint: [u8; 32],
    ) -> CatalogResult<Self> {
        validate_version("planner support", version)?;
        validate_flags("planner support", flags)?;
        if local_id.is_empty()
            || local_id.len() > MAX_PLANNER_SUPPORT_LOCAL_ID_BYTES
            || local_id.contains('\0')
        {
            return Err(CatalogError::InvalidPlannerSupportPayload {
                detail: "local id must be 1..=255 UTF-8 bytes without NUL",
            });
        }
        if semantic_revision == 0 {
            return Err(CatalogError::InvalidPlannerSupportPayload {
                detail: "semantic revision must be at least one",
            });
        }
        if target_function_id.is_none() && target_operator_class_id.is_none() {
            return Err(CatalogError::InvalidPlannerSupportPayload {
                detail: "at least one target is required",
            });
        }
        if !(1..=MAX_PLANNER_SUPPORT_SPANS).contains(&max_spans) {
            return Err(CatalogError::InvalidPlannerSupportPayload {
                detail: "maximum spans must be in 1..=4096",
            });
        }
        if !(1..=MAX_PLANNER_SUPPORT_OUTPUT_BYTES).contains(&max_output_bytes) {
            return Err(CatalogError::InvalidPlannerSupportPayload {
                detail: "maximum output bytes must be in 1..=16 MiB",
            });
        }
        Ok(Self {
            extension_binding_id,
            local_id,
            semantic_revision,
            target_function_id,
            target_operator_class_id,
            max_spans,
            max_output_bytes,
            recheck_policy,
            fingerprint,
        })
    }

    pub const fn flags(&self) -> u64 {
        0
    }
    pub const fn extension_binding_id(&self) -> ObjectId {
        self.extension_binding_id
    }
    pub fn local_id(&self) -> &str {
        &self.local_id
    }
    pub const fn semantic_revision(&self) -> u32 {
        self.semantic_revision
    }
    pub const fn target_function_id(&self) -> Option<ObjectId> {
        self.target_function_id
    }
    pub const fn target_operator_class_id(&self) -> Option<ObjectId> {
        self.target_operator_class_id
    }
    pub const fn max_spans(&self) -> u32 {
        self.max_spans
    }
    pub const fn max_output_bytes(&self) -> u32 {
        self.max_output_bytes
    }
    pub const fn recheck_policy(&self) -> PlannerRecheckPolicy {
        self.recheck_policy
    }
    pub const fn fingerprint(&self) -> &[u8; 32] {
        &self.fingerprint
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn id(byte: u8) -> ObjectId {
        ObjectId::from_user_bytes([byte; 16]).unwrap()
    }

    #[test]
    fn planner_support_contract_is_exact() {
        let payload = PlannerSupportPayload::new(
            id(1),
            "point_eq_support",
            3,
            Some(id(2)),
            Some(id(3)),
            64,
            4096,
            PlannerRecheckPolicy::Always,
            [4; 32],
        )
        .unwrap();
        assert_eq!(payload.extension_binding_id(), id(1));
        assert_eq!(payload.target_function_id(), Some(id(2)));
        assert_eq!(payload.target_operator_class_id(), Some(id(3)));
        assert_eq!(payload.max_spans(), 64);
        assert_eq!(payload.max_output_bytes(), 4096);
        assert_eq!(payload.recheck_policy(), PlannerRecheckPolicy::Always);
        assert_eq!(
            PlannerRecheckPolicy::try_from(1).unwrap(),
            PlannerRecheckPolicy::Exact
        );
        assert!(PlannerRecheckPolicy::try_from(3).is_err());
    }

    #[test]
    fn planner_support_bounds_fail_closed() {
        let make = |target_function, target_class, spans, bytes| {
            PlannerSupportPayload::new(
                id(1),
                "support",
                1,
                target_function,
                target_class,
                spans,
                bytes,
                PlannerRecheckPolicy::Exact,
                [0; 32],
            )
        };
        assert!(make(None, None, 1, 1).is_err());
        assert!(make(Some(id(2)), None, 0, 1).is_err());
        assert!(make(Some(id(2)), None, MAX_PLANNER_SUPPORT_SPANS + 1, 1).is_err());
        assert!(make(Some(id(2)), None, 1, 0).is_err());
        assert!(make(Some(id(2)), None, 1, MAX_PLANNER_SUPPORT_OUTPUT_BYTES + 1,).is_err());
    }
}