Skip to main content

cratestack_sqlx/delegate/
model.rs

1//! `ModelDelegate` — the per-model entry point handed out by the
2//! generated `Cratestack::<model>()` accessor. Hosts the unbound (no
3//! `CoolContext`) builders for every CRUD/aggregate primitive. Batch
4//! and authorize methods live in [`super::model_batch`] and
5//! [`super::model_authorize`] respectively.
6
7use cratestack_core::CoolContext;
8
9use crate::{
10    Aggregate, CreateRecord, DeleteMany, DeleteRecord, FindMany, FindUnique, ModelDescriptor,
11    SqlxRuntime, UpdateMany, UpdateRecord, UpsertRecord,
12};
13
14use super::scoped::ScopedModelDelegate;
15
16#[derive(Debug, Clone, Copy)]
17pub struct ModelDelegate<'a, M: 'static, PK: 'static> {
18    pub(super) runtime: &'a SqlxRuntime,
19    pub(super) descriptor: &'static ModelDescriptor<M, PK>,
20}
21
22impl<'a, M: 'static, PK: 'static> ModelDelegate<'a, M, PK> {
23    pub fn new(runtime: &'a SqlxRuntime, descriptor: &'static ModelDescriptor<M, PK>) -> Self {
24        Self {
25            runtime,
26            descriptor,
27        }
28    }
29
30    pub fn descriptor(&self) -> &'static ModelDescriptor<M, PK> {
31        self.descriptor
32    }
33
34    pub fn bind(self, ctx: CoolContext) -> ScopedModelDelegate<'a, M, PK> {
35        ScopedModelDelegate::new(self, ctx)
36    }
37
38    pub fn find_many(&self) -> FindMany<'a, M, PK> {
39        FindMany {
40            runtime: self.runtime,
41            descriptor: self.descriptor,
42            filters: Vec::new(),
43            order_by: Vec::new(),
44            limit: None,
45            offset: None,
46            for_update: false,
47        }
48    }
49
50    pub fn find_unique(&self, id: PK) -> FindUnique<'a, M, PK> {
51        FindUnique {
52            runtime: self.runtime,
53            descriptor: self.descriptor,
54            id,
55            for_update: false,
56            policy_kind: crate::query::ReadPolicyKind::Detail,
57        }
58    }
59
60    pub fn create<I>(&self, input: I) -> CreateRecord<'a, M, PK, I> {
61        CreateRecord {
62            runtime: self.runtime,
63            descriptor: self.descriptor,
64            input,
65        }
66    }
67
68    /// Insert-or-update on primary-key conflict. Available only on
69    /// models whose `@id` field is client-supplied (no `@default(...)`);
70    /// attempting to call this on a model with a server-generated PK
71    /// is a compile error.
72    pub fn upsert<I>(&self, input: I) -> UpsertRecord<'a, M, PK, I> {
73        UpsertRecord {
74            runtime: self.runtime,
75            descriptor: self.descriptor,
76            input,
77            conflict_target: cratestack_sql::ConflictTarget::PrimaryKey,
78        }
79    }
80
81    pub fn update(&self, id: PK) -> UpdateRecord<'a, M, PK> {
82        UpdateRecord {
83            runtime: self.runtime,
84            descriptor: self.descriptor,
85            id,
86        }
87    }
88
89    /// Bulk UPDATE by predicate. Refuses to run without at least one
90    /// filter — table-wide bulk updates are a footgun that should be
91    /// written in raw SQL.
92    pub fn update_many(&self) -> UpdateMany<'a, M, PK> {
93        UpdateMany {
94            runtime: self.runtime,
95            descriptor: self.descriptor,
96            filters: Vec::new(),
97        }
98    }
99
100    pub fn delete(&self, id: PK) -> DeleteRecord<'a, M, PK> {
101        DeleteRecord {
102            runtime: self.runtime,
103            descriptor: self.descriptor,
104            id,
105        }
106    }
107
108    /// Side-effect-free aggregate read. Returns a builder that
109    /// branches into `.count()` / `.sum(col)` / `.avg(col)` /
110    /// `.min(col)` / `.max(col)`. Aggregates apply the read policy
111    /// AND soft-delete column so the result always describes rows the
112    /// caller could retrieve via `find_many`.
113    pub fn aggregate(&self) -> Aggregate<'a, M, PK> {
114        Aggregate {
115            runtime: self.runtime,
116            descriptor: self.descriptor,
117        }
118    }
119
120    /// Bulk DELETE by predicate. Mirrors `update_many`: applies the
121    /// delete policy and soft-delete column (if any), fans audit +
122    /// outbox out per-row via RETURNING, refuses to run without at
123    /// least one filter.
124    pub fn delete_many(&self) -> DeleteMany<'a, M, PK> {
125        DeleteMany {
126            runtime: self.runtime,
127            descriptor: self.descriptor,
128            filters: Vec::new(),
129        }
130    }
131}