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