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