Skip to main content

drizzle_core/
schema.rs

1use crate::prelude::*;
2use crate::{ToSQL, sql::SQL, traits::SQLParam};
3use core::any::Any;
4
5#[cfg(feature = "std")]
6use std::collections::BTreeSet;
7
8/// Trait for database enum types that can be part of a schema
9pub trait SQLEnumInfo: Any + Send + Sync {
10    /// The name of this enum type
11    fn name(&self) -> &'static str;
12
13    /// The SQL CREATE TYPE statement for this enum
14    fn create_type_sql(&self) -> String;
15
16    /// All possible values of this enum
17    fn variants(&self) -> &'static [&'static str];
18}
19
20impl core::fmt::Debug for dyn SQLEnumInfo {
21    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
22        f.debug_struct("SQLEnumInfo")
23            .field("name", &self.name())
24            .field("variants", &self.variants())
25            .finish()
26    }
27}
28
29/// Sort direction for ORDER BY clauses
30#[derive(Clone, Copy, Debug, PartialEq, Eq)]
31pub enum OrderBy {
32    Asc,
33    Desc,
34}
35
36/// Creates an ascending ORDER BY expression: "column ASC"
37pub fn asc<'a, V, T>(column: T) -> SQL<'a, V>
38where
39    V: SQLParam + 'a,
40    T: ToSQL<'a, V>,
41{
42    column.to_sql().append(&OrderBy::Asc)
43}
44
45/// Creates a descending ORDER BY expression: "column DESC"
46pub fn desc<'a, V, T>(column: T) -> SQL<'a, V>
47where
48    V: SQLParam + 'a,
49    T: ToSQL<'a, V>,
50{
51    column.to_sql().append(&OrderBy::Desc)
52}
53
54/// Topological sort of `(name, dependency_names)` pairs using Kahn's algorithm.
55///
56/// Returns names in dependency order (dependencies before dependents).
57/// Uses `BTreeSet` for deterministic tie-breaking (lexicographic).
58///
59/// # Errors
60///
61/// Returns an error if a cycle is detected.
62#[cfg(feature = "std")]
63#[allow(dead_code)]
64pub(crate) fn topological_order<'a>(
65    items: impl IntoIterator<Item = (&'a str, &'a [&'a str])>,
66) -> crate::error::Result<Vec<&'a str>> {
67    let items: Vec<_> = items.into_iter().collect();
68    let name_set: HashMap<&str, usize> = items
69        .iter()
70        .enumerate()
71        .map(|(i, (name, _))| (*name, i))
72        .collect();
73
74    let n = items.len();
75    let mut indegree = vec![0usize; n];
76    let mut reverse_edges: Vec<Vec<usize>> = vec![Vec::new(); n];
77
78    for (i, (_name, deps)) in items.iter().enumerate() {
79        for dep in *deps {
80            if let Some(&j) = name_set.get(dep) {
81                indegree[i] += 1;
82                reverse_edges[j].push(i);
83            }
84        }
85    }
86
87    let mut queue: BTreeSet<(&str, usize)> = BTreeSet::new();
88    for (i, &deg) in indegree.iter().enumerate() {
89        if deg == 0 {
90            queue.insert((items[i].0, i));
91        }
92    }
93
94    let mut result = Vec::with_capacity(n);
95    while let Some(&entry) = queue.first() {
96        queue.remove(&entry);
97        let (_name, idx) = entry;
98        result.push(items[idx].0);
99
100        for &neighbor in &reverse_edges[idx] {
101            indegree[neighbor] -= 1;
102            if indegree[neighbor] == 0 {
103                queue.insert((items[neighbor].0, neighbor));
104            }
105        }
106    }
107
108    if result.len() != n {
109        return Err(crate::error::DrizzleError::Schema(
110            "cycle detected in table dependencies".into(),
111        ));
112    }
113
114    Ok(result)
115}
116
117impl<'a, V: SQLParam + 'a> ToSQL<'a, V> for OrderBy {
118    fn to_sql(&self) -> SQL<'a, V> {
119        let sql_str = match self {
120            Self::Asc => "ASC",
121            Self::Desc => "DESC",
122        };
123        SQL::raw(sql_str)
124    }
125}