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