ic_dbms_api/dbms/
query.rs

1//! This module exposes all the types related to queries that can be performed on the DBMS.
2
3mod builder;
4mod delete;
5mod filter;
6
7use std::marker::PhantomData;
8
9use thiserror::Error;
10
11pub use self::builder::QueryBuilder;
12pub use self::delete::DeleteBehavior;
13pub use self::filter::Filter;
14use crate::dbms::table::TableSchema;
15use crate::dbms::value::Value;
16use crate::memory::MemoryError;
17
18/// The result type for query operations.
19pub type QueryResult<T> = Result<T, QueryError>;
20
21/// An enum representing possible errors that can occur during query operations.
22#[derive(Debug, Error)]
23pub enum QueryError {
24    /// The specified primary key value already exists in the table.
25    #[error("Primary key conflict: record with the same primary key already exists")]
26    PrimaryKeyConflict,
27
28    /// A foreign key references a non-existent record in another table.
29    #[error("Broken foreign key reference to table '{table}' with key '{key:?}'")]
30    BrokenForeignKeyReference { table: &'static str, key: Value },
31
32    /// Tried to delete or update a record that is referenced by another table's foreign key.
33    #[error("Foreign key constraint violation on table '{referencing_table}' for field '{field}'")]
34    ForeignKeyConstraintViolation {
35        referencing_table: &'static str,
36        field: &'static str,
37    },
38
39    /// Tried to reference a column that does not exist in the table schema.
40    #[error("Unknown column: {0}")]
41    UnknownColumn(String),
42
43    /// Tried to insert a record missing non-nullable fields.
44    #[error("Missing non-nullable field: {0}")]
45    MissingNonNullableField(&'static str),
46
47    /// Tried to cast or compare values of incompatible types (e.g. Integer vs Text).
48    #[error("Type mismatch: expected {expected}, found {found}")]
49    TypeMismatch {
50        column: &'static str,
51        expected: &'static str,
52        found: &'static str,
53    },
54
55    /// The specified transaction was not found or has expired.
56    #[error("transaction not found")]
57    TransactionNotFound,
58
59    /// Query contains syntactically or semantically invalid conditions.
60    #[error("Invalid query: {0}")]
61    InvalidQuery(String),
62
63    /// Generic constraint violation (e.g., UNIQUE, CHECK, etc.)
64    #[error("Constraint violation: {0}")]
65    ConstraintViolation(String),
66
67    /// The memory allocator or memory manager failed to allocate or access stable memory.
68    #[error("Memory error: {0}")]
69    MemoryError(MemoryError),
70
71    /// The table or schema was not found.
72    #[error("Table not found: {0}")]
73    TableNotFound(&'static str),
74
75    /// The record identified by the given key or filter does not exist.
76    #[error("Record not found")]
77    RecordNotFound,
78
79    /// Any low-level IO or serialization/deserialization issue.
80    #[error("Serialization error: {0}")]
81    SerializationError(String),
82
83    /// Generic catch-all error (for internal, unexpected conditions).
84    #[error("Internal error: {0}")]
85    Internal(String),
86}
87
88/// An enum representing the fields to select in a query.
89#[derive(Debug, Default, Clone, PartialEq, Eq)]
90pub enum Select {
91    #[default]
92    All,
93    Columns(Vec<&'static str>),
94}
95
96/// An enum representing the direction of ordering in a query.
97#[derive(Debug, Clone, PartialEq, Eq)]
98pub enum OrderDirection {
99    Ascending,
100    Descending,
101}
102
103/// A struct representing a query in the DBMS.
104#[derive(Debug, Clone, PartialEq, Eq)]
105pub struct Query<T>
106where
107    T: TableSchema,
108{
109    /// Fields to select in the query.
110    columns: Select,
111    /// Relations to eagerly load with the main records.
112    pub eager_relations: Vec<&'static str>,
113    /// [`Filter`] to apply to the query.
114    pub filter: Option<Filter>,
115    /// Order by clauses for sorting the results.
116    pub order_by: Vec<(&'static str, OrderDirection)>,
117    /// Limit on the number of records to return.
118    pub limit: Option<usize>,
119    /// Offset for pagination.
120    pub offset: Option<usize>,
121    /// Marker for the table schema type.
122    _marker: PhantomData<T>,
123}
124
125impl<T> Default for Query<T>
126where
127    T: TableSchema,
128{
129    fn default() -> Self {
130        Self {
131            columns: Select::All,
132            eager_relations: Vec::new(),
133            filter: None,
134            order_by: Vec::new(),
135            limit: None,
136            offset: None,
137            _marker: PhantomData,
138        }
139    }
140}
141
142impl<T> Query<T>
143where
144    T: TableSchema,
145{
146    /// Creates a new [`QueryBuilder`] for building a query.
147    pub fn builder() -> QueryBuilder<T> {
148        QueryBuilder::default()
149    }
150
151    /// Returns whether all columns are selected in the query.
152    pub fn all_selected(&self) -> bool {
153        matches!(self.columns, Select::All)
154    }
155
156    /// Returns the list of columns to be selected in the query.
157    pub fn columns(&self) -> Vec<&'static str> {
158        match &self.columns {
159            Select::All => T::columns().iter().map(|col| col.name).collect(),
160            Select::Columns(cols) => cols.clone(),
161        }
162    }
163}
164
165#[cfg(test)]
166mod tests {
167
168    use super::*;
169    use crate::tests::User;
170
171    #[test]
172    fn test_should_build_default_query() {
173        let query: Query<User> = Query::default();
174        assert!(matches!(query.columns, Select::All));
175        assert!(query.eager_relations.is_empty());
176        assert!(query.filter.is_none());
177        assert!(query.order_by.is_empty());
178        assert!(query.limit.is_none());
179        assert!(query.offset.is_none());
180    }
181
182    #[test]
183    fn test_should_get_columns() {
184        let query = Query::<User>::default();
185        let columns = query.columns();
186        assert_eq!(columns, vec!["id", "name",]);
187
188        let query = Query::<User> {
189            columns: Select::Columns(vec!["id"]),
190            ..Default::default()
191        };
192
193        let columns = query.columns();
194        assert_eq!(columns, vec!["id"]);
195    }
196
197    #[test]
198    fn test_should_check_all_selected() {
199        let query = Query::<User>::default();
200        assert!(query.all_selected());
201    }
202}