Skip to main content

sqlmodel_core/
fields_set.rs

1//! Track which fields were explicitly provided ("set") for a model instance.
2//!
3//! This is required to implement Pydantic-compatible `exclude_unset` semantics:
4//! a field with a default value should still be included if it was explicitly
5//! provided at construction/validation time.
6
7/// A compact bitset representing "field is set" for indices `0..len`.
8#[derive(Clone, Debug, PartialEq, Eq)]
9pub struct FieldsSet {
10    len: usize,
11    bits: Box<[u64]>,
12}
13
14impl FieldsSet {
15    /// Create an empty (all-unset) set for `len` fields.
16    #[must_use]
17    pub fn empty(len: usize) -> Self {
18        let words = len.div_ceil(64);
19        Self {
20            len,
21            bits: vec![0u64; words].into_boxed_slice(),
22        }
23    }
24
25    /// Create a full (all-set) set for `len` fields.
26    #[must_use]
27    pub fn all(len: usize) -> Self {
28        let mut s = Self::empty(len);
29        for idx in 0..len {
30            s.set(idx);
31        }
32        s
33    }
34
35    /// Number of fields represented by this set.
36    #[must_use]
37    pub const fn len(&self) -> usize {
38        self.len
39    }
40
41    /// True if `len == 0`.
42    #[must_use]
43    pub const fn is_empty(&self) -> bool {
44        self.len == 0
45    }
46
47    /// Mark a field index as set.
48    ///
49    /// Indices outside `0..len` are ignored (defensive for forward-compat).
50    pub fn set(&mut self, idx: usize) {
51        if idx >= self.len {
52            return;
53        }
54        let word = idx / 64;
55        let bit = idx % 64;
56        if let Some(w) = self.bits.get_mut(word) {
57            *w |= 1u64 << bit;
58        }
59    }
60
61    /// Check whether a field index is set.
62    #[must_use]
63    pub fn is_set(&self, idx: usize) -> bool {
64        if idx >= self.len {
65            return false;
66        }
67        let word = idx / 64;
68        let bit = idx % 64;
69        self.bits
70            .get(word)
71            .is_some_and(|w| (w & (1u64 << bit)) != 0)
72    }
73}