1mod builder;
4mod delete;
5mod filter;
6mod join;
7
8use candid::CandidType;
9use candid::types::{Compound, Type, TypeInner};
10use serde::{Deserialize, Serialize};
11use thiserror::Error;
12
13pub use self::builder::QueryBuilder;
14pub use self::delete::DeleteBehavior;
15pub use self::filter::{Filter, JsonCmp, JsonFilter};
16pub use self::join::{Join, JoinType};
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("Join cannot be used on type select")]
60 JoinInsideTypedSelect,
61
62 #[error("Constraint violation: {0}")]
64 ConstraintViolation(String),
65
66 #[error("Memory error: {0}")]
68 MemoryError(MemoryError),
69
70 #[error("Table not found: {0}")]
72 TableNotFound(String),
73
74 #[error("Record not found")]
76 RecordNotFound,
77
78 #[error("Serialization error: {0}")]
80 SerializationError(String),
81
82 #[error("Internal error: {0}")]
84 Internal(String),
85}
86
87#[derive(Debug, Default, Clone, PartialEq, Eq, CandidType, Serialize, Deserialize)]
89pub enum Select {
90 #[default]
91 All,
92 Columns(Vec<String>),
93}
94
95#[derive(Debug, Copy, Clone, PartialEq, Eq, CandidType, Serialize, Deserialize)]
97pub enum OrderDirection {
98 Ascending,
99 Descending,
100}
101
102#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
104pub struct Query {
105 columns: Select,
107 pub eager_relations: Vec<String>,
109 pub joins: Vec<Join>,
111 pub filter: Option<Filter>,
113 pub order_by: Vec<(String, OrderDirection)>,
115 pub limit: Option<usize>,
117 pub offset: Option<usize>,
119}
120
121impl CandidType for Query {
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! { joins: <Vec<Join>>::_ty() },
127 candid::field! { filter: <Option<Filter>>::_ty() },
128 candid::field! { order_by: <Vec<(String, OrderDirection)>>::_ty() },
129 candid::field! { limit: <Option<usize>>::_ty() },
130 candid::field! { offset: <Option<usize>>::_ty() },
131 ];
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.joins)?;
146 record_serializer.serialize_element(&self.offset)?;
147 record_serializer.serialize_element(&self.limit)?;
148 record_serializer.serialize_element(&self.filter)?;
149 record_serializer.serialize_element(&self.order_by)?;
150 record_serializer.serialize_element(&self.columns)?;
151
152 Ok(())
153 }
154}
155
156impl Query {
157 pub fn builder() -> QueryBuilder {
159 QueryBuilder::default()
160 }
161
162 pub fn all_selected(&self) -> bool {
164 matches!(self.columns, Select::All)
165 }
166 pub fn columns<T>(&self) -> Vec<String>
168 where
169 T: TableSchema,
170 {
171 match &self.columns {
172 Select::All => T::columns()
173 .iter()
174 .map(|col| col.name.to_string())
175 .collect(),
176 Select::Columns(cols) => cols.clone(),
177 }
178 }
179
180 pub fn has_joins(&self) -> bool {
182 !self.joins.is_empty()
183 }
184
185 pub fn raw_columns(&self) -> &[String] {
190 match &self.columns {
191 Select::All => &[],
192 Select::Columns(cols) => cols,
193 }
194 }
195}
196
197#[cfg(test)]
198mod tests {
199
200 use super::*;
201 use crate::tests::User;
202
203 #[test]
204 fn test_should_build_default_query() {
205 let query = Query::default();
206 assert!(matches!(query.columns, Select::All));
207 assert!(query.eager_relations.is_empty());
208 assert!(query.filter.is_none());
209 assert!(query.order_by.is_empty());
210 assert!(query.limit.is_none());
211 assert!(query.offset.is_none());
212 }
213
214 #[test]
215 fn test_should_get_columns() {
216 let query = Query::default();
217 let columns = query.columns::<User>();
218 assert_eq!(columns, vec!["id", "name",]);
219
220 let query = Query {
221 columns: Select::Columns(vec!["id".to_string()]),
222 ..Default::default()
223 };
224
225 let columns = query.columns::<User>();
226 assert_eq!(columns, vec!["id"]);
227 }
228
229 #[test]
230 fn test_should_check_all_selected() {
231 let query = Query::default();
232 assert!(query.all_selected());
233 }
234
235 #[test]
236 fn test_should_encode_decode_query_candid() {
237 let query = Query::builder()
238 .field("id")
239 .with("posts")
240 .and_where(Filter::eq("name", Value::Text("Alice".into())))
241 .order_by_asc("id")
242 .limit(10)
243 .offset(5)
244 .build();
245 let encoded = candid::encode_one(&query).unwrap();
246 let decoded: Query = candid::decode_one(&encoded).unwrap();
247 assert_eq!(query, decoded);
248 }
249
250 #[test]
251 fn test_should_build_query_with_joins() {
252 let query = Query::builder()
253 .all()
254 .inner_join("posts", "id", "user")
255 .build();
256 assert_eq!(query.joins.len(), 1);
257 assert_eq!(query.joins[0].table, "posts");
258 }
259
260 #[test]
261 fn test_should_encode_decode_query_with_joins_candid() {
262 let query = Query::builder()
263 .all()
264 .inner_join("posts", "id", "user")
265 .left_join("comments", "posts.id", "post_id")
266 .and_where(Filter::eq("users.name", Value::Text("Alice".into())))
267 .build();
268 let encoded = candid::encode_one(&query).unwrap();
269 let decoded: Query = candid::decode_one(&encoded).unwrap();
270 assert_eq!(query, decoded);
271 }
272
273 #[test]
274 fn test_default_query_has_empty_joins() {
275 let query = Query::default();
276 assert!(query.joins.is_empty());
277 assert!(!query.has_joins());
278 }
279
280 #[test]
281 fn test_has_joins() {
282 let query = Query::builder()
283 .all()
284 .inner_join("posts", "id", "user")
285 .build();
286 assert!(query.has_joins());
287 }
288
289 #[test]
290 fn test_raw_columns_returns_empty_for_all() {
291 let query = Query::builder().all().build();
292 assert!(query.raw_columns().is_empty());
293 }
294
295 #[test]
296 fn test_raw_columns_returns_specified_columns() {
297 let query = Query::builder().field("id").field("name").build();
298 assert_eq!(query.raw_columns(), &["id", "name"]);
299 }
300}