Skip to main content

queryweave_sql/backend/sqlx/
bind.rs

1//! Binding the domain's raw values, whatever the database.
2
3use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc};
4use queryweave::Value;
5use sqlx::query_builder::Separated;
6use sqlx::{Database, Encode, QueryBuilder, Type};
7
8/// A database the SQL helpers can write for.
9///
10/// It is one whose driver binds every raw value a domain model may filter on
11/// or store ([`Value`]'s variants: `bool`, `i16`, `i32`, `i64`, `f32`, `f64`,
12/// `String` and the `chrono` date/time types, each possibly `NULL`).
13///
14/// Implemented for every such sqlx database: Postgres, MySQL/MariaDB and
15/// SQLite, with sqlx's `chrono` feature (`sqlx::Any` is not one, having no
16/// date/time types). Code generic over the database only needs
17/// `DB: BindValues`.
18pub trait BindValues: Database {
19    /// Bind `value` with its own SQL type; an absent value binds as a `NULL`
20    /// of that type.
21    fn push_bind_value(query_builder: &mut QueryBuilder<'_, Self>, value: Value);
22
23    /// Bind `value` like [`BindValues::push_bind_value`], into a separated
24    /// list without a separator before it (e.g. right after `column = `).
25    fn push_bind_value_unseparated<Sep: std::fmt::Display>(
26        separated: &mut Separated<'_, '_, Self, Sep>,
27        value: Value,
28    );
29}
30
31/// Bind a [`Value`] with the method `$bind` of `$target` (a `QueryBuilder`'s
32/// `push_bind` or a `Separated`'s `push_bind_unseparated`).
33macro_rules! bind_value {
34    ($target:expr, $bind:ident, $value:expr) => {
35        match $value {
36            Value::Bool(value) => $target.$bind(value),
37            Value::I16(value) => $target.$bind(value),
38            Value::I32(value) => $target.$bind(value),
39            Value::I64(value) => $target.$bind(value),
40            Value::F32(value) => $target.$bind(value),
41            Value::F64(value) => $target.$bind(value),
42            Value::Text(value) => $target.$bind(value),
43            Value::DateTimeUtc(value) => $target.$bind(value),
44            Value::NaiveDateTime(value) => $target.$bind(value),
45            Value::NaiveDate(value) => $target.$bind(value),
46            Value::NaiveTime(value) => $target.$bind(value),
47        }
48    };
49}
50
51// sqlx implements `Encode` for `Option<T>` per database, not generically, so
52// the bounds name the `Option`s the values are bound as.
53impl<DB> BindValues for DB
54where
55    DB: Database,
56    for<'q> Option<bool>: Encode<'q, DB> + Type<DB>,
57    for<'q> Option<i16>: Encode<'q, DB> + Type<DB>,
58    for<'q> Option<i32>: Encode<'q, DB> + Type<DB>,
59    for<'q> Option<i64>: Encode<'q, DB> + Type<DB>,
60    for<'q> Option<f32>: Encode<'q, DB> + Type<DB>,
61    for<'q> Option<f64>: Encode<'q, DB> + Type<DB>,
62    for<'q> Option<String>: Encode<'q, DB> + Type<DB>,
63    for<'q> Option<DateTime<Utc>>: Encode<'q, DB> + Type<DB>,
64    for<'q> Option<NaiveDateTime>: Encode<'q, DB> + Type<DB>,
65    for<'q> Option<NaiveDate>: Encode<'q, DB> + Type<DB>,
66    for<'q> Option<NaiveTime>: Encode<'q, DB> + Type<DB>,
67{
68    fn push_bind_value(query_builder: &mut QueryBuilder<'_, Self>, value: Value) {
69        bind_value!(query_builder, push_bind, value);
70    }
71
72    fn push_bind_value_unseparated<Sep: std::fmt::Display>(
73        separated: &mut Separated<'_, '_, Self, Sep>,
74        value: Value,
75    ) {
76        bind_value!(separated, push_bind_unseparated, value);
77    }
78}