sift_query_sqlx/execution/
external.rs1use sift_query_core::{
10 GroupScope, QueryInput, QueryTarget, SearchEntity, SearchOptions, ServerScope,
11};
12use sqlx::FromRow;
13use sqlx::postgres::{PgConnection, PgRow};
14
15use crate::cache::RenderRequest;
16use crate::execution::io::{
17 fetch_cluster_external, fetch_maps_external, fetch_typed_external, prepare,
18};
19use crate::{ExecutionStrategy, Page, PgSearchError, PgSiftQuery, RowMap, TimeoutStage};
20
21impl PgSiftQuery {
22 pub async fn search_on<E>(
27 &self,
28 connection: &mut PgConnection,
29 query: impl QueryInput<E>,
30 scope: &ServerScope<E>,
31 ) -> Result<Page<E>, PgSearchError>
32 where
33 E: SearchEntity + Send + Unpin + for<'row> FromRow<'row, PgRow>,
34 {
35 self.search_on_with(connection, query, scope, &GroupScope::unrestricted(), None)
36 .await
37 }
38
39 pub async fn search_on_with<E>(
41 &self,
42 connection: &mut PgConnection,
43 query: impl QueryInput<E>,
44 scope: &ServerScope<E>,
45 group_scope: &GroupScope<E>,
46 options: Option<&SearchOptions<E>>,
47 ) -> Result<Page<E>, PgSearchError>
48 where
49 E: SearchEntity + Send + Unpin + for<'row> FromRow<'row, PgRow>,
50 {
51 self.observe::<E, _>(
52 "search_on",
53 async {
54 self.validate_external_strategy()?;
55 let plan = self.resolve_plan_with_scopes(
56 query,
57 scope,
58 group_scope,
59 QueryTarget::TypedPage,
60 "search_on",
61 )?;
62 if plan.force_empty {
63 return Ok(empty_page());
64 }
65 let partition = self.extensions.resolve_partition(&plan, "search_on")?;
66 let list = (plan.paging.size > 0)
67 .then(|| {
68 self.render(
69 &plan,
70 "search_on",
71 "list",
72 partition.as_deref(),
73 RenderRequest::List,
74 )
75 })
76 .transpose()?
77 .map(prepare)
78 .transpose()?;
79 let cluster = prepare(self.render(
80 &plan,
81 "search_on",
82 "cluster",
83 partition.as_deref(),
84 RenderRequest::Cluster(options),
85 )?)?;
86 let deadline = self.deadline::<E>();
87 let data = match list {
88 Some(list) => {
89 fetch_typed_external(connection, list, deadline, TimeoutStage::ListQuery)
90 .await?
91 }
92 None => Vec::new(),
93 };
94 let (total_count, summaries) =
95 fetch_cluster_external(connection, cluster, options, E::META, deadline).await?;
96 let next_cursor = if data.len() == plan.paging.size as usize {
97 self.entity_cursor(&plan, data.last())?
98 } else {
99 None
100 };
101 Ok(Page {
102 data,
103 total_count,
104 summaries,
105 next_cursor,
106 })
107 },
108 |page| page.data.len(),
109 )
110 .await
111 }
112
113 pub async fn search_map_on<E>(
115 &self,
116 connection: &mut PgConnection,
117 query: impl QueryInput<E>,
118 scope: &ServerScope<E>,
119 ) -> Result<Page<RowMap>, PgSearchError>
120 where
121 E: SearchEntity,
122 {
123 self.search_map_on_with(connection, query, scope, &GroupScope::unrestricted(), None)
124 .await
125 }
126
127 pub async fn search_map_on_with<E>(
129 &self,
130 connection: &mut PgConnection,
131 query: impl QueryInput<E>,
132 scope: &ServerScope<E>,
133 group_scope: &GroupScope<E>,
134 options: Option<&SearchOptions<E>>,
135 ) -> Result<Page<RowMap>, PgSearchError>
136 where
137 E: SearchEntity,
138 {
139 self.observe::<E, _>(
140 "search_map_on",
141 async {
142 self.validate_external_strategy()?;
143 let plan = self.resolve_plan_with_scopes(
144 query,
145 scope,
146 group_scope,
147 QueryTarget::MapPage,
148 "search_map_on",
149 )?;
150 if plan.force_empty {
151 return Ok(empty_page());
152 }
153 let partition = self.extensions.resolve_partition(&plan, "search_map_on")?;
154 let list = (plan.paging.size > 0)
155 .then(|| {
156 self.render(
157 &plan,
158 "search_map_on",
159 "list",
160 partition.as_deref(),
161 RenderRequest::List,
162 )
163 })
164 .transpose()?
165 .map(prepare)
166 .transpose()?;
167 let cluster = prepare(self.render(
168 &plan,
169 "search_map_on",
170 "cluster",
171 partition.as_deref(),
172 RenderRequest::Cluster(options),
173 )?)?;
174 let deadline = self.deadline::<E>();
175 let mut data = match list {
176 Some(list) => fetch_maps_external(connection, list, &plan, deadline).await?,
177 None => Vec::new(),
178 };
179 let (total_count, summaries) =
180 fetch_cluster_external(connection, cluster, options, E::META, deadline).await?;
181 let next_cursor = if data.len() == plan.paging.size as usize {
182 self.map_cursor(&plan, data.last())?
183 } else {
184 None
185 };
186 self.format_map_rows::<E>(&mut data)?;
187 self.extensions
188 .filter_rows::<E>("search_map_on", &mut data)?;
189 Ok(self
190 .extensions
191 .enrich_page(
192 E::META.entity_name,
193 "search_map_on",
194 Page {
195 data,
196 total_count,
197 summaries,
198 next_cursor,
199 },
200 )
201 .await?)
202 },
203 |page| page.data.len(),
204 )
205 .await
206 }
207
208 pub(crate) fn validate_external_strategy(&self) -> Result<(), PgSearchError> {
209 if self.execution.strategy != ExecutionStrategy::SequentialBestEffort {
210 return Err(PgSearchError::ExternalExecutionStrategy);
211 }
212 Ok(())
213 }
214}
215
216fn empty_page<T>() -> Page<T> {
217 Page::empty()
218}