Skip to main content

cartel_sqlite/engine/
catalogs.rs

1use std::marker;
2
3/// One SQL program in a connection's compile-time statement catalog.
4#[derive(Clone, Copy)]
5pub struct Entry<'sql> {
6    sql: &'sql str,
7    parameters: usize,
8    columns: usize,
9}
10
11impl<'sql> Entry<'sql> {
12    #[doc(hidden)]
13    pub const fn of<Q: TypedQuery>() -> Self {
14        Self {
15            sql: Q::SQL,
16            parameters: Q::PARAMETERS,
17            columns: Q::COLUMNS,
18        }
19    }
20
21    pub const fn sql(self) -> &'sql str {
22        self.sql
23    }
24
25    pub const fn parameters(self) -> usize {
26        self.parameters
27    }
28
29    pub const fn columns(self) -> usize {
30        self.columns
31    }
32}
33
34/// Closed SQL program set accepted by a database.
35pub trait Catalog {
36    const STATEMENTS: &[Entry<'_>];
37}
38
39/// Exact static SQL shape of a catalog.
40#[derive(Clone, Copy, PartialEq, Eq)]
41pub struct Shape {
42    pub statements: usize,
43    pub max_sql_bytes: usize,
44}
45
46impl Shape {
47    pub const fn of<I: Catalog>() -> Self {
48        let entries = I::STATEMENTS;
49        let mut stmt: usize = 0;
50        let mut max_sql: usize = 0;
51        while stmt < entries.len() {
52            let bytes = entries[stmt].sql().len();
53            if max_sql < bytes {
54                max_sql = bytes;
55            }
56            stmt += 1;
57        }
58        Self {
59            statements: entries.len(),
60            max_sql_bytes: max_sql,
61        }
62    }
63}
64
65pub(crate) fn validate<I: Catalog>() -> Result<(), crate::Error> {
66    for (stmt, entry) in I::STATEMENTS.iter().enumerate() {
67        let sql = entry.sql();
68        if sql.trim().is_empty() || sql.as_bytes().contains(&0) {
69            return Err(crate::Error::CatalogStatementSql { statement: stmt });
70        }
71    }
72    Ok(())
73}
74
75/// Empty catalog used by connections that execute only transaction controls.
76pub struct Empty;
77
78impl Catalog for Empty {
79    const STATEMENTS: &[Entry<'_>] = &[];
80}
81
82#[doc(hidden)]
83pub trait TypedQuery {
84    const SQL: &str;
85    const PARAMETERS: usize;
86    const COLUMNS: usize;
87}
88
89#[doc(hidden)]
90pub trait HasQuery<Q: TypedQuery>: Catalog {
91    const SLOT: usize;
92}
93
94/// Dense, catalog-branded statement selector.
95#[doc(hidden)]
96pub struct Statement<I: Catalog, Q: TypedQuery> {
97    marker: marker::PhantomData<(I, Q)>,
98}
99
100enum Mismatch {
101    Slot,
102    Sql,
103    Parameters,
104    Columns,
105}
106
107impl<I: HasQuery<Q>, Q: TypedQuery> Statement<I, Q> {
108    /// Admits the exact catalog entry; invalid mappings fail compilation.
109    #[doc(hidden)]
110    pub const fn of() -> Self {
111        const {
112            match Self::try_new() {
113                Ok(stmt) => stmt,
114                Err(_) => panic!("statement"),
115            }
116        }
117    }
118
119    const fn try_new() -> Result<Self, Mismatch> {
120        let slot = <I as HasQuery<Q>>::SLOT;
121        if slot >= I::STATEMENTS.len() {
122            return Err(Mismatch::Slot);
123        }
124        let entry = I::STATEMENTS[slot];
125        if !crate::sql::raw::Parts::new(&[entry.sql]).matches(Q::SQL) {
126            return Err(Mismatch::Sql);
127        }
128        if entry.parameters != Q::PARAMETERS {
129            return Err(Mismatch::Parameters);
130        }
131        if entry.columns != Q::COLUMNS {
132            return Err(Mismatch::Columns);
133        }
134        Ok(Self {
135            marker: marker::PhantomData,
136        })
137    }
138
139    pub(crate) const fn slot(self) -> usize {
140        <I as HasQuery<Q>>::SLOT
141    }
142}
143
144impl<I: Catalog, Q: TypedQuery> Copy for Statement<I, Q> {}
145
146impl<I: Catalog, Q: TypedQuery> Clone for Statement<I, Q> {
147    fn clone(&self) -> Self {
148        *self
149    }
150}