Skip to main content

qraft_core/
lib.rs

1#![allow(
2    clippy::module_inception,
3    clippy::type_complexity,
4    clippy::wrong_self_convention
5)]
6
7//! Core query-building, typing, and lowering primitives used by `qraft`.
8
9/// Alias helpers for tables, subqueries, and projected expressions.
10pub mod alias;
11/// Rust-to-SQL compatibility traits for typed binding.
12pub mod compat;
13pub use compat::{Compatible, DefaultMeta, LowerCompatible};
14pub use quex;
15/// Common table expressions and references to named CTEs.
16pub mod cte;
17pub use cte::{Cte, CteDefinition, IntoCtes, WithClause, with, with_recursive};
18/// Typed SQL expression nodes and extension traits.
19pub mod expression;
20mod ty;
21
22pub use builder::delete_from;
23/// Starts an `insert` builder for the given table.
24pub use builder::insert_into;
25/// Starts an `update` builder for the given table or aliased table.
26pub use builder::update::update;
27/// Untyped select query state.
28pub use query::Query;
29/// Select query state with a row type attached.
30pub use query::QueryOf;
31pub use ty::*;
32
33mod emitter;
34mod instr;
35pub(crate) mod lower;
36pub(crate) mod param;
37/// Raw SQL escape hatches with bound parameters.
38pub mod raw;
39/// Manual relationship field descriptors and builders.
40pub mod relation;
41pub(crate) mod span;
42pub use instr::RpnInstr;
43mod dialect;
44pub use dialect::{Dialect, HasDialect, MySql, Postgres, Sqlite};
45mod precedence;
46/// Aggregate expressions such as `count`, `sum`, and `avg`.
47pub mod aggregate;
48/// Insert, update, and delete builders.
49pub mod builder;
50/// Declarative helper macros used inside the crate and derive output.
51pub mod macros;
52/// Select builders and related clause traits.
53#[path = "select/mod.rs"]
54pub mod query;
55mod quex_exec;
56
57use builder::{Insert, Insertable, VisitParam};
58
59use crate::{alias::Aliased, expression::In as _, query::ColumnOf};
60
61mod from_row;
62pub use from_row::{FromPivotRow, FromPrefixedRow};
63pub use quex::FromRow;
64
65#[cfg(feature = "hash")]
66mod hashed;
67#[cfg(feature = "hash")]
68pub use hashed::*;
69
70mod value;
71pub(crate) use value::{DbValue, ValueKind};
72pub use raw::{Raw, RawQuery, RawScalar, raw};
73pub use ty::{
74    AsCast, BigInt, Blob, Bool, Boolean, Comparable, Date, Double, Float, Int, Likeable,
75    Logical, MathType, NotNull, Null, NullOf, Nullability, Nullable, Numeric, Orderable,
76    PredicateType, Required, Text, Time, Timestamp, TypeCast, TypeMeta, UBigInt, UInt, Untyped,
77};
78
79/// Connects a Rust model type to its generated schema module and table name.
80#[derive(Debug, Clone, Copy, PartialEq, Eq)]
81pub enum QueryVisibility {
82    Default,
83    WithDeleted,
84    OnlyDeleted,
85}
86
87pub trait ModelQueryPolicy<M>
88where
89    M: Qrafting,
90{
91    fn base_query(visibility: QueryVisibility) -> Query {
92        Self::apply_visibility(Query::from(M::TABLE), visibility)
93    }
94
95    fn apply_visibility(query: Query, _visibility: QueryVisibility) -> Query {
96        query
97    }
98
99    fn soft_delete(_query: Query) -> Option<builder::Update<M>> {
100        None
101    }
102
103    fn restore(_query: Query) -> Option<builder::Update<M>> {
104        None
105    }
106}
107
108pub struct DefaultQueryPolicy<M>(std::marker::PhantomData<fn() -> M>);
109
110impl<M> ModelQueryPolicy<M> for DefaultQueryPolicy<M> where M: Qrafting {}
111
112pub trait Qrafting: Sized {
113    type Schema;
114    type QueryPolicy: ModelQueryPolicy<Self>;
115    const FIELD_COUNT: usize;
116    const TABLE: &'static str;
117
118    #[doc(hidden)]
119    fn __qraft_query() -> QueryOf<Self>
120    where
121        Self: Sized + FromRow,
122    {
123        <Self::QueryPolicy as ModelQueryPolicy<Self>>::base_query(QueryVisibility::Default)
124            .typed::<Self>()
125    }
126}
127
128/// Describes the projected fields of a pivot type in a fixed order.
129pub trait Pivot<const N: usize>: Sized {
130    const FIELDS: [Aliased<ColumnOf>; N];
131}
132
133/// Converts a relation wrapper into an optional typed parent query.
134pub trait RelationQuery<T> {
135    fn into_query(self) -> Option<QueryOf<T>>;
136}
137
138/// Converts many-to-many attach inputs into related key values.
139pub trait AttachInput<'a, Related, Key> {
140    fn into_related_keys(self, related_key: fn(&Related) -> Key) -> Vec<Key>;
141}
142
143/// Generated draft wrapper capable of persisting model changes.
144pub enum SaveResult {
145    Clean,
146    Saved(quex::ExecResult),
147}
148
149/// Identifies a persisted field inside a model draft.
150pub struct DraftField<Model> {
151    name: &'static str,
152    index: usize,
153    _marker: std::marker::PhantomData<fn() -> Model>,
154}
155
156impl<Model> DraftField<Model> {
157    pub const fn new(name: &'static str, index: usize) -> Self {
158        Self {
159            name,
160            index,
161            _marker: std::marker::PhantomData,
162        }
163    }
164
165    pub const fn name(self) -> &'static str {
166        self.name
167    }
168
169    pub const fn index(self) -> usize {
170        self.index
171    }
172}
173
174impl<Model> Clone for DraftField<Model> {
175    fn clone(&self) -> Self {
176        *self
177    }
178}
179
180impl<Model> Copy for DraftField<Model> {}
181
182#[derive(Debug, Clone, Copy, PartialEq, Eq)]
183pub struct SyncResult {
184    pub attached: u64,
185    pub detached: u64,
186}
187
188#[derive(Debug, Clone, Copy, PartialEq, Eq)]
189pub struct ToggleResult {
190    pub attached: u64,
191    pub detached: u64,
192}
193
194#[derive(Debug, Clone, Copy, PartialEq, Eq)]
195pub struct SyncWithoutDetachingResult {
196    pub attached: u64,
197}
198
199/// Generated draft wrapper capable of persisting model changes.
200pub trait SaveDraft {
201    type Model: Qrafting;
202
203    fn is_dirty(&self) -> bool;
204
205    fn save_update(&self) -> Option<builder::Update<Self::Model>>;
206
207    fn save<E>(self, exec: E) -> impl std::future::Future<Output = quex::Result<SaveResult>>
208    where
209        Self: Sized,
210        E: quex::Executor,
211    {
212        async move {
213            match self.save_update() {
214                Some(update) => update.execute(exec).await.map(SaveResult::Saved),
215                None => Ok(SaveResult::Clean),
216            }
217        }
218    }
219}
220
221/// Produces the generated draft wrapper for a model.
222pub trait Draftable: Sized {
223    type Draft<'a>: SaveDraft<Model = Self>
224    where
225        Self: 'a;
226
227    fn draft(&mut self) -> Self::Draft<'_>;
228
229    fn draft_mark(&mut self, field: DraftField<Self>) -> Self::Draft<'_>;
230
231    fn draft_mark_many(&mut self, fields: &[DraftField<Self>]) -> Self::Draft<'_>;
232
233    #[doc(hidden)]
234    fn __qraft_draft_with(&mut self, field: usize) -> Self::Draft<'_>;
235
236    #[doc(hidden)]
237    fn __qraft_draft_for_field(&mut self, field: &'static str) -> Self::Draft<'_>;
238
239    #[doc(hidden)]
240    fn __qraft_draft_for_fields(&mut self, fields: &[&'static str]) -> Self::Draft<'_>;
241}
242
243/// Exposes the primary key value type for derived models.
244pub trait ModelKey {
245    type Key: Clone;
246
247    #[doc(hidden)]
248    fn __qraft_key(&self) -> &Self::Key;
249
250    #[doc(hidden)]
251    fn __qraft_key_column() -> &'static str;
252}
253
254/// Describes a model that can build a query from its declared primary key.
255pub trait FindsByKey: Sized {
256    type Key;
257
258    fn find(id: &Self::Key) -> QueryOf<Self>;
259}
260
261/// Exposes the discriminator stored for polymorphic relations.
262pub trait MorphName {
263    fn morph_name() -> &'static str;
264}
265
266/// Converts a relation parent key into the child foreign-key field type.
267pub trait RelationForeignKey<Key> {
268    fn from_relation_key(key: Key) -> Self;
269}
270
271/// Converts a borrowed relation parent key into the child foreign-key field type.
272pub trait RelationForeignKeyRef<Key: ?Sized> {
273    fn from_relation_key_ref(key: &Key) -> Self;
274}
275
276impl<Key> RelationForeignKey<Key> for Key {
277    fn from_relation_key(key: Key) -> Self {
278        key
279    }
280}
281
282impl<Key> RelationForeignKey<Key> for Option<Key> {
283    fn from_relation_key(key: Key) -> Self {
284        Some(key)
285    }
286}
287
288impl<Key: Clone, T> RelationForeignKeyRef<Key> for T
289where
290    T: RelationForeignKey<Key>,
291{
292    fn from_relation_key_ref(key: &Key) -> Self {
293        T::from_relation_key(key.clone())
294    }
295}
296
297/// Converts a morph discriminator string into the child field type.
298pub trait RelationMorphType {
299    fn from_relation_morph_type(value: &'static str) -> Self;
300}
301
302impl RelationMorphType for String {
303    fn from_relation_morph_type(value: &'static str) -> Self {
304        value.to_owned()
305    }
306}
307
308impl RelationMorphType for Option<String> {
309    fn from_relation_morph_type(value: &'static str) -> Self {
310        Some(value.to_owned())
311    }
312}
313
314/// Insertable adapter that injects a fixed morph id + type and ignores duplicates.
315pub struct InjectMorphValues<V, Key> {
316    values: V,
317    id_field: &'static str,
318    type_field: &'static str,
319    key: Key,
320    morph_type: &'static str,
321}
322
323struct SkipFieldVisitor<'a, V> {
324    inner: &'a mut V,
325    fields: &'a [&'static str],
326}
327
328impl<'a, 'v, V> VisitParam<'v> for SkipFieldVisitor<'a, V>
329where
330    V: VisitParam<'v>,
331{
332    fn param(&mut self, field: &'static str, param: impl quex::Encode) {
333        if self.fields.contains(&field) {
334            return;
335        }
336        self.inner.param(field, param)
337    }
338
339    fn param_typed<T>(&mut self, field: &'static str, value: &'v (impl Compatible<T> + ?Sized))
340    where
341        T: TypeMeta,
342    {
343        if self.fields.contains(&field) {
344            return;
345        }
346        self.inner.param_typed::<T>(field, value)
347    }
348}
349
350impl<M, V, Key> Insertable<M> for InjectMorphValues<V, Key>
351where
352    M: Qrafting,
353    V: Insertable<M>,
354    Key: DefaultMeta + Compatible<<Key as DefaultMeta>::Meta>,
355{
356    fn values<'v>(&'v self, visitor: &mut impl VisitParam<'v>) {
357        visitor.param_typed::<<Key as DefaultMeta>::Meta>(self.id_field, &self.key);
358        visitor.param(self.type_field, self.morph_type);
359        let mut visitor = SkipFieldVisitor {
360            inner: visitor,
361            fields: &[self.id_field, self.type_field],
362        };
363        self.values.values(&mut visitor)
364    }
365}
366
367impl<'a, Related, Key> AttachInput<'a, Related, Key> for &'a Related {
368    fn into_related_keys(self, related_key: fn(&Related) -> Key) -> Vec<Key> {
369        vec![related_key(self)]
370    }
371}
372
373impl<'a, Related, Key> AttachInput<'a, Related, Key> for Vec<&'a Related> {
374    fn into_related_keys(self, related_key: fn(&Related) -> Key) -> Vec<Key> {
375        self.into_iter().map(related_key).collect()
376    }
377}
378
379impl<'a, Related, Key> AttachInput<'a, Related, Key> for &'a [&'a Related] {
380    fn into_related_keys(self, related_key: fn(&Related) -> Key) -> Vec<Key> {
381        self.iter().copied().map(related_key).collect()
382    }
383}
384
385impl<'a, Related, Key, const N: usize> AttachInput<'a, Related, Key> for [&'a Related; N] {
386    fn into_related_keys(self, related_key: fn(&Related) -> Key) -> Vec<Key> {
387        self.into_iter().map(related_key).collect()
388    }
389}
390
391macro_rules! impl_attach_input_tuple {
392    ($($T:ident),+) => {
393        impl<'a, Related, Key, $($T),+> AttachInput<'a, Related, Key> for ($($T,)+)
394        where
395            Related: 'a,
396            $($T: ::std::borrow::Borrow<Related>,)+
397        {
398            fn into_related_keys(self, related_key: fn(&Related) -> Key) -> Vec<Key> {
399                #[allow(non_snake_case)]
400                let ($($T,)+) = self;
401                vec![$(related_key($T.borrow()),)+]
402            }
403        }
404    };
405}
406
407impl_for_all_tuples!(impl_attach_input_tuple);
408
409pub type ManyToManyValues<Pivot, ParentMeta, RelatedMeta, ParentKey, RelatedKey> = (
410    expression::Binary<expression::op::Eq, expression::Column<Pivot, ParentMeta>, ParentKey>,
411    expression::Binary<expression::op::Eq, expression::Column<Pivot, RelatedMeta>, RelatedKey>,
412);
413
414pub type MorphToManyValues<Pivot, ParentMeta, RelatedMeta, ParentKey, RelatedKey> = (
415    expression::Binary<expression::op::Eq, expression::Column<Pivot, ParentMeta>, ParentKey>,
416    expression::Binary<expression::op::Eq, expression::Column<Pivot, Text>, &'static str>,
417    expression::Binary<expression::op::Eq, expression::Column<Pivot, RelatedMeta>, RelatedKey>,
418);
419
420pub(crate) fn build_many_to_many_values<
421    Pivot: Qrafting,
422    ParentMeta: TypeMeta + Comparable + Nullability,
423    RelatedMeta: TypeMeta + Comparable + Nullability,
424    ParentKey,
425    RelatedKey,
426>(
427    parent_column: expression::Column<Pivot, ParentMeta>,
428    related_column: expression::Column<Pivot, RelatedMeta>,
429    parent_key: &ParentKey,
430    related_keys: Vec<RelatedKey>,
431) -> Vec<ManyToManyValues<Pivot, ParentMeta, RelatedMeta, ParentKey, RelatedKey>>
432where
433    ParentKey: Clone + Compatible<ParentMeta>,
434    RelatedKey: Compatible<RelatedMeta>,
435    BinaryType<NullOf<ParentMeta>, NullOf<ParentMeta>>: PredicateType,
436    BinaryType<NullOf<RelatedMeta>, NullOf<RelatedMeta>>: PredicateType,
437{
438    let mut values = Vec::with_capacity(related_keys.len());
439    for related_key in related_keys {
440        values.push((
441            parent_column.eq(parent_key.clone()),
442            related_column.eq(related_key),
443        ));
444    }
445    values
446}
447
448pub(crate) fn build_morph_to_many_values<
449    Pivot: Qrafting,
450    ParentMeta: TypeMeta + Comparable + Nullability,
451    RelatedMeta: TypeMeta + Comparable + Nullability,
452    ParentKey,
453    RelatedKey,
454>(
455    spec: MorphToManyValueSpec<Pivot, ParentMeta, RelatedMeta, ParentKey, RelatedKey>,
456) -> Vec<MorphToManyValues<Pivot, ParentMeta, RelatedMeta, ParentKey, RelatedKey>>
457where
458    ParentKey: Clone + Compatible<ParentMeta>,
459    RelatedKey: Compatible<RelatedMeta>,
460    BinaryType<NullOf<ParentMeta>, NullOf<ParentMeta>>: PredicateType,
461    BinaryType<NullOf<RelatedMeta>, NullOf<RelatedMeta>>: PredicateType,
462{
463    let MorphToManyValueSpec {
464        parent_column,
465        type_column,
466        related_column,
467        parent_key,
468        morph_type,
469        related_keys,
470    } = spec;
471    let mut values = Vec::with_capacity(related_keys.len());
472    for related_key in related_keys {
473        values.push((
474            parent_column.eq(parent_key.clone()),
475            type_column.eq(morph_type),
476            related_column.eq(related_key),
477        ));
478    }
479    values
480}
481
482pub(crate) struct MorphToManyValueSpec<
483    Pivot: Qrafting,
484    ParentMeta: TypeMeta + Comparable + Nullability,
485    RelatedMeta: TypeMeta + Comparable + Nullability,
486    ParentKey,
487    RelatedKey,
488> {
489    pub(crate) parent_column: expression::Column<Pivot, ParentMeta>,
490    pub(crate) type_column: expression::Column<Pivot, Text>,
491    pub(crate) related_column: expression::Column<Pivot, RelatedMeta>,
492    pub(crate) parent_key: ParentKey,
493    pub(crate) morph_type: &'static str,
494    pub(crate) related_keys: Vec<RelatedKey>,
495}
496
497pub struct AttachBelongsToMany<
498    Pivot,
499    ParentMeta: TypeMeta,
500    RelatedMeta: TypeMeta,
501    ParentKey,
502    RelatedKey,
503> {
504    pivot_table: query::Table<Pivot>,
505    parent_column: expression::Column<Pivot, ParentMeta>,
506    related_column: expression::Column<Pivot, RelatedMeta>,
507    parent_key: ParentKey,
508    related_keys: Vec<RelatedKey>,
509}
510
511pub struct AttachMorphToMany<
512    Pivot,
513    ParentMeta: TypeMeta,
514    RelatedMeta: TypeMeta,
515    ParentKey,
516    RelatedKey,
517> {
518    pivot_table: query::Table<Pivot>,
519    parent_column: expression::Column<Pivot, ParentMeta>,
520    type_column: expression::Column<Pivot, Text>,
521    related_column: expression::Column<Pivot, RelatedMeta>,
522    parent_key: ParentKey,
523    morph_type: &'static str,
524    related_keys: Vec<RelatedKey>,
525}
526
527/// Pending many-to-many synchronization action.
528pub struct SyncBelongsToMany<
529    Pivot,
530    ParentMeta: TypeMeta,
531    RelatedMeta: TypeMeta,
532    ParentKey,
533    RelatedKey,
534> {
535    pivot_table: query::Table<Pivot>,
536    parent_column: expression::Column<Pivot, ParentMeta>,
537    related_column: expression::Column<Pivot, RelatedMeta>,
538    parent_key: ParentKey,
539    related_keys: Vec<RelatedKey>,
540}
541
542pub struct SyncMorphToMany<
543    Pivot,
544    ParentMeta: TypeMeta,
545    RelatedMeta: TypeMeta,
546    ParentKey,
547    RelatedKey,
548> {
549    pivot_table: query::Table<Pivot>,
550    parent_column: expression::Column<Pivot, ParentMeta>,
551    type_column: expression::Column<Pivot, Text>,
552    related_column: expression::Column<Pivot, RelatedMeta>,
553    parent_key: ParentKey,
554    morph_type: &'static str,
555    related_keys: Vec<RelatedKey>,
556}
557
558/// Pending many-to-many toggle action.
559pub struct ToggleBelongsToMany<
560    Pivot,
561    ParentMeta: TypeMeta,
562    RelatedMeta: TypeMeta,
563    ParentKey,
564    RelatedKey,
565> {
566    pivot_table: query::Table<Pivot>,
567    parent_column: expression::Column<Pivot, ParentMeta>,
568    related_column: expression::Column<Pivot, RelatedMeta>,
569    parent_key: ParentKey,
570    related_keys: Vec<RelatedKey>,
571}
572
573pub struct ToggleMorphToMany<
574    Pivot,
575    ParentMeta: TypeMeta,
576    RelatedMeta: TypeMeta,
577    ParentKey,
578    RelatedKey,
579> {
580    pivot_table: query::Table<Pivot>,
581    parent_column: expression::Column<Pivot, ParentMeta>,
582    type_column: expression::Column<Pivot, Text>,
583    related_column: expression::Column<Pivot, RelatedMeta>,
584    parent_key: ParentKey,
585    morph_type: &'static str,
586    related_keys: Vec<RelatedKey>,
587}
588
589/// Pending many-to-many sync-without-detaching action.
590pub struct SyncWithoutDetachingBelongsToMany<
591    Pivot,
592    ParentMeta: TypeMeta,
593    RelatedMeta: TypeMeta,
594    ParentKey,
595    RelatedKey,
596> {
597    pivot_table: query::Table<Pivot>,
598    parent_column: expression::Column<Pivot, ParentMeta>,
599    related_column: expression::Column<Pivot, RelatedMeta>,
600    parent_key: ParentKey,
601    related_keys: Vec<RelatedKey>,
602}
603
604pub struct SyncWithoutDetachingMorphToMany<
605    Pivot,
606    ParentMeta: TypeMeta,
607    RelatedMeta: TypeMeta,
608    ParentKey,
609    RelatedKey,
610> {
611    pivot_table: query::Table<Pivot>,
612    parent_column: expression::Column<Pivot, ParentMeta>,
613    type_column: expression::Column<Pivot, Text>,
614    related_column: expression::Column<Pivot, RelatedMeta>,
615    parent_key: ParentKey,
616    morph_type: &'static str,
617    related_keys: Vec<RelatedKey>,
618}
619
620impl<
621    Pivot,
622    ParentMeta: TypeMeta + Comparable + Nullability,
623    RelatedMeta: TypeMeta + Comparable + Nullability,
624    ParentKey,
625    RelatedKey,
626> AttachBelongsToMany<Pivot, ParentMeta, RelatedMeta, ParentKey, RelatedKey>
627{
628    pub fn related_keys(&self) -> &[RelatedKey] {
629        &self.related_keys
630    }
631
632    pub fn insert_for(&self, related_key: &RelatedKey) -> builder::Insert<Pivot>
633    where
634        Pivot: Qrafting,
635        ParentKey: Clone + Compatible<ParentMeta>,
636        RelatedKey: Clone + Compatible<RelatedMeta>,
637        BinaryType<NullOf<ParentMeta>, NullOf<ParentMeta>>: PredicateType,
638        BinaryType<NullOf<RelatedMeta>, NullOf<RelatedMeta>>: PredicateType,
639    {
640        insert_into(self.pivot_table)
641            .values((
642                self.parent_column.eq(self.parent_key.clone()),
643                self.related_column.eq(related_key.clone()),
644            ))
645            .no_returning()
646    }
647
648    pub fn insert_many(&self, related_keys: Vec<RelatedKey>) -> builder::Insert<Pivot>
649    where
650        Pivot: Qrafting,
651        ParentKey: Clone + Compatible<ParentMeta>,
652        RelatedKey: Clone + Compatible<RelatedMeta>,
653        BinaryType<NullOf<ParentMeta>, NullOf<ParentMeta>>: PredicateType,
654        BinaryType<NullOf<RelatedMeta>, NullOf<RelatedMeta>>: PredicateType,
655    {
656        let values = build_many_to_many_values(
657            self.parent_column,
658            self.related_column,
659            &self.parent_key,
660            related_keys,
661        );
662
663        insert_into(self.pivot_table).values(values).no_returning()
664    }
665}
666
667impl<
668    Pivot,
669    ParentMeta: TypeMeta + Comparable + Nullability,
670    RelatedMeta: TypeMeta + Comparable + Nullability,
671    ParentKey,
672    RelatedKey,
673> AttachMorphToMany<Pivot, ParentMeta, RelatedMeta, ParentKey, RelatedKey>
674{
675    pub fn related_keys(&self) -> &[RelatedKey] {
676        &self.related_keys
677    }
678
679    pub fn insert_for(&self, related_key: &RelatedKey) -> builder::Insert<Pivot>
680    where
681        Pivot: Qrafting,
682        ParentKey: Clone + Compatible<ParentMeta>,
683        RelatedKey: Clone + Compatible<RelatedMeta>,
684        BinaryType<NullOf<ParentMeta>, NullOf<ParentMeta>>: PredicateType,
685        BinaryType<NullOf<RelatedMeta>, NullOf<RelatedMeta>>: PredicateType,
686    {
687        insert_into(self.pivot_table)
688            .values((
689                self.parent_column.eq(self.parent_key.clone()),
690                self.type_column.eq(self.morph_type),
691                self.related_column.eq(related_key.clone()),
692            ))
693            .no_returning()
694    }
695
696    pub fn insert_many(&self, related_keys: Vec<RelatedKey>) -> builder::Insert<Pivot>
697    where
698        Pivot: Qrafting,
699        ParentKey: Clone + Compatible<ParentMeta>,
700        RelatedKey: Clone + Compatible<RelatedMeta>,
701        BinaryType<NullOf<ParentMeta>, NullOf<ParentMeta>>: PredicateType,
702        BinaryType<NullOf<RelatedMeta>, NullOf<RelatedMeta>>: PredicateType,
703    {
704        let values = build_morph_to_many_values(MorphToManyValueSpec {
705            parent_column: self.parent_column,
706            type_column: self.type_column,
707            related_column: self.related_column,
708            parent_key: self.parent_key.clone(),
709            morph_type: self.morph_type,
710            related_keys,
711        });
712
713        insert_into(self.pivot_table).values(values).no_returning()
714    }
715}
716
717impl<
718    Pivot: Qrafting,
719    ParentMeta: Comparable + Nullability,
720    RelatedMeta: Comparable + Nullability,
721    ParentKey,
722    RelatedKey,
723> SyncBelongsToMany<Pivot, ParentMeta, RelatedMeta, ParentKey, RelatedKey>
724where
725    BinaryType<NullOf<ParentMeta>, NullOf<ParentMeta>>: PredicateType,
726    BinaryType<NullOf<RelatedMeta>, NullOf<RelatedMeta>>: PredicateType,
727{
728    pub fn existing_query(&self) -> QueryOf<RelatedKey>
729    where
730        Pivot: Qrafting,
731        RelatedKey: FromRow + LowerCompatible<RelatedMeta>,
732        ParentKey: Clone + LowerCompatible<ParentMeta>,
733    {
734        Query::from(self.pivot_table)
735            .select(self.related_column)
736            .filter(self.parent_column.eq(self.parent_key.clone()))
737            .typed::<RelatedKey>()
738    }
739
740    pub fn desired_keys(&self) -> &[RelatedKey] {
741        &self.related_keys
742    }
743
744    pub fn attach_insert(&self, related_key: &RelatedKey) -> builder::Insert<Pivot>
745    where
746        ParentKey: Clone + Compatible<ParentMeta>,
747        RelatedKey: Clone + Compatible<RelatedMeta>,
748    {
749        insert_into(self.pivot_table)
750            .values((
751                self.parent_column.eq(self.parent_key.clone()),
752                self.related_column.eq(related_key.clone()),
753            ))
754            .no_returning()
755    }
756
757    pub fn attach_insert_many(&self, related_keys: Vec<RelatedKey>) -> builder::Insert<Pivot>
758    where
759        ParentKey: Clone + Compatible<ParentMeta>,
760        RelatedKey: Clone + Compatible<RelatedMeta>,
761    {
762        let values = build_many_to_many_values(
763            self.parent_column,
764            self.related_column,
765            &self.parent_key,
766            related_keys,
767        );
768
769        insert_into(self.pivot_table).values(values).no_returning()
770    }
771
772    pub fn detach_query(&self, related_key: &RelatedKey) -> builder::Delete<Pivot>
773    where
774        ParentKey: Clone + LowerCompatible<ParentMeta>,
775        RelatedKey: Clone + LowerCompatible<RelatedMeta>,
776    {
777        builder::delete::delete_from(self.pivot_table)
778            .filter(self.parent_column.eq(self.parent_key.clone()))
779            .filter(self.related_column.eq(related_key.clone()))
780    }
781
782    pub fn detach_query_many(&self, related_keys: Vec<RelatedKey>) -> builder::Delete<Pivot>
783    where
784        ParentKey: Clone + LowerCompatible<ParentMeta>,
785        RelatedKey: Clone + LowerCompatible<RelatedMeta>,
786        UnaryType<NullOf<RelatedMeta>>: PredicateType,
787    {
788        builder::delete::delete_from(self.pivot_table)
789            .filter(self.parent_column.eq(self.parent_key.clone()))
790            .filter(self.related_column.in_(related_keys))
791    }
792}
793
794impl<
795    Pivot: Qrafting,
796    ParentMeta: Comparable + Nullability,
797    RelatedMeta: Comparable + Nullability,
798    ParentKey,
799    RelatedKey,
800> SyncMorphToMany<Pivot, ParentMeta, RelatedMeta, ParentKey, RelatedKey>
801where
802    BinaryType<NullOf<ParentMeta>, NullOf<ParentMeta>>: PredicateType,
803    BinaryType<NullOf<RelatedMeta>, NullOf<RelatedMeta>>: PredicateType,
804{
805    pub fn existing_query(&self) -> QueryOf<RelatedKey>
806    where
807        Pivot: Qrafting,
808        RelatedKey: FromRow + LowerCompatible<RelatedMeta>,
809        ParentKey: Clone + LowerCompatible<ParentMeta>,
810    {
811        Query::from(self.pivot_table)
812            .select(self.related_column)
813            .filter(self.parent_column.eq(self.parent_key.clone()))
814            .filter(self.type_column.eq(self.morph_type))
815            .typed::<RelatedKey>()
816    }
817
818    pub fn desired_keys(&self) -> &[RelatedKey] {
819        &self.related_keys
820    }
821
822    pub fn attach_insert(&self, related_key: &RelatedKey) -> builder::Insert<Pivot>
823    where
824        ParentKey: Clone + Compatible<ParentMeta>,
825        RelatedKey: Clone + Compatible<RelatedMeta>,
826    {
827        insert_into(self.pivot_table)
828            .values((
829                self.parent_column.eq(self.parent_key.clone()),
830                self.type_column.eq(self.morph_type),
831                self.related_column.eq(related_key.clone()),
832            ))
833            .no_returning()
834    }
835
836    pub fn attach_insert_many(&self, related_keys: Vec<RelatedKey>) -> builder::Insert<Pivot>
837    where
838        ParentKey: Clone + Compatible<ParentMeta>,
839        RelatedKey: Clone + Compatible<RelatedMeta>,
840    {
841        let values = build_morph_to_many_values(MorphToManyValueSpec {
842            parent_column: self.parent_column,
843            type_column: self.type_column,
844            related_column: self.related_column,
845            parent_key: self.parent_key.clone(),
846            morph_type: self.morph_type,
847            related_keys,
848        });
849
850        insert_into(self.pivot_table).values(values).no_returning()
851    }
852
853    pub fn detach_query(&self, related_key: &RelatedKey) -> builder::Delete<Pivot>
854    where
855        ParentKey: Clone + LowerCompatible<ParentMeta>,
856        RelatedKey: Clone + LowerCompatible<RelatedMeta>,
857    {
858        builder::delete::delete_from(self.pivot_table)
859            .filter(self.parent_column.eq(self.parent_key.clone()))
860            .filter(self.type_column.eq(self.morph_type))
861            .filter(self.related_column.eq(related_key.clone()))
862    }
863
864    pub fn detach_query_many(&self, related_keys: Vec<RelatedKey>) -> builder::Delete<Pivot>
865    where
866        ParentKey: Clone + LowerCompatible<ParentMeta>,
867        RelatedKey: Clone + LowerCompatible<RelatedMeta>,
868        UnaryType<NullOf<RelatedMeta>>: PredicateType,
869    {
870        builder::delete::delete_from(self.pivot_table)
871            .filter(self.parent_column.eq(self.parent_key.clone()))
872            .filter(self.type_column.eq(self.morph_type))
873            .filter(self.related_column.in_(related_keys))
874    }
875}
876
877macro_rules! impl_many_to_many_action_helpers {
878    ($name:ident) => {
879        impl<
880            Pivot: Qrafting,
881            ParentMeta: Comparable + Nullability,
882            RelatedMeta: Comparable + Nullability,
883            ParentKey,
884            RelatedKey,
885        > $name<Pivot, ParentMeta, RelatedMeta, ParentKey, RelatedKey>
886        where
887            BinaryType<NullOf<ParentMeta>, NullOf<ParentMeta>>: PredicateType,
888            BinaryType<NullOf<RelatedMeta>, NullOf<RelatedMeta>>: PredicateType,
889        {
890            pub fn existing_query(&self) -> QueryOf<RelatedKey>
891            where
892                Pivot: Qrafting,
893                RelatedKey: FromRow + LowerCompatible<RelatedMeta>,
894                ParentKey: Clone + LowerCompatible<ParentMeta>,
895            {
896                Query::from(self.pivot_table)
897                    .select(self.related_column)
898                    .filter(self.parent_column.eq(self.parent_key.clone()))
899                    .typed::<RelatedKey>()
900            }
901
902            pub fn desired_keys(&self) -> &[RelatedKey] {
903                &self.related_keys
904            }
905
906            pub fn attach_insert(&self, related_key: &RelatedKey) -> builder::Insert<Pivot>
907            where
908                ParentKey: Clone + Compatible<ParentMeta>,
909                RelatedKey: Clone + Compatible<RelatedMeta>,
910            {
911                insert_into(self.pivot_table)
912                    .values((
913                        self.parent_column.eq(self.parent_key.clone()),
914                        self.related_column.eq(related_key.clone()),
915                    ))
916                    .no_returning()
917            }
918
919            pub fn detach_query(&self, related_key: &RelatedKey) -> builder::Delete<Pivot>
920            where
921                ParentKey: Clone + LowerCompatible<ParentMeta>,
922                RelatedKey: Clone + LowerCompatible<RelatedMeta>,
923            {
924                builder::delete::delete_from(self.pivot_table)
925                    .filter(self.parent_column.eq(self.parent_key.clone()))
926                    .filter(self.related_column.eq(related_key.clone()))
927            }
928
929            pub fn attach_insert_many(
930                &self,
931                related_keys: Vec<RelatedKey>,
932            ) -> builder::Insert<Pivot>
933            where
934                ParentKey: Clone + Compatible<ParentMeta>,
935                RelatedKey: Clone + Compatible<RelatedMeta>,
936            {
937                let values = build_many_to_many_values(
938                    self.parent_column,
939                    self.related_column,
940                    &self.parent_key,
941                    related_keys,
942                );
943
944                insert_into(self.pivot_table).values(values).no_returning()
945            }
946
947            pub fn detach_query_many(&self, related_keys: Vec<RelatedKey>) -> builder::Delete<Pivot>
948            where
949                ParentKey: Clone + LowerCompatible<ParentMeta>,
950                RelatedKey: Clone + LowerCompatible<RelatedMeta>,
951                UnaryType<NullOf<RelatedMeta>>: PredicateType,
952            {
953                builder::delete::delete_from(self.pivot_table)
954                    .filter(self.parent_column.eq(self.parent_key.clone()))
955                    .filter(self.related_column.in_(related_keys))
956            }
957        }
958    };
959}
960
961impl_many_to_many_action_helpers!(ToggleBelongsToMany);
962impl_many_to_many_action_helpers!(SyncWithoutDetachingBelongsToMany);
963
964macro_rules! impl_morph_to_many_action_helpers {
965    ($name:ident) => {
966        impl<
967            Pivot: Qrafting,
968            ParentMeta: Comparable + Nullability,
969            RelatedMeta: Comparable + Nullability,
970            ParentKey,
971            RelatedKey,
972        > $name<Pivot, ParentMeta, RelatedMeta, ParentKey, RelatedKey>
973        where
974            BinaryType<NullOf<ParentMeta>, NullOf<ParentMeta>>: PredicateType,
975            BinaryType<NullOf<RelatedMeta>, NullOf<RelatedMeta>>: PredicateType,
976        {
977            pub fn existing_query(&self) -> QueryOf<RelatedKey>
978            where
979                Pivot: Qrafting,
980                RelatedKey: FromRow + LowerCompatible<RelatedMeta>,
981                ParentKey: Clone + LowerCompatible<ParentMeta>,
982            {
983                Query::from(self.pivot_table)
984                    .select(self.related_column)
985                    .filter(self.parent_column.eq(self.parent_key.clone()))
986                    .filter(self.type_column.eq(self.morph_type))
987                    .typed::<RelatedKey>()
988            }
989
990            pub fn desired_keys(&self) -> &[RelatedKey] {
991                &self.related_keys
992            }
993
994            pub fn attach_insert(&self, related_key: &RelatedKey) -> builder::Insert<Pivot>
995            where
996                ParentKey: Clone + Compatible<ParentMeta>,
997                RelatedKey: Clone + Compatible<RelatedMeta>,
998            {
999                insert_into(self.pivot_table)
1000                    .values((
1001                        self.parent_column.eq(self.parent_key.clone()),
1002                        self.type_column.eq(self.morph_type),
1003                        self.related_column.eq(related_key.clone()),
1004                    ))
1005                    .no_returning()
1006            }
1007
1008            pub fn detach_query(&self, related_key: &RelatedKey) -> builder::Delete<Pivot>
1009            where
1010                ParentKey: Clone + LowerCompatible<ParentMeta>,
1011                RelatedKey: Clone + LowerCompatible<RelatedMeta>,
1012            {
1013                builder::delete::delete_from(self.pivot_table)
1014                    .filter(self.parent_column.eq(self.parent_key.clone()))
1015                    .filter(self.type_column.eq(self.morph_type))
1016                    .filter(self.related_column.eq(related_key.clone()))
1017            }
1018
1019            pub fn attach_insert_many(
1020                &self,
1021                related_keys: Vec<RelatedKey>,
1022            ) -> builder::Insert<Pivot>
1023            where
1024                ParentKey: Clone + Compatible<ParentMeta>,
1025                RelatedKey: Clone + Compatible<RelatedMeta>,
1026            {
1027                let values = build_morph_to_many_values(MorphToManyValueSpec {
1028                    parent_column: self.parent_column,
1029                    type_column: self.type_column,
1030                    related_column: self.related_column,
1031                    parent_key: self.parent_key.clone(),
1032                    morph_type: self.morph_type,
1033                    related_keys,
1034                });
1035
1036                insert_into(self.pivot_table).values(values).no_returning()
1037            }
1038
1039            pub fn detach_query_many(&self, related_keys: Vec<RelatedKey>) -> builder::Delete<Pivot>
1040            where
1041                ParentKey: Clone + LowerCompatible<ParentMeta>,
1042                RelatedKey: Clone + LowerCompatible<RelatedMeta>,
1043                UnaryType<NullOf<RelatedMeta>>: PredicateType,
1044            {
1045                builder::delete::delete_from(self.pivot_table)
1046                    .filter(self.parent_column.eq(self.parent_key.clone()))
1047                    .filter(self.type_column.eq(self.morph_type))
1048                    .filter(self.related_column.in_(related_keys))
1049            }
1050        }
1051    };
1052}
1053
1054impl_morph_to_many_action_helpers!(ToggleMorphToMany);
1055impl_morph_to_many_action_helpers!(SyncWithoutDetachingMorphToMany);
1056
1057/// Shared boxed error type used by higher-level integrations.
1058pub type BoxDynError = Box<dyn ::std::error::Error + 'static + Send + Sync>;
1059
1060/// Marker used by builders for values that have not been provided yet.
1061pub struct Unset;
1062/// Marker used by builders for values that are already present.
1063pub struct Set;
1064
1065#[cfg(test)]
1066mod tests {
1067    use crate::{BigInt, FromRow, Nullable, Qrafting, Text, expression::Column, query::Table};
1068
1069    #[allow(dead_code)]
1070    #[derive(Debug)]
1071    pub struct User {
1072        pub id: i64,
1073        pub name: Option<String>,
1074        pub username: String,
1075    }
1076
1077    impl FromRow for User {
1078        fn from_row(row: &crate::quex::Row) -> crate::quex::Result<Self> {
1079            Ok(Self {
1080                id: row.get("id")?,
1081                name: row.get("name")?,
1082                username: row.get("username")?,
1083            })
1084        }
1085    }
1086
1087    impl Qrafting for User {
1088        type Schema = ();
1089        type QueryPolicy = crate::DefaultQueryPolicy<Self>;
1090        const FIELD_COUNT: usize = 3;
1091        const TABLE: &'static str = "users";
1092    }
1093
1094    #[allow(non_upper_case_globals)]
1095    pub const table: Table<User> = Table::new("users");
1096    #[allow(non_upper_case_globals)]
1097    pub const id: Column<User, BigInt> = Column::new("id");
1098    #[allow(non_upper_case_globals)]
1099    pub const name: Column<User, Nullable<Text>> = Column::new("name");
1100    #[allow(non_upper_case_globals)]
1101    pub const username: Column<User, Text> = Column::new("username");
1102}