1mod aggregate;
4mod builder;
5mod delete;
6mod filter;
7mod join;
8
9use serde::{Deserialize, Serialize};
10use thiserror::Error;
11
12pub use self::aggregate::{AggregateFunction, AggregatedRow, AggregatedValue};
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, Serialize, Deserialize)]
26#[cfg_attr(feature = "candid", derive(candid::CandidType))]
27pub enum QueryError {
28 #[error("Primary key conflict: record with the same primary key already exists")]
30 PrimaryKeyConflict,
31
32 #[error("Unique constraint violation on field '{field}'")]
34 UniqueConstraintViolation { field: String },
35
36 #[error("Broken foreign key reference to table '{table}' with key '{key:?}'")]
38 BrokenForeignKeyReference { table: String, key: Value },
39
40 #[error("Foreign key constraint violation on table '{referencing_table}' for field '{field}'")]
42 ForeignKeyConstraintViolation {
43 referencing_table: String,
44 field: String,
45 },
46
47 #[error("Unknown column: {0}")]
49 UnknownColumn(String),
50
51 #[error("Missing non-nullable field: {0}")]
53 MissingNonNullableField(String),
54
55 #[error("transaction not found")]
57 TransactionNotFound,
58
59 #[error("Invalid query: {0}")]
61 InvalidQuery(String),
62
63 #[error("Join cannot be used on type select")]
65 JoinInsideTypedSelect,
66
67 #[error("GROUP BY / HAVING require aggregate(); use Database::aggregate")]
70 AggregateClauseInSelect,
71
72 #[error("Constraint violation: {0}")]
74 ConstraintViolation(String),
75
76 #[error("Memory error: {0}")]
78 MemoryError(MemoryError),
79
80 #[error("Table not found: {0}")]
82 TableNotFound(String),
83
84 #[error("Record not found")]
86 RecordNotFound,
87
88 #[error("Serialization error: {0}")]
90 SerializationError(String),
91
92 #[error("Internal error: {0}")]
94 Internal(String),
95}
96
97#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
99#[cfg_attr(feature = "candid", derive(candid::CandidType))]
100pub enum Select {
101 #[default]
102 All,
103 Columns(Vec<String>),
104}
105
106#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
108#[cfg_attr(feature = "candid", derive(candid::CandidType))]
109pub enum OrderDirection {
110 Ascending,
111 Descending,
112}
113
114#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
116pub struct Query {
117 columns: Select,
119 pub distinct_by: Vec<String>,
121 pub eager_relations: Vec<String>,
123 pub filter: Option<Filter>,
125 pub group_by: Vec<String>,
127 pub having: Option<Filter>,
129 pub joins: Vec<Join>,
131 pub limit: Option<usize>,
133 pub offset: Option<usize>,
135 pub order_by: Vec<(String, OrderDirection)>,
137}
138
139#[cfg(feature = "candid")]
140impl candid::CandidType for Query {
141 fn _ty() -> candid::types::Type {
142 use candid::types::TypeInner;
143 let mut fields = vec![
144 candid::field! { columns: Select::_ty() },
145 candid::field! { distinct_by: <Vec<String>>::_ty() },
146 candid::field! { eager_relations: <Vec<String>>::_ty() },
147 candid::field! { filter: <Option<Filter>>::_ty() },
148 candid::field! { group_by: <Vec<String>>::_ty() },
149 candid::field! { having: <Option<Filter>>::_ty() },
150 candid::field! { joins: <Vec<Join>>::_ty() },
151 candid::field! { limit: <Option<usize>>::_ty() },
152 candid::field! { offset: <Option<usize>>::_ty() },
153 candid::field! { order_by: <Vec<(String, OrderDirection)>>::_ty() },
154 ];
155
156 fields.sort_by_key(|f| f.id.clone());
157 TypeInner::Record(fields).into()
158 }
159
160 fn idl_serialize<S>(&self, serializer: S) -> Result<(), S::Error>
161 where
162 S: candid::types::Serializer,
163 {
164 use candid::types::Compound;
165 let mut record_serializer = serializer.serialize_struct()?;
168 record_serializer.serialize_element(&self.eager_relations)?;
169 record_serializer.serialize_element(&self.distinct_by)?;
170 record_serializer.serialize_element(&self.joins)?;
171 record_serializer.serialize_element(&self.offset)?;
172 record_serializer.serialize_element(&self.limit)?;
173 record_serializer.serialize_element(&self.filter)?;
174 record_serializer.serialize_element(&self.group_by)?;
175 record_serializer.serialize_element(&self.having)?;
176 record_serializer.serialize_element(&self.order_by)?;
177 record_serializer.serialize_element(&self.columns)?;
178
179 Ok(())
180 }
181}
182
183impl Query {
184 pub fn builder() -> QueryBuilder {
186 QueryBuilder::default()
187 }
188
189 pub fn all_selected(&self) -> bool {
191 matches!(self.columns, Select::All)
192 }
193 pub fn columns<T>(&self) -> Vec<String>
195 where
196 T: TableSchema,
197 {
198 match &self.columns {
199 Select::All => T::columns()
200 .iter()
201 .map(|col| col.name.to_string())
202 .collect(),
203 Select::Columns(cols) => cols.clone(),
204 }
205 }
206
207 pub fn has_joins(&self) -> bool {
209 !self.joins.is_empty()
210 }
211
212 pub fn raw_columns(&self) -> &[String] {
217 match &self.columns {
218 Select::All => &[],
219 Select::Columns(cols) => cols,
220 }
221 }
222}
223
224#[cfg(test)]
225mod tests {
226
227 use super::*;
228 use crate::tests::User;
229
230 #[test]
231 fn test_should_build_default_query() {
232 let query = Query::default();
233 assert!(matches!(query.columns, Select::All));
234 assert!(query.eager_relations.is_empty());
235 assert!(query.filter.is_none());
236 assert!(query.order_by.is_empty());
237 assert!(query.limit.is_none());
238 assert!(query.offset.is_none());
239 }
240
241 #[test]
242 fn test_should_get_columns() {
243 let query = Query::default();
244 let columns = query.columns::<User>();
245 assert_eq!(columns, vec!["id", "name",]);
246
247 let query = Query {
248 columns: Select::Columns(vec!["id".to_string()]),
249 ..Default::default()
250 };
251
252 let columns = query.columns::<User>();
253 assert_eq!(columns, vec!["id"]);
254 }
255
256 #[test]
257 fn test_should_check_all_selected() {
258 let query = Query::default();
259 assert!(query.all_selected());
260 }
261
262 #[cfg(feature = "candid")]
263 #[test]
264 fn test_should_encode_decode_query_candid() {
265 let query = Query::builder()
266 .field("id")
267 .with("posts")
268 .and_where(Filter::eq("name", Value::Text("Alice".into())))
269 .order_by_asc("id")
270 .limit(10)
271 .offset(5)
272 .build();
273 let encoded = candid::encode_one(&query).unwrap();
274 let decoded: Query = candid::decode_one(&encoded).unwrap();
275 assert_eq!(query, decoded);
276 }
277
278 #[test]
279 fn test_should_build_query_with_joins() {
280 let query = Query::builder()
281 .all()
282 .inner_join("posts", "id", "user")
283 .build();
284 assert_eq!(query.joins.len(), 1);
285 assert_eq!(query.joins[0].table, "posts");
286 }
287
288 #[cfg(feature = "candid")]
289 #[test]
290 fn test_should_encode_decode_query_with_joins_candid() {
291 let query = Query::builder()
292 .all()
293 .inner_join("posts", "id", "user")
294 .left_join("comments", "posts.id", "post_id")
295 .and_where(Filter::eq("users.name", Value::Text("Alice".into())))
296 .build();
297 let encoded = candid::encode_one(&query).unwrap();
298 let decoded: Query = candid::decode_one(&encoded).unwrap();
299 assert_eq!(query, decoded);
300 }
301
302 #[test]
303 fn test_default_query_has_empty_joins() {
304 let query = Query::default();
305 assert!(query.joins.is_empty());
306 assert!(!query.has_joins());
307 }
308
309 #[test]
310 fn test_has_joins() {
311 let query = Query::builder()
312 .all()
313 .inner_join("posts", "id", "user")
314 .build();
315 assert!(query.has_joins());
316 }
317
318 #[test]
319 fn test_raw_columns_returns_empty_for_all() {
320 let query = Query::builder().all().build();
321 assert!(query.raw_columns().is_empty());
322 }
323
324 #[test]
325 fn test_raw_columns_returns_specified_columns() {
326 let query = Query::builder().field("id").field("name").build();
327 assert_eq!(query.raw_columns(), &["id", "name"]);
328 }
329}