1use crate::entity::{IEntitySnapshot, IEntityType, IFromRow, IGetKeyValues, INavigationSetter};
4use crate::error::{EFError, EFResult};
5use crate::metadata::{EntityTypeMeta, NavigationKind, NavigationMeta};
6use crate::provider::{DbValue, DbValueKey, IDatabaseProvider, ISqlGenerator};
7use crate::query::{compile_bool_expr, CompiledFilter, IncludePath};
8use std::collections::{HashMap, HashSet};
9
10fn apply_filter_to_sql(
17 sql: &mut String,
18 params: &mut Vec<DbValue>,
19 related_table: &str,
20 filter_map: Option<&HashMap<String, CompiledFilter>>,
21 gen: &dyn ISqlGenerator,
22) {
23 if let Some(filter) = filter_map.and_then(|m| m.get(related_table)) {
24 let mut idx = params.len() + 1;
25 let filter_sql = compile_bool_expr(&filter.expr, gen, &mut idx);
26 params.extend(filter.params.iter().cloned());
27 *sql = format!("{} AND ({})", sql, filter_sql);
28 }
29}
30
31pub fn include_paths_for<T: IEntityType, S: AsRef<str>>(
33 navigation_names: &[S],
34) -> Vec<IncludePath> {
35 let meta = T::entity_meta();
36 navigation_names
37 .iter()
38 .filter_map(|name| include_path_from_meta(&meta, name.as_ref()))
39 .collect()
40}
41
42fn include_path_from_meta(meta: &EntityTypeMeta, navigation: &str) -> Option<IncludePath> {
43 let nav_meta = meta.find_navigation(navigation)?;
44 Some(IncludePath {
45 navigation: navigation.to_string(),
46 nested: Vec::new(),
47 related_table: nav_meta.related_table.as_ref().map(|s| s.to_string()),
48 foreign_key_column: nav_meta.fk_column.as_ref().map(|s| s.to_string()),
49 referenced_key_column: nav_meta
50 .referenced_key_column
51 .as_ref()
52 .map(|s| s.to_string()),
53 })
54}
55
56pub async fn load_includes<T>(
63 entities: &mut [T],
64 includes: &[IncludePath],
65 provider: &dyn IDatabaseProvider,
66 filter_map: Option<&HashMap<String, CompiledFilter>>,
67) -> EFResult<()>
68where
69 T: IEntityType + IFromRow + INavigationSetter + IGetKeyValues + IEntitySnapshot,
70{
71 if entities.is_empty() || includes.is_empty() {
72 return Ok(());
73 }
74
75 let meta = T::entity_meta();
76 for include in includes {
77 let nav = match meta.find_navigation(&include.navigation) {
78 Some(n) => n,
79 None => continue,
80 };
81
82 if nav.kind == NavigationKind::ManyToMany {
83 load_many_to_many(entities, &include.navigation, nav, provider, filter_map).await?;
84 } else {
85 load_scalar_navigation(entities, include, nav, &meta, provider, filter_map).await?;
86 }
87
88 if !include.nested.is_empty() {
89 T::load_nested_includes(
90 entities,
91 &include.navigation,
92 &include.nested,
93 provider,
94 filter_map,
95 )
96 .await?;
97 }
98 }
99 Ok(())
100}
101
102async fn load_scalar_navigation<T>(
103 entities: &mut [T],
104 include: &IncludePath,
105 nav: &NavigationMeta,
106 meta: &EntityTypeMeta,
107 provider: &dyn IDatabaseProvider,
108 filter_map: Option<&HashMap<String, CompiledFilter>>,
109) -> EFResult<()>
110where
111 T: IEntitySnapshot + INavigationSetter + IGetKeyValues,
112{
113 let related_table = include
114 .related_table
115 .as_deref()
116 .or(nav.related_table.as_deref());
117 let fk_column = include
118 .foreign_key_column
119 .as_deref()
120 .or(nav.fk_column.as_deref());
121 let ref_column = include
122 .referenced_key_column
123 .as_deref()
124 .or(nav.referenced_key_column.as_deref())
125 .or_else(|| meta.primary_keys.first().map(|k| k.as_ref()));
126
127 let (Some(related_table), Some(fk_column), Some(ref_column)) =
128 (related_table, fk_column, ref_column)
129 else {
130 return Ok(());
131 };
132
133 match nav.kind {
134 NavigationKind::HasMany => {
135 let parent_ids: Vec<DbValue> = entities
136 .iter()
137 .filter_map(|e| e.key_values().get(ref_column).cloned())
138 .collect();
139 if parent_ids.is_empty() {
140 return Ok(());
141 }
142
143 let gen = provider.sql_generator();
144 let placeholders: Vec<String> = (0..parent_ids.len())
145 .map(|i| gen.parameter_placeholder(i + 1))
146 .collect();
147 let mut sql = format!(
148 "SELECT * FROM {} WHERE {} IN ({})",
149 related_table,
150 fk_column,
151 placeholders.join(", ")
152 );
153 let mut params = parent_ids;
154 apply_filter_to_sql(&mut sql, &mut params, related_table, filter_map, gen);
155
156 let mut conn = provider.get_connection().await?;
157 let rows = conn.query(&sql, ¶ms).await?;
158 let grouped = group_rows(&rows, nav.fk_row_index);
159
160 for entity in entities.iter_mut() {
161 if let Some(pk) = entity.key_values().get(ref_column) {
162 let key = db_value_key(pk);
163 if let Some(child_rows) = grouped.get(&key) {
164 entity.apply_has_many(&include.navigation, child_rows)?;
165 }
166 }
167 }
168 }
169 NavigationKind::BelongsTo | NavigationKind::HasOne => {
170 let fk_values: Vec<DbValue> = entities
171 .iter()
172 .filter_map(|e| e.snapshot().get(fk_column).cloned())
173 .collect();
174 if fk_values.is_empty() {
175 return Ok(());
176 }
177
178 let gen = provider.sql_generator();
179 let placeholders: Vec<String> = (0..fk_values.len())
180 .map(|i| gen.parameter_placeholder(i + 1))
181 .collect();
182 let mut sql = format!(
183 "SELECT * FROM {} WHERE {} IN ({})",
184 related_table,
185 ref_column,
186 placeholders.join(", ")
187 );
188 let mut params = fk_values;
189 apply_filter_to_sql(&mut sql, &mut params, related_table, filter_map, gen);
190
191 let mut conn = provider.get_connection().await?;
192 let rows = conn.query(&sql, ¶ms).await?;
193 let lookup = index_rows(&rows, nav.pk_row_index);
194
195 for entity in entities.iter_mut() {
196 if let Some(fk) = entity.snapshot().get(fk_column) {
197 let key = db_value_key(fk);
198 if let Some(related_row) = lookup.get(&key) {
199 entity.apply_reference(&include.navigation, related_row)?;
200 }
201 }
202 }
203 }
204 NavigationKind::ManyToMany => {
205 return Err(EFError::configuration(
206 "many-to-many navigations are loaded via load_many_to_many",
207 ));
208 }
209 }
210 Ok(())
211}
212
213async fn load_many_to_many<T>(
214 entities: &mut [T],
215 navigation: &str,
216 nav: &NavigationMeta,
217 provider: &dyn IDatabaseProvider,
218 filter_map: Option<&HashMap<String, CompiledFilter>>,
219) -> EFResult<()>
220where
221 T: IEntityType + INavigationSetter + IGetKeyValues,
222{
223 let meta = T::entity_meta();
224 let ref_column = meta
225 .primary_keys
226 .first()
227 .map(|k| k.as_ref())
228 .unwrap_or("id");
229
230 let (Some(join_table), Some(parent_fk), Some(_related_fk), Some(related_table)) = (
231 nav.through_table.as_deref(),
232 nav.through_parent_fk.as_deref(),
233 nav.through_related_fk.as_deref(),
234 nav.related_table.as_deref(),
235 ) else {
236 return Ok(());
237 };
238
239 let parent_ids: Vec<DbValue> = entities
240 .iter()
241 .filter_map(|e| e.key_values().get(ref_column).cloned())
242 .collect();
243 if parent_ids.is_empty() {
244 return Ok(());
245 }
246
247 let gen = provider.sql_generator();
248 let placeholders: Vec<String> = (0..parent_ids.len())
249 .map(|i| gen.parameter_placeholder(i + 1))
250 .collect();
251 let join_sql = format!(
252 "SELECT * FROM {} WHERE {} IN ({})",
253 join_table,
254 parent_fk,
255 placeholders.join(", ")
256 );
257
258 let mut conn = provider.get_connection().await?;
259 let join_rows = conn.query(&join_sql, &parent_ids).await?;
260 if join_rows.is_empty() {
261 return Ok(());
262 }
263
264 let parent_to_related = group_join_rows(
265 &join_rows,
266 nav.through_parent_fk_index,
267 nav.through_related_fk_index,
268 );
269
270 let mut seen: HashSet<DbValueKey> = HashSet::new();
273 let related_ids: Vec<DbValue> = join_rows
274 .iter()
275 .filter_map(|row| row.get(nav.through_related_fk_index).cloned())
276 .filter(|v| seen.insert(db_value_key(v)))
277 .collect();
278
279 if related_ids.is_empty() {
280 return Ok(());
281 }
282
283 let related_pk = nav.referenced_key_column.as_deref().unwrap_or("id");
284 let rel_placeholders: Vec<String> = (0..related_ids.len())
285 .map(|i| gen.parameter_placeholder(i + 1))
286 .collect();
287 let mut related_sql = format!(
288 "SELECT * FROM {} WHERE {} IN ({})",
289 related_table,
290 related_pk,
291 rel_placeholders.join(", ")
292 );
293 let mut related_params = related_ids;
294 apply_filter_to_sql(
295 &mut related_sql,
296 &mut related_params,
297 related_table,
298 filter_map,
299 gen,
300 );
301 let related_rows = conn.query(&related_sql, &related_params).await?;
302 let related_by_pk = index_rows(&related_rows, nav.pk_row_index);
303
304 for entity in entities.iter_mut() {
305 if let Some(pk) = entity.key_values().get(ref_column) {
306 let parent_key = db_value_key(pk);
307 if let Some(related_keys) = parent_to_related.get(&parent_key) {
308 let mut child_rows = Vec::new();
309 for rk in related_keys {
310 if let Some(row) = related_by_pk.get(&db_value_key(rk)) {
311 child_rows.push(row.clone());
312 }
313 }
314 if !child_rows.is_empty() {
315 entity.apply_has_many(navigation, &child_rows)?;
316 }
317 }
318 }
319 }
320
321 Ok(())
322}
323
324fn db_value_key(v: &DbValue) -> DbValueKey {
325 DbValueKey::from(v)
326}
327
328fn group_rows(rows: &[Vec<DbValue>], fk_index: usize) -> HashMap<DbValueKey, Vec<Vec<DbValue>>> {
329 let mut map: HashMap<DbValueKey, Vec<Vec<DbValue>>> = HashMap::new();
330 for row in rows {
331 if let Some(fk_val) = row.get(fk_index) {
332 map.entry(db_value_key(fk_val))
333 .or_default()
334 .push(row.clone());
335 }
336 }
337 map
338}
339
340fn group_join_rows(
341 rows: &[Vec<DbValue>],
342 parent_index: usize,
343 related_index: usize,
344) -> HashMap<DbValueKey, Vec<DbValue>> {
345 let mut map: HashMap<DbValueKey, Vec<DbValue>> = HashMap::new();
346 for row in rows {
347 if let (Some(parent), Some(related)) = (row.get(parent_index), row.get(related_index)) {
348 map.entry(db_value_key(parent))
349 .or_default()
350 .push(related.clone());
351 }
352 }
353 map
354}
355
356fn index_rows(rows: &[Vec<DbValue>], pk_index: usize) -> HashMap<DbValueKey, Vec<DbValue>> {
357 let mut map = HashMap::new();
358 for row in rows {
359 if let Some(pk) = row.get(pk_index) {
360 map.insert(db_value_key(pk), row.clone());
361 }
362 }
363 map
364}