drizzle_core/schema.rs
1//! Schema metadata for database enums, and `ORDER BY` terms.
2//!
3//! [`asc`] and [`desc`] (re-exported at the crate root) build the [`Ordered`]
4//! terms that `order_by` and window [`order_by`](crate::expr::WindowSpec::order_by)
5//! take. [`SQLEnumInfo`] is implemented by `#[PostgresEnum]`.
6
7use crate::prelude::*;
8use crate::{ToSQL, sql::SQL, traits::SQLParam};
9use core::any::Any;
10
11#[cfg(feature = "std")]
12use std::collections::BTreeSet;
13
14/// Schema metadata for a database enum type, such as a PostgreSQL
15/// `CREATE TYPE ... AS ENUM`.
16///
17/// Implemented by `#[PostgresEnum]`.
18pub trait SQLEnumInfo: Any + Send + Sync {
19 /// The type name in the database.
20 fn name(&self) -> &'static str;
21
22 /// The `CREATE TYPE` statement for this enum.
23 fn create_type_sql(&self) -> String;
24
25 /// Every value of the enum, in declaration order.
26 fn variants(&self) -> &'static [&'static str];
27}
28
29impl core::fmt::Debug for dyn SQLEnumInfo {
30 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
31 f.debug_struct("SQLEnumInfo")
32 .field("name", &self.name())
33 .field("variants", &self.variants())
34 .finish()
35 }
36}
37
38/// Sort direction of an `ORDER BY` term.
39///
40/// Usually written through [`asc`] and [`desc`].
41#[derive(Clone, Copy, Debug, PartialEq, Eq)]
42pub enum OrderBy {
43 /// Ascending order (`ASC`): smallest first.
44 Asc,
45 /// Descending order (`DESC`): largest first.
46 Desc,
47}
48
49/// One `ORDER BY` term, such as `"users"."age" DESC`; created by [`asc`] or
50/// [`desc`].
51///
52/// `S` records the tables the term reads, for the query's scope check. To
53/// order by several columns, pass a tuple of terms:
54/// `order_by((asc(a), desc(b)))`. An array or `Vec` also works when every
55/// term has the same type.
56#[derive(Debug, Clone)]
57pub struct Ordered<'a, V: SQLParam, S> {
58 sql: SQL<'a, V>,
59 sources: core::marker::PhantomData<fn() -> S>,
60}
61
62impl<'a, V: SQLParam, S> Ordered<'a, V, S> {
63 /// Forgets which tables this term reads (see
64 /// [`SQLExpr::unscoped`](crate::expr::SQLExpr::unscoped)).
65 #[must_use]
66 pub fn unscoped(self) -> Ordered<'a, V, ()> {
67 Ordered {
68 sql: self.sql,
69 sources: core::marker::PhantomData,
70 }
71 }
72}
73
74impl<'a, V: SQLParam, S> ToSQL<'a, V> for Ordered<'a, V, S> {
75 fn to_sql(&self) -> SQL<'a, V> {
76 self.sql.clone()
77 }
78
79 fn into_sql(self) -> SQL<'a, V> {
80 self.sql
81 }
82}
83
84impl<V: SQLParam, S> crate::expr::ExprSources for Ordered<'_, V, S> {
85 type Sources = S;
86}
87
88/// A value that can be one `ORDER BY` term: an [`Ordered`] or raw [`SQL`].
89///
90/// Arrays and `Vec`s of one term type read that type's sources.
91pub trait OrderTerm: crate::expr::ExprSources {}
92
93impl<V: SQLParam, S> OrderTerm for Ordered<'_, V, S> {}
94
95impl<V: SQLParam> OrderTerm for SQL<'_, V> {}
96
97impl<T: OrderTerm, const N: usize> crate::expr::ExprSources for [T; N] {
98 type Sources = T::Sources;
99}
100
101impl<T: OrderTerm> crate::expr::ExprSources for Vec<T> {
102 type Sources = T::Sources;
103}
104
105/// Sorts by an expression in ascending order (`expr ASC`).
106///
107/// Accepts a column or any other expression and returns an [`Ordered`] term
108/// for `order_by`.
109///
110/// # Examples
111///
112/// ```rust
113/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
114/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
115/// # #[derive(Clone, Debug)] struct Value(String);
116/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
117/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
118/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
119/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
120/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
121/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
122/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
123/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
124/// use drizzle_core::{asc, desc};
125/// # use drizzle_core::ToSQL;
126///
127/// assert_eq!(asc(users.age).to_sql().sql(), r#""users"."age" ASC"#);
128///
129/// // Several terms go in a tuple.
130/// let terms = (desc(users.score), asc(users.name));
131/// assert_eq!(terms.to_sql().sql(), r#""users"."score" DESC, "users"."name" ASC"#);
132/// ```
133pub fn asc<'a, V, T>(column: T) -> Ordered<'a, V, T::Sources>
134where
135 V: SQLParam + 'a,
136 T: ToSQL<'a, V> + crate::expr::ExprSources,
137{
138 Ordered {
139 sql: column.to_sql().append(&OrderBy::Asc),
140 sources: core::marker::PhantomData,
141 }
142}
143
144/// Sorts by an expression in descending order (`expr DESC`).
145///
146/// Accepts a column or any other expression and returns an [`Ordered`] term
147/// for `order_by`.
148///
149/// # Examples
150///
151/// ```rust
152/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
153/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
154/// # #[derive(Clone, Debug)] struct Value(String);
155/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
156/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
157/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
158/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
159/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
160/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
161/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
162/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
163/// use drizzle_core::{asc, desc};
164/// # use drizzle_core::ToSQL;
165///
166/// assert_eq!(desc(users.age).to_sql().sql(), r#""users"."age" DESC"#);
167///
168/// // Several terms go in a tuple.
169/// let terms = (desc(users.score), asc(users.name));
170/// assert_eq!(terms.to_sql().sql(), r#""users"."score" DESC, "users"."name" ASC"#);
171/// ```
172pub fn desc<'a, V, T>(column: T) -> Ordered<'a, V, T::Sources>
173where
174 V: SQLParam + 'a,
175 T: ToSQL<'a, V> + crate::expr::ExprSources,
176{
177 Ordered {
178 sql: column.to_sql().append(&OrderBy::Desc),
179 sources: core::marker::PhantomData,
180 }
181}
182
183/// Topological sort of `(name, dependency_names)` pairs using Kahn's algorithm.
184///
185/// Returns names in dependency order (dependencies before dependents).
186/// Uses `BTreeSet` for deterministic tie-breaking (lexicographic).
187///
188/// # Errors
189///
190/// Returns an error if a cycle is detected.
191#[cfg(feature = "std")]
192#[allow(dead_code)]
193pub(crate) fn topological_order<'a>(
194 items: impl IntoIterator<Item = (&'a str, &'a [&'a str])>,
195) -> crate::error::Result<Vec<&'a str>> {
196 let items: Vec<_> = items.into_iter().collect();
197 let name_set: HashMap<&str, usize> = items
198 .iter()
199 .enumerate()
200 .map(|(i, (name, _))| (*name, i))
201 .collect();
202
203 let n = items.len();
204 let mut indegree = vec![0usize; n];
205 let mut reverse_edges: Vec<Vec<usize>> = vec![Vec::new(); n];
206
207 for (i, (_name, deps)) in items.iter().enumerate() {
208 for dep in *deps {
209 if let Some(&j) = name_set.get(dep) {
210 indegree[i] += 1;
211 reverse_edges[j].push(i);
212 }
213 }
214 }
215
216 let mut queue: BTreeSet<(&str, usize)> = BTreeSet::new();
217 for (i, °) in indegree.iter().enumerate() {
218 if deg == 0 {
219 queue.insert((items[i].0, i));
220 }
221 }
222
223 let mut result = Vec::with_capacity(n);
224 while let Some(&entry) = queue.first() {
225 queue.remove(&entry);
226 let (_name, idx) = entry;
227 result.push(items[idx].0);
228
229 for &neighbor in &reverse_edges[idx] {
230 indegree[neighbor] -= 1;
231 if indegree[neighbor] == 0 {
232 queue.insert((items[neighbor].0, neighbor));
233 }
234 }
235 }
236
237 if result.len() != n {
238 return Err(crate::error::DrizzleError::Schema(
239 "cycle detected in table dependencies".into(),
240 ));
241 }
242
243 Ok(result)
244}
245
246/// Renders `ASC` or `DESC`.
247impl<'a, V: SQLParam + 'a> ToSQL<'a, V> for OrderBy {
248 fn to_sql(&self) -> SQL<'a, V> {
249 let sql_str = match self {
250 Self::Asc => "ASC",
251 Self::Desc => "DESC",
252 };
253 SQL::raw(sql_str)
254 }
255}