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