ic_dbms_api/dbms/
query.rs1mod builder;
4mod delete;
5mod filter;
6
7use std::marker::PhantomData;
8
9use candid::CandidType;
10use candid::types::{Compound, Type, TypeInner};
11use serde::{Deserialize, Serialize};
12use thiserror::Error;
13
14pub use self::builder::QueryBuilder;
15pub use self::delete::DeleteBehavior;
16pub use self::filter::Filter;
17use crate::dbms::table::TableSchema;
18use crate::dbms::value::Value;
19use crate::memory::MemoryError;
20
21pub type QueryResult<T> = Result<T, QueryError>;
23
24#[derive(Debug, Error, CandidType, Serialize, Deserialize)]
26pub enum QueryError {
27 #[error("Primary key conflict: record with the same primary key already exists")]
29 PrimaryKeyConflict,
30
31 #[error("Broken foreign key reference to table '{table}' with key '{key:?}'")]
33 BrokenForeignKeyReference { table: String, key: Value },
34
35 #[error("Foreign key constraint violation on table '{referencing_table}' for field '{field}'")]
37 ForeignKeyConstraintViolation {
38 referencing_table: String,
39 field: String,
40 },
41
42 #[error("Unknown column: {0}")]
44 UnknownColumn(String),
45
46 #[error("Missing non-nullable field: {0}")]
48 MissingNonNullableField(String),
49
50 #[error("transaction not found")]
52 TransactionNotFound,
53
54 #[error("Invalid query: {0}")]
56 InvalidQuery(String),
57
58 #[error("Constraint violation: {0}")]
60 ConstraintViolation(String),
61
62 #[error("Memory error: {0}")]
64 MemoryError(MemoryError),
65
66 #[error("Table not found: {0}")]
68 TableNotFound(String),
69
70 #[error("Record not found")]
72 RecordNotFound,
73
74 #[error("Serialization error: {0}")]
76 SerializationError(String),
77
78 #[error("Internal error: {0}")]
80 Internal(String),
81}
82
83#[derive(Debug, Default, Clone, PartialEq, Eq, CandidType, Serialize, Deserialize)]
85pub enum Select {
86 #[default]
87 All,
88 Columns(Vec<String>),
89}
90
91#[derive(Debug, Clone, PartialEq, Eq, CandidType, Serialize, Deserialize)]
93pub enum OrderDirection {
94 Ascending,
95 Descending,
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
100pub struct Query<T>
101where
102 T: TableSchema,
103{
104 columns: Select,
106 pub eager_relations: Vec<String>,
108 pub filter: Option<Filter>,
110 pub order_by: Vec<(String, OrderDirection)>,
112 pub limit: Option<usize>,
114 pub offset: Option<usize>,
116 #[serde(skip)]
118 _marker: PhantomData<T>,
119}
120
121impl<T: TableSchema> CandidType for Query<T> {
122 fn _ty() -> Type {
123 let mut fields = vec![
124 candid::field! { columns: Select::_ty() },
125 candid::field! { eager_relations: <Vec<String>>::_ty() },
126 candid::field! { filter: <Option<Filter>>::_ty() },
127 candid::field! { order_by: <Vec<(String, OrderDirection)>>::_ty() },
128 candid::field! { limit: <Option<usize>>::_ty() },
129 candid::field! { offset: <Option<usize>>::_ty() },
130 ];
132
133 fields.sort_by_key(|f| f.id.clone());
134 TypeInner::Record(fields).into()
135 }
136
137 fn idl_serialize<S>(&self, serializer: S) -> Result<(), S::Error>
138 where
139 S: candid::types::Serializer,
140 {
141 let mut record_serializer = serializer.serialize_struct()?;
144 record_serializer.serialize_element(&self.eager_relations)?;
145 record_serializer.serialize_element(&self.offset)?;
146 record_serializer.serialize_element(&self.limit)?;
147 record_serializer.serialize_element(&self.filter)?;
148 record_serializer.serialize_element(&self.order_by)?;
149 record_serializer.serialize_element(&self.columns)?;
150
151 Ok(())
152 }
153}
154
155impl<T> Default for Query<T>
156where
157 T: TableSchema,
158{
159 fn default() -> Self {
160 Self {
161 columns: Select::All,
162 eager_relations: Vec::new(),
163 filter: None,
164 order_by: Vec::new(),
165 limit: None,
166 offset: None,
167 _marker: PhantomData,
168 }
169 }
170}
171
172impl<T> Query<T>
173where
174 T: TableSchema,
175{
176 pub fn builder() -> QueryBuilder<T> {
178 QueryBuilder::default()
179 }
180
181 pub fn all_selected(&self) -> bool {
183 matches!(self.columns, Select::All)
184 }
185
186 pub fn columns(&self) -> Vec<String> {
188 match &self.columns {
189 Select::All => T::columns()
190 .iter()
191 .map(|col| col.name.to_string())
192 .collect(),
193 Select::Columns(cols) => cols.clone(),
194 }
195 }
196}
197
198#[cfg(test)]
199mod tests {
200
201 use super::*;
202 use crate::tests::User;
203
204 #[test]
205 fn test_should_build_default_query() {
206 let query: Query<User> = Query::default();
207 assert!(matches!(query.columns, Select::All));
208 assert!(query.eager_relations.is_empty());
209 assert!(query.filter.is_none());
210 assert!(query.order_by.is_empty());
211 assert!(query.limit.is_none());
212 assert!(query.offset.is_none());
213 }
214
215 #[test]
216 fn test_should_get_columns() {
217 let query = Query::<User>::default();
218 let columns = query.columns();
219 assert_eq!(columns, vec!["id", "name",]);
220
221 let query = Query::<User> {
222 columns: Select::Columns(vec!["id".to_string()]),
223 ..Default::default()
224 };
225
226 let columns = query.columns();
227 assert_eq!(columns, vec!["id"]);
228 }
229
230 #[test]
231 fn test_should_check_all_selected() {
232 let query = Query::<User>::default();
233 assert!(query.all_selected());
234 }
235
236 #[test]
237 fn test_should_encode_decode_query_candid() {
238 let query: Query<User> = Query::builder()
239 .field("id")
240 .with("posts")
241 .and_where(Filter::eq("name", Value::Text("Alice".into())))
242 .order_by_asc("id")
243 .limit(10)
244 .offset(5)
245 .build();
246 let encoded = candid::encode_one(&query).unwrap();
247 let decoded: Query<User> = candid::decode_one(&encoded).unwrap();
248 assert_eq!(query, decoded);
249 }
250}