cratestack_sqlx/query/read/
find_unique.rs1use cratestack_core::{CoolContext, CoolError};
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: &CoolContext) -> 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: &CoolContext) -> Result<Option<M>, CoolError>
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 let mut query = sqlx::QueryBuilder::<sqlx::Postgres>::new("SELECT ");
97 query
98 .push(self.descriptor.select_projection())
99 .push(" FROM ")
100 .push(self.descriptor.table_name());
101 push_scoped_conditions(
102 &mut query,
103 self.descriptor,
104 &[],
105 Some((self.descriptor.primary_key(), self.id)),
106 ctx,
107 self.policy_kind,
108 );
109 query.push(" LIMIT 1");
110 if self.for_update {
111 query.push(" FOR UPDATE");
112 }
113
114 query
115 .build_query_as::<M>()
116 .fetch_optional(self.runtime.pool())
117 .await
118 .map_err(|error| CoolError::Database(error.to_string()))
119 }
120
121 pub async fn run_in_tx<'tx>(
122 self,
123 tx: &mut sqlx::Transaction<'tx, sqlx::Postgres>,
124 ctx: &CoolContext,
125 ) -> Result<Option<M>, CoolError>
126 where
127 for<'r> M: Send + Unpin + sqlx::FromRow<'r, sqlx::postgres::PgRow>,
128 PK: Send + sqlx::Type<sqlx::Postgres> + for<'q> sqlx::Encode<'q, sqlx::Postgres>,
129 {
130 let mut query = sqlx::QueryBuilder::<sqlx::Postgres>::new("SELECT ");
131 query
132 .push(self.descriptor.select_projection())
133 .push(" FROM ")
134 .push(self.descriptor.table_name());
135 push_scoped_conditions(
136 &mut query,
137 self.descriptor,
138 &[],
139 Some((self.descriptor.primary_key(), self.id)),
140 ctx,
141 self.policy_kind,
142 );
143 query.push(" LIMIT 1");
144 if self.for_update {
145 query.push(" FOR UPDATE");
146 }
147
148 query
149 .build_query_as::<M>()
150 .fetch_optional(&mut **tx)
151 .await
152 .map_err(|error| CoolError::Database(error.to_string()))
153 }
154}