cratestack_sqlx/query/read/
projected_find_unique.rs1use cratestack_core::{CratestackContext, CratestackError};
7use cratestack_sql::{IntoColumnName, ReadSource};
8
9use crate::query::support::{ReadPolicyKind, push_scoped_conditions};
10use crate::{SqlxRuntime, sqlx};
11
12use super::find_unique::FindUnique;
13
14#[derive(Clone)]
15pub struct ProjectedFindUnique<'a, M: 'static, PK: 'static> {
16 runtime: &'a SqlxRuntime,
17 descriptor: &'static dyn ReadSource<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: &CratestackContext,
43 ) -> Result<Option<cratestack_sql::Projection<M>>, CratestackError>
44 where
45 M: crate::FromPartialPgRow,
46 PK: Send + sqlx::Type<sqlx::Postgres> + for<'q> sqlx::Encode<'q, sqlx::Postgres>,
47 {
48 if let Some(bound) = self.runtime.bound() {
51 return crate::bound::in_bound_savepoint!(bound, |sp| self.run_in_tx(sp, ctx));
52 }
53 let mut query = sqlx::QueryBuilder::<sqlx::Postgres>::new("SELECT ");
54 query
55 .push(self.descriptor.select_projection_subset(&self.selected))
56 .push(" FROM ")
57 .push(self.descriptor.table_name());
58 push_scoped_conditions(
59 &mut query,
60 self.descriptor,
61 &[],
62 Some((self.descriptor.primary_key(), self.id)),
63 ctx,
64 self.policy_kind,
65 );
66 query.push(" LIMIT 1");
67 if self.for_update {
68 query.push(" FOR UPDATE");
69 }
70
71 let row = query
72 .build()
73 .fetch_optional(self.runtime.pool())
74 .await
75 .map_err(crate::error::cratestack_error_from_sqlx)?;
76 decode_optional(row, &self.selected)
77 }
78
79 pub async fn run_in_tx<'tx>(
80 self,
81 tx: &mut sqlx::Transaction<'tx, sqlx::Postgres>,
82 ctx: &CratestackContext,
83 ) -> Result<Option<cratestack_sql::Projection<M>>, CratestackError>
84 where
85 M: crate::FromPartialPgRow,
86 PK: Send + sqlx::Type<sqlx::Postgres> + for<'q> sqlx::Encode<'q, sqlx::Postgres>,
87 {
88 let mut query = sqlx::QueryBuilder::<sqlx::Postgres>::new("SELECT ");
89 query
90 .push(self.descriptor.select_projection_subset(&self.selected))
91 .push(" FROM ")
92 .push(self.descriptor.table_name());
93 push_scoped_conditions(
94 &mut query,
95 self.descriptor,
96 &[],
97 Some((self.descriptor.primary_key(), self.id)),
98 ctx,
99 self.policy_kind,
100 );
101 query.push(" LIMIT 1");
102 if self.for_update {
103 query.push(" FOR UPDATE");
104 }
105
106 let row = query
107 .build()
108 .fetch_optional(&mut **tx)
109 .await
110 .map_err(crate::error::cratestack_error_from_sqlx)?;
111 decode_optional(row, &self.selected)
112 }
113}
114
115fn decode_optional<M>(
116 row: Option<sqlx::postgres::PgRow>,
117 selected: &[&'static str],
118) -> Result<Option<cratestack_sql::Projection<M>>, CratestackError>
119where
120 M: crate::FromPartialPgRow,
121{
122 match row {
123 Some(row) => {
124 let value = M::decode_partial_pg_row(&row, selected)
125 .map_err(crate::error::cratestack_error_from_sqlx)?;
126 Ok(Some(cratestack_sql::Projection {
127 value,
128 selected: selected.to_vec(),
129 }))
130 }
131 None => Ok(None),
132 }
133}
134
135impl<'a, M: 'static, PK: 'static> FindUnique<'a, M, PK> {
136 pub fn select<I, C>(self, columns: I) -> ProjectedFindUnique<'a, M, PK>
140 where
141 I: IntoIterator<Item = C>,
142 C: IntoColumnName,
143 {
144 ProjectedFindUnique {
145 runtime: self.runtime,
146 descriptor: self.descriptor,
147 id: self.id,
148 selected: columns
149 .into_iter()
150 .map(IntoColumnName::into_column_name)
151 .collect(),
152 policy_kind: self.policy_kind,
153 for_update: self.for_update,
154 }
155 }
156}