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