cratestack_sqlx/query/read/
projected_find_unique.rs1use cratestack_core::{CoolContext, CoolError};
7use cratestack_sql::IntoColumnName;
8
9use crate::query::support::{ReadPolicyKind, push_scoped_conditions};
10use crate::{ModelDescriptor, SqlxRuntime, sqlx};
11
12use super::find_unique::FindUnique;
13
14#[derive(Debug, Clone)]
15pub struct ProjectedFindUnique<'a, M: 'static, PK: 'static> {
16 runtime: &'a SqlxRuntime,
17 descriptor: &'static ModelDescriptor<M, PK>,
18 id: PK,
19 selected: Vec<&'static str>,
20 policy_kind: ReadPolicyKind,
21 for_update: bool,
22}
23
24impl<'a, M: 'static, PK: 'static> ProjectedFindUnique<'a, M, PK> {
25 pub fn as_detail(mut self) -> Self {
26 self.policy_kind = ReadPolicyKind::Detail;
27 self
28 }
29
30 pub fn as_list(mut self) -> Self {
31 self.policy_kind = ReadPolicyKind::List;
32 self
33 }
34
35 pub fn for_update(mut self) -> Self {
36 self.for_update = true;
37 self
38 }
39
40 pub async fn run(
41 self,
42 ctx: &CoolContext,
43 ) -> Result<Option<cratestack_sql::Projection<M>>, CoolError>
44 where
45 M: crate::FromPartialPgRow,
46 PK: Send + sqlx::Type<sqlx::Postgres> + for<'q> sqlx::Encode<'q, sqlx::Postgres>,
47 {
48 let mut query = sqlx::QueryBuilder::<sqlx::Postgres>::new("SELECT ");
49 query
50 .push(self.descriptor.select_projection_subset(&self.selected))
51 .push(" FROM ")
52 .push(self.descriptor.table_name);
53 push_scoped_conditions(
54 &mut query,
55 self.descriptor,
56 &[],
57 Some((self.descriptor.primary_key, self.id)),
58 ctx,
59 self.policy_kind,
60 );
61 query.push(" LIMIT 1");
62 if self.for_update {
63 query.push(" FOR UPDATE");
64 }
65
66 let row = query
67 .build()
68 .fetch_optional(self.runtime.pool())
69 .await
70 .map_err(|error| CoolError::Database(error.to_string()))?;
71 decode_optional(row, &self.selected)
72 }
73
74 pub async fn run_in_tx<'tx>(
75 self,
76 tx: &mut sqlx::Transaction<'tx, sqlx::Postgres>,
77 ctx: &CoolContext,
78 ) -> Result<Option<cratestack_sql::Projection<M>>, CoolError>
79 where
80 M: crate::FromPartialPgRow,
81 PK: Send + sqlx::Type<sqlx::Postgres> + for<'q> sqlx::Encode<'q, sqlx::Postgres>,
82 {
83 let mut query = sqlx::QueryBuilder::<sqlx::Postgres>::new("SELECT ");
84 query
85 .push(self.descriptor.select_projection_subset(&self.selected))
86 .push(" FROM ")
87 .push(self.descriptor.table_name);
88 push_scoped_conditions(
89 &mut query,
90 self.descriptor,
91 &[],
92 Some((self.descriptor.primary_key, self.id)),
93 ctx,
94 self.policy_kind,
95 );
96 query.push(" LIMIT 1");
97 if self.for_update {
98 query.push(" FOR UPDATE");
99 }
100
101 let row = query
102 .build()
103 .fetch_optional(&mut **tx)
104 .await
105 .map_err(|error| CoolError::Database(error.to_string()))?;
106 decode_optional(row, &self.selected)
107 }
108}
109
110fn decode_optional<M>(
111 row: Option<sqlx::postgres::PgRow>,
112 selected: &[&'static str],
113) -> Result<Option<cratestack_sql::Projection<M>>, CoolError>
114where
115 M: crate::FromPartialPgRow,
116{
117 match row {
118 Some(row) => {
119 let value = M::decode_partial_pg_row(&row, selected)
120 .map_err(|error| CoolError::Database(error.to_string()))?;
121 Ok(Some(cratestack_sql::Projection {
122 value,
123 selected: selected.to_vec(),
124 }))
125 }
126 None => Ok(None),
127 }
128}
129
130impl<'a, M: 'static, PK: 'static> FindUnique<'a, M, PK> {
131 pub fn select<I, C>(self, columns: I) -> ProjectedFindUnique<'a, M, PK>
135 where
136 I: IntoIterator<Item = C>,
137 C: IntoColumnName,
138 {
139 ProjectedFindUnique {
140 runtime: self.runtime,
141 descriptor: self.descriptor,
142 id: self.id,
143 selected: columns
144 .into_iter()
145 .map(IntoColumnName::into_column_name)
146 .collect(),
147 policy_kind: self.policy_kind,
148 for_update: self.for_update,
149 }
150 }
151}