queryweave_sql/lib.rs
1//! The `queryweave` runtime for the persistence layer: the traits and SQL the
2//! code generated for entities and wrappers (`Columns`, `AppendToQuery`,
3//! `AppendConditions`) names, on top of the core runtime
4//! (`queryweave`), which it re-exports so that code names only this crate.
5//!
6//! This is where the domain's database-agnostic filters, sorting and writes
7//! (`queryweave::FilterCondition`, `queryweave::SortKey`,
8//! `queryweave::FieldValue`) become SQL. They name domain fields; the
9//! [`Columns`] of the entity storing them say which column each lives in, and
10//! write the SQL against them, into the query being built: any type
11//! implementing the target traits ([`ConditionsTarget`], [`OrderByTarget`],
12//! [`PageTarget`], and `AssignmentsTarget` with merge patches).
13//!
14//! # Features
15//!
16//! - `sqlx` (default): sqlx's `QueryBuilder` is a target, for any database
17//! whose driver binds the domain's raw values (`BindValues`).
18//! - `sea-query`: sea-query's `SelectStatement` (conditions, `ORDER BY`, page),
19//! `UpdateStatement` (conditions, assignments) and `DeleteStatement`
20//! (conditions) are targets. With `sea-orm`, write into a query's
21//! statement through `QueryTrait::query`:
22//! `Order::push_conditions(select.query(), …)`.
23//! - `diesel`: the `diesel` module, the typed conditions, orderings,
24//! page and assignments of an entity's Diesel columns, for the SQL types
25//! every backend has; `diesel-postgres`, `diesel-mysql` and `diesel-sqlite`
26//! add each backend's own date-time types.
27//! - `merge-patch` (default): merge-patch writes (`push_assignments`).
28//!
29//! The SQL is portable across Postgres, MySQL/MariaDB and SQLite. What stays
30//! database-specific is semantics, not syntax: `LIKE` is case-sensitive on
31//! Postgres but case-insensitive on SQLite (for ASCII) and under MySQL's
32//! default collations, and column names are written unquoted, so they must not
33//! be reserved words of the database.
34
35mod backend;
36mod columns;
37mod sql;
38mod target;
39
40pub use queryweave::*;
41
42#[cfg(feature = "sqlx")]
43pub use backend::BindValues;
44#[cfg(feature = "diesel")]
45pub use backend::diesel;
46#[cfg(feature = "sea-query")]
47pub use backend::sea_query;
48pub use columns::{
49 Columns, Message, column_for, first_field_without_column, missing_column_message,
50};
51pub use sql::{
52 LIKE_ESCAPE, escape_like, like_pattern, page_offset, push_page, sql_direction, sql_junction,
53 sql_operator,
54};
55#[cfg(feature = "merge-patch")]
56pub use target::AssignmentsTarget;
57pub use target::{ConditionsTarget, Junction, OrderByTarget, PageTarget};
58
59/// The sqlx query builder the clauses are appended to, and the separated
60/// builder `Columns::push_assignments` returns for it: re-exported so callers
61/// can name the same sqlx as this crate, whatever they call their own sqlx
62/// dependency.
63#[cfg(feature = "sqlx")]
64pub use sqlx::{QueryBuilder, query_builder::Separated};
65
66/// Not public API: what the code generated by `queryweave-derive`
67/// calls.
68///
69/// The inherent methods `#[derive(Columns)]` emits run the column check
70/// themselves (so a missing column is reported at *their* caller, the line
71/// writing the data, rather than inside the derive) and then call the target's
72/// unchecked `write_*` method, so the check is not reported twice.
73#[doc(hidden)]
74pub mod __private {
75 pub use crate::__if_diesel as if_diesel;
76 pub use crate::__if_merge_patch as if_merge_patch;
77 #[cfg(feature = "diesel")]
78 pub use crate::backend::diesel::check::{assert_assignment_types, assert_condition_types};
79 pub use crate::columns::assert_model_columns;
80 pub use queryweave::__private::*;
81}
82
83/// Not public API: the code `#[derive(Columns)]` generates for a Diesel
84/// entity, let through only when this crate has its `diesel` feature.
85///
86/// The derive cannot see this crate's features, and a `#[cfg]` in its output
87/// would test the user's crate's; this macro is defined by this crate, once
88/// per state of the feature, so the generated Diesel code (which names this
89/// crate's `diesel` module) is dropped before it is checked when the feature
90/// is off, and an entity also used with another backend costs nothing.
91#[cfg(feature = "diesel")]
92#[doc(hidden)]
93#[macro_export]
94macro_rules! __if_diesel {
95 ($($items:tt)*) => {
96 $($items)*
97 };
98}
99
100/// See the other definition: without the `diesel` feature, nothing.
101#[cfg(not(feature = "diesel"))]
102#[doc(hidden)]
103#[macro_export]
104macro_rules! __if_diesel {
105 ($($items:tt)*) => {};
106}
107
108/// Not public API: the merge-patch writes `#[derive(Columns)]` generates
109/// (`push_assignments`, and a Diesel entity's `diesel_changeset`), let
110/// through only when this crate has its `merge-patch` feature.
111///
112/// They name this crate's merge-patch items (`AssignmentsTarget`,
113/// `FieldValues`), which exist only with the feature; the derive's own
114/// `merge-patch` feature is another crate's, so it cannot tell. Like
115/// `__if_diesel`, defined once per state of the feature.
116#[cfg(feature = "merge-patch")]
117#[doc(hidden)]
118#[macro_export]
119macro_rules! __if_merge_patch {
120 ($($items:tt)*) => {
121 $($items)*
122 };
123}
124
125/// See the other definition: without the `merge-patch` feature, nothing.
126#[cfg(not(feature = "merge-patch"))]
127#[doc(hidden)]
128#[macro_export]
129macro_rules! __if_merge_patch {
130 ($($items:tt)*) => {};
131}
132
133/// Append the filters query to the parent query.
134pub trait AppendToQuery {
135 /// Append the filters query to the parent query: any target taking
136 /// conditions, an `ORDER BY` and a page (for sqlx, a `QueryBuilder` of any
137 /// `BindValues` database).
138 fn append_to_query<Q, F>(&self, query: &mut Q, privileged_guard_option: Option<F>)
139 where
140 Q: ConditionsTarget + OrderByTarget + PageTarget,
141 F: FnOnce(&mut Q, &Self);
142}
143
144/// Append a filter-options model's set filters to a query as conditions.
145pub trait AppendConditions {
146 /// Append the set filters as conditions joined by `junction`, to any
147 /// target taking conditions (for sqlx, a `QueryBuilder` of any
148 /// `BindValues` database).
149 fn append_conditions<Q: ConditionsTarget>(&self, query: &mut Q, junction: Junction);
150}