cratestack_sqlx/query/read/
find_unique.rs1use cratestack_core::{CratestackContext, CratestackError};
5use cratestack_sql::ReadSource;
6
7use crate::query::support::{ReadPolicyKind, push_scoped_conditions};
8use crate::render::render_read_policy_sql;
9use crate::{SqlxRuntime, sqlx};
10
11#[derive(Clone)]
12pub struct FindUnique<'a, M: 'static, PK: 'static> {
13 pub(crate) runtime: &'a SqlxRuntime,
14 pub(crate) descriptor: &'static dyn ReadSource<M, PK>,
15 pub(crate) id: PK,
16 pub(crate) for_update: bool,
17 pub(crate) policy_kind: ReadPolicyKind,
18}
19
20impl<'a, M: 'static, PK: 'static> FindUnique<'a, M, PK> {
21 pub fn for_update(mut self) -> Self {
24 self.for_update = true;
25 self
26 }
27
28 pub fn as_detail(mut self) -> Self {
32 self.policy_kind = ReadPolicyKind::Detail;
33 self
34 }
35
36 pub fn as_list(mut self) -> Self {
40 self.policy_kind = ReadPolicyKind::List;
41 self
42 }
43
44 pub fn preview_sql(&self) -> String {
45 let mut sql = format!(
46 "SELECT {} FROM {} WHERE {} = $1 LIMIT 1",
47 self.descriptor.select_projection(),
48 self.descriptor.table_name(),
49 self.descriptor.primary_key(),
50 );
51 if self.for_update {
52 sql.push_str(" FOR UPDATE");
53 }
54 sql
55 }
56
57 pub fn preview_scoped_sql(&self, ctx: &CratestackContext) -> String {
58 let mut sql = format!(
59 "SELECT {} FROM {}",
60 self.descriptor.select_projection(),
61 self.descriptor.table_name(),
62 );
63 let mut bind_index = 1usize;
64 let (allow, deny) = match self.policy_kind {
65 ReadPolicyKind::List => (
66 self.descriptor.read_allow_policies(),
67 self.descriptor.read_deny_policies(),
68 ),
69 ReadPolicyKind::Detail => (
70 self.descriptor.detail_allow_policies(),
71 self.descriptor.detail_deny_policies(),
72 ),
73 };
74 if let Some(policy_clause) = render_read_policy_sql(allow, deny, ctx, &mut bind_index) {
75 sql.push_str(&format!(
76 " WHERE {policy_clause} AND {} = ${bind_index} LIMIT 1",
77 self.descriptor.primary_key()
78 ));
79 } else {
80 sql.push_str(&format!(
81 " WHERE {} = ${bind_index} LIMIT 1",
82 self.descriptor.primary_key()
83 ));
84 }
85 if self.for_update {
86 sql.push_str(" FOR UPDATE");
87 }
88 sql
89 }
90
91 pub async fn run(self, ctx: &CratestackContext) -> Result<Option<M>, CratestackError>
92 where
93 for<'r> M: Send + Unpin + sqlx::FromRow<'r, sqlx::postgres::PgRow>,
94 PK: Send + sqlx::Type<sqlx::Postgres> + for<'q> sqlx::Encode<'q, sqlx::Postgres>,
95 {
96 if let Some(bound) = self.runtime.bound() {
99 return crate::bound::in_bound_savepoint!(bound, |sp| self.run_in_tx(sp, ctx));
100 }
101 let mut query = sqlx::QueryBuilder::<sqlx::Postgres>::new("SELECT ");
102 query
103 .push(self.descriptor.select_projection())
104 .push(" FROM ")
105 .push(self.descriptor.table_name());
106 push_scoped_conditions(
107 &mut query,
108 self.descriptor,
109 &[],
110 Some((self.descriptor.primary_key(), self.id)),
111 ctx,
112 self.policy_kind,
113 );
114 query.push(" LIMIT 1");
115 if self.for_update {
116 query.push(" FOR UPDATE");
117 }
118
119 query
120 .build_query_as::<M>()
121 .fetch_optional(self.runtime.pool())
122 .await
123 .map_err(crate::error::cratestack_error_from_sqlx)
124 }
125
126 pub async fn run_in_tx<'tx>(
127 self,
128 tx: &mut sqlx::Transaction<'tx, sqlx::Postgres>,
129 ctx: &CratestackContext,
130 ) -> Result<Option<M>, CratestackError>
131 where
132 for<'r> M: Send + Unpin + sqlx::FromRow<'r, sqlx::postgres::PgRow>,
133 PK: Send + sqlx::Type<sqlx::Postgres> + for<'q> sqlx::Encode<'q, sqlx::Postgres>,
134 {
135 let mut query = sqlx::QueryBuilder::<sqlx::Postgres>::new("SELECT ");
136 query
137 .push(self.descriptor.select_projection())
138 .push(" FROM ")
139 .push(self.descriptor.table_name());
140 push_scoped_conditions(
141 &mut query,
142 self.descriptor,
143 &[],
144 Some((self.descriptor.primary_key(), self.id)),
145 ctx,
146 self.policy_kind,
147 );
148 query.push(" LIMIT 1");
149 if self.for_update {
150 query.push(" FOR UPDATE");
151 }
152
153 query
154 .build_query_as::<M>()
155 .fetch_optional(&mut **tx)
156 .await
157 .map_err(crate::error::cratestack_error_from_sqlx)
158 }
159}