spacetimedb_primitives/
attr.rs

1//! Constraints and Column attributes.
2//!
3//! ## For tables
4//!
5//! The database engine support a sub-set of constraints defined by the `SQL` standard:
6//!
7//! ### UNIQUE:
8//!
9//!  One or many columns with enforced uniqueness using an auto-generate index.
10//!
11//! ### IDENTITY:
12//!
13//! A column that is made up of values generated by the database using a sequence.
14//!
15//!  Differs from `PRIMARY_KEY` in that its values are managed by the database and usually cannot be modified.
16//!
17//! ### PRIMARY_KEY:
18//!
19//! One or many columns that uniquely identifies each record in a table.
20//!
21//! Enforce uniqueness using an auto-generate index.
22//!
23//! Can only be one per-table.
24//!
25//! ## For Columns
26//!
27//! Additionally, is possible to add markers to columns as:
28//!
29//! - AUTO_INC: Auto-generate a sequence
30//! - INDEXED: Auto-generate a non-unique index
31//! - PRIMARY_KEY_AUTO: Make it a `PRIMARY_KEY` + `AUTO_INC`
32//! - PRIMARY_KEY_IDENTITY: Make it a `PRIMARY_KEY` + `IDENTITY`
33//!
34//! NOTE: We have [ConstraintKind] and [AttributeKind] intentionally semi-duplicated because
35//! the first is for the [Constrains] that are per-table and the second is for markers of the column.
36
37//TODO: This needs a proper refactor, and use types for `column attributes` and `table tributes`
38/// The assigned constraint for a `Table`
39#[allow(non_camel_case_types)]
40#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord)]
41pub enum ConstraintKind {
42    UNSET,
43    ///  Index no unique
44    INDEXED,
45    /// Index unique
46    UNIQUE,
47    /// Unique + AutoInc
48    IDENTITY,
49    /// Primary key column (implies Unique)
50    PRIMARY_KEY,
51    /// PrimaryKey + AutoInc
52    PRIMARY_KEY_AUTO,
53    /// PrimaryKey + Identity
54    PRIMARY_KEY_IDENTITY,
55}
56
57/// The assigned constraint OR auto-inc marker for a `Column`
58#[allow(non_camel_case_types, clippy::upper_case_acronyms)]
59#[derive(Eq, PartialEq)]
60pub enum AttributeKind {
61    UNSET,
62    ///  Index no unique
63    INDEXED,
64    ///  Auto Increment
65    AUTO_INC,
66    /// Index unique
67    UNIQUE,
68    /// Unique + AutoInc
69    IDENTITY,
70    /// Primary key column (implies Unique).
71    PRIMARY_KEY,
72    /// PrimaryKey + AutoInc
73    PRIMARY_KEY_AUTO,
74    /// PrimaryKey + Identity
75    PRIMARY_KEY_IDENTITY,
76}
77
78bitflags::bitflags! {
79    #[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash)]
80    pub struct ColumnAttribute: u8 {
81        const UNSET = Self::empty().bits();
82        ///  Index no unique
83        const INDEXED = 0b0001;
84        /// Generate the next [Sequence]
85        const AUTO_INC = 0b0010;
86        /// Index unique
87        const UNIQUE = Self::INDEXED.bits() | 0b0100;
88        /// Unique + AutoInc
89        const IDENTITY = Self::UNIQUE.bits() | Self::AUTO_INC.bits();
90        /// Primary key column (implies Unique)
91        const PRIMARY_KEY = Self::UNIQUE.bits() | 0b1000;
92        /// PrimaryKey + AutoInc
93        const PRIMARY_KEY_AUTO = Self::PRIMARY_KEY.bits() | Self::AUTO_INC.bits();
94         /// PrimaryKey + Identity
95        const PRIMARY_KEY_IDENTITY = Self::PRIMARY_KEY.bits() | Self::IDENTITY.bits() ;
96    }
97}
98
99impl ColumnAttribute {
100    /// Checks if either 'IDENTITY' or 'PRIMARY_KEY_AUTO' constraints are set because the imply the use of
101    /// auto increment sequence.
102    pub const fn has_autoinc(&self) -> bool {
103        self.contains(Self::IDENTITY) || self.contains(Self::PRIMARY_KEY_AUTO) || self.contains(Self::AUTO_INC)
104    }
105
106    /// Checks if the 'UNIQUE' constraint is set.
107    pub const fn has_unique(&self) -> bool {
108        self.contains(Self::UNIQUE)
109    }
110
111    /// Checks if the 'INDEXED' constraint is set.
112    pub const fn has_indexed(&self) -> bool {
113        self.contains(ColumnAttribute::INDEXED)
114    }
115
116    /// Checks if the 'PRIMARY_KEY' constraint is set.
117    pub const fn has_primary_key(&self) -> bool {
118        self.contains(ColumnAttribute::PRIMARY_KEY)
119            || self.contains(ColumnAttribute::PRIMARY_KEY_AUTO)
120            || self.contains(ColumnAttribute::PRIMARY_KEY_IDENTITY)
121    }
122
123    /// Returns the [ColumnAttribute] of constraints as an enum variant.
124    ///
125    /// NOTE: This represent the higher possible representation of a constraints, so for example
126    /// `IDENTITY` imply that is `INDEXED, UNIQUE`
127    pub fn kind(&self) -> AttributeKind {
128        match *self {
129            x if x == Self::UNSET => AttributeKind::UNSET,
130            x if x == Self::INDEXED => AttributeKind::INDEXED,
131            x if x == Self::UNIQUE => AttributeKind::UNIQUE,
132            x if x == Self::AUTO_INC => AttributeKind::AUTO_INC,
133            x if x == Self::IDENTITY => AttributeKind::IDENTITY,
134            x if x == Self::PRIMARY_KEY => AttributeKind::PRIMARY_KEY,
135            x if x == Self::PRIMARY_KEY_AUTO => AttributeKind::PRIMARY_KEY_AUTO,
136            x if x == Self::PRIMARY_KEY_IDENTITY => AttributeKind::PRIMARY_KEY_IDENTITY,
137            x => unreachable!("Unexpected value {x:?}"),
138        }
139    }
140}
141
142/// Represents constraints for a database table. May apply to multiple columns.
143#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Hash)]
144pub struct Constraints {
145    attr: ColumnAttribute,
146}
147
148impl Constraints {
149    /// Creates a new `Constraints` instance with the given `attr` flags.
150    #[inline(always)]
151    const fn new(attr: ColumnAttribute) -> Self {
152        Self { attr }
153    }
154
155    /// Creates a new `Constraints` instance that is [`Self::unique`] if `is_unique`
156    /// and [`Self::indexed`] otherwise.
157    pub const fn from_is_unique(is_unique: bool) -> Self {
158        if is_unique {
159            Self::unique()
160        } else {
161            Self::indexed()
162        }
163    }
164
165    /// Creates a new `Constraints` instance with no constraints set.
166    pub const fn unset() -> Self {
167        Self::new(ColumnAttribute::UNSET)
168    }
169
170    /// Creates a new `Constraints` instance with [ColumnAttribute::INDEXED] set.
171    pub const fn indexed() -> Self {
172        Self::new(ColumnAttribute::INDEXED)
173    }
174
175    /// Creates a new `Constraints` instance with [ColumnAttribute::UNIQUE] constraint set.
176    pub const fn unique() -> Self {
177        Self::new(ColumnAttribute::UNIQUE)
178    }
179
180    /// Creates a new `Constraints` instance with [ColumnAttribute::IDENTITY] set.
181    pub const fn identity() -> Self {
182        Self::new(ColumnAttribute::IDENTITY)
183    }
184
185    /// Creates a new `Constraints` instance with [ColumnAttribute::PRIMARY_KEY] set.
186    pub const fn primary_key() -> Self {
187        Self::new(ColumnAttribute::PRIMARY_KEY)
188    }
189
190    /// Creates a new `Constraints` instance with [ColumnAttribute::PRIMARY_KEY_AUTO] set.
191    pub const fn primary_key_auto() -> Self {
192        Self::new(ColumnAttribute::PRIMARY_KEY_AUTO)
193    }
194
195    /// Creates a new `Constraints` instance with [ColumnAttribute::PRIMARY_KEY_IDENTITY] set.
196    pub const fn primary_key_identity() -> Self {
197        Self::new(ColumnAttribute::PRIMARY_KEY_IDENTITY)
198    }
199
200    /// Creates a new `Constraints` instance with [ColumnAttribute::AUTO_INC] set.
201    pub const fn auto_inc() -> Self {
202        Self::new(ColumnAttribute::AUTO_INC)
203    }
204
205    /// Adds a constraint to the existing constraints.
206    ///
207    /// # Example
208    ///
209    /// ```
210    /// use spacetimedb_primitives::Constraints;
211    ///
212    /// let constraints = Constraints::unset().push(Constraints::indexed());
213    /// assert!(constraints.has_indexed());
214    /// ```
215    pub fn push(self, other: Constraints) -> Self {
216        Self::new(self.attr | other.attr)
217    }
218
219    /// Add auto-increment constraint to the existing constraints.
220    /// Returns Err if the result would not be valid.
221    #[allow(clippy::result_unit_err)]
222    pub fn push_auto_inc(self) -> Result<Self, ()> {
223        Self::try_from(self.attr | ColumnAttribute::AUTO_INC)
224    }
225
226    /// Returns the bits representing the constraints.
227    pub const fn bits(&self) -> u8 {
228        self.attr.bits()
229    }
230
231    /// Returns the [ConstraintKind] of constraints as an enum variant.
232    ///
233    /// NOTE: This represent the higher possible representation of a constraints, so for example
234    /// `IDENTITY` imply that is `INDEXED, UNIQUE`
235    pub fn kind(&self) -> ConstraintKind {
236        match self {
237            x if x.attr == ColumnAttribute::UNSET => ConstraintKind::UNSET,
238            x if x.attr == ColumnAttribute::INDEXED => ConstraintKind::INDEXED,
239            x if x.attr == ColumnAttribute::UNIQUE => ConstraintKind::UNIQUE,
240            x if x.attr == ColumnAttribute::IDENTITY => ConstraintKind::IDENTITY,
241            x if x.attr == ColumnAttribute::PRIMARY_KEY => ConstraintKind::PRIMARY_KEY,
242            x if x.attr == ColumnAttribute::PRIMARY_KEY_AUTO => ConstraintKind::PRIMARY_KEY_AUTO,
243            x if x.attr == ColumnAttribute::PRIMARY_KEY_IDENTITY => ConstraintKind::PRIMARY_KEY_IDENTITY,
244            x => unreachable!("Unexpected value {x:?}"),
245        }
246    }
247
248    pub fn contains(&self, other: &Self) -> bool {
249        self.attr.contains(other.attr)
250    }
251
252    /// Checks if the 'UNIQUE' constraint is set.
253    pub const fn has_unique(&self) -> bool {
254        self.attr.has_unique()
255    }
256
257    /// Checks if the 'INDEXED' constraint is set.
258    pub const fn has_indexed(&self) -> bool {
259        self.attr.has_indexed()
260    }
261
262    /// Checks if either 'IDENTITY' or 'PRIMARY_KEY_AUTO' constraints are set because the imply the use of
263    /// auto increment sequence.
264    pub const fn has_autoinc(&self) -> bool {
265        self.attr.has_autoinc()
266    }
267
268    /// Checks if the 'PRIMARY_KEY' constraint is set.
269    pub const fn has_primary_key(&self) -> bool {
270        self.attr.has_primary_key()
271    }
272}
273
274impl TryFrom<u8> for Constraints {
275    type Error = ();
276    fn try_from(v: u8) -> Result<Self, Self::Error> {
277        ColumnAttribute::from_bits(v).ok_or(()).map(Self::new)
278    }
279}
280
281impl TryFrom<ColumnAttribute> for Constraints {
282    type Error = ();
283
284    fn try_from(value: ColumnAttribute) -> Result<Self, Self::Error> {
285        Ok(match value.kind() {
286            AttributeKind::UNSET => Self::unset(),
287            AttributeKind::INDEXED => Self::indexed(),
288            AttributeKind::UNIQUE => Self::unique(),
289            AttributeKind::IDENTITY => Self::identity(),
290            AttributeKind::PRIMARY_KEY => Self::primary_key(),
291            AttributeKind::PRIMARY_KEY_AUTO => Self::primary_key_auto(),
292            AttributeKind::PRIMARY_KEY_IDENTITY => Self::primary_key_identity(),
293            AttributeKind::AUTO_INC => return Err(()),
294        })
295    }
296}