1use std::collections::BTreeSet;
9
10use corium_core::{AttrId, EntityId, IndexOrder, Keyword, Value, ValueType};
11use corium_db::{Db, key_prefix};
12
13use crate::QueryError;
14use crate::boundary::value_to_edn;
15use crate::edn::Edn;
16
17const DEFAULT_LIMIT: usize = 1000;
19
20#[derive(Clone, Debug)]
21enum PullAttr {
22 Forward(AttrId, Keyword),
23 Reverse(AttrId, Keyword),
24}
25
26#[derive(Clone, Debug)]
27enum SubSelect {
28 None,
29 Pattern(Box<PullPattern>),
30 Recur(Option<usize>),
31}
32
33#[derive(Clone, Copy, Debug)]
36enum Limit {
37 Default,
38 Unlimited,
39 At(usize),
40}
41
42impl Limit {
43 fn bound(self) -> Option<usize> {
44 match self {
45 Self::Default => Some(DEFAULT_LIMIT),
46 Self::Unlimited => None,
47 Self::At(bound) => Some(bound),
48 }
49 }
50}
51
52#[derive(Clone, Debug)]
53struct PullSpec {
54 attr: PullAttr,
55 as_key: Option<Edn>,
56 limit: Limit,
57 default: Option<Edn>,
58 sub: SubSelect,
59}
60
61#[derive(Clone, Debug, Default)]
62struct PullPattern {
63 wildcard: bool,
64 db_id: bool,
65 specs: Vec<PullSpec>,
66}
67
68fn parse_error(message: impl Into<String>) -> QueryError {
69 QueryError::Parse(message.into())
70}
71
72fn resolve_attr(db: &Db, keyword: &Keyword) -> Result<PullAttr, QueryError> {
73 if let Some(reverse_name) = keyword.name.strip_prefix('_') {
74 let forward = Keyword::new(keyword.namespace.as_deref(), reverse_name);
75 let attr = db
76 .idents()
77 .entid(&forward)
78 .ok_or_else(|| QueryError::UnknownIdent(forward.clone()))?;
79 return Ok(PullAttr::Reverse(attr, keyword.clone()));
80 }
81 let attr = db
82 .idents()
83 .entid(keyword)
84 .ok_or_else(|| QueryError::UnknownIdent(keyword.clone()))?;
85 Ok(PullAttr::Forward(attr, keyword.clone()))
86}
87
88fn parse_pattern(db: &Db, form: &Edn) -> Result<PullPattern, QueryError> {
89 let items = form
90 .as_seq()
91 .ok_or_else(|| parse_error("pull pattern must be a vector"))?;
92 let mut pattern = PullPattern::default();
93 for item in items {
94 match item {
95 Edn::Symbol(sym) if sym == "*" => pattern.wildcard = true,
96 Edn::Keyword(k) if k.namespace.as_deref() == Some("db") && k.name == "id" => {
97 pattern.db_id = true;
98 }
99 Edn::Keyword(k) => pattern.specs.push(PullSpec {
100 attr: resolve_attr(db, k)?,
101 as_key: None,
102 limit: Limit::Default,
103 default: None,
104 sub: SubSelect::None,
105 }),
106 Edn::Vector(_) => pattern
107 .specs
108 .push(parse_attr_spec(db, item, SubSelect::None)?),
109 Edn::Map(pairs) => {
110 for (key, sub_form) in pairs {
111 let sub = match sub_form {
112 Edn::Long(depth) => SubSelect::Recur(Some(
113 usize::try_from(*depth)
114 .map_err(|_| parse_error("bad recursion depth"))?,
115 )),
116 Edn::Symbol(sym) if sym == "..." => SubSelect::Recur(None),
117 _ => SubSelect::Pattern(Box::new(parse_pattern(db, sub_form)?)),
118 };
119 pattern.specs.push(parse_attr_spec(db, key, sub)?);
120 }
121 }
122 _ => return Err(parse_error(format!("bad pull spec {item}"))),
123 }
124 }
125 Ok(pattern)
126}
127
128fn parse_attr_spec(db: &Db, form: &Edn, sub: SubSelect) -> Result<PullSpec, QueryError> {
130 match form {
131 Edn::Keyword(k) => Ok(PullSpec {
132 attr: resolve_attr(db, k)?,
133 as_key: None,
134 limit: Limit::Default,
135 default: None,
136 sub,
137 }),
138 Edn::Vector(items) => {
139 let (attr_form, opts) = items
140 .split_first()
141 .ok_or_else(|| parse_error("empty attribute spec"))?;
142 let Edn::Keyword(k) = attr_form else {
143 return Err(parse_error(format!("bad attribute spec {form}")));
144 };
145 let mut spec = PullSpec {
146 attr: resolve_attr(db, k)?,
147 as_key: None,
148 limit: Limit::Default,
149 default: None,
150 sub,
151 };
152 let mut opts = opts.iter();
153 while let Some(opt) = opts.next() {
154 let value = opts
155 .next()
156 .ok_or_else(|| parse_error("attribute option requires a value"))?;
157 match opt.as_keyword().map(|k| k.name.as_str()) {
158 Some("as") => spec.as_key = Some(value.clone()),
159 Some("limit") => {
160 spec.limit = match value {
161 Edn::Nil => Limit::Unlimited,
162 Edn::Long(n) => Limit::At(
163 usize::try_from(*n).map_err(|_| parse_error("bad :limit value"))?,
164 ),
165 _ => return Err(parse_error("bad :limit value")),
166 };
167 }
168 Some("default") => spec.default = Some(value.clone()),
169 _ => return Err(parse_error(format!("unknown attribute option {opt}"))),
170 }
171 }
172 Ok(spec)
173 }
174 _ => Err(parse_error(format!("bad attribute spec {form}"))),
175 }
176}
177
178pub fn pull(db: &Db, pattern: &Edn, eid: EntityId) -> Result<Edn, QueryError> {
184 let parsed = parse_pattern(db, pattern)?;
185 let mut path = BTreeSet::from([eid]);
187 pull_entity(db, &parsed, eid, &mut path)
188}
189
190pub fn pull_many(db: &Db, pattern: &Edn, eids: &[EntityId]) -> Result<Edn, QueryError> {
195 let parsed = parse_pattern(db, pattern)?;
196 let results = eids
197 .iter()
198 .map(|eid| {
199 let mut path = BTreeSet::from([*eid]);
200 pull_entity(db, &parsed, *eid, &mut path)
201 })
202 .collect::<Result<Vec<_>, _>>()?;
203 Ok(Edn::Vector(results))
204}
205
206fn entity_datoms(db: &Db, eid: EntityId) -> Vec<(AttrId, Value)> {
207 let prefix = key_prefix(IndexOrder::Eavt, Some(eid), None, None);
208 db.datoms_prefix(IndexOrder::Eavt, &prefix)
209 .map(|datom| (datom.a, datom.v.clone()))
210 .collect()
211}
212
213fn reverse_refs(db: &Db, attr: AttrId, eid: EntityId) -> Vec<EntityId> {
214 let value = Value::Ref(eid);
215 let prefix = key_prefix(IndexOrder::Vaet, None, Some(attr), Some(&value));
216 db.datoms_prefix(IndexOrder::Vaet, &prefix)
217 .map(|datom| datom.e)
218 .collect()
219}
220
221fn pull_entity(
222 db: &Db,
223 pattern: &PullPattern,
224 eid: EntityId,
225 path: &mut BTreeSet<EntityId>,
226) -> Result<Edn, QueryError> {
227 let own = entity_datoms(db, eid);
228 let mut pairs: Vec<(Edn, Edn)> = Vec::new();
229 if pattern.db_id || pattern.wildcard {
230 pairs.push((
231 Edn::keyword("db/id"),
232 Edn::Long(i64::try_from(eid.raw()).unwrap_or(i64::MAX)),
233 ));
234 }
235 if pattern.wildcard {
236 let explicit: BTreeSet<AttrId> = pattern
237 .specs
238 .iter()
239 .filter_map(|spec| match &spec.attr {
240 PullAttr::Forward(attr, _) => Some(*attr),
241 PullAttr::Reverse(_, _) => None,
242 })
243 .collect();
244 let mut attrs: Vec<AttrId> = own.iter().map(|(a, _)| *a).collect();
245 attrs.dedup();
246 for attr in attrs {
247 if explicit.contains(&attr) {
248 continue;
249 }
250 let Some(ident) = db.idents().ident(attr) else {
251 continue;
252 };
253 let spec = PullSpec {
254 attr: PullAttr::Forward(attr, ident.clone()),
255 as_key: None,
256 limit: Limit::Default,
257 default: None,
258 sub: SubSelect::None,
260 };
261 if let Some((key, value)) = pull_spec(db, &spec, pattern, eid, &own, path)? {
262 pairs.push((key, value));
263 }
264 }
265 }
266 for spec in &pattern.specs {
267 if let Some((key, value)) = pull_spec(db, spec, pattern, eid, &own, path)? {
268 pairs.push((key, value));
269 }
270 }
271 if pairs.is_empty() {
272 return Ok(Edn::Nil);
273 }
274 pairs.sort_by(|left, right| left.0.cmp(&right.0));
275 pairs.dedup_by(|left, right| left.0 == right.0);
276 Ok(Edn::Map(pairs))
277}
278
279#[allow(clippy::too_many_lines)]
280fn pull_spec(
281 db: &Db,
282 spec: &PullSpec,
283 enclosing: &PullPattern,
284 eid: EntityId,
285 own: &[(AttrId, Value)],
286 path: &mut BTreeSet<EntityId>,
287) -> Result<Option<(Edn, Edn)>, QueryError> {
288 match &spec.attr {
289 PullAttr::Forward(attr, ident) => {
290 let meta = db.schema().get(*attr);
291 let is_ref = meta.is_some_and(|m| m.value_type == ValueType::Ref);
292 let is_component = meta.is_some_and(|m| m.is_component);
293 let many = meta.is_none_or(|m| m.cardinality == corium_core::Cardinality::Many);
294 let limit = spec.limit.bound();
295 let mut values: Vec<Value> = own
296 .iter()
297 .filter(|(a, _)| a == attr)
298 .map(|(_, v)| v.clone())
299 .collect();
300 if let Some(limit) = limit {
301 values.truncate(limit);
302 }
303 if values.is_empty() {
304 let default = spec.default.clone().map(|form| {
305 (
306 spec.as_key
307 .clone()
308 .unwrap_or_else(|| Edn::Keyword(ident.clone())),
309 form,
310 )
311 });
312 return Ok(default);
313 }
314 let render =
315 |value: &Value, path: &mut BTreeSet<EntityId>| -> Result<Edn, QueryError> {
316 if is_ref {
317 if let Value::Ref(child) = value {
318 return render_ref(db, spec, enclosing, *child, is_component, path);
319 }
320 }
321 Ok(value_to_edn(db, value))
322 };
323 let rendered = if many {
324 let items = values
325 .iter()
326 .map(|value| render(value, path))
327 .collect::<Result<Vec<_>, _>>()?;
328 Edn::Vector(items)
329 } else {
330 render(&values[0], path)?
331 };
332 let key = spec
333 .as_key
334 .clone()
335 .unwrap_or_else(|| Edn::Keyword(ident.clone()));
336 Ok(Some((key, rendered)))
337 }
338 PullAttr::Reverse(attr, reverse_ident) => {
339 let is_component = db.schema().get(*attr).is_some_and(|m| m.is_component);
340 let parents = reverse_refs(db, *attr, eid);
341 if parents.is_empty() {
342 let default = spec.default.clone().map(|form| {
343 (
344 spec.as_key
345 .clone()
346 .unwrap_or_else(|| Edn::Keyword(reverse_ident.clone())),
347 form,
348 )
349 });
350 return Ok(default);
351 }
352 let limit = spec.limit.bound();
353 let mut parents = parents;
354 if let Some(limit) = limit {
355 parents.truncate(limit);
356 }
357 let render = |parent: EntityId, path: &mut BTreeSet<EntityId>| match &spec.sub {
358 SubSelect::None => Ok(Edn::Map(vec![(
359 Edn::keyword("db/id"),
360 Edn::Long(i64::try_from(parent.raw()).unwrap_or(i64::MAX)),
361 )])),
362 SubSelect::Pattern(sub) => pull_entity(db, sub, parent, path),
363 SubSelect::Recur(_) => Err(QueryError::Parse(
364 "recursion is not supported on reverse refs".into(),
365 )),
366 };
367 let rendered = if is_component {
369 render(parents[0], path)?
370 } else {
371 Edn::Vector(
372 parents
373 .into_iter()
374 .map(|parent| render(parent, path))
375 .collect::<Result<Vec<_>, _>>()?,
376 )
377 };
378 let key = spec
379 .as_key
380 .clone()
381 .unwrap_or_else(|| Edn::Keyword(reverse_ident.clone()));
382 Ok(Some((key, rendered)))
383 }
384 }
385}
386
387fn render_ref(
388 db: &Db,
389 spec: &PullSpec,
390 enclosing: &PullPattern,
391 child: EntityId,
392 is_component: bool,
393 path: &mut BTreeSet<EntityId>,
394) -> Result<Edn, QueryError> {
395 let db_id_map = |child: EntityId| {
396 Edn::Map(vec![(
397 Edn::keyword("db/id"),
398 Edn::Long(i64::try_from(child.raw()).unwrap_or(i64::MAX)),
399 )])
400 };
401 let spec_attr = |candidate: &PullSpec| match (&candidate.attr, &spec.attr) {
402 (PullAttr::Forward(a, _), PullAttr::Forward(b, _))
403 | (PullAttr::Reverse(a, _), PullAttr::Reverse(b, _)) => a == b,
404 _ => false,
405 };
406 match &spec.sub {
407 SubSelect::Pattern(sub) => {
408 if !path.insert(child) {
409 return Ok(db_id_map(child));
410 }
411 let result = pull_entity(db, sub, child, path);
412 path.remove(&child);
413 result
414 }
415 SubSelect::Recur(depth) => {
416 if depth == &Some(0) || !path.insert(child) {
417 return Ok(db_id_map(child));
418 }
419 let mut sub = enclosing.clone();
422 for candidate in &mut sub.specs {
423 if spec_attr(candidate) {
424 if let SubSelect::Recur(d) = &candidate.sub {
425 candidate.sub = SubSelect::Recur(d.map(|d| d.saturating_sub(1)));
426 }
427 }
428 }
429 let result = pull_entity(db, &sub, child, path);
430 path.remove(&child);
431 result
432 }
433 SubSelect::None => {
434 if is_component {
435 if !path.insert(child) {
436 return Ok(db_id_map(child));
437 }
438 let wildcard = PullPattern {
440 wildcard: true,
441 db_id: true,
442 specs: Vec::new(),
443 };
444 let result = pull_entity(db, &wildcard, child, path);
445 path.remove(&child);
446 result
447 } else {
448 Ok(db_id_map(child))
449 }
450 }
451 }
452}