1#![forbid(unsafe_code)]
9
10use bitcode::{Decode, Encode};
11
12use crate::types::{FieldId, RowId, TableId};
13
14pub type ConstraintId = u32;
19
20const TABLE_ID_BITS: u32 = (std::mem::size_of::<TableId>() * 8) as u32;
21const CONSTRAINT_ID_BITS: u32 = 64 - TABLE_ID_BITS;
22const CONSTRAINT_ID_MASK: u64 = (1u64 << CONSTRAINT_ID_BITS) - 1;
23
24const _: () = assert!(
25 std::mem::size_of::<ConstraintId>() * 8 <= CONSTRAINT_ID_BITS as usize,
26 "ConstraintId does not fit within allocated row id bits"
27);
28
29#[inline]
31pub fn encode_constraint_row_id(table_id: TableId, constraint_id: ConstraintId) -> RowId {
32 ((table_id as u64) << CONSTRAINT_ID_BITS) | (constraint_id as u64 & CONSTRAINT_ID_MASK)
33}
34
35#[inline]
37pub fn decode_constraint_row_id(row_id: RowId) -> (TableId, ConstraintId) {
38 let constraint_id = (row_id & CONSTRAINT_ID_MASK) as ConstraintId;
39 let table_id = (row_id >> CONSTRAINT_ID_BITS) as TableId;
40 (table_id, constraint_id)
41}
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq, Encode, Decode)]
45pub enum ConstraintState {
46 Active,
47 Dropped,
48}
49
50impl ConstraintState {
51 #[inline]
52 pub fn is_active(self) -> bool {
53 matches!(self, ConstraintState::Active)
54 }
55}
56
57#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
59pub struct ConstraintRecord {
60 pub constraint_id: ConstraintId,
61 pub kind: ConstraintKind,
62 pub state: ConstraintState,
63 pub revision: u64,
65 pub last_modified_micros: u64,
67}
68
69impl ConstraintRecord {
70 pub fn is_active(&self) -> bool {
71 self.state.is_active()
72 }
73}
74
75#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
77pub enum ConstraintKind {
78 PrimaryKey(PrimaryKeyConstraint),
79 Unique(UniqueConstraint),
80 ForeignKey(ForeignKeyConstraint),
81 Check(CheckConstraint),
82}
83
84impl ConstraintKind {
85 pub fn field_ids(&self) -> &[FieldId] {
87 match self {
88 ConstraintKind::PrimaryKey(payload) => &payload.field_ids,
89 ConstraintKind::Unique(payload) => &payload.field_ids,
90 ConstraintKind::ForeignKey(payload) => &payload.referencing_field_ids,
91 ConstraintKind::Check(payload) => &payload.field_ids,
92 }
93 }
94}
95
96#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
98pub struct PrimaryKeyConstraint {
99 pub field_ids: Vec<FieldId>,
100}
101
102#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
104pub struct UniqueConstraint {
105 pub field_ids: Vec<FieldId>,
106}
107
108#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
110pub struct ForeignKeyConstraint {
111 pub referencing_field_ids: Vec<FieldId>,
112 pub referenced_table: TableId,
113 pub referenced_field_ids: Vec<FieldId>,
114 pub on_delete: ForeignKeyAction,
115 pub on_update: ForeignKeyAction,
116}
117
118#[derive(Clone, Copy, Debug, PartialEq, Eq, Encode, Decode)]
120pub enum ForeignKeyAction {
121 NoAction,
122 Restrict,
123}
124
125#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
130pub struct CheckConstraint {
131 pub field_ids: Vec<FieldId>,
132 pub expression_ref: ConstraintExpressionRef,
133}
134
135#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Encode, Decode)]
137pub struct ConstraintExpressionRef(pub u64);
138
139impl ConstraintExpressionRef {
140 pub const NONE: ConstraintExpressionRef = ConstraintExpressionRef(0);
141
142 #[inline]
143 pub fn is_none(self) -> bool {
144 self.0 == 0
145 }
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn constraint_row_id_round_trip() {
154 let samples: &[(TableId, ConstraintId)] = &[
155 (1, 1),
156 (42, 7),
157 (TableId::MAX, ConstraintId::MAX),
158 (123, 987_654),
159 ];
160
161 for &(table_id, constraint_id) in samples {
162 let row_id = encode_constraint_row_id(table_id, constraint_id);
163 let (decoded_table, decoded_constraint) = decode_constraint_row_id(row_id);
164 assert_eq!(decoded_table, table_id);
165 assert_eq!(decoded_constraint, constraint_id);
166 }
167 }
168
169 #[test]
170 fn constraint_record_active_state() {
171 let record = ConstraintRecord {
172 constraint_id: 1,
173 kind: ConstraintKind::PrimaryKey(PrimaryKeyConstraint {
174 field_ids: vec![1, 2],
175 }),
176 state: ConstraintState::Active,
177 revision: 5,
178 last_modified_micros: 123,
179 };
180 assert!(record.is_active());
181 }
182}