1use crate::entity::IEntityType;
16use crate::error::{EFError, EFResult};
17use crate::metadata::{NavigationKind, NavigationMeta};
18use crate::provider::{DbValue, IDatabaseProvider, ISqlGenerator};
19use crate::query::CompiledFilter;
20use std::collections::HashMap;
21use std::sync::Arc;
22
23pub const MAX_LAZY_DEPTH: usize = 16;
30
31pub trait LazyContext: Send + Sync {
50 fn provider(&self) -> &Arc<dyn IDatabaseProvider>;
52
53 fn owner_snapshot(&self) -> &HashMap<String, DbValue>;
57
58 fn owner_key_values(&self) -> &HashMap<String, DbValue>;
63
64 fn navigation(&self) -> &NavigationMeta;
66
67 fn filter_map(&self) -> Option<&HashMap<String, CompiledFilter>>;
72
73 fn depth(&self) -> usize;
78}
79
80pub struct LazyContextImpl {
85 provider: Arc<dyn IDatabaseProvider>,
86 owner_snapshot: HashMap<String, DbValue>,
87 owner_key_values: HashMap<String, DbValue>,
88 navigation: NavigationMeta,
89 filter_map: Option<Arc<HashMap<String, CompiledFilter>>>,
90 depth: usize,
91}
92
93impl LazyContextImpl {
94 pub fn new(
96 provider: Arc<dyn IDatabaseProvider>,
97 owner_snapshot: HashMap<String, DbValue>,
98 owner_key_values: HashMap<String, DbValue>,
99 navigation: NavigationMeta,
100 filter_map: Option<Arc<HashMap<String, CompiledFilter>>>,
101 depth: usize,
102 ) -> Self {
103 Self {
104 provider,
105 owner_snapshot,
106 owner_key_values,
107 navigation,
108 filter_map,
109 depth,
110 }
111 }
112}
113
114impl LazyContext for LazyContextImpl {
115 fn provider(&self) -> &Arc<dyn IDatabaseProvider> {
116 &self.provider
117 }
118 fn owner_snapshot(&self) -> &HashMap<String, DbValue> {
119 &self.owner_snapshot
120 }
121 fn owner_key_values(&self) -> &HashMap<String, DbValue> {
122 &self.owner_key_values
123 }
124 fn navigation(&self) -> &NavigationMeta {
125 &self.navigation
126 }
127 fn filter_map(&self) -> Option<&HashMap<String, CompiledFilter>> {
128 self.filter_map.as_deref()
129 }
130 fn depth(&self) -> usize {
131 self.depth
132 }
133}
134
135fn build_lazy_query(
149 nav: &NavigationMeta,
150 owner_snapshot: &HashMap<String, DbValue>,
151 owner_key_values: &HashMap<String, DbValue>,
152 gen: &dyn ISqlGenerator,
153 filter_map: Option<&HashMap<String, CompiledFilter>>,
154) -> EFResult<Option<(String, Vec<DbValue>)>> {
155 let Some(related_table) = nav.related_table.as_deref() else {
156 return Ok(None);
157 };
158 let Some(fk_column) = nav.fk_column.as_deref() else {
159 return Ok(None);
160 };
161 let Some(ref_column) = nav.referenced_key_column.as_deref() else {
162 return Ok(None);
163 };
164
165 match nav.kind {
166 NavigationKind::HasMany | NavigationKind::HasOne => {
167 let Some(owner_pk) = owner_key_values.get(ref_column) else {
170 return Ok(None);
171 };
172 let placeholder = gen.parameter_placeholder(1);
173 let mut sql = format!(
174 "SELECT * FROM {} WHERE {} = {}",
175 related_table, fk_column, placeholder
176 );
177 let mut params = vec![owner_pk.clone()];
178 apply_filter(&mut sql, &mut params, related_table, filter_map, gen);
179 if nav.kind == NavigationKind::HasOne {
180 sql.push_str(" LIMIT 1");
181 }
182 Ok(Some((sql, params)))
183 }
184 NavigationKind::BelongsTo => {
185 let Some(owner_fk) = owner_snapshot.get(fk_column) else {
188 return Ok(None);
189 };
190 if matches!(owner_fk, DbValue::Null) {
191 return Ok(None);
193 }
194 let placeholder = gen.parameter_placeholder(1);
195 let mut sql = format!(
196 "SELECT * FROM {} WHERE {} = {}",
197 related_table, ref_column, placeholder
198 );
199 let mut params = vec![owner_fk.clone()];
200 apply_filter(&mut sql, &mut params, related_table, filter_map, gen);
201 Ok(Some((sql, params)))
202 }
203 NavigationKind::ManyToMany => {
204 Ok(None)
206 }
207 }
208}
209
210fn build_m2m_lazy_queries(
217 nav: &NavigationMeta,
218 owner_key_values: &HashMap<String, DbValue>,
219 ref_column: &str,
220 gen: &dyn ISqlGenerator,
221 filter_map: Option<&HashMap<String, CompiledFilter>>,
222) -> EFResult<Option<(String, Vec<DbValue>, String)>> {
223 let Some(join_table) = nav.through_table.as_deref() else {
224 return Ok(None);
225 };
226 let Some(parent_fk) = nav.through_parent_fk.as_deref() else {
227 return Ok(None);
228 };
229 let Some(related_fk) = nav.through_related_fk.as_deref() else {
230 return Ok(None);
231 };
232 let Some(related_table) = nav.related_table.as_deref() else {
233 return Ok(None);
234 };
235 let Some(owner_pk) = owner_key_values.get(ref_column) else {
236 return Ok(None);
237 };
238
239 let placeholder = gen.parameter_placeholder(1);
241 let join_sql = format!(
242 "SELECT {} FROM {} WHERE {} = {}",
243 related_fk, join_table, parent_fk, placeholder
244 );
245 let join_params = vec![owner_pk.clone()];
246
247 let _ = filter_map; Ok(Some((join_sql, join_params, related_table.to_string())))
252}
253
254fn apply_filter(
256 sql: &mut String,
257 params: &mut Vec<DbValue>,
258 related_table: &str,
259 filter_map: Option<&HashMap<String, CompiledFilter>>,
260 gen: &dyn ISqlGenerator,
261) {
262 use crate::query::compile_bool_expr;
263
264 if let Some(filter) = filter_map.and_then(|m| m.get(related_table)) {
265 let mut idx = params.len() + 1;
266 let filter_sql = compile_bool_expr(&filter.expr, gen, &mut idx);
267 params.extend(filter.params.iter().cloned());
268 *sql = format!("{} AND ({})", sql, filter_sql);
269 }
270}
271
272pub async fn load_scalar_lazy<T>(ctx: &dyn LazyContext) -> EFResult<Option<T>>
282where
283 T: IEntityType + crate::entity::IFromRow,
284{
285 let nav = ctx.navigation();
286 if nav.kind == NavigationKind::ManyToMany {
287 return Err(EFError::other(
288 "load_scalar_lazy called for ManyToMany navigation",
289 ));
290 }
291
292 let gen = ctx.provider().sql_generator();
293 let Some((sql, params)) = build_lazy_query(
294 nav,
295 ctx.owner_snapshot(),
296 ctx.owner_key_values(),
297 gen,
298 ctx.filter_map(),
299 )?
300 else {
301 return Ok(None);
302 };
303
304 let provider = ctx.provider();
305 let mut conn = provider.get_connection().await?;
306 let rows = conn.query(&sql, ¶ms).await?;
307 drop(sql);
308 drop(params);
309
310 if let Some(row) = rows.into_iter().next() {
311 let entity = T::from_row(&row)?;
312 Ok(Some(entity))
313 } else {
314 Ok(None)
315 }
316}
317
318pub async fn load_collection_lazy<T>(ctx: &dyn LazyContext) -> EFResult<Vec<T>>
323where
324 T: IEntityType + crate::entity::IFromRow,
325{
326 let nav = ctx.navigation();
327 if nav.kind == NavigationKind::ManyToMany {
328 return load_m2m_collection_lazy::<T>(ctx).await;
329 }
330
331 let gen = ctx.provider().sql_generator();
332 let Some((sql, params)) = build_lazy_query(
333 nav,
334 ctx.owner_snapshot(),
335 ctx.owner_key_values(),
336 gen,
337 ctx.filter_map(),
338 )?
339 else {
340 return Ok(Vec::new());
341 };
342
343 let provider = ctx.provider();
344 let mut conn = provider.get_connection().await?;
345 let rows = conn.query(&sql, ¶ms).await?;
346
347 let mut entities = Vec::with_capacity(rows.len());
348 for row in rows {
349 entities.push(T::from_row(&row)?);
350 }
351 Ok(entities)
352}
353
354async fn load_m2m_collection_lazy<T>(ctx: &dyn LazyContext) -> EFResult<Vec<T>>
356where
357 T: IEntityType + crate::entity::IFromRow,
358{
359 let nav = ctx.navigation();
360 let provider = ctx.provider();
361 let gen = provider.sql_generator();
362
363 let ref_column = nav.referenced_key_column.as_deref().unwrap_or("id");
365
366 let Some((join_sql, join_params, related_table)) = build_m2m_lazy_queries(
367 nav,
368 ctx.owner_key_values(),
369 ref_column,
370 gen,
371 ctx.filter_map(),
372 )?
373 else {
374 return Ok(Vec::new());
375 };
376
377 let mut conn = provider.get_connection().await?;
379 let join_rows = conn.query(&join_sql, &join_params).await?;
380 if join_rows.is_empty() {
381 return Ok(Vec::new());
382 }
383
384 let related_fk_index = nav.through_related_fk_index;
386 let mut seen: std::collections::HashSet<String> = std::collections::HashSet::new();
387 let related_ids: Vec<DbValue> = join_rows
388 .iter()
389 .filter_map(|row| row.get(related_fk_index).cloned())
390 .filter(|v| seen.insert(format!("{}", v)))
391 .collect();
392
393 if related_ids.is_empty() {
394 return Ok(Vec::new());
395 }
396
397 let ref_pk_col = nav
399 .referenced_key_column
400 .as_deref()
401 .map(|s| s.to_string())
402 .or_else(|| {
403 nav.related_entity_meta.and_then(|f| {
404 let meta = f();
405 meta.primary_keys.first().map(|k| k.to_string())
406 })
407 })
408 .unwrap_or_else(|| "id".to_string());
409
410 let placeholders: Vec<String> = (0..related_ids.len())
411 .map(|i| gen.parameter_placeholder(i + 1))
412 .collect();
413 let mut sql = format!(
414 "SELECT * FROM {} WHERE {} IN ({})",
415 related_table,
416 ref_pk_col,
417 placeholders.join(", ")
418 );
419 let mut params: Vec<DbValue> = related_ids;
420 apply_filter(&mut sql, &mut params, &related_table, ctx.filter_map(), gen);
421
422 let rows = conn.query(&sql, ¶ms).await?;
423 let mut entities = Vec::with_capacity(rows.len());
424 for row in rows {
425 entities.push(T::from_row(&row)?);
426 }
427 Ok(entities)
428}