1mod builder;
4mod delete;
5mod filter;
6mod join;
7
8use serde::{Deserialize, Serialize};
9use thiserror::Error;
10
11pub use self::builder::QueryBuilder;
12pub use self::delete::DeleteBehavior;
13pub use self::filter::{Filter, JsonCmp, JsonFilter};
14pub use self::join::{Join, JoinType};
15use crate::dbms::table::TableSchema;
16use crate::dbms::value::Value;
17use crate::memory::MemoryError;
18
19pub type QueryResult<T> = Result<T, QueryError>;
21
22#[derive(Debug, Error, Serialize, Deserialize)]
24#[cfg_attr(feature = "candid", derive(candid::CandidType))]
25pub enum QueryError {
26 #[error("Primary key conflict: record with the same primary key already exists")]
28 PrimaryKeyConflict,
29
30 #[error("Unique constraint violation on field '{field}'")]
32 UniqueConstraintViolation { field: String },
33
34 #[error("Broken foreign key reference to table '{table}' with key '{key:?}'")]
36 BrokenForeignKeyReference { table: String, key: Value },
37
38 #[error("Foreign key constraint violation on table '{referencing_table}' for field '{field}'")]
40 ForeignKeyConstraintViolation {
41 referencing_table: String,
42 field: String,
43 },
44
45 #[error("Unknown column: {0}")]
47 UnknownColumn(String),
48
49 #[error("Missing non-nullable field: {0}")]
51 MissingNonNullableField(String),
52
53 #[error("transaction not found")]
55 TransactionNotFound,
56
57 #[error("Invalid query: {0}")]
59 InvalidQuery(String),
60
61 #[error("Join cannot be used on type select")]
63 JoinInsideTypedSelect,
64
65 #[error("Constraint violation: {0}")]
67 ConstraintViolation(String),
68
69 #[error("Memory error: {0}")]
71 MemoryError(MemoryError),
72
73 #[error("Table not found: {0}")]
75 TableNotFound(String),
76
77 #[error("Record not found")]
79 RecordNotFound,
80
81 #[error("Serialization error: {0}")]
83 SerializationError(String),
84
85 #[error("Internal error: {0}")]
87 Internal(String),
88}
89
90#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
92#[cfg_attr(feature = "candid", derive(candid::CandidType))]
93pub enum Select {
94 #[default]
95 All,
96 Columns(Vec<String>),
97}
98
99#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
101#[cfg_attr(feature = "candid", derive(candid::CandidType))]
102pub enum OrderDirection {
103 Ascending,
104 Descending,
105}
106
107#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
109pub struct Query {
110 columns: Select,
112 pub eager_relations: Vec<String>,
114 pub joins: Vec<Join>,
116 pub filter: Option<Filter>,
118 pub order_by: Vec<(String, OrderDirection)>,
120 pub limit: Option<usize>,
122 pub offset: Option<usize>,
124}
125
126#[cfg(feature = "candid")]
127impl candid::CandidType for Query {
128 fn _ty() -> candid::types::Type {
129 use candid::types::TypeInner;
130 let mut fields = vec![
131 candid::field! { columns: Select::_ty() },
132 candid::field! { eager_relations: <Vec<String>>::_ty() },
133 candid::field! { joins: <Vec<Join>>::_ty() },
134 candid::field! { filter: <Option<Filter>>::_ty() },
135 candid::field! { order_by: <Vec<(String, OrderDirection)>>::_ty() },
136 candid::field! { limit: <Option<usize>>::_ty() },
137 candid::field! { offset: <Option<usize>>::_ty() },
138 ];
139
140 fields.sort_by_key(|f| f.id.clone());
141 TypeInner::Record(fields).into()
142 }
143
144 fn idl_serialize<S>(&self, serializer: S) -> Result<(), S::Error>
145 where
146 S: candid::types::Serializer,
147 {
148 use candid::types::Compound;
149 let mut record_serializer = serializer.serialize_struct()?;
152 record_serializer.serialize_element(&self.eager_relations)?;
153 record_serializer.serialize_element(&self.joins)?;
154 record_serializer.serialize_element(&self.offset)?;
155 record_serializer.serialize_element(&self.limit)?;
156 record_serializer.serialize_element(&self.filter)?;
157 record_serializer.serialize_element(&self.order_by)?;
158 record_serializer.serialize_element(&self.columns)?;
159
160 Ok(())
161 }
162}
163
164impl Query {
165 pub fn builder() -> QueryBuilder {
167 QueryBuilder::default()
168 }
169
170 pub fn all_selected(&self) -> bool {
172 matches!(self.columns, Select::All)
173 }
174 pub fn columns<T>(&self) -> Vec<String>
176 where
177 T: TableSchema,
178 {
179 match &self.columns {
180 Select::All => T::columns()
181 .iter()
182 .map(|col| col.name.to_string())
183 .collect(),
184 Select::Columns(cols) => cols.clone(),
185 }
186 }
187
188 pub fn has_joins(&self) -> bool {
190 !self.joins.is_empty()
191 }
192
193 pub fn raw_columns(&self) -> &[String] {
198 match &self.columns {
199 Select::All => &[],
200 Select::Columns(cols) => cols,
201 }
202 }
203}
204
205#[cfg(test)]
206mod tests {
207
208 use super::*;
209 use crate::tests::User;
210
211 #[test]
212 fn test_should_build_default_query() {
213 let query = Query::default();
214 assert!(matches!(query.columns, Select::All));
215 assert!(query.eager_relations.is_empty());
216 assert!(query.filter.is_none());
217 assert!(query.order_by.is_empty());
218 assert!(query.limit.is_none());
219 assert!(query.offset.is_none());
220 }
221
222 #[test]
223 fn test_should_get_columns() {
224 let query = Query::default();
225 let columns = query.columns::<User>();
226 assert_eq!(columns, vec!["id", "name",]);
227
228 let query = Query {
229 columns: Select::Columns(vec!["id".to_string()]),
230 ..Default::default()
231 };
232
233 let columns = query.columns::<User>();
234 assert_eq!(columns, vec!["id"]);
235 }
236
237 #[test]
238 fn test_should_check_all_selected() {
239 let query = Query::default();
240 assert!(query.all_selected());
241 }
242
243 #[cfg(feature = "candid")]
244 #[test]
245 fn test_should_encode_decode_query_candid() {
246 let query = Query::builder()
247 .field("id")
248 .with("posts")
249 .and_where(Filter::eq("name", Value::Text("Alice".into())))
250 .order_by_asc("id")
251 .limit(10)
252 .offset(5)
253 .build();
254 let encoded = candid::encode_one(&query).unwrap();
255 let decoded: Query = candid::decode_one(&encoded).unwrap();
256 assert_eq!(query, decoded);
257 }
258
259 #[test]
260 fn test_should_build_query_with_joins() {
261 let query = Query::builder()
262 .all()
263 .inner_join("posts", "id", "user")
264 .build();
265 assert_eq!(query.joins.len(), 1);
266 assert_eq!(query.joins[0].table, "posts");
267 }
268
269 #[cfg(feature = "candid")]
270 #[test]
271 fn test_should_encode_decode_query_with_joins_candid() {
272 let query = Query::builder()
273 .all()
274 .inner_join("posts", "id", "user")
275 .left_join("comments", "posts.id", "post_id")
276 .and_where(Filter::eq("users.name", Value::Text("Alice".into())))
277 .build();
278 let encoded = candid::encode_one(&query).unwrap();
279 let decoded: Query = candid::decode_one(&encoded).unwrap();
280 assert_eq!(query, decoded);
281 }
282
283 #[test]
284 fn test_default_query_has_empty_joins() {
285 let query = Query::default();
286 assert!(query.joins.is_empty());
287 assert!(!query.has_joins());
288 }
289
290 #[test]
291 fn test_has_joins() {
292 let query = Query::builder()
293 .all()
294 .inner_join("posts", "id", "user")
295 .build();
296 assert!(query.has_joins());
297 }
298
299 #[test]
300 fn test_raw_columns_returns_empty_for_all() {
301 let query = Query::builder().all().build();
302 assert!(query.raw_columns().is_empty());
303 }
304
305 #[test]
306 fn test_raw_columns_returns_specified_columns() {
307 let query = Query::builder().field("id").field("name").build();
308 assert_eq!(query.raw_columns(), &["id", "name"]);
309 }
310}