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