egglog_core_relations/base_values/
mod.rs

1//! Mechanisms for declaring types of base values and functions on them.
2
3use std::{
4    any::{Any, TypeId},
5    fmt::{self, Debug},
6    hash::Hash,
7};
8
9use crate::numeric_id::{DenseIdMap, NumericId, define_id};
10
11use crate::common::{HashMap, InternTable, Value};
12
13#[cfg(test)]
14mod tests;
15mod unboxed;
16
17define_id!(pub BaseValueId, u32, "an identifier for base value types");
18
19/// A simple data type that can be interned in a database.
20///
21/// Most callers can simply implement this trait on their desired type, with no overrides needed.
22/// For types that are particularly small, users can override [`BaseValue::try_box`] and [`BaseValue::try_unbox`]
23/// methods and set [`BaseValue::MAY_UNBOX`] to `true` to allow the Rust value to be stored in-place in a
24/// [`Value`].
25///
26/// Regardless, all base value types should be registered in a [`BaseValues`] instance using the
27/// [`BaseValues::register_type`] method before they can be used in the database.
28pub trait BaseValue: Clone + Hash + Eq + Any + Debug + Send + Sync {
29    const MAY_UNBOX: bool = false;
30    fn intern(&self, table: &InternTable<Self, Value>) -> Value {
31        table.intern(self)
32    }
33    fn as_any(&self) -> &dyn Any {
34        self
35    }
36    fn try_box(&self) -> Option<Value> {
37        None
38    }
39    fn try_unbox(_val: Value) -> Option<Self> {
40        None
41    }
42}
43
44impl BaseValue for String {}
45impl BaseValue for &'static str {}
46impl BaseValue for num::Rational64 {}
47
48/// A wrapper used to print a base value.
49///
50/// The given base value type must be registered with the [`BaseValues`] instance,
51/// otherwise attempting to call the [`Debug`] implementation will panic.
52pub struct BaseValuePrinter<'a> {
53    pub base: &'a BaseValues,
54    pub ty: BaseValueId,
55    pub val: Value,
56}
57
58impl Debug for BaseValuePrinter<'_> {
59    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
60        self.base.tables[self.ty].print_value(self.val, f)
61    }
62}
63
64/// A registry for base value types and functions on them.
65#[derive(Clone, Default)]
66pub struct BaseValues {
67    type_ids: HashMap<TypeId, BaseValueId>,
68    tables: DenseIdMap<BaseValueId, Box<dyn DynamicInternTable>>,
69}
70
71impl BaseValues {
72    /// Register the given type `P` as a base value type in this registry.
73    pub fn register_type<P: BaseValue>(&mut self) -> BaseValueId {
74        let type_id = TypeId::of::<P>();
75        let next_id = BaseValueId::from_usize(self.type_ids.len());
76        let id = *self.type_ids.entry(type_id).or_insert(next_id);
77        self.tables
78            .get_or_insert(id, || Box::<BaseInternTable<P>>::default());
79        id
80    }
81
82    /// Get the [`BaseValueId`] for the given base value type `P`.
83    pub fn get_ty<P: BaseValue>(&self) -> BaseValueId {
84        self.type_ids[&TypeId::of::<P>()]
85    }
86
87    /// Get the [`BaseValueId`] for the given base value type id.
88    pub fn get_ty_by_id(&self, id: TypeId) -> BaseValueId {
89        self.type_ids[&id]
90    }
91
92    /// Get a [`Value`] representing the given base value `p`.
93    pub fn get<P: BaseValue>(&self, p: P) -> Value {
94        if P::MAY_UNBOX {
95            if let Some(v) = p.try_box() {
96                return v;
97            }
98        }
99        let id = self.get_ty::<P>();
100        let table = self.tables[id]
101            .as_any()
102            .downcast_ref::<BaseInternTable<P>>()
103            .unwrap();
104        table.intern(p)
105    }
106
107    /// Get the base value of type `P` corresponding to the given [`Value`].
108    pub fn unwrap<P: BaseValue>(&self, v: Value) -> P {
109        if P::MAY_UNBOX {
110            if let Some(p) = P::try_unbox(v) {
111                return p;
112            }
113        }
114        let id = self.get_ty::<P>();
115        let table = self
116            .tables
117            .get(id)
118            .expect("types must be registered before unwrapping")
119            .as_any()
120            .downcast_ref::<BaseInternTable<P>>()
121            .unwrap();
122        table.get(v)
123    }
124}
125
126trait DynamicInternTable: Any + dyn_clone::DynClone + Send + Sync {
127    fn as_any(&self) -> &dyn Any;
128    fn print_value(&self, val: Value, f: &mut fmt::Formatter) -> fmt::Result;
129}
130
131// Implements `Clone` for `Box<dyn DynamicInternTable>`.
132dyn_clone::clone_trait_object!(DynamicInternTable);
133
134#[derive(Clone)]
135struct BaseInternTable<P> {
136    table: InternTable<P, Value>,
137}
138
139impl<P> Default for BaseInternTable<P> {
140    fn default() -> Self {
141        Self {
142            table: InternTable::default(),
143        }
144    }
145}
146
147impl<P: BaseValue> DynamicInternTable for BaseInternTable<P> {
148    fn as_any(&self) -> &dyn Any {
149        self
150    }
151
152    fn print_value(&self, val: Value, f: &mut fmt::Formatter) -> fmt::Result {
153        let p = self.get(val);
154        write!(f, "{p:?}")
155    }
156}
157
158const VAL_OFFSET: u32 = 1 << (std::mem::size_of::<Value>() as u32 * 8 - 1);
159
160impl<P: BaseValue> BaseInternTable<P> {
161    pub fn intern(&self, p: P) -> Value {
162        if P::MAY_UNBOX {
163            p.try_box().unwrap_or_else(|| {
164                // If the base value type is too large to fit in a Value, we intern it and return
165                // the corresponding Value with its top bit set. We use add to ensure we overflow
166                // if the number of interned values is too large.
167                Value::new(
168                    self.table
169                        .intern(&p)
170                        .rep()
171                        .checked_add(VAL_OFFSET)
172                        .expect("interned value overflowed"),
173                )
174            })
175        } else {
176            self.table.intern(&p)
177        }
178    }
179
180    pub fn get(&self, v: Value) -> P {
181        if P::MAY_UNBOX {
182            P::try_unbox(v)
183                .unwrap_or_else(|| self.table.get_cloned(Value::new(v.rep() - VAL_OFFSET)))
184        } else {
185            self.table.get_cloned(v)
186        }
187    }
188}
189
190/// A newtype wrapper used to implement the [`BaseValue`] trait on types not
191/// defined in this crate.
192///
193/// This type is just a helper: users can also implement the [`BaseValue`] trait directly on their
194/// types if the type is defined in the crate in which the implementation is defined, or if they
195/// need custom logic for boxing or unboxing the type.
196#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
197pub struct Boxed<T>(pub T);
198
199impl<T> Boxed<T> {
200    pub fn new(value: T) -> Self {
201        Boxed(value)
202    }
203
204    pub fn into_inner(self) -> T {
205        self.0
206    }
207}
208
209impl<T: Debug> Debug for Boxed<T> {
210    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
211        write!(f, "{:?}", self.0)
212    }
213}
214
215impl<T: Hash + Eq + Debug + Clone + Send + Sync + 'static> BaseValue for Boxed<T> {}
216
217impl<T> std::ops::Deref for Boxed<T> {
218    type Target = T;
219
220    fn deref(&self) -> &Self::Target {
221        &self.0
222    }
223}
224
225impl<T> std::ops::DerefMut for Boxed<T> {
226    fn deref_mut(&mut self) -> &mut Self::Target {
227        &mut self.0
228    }
229}
230
231impl<T> From<T> for Boxed<T> {
232    fn from(value: T) -> Self {
233        Boxed(value)
234    }
235}
236
237impl<T: Copy> From<&T> for Boxed<T> {
238    fn from(value: &T) -> Self {
239        Boxed(*value)
240    }
241}
242
243impl<T: std::ops::Add<Output = T>> std::ops::Add for Boxed<T> {
244    type Output = Self;
245
246    fn add(self, other: Self) -> Self::Output {
247        Boxed(self.0 + other.0)
248    }
249}
250
251impl<T: std::ops::Sub<Output = T>> std::ops::Sub for Boxed<T> {
252    type Output = Self;
253
254    fn sub(self, other: Self) -> Self::Output {
255        Boxed(self.0 - other.0)
256    }
257}
258
259impl<T: std::ops::Mul<Output = T>> std::ops::Mul for Boxed<T> {
260    type Output = Self;
261
262    fn mul(self, other: Self) -> Self::Output {
263        Boxed(self.0 * other.0)
264    }
265}
266
267impl<T: std::ops::Div<Output = T>> std::ops::Div for Boxed<T> {
268    type Output = Self;
269
270    fn div(self, other: Self) -> Self::Output {
271        Boxed(self.0 / other.0)
272    }
273}
274
275impl<T: std::ops::Rem<Output = T>> std::ops::Rem for Boxed<T> {
276    type Output = Self;
277
278    fn rem(self, other: Self) -> Self::Output {
279        Boxed(self.0 % other.0)
280    }
281}
282
283impl<T: std::ops::Neg<Output = T>> std::ops::Neg for Boxed<T> {
284    type Output = Self;
285
286    fn neg(self) -> Self::Output {
287        Boxed(-self.0)
288    }
289}
290
291impl<T: std::ops::BitAnd<Output = T>> std::ops::BitAnd for Boxed<T> {
292    type Output = Self;
293
294    fn bitand(self, other: Self) -> Self::Output {
295        Boxed(self.0 & other.0)
296    }
297}
298
299impl<T: std::ops::BitOr<Output = T>> std::ops::BitOr for Boxed<T> {
300    type Output = Self;
301
302    fn bitor(self, other: Self) -> Self::Output {
303        Boxed(self.0 | other.0)
304    }
305}
306
307impl<T: std::ops::BitXor<Output = T>> std::ops::BitXor for Boxed<T> {
308    type Output = Self;
309
310    fn bitxor(self, other: Self) -> Self::Output {
311        Boxed(self.0 ^ other.0)
312    }
313}