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 && let Value::Ref(child) = value {
317 return render_ref(db, spec, enclosing, *child, is_component, path);
318 }
319 Ok(value_to_edn(db, value))
320 };
321 let rendered = if many {
322 let items = values
323 .iter()
324 .map(|value| render(value, path))
325 .collect::<Result<Vec<_>, _>>()?;
326 Edn::Vector(items)
327 } else {
328 render(&values[0], path)?
329 };
330 let key = spec
331 .as_key
332 .clone()
333 .unwrap_or_else(|| Edn::Keyword(ident.clone()));
334 Ok(Some((key, rendered)))
335 }
336 PullAttr::Reverse(attr, reverse_ident) => {
337 let is_component = db.schema().get(*attr).is_some_and(|m| m.is_component);
338 let parents = reverse_refs(db, *attr, eid);
339 if parents.is_empty() {
340 let default = spec.default.clone().map(|form| {
341 (
342 spec.as_key
343 .clone()
344 .unwrap_or_else(|| Edn::Keyword(reverse_ident.clone())),
345 form,
346 )
347 });
348 return Ok(default);
349 }
350 let limit = spec.limit.bound();
351 let mut parents = parents;
352 if let Some(limit) = limit {
353 parents.truncate(limit);
354 }
355 let render = |parent: EntityId, path: &mut BTreeSet<EntityId>| match &spec.sub {
356 SubSelect::None => Ok(Edn::Map(vec![(
357 Edn::keyword("db/id"),
358 Edn::Long(i64::try_from(parent.raw()).unwrap_or(i64::MAX)),
359 )])),
360 SubSelect::Pattern(sub) => pull_entity(db, sub, parent, path),
361 SubSelect::Recur(_) => Err(QueryError::Parse(
362 "recursion is not supported on reverse refs".into(),
363 )),
364 };
365 let rendered = if is_component {
367 render(parents[0], path)?
368 } else {
369 Edn::Vector(
370 parents
371 .into_iter()
372 .map(|parent| render(parent, path))
373 .collect::<Result<Vec<_>, _>>()?,
374 )
375 };
376 let key = spec
377 .as_key
378 .clone()
379 .unwrap_or_else(|| Edn::Keyword(reverse_ident.clone()));
380 Ok(Some((key, rendered)))
381 }
382 }
383}
384
385fn render_ref(
386 db: &Db,
387 spec: &PullSpec,
388 enclosing: &PullPattern,
389 child: EntityId,
390 is_component: bool,
391 path: &mut BTreeSet<EntityId>,
392) -> Result<Edn, QueryError> {
393 let db_id_map = |child: EntityId| {
394 Edn::Map(vec![(
395 Edn::keyword("db/id"),
396 Edn::Long(i64::try_from(child.raw()).unwrap_or(i64::MAX)),
397 )])
398 };
399 let spec_attr = |candidate: &PullSpec| match (&candidate.attr, &spec.attr) {
400 (PullAttr::Forward(a, _), PullAttr::Forward(b, _))
401 | (PullAttr::Reverse(a, _), PullAttr::Reverse(b, _)) => a == b,
402 _ => false,
403 };
404 match &spec.sub {
405 SubSelect::Pattern(sub) => {
406 if !path.insert(child) {
407 return Ok(db_id_map(child));
408 }
409 let result = pull_entity(db, sub, child, path);
410 path.remove(&child);
411 result
412 }
413 SubSelect::Recur(depth) => {
414 if depth == &Some(0) || !path.insert(child) {
415 return Ok(db_id_map(child));
416 }
417 let mut sub = enclosing.clone();
420 for candidate in &mut sub.specs {
421 if spec_attr(candidate)
422 && let SubSelect::Recur(d) = &candidate.sub
423 {
424 candidate.sub = SubSelect::Recur(d.map(|d| d.saturating_sub(1)));
425 }
426 }
427 let result = pull_entity(db, &sub, child, path);
428 path.remove(&child);
429 result
430 }
431 SubSelect::None => {
432 if is_component {
433 if !path.insert(child) {
434 return Ok(db_id_map(child));
435 }
436 let wildcard = PullPattern {
438 wildcard: true,
439 db_id: true,
440 specs: Vec::new(),
441 };
442 let result = pull_entity(db, &wildcard, child, path);
443 path.remove(&child);
444 result
445 } else {
446 Ok(db_id_map(child))
447 }
448 }
449 }
450}