1mod check;
17
18pub use check::{ColumnTypeMismatch, MismatchReason, accepted_kinds, check_column_types};
19
20#[cfg(feature = "merge-patch")]
21use crate::AssignmentsTarget;
22use crate::sql::{LIKE_ESCAPE, like_pattern, page_offset};
23use crate::{Columns, ConditionsTarget, Junction, OrderByTarget, PageTarget};
24use queryweave::{
25 Conditions, FilterCondition, FilterOperator, SortDirectionModel, SortKey, SortKeys, Value,
26};
27#[cfg(feature = "merge-patch")]
28use queryweave::{FieldValue, FieldValues};
29#[cfg(feature = "merge-patch")]
30use sea_query::UpdateStatement;
31use sea_query::{
32 BinOper, Condition, DeleteStatement, Expr, ExprTrait, LikeExpr, Order, SelectStatement,
33};
34
35use super::column;
36
37fn sea_value(value: Value) -> sea_query::Value {
40 match value {
41 Value::Bool(value) => value.into(),
42 Value::I16(value) => value.into(),
43 Value::I32(value) => value.into(),
44 Value::I64(value) => value.into(),
45 Value::F32(value) => value.into(),
46 Value::F64(value) => value.into(),
47 Value::Text(value) => value.into(),
48 Value::DateTimeUtc(value) => value.into(),
49 Value::NaiveDateTime(value) => value.into(),
50 Value::NaiveDate(value) => value.into(),
51 Value::NaiveTime(value) => value.into(),
52 }
53}
54
55fn comparison(column: Expr, operator: FilterOperator, value: Value) -> Expr {
57 let value = sea_value(value);
58 match operator {
59 FilterOperator::LessThan => column.lt(value),
60 FilterOperator::Equal => column.eq(value),
61 FilterOperator::GreaterThan => column.gt(value),
62 FilterOperator::StartsWith | FilterOperator::Contains | FilterOperator::EndsWith => {
63 column.binary(BinOper::Like, value)
64 },
65 }
66}
67
68fn condition_expr<C: Columns + ?Sized>(condition: FilterCondition) -> Expr {
75 let FilterCondition {
76 field,
77 operator,
78 value,
79 } = condition;
80 let column = Expr::col(column::<C>(field));
81
82 match value {
85 Value::Text(Some(text)) => match like_pattern(operator, &text) {
86 Some(pattern) => column.like(LikeExpr::new(pattern).escape(LIKE_ESCAPE)),
87 None => comparison(column, operator, Value::Text(Some(text))),
88 },
89 value => comparison(column, operator, value),
90 }
91}
92
93fn condition<C: Columns + ?Sized, M>(
96 conditions: Conditions<M>,
97 junction: Junction,
98) -> Option<Condition> {
99 if conditions.is_empty() {
100 return None;
101 }
102
103 let joined = match junction {
104 Junction::And => Condition::all(),
105 Junction::Or => Condition::any(),
106 };
107 Some(conditions.into_iter().fold(joined, |joined, condition| {
108 joined.add(condition_expr::<C>(condition))
109 }))
110}
111
112macro_rules! conditions_target {
114 ($($statement:ty),+ $(,)?) => {$(
115 impl ConditionsTarget for $statement {
116 fn write_where(&mut self) {}
118
119 fn write_conditions<C: Columns + ?Sized, M>(
120 &mut self,
121 conditions: Conditions<M>,
122 junction: Junction,
123 ) {
124 if let Some(condition) = condition::<C, M>(conditions, junction) {
125 self.cond_where(condition);
126 }
127 }
128 }
129 )+};
130}
131
132#[cfg(feature = "merge-patch")]
133conditions_target!(SelectStatement, UpdateStatement, DeleteStatement);
134#[cfg(not(feature = "merge-patch"))]
135conditions_target!(SelectStatement, DeleteStatement);
136
137impl OrderByTarget for SelectStatement {
138 fn write_order_by<C: Columns + ?Sized, M>(&mut self, sort_keys: SortKeys<M>) {
139 for SortKey { field, direction } in sort_keys {
140 let order = match direction {
141 SortDirectionModel::Ascending => Order::Asc,
142 SortDirectionModel::Descending => Order::Desc,
143 };
144 self.order_by(column::<C>(field), order);
145 }
146 }
147}
148
149impl PageTarget for SelectStatement {
150 fn write_page(&mut self, page: Option<u32>, page_size: Option<u32>) {
151 let (Some(page), Some(page_size)) = (page, page_size) else {
152 return;
153 };
154
155 self.limit(u64::from(page_size))
158 .offset(page_offset(page, page_size).unsigned_abs());
159 }
160}
161
162#[cfg(feature = "merge-patch")]
163impl AssignmentsTarget for UpdateStatement {
164 type Assignments<'target> = &'target mut Self;
165
166 fn write_assignments<C: Columns + ?Sized, M>(
167 &mut self,
168 assignments: FieldValues<M>,
169 ) -> Self::Assignments<'_> {
170 for FieldValue { field, value } in assignments {
171 self.value(column::<C>(field), sea_value(value));
172 }
173
174 self
175 }
176}
177
178#[cfg(test)]
179mod tests {
180 use crate::{Columns, Junction, push_page};
181 use pretty_assertions::assert_eq;
182 #[cfg(feature = "merge-patch")]
183 use queryweave::FieldValue;
184 use queryweave::{
185 FilterCondition, FilterOperator, PersistedFields, SortDirectionModel, SortKey, Value,
186 };
187 use rstest::rstest;
188 use sea_query::{
189 MysqlQueryBuilder, PostgresQueryBuilder, Query, QueryBuilder, SelectStatement,
190 SqliteQueryBuilder, Values,
191 };
192
193 struct Table;
196
197 impl Columns for Table {
198 const NAME: &'static str = "Table";
199 const COLUMNS: &'static [(&'static str, &'static str)] = &[
200 ("number", "number"),
201 ("client", "client"),
202 ("value", "value"),
203 ("delivery_date", "delivered_at"),
204 ];
205 }
206
207 struct Model;
209
210 impl PersistedFields for Model {
211 const FIELD_NAMES: &'static [&'static str] =
212 &["number", "client", "value", "delivery_date"];
213 }
214
215 fn condition(field: &'static str, operator: FilterOperator) -> FilterCondition {
216 FilterCondition {
217 field,
218 operator,
219 value: Value::Text(Some(String::from("needle"))),
220 }
221 }
222
223 fn select() -> SelectStatement {
224 Query::select().column("id").from("orders").to_owned()
225 }
226
227 fn where_sql(conditions: Vec<FilterCondition>, junction: Junction) -> (String, Values) {
228 let mut statement = select();
229 Table::push_conditions(
230 &mut statement,
231 queryweave::__private::model_data::<Model, _>(conditions),
232 junction,
233 );
234 statement.build(PostgresQueryBuilder)
235 }
236
237 fn select_sql(builder: &impl QueryBuilder) -> String {
239 let mut statement = select();
240 Table::push_conditions(
241 &mut statement,
242 queryweave::__private::model_data::<Model, _>(vec![
243 condition("number", FilterOperator::Equal),
244 condition("client", FilterOperator::StartsWith),
245 ]),
246 Junction::And,
247 );
248 Table::push_order_by(
249 &mut statement,
250 queryweave::__private::model_data::<Model, _>(vec![SortKey {
251 field: "delivery_date",
252 direction: SortDirectionModel::Descending,
253 }]),
254 );
255 push_page(&mut statement, Some(2), Some(10));
256 statement.build_any(builder).0
257 }
258
259 #[test]
261 fn every_database_gets_its_own_placeholders_and_quotes() {
262 assert_eq!(
263 select_sql(&PostgresQueryBuilder),
264 r#"SELECT "id" FROM "orders" WHERE "number" = $1 AND "client" LIKE $2 ESCAPE '!' ORDER BY "delivered_at" DESC LIMIT $3 OFFSET $4"#
265 );
266 assert_eq!(
267 select_sql(&MysqlQueryBuilder),
268 "SELECT `id` FROM `orders` WHERE `number` = ? AND `client` LIKE ? ESCAPE '!' ORDER BY `delivered_at` DESC LIMIT ? OFFSET ?"
269 );
270 assert_eq!(
271 select_sql(&SqliteQueryBuilder),
272 r#"SELECT "id" FROM "orders" WHERE "number" = ? AND "client" LIKE ? ESCAPE '!' ORDER BY "delivered_at" DESC LIMIT ? OFFSET ?"#
273 );
274 }
275
276 #[test]
279 fn the_values_are_bound() {
280 let mut statement = select();
281 Table::push_conditions(
282 &mut statement,
283 queryweave::__private::model_data::<Model, _>(vec![
284 FilterCondition {
285 field: "value",
286 operator: FilterOperator::LessThan,
287 value: Value::F64(Some(9.5)),
288 },
289 FilterCondition {
290 field: "client",
291 operator: FilterOperator::Contains,
292 value: Value::Text(Some(String::from("50%"))),
293 },
294 ]),
295 Junction::And,
296 );
297 push_page(&mut statement, Some(3), Some(10));
298
299 let (_, values) = statement.build(PostgresQueryBuilder);
300 assert_eq!(
301 values,
302 Values(vec![
303 9.5_f64.into(),
304 String::from("%50!%%").into(),
305 10_u64.into(),
306 30_u64.into(),
307 ])
308 );
309 }
310
311 #[rstest]
314 #[case(
315 Junction::And,
316 r#"SELECT "id" FROM "orders" WHERE "number" = $1 AND "client" = $2"#
317 )]
318 #[case(
319 Junction::Or,
320 r#"SELECT "id" FROM "orders" WHERE "number" = $1 OR "client" = $2"#
321 )]
322 fn conditions_are_joined_by_the_junction(#[case] junction: Junction, #[case] expected: &str) {
323 let conditions = vec![
324 condition("number", FilterOperator::Equal),
325 condition("client", FilterOperator::Equal),
326 ];
327
328 assert_eq!(where_sql(conditions, junction).0, expected);
329 assert_eq!(
330 where_sql(Vec::new(), junction).0,
331 r#"SELECT "id" FROM "orders""#
332 );
333 }
334
335 #[rstest]
338 #[case(FilterOperator::LessThan, r#""value" < $1"#)]
339 #[case(FilterOperator::GreaterThan, r#""value" > $1"#)]
340 #[case(FilterOperator::Equal, r#""value" = $1"#)]
341 #[case(FilterOperator::StartsWith, r#""value" LIKE $1 ESCAPE '!'"#)]
342 #[case(FilterOperator::Contains, r#""value" LIKE $1 ESCAPE '!'"#)]
343 #[case(FilterOperator::EndsWith, r#""value" LIKE $1 ESCAPE '!'"#)]
344 fn the_operator_selects_the_comparison(
345 #[case] operator: FilterOperator,
346 #[case] expected: &str,
347 ) {
348 let (sql, _) = where_sql(vec![condition("value", operator)], Junction::And);
349
350 assert_eq!(
351 sql,
352 format!(r#"SELECT "id" FROM "orders" WHERE {expected}"#)
353 );
354 }
355
356 #[test]
358 fn a_renamed_field_is_written_as_its_column() {
359 let (sql, _) = where_sql(
360 vec![condition("delivery_date", FilterOperator::GreaterThan)],
361 Junction::And,
362 );
363 assert_eq!(
364 sql,
365 r#"SELECT "id" FROM "orders" WHERE "delivered_at" > $1"#
366 );
367 }
368
369 #[rstest]
371 #[case(None, Some(10))]
372 #[case(Some(1), None)]
373 fn nothing_is_written_without_sort_keys_or_a_whole_page(
374 #[case] page: Option<u32>,
375 #[case] page_size: Option<u32>,
376 ) {
377 let mut statement = select();
378 Table::push_order_by(
379 &mut statement,
380 queryweave::__private::model_data::<Model, _>(Vec::new()),
381 );
382 push_page(&mut statement, page, page_size);
383
384 assert_eq!(
385 statement.to_string(PostgresQueryBuilder),
386 r#"SELECT "id" FROM "orders""#
387 );
388 }
389
390 #[cfg(feature = "merge-patch")]
393 #[test]
394 fn assignments_set_each_column_and_are_extendable() {
395 let mut statement = Query::update().table("orders").to_owned();
396 Table::push_assignments(
397 &mut statement,
398 queryweave::__private::model_data::<Model, _>(vec![
399 FieldValue {
400 field: "number",
401 value: Value::Text(None),
402 },
403 FieldValue {
404 field: "delivery_date",
405 value: Value::I32(Some(3)),
406 },
407 ]),
408 )
409 .value("last_modified_at", 1_i64);
410
411 assert_eq!(
412 statement.build(PostgresQueryBuilder).0,
413 r#"UPDATE "orders" SET "number" = $1, "delivered_at" = $2, "last_modified_at" = $3"#
414 );
415 }
416}