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