llkv_table/constraints/
types.rs

1//! Constraint metadata definitions and helpers for persistence.
2//!
3//! This module defines the storage-friendly representations of constraints
4//! that are persisted through the system catalog. The structures intentionally
5//! avoid heavyweight data (like table names) to keep serialized blobs small and
6//! cache friendly.
7
8#![forbid(unsafe_code)]
9
10use bitcode::{Decode, Encode};
11
12use crate::types::{FieldId, RowId, TableId};
13
14/// Controls when constraint validation occurs for new writes.
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Encode, Decode, Default)]
16pub enum ConstraintEnforcementMode {
17    /// Perform all constraint checks before rows become visible.
18    #[default]
19    Immediate,
20    /// Defer checks so callers can load data quickly and validate later.
21    Deferred,
22}
23
24impl ConstraintEnforcementMode {
25    #[inline]
26    pub const fn is_immediate(self) -> bool {
27        matches!(self, ConstraintEnforcementMode::Immediate)
28    }
29
30    #[inline]
31    pub const fn is_deferred(self) -> bool {
32        matches!(self, ConstraintEnforcementMode::Deferred)
33    }
34}
35
36/// Identifier assigned to a persisted constraint.
37///
38/// Constraint IDs are scoped per-table and limited to 32 bits so they can be
39/// embedded into catalog row IDs without collisions.
40pub type ConstraintId = u32;
41
42const TABLE_ID_BITS: u32 = (std::mem::size_of::<TableId>() * 8) as u32;
43const CONSTRAINT_ID_BITS: u32 = 64 - TABLE_ID_BITS;
44const CONSTRAINT_ID_MASK: u64 = (1u64 << CONSTRAINT_ID_BITS) - 1;
45
46const _: () = assert!(
47    std::mem::size_of::<ConstraintId>() * 8 <= CONSTRAINT_ID_BITS as usize,
48    "ConstraintId does not fit within allocated row id bits"
49);
50
51/// Pack a table/constraint pair into a catalog row identifier.
52#[inline]
53pub fn encode_constraint_row_id(table_id: TableId, constraint_id: ConstraintId) -> RowId {
54    ((table_id as u64) << CONSTRAINT_ID_BITS) | (constraint_id as u64 & CONSTRAINT_ID_MASK)
55}
56
57/// Decode a catalog row identifier into its table/constraint components.
58#[inline]
59pub fn decode_constraint_row_id(row_id: RowId) -> (TableId, ConstraintId) {
60    let constraint_id = (row_id & CONSTRAINT_ID_MASK) as ConstraintId;
61    let table_id = (row_id >> CONSTRAINT_ID_BITS) as TableId;
62    (table_id, constraint_id)
63}
64
65/// Persisted state of a constraint.
66#[derive(Clone, Copy, Debug, PartialEq, Eq, Encode, Decode)]
67pub enum ConstraintState {
68    Active,
69    Dropped,
70}
71
72impl ConstraintState {
73    #[inline]
74    pub fn is_active(self) -> bool {
75        matches!(self, ConstraintState::Active)
76    }
77}
78
79/// Persisted constraint record stored in the system catalog.
80#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
81pub struct ConstraintRecord {
82    pub constraint_id: ConstraintId,
83    pub kind: ConstraintKind,
84    pub state: ConstraintState,
85    /// Monotonic revision counter maintained by the metadata manager.
86    pub revision: u64,
87    /// Timestamp (microseconds since epoch) when this record was last updated.
88    pub last_modified_micros: u64,
89}
90
91impl ConstraintRecord {
92    pub fn is_active(&self) -> bool {
93        self.state.is_active()
94    }
95}
96
97/// Logical description of a constraint.
98#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
99pub enum ConstraintKind {
100    PrimaryKey(PrimaryKeyConstraint),
101    Unique(UniqueConstraint),
102    ForeignKey(ForeignKeyConstraint),
103    Check(CheckConstraint),
104}
105
106impl ConstraintKind {
107    /// Return the field IDs participating on the referencing side of the constraint.
108    pub fn field_ids(&self) -> &[FieldId] {
109        match self {
110            ConstraintKind::PrimaryKey(payload) => &payload.field_ids,
111            ConstraintKind::Unique(payload) => &payload.field_ids,
112            ConstraintKind::ForeignKey(payload) => &payload.referencing_field_ids,
113            ConstraintKind::Check(payload) => &payload.field_ids,
114        }
115    }
116}
117
118/// Primary key definition.
119#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
120pub struct PrimaryKeyConstraint {
121    pub field_ids: Vec<FieldId>,
122}
123
124/// Unique constraint definition (single or multi-column).
125#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
126pub struct UniqueConstraint {
127    pub field_ids: Vec<FieldId>,
128}
129
130/// Foreign key definition referencing another table.
131#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
132pub struct ForeignKeyConstraint {
133    pub referencing_field_ids: Vec<FieldId>,
134    pub referenced_table: TableId,
135    pub referenced_field_ids: Vec<FieldId>,
136    pub on_delete: ForeignKeyAction,
137    pub on_update: ForeignKeyAction,
138}
139
140/// Foreign key referential action.
141#[derive(Clone, Copy, Debug, PartialEq, Eq, Encode, Decode)]
142pub enum ForeignKeyAction {
143    NoAction,
144    Restrict,
145}
146
147/// Serialized check constraint definition.
148///
149/// The expression payload is referenced indirectly via an identifier so the
150/// metadata layer can resolve the actual expression lazily.
151#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
152pub struct CheckConstraint {
153    pub field_ids: Vec<FieldId>,
154    pub expression_ref: ConstraintExpressionRef,
155}
156
157/// Reference to a stored constraint expression payload.
158#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Encode, Decode)]
159pub struct ConstraintExpressionRef(pub u64);
160
161impl ConstraintExpressionRef {
162    pub const NONE: ConstraintExpressionRef = ConstraintExpressionRef(0);
163
164    #[inline]
165    pub fn is_none(self) -> bool {
166        self.0 == 0
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use super::*;
173
174    #[test]
175    fn constraint_row_id_round_trip() {
176        let samples: &[(TableId, ConstraintId)] = &[
177            (1, 1),
178            (42, 7),
179            (TableId::MAX, ConstraintId::MAX),
180            (123, 987_654),
181        ];
182
183        for &(table_id, constraint_id) in samples {
184            let row_id = encode_constraint_row_id(table_id, constraint_id);
185            let (decoded_table, decoded_constraint) = decode_constraint_row_id(row_id);
186            assert_eq!(decoded_table, table_id);
187            assert_eq!(decoded_constraint, constraint_id);
188        }
189    }
190
191    #[test]
192    fn constraint_record_active_state() {
193        let record = ConstraintRecord {
194            constraint_id: 1,
195            kind: ConstraintKind::PrimaryKey(PrimaryKeyConstraint {
196                field_ids: vec![1, 2],
197            }),
198            state: ConstraintState::Active,
199            revision: 5,
200            last_modified_micros: 123,
201        };
202        assert!(record.is_active());
203    }
204}