1pub mod aggregate;
6pub mod ast;
7pub mod boundary;
8pub mod builtins;
9pub mod cache;
10pub mod edn;
11pub mod entity;
12pub mod exec;
13pub mod plan;
14pub mod pull;
15
16use std::collections::{BTreeMap, BTreeSet};
17
18use corium_core::{Keyword, Value};
19use corium_db::Db;
20use thiserror::Error;
21
22use crate::aggregate::AggOut;
23use crate::ast::{FindElem, FindSpec, InSpec, Query, Var, parse_query, parse_rules};
24use crate::boundary::{edn_to_value, value_to_edn};
25use crate::edn::{Edn, EdnError};
26use crate::exec::{ExecCtx, Frame, to_entity};
27
28pub use crate::cache::QueryCache;
29pub use crate::entity::Entity;
30pub use crate::pull::{pull, pull_many};
31
32#[derive(Debug, Error, Eq, PartialEq)]
34pub enum QueryError {
35 #[error("query parse error: {0}")]
37 Parse(String),
38 #[error(transparent)]
40 Edn(#[from] EdnError),
41 #[error("unknown ident {0}")]
43 UnknownIdent(Keyword),
44 #[error("unknown database source {0}")]
46 UnknownSource(String),
47 #[error("unbound variable {0}")]
49 Unbound(String),
50 #[error("arity error: {0}")]
52 Arity(String),
53 #[error("type error: {0}")]
55 Type(String),
56 #[error("unsupported: {0}")]
58 Unsupported(String),
59 #[error("query fuel exhausted")]
61 FuelExhausted,
62}
63
64#[derive(Clone, Debug)]
66pub enum QInput<'a> {
67 Db(&'a Db),
69 Edn(Edn),
71}
72
73#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
75pub struct ExecReport {
76 pub datoms_scanned: usize,
78}
79
80pub type ExternCall = std::sync::Arc<
86 dyn Fn(&str, &[Value]) -> Option<Result<builtins::CallResult, QueryError>> + Send + Sync,
87>;
88
89#[derive(Clone, Default)]
91pub struct ExecOptions {
92 pub fuel: Option<u64>,
94 pub extern_call: Option<ExternCall>,
96}
97
98impl std::fmt::Debug for ExecOptions {
99 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100 f.debug_struct("ExecOptions")
101 .field("fuel", &self.fuel)
102 .field("extern_call", &self.extern_call.is_some())
103 .finish()
104 }
105}
106
107pub fn q(form: &Edn, inputs: &[QInput<'_>]) -> Result<Edn, QueryError> {
112 let query = parse_query(form)?;
113 run(&query, inputs, ExecOptions::default()).map(|(result, _)| result)
114}
115
116pub fn q_str(text: &str, inputs: &[QInput<'_>]) -> Result<Edn, QueryError> {
122 q(&edn::read_one(text)?, inputs)
123}
124
125#[allow(clippy::too_many_lines, clippy::needless_pass_by_value)]
130pub fn run(
131 query: &Query,
132 inputs: &[QInput<'_>],
133 options: ExecOptions,
134) -> Result<(Edn, ExecReport), QueryError> {
135 if inputs.len() != query.inputs.len() {
136 return Err(QueryError::Arity(format!(
137 "query takes {} inputs, got {}",
138 query.inputs.len(),
139 inputs.len()
140 )));
141 }
142
143 let mut dbs: BTreeMap<String, &Db> = BTreeMap::new();
145 let mut rules = Vec::new();
146 for (spec, input) in query.inputs.iter().zip(inputs) {
147 if let (InSpec::Db(name), QInput::Db(db)) = (spec, input) {
148 dbs.insert(name.clone(), db);
149 }
150 }
151 let default_db = dbs
152 .get(ast::DEFAULT_SRC)
153 .copied()
154 .or_else(|| dbs.values().next().copied());
155 let mut frames: Vec<Frame> = vec![Frame::new()];
156 for (spec, input) in query.inputs.iter().zip(inputs) {
157 match (spec, input) {
158 (InSpec::Db(_), QInput::Db(_)) => {}
159 (InSpec::Rules, QInput::Edn(form)) => rules = parse_rules(form)?,
160 (InSpec::Scalar(var), QInput::Edn(form)) => {
161 let value = input_value(default_db, form)?;
162 for frame in &mut frames {
163 frame.insert(var.clone(), value.clone());
164 }
165 }
166 (InSpec::Tuple(vars), QInput::Edn(form)) => {
167 let items = form.as_seq().ok_or_else(|| {
168 QueryError::Type(format!("tuple input requires a vector, got {form}"))
169 })?;
170 if items.len() != vars.len() {
171 return Err(QueryError::Arity("tuple input arity mismatch".into()));
172 }
173 for (var, item) in vars.iter().zip(items) {
174 let value = input_value(default_db, item)?;
175 for frame in &mut frames {
176 frame.insert(var.clone(), value.clone());
177 }
178 }
179 }
180 (InSpec::Coll(var), QInput::Edn(form)) => {
181 let items = form.as_seq().ok_or_else(|| {
182 QueryError::Type(format!("collection input requires a vector, got {form}"))
183 })?;
184 let values = items
185 .iter()
186 .map(|item| input_value(default_db, item))
187 .collect::<Result<Vec<_>, _>>()?;
188 frames = cross_bind(&frames, |frame, out| {
189 for value in &values {
190 let mut next = frame.clone();
191 next.insert(var.clone(), value.clone());
192 out.push(next);
193 }
194 });
195 }
196 (InSpec::Rel(vars), QInput::Edn(form)) => {
197 let tuples = form.as_seq().ok_or_else(|| {
198 QueryError::Type(format!("relation input requires a vector, got {form}"))
199 })?;
200 let mut converted: Vec<Vec<Value>> = Vec::new();
201 for tuple in tuples {
202 let items = tuple
203 .as_seq()
204 .ok_or_else(|| QueryError::Type("relation input requires tuples".into()))?;
205 if items.len() != vars.len() {
206 return Err(QueryError::Arity("relation input arity mismatch".into()));
207 }
208 converted.push(
209 items
210 .iter()
211 .map(|item| input_value(default_db, item))
212 .collect::<Result<_, _>>()?,
213 );
214 }
215 frames = cross_bind(&frames, |frame, out| {
216 for tuple in &converted {
217 let mut next = frame.clone();
218 for (var, value) in vars.iter().zip(tuple) {
219 next.insert(var.clone(), value.clone());
220 }
221 out.push(next);
222 }
223 });
224 }
225 (spec, input) => {
226 return Err(QueryError::Type(format!(
227 "input does not fit :in specification {spec:?}: {input:?}"
228 )));
229 }
230 }
231 }
232
233 let mut ctx = ExecCtx::new(dbs, rules);
234 if let Some(fuel) = options.fuel {
235 ctx.set_fuel(fuel);
236 }
237 if let Some(extern_call) = options.extern_call.clone() {
238 ctx.set_extern_call(extern_call);
239 }
240 let frames = exec::eval_clauses(&ctx, &query.wheres, frames, &mut Vec::new())?;
241
242 let elems = query.find.elems();
244 let mut proj_vars: Vec<Var> = elems.iter().map(|elem| elem.var().clone()).collect();
245 proj_vars.extend(query.with.iter().cloned());
246 let mut rows: BTreeSet<Vec<Value>> = BTreeSet::new();
247 for frame in frames {
248 let row = proj_vars
249 .iter()
250 .map(|var| {
251 frame
252 .get(var)
253 .cloned()
254 .ok_or_else(|| QueryError::Unbound(var.clone()))
255 })
256 .collect::<Result<Vec<_>, _>>()?;
257 rows.insert(row);
258 }
259
260 let has_aggregate = elems
262 .iter()
263 .any(|elem| matches!(elem, FindElem::Aggregate(_)));
264 let out_rows: Vec<Vec<Cell>> = if has_aggregate {
265 let group_positions: Vec<usize> = elems
266 .iter()
267 .enumerate()
268 .filter(|(_, elem)| !matches!(elem, FindElem::Aggregate(_)))
269 .map(|(index, _)| index)
270 .collect();
271 let mut groups: BTreeMap<Vec<Value>, Vec<Vec<Value>>> = BTreeMap::new();
272 for row in rows {
273 let key = group_positions
274 .iter()
275 .map(|&index| row[index].clone())
276 .collect();
277 groups.entry(key).or_default().push(row);
278 }
279 let mut out = Vec::new();
280 for group in groups.values() {
281 let mut cells = Vec::with_capacity(elems.len());
282 for (index, elem) in elems.iter().enumerate() {
283 match elem {
284 FindElem::Aggregate(agg) => {
285 let values: Vec<Value> =
286 group.iter().map(|row| row[index].clone()).collect();
287 cells.push(Cell::Agg(aggregate::apply(&agg.op, agg.n, &values)?));
288 }
289 FindElem::Var(_) | FindElem::Pull(_, _) => {
290 cells.push(Cell::Val(group[0][index].clone(), (*elem).clone()));
291 }
292 }
293 }
294 out.push(cells);
295 }
296 out
297 } else {
298 rows.into_iter()
299 .map(|row| {
300 elems
301 .iter()
302 .enumerate()
303 .map(|(index, elem)| Cell::Val(row[index].clone(), (*elem).clone()))
304 .collect()
305 })
306 .collect()
307 };
308
309 let mut edn_rows: Vec<Vec<Edn>> = Vec::with_capacity(out_rows.len());
311 for row in out_rows {
312 let mut out = Vec::with_capacity(row.len());
313 for cell in row {
314 out.push(cell_to_edn(default_db, cell)?);
315 }
316 edn_rows.push(out);
317 }
318 edn_rows.sort();
319
320 let result = match &query.find {
321 FindSpec::Rel(_) => Edn::Vector(edn_rows.into_iter().map(Edn::Vector).collect()),
322 FindSpec::Coll(_) => Edn::Vector(
323 edn_rows
324 .into_iter()
325 .filter_map(|row| row.into_iter().next())
326 .collect(),
327 ),
328 FindSpec::Tuple(_) => edn_rows.into_iter().next().map_or(Edn::Nil, Edn::Vector),
329 FindSpec::Scalar(_) => edn_rows
330 .into_iter()
331 .next()
332 .and_then(|row| row.into_iter().next())
333 .unwrap_or(Edn::Nil),
334 };
335 Ok((
336 result,
337 ExecReport {
338 datoms_scanned: ctx.scanned(),
339 },
340 ))
341}
342
343enum Cell {
344 Val(Value, FindElem),
345 Agg(AggOut),
346}
347
348fn cell_to_edn(default_db: Option<&Db>, cell: Cell) -> Result<Edn, QueryError> {
349 let db = default_db.ok_or_else(|| QueryError::UnknownSource(ast::DEFAULT_SRC.into()))?;
350 match cell {
351 Cell::Val(value, FindElem::Pull(_, pattern)) => {
352 let eid = to_entity(&value).ok_or_else(|| {
353 QueryError::Type("pull requires an entity-valued variable".into())
354 })?;
355 pull::pull(db, &pattern, eid)
356 }
357 Cell::Val(value, _) | Cell::Agg(AggOut::Value(value)) => Ok(value_to_edn(db, &value)),
358 Cell::Agg(AggOut::Coll(values)) => Ok(Edn::Vector(
359 values.iter().map(|value| value_to_edn(db, value)).collect(),
360 )),
361 Cell::Agg(AggOut::Set(values)) => {
362 let mut items: Vec<Edn> = values.iter().map(|value| value_to_edn(db, value)).collect();
363 items.sort();
364 items.dedup();
365 Ok(Edn::Set(items))
366 }
367 }
368}
369
370fn cross_bind(frames: &[Frame], mut expand: impl FnMut(&Frame, &mut Vec<Frame>)) -> Vec<Frame> {
371 let mut out = Vec::new();
372 for frame in frames {
373 expand(frame, &mut out);
374 }
375 out
376}
377
378fn input_value(db: Option<&Db>, form: &Edn) -> Result<Value, QueryError> {
381 if let Some(value) = edn_to_value(db, form) {
382 return Ok(value);
383 }
384 if let (Some(db), Edn::Vector(items)) = (db, form) {
386 if let [attr_form, value_form] = items.as_slice() {
387 if let Some(attr_kw) = attr_form.as_keyword() {
388 let attr = db
389 .idents()
390 .entid(attr_kw)
391 .ok_or_else(|| QueryError::UnknownIdent(attr_kw.clone()))?;
392 let value = edn_to_value(Some(db), value_form).ok_or_else(|| {
393 QueryError::Type(format!("bad lookup ref value {value_form}"))
394 })?;
395 let value = db.schema().get(attr).map_or(value.clone(), |meta| {
396 exec::coerce_for_type(value, meta.value_type)
397 });
398 return db
399 .lookup(attr, &value)
400 .map(Value::Ref)
401 .ok_or_else(|| QueryError::Type(format!("lookup ref {form} did not resolve")));
402 }
403 }
404 }
405 Err(QueryError::Type(format!("cannot convert input {form}")))
406}