microrm/
schema.rs

1//! Schema specification types.
2//!
3//! The following terminology used in some types in this module:
4//! - *domain*: one side of a relation, the "pointed-from" side for one-sided relations
5//! - *range*: one side of a relation, the "pointed-to" side for one-sided relations
6//! - *local*: the current side of a relation
7//! - *remote*: the opposite side of a relation
8
9use crate::{
10    db::{StatementContext, StatementRow, Transaction},
11    DBResult, Error,
12};
13
14use self::{
15    datum::{ConcreteDatum, Datum, DatumDiscriminator, DatumDiscriminatorRef},
16    entity::{Entity, EntityPartList},
17};
18
19/// Types related to datums, or struct fields.
20pub mod datum;
21/// Types related to entities, or structs that can be serialized into/deserialized from the
22/// database.
23pub mod entity;
24
25/// Types related to inter-entity relationships.
26pub mod relation;
27
28/// Types related to indexes.
29pub mod index;
30
31mod build;
32mod check;
33mod collect;
34pub(crate) mod meta;
35
36/// Types and functionality related to schema migration.
37pub mod migration;
38
39mod detail;
40
41// ----------------------------------------------------------------------
42// API types
43// ----------------------------------------------------------------------
44
45/// Wrapper struct that holds both an EntityID and an Entity itself.
46pub struct Stored<T: Entity> {
47    id: T::ID,
48    wrap: T,
49}
50
51impl<T: Entity> Stored<T> {
52    pub(crate) fn new(id: T::ID, value: T) -> Self {
53        Self { id, wrap: value }
54    }
55
56    /// Retrieve the entity ID of the stored entity.
57    pub fn id(&self) -> T::ID {
58        self.id
59    }
60
61    /// Consume the `Stored<>` wrapper and return the wrapped data.
62    pub fn wrapped(self) -> T {
63        self.wrap
64    }
65}
66
67impl<T: Entity + std::fmt::Debug> std::fmt::Debug for Stored<T> {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        f.write_fmt(format_args!(
70            "Stored {{ id: {:?}, value: {:?} }}",
71            self.id, self.wrap
72        ))
73    }
74}
75
76impl<T: Entity> AsRef<T> for Stored<T> {
77    fn as_ref(&self) -> &T {
78        &self.wrap
79    }
80}
81
82impl<T: Entity> AsMut<T> for Stored<T> {
83    fn as_mut(&mut self) -> &mut T {
84        &mut self.wrap
85    }
86}
87
88impl<T: Entity> std::ops::Deref for Stored<T> {
89    type Target = T;
90    fn deref(&self) -> &Self::Target {
91        &self.wrap
92    }
93}
94
95impl<T: Entity> std::ops::DerefMut for Stored<T> {
96    fn deref_mut(&mut self) -> &mut Self::Target {
97        &mut self.wrap
98    }
99}
100
101impl<T: Entity + Clone> Clone for Stored<T> {
102    fn clone(&self) -> Self {
103        Self {
104            id: self.id,
105            wrap: self.wrap.clone(),
106        }
107    }
108}
109
110impl<T: Entity> PartialEq for Stored<T> {
111    fn eq(&self, other: &Self) -> bool {
112        self.id == other.id
113    }
114}
115
116// ----------------------------------------------------------------------
117// Entity field types
118// ----------------------------------------------------------------------
119
120/// Stores an arbitrary Rust data type as serialized JSON in a string field.
121#[derive(Clone)]
122pub struct Serialized<T: serde::Serialize + serde::de::DeserializeOwned + Clone> {
123    wrapped: T,
124}
125
126impl<T: serde::Serialize + serde::de::DeserializeOwned + Clone> Serialized<T> {
127    /// Returns the object stored inside this representational wrapper.
128    pub fn wrapped(self) -> T {
129        self.wrapped
130    }
131}
132
133impl<T: serde::Serialize + serde::de::DeserializeOwned + Default + Clone> Default
134    for Serialized<T>
135{
136    fn default() -> Self {
137        Self {
138            wrapped: T::default(),
139        }
140    }
141}
142
143impl<T: serde::Serialize + serde::de::DeserializeOwned + std::fmt::Debug + Clone> std::fmt::Debug
144    for Serialized<T>
145{
146    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
147        <T as std::fmt::Debug>::fmt(&self.wrapped, f)
148    }
149}
150
151impl<T: serde::Serialize + serde::de::DeserializeOwned + Clone> From<T> for Serialized<T> {
152    fn from(value: T) -> Self {
153        Self { wrapped: value }
154    }
155}
156
157impl<T: serde::Serialize + serde::de::DeserializeOwned + Clone> AsRef<T> for Serialized<T> {
158    fn as_ref(&self) -> &T {
159        &self.wrapped
160    }
161}
162
163impl<T: serde::Serialize + serde::de::DeserializeOwned + Clone> AsMut<T> for Serialized<T> {
164    fn as_mut(&mut self) -> &mut T {
165        &mut self.wrapped
166    }
167}
168
169impl<T: serde::Serialize + serde::de::DeserializeOwned + Clone> std::ops::Deref for Serialized<T> {
170    type Target = T;
171    fn deref(&self) -> &Self::Target {
172        &self.wrapped
173    }
174}
175
176impl<T: serde::Serialize + serde::de::DeserializeOwned + Clone> std::ops::DerefMut
177    for Serialized<T>
178{
179    fn deref_mut(&mut self) -> &mut Self::Target {
180        &mut self.wrapped
181    }
182}
183
184impl<T: 'static + serde::Serialize + serde::de::DeserializeOwned + std::fmt::Debug + Clone> Datum
185    for Serialized<T>
186{
187    fn sql_type() -> &'static str {
188        "text"
189    }
190
191    fn bind_to(&self, stmt: &mut StatementContext, index: i32) {
192        let json = std::pin::Pin::new(
193            serde_json::to_string(&self.wrapped).expect("couldn't serialize object into JSON"),
194        );
195
196        <&str as Datum>::bind_to(&&*json.as_ref(), stmt, index);
197
198        // transfer ownership so that the data is still around for later
199        stmt.transfer(json);
200    }
201
202    fn build_from(
203        rdata: relation::RelationData,
204        stmt: &mut StatementRow,
205        index: &mut i32,
206    ) -> DBResult<Self>
207    where
208        Self: Sized,
209    {
210        let s = <String as Datum>::build_from(rdata, stmt, index)?;
211
212        let d = serde_json::from_str::<T>(s.as_str()).map_err(Error::JSON)?;
213
214        Ok(Self { wrapped: d })
215    }
216
217    fn accept_discriminator(d: &mut impl DatumDiscriminator)
218    where
219        Self: Sized,
220    {
221        d.visit_serialized::<T>();
222    }
223
224    fn accept_discriminator_ref(&self, d: &mut impl DatumDiscriminatorRef)
225    where
226        Self: Sized,
227    {
228        d.visit_serialized::<T>(&self.wrapped);
229    }
230}
231
232impl<T: 'static + serde::Serialize + serde::de::DeserializeOwned + std::fmt::Debug + Clone>
233    ConcreteDatum for Serialized<T>
234{
235}
236
237/// Helper trait to make working with [`Serialized`] fields a little bit nicer.
238pub trait Serializable:
239    serde::Serialize + serde::de::DeserializeOwned + std::fmt::Debug + Clone
240{
241    /// Wrap an eligible object into a [`Serialized`] version of itself.
242    fn into_serialized(self) -> Serialized<Self>
243    where
244        Self: Sized;
245}
246
247impl<T: 'static + serde::Serialize + serde::de::DeserializeOwned + std::fmt::Debug + Clone>
248    Serializable for T
249{
250    fn into_serialized(self) -> Serialized<Self>
251    where
252        Self: Sized,
253    {
254        Serialized { wrapped: self }
255    }
256}
257
258// ----------------------------------------------------------------------
259// Database specification types
260// ----------------------------------------------------------------------
261
262/// Table with EntityID-based lookup.
263pub struct IDMap<T: Entity> {
264    _ghost: std::marker::PhantomData<T>,
265}
266
267impl<T: Entity> Clone for IDMap<T> {
268    fn clone(&self) -> Self {
269        Self {
270            _ghost: Default::default(),
271        }
272    }
273}
274
275impl<E: Entity> DatabaseItem for IDMap<E> {
276    fn accept_item_visitor(visitor: &mut impl DatabaseItemVisitor) {
277        visitor.visit_idmap::<E>();
278    }
279
280    fn build(_: BuildSeal) -> Self
281    where
282        Self: Sized,
283    {
284        Self {
285            _ghost: std::marker::PhantomData,
286        }
287    }
288
289    type Subitems = ();
290}
291
292#[derive(Clone, Copy)]
293pub(crate) struct Sealed;
294
295/// Sealing type for [`DatabaseItem::build`]. Not constructible outside of `microrm`.
296#[derive(Clone, Copy)]
297pub struct BuildSeal(Sealed);
298impl BuildSeal {
299    pub(crate) fn new() -> Self {
300        Self(Sealed)
301    }
302}
303
304/// Represents a logical item in a database schema, be it an index, table, or logical grouping.
305pub trait DatabaseItem {
306    /// Accept an entity visitor for (local) entity discovery.
307    fn accept_item_visitor(visitor: &mut impl DatabaseItemVisitor);
308
309    /// Build an instance of this DatabaseItem.
310    fn build(_: BuildSeal) -> Self
311    where
312        Self: Sized;
313
314    /// Ordered list of DatabaseItems that are direct children of this item.
315    type Subitems: DatabaseItemList;
316}
317
318/// A special sentinel DatabaseItem for DatabaseItemList.
319#[derive(Default, Debug, Clone, Copy)]
320pub struct SentinelDatabaseItem;
321impl DatabaseItem for SentinelDatabaseItem {
322    fn accept_item_visitor(_visitor: &mut impl DatabaseItemVisitor) {}
323
324    fn build(_: BuildSeal) -> Self
325    where
326        Self: Sized,
327    {
328        Self
329    }
330
331    type Subitems = ();
332}
333
334/// Representation of a list of DatabaseItems.
335pub trait DatabaseItemList {
336    /// Head of the list.
337    type Head: DatabaseItem;
338    /// The rest of the list.
339    type Tail: DatabaseItemList;
340    /// Whether this is an empty list or not.
341    const EMPTY: bool = false;
342}
343
344impl DatabaseItemList for () {
345    type Head = SentinelDatabaseItem;
346    type Tail = ();
347    const EMPTY: bool = true;
348}
349
350impl<DI0: DatabaseItem> DatabaseItemList for (DI0,) {
351    type Head = DI0;
352    type Tail = ();
353}
354
355/// Visitor trait for iterating across the types in a [`Schema`] tree.
356pub trait DatabaseItemVisitor {
357    /// Visit an `IDMap<T>` type.
358    fn visit_idmap<T: Entity>(&mut self)
359    where
360        Self: Sized;
361    /// Visit an `Index<T, PL>` type.
362    fn visit_index<T: Entity, PL: EntityPartList<Entity = T>>(&mut self)
363    where
364        Self: Sized;
365}
366
367/// A root structure for the database specification graph.
368pub trait Schema: 'static + DatabaseItem {
369    /// Install this schema into a database
370    fn install(&self, txn: &mut Transaction) -> DBResult<()>
371    where
372        Self: Sized,
373    {
374        let schema = build::generate_from_schema::<Self>();
375        match schema.check(txn) {
376            // schema checks out
377            Some(true) => {},
378            // schema doesn't match
379            Some(false) => Err(Error::IncompatibleSchema)?,
380            // no schema found
381            None => {
382                schema.create(txn)?;
383            },
384        }
385        Ok(())
386    }
387
388    /// Create a clone of the current schema object.
389    ///
390    /// More generally, [`DatabaseItem`] is not Clone, but [`Schema`] must be, so the typical
391    /// implementation does not work here. But since `build` is a sealed method, the implementation
392    /// must be in the `microrm` crate; since `Clone` is a foreign trait it cannot be implemented
393    /// on all `Schema` using Rust's current type system.
394    fn clone(&self) -> Self
395    where
396        Self: Sized,
397    {
398        Self::build(BuildSeal::new())
399    }
400
401    /// The printed name used to represent this schema if it is ever mentioned in e.g. logs.
402    const NAME: Option<&'static str> = None;
403}
404
405impl crate::ConnectionPool {
406    /// Open a SQLite connection to a given URI, using a given schema.
407    pub fn open<S: Schema>(
408        config: impl Into<crate::db::ConnectionPoolConfig>,
409    ) -> DBResult<(Self, S)> {
410        let pool = Self::new(config)?;
411        let schema = S::build(BuildSeal::new());
412
413        let mut txn = pool.start()?;
414        schema.install(&mut txn)?;
415        txn.commit()?;
416
417        Ok((pool, schema))
418    }
419}