1use crate::{
22 db,
23 error::EntityHydrationError,
24 events::{EntityEvents, HydrationRow},
25 one_time_executor::IntoOneTimeExecutor,
26 snapshot::NO_SNAPSHOT_FINGERPRINT,
27 traits::*,
28 tree_query::{TreeQuerySource, build_tree_query, partition_by_tag, snapshot_fingerprints},
29};
30
31pub struct EsQuery<
41 'q,
42 Repo,
43 Flavor,
44 F,
45 A,
46 R = crate::events::GenericEvent<
47 <<<Repo as EsRepo>::Entity as EsEntity>::Event as EsEvent>::EntityId,
48 >,
49> where
50 Repo: EsRepo,
51{
52 inner: sqlx::query::Map<'q, db::Db, F, A>,
53 source: TreeQuerySource<<<<Repo as EsRepo>::Entity as EsEntity>::Event as EsEvent>::EntityId>,
54 _repo: std::marker::PhantomData<Repo>,
55 _flavor: std::marker::PhantomData<Flavor>,
56 _row: std::marker::PhantomData<fn() -> R>,
57}
58
59pub struct EsQueryFlavorFlat;
61
62pub struct EsQueryFlavorNested;
64
65impl<'q, Repo, Flavor, F, A, R> EsQuery<'q, Repo, Flavor, F, A, R>
66where
67 Repo: EsRepo,
68 <<<Repo as EsRepo>::Entity as EsEntity>::Event as EsEvent>::EntityId: Unpin,
69 F: FnMut(db::Row) -> Result<R, sqlx::Error> + Send,
70 R: Into<HydrationRow<<<<Repo as EsRepo>::Entity as EsEntity>::Event as EsEvent>::EntityId>>
71 + Send
72 + Unpin,
73 A: 'q + Send + sqlx::IntoArguments<'q, db::Db>,
74{
75 pub fn new(
76 query: sqlx::query::Map<'q, db::Db, F, A>,
77 source: TreeQuerySource<
78 <<<Repo as EsRepo>::Entity as EsEntity>::Event as EsEvent>::EntityId,
79 >,
80 ) -> Self {
81 Self {
82 inner: query,
83 source,
84 _repo: std::marker::PhantomData,
85 _flavor: std::marker::PhantomData,
86 _row: std::marker::PhantomData,
87 }
88 }
89
90 async fn fetch_optional_inner<E: From<sqlx::Error> + From<EntityHydrationError>>(
91 self,
92 op: impl IntoOneTimeExecutor<'_>,
93 ) -> Result<Option<<Repo as EsRepo>::Entity>, E> {
94 let executor = op.into_executor();
95 let rows = executor.fetch_all(self.inner).await?;
96 if rows.is_empty() {
97 return Ok(None);
98 }
99
100 Ok(EntityEvents::load_first(rows.into_iter())?)
101 }
102
103 async fn fetch_n_inner<E: From<sqlx::Error> + From<EntityHydrationError>>(
104 self,
105 op: impl IntoOneTimeExecutor<'_>,
106 first: usize,
107 ) -> Result<(Vec<<Repo as EsRepo>::Entity>, bool), E> {
108 let executor = op.into_executor();
109 let rows = executor.fetch_all(self.inner).await?;
110 Ok(EntityEvents::load_n(rows.into_iter(), first)?)
111 }
112
113 async fn fetch_tree_rows<E: From<sqlx::Error>>(
114 self,
115 op: impl IntoOneTimeExecutor<'_>,
116 include_deleted: bool,
117 ) -> Result<
118 (
119 Vec<HydrationRow<<<<Repo as EsRepo>::Entity as EsEntity>::Event as EsEvent>::EntityId>>,
120 std::collections::HashMap<i32, Vec<db::Row>>,
121 ),
122 E,
123 > {
124 let executor = op.into_executor();
125 let spec = <Repo as EsRepo>::nested_tree_spec();
126 let sql = build_tree_query(
127 self.source.user_sql,
128 self.source.order_by_cols,
129 &spec,
130 include_deleted,
131 self.source.n_user_args + 1,
132 );
133
134 let mut inner = self.inner;
135 let mut args = sqlx::Execute::take_arguments(&mut inner)
136 .map_err(sqlx::Error::Encode)?
137 .unwrap_or_default();
138
139 let full_history = spec.snapshot_table_name.is_some()
145 && self.source.snapshot_fingerprint == NO_SNAPSHOT_FINGERPRINT;
146 let mut fingerprints = snapshot_fingerprints(&spec).into_iter();
150 if spec.snapshot_table_name.is_some() {
151 fingerprints.next();
152 }
153 for fp in fingerprints {
154 let bind = if full_history {
155 NO_SNAPSHOT_FINGERPRINT
156 } else {
157 fp
158 };
159 sqlx::Arguments::add(&mut args, bind).map_err(sqlx::Error::Encode)?;
160 }
161
162 let rows: Vec<db::Row> = sqlx::query_with::<db::Db, _>(&sql, args)
163 .fetch_all(executor)
164 .await?;
165 let mut by_tag = partition_by_tag(rows)?;
166 let root_rows = by_tag.remove(&0).unwrap_or_default();
167 let root = root_rows
168 .iter()
169 .map(self.source.decode)
170 .collect::<Result<Vec<_>, sqlx::Error>>()?;
171 Ok((root, by_tag))
172 }
173}
174
175impl<'q, Repo, F, A, R> EsQuery<'q, Repo, EsQueryFlavorFlat, F, A, R>
176where
177 Repo: EsRepo,
178 <<<Repo as EsRepo>::Entity as EsEntity>::Event as EsEvent>::EntityId: Unpin,
179 F: FnMut(db::Row) -> Result<R, sqlx::Error> + Send,
180 R: Into<HydrationRow<<<<Repo as EsRepo>::Entity as EsEntity>::Event as EsEvent>::EntityId>>
181 + Send
182 + Unpin,
183 A: 'q + Send + sqlx::IntoArguments<'q, db::Db>,
184{
185 pub async fn fetch_optional(
189 self,
190 op: impl IntoOneTimeExecutor<'_>,
191 ) -> Result<Option<<Repo as EsRepo>::Entity>, <Repo as EsRepo>::QueryError> {
192 self.fetch_optional_inner(op).await
193 }
194
195 pub async fn fetch_n(
200 self,
201 op: impl IntoOneTimeExecutor<'_>,
202 first: usize,
203 ) -> Result<(Vec<<Repo as EsRepo>::Entity>, bool), <Repo as EsRepo>::QueryError> {
204 self.fetch_n_inner(op, first).await
205 }
206}
207
208impl<'q, Repo, F, A, R> EsQuery<'q, Repo, EsQueryFlavorNested, F, A, R>
209where
210 Repo: EsRepo,
211 <<<Repo as EsRepo>::Entity as EsEntity>::Event as EsEvent>::EntityId: Unpin,
212 F: FnMut(db::Row) -> Result<R, sqlx::Error> + Send,
213 R: Into<HydrationRow<<<<Repo as EsRepo>::Entity as EsEntity>::Event as EsEvent>::EntityId>>
214 + Send
215 + Unpin,
216 A: 'q + Send + sqlx::IntoArguments<'q, db::Db>,
217{
218 pub async fn fetch_optional(
223 self,
224 op: impl IntoOneTimeExecutor<'_>,
225 ) -> Result<Option<<Repo as EsRepo>::Entity>, <Repo as EsRepo>::QueryError> {
226 self.fetch_optional_tree(op, false).await
227 }
228
229 pub async fn fetch_n(
234 self,
235 op: impl IntoOneTimeExecutor<'_>,
236 first: usize,
237 ) -> Result<(Vec<<Repo as EsRepo>::Entity>, bool), <Repo as EsRepo>::QueryError> {
238 self.fetch_n_tree(op, first, false).await
239 }
240
241 pub async fn fetch_optional_include_deleted(
244 self,
245 op: impl IntoOneTimeExecutor<'_>,
246 ) -> Result<Option<<Repo as EsRepo>::Entity>, <Repo as EsRepo>::QueryError> {
247 self.fetch_optional_tree(op, true).await
248 }
249
250 pub async fn fetch_n_include_deleted(
253 self,
254 op: impl IntoOneTimeExecutor<'_>,
255 first: usize,
256 ) -> Result<(Vec<<Repo as EsRepo>::Entity>, bool), <Repo as EsRepo>::QueryError> {
257 self.fetch_n_tree(op, first, true).await
258 }
259
260 async fn fetch_optional_tree(
261 self,
262 op: impl IntoOneTimeExecutor<'_>,
263 include_deleted: bool,
264 ) -> Result<Option<<Repo as EsRepo>::Entity>, <Repo as EsRepo>::QueryError> {
265 let (root, mut by_tag) = self
266 .fetch_tree_rows::<<Repo as EsRepo>::QueryError>(op, include_deleted)
267 .await?;
268 let Some(entity) = EntityEvents::load_first::<<Repo as EsRepo>::Entity>(root)? else {
269 return Ok(None);
270 };
271 let mut entities = [entity];
272 let mut cursor = 1i32;
273 <Repo as EsRepo>::hydrate_nested_from_rows::<<Repo as EsRepo>::QueryError>(
274 &mut by_tag,
275 &mut cursor,
276 &mut entities,
277 )?;
278 let [entity] = entities;
279 Ok(Some(entity))
280 }
281
282 async fn fetch_n_tree(
283 self,
284 op: impl IntoOneTimeExecutor<'_>,
285 first: usize,
286 include_deleted: bool,
287 ) -> Result<(Vec<<Repo as EsRepo>::Entity>, bool), <Repo as EsRepo>::QueryError> {
288 let (root, mut by_tag) = self
289 .fetch_tree_rows::<<Repo as EsRepo>::QueryError>(op, include_deleted)
290 .await?;
291 let (mut entities, more) = EntityEvents::load_n::<<Repo as EsRepo>::Entity>(root, first)?;
292 let mut cursor = 1i32;
293 <Repo as EsRepo>::hydrate_nested_from_rows::<<Repo as EsRepo>::QueryError>(
294 &mut by_tag,
295 &mut cursor,
296 &mut entities,
297 )?;
298 Ok((entities, more))
299 }
300}