1pub mod expr;
11mod ops;
12pub mod plan;
13
14use crate::encoding::{encode_literal, named_node_id, term_id, EncodedRows, DEFAULT_GRAPH_ID};
15use crate::error::{Error, Result};
16use crate::reason::{Entailment, GraphFilter};
17use crate::sql::Capabilities;
18use crate::stats::Stats;
19use expr::V;
20use oxrdf::{Literal, Term, Variable};
21use plan::Pos;
22use spargebra::algebra::{Expression, GraphPattern, OrderExpression, QueryDataset};
23use spargebra::term::{GroundTerm, NamedNodePattern, TermPattern, TriplePattern};
24use std::collections::{BTreeMap, HashMap, HashSet};
25use std::fmt::Write;
26
27#[derive(Debug, Clone, Default, PartialEq, Eq)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30#[cfg_attr(feature = "serde", serde(default, rename_all = "camelCase"))]
31pub struct QueryOptions {
32 pub union_default_graph: bool,
34 pub sqlite_planner: bool,
36 pub default_graph: Option<Vec<i64>>,
39 pub named_graphs: Option<Vec<i64>>,
41 pub reasoning: crate::reason::Reasoning,
43 pub include_inferred: bool,
45}
46
47#[derive(Debug, Clone)]
49pub(crate) enum Col {
50 Id(String),
52 Val(Box<V>),
54}
55
56impl Col {
57 pub(crate) fn key(&self) -> Option<&str> {
59 match self {
60 Self::Id(x) => Some(x),
61 Self::Val(v) => v.id.as_deref(),
62 }
63 }
64
65 pub(crate) fn value(&self) -> V {
66 match self {
67 Self::Id(x) => V::from_id(x),
68 Self::Val(v) => (**v).clone(),
69 }
70 }
71}
72
73#[derive(Debug, Clone)]
74pub(crate) struct Binding {
75 pub col: Col,
76 pub nullable: bool,
77 #[allow(dead_code)]
80 pub computed: bool,
81 pub correlated: bool,
83}
84
85impl Binding {
86 fn id(sql: impl Into<String>) -> Self {
87 Self {
88 col: Col::Id(sql.into()),
89 nullable: false,
90 computed: false,
91 correlated: false,
92 }
93 }
94}
95
96#[derive(Debug, Clone, PartialEq, Eq)]
97pub(crate) enum Join {
98 First,
99 Cross,
100 Inner,
101 #[allow(dead_code)]
103 Left(String),
104}
105
106#[derive(Debug, Clone)]
107pub(crate) struct FromItem {
108 pub join: Join,
109 pub item: String,
110}
111
112#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord)]
113pub(crate) enum Stage {
114 #[default]
115 Plain,
116 Grouped,
117 Ordered,
118 Distinct,
119 Sliced,
120}
121
122#[derive(Debug, Clone, Default)]
123pub(crate) struct Block {
124 pub from: Vec<FromItem>,
125 pub wheres: Vec<String>,
126 pub cols: BTreeMap<usize, Binding>,
127 pub group_by: Option<Vec<String>>,
128 pub order_by: Vec<String>,
129 pub distinct: bool,
130 pub limit: Option<usize>,
131 pub offset: usize,
132 pub stage: Stage,
133 pub extra_select: Vec<String>,
135 pub no_flatten: bool,
139}
140
141pub(crate) const VAL_FIELDS: [&str; 10] = ["i", "k", "l", "d", "g", "n", "t", "s", "b", "x"];
142
143pub(crate) fn val_fields(v: &V) -> [String; 10] {
144 [
145 v.id.clone().unwrap_or_else(|| "NULL".into()),
146 v.kind.clone(),
147 if v.computed_num {
148 "NULL".into()
149 } else {
150 v.lex.clone()
151 },
152 v.dt.clone(),
153 v.lang.clone(),
154 v.num.clone(),
155 v.nt.clone(),
156 v.ts.clone(),
157 v.boolv.clone(),
158 v.aux.clone(),
159 ]
160}
161
162impl Block {
163 pub(crate) fn is_plain(&self) -> bool {
164 self.stage == Stage::Plain
165 }
166
167 pub(crate) fn is_unit(&self) -> bool {
168 self.from.is_empty() && self.wheres.is_empty() && self.cols.is_empty()
169 }
170
171 pub(crate) fn render_from(items: &[FromItem]) -> String {
172 let mut out = String::new();
173 for (i, f) in items.iter().enumerate() {
174 if i == 0 {
175 out.push_str(&f.item);
176 continue;
177 }
178 match &f.join {
179 Join::First | Join::Inner => {
180 let _ = write!(out, " JOIN {}", f.item);
181 }
182 Join::Cross => {
183 let _ = write!(out, " CROSS JOIN {}", f.item);
184 }
185 Join::Left(on) => {
186 let _ = write!(out, " LEFT JOIN {} ON ({on})", f.item);
187 }
188 }
189 }
190 out
191 }
192
193 fn select_list(&self, vars: &[usize], text_ids: bool) -> Vec<String> {
195 let mut sel = Vec::new();
196 for idx in vars {
197 match self.cols.get(idx).map(|b| &b.col) {
198 Some(Col::Id(x)) => sel.push(if text_ids {
199 format!("CAST({x} AS TEXT) AS v{idx}")
200 } else {
201 format!("{x} AS v{idx}")
202 }),
203 Some(Col::Val(v)) => {
204 for (suffix, field) in VAL_FIELDS.iter().zip(val_fields(v)) {
205 if text_ids && *suffix == "i" {
206 sel.push(format!("CAST({field} AS TEXT) AS v{idx}_{suffix}"));
207 } else {
208 sel.push(format!("{field} AS v{idx}_{suffix}"));
209 }
210 }
211 }
212 None => sel.push(format!("NULL AS v{idx}")),
213 }
214 }
215 sel
216 }
217
218 pub(crate) fn tail(&self) -> String {
220 let mut sql = String::new();
221 if !self.from.is_empty() {
222 sql.push_str(" FROM ");
223 sql.push_str(&Self::render_from(&self.from));
224 }
225 if !self.wheres.is_empty() {
226 sql.push_str(" WHERE ");
227 sql.push_str(&self.wheres.join(" AND "));
228 }
229 if let Some(g) = &self.group_by {
230 if !g.is_empty() {
231 sql.push_str(" GROUP BY ");
232 sql.push_str(&g.join(", "));
233 }
234 }
235 if !self.order_by.is_empty() {
236 sql.push_str(" ORDER BY ");
237 sql.push_str(&self.order_by.join(", "));
238 }
239 if self.limit.is_some() || self.offset > 0 || self.no_flatten {
240 let _ = write!(
241 sql,
242 " LIMIT {}",
243 self.limit.map_or_else(|| "-1".into(), |l| l.to_string())
244 );
245 if self.offset > 0 {
246 let _ = write!(sql, " OFFSET {}", self.offset);
247 }
248 }
249 sql
250 }
251
252 pub(crate) fn to_select(&self, vars: Option<&[usize]>, text_ids: bool) -> String {
254 let all: Vec<usize> = self.cols.keys().copied().collect();
255 let mut sel = self.select_list(vars.unwrap_or(&all), text_ids);
256 sel.extend(self.extra_select.iter().cloned());
257 if sel.is_empty() {
258 sel.push("1 AS _u".into());
259 }
260 format!(
261 "SELECT {}{}{}",
262 if self.distinct { "DISTINCT " } else { "" },
263 sel.join(", "),
264 self.tail()
265 )
266 }
267}
268
269#[derive(Debug, Clone)]
270enum DefaultGraph {
271 Zero,
272 Union,
273 List(Vec<i64>),
274}
275
276#[derive(Debug, Clone)]
277struct Dataset {
278 default: DefaultGraph,
279 named: Option<Vec<i64>>,
280}
281
282#[derive(Debug, Clone, Copy)]
283enum GraphScope {
284 Default,
285 Fixed(i64),
286 Var(usize),
287}
288
289pub(crate) struct Compiler<'a> {
291 pub stats: &'a Stats,
292 pub caps: &'a Capabilities,
293 pub options: &'a QueryOptions,
294 vars: HashMap<Variable, usize>,
295 pub var_names: Vec<Variable>,
296 aliases: usize,
297 pub constants: HashMap<i64, Term>,
299 pub outer: Vec<BTreeMap<usize, Binding>>,
301 pub now: Literal,
302 pub base_iri: Option<String>,
303 dataset: Dataset,
304 scope: GraphScope,
305 pub rows: EncodedRows,
307 pub notes: Vec<String>,
309 pending_triples: Vec<(usize, TriplePattern)>,
311 pub(crate) plan_hint: Vec<HashSet<usize>>,
314}
315
316impl<'a> Compiler<'a> {
317 pub(crate) fn new(
318 stats: &'a Stats,
319 caps: &'a Capabilities,
320 options: &'a QueryOptions,
321 dataset: Option<&QueryDataset>,
322 base_iri: Option<String>,
323 ) -> Self {
324 let mut dataset = match dataset {
325 Some(ds) => Dataset {
326 default: DefaultGraph::List(
327 ds.default
328 .iter()
329 .map(|g| named_node_id(g.as_str()))
330 .collect(),
331 ),
332 named: ds
334 .named
335 .as_ref()
336 .map(|n| n.iter().map(|g| named_node_id(g.as_str())).collect()),
337 },
338 None => Dataset {
339 default: if options.union_default_graph {
340 DefaultGraph::Union
341 } else {
342 DefaultGraph::Zero
343 },
344 named: None,
345 },
346 };
347 if let Some(d) = &options.default_graph {
348 dataset.default = DefaultGraph::List(d.clone());
349 }
350 if let Some(n) = &options.named_graphs {
351 dataset.named = Some(n.clone());
352 }
353 Self {
354 stats,
355 caps,
356 options,
357 vars: HashMap::new(),
358 var_names: Vec::new(),
359 aliases: 0,
360 constants: HashMap::new(),
361 outer: Vec::new(),
362 now: Literal::from(oxsdatatypes::DateTime::now()),
363 base_iri,
364 dataset,
365 scope: GraphScope::Default,
366 rows: EncodedRows::default(),
367 notes: Vec::new(),
368 pending_triples: Vec::new(),
369 plan_hint: Vec::new(),
370 }
371 }
372
373 pub(crate) fn join_terms(
378 &mut self,
379 b: &mut Block,
380 e: &Expression,
381 ) -> Vec<(usize, Binding, String)> {
382 if matches!(e, Expression::Variable(_) | Expression::Bound(_)) {
383 return Vec::new();
384 }
385 let mut vars = Vec::new();
386 expression_variables(e, &mut vars);
387 self.join_term_vars(b, &vars)
388 }
389
390 pub(crate) fn join_term_vars(
392 &mut self,
393 b: &mut Block,
394 vars: &[Variable],
395 ) -> Vec<(usize, Binding, String)> {
396 let mut restore = Vec::new();
397 if b.stage >= Stage::Grouped || b.from.is_empty() || !b.is_plain() {
398 return restore;
399 }
400 for v in vars {
401 let Some(&idx) = self.vars.get(v) else {
402 continue;
403 };
404 let Some(bind) = b.cols.get(&idx) else {
405 continue;
406 };
407 let Col::Id(x) = &bind.col else {
408 continue;
409 };
410 if bind.correlated || bind.computed {
411 continue;
412 }
413 let x = x.clone();
414 let t = self.alias("t");
415 b.from.push(FromItem {
416 join: Join::Left(format!("{t}.id = {x}")),
417 item: format!("terms {t}"),
418 });
419 let mut joined = bind.clone();
420 let v = V::from_joined(&x, &t);
421 let marker = v.lex.clone();
422 joined.col = Col::Val(Box::new(v));
423 restore.push((idx, bind.clone(), marker));
424 b.cols.insert(idx, joined);
425 }
426 restore
427 }
428
429 pub(crate) fn restore_terms(b: &mut Block, restore: Vec<(usize, Binding, String)>) {
432 for (i, bind, marker) in restore {
433 if let Some(cur) = b.cols.get_mut(&i) {
434 if matches!(&cur.col, Col::Val(v) if v.lex == marker) {
435 *cur = bind;
436 }
437 }
438 }
439 }
440
441 pub(crate) fn shallow(&mut self, b: Block, e: &Expression) -> Result<(Block, Expression)> {
446 use Expression as E;
447 let two = |me: &mut Self, b: Block, x: &E, y: &E| -> Result<(Block, E, E)> {
448 let (b, x) = me.shallow_arg(b, x)?;
449 let (b, y) = me.shallow_arg(b, y)?;
450 Ok((b, x, y))
451 };
452 Ok(match e {
453 E::FunctionCall(f, args) => {
454 let mut b = b;
455 let mut out = Vec::with_capacity(args.len());
456 for a in args {
457 let (nb, a) = self.shallow_arg(b, a)?;
458 b = nb;
459 out.push(a);
460 }
461 (b, E::FunctionCall(f.clone(), out))
462 }
463 E::Equal(x, y) => {
464 let (b, x, y) = two(self, b, x, y)?;
465 (b, E::Equal(Box::new(x), Box::new(y)))
466 }
467 E::Less(x, y) => {
468 let (b, x, y) = two(self, b, x, y)?;
469 (b, E::Less(Box::new(x), Box::new(y)))
470 }
471 E::LessOrEqual(x, y) => {
472 let (b, x, y) = two(self, b, x, y)?;
473 (b, E::LessOrEqual(Box::new(x), Box::new(y)))
474 }
475 E::Greater(x, y) => {
476 let (b, x, y) = two(self, b, x, y)?;
477 (b, E::Greater(Box::new(x), Box::new(y)))
478 }
479 E::GreaterOrEqual(x, y) => {
480 let (b, x, y) = two(self, b, x, y)?;
481 (b, E::GreaterOrEqual(Box::new(x), Box::new(y)))
482 }
483 E::Add(x, y) => {
484 let (b, x, y) = two(self, b, x, y)?;
485 (b, E::Add(Box::new(x), Box::new(y)))
486 }
487 E::Subtract(x, y) => {
488 let (b, x, y) = two(self, b, x, y)?;
489 (b, E::Subtract(Box::new(x), Box::new(y)))
490 }
491 E::Multiply(x, y) => {
492 let (b, x, y) = two(self, b, x, y)?;
493 (b, E::Multiply(Box::new(x), Box::new(y)))
494 }
495 E::Divide(x, y) => {
496 let (b, x, y) = two(self, b, x, y)?;
497 (b, E::Divide(Box::new(x), Box::new(y)))
498 }
499 E::And(x, y) => {
500 let (b, x) = self.shallow(b, x)?;
501 let (b, y) = self.shallow(b, y)?;
502 (b, E::And(Box::new(x), Box::new(y)))
503 }
504 E::Or(x, y) => {
505 let (b, x) = self.shallow(b, x)?;
506 let (b, y) = self.shallow(b, y)?;
507 (b, E::Or(Box::new(x), Box::new(y)))
508 }
509 E::Not(x) => {
510 let (b, x) = self.shallow(b, x)?;
511 (b, E::Not(Box::new(x)))
512 }
513 _ => (b, e.clone()),
514 })
515 }
516
517 fn shallow_arg(&mut self, b: Block, a: &Expression) -> Result<(Block, Expression)> {
520 const LIMIT: usize = 1_500;
521 let (mut b, a) = self.shallow(b, a)?;
522 if !matches!(a, Expression::FunctionCall(..)) {
523 return Ok((b, a));
524 }
525 let v = self.expr_term(&a, &b.cols)?;
526 if v.size() <= LIMIT {
527 return Ok((b, a));
528 }
529 let hidden = self.fresh_var("arg");
530 b.cols.insert(
531 hidden,
532 Binding {
533 col: Col::Val(Box::new(v)),
534 nullable: true,
535 computed: true,
536 correlated: false,
537 },
538 );
539 b = self.seal(b);
540 Ok((b, Expression::Variable(self.var_names[hidden].clone())))
541 }
542
543 pub(crate) fn var(&mut self, v: &Variable) -> usize {
544 if let Some(i) = self.vars.get(v) {
545 return *i;
546 }
547 let i = self.var_names.len();
548 self.vars.insert(v.clone(), i);
549 self.var_names.push(v.clone());
550 i
551 }
552
553 pub(crate) fn fresh_var(&mut self, hint: &str) -> usize {
554 let v = Variable::new_unchecked(format!("\u{1}{hint}{}", self.var_names.len()));
555 self.var(&v)
556 }
557
558 pub(crate) fn alias(&mut self, prefix: &str) -> String {
559 self.aliases += 1;
560 format!("{prefix}{}", self.aliases)
561 }
562
563 pub(crate) fn constant_id(&mut self, t: &Term) -> Result<i64> {
565 let id = match t {
566 Term::Triple(tr) => {
567 let id = self.rows.triple(tr.as_ref().as_ref());
568 self.constants.insert(id, t.clone());
569 self.constants.insert(
571 term_id(tr.subject.as_ref().into()),
572 tr.subject.clone().into(),
573 );
574 self.constants.insert(
575 named_node_id(tr.predicate.as_str()),
576 tr.predicate.clone().into(),
577 );
578 self.constants
579 .insert(term_id(tr.object.as_ref()), tr.object.clone());
580 return Ok(id);
581 }
582 Term::Literal(l) => encode_literal(l.as_ref()).0,
583 _ => term_id(t.as_ref()),
584 };
585 self.constants.insert(id, t.clone());
586 Ok(id)
587 }
588
589 fn outer_binding(&self, idx: usize) -> Option<&Binding> {
590 self.outer.iter().rev().find_map(|s| s.get(&idx))
591 }
592
593 pub(crate) fn seal(&mut self, b: Block) -> Block {
595 let alias = self.alias("s");
596 let sql = b.to_select(None, false);
597 let mut cols = BTreeMap::new();
598 for (idx, bind) in &b.cols {
599 let col = match &bind.col {
600 Col::Id(_) => Col::Id(format!("{alias}.v{idx}")),
601 Col::Val(v) => Col::Val(Box::new(V {
602 id: v.id.as_ref().map(|_| format!("{alias}.v{idx}_i")),
603 kind: format!("{alias}.v{idx}_k"),
604 lex: format!("{alias}.v{idx}_l"),
605 dt: format!("{alias}.v{idx}_d"),
606 lang: format!("{alias}.v{idx}_g"),
607 num: format!("{alias}.v{idx}_n"),
608 nt: format!("{alias}.v{idx}_t"),
609 ts: format!("{alias}.v{idx}_s"),
610 boolv: format!("{alias}.v{idx}_b"),
611 stat: v.stat,
612 computed_num: false,
613 decodable: v.decodable,
614 aux: format!("{alias}.v{idx}_x"),
615 tz: "NULL".into(),
616 })),
617 };
618 cols.insert(
619 *idx,
620 Binding {
621 col,
622 nullable: bind.nullable,
623 computed: false,
624 correlated: false,
625 },
626 );
627 }
628 for (idx, bind) in &b.cols {
630 if let Col::Val(v) = &bind.col {
631 if v.computed_num {
632 if let Some(Binding {
633 col: Col::Val(sv), ..
634 }) = cols.get_mut(idx)
635 {
636 let n = V::numeric(sv.num.clone(), sv.nt.clone());
637 sv.lex = n.lex;
638 sv.computed_num = true;
639 }
640 }
641 }
642 }
643 Block {
644 from: vec![FromItem {
645 join: Join::First,
646 item: format!("({sql}) AS {alias}"),
647 }],
648 cols,
649 ..Block::default()
650 }
651 }
652
653 fn plain(&mut self, b: Block) -> Block {
654 if b.is_plain() {
655 b
656 } else {
657 self.seal(b)
658 }
659 }
660
661 pub(crate) fn pattern(&mut self, p: &GraphPattern) -> Result<Block> {
663 match p {
664 GraphPattern::Bgp { patterns } => self.bgp(patterns),
665 GraphPattern::Join { left, right } => {
666 let a = self.pattern(left)?;
667 if let GraphPattern::Path {
668 subject,
669 path,
670 object,
671 } = right.as_ref()
672 {
673 if let Some(b) = self.seeded_path(&a, subject, path, object)? {
674 return self.join(a, b);
675 }
676 }
677 let b = self.pattern(right)?;
678 self.join(a, b)
679 }
680 GraphPattern::Filter { expr, inner } => {
681 let b = self.pattern(inner)?;
682 let b = self.plain(b);
683 let (b, expr) = self.shallow(b, expr)?;
684 let mut b = b;
685 let conds = self.filter_conditions(&expr, &b.cols)?;
686 b.wheres.extend(conds);
687 Ok(b)
688 }
689 GraphPattern::Graph { name, inner } => {
690 let saved = self.scope;
691 self.scope = match name {
692 NamedNodePattern::NamedNode(n) => {
693 let id = self.constant_id(&n.clone().into())?;
694 GraphScope::Fixed(id)
695 }
696 NamedNodePattern::Variable(v) => GraphScope::Var(self.var(v)),
697 };
698 let accesses = self.aliases;
699 let r = self.pattern(inner);
700 let scope = self.scope;
701 self.scope = saved;
702 let mut b = r?;
703 match scope {
704 GraphScope::Var(v) => {
705 if !b.cols.contains_key(&v) {
706 let g = self.graph_list_block(v);
708 b = self.join(b, g)?;
709 }
710 }
711 GraphScope::Fixed(id) => {
712 if self.aliases == accesses || !has_quad_access(p) {
713 b = self.plain(b);
715 b.wheres
716 .push(format!("EXISTS (SELECT 1 FROM graphs WHERE id = {id})"));
717 }
718 }
719 GraphScope::Default => {}
720 }
721 Ok(b)
722 }
723 GraphPattern::Extend {
724 inner,
725 variable,
726 expression,
727 } => {
728 let b = self.pattern(inner)?;
729 let mut b = self.plain(b);
730 let restore = self.join_terms(&mut b, expression);
731 let (b, expression) = self.shallow(b, expression)?;
732 let mut b = b;
733 let expression = &expression;
734 let v = self.expr_term(expression, &b.cols)?;
735 Self::restore_terms(&mut b, restore);
736 let idx = self.var(variable);
737 let (nullable, computed) = match expression {
738 Expression::Variable(x) => {
739 let xi = self.var(x);
740 (b.cols.get(&xi).is_none_or(|b| b.nullable), false)
741 }
742 Expression::NamedNode(_) | Expression::Literal(_) => (false, true),
743 _ => (true, true),
744 };
745 let col = match &v.id {
746 Some(id) if v.decodable => Col::Id(id.clone()),
747 _ => Col::Val(Box::new(v)),
748 };
749 b.cols.insert(
750 idx,
751 Binding {
752 col,
753 nullable,
754 computed,
755 correlated: false,
756 },
757 );
758 Ok(b)
759 }
760 GraphPattern::Values {
761 variables,
762 bindings,
763 } => self.values(variables, bindings),
764 GraphPattern::OrderBy { inner, expression } => {
765 let mut b = self.pattern(inner)?;
766 if b.stage > Stage::Grouped {
767 b = self.seal(b);
768 }
769 for oe in expression {
770 let (e, desc) = match oe {
771 OrderExpression::Asc(e) => (e, false),
772 OrderExpression::Desc(e) => (e, true),
773 };
774 let v = self.expr_term(e, &b.cols)?;
775 for k in order_keys(&v) {
776 b.order_by.push(if desc { format!("{k} DESC") } else { k });
777 }
778 }
779 b.stage = Stage::Ordered;
780 Ok(b)
781 }
782 GraphPattern::Project { inner, variables } => {
783 let graph_var = match self.scope {
787 GraphScope::Var(g) => Some(g),
788 _ => None,
789 };
790 let hidden = graph_var.map(|_| self.fresh_var("g"));
791 let saved = self.scope;
792 if let Some(h) = hidden {
793 self.scope = GraphScope::Var(h);
794 }
795 let r = self.pattern(inner);
796 self.scope = saved;
797 let mut b = r?;
798 if b.stage >= Stage::Distinct {
799 b = self.seal(b);
800 }
801 let hidden_binding = hidden.and_then(|h| b.cols.get(&h).cloned());
802 let mut cols = BTreeMap::new();
803 for v in variables {
804 let idx = self.var(v);
805 let bind = b.cols.remove(&idx).unwrap_or(Binding {
806 col: Col::Id("NULL".into()),
807 nullable: true,
808 computed: true,
809 correlated: false,
810 });
811 cols.insert(idx, bind);
812 }
813 b.cols = cols;
814 if let (Some(g), Some(h)) = (graph_var, hidden_binding) {
815 match b.cols.get(&g).and_then(|x| x.col.key().map(str::to_string)) {
816 Some(inner_g) => {
817 let hk = h.col.key().unwrap_or("NULL").to_string();
818 b.wheres.push(format!("{inner_g} = {hk}"));
819 }
820 None => {
821 b.cols.insert(g, h);
822 }
823 }
824 }
825 Ok(b)
826 }
827 GraphPattern::Distinct { inner } => {
828 let mut b = self.pattern(inner)?;
829 if b.stage >= Stage::Distinct {
830 b = self.seal(b);
831 }
832 b.distinct = true;
833 b.stage = Stage::Distinct;
834 Ok(b)
835 }
836 GraphPattern::Reduced { inner } => self.pattern(&GraphPattern::Distinct {
838 inner: inner.clone(),
839 }),
840 GraphPattern::Slice {
841 inner,
842 start,
843 length,
844 } => {
845 let mut b = self.pattern(inner)?;
846 if b.stage == Stage::Sliced {
847 b = self.seal(b);
848 }
849 b.offset = *start;
850 b.limit = *length;
851 b.stage = Stage::Sliced;
852 Ok(b)
853 }
854 other => self.pattern_ext(other),
855 }
856 }
857
858 fn pattern_ext(&mut self, p: &GraphPattern) -> Result<Block> {
860 self.pattern_m2(p)
861 }
862
863 pub(crate) fn unify(a: &Binding, b: &Binding) -> (String, Binding) {
866 let eq = match (a.col.key(), b.col.key()) {
867 (Some(x), Some(y)) => format!("{x} = {y}"),
868 _ => expr::same_term(&a.col.value(), &b.col.value()),
869 };
870 if !a.nullable && !b.nullable {
871 return (eq, a.clone());
872 }
873 let is_null = |x: &Binding| match &x.col {
874 Col::Id(i) => format!("{i} IS NULL"),
875 Col::Val(v) => format!("({}) IS NULL", v.kind),
876 };
877 let cond = format!("({eq} OR {} OR {})", is_null(a), is_null(b));
878 let col = match (&a.col, &b.col) {
879 (Col::Id(x), Col::Id(y)) => Col::Id(format!("COALESCE({x}, {y})")),
880 _ => {
881 let (av, bv) = (a.col.value(), b.col.value());
882 Col::Val(Box::new(Self::choose(
883 &format!("(({}) IS NOT NULL)", av.kind),
884 &av,
885 &bv,
886 )))
887 }
888 };
889 (
890 cond,
891 Binding {
892 col,
893 nullable: a.nullable && b.nullable,
894 computed: true,
895 correlated: a.correlated || b.correlated,
896 },
897 )
898 }
899
900 fn graph_list_block(&mut self, v: usize) -> Block {
901 let a = self.alias("gr");
902 let mut b = Block::default();
903 b.from.push(FromItem {
904 join: Join::First,
905 item: format!("graphs {a}"),
906 });
907 if let Some(named) = &self.dataset.named {
908 b.wheres.push(in_list(&format!("{a}.id"), named));
909 }
910 b.cols.insert(v, Binding::id(format!("{a}.id")));
911 b
912 }
913
914 fn pos(&mut self, t: &TermPattern, bnodes: &mut HashMap<String, usize>) -> Result<Pos> {
915 Ok(match t {
916 TermPattern::NamedNode(n) => Pos::Const(self.constant_id(&n.clone().into())?),
917 TermPattern::Literal(l) => Pos::Const(self.constant_id(&l.clone().into())?),
918 TermPattern::Variable(v) => Pos::Var(self.var(v)),
919 TermPattern::BlankNode(b) => {
920 let _ = bnodes;
923 Pos::Var(self.var(&Variable::new_unchecked(format!("\u{1}b{}", b.as_str()))))
924 }
925 TermPattern::Triple(tp) => {
926 if let Some(t) = ground_triple(tp) {
927 Pos::Const(self.constant_id(&t.into())?)
928 } else {
929 let v = self.fresh_var("t");
930 self.pending_triples.push((v, (**tp).clone()));
931 Pos::Var(v)
932 }
933 }
934 })
935 }
936
937 fn pos_nn(&mut self, p: &NamedNodePattern) -> Result<Pos> {
938 Ok(match p {
939 NamedNodePattern::NamedNode(n) => Pos::Const(self.constant_id(&n.clone().into())?),
940 NamedNodePattern::Variable(v) => Pos::Var(self.var(v)),
941 })
942 }
943
944 pub(crate) fn bind_pos(&mut self, b: &mut Block, colsql: &str, pos: Pos) -> Result<()> {
946 match pos {
947 Pos::Const(id) => b.wheres.push(format!("{colsql} = {id}")),
948 Pos::Var(v) => {
949 if let Some(existing) = b.cols.get(&v) {
950 let cond = match existing.col.key() {
951 Some(x) => format!("{colsql} = {x}"),
952 None => expr::same_term(&V::from_id(colsql), &existing.col.value()),
953 };
954 b.wheres.push(if existing.nullable {
955 let null = match &existing.col {
956 Col::Id(x) => format!("{x} IS NULL"),
957 Col::Val(v) => format!("({}) IS NULL", v.kind),
958 };
959 format!("({null} OR {cond})")
960 } else {
961 cond
962 });
963 return Ok(());
964 }
965 let mut correlated = false;
966 if let Some(outer) = self.outer_binding(v).cloned() {
967 let (cond, _) = Self::unify(&Binding::id(colsql), &outer);
968 b.wheres.push(cond);
969 correlated = true;
970 }
971 b.cols.insert(
972 v,
973 Binding {
974 correlated,
975 ..Binding::id(colsql)
976 },
977 );
978 }
979 }
980 Ok(())
981 }
982
983 pub(crate) fn graph_pos(&mut self, b: &mut Block, q: &str, selective: bool) -> Result<()> {
989 let plus = if selective { "+" } else { "" };
990 match self.scope {
991 GraphScope::Default => match self.dataset.default.clone() {
992 DefaultGraph::Zero => b.wheres.push(format!("{plus}{q}.g = {DEFAULT_GRAPH_ID}")),
993 DefaultGraph::List(l) if l.is_empty() => b.wheres.push("0".into()),
994 DefaultGraph::List(l) if l.len() == 1 => {
995 b.wheres.push(format!("{plus}{q}.g = {}", l[0]))
996 }
997 DefaultGraph::List(l) => {
998 b.wheres.push(in_list(&format!("{q}.g"), &l));
999 let d = self.alias("d");
1000 b.wheres.push(format!(
1001 "NOT EXISTS (SELECT 1 FROM {} {d} WHERE {d}.s = {q}.s AND {d}.p = {q}.p AND {d}.o = {q}.o AND {d}.g < {q}.g AND {})",
1002 self.entailment().base(),
1003 in_list(&format!("{d}.g"), &l)
1004 ));
1005 }
1006 DefaultGraph::Union => {
1007 let d = self.alias("d");
1008 let base = self.entailment().base();
1009 b.wheres.push(format!(
1010 "NOT EXISTS (SELECT 1 FROM {base} {d} WHERE {d}.s = {q}.s AND {d}.p = {q}.p AND {d}.o = {q}.o AND {d}.g < {q}.g)"
1011 ));
1012 }
1013 },
1014 GraphScope::Fixed(id) => {
1015 if self
1016 .dataset
1017 .named
1018 .as_ref()
1019 .is_some_and(|n| !n.contains(&id))
1020 {
1021 b.wheres.push("0".into());
1022 }
1023 b.wheres.push(format!("{plus}{q}.g = {id}"));
1024 }
1025 GraphScope::Var(v) => {
1026 b.wheres.push(format!("{q}.g <> {DEFAULT_GRAPH_ID}"));
1027 if let Some(named) = self.dataset.named.clone() {
1028 b.wheres.push(in_list(&format!("{q}.g"), &named));
1029 }
1030 self.bind_pos(b, &format!("{q}.g"), Pos::Var(v))?;
1031 }
1032 }
1033 Ok(())
1034 }
1035
1036 pub(crate) fn entailment(&self) -> Entailment<'a> {
1038 Entailment {
1039 reasoning: self.options.reasoning,
1040 inferred: self.options.include_inferred,
1041 transitive: &self.stats.transitive,
1042 max_compound: self.caps.max_compound_select,
1043 }
1044 }
1045
1046 pub(crate) fn quad_access(
1048 &mut self,
1049 b: &mut Block,
1050 join: Join,
1051 s: Pos,
1052 p: Pos,
1053 o: Pos,
1054 ) -> Result<String> {
1055 let q = self.alias("q");
1056 let bound = |pos: Pos, b: &Block, me: &Self| match pos {
1057 Pos::Const(_) => true,
1058 Pos::Var(v) => b.cols.contains_key(&v) || me.outer_binding(v).is_some(),
1059 };
1060 let selective = bound(s, b, self) || bound(p, b, self) || bound(o, b, self);
1061 let ent = self.entailment();
1062 let merge = match (&self.scope, &self.dataset.default) {
1065 (GraphScope::Default, DefaultGraph::Union) if ent.active() => {
1066 Some(GraphFilter::Merge(None))
1067 }
1068 (GraphScope::Default, DefaultGraph::List(l)) if ent.active() && l.len() > 1 => {
1069 Some(GraphFilter::Merge(Some(l.clone())))
1070 }
1071 _ => None,
1072 };
1073 let source = if ent.active() {
1074 let c = |p: Pos| match p {
1075 Pos::Const(id) => Some(id),
1076 Pos::Var(_) => None,
1077 };
1078 ent.source(
1079 c(s),
1080 c(p),
1081 c(o),
1082 merge.as_ref().unwrap_or(&GraphFilter::Keep),
1083 )
1084 } else {
1085 "quads".into()
1086 };
1087 b.from.push(FromItem {
1088 join: if b.from.is_empty() { Join::First } else { join },
1089 item: format!("{source} {q}"),
1090 });
1091 self.bind_pos(b, &format!("{q}.s"), s)?;
1092 self.bind_pos(b, &format!("{q}.p"), p)?;
1093 self.bind_pos(b, &format!("{q}.o"), o)?;
1094 if merge.is_none() {
1095 self.graph_pos(b, &q, selective)?;
1096 }
1097 Ok(q)
1098 }
1099
1100 fn bgp(&mut self, patterns: &[TriplePattern]) -> Result<Block> {
1101 if patterns.is_empty() {
1102 return Ok(Block::default());
1103 }
1104 let mut bnodes = HashMap::new();
1105 let mut enc = Vec::with_capacity(patterns.len());
1106 for tp in patterns {
1107 enc.push([
1108 self.pos(&tp.subject, &mut bnodes)?,
1109 self.pos_nn(&tp.predicate)?,
1110 self.pos(&tp.object, &mut bnodes)?,
1111 ]);
1112 }
1113 let pre: HashSet<usize> = self
1114 .outer
1115 .iter()
1116 .flat_map(|m| m.keys().copied())
1117 .chain(self.plan_hint.iter().flatten().copied())
1118 .collect();
1119 let ord: Vec<usize> = if self.options.sqlite_planner {
1120 (0..enc.len()).collect()
1121 } else {
1122 plan::order(&enc, &pre, self.stats)
1123 };
1124 let join = if self.options.sqlite_planner {
1125 Join::Inner
1126 } else {
1127 Join::Cross
1128 };
1129 {
1131 let mut bound = pre.clone();
1132 let mut steps = Vec::new();
1133 for (n, i) in ord.iter().enumerate() {
1134 let est = plan::estimate(&enc[*i], &bound, self.stats);
1135 let vars: Vec<usize> = enc[*i]
1136 .iter()
1137 .filter_map(|p| match p {
1138 Pos::Var(v) => Some(*v),
1139 Pos::Const(_) => None,
1140 })
1141 .collect();
1142 if n > 0 && !vars.iter().any(|v| bound.contains(v)) {
1143 self.notes.push(format!(
1144 "warning: Cartesian product: triple pattern {} shares no variable with the patterns before it",
1145 patterns[*i]
1146 ));
1147 }
1148 steps.push(format!("{} (~{est:.0} rows)", patterns[*i]));
1149 bound.extend(vars);
1150 }
1151 self.notes.push(format!(
1152 "join order ({}): {}",
1153 if self.options.sqlite_planner {
1154 "SQLite planner"
1155 } else if self.stats.available {
1156 "statistics"
1157 } else {
1158 "heuristics, run optimize() for statistics"
1159 },
1160 steps.join(" → ")
1161 ));
1162 }
1163 let mut b = Block::default();
1164 for i in ord {
1165 let [s, p, o] = enc[i];
1166 self.quad_access(&mut b, join.clone(), s, p, o)?;
1167 }
1168 while let Some((v, tp)) = self.pending_triples.pop() {
1171 let Some(key) = b.cols.get(&v).and_then(|x| x.col.key().map(str::to_string)) else {
1172 return Err(Error::unsupported("unbound triple term pattern"));
1173 };
1174 let t = self.alias("tt");
1175 b.from.push(FromItem {
1176 join: Join::Inner,
1177 item: format!("triple_terms {t}"),
1178 });
1179 b.wheres.push(format!("{t}.id = {key}"));
1180 let sp = self.pos(&tp.subject, &mut bnodes)?;
1181 let pp = self.pos_nn(&tp.predicate)?;
1182 let op = self.pos(&tp.object, &mut bnodes)?;
1183 self.bind_pos(&mut b, &format!("{t}.s"), sp)?;
1184 self.bind_pos(&mut b, &format!("{t}.p"), pp)?;
1185 self.bind_pos(&mut b, &format!("{t}.o"), op)?;
1186 }
1187 Ok(b)
1188 }
1189
1190 pub(crate) fn join(&mut self, a: Block, b: Block) -> Result<Block> {
1192 let mut a = self.plain(a);
1193 let b = self.plain(b);
1194 if a.is_unit() {
1195 return Ok(b);
1196 }
1197 if b.is_unit() {
1198 return Ok(a);
1199 }
1200 for (idx, bb) in b.cols {
1201 match a.cols.get(&idx).cloned() {
1202 None => {
1203 a.cols.insert(idx, bb);
1204 }
1205 Some(ab) => {
1206 let (cond, merged) = Self::unify(&ab, &bb);
1207 a.wheres.push(cond);
1208 a.cols.insert(idx, merged);
1209 }
1210 }
1211 }
1212 for mut item in b.from {
1213 if a.from.is_empty() {
1214 item.join = Join::First;
1215 } else if item.join == Join::First {
1216 item.join = Join::Inner;
1217 }
1218 a.from.push(item);
1219 }
1220 a.wheres.extend(b.wheres);
1221 Ok(a)
1222 }
1223
1224 fn values(
1225 &mut self,
1226 variables: &[Variable],
1227 bindings: &[Vec<Option<GroundTerm>>],
1228 ) -> Result<Block> {
1229 let mut b = Block::default();
1230 if variables.is_empty() {
1231 match bindings.len() {
1232 0 => b.wheres.push("0".into()),
1233 1 => {}
1234 n => b.from.push(FromItem {
1235 join: Join::First,
1236 item: format!(
1237 "(VALUES {}) AS {}",
1238 vec!["(1)"; n].join(","),
1239 self.alias("u")
1240 ),
1241 }),
1242 }
1243 return Ok(b);
1244 }
1245 let idxs: Vec<usize> = variables.iter().map(|v| self.var(v)).collect();
1246 if bindings.is_empty() {
1247 b.wheres.push("0".into());
1248 for i in idxs {
1249 b.cols.insert(
1250 i,
1251 Binding {
1252 nullable: true,
1253 computed: true,
1254 ..Binding::id("NULL")
1255 },
1256 );
1257 }
1258 return Ok(b);
1259 }
1260 let mut rows = Vec::new();
1263 let mut nullable = vec![false; idxs.len()];
1264 for row in bindings {
1265 let mut vals = Vec::new();
1266 for (j, t) in row.iter().enumerate() {
1267 let fields = match t {
1268 None => {
1269 nullable[j] = true;
1270 val_fields(&V::null())
1271 }
1272 Some(t) => {
1273 let term = ground_to_term(t);
1274 let id = self.constant_id(&term)?;
1275 val_fields(&V::from_term(&term, id)?)
1276 }
1277 };
1278 vals.extend(fields);
1279 }
1280 rows.push(format!("({})", vals.join(",")));
1281 }
1282 let a = self.alias("vals");
1283 b.from.push(FromItem {
1284 join: Join::First,
1285 item: format!("(VALUES {}) AS {a}", rows.join(",")),
1286 });
1287 let n = VAL_FIELDS.len();
1288 for (j, i) in idxs.into_iter().enumerate() {
1289 let c = |k: usize| format!("{a}.column{}", j * n + k + 1);
1290 let v = V {
1291 id: Some(c(0)),
1292 kind: c(1),
1293 lex: c(2),
1294 dt: c(3),
1295 lang: c(4),
1296 num: c(5),
1297 nt: c(6),
1298 ts: c(7),
1299 boolv: c(8),
1300 stat: expr::Stat::Any,
1301 computed_num: false,
1302 decodable: false,
1303 aux: c(9),
1304 tz: "NULL".into(),
1305 };
1306 b.cols.insert(
1307 i,
1308 Binding {
1309 col: Col::Val(Box::new(v)),
1310 nullable: nullable[j],
1311 computed: false,
1312 correlated: false,
1313 },
1314 );
1315 }
1316 Ok(b)
1317 }
1318
1319 pub(crate) fn filter_conditions(
1321 &mut self,
1322 e: &Expression,
1323 cols: &BTreeMap<usize, Binding>,
1324 ) -> Result<Vec<String>> {
1325 if let Expression::And(a, b) = e {
1326 let mut out = self.filter_conditions(a, cols)?;
1327 out.extend(self.filter_conditions(b, cols)?);
1328 return Ok(out);
1329 }
1330 if let Expression::Equal(a, b) | Expression::SameTerm(a, b) = e {
1331 let pair = match (a.as_ref(), b.as_ref()) {
1332 (Expression::Variable(v), c) | (c, Expression::Variable(v)) => Some((v, c)),
1333 _ => None,
1334 };
1335 if let Some((v, c)) = pair {
1336 let t: Option<Term> = match c {
1337 Expression::NamedNode(n) => Some(n.clone().into()),
1338 Expression::Literal(l)
1339 if matches!(e, Expression::SameTerm(..))
1340 || l.language().is_some()
1341 || l.datatype() == oxrdf::vocab::xsd::STRING =>
1342 {
1343 Some(l.clone().into())
1344 }
1345 _ => None,
1346 };
1347 if let Some(t) = t {
1348 let idx = self.var(v);
1349 let binding = cols.get(&idx).or_else(|| self.outer_binding(idx)).cloned();
1350 if let Some(Binding {
1351 col: Col::Id(x), ..
1352 }) = binding
1353 {
1354 let id = self.constant_id(&t)?;
1356 return Ok(vec![format!("{x} = {id}")]);
1357 }
1358 }
1359 }
1360 }
1361 Ok(vec![self.expr_bool(e, cols)?])
1362 }
1363}
1364
1365fn ground_triple(tp: &TriplePattern) -> Option<oxrdf::Triple> {
1367 fn term(t: &TermPattern) -> Option<Term> {
1368 Some(match t {
1369 TermPattern::NamedNode(n) => n.clone().into(),
1370 TermPattern::Literal(l) => l.clone().into(),
1371 TermPattern::Triple(tp) => ground_triple(tp)?.into(),
1372 TermPattern::BlankNode(_) | TermPattern::Variable(_) => return None,
1373 })
1374 }
1375 let s = match term(&tp.subject)? {
1376 Term::NamedNode(n) => oxrdf::NamedOrBlankNode::from(n),
1377 _ => return None,
1378 };
1379 let NamedNodePattern::NamedNode(p) = &tp.predicate else {
1380 return None;
1381 };
1382 Some(oxrdf::Triple::new(s, p.clone(), term(&tp.object)?))
1383}
1384
1385fn has_quad_access(p: &GraphPattern) -> bool {
1386 match p {
1387 GraphPattern::Bgp { patterns } => !patterns.is_empty(),
1388 GraphPattern::Path { .. } => true,
1389 GraphPattern::Graph { .. } | GraphPattern::Values { .. } => false,
1390 GraphPattern::Join { left, right }
1391 | GraphPattern::LeftJoin { left, right, .. }
1392 | GraphPattern::Union { left, right }
1393 | GraphPattern::Minus { left, right } => has_quad_access(left) || has_quad_access(right),
1394 GraphPattern::Filter { inner, .. }
1395 | GraphPattern::Extend { inner, .. }
1396 | GraphPattern::OrderBy { inner, .. }
1397 | GraphPattern::Project { inner, .. }
1398 | GraphPattern::Distinct { inner }
1399 | GraphPattern::Reduced { inner }
1400 | GraphPattern::Slice { inner, .. }
1401 | GraphPattern::Group { inner, .. } => has_quad_access(inner),
1402 _ => true,
1403 }
1404}
1405
1406fn expression_variables(e: &Expression, out: &mut Vec<Variable>) {
1408 match e {
1409 Expression::Variable(v) | Expression::Bound(v) if !out.contains(v) => out.push(v.clone()),
1410 Expression::Or(a, b)
1411 | Expression::And(a, b)
1412 | Expression::Equal(a, b)
1413 | Expression::SameTerm(a, b)
1414 | Expression::Greater(a, b)
1415 | Expression::GreaterOrEqual(a, b)
1416 | Expression::Less(a, b)
1417 | Expression::LessOrEqual(a, b)
1418 | Expression::Add(a, b)
1419 | Expression::Subtract(a, b)
1420 | Expression::Multiply(a, b)
1421 | Expression::Divide(a, b) => {
1422 expression_variables(a, out);
1423 expression_variables(b, out);
1424 }
1425 Expression::UnaryPlus(a) | Expression::UnaryMinus(a) | Expression::Not(a) => {
1426 expression_variables(a, out)
1427 }
1428 Expression::In(a, l) => {
1429 expression_variables(a, out);
1430 for x in l {
1431 expression_variables(x, out);
1432 }
1433 }
1434 Expression::If(a, b, c) => {
1435 for x in [a, b, c] {
1436 expression_variables(x, out);
1437 }
1438 }
1439 Expression::Coalesce(l) | Expression::FunctionCall(_, l) => {
1440 for x in l {
1441 expression_variables(x, out);
1442 }
1443 }
1444 _ => {}
1445 }
1446}
1447
1448pub(crate) fn in_list(col: &str, ids: &[i64]) -> String {
1449 if ids.is_empty() {
1450 return "0".into();
1451 }
1452 format!(
1453 "{col} IN ({})",
1454 ids.iter()
1455 .map(ToString::to_string)
1456 .collect::<Vec<_>>()
1457 .join(",")
1458 )
1459}
1460
1461pub(crate) fn ground_to_term(t: &GroundTerm) -> Term {
1462 match t {
1463 GroundTerm::NamedNode(n) => n.clone().into(),
1464 GroundTerm::Literal(l) => l.clone().into(),
1465 GroundTerm::Triple(t) => oxrdf::Triple::new(
1466 t.subject.clone(),
1467 t.predicate.clone(),
1468 ground_to_term(&t.object),
1469 )
1470 .into(),
1471 }
1472}
1473
1474pub(crate) fn order_keys(v: &V) -> Vec<String> {
1478 use expr::{Stat, K_BNODE, K_IRI, K_TRIPLE};
1479 match v.stat {
1480 Stat::Numeric => vec![format!("({}) IS NOT NULL", v.kind), v.num.clone()],
1481 Stat::String => vec![format!("({}) IS NOT NULL", v.kind), v.lex.clone()],
1482 _ => vec![
1483 format!(
1484 "CASE WHEN ({k}) IS NULL THEN 0 WHEN ({k}) = {K_BNODE} THEN 1 WHEN ({k}) = {K_IRI} THEN 2 WHEN ({k}) = {K_TRIPLE} THEN 4 ELSE 3 END",
1485 k = v.kind
1486 ),
1487 format!("({}) IS NULL", v.num),
1488 v.num.clone(),
1489 v.lex.clone(),
1490 v.dt.clone(),
1491 v.lang.clone(),
1492 ]
1493 .into_iter()
1494 .chain(triple_order_keys(v))
1495 .collect(),
1496 }
1497}
1498
1499fn triple_order_keys(v: &V) -> Vec<String> {
1501 use expr::K_TRIPLE;
1502 let (Some(id), true) = (&v.id, v.decodable) else {
1503 return Vec::new();
1504 };
1505 vec![format!(
1506 "(CASE WHEN ({}) = {K_TRIPLE} THEN (SELECT sk FROM triple_terms WHERE id = {id}) END)",
1507 v.kind
1508 )]
1509}