1use super::{Binding, Block, Compiler};
13use crate::encoding::{
14 encode_literal, numeric_rank, numeric_type, Tag, INT_OFFSET, PAYLOAD_BITS, PAYLOAD_MASK,
15};
16use crate::error::{Error, Result};
17use crate::sql::{sql_f64, sql_str};
18use oxrdf::vocab::{rdf, xsd};
19use oxrdf::{Literal, NamedNode, Term, Variable};
20use spargebra::algebra::{Expression, Function};
21use std::collections::BTreeMap;
22
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub(crate) enum Stat {
26 Any,
27 Numeric,
28 String,
29 LangString,
30 Bool,
31 DateTime,
32 Iri,
33}
34
35#[derive(Debug, Clone)]
37pub(crate) struct V {
38 pub id: Option<String>,
40 pub kind: String,
41 pub lex: String,
42 pub dt: String,
43 pub lang: String,
44 pub num: String,
45 pub nt: String,
46 pub ts: String,
47 pub boolv: String,
48 pub stat: Stat,
49 pub computed_num: bool,
51 pub decodable: bool,
53 pub aux: String,
56 pub tz: String,
58}
59
60impl V {
61 pub(crate) fn size(&self) -> usize {
63 self.id.as_ref().map_or(0, String::len)
64 + self.kind.len()
65 + self.lex.len()
66 + self.dt.len()
67 + self.lang.len()
68 + self.num.len()
69 + self.nt.len()
70 + self.ts.len()
71 + self.boolv.len()
72 + self.aux.len()
73 + self.tz.len()
74 }
75}
76
77pub(crate) const K_IRI: i64 = Tag::Iri as i64;
78pub(crate) const K_BNODE: i64 = Tag::BlankNode as i64;
79pub(crate) const K_STRING: i64 = Tag::String as i64;
80pub(crate) const K_LANG: i64 = Tag::LangString as i64;
81pub(crate) const K_TYPED: i64 = Tag::Typed as i64;
82pub(crate) const K_INT: i64 = Tag::Integer as i64;
83pub(crate) const K_BOOL: i64 = Tag::Boolean as i64;
84pub(crate) const K_TRIPLE: i64 = Tag::Triple as i64;
85pub(crate) const K_DIRLANG: i64 = Tag::DirLangString as i64;
86
87pub(crate) const INT_BASE: i64 = Tag::Integer.base() + INT_OFFSET;
89pub(crate) const BOOL_BASE: i64 = Tag::Boolean.base();
90
91fn xsd_str(n: oxrdf::NamedNodeRef<'_>) -> String {
92 sql_str(n.as_str())
93}
94
95impl V {
96 pub(crate) fn null() -> Self {
97 Self {
98 id: Some("NULL".into()),
99 kind: "NULL".into(),
100 lex: "NULL".into(),
101 dt: "NULL".into(),
102 lang: "NULL".into(),
103 num: "NULL".into(),
104 nt: "NULL".into(),
105 ts: "NULL".into(),
106 boolv: "NULL".into(),
107 stat: Stat::Any,
108 computed_num: false,
109 decodable: true,
110 aux: "NULL".into(),
111 tz: "NULL".into(),
112 }
113 }
114
115 pub(crate) fn from_id(x: &str) -> Self {
117 let k = format!("(({x}) >> {PAYLOAD_BITS})");
118 let payload = format!("(({x}) & {PAYLOAD_MASK})");
119 Self {
120 id: Some(x.into()),
121 lex: format!(
122 "CASE {k} WHEN {K_INT} THEN CAST({payload} - {INT_OFFSET} AS TEXT) WHEN {K_BOOL} THEN CASE {payload} WHEN 1 THEN 'true' ELSE 'false' END ELSE (SELECT lex FROM terms WHERE id = {x}) END"
123 ),
124 dt: format!(
125 "CASE {k} WHEN {K_STRING} THEN {} WHEN {K_LANG} THEN {} WHEN {K_DIRLANG} THEN {} WHEN {K_INT} THEN {} WHEN {K_BOOL} THEN {} WHEN {K_TYPED} THEN (SELECT dt FROM terms WHERE id = {x}) END",
126 xsd_str(xsd::STRING),
127 xsd_str(rdf::LANG_STRING),
128 sql_str("http://www.w3.org/1999/02/22-rdf-syntax-ns#dirLangString"),
129 xsd_str(xsd::INTEGER),
130 xsd_str(xsd::BOOLEAN),
131 ),
132 lang: format!(
133 "CASE {k} WHEN {K_LANG} THEN (SELECT lang FROM terms WHERE id = {x}) WHEN {K_DIRLANG} THEN (SELECT lang || '--' || CASE dir WHEN 2 THEN 'rtl' ELSE 'ltr' END FROM terms WHERE id = {x}) END"
134 ),
135 num: format!(
136 "CASE {k} WHEN {K_INT} THEN {payload} - {INT_OFFSET} WHEN {K_TYPED} THEN (SELECT num FROM terms WHERE id = {x} AND nt IS NOT NULL) END"
137 ),
138 nt: format!(
139 "CASE {k} WHEN {K_INT} THEN 1 WHEN {K_TYPED} THEN (SELECT nt FROM terms WHERE id = {x}) END"
140 ),
141 ts: format!("CASE WHEN {k} = {K_TYPED} THEN (SELECT ts FROM terms WHERE id = {x}) END"),
142 boolv: format!(
143 "CASE {k} WHEN {K_BOOL} THEN {payload} WHEN {K_TYPED} THEN (SELECT num FROM terms WHERE id = {x} AND dt = {}) END",
144 xsd_str(xsd::BOOLEAN)
145 ),
146 aux: format!("CASE WHEN {k} = {K_TRIPLE} THEN (SELECT vk FROM triple_terms WHERE id = {x}) END"),
147 tz: format!("CASE WHEN {k} = {K_TYPED} THEN (SELECT dt || '|' || dir FROM terms WHERE id = {x} AND ts IS NOT NULL) END"),
148 kind: k,
149 stat: Stat::Any,
150 computed_num: false,
151 decodable: true,
152 }
153 }
154
155 pub(crate) fn from_joined(x: &str, t: &str) -> Self {
159 let k = format!("(({x}) >> {PAYLOAD_BITS})");
160 let payload = format!("(({x}) & {PAYLOAD_MASK})");
161 let mut v = Self::from_id(x);
162 v.lex = format!(
163 "CASE {k} WHEN {K_INT} THEN CAST({payload} - {INT_OFFSET} AS TEXT) WHEN {K_BOOL} THEN CASE {payload} WHEN 1 THEN 'true' ELSE 'false' END ELSE {t}.lex END"
164 );
165 v.dt = format!(
166 "CASE {k} WHEN {K_STRING} THEN {} WHEN {K_LANG} THEN {} WHEN {K_DIRLANG} THEN {} WHEN {K_INT} THEN {} WHEN {K_BOOL} THEN {} WHEN {K_TYPED} THEN {t}.dt END",
167 xsd_str(xsd::STRING),
168 xsd_str(rdf::LANG_STRING),
169 sql_str("http://www.w3.org/1999/02/22-rdf-syntax-ns#dirLangString"),
170 xsd_str(xsd::INTEGER),
171 xsd_str(xsd::BOOLEAN),
172 );
173 v.lang = format!(
174 "CASE {k} WHEN {K_LANG} THEN {t}.lang WHEN {K_DIRLANG} THEN {t}.lang || '--' || CASE {t}.dir WHEN 2 THEN 'rtl' ELSE 'ltr' END END"
175 );
176 v.num = format!(
177 "CASE {k} WHEN {K_INT} THEN {payload} - {INT_OFFSET} WHEN {K_TYPED} THEN CASE WHEN {t}.nt IS NOT NULL THEN {t}.num END END"
178 );
179 v.nt = format!("CASE {k} WHEN {K_INT} THEN 1 WHEN {K_TYPED} THEN {t}.nt END");
180 v.ts = format!("CASE WHEN {k} = {K_TYPED} THEN {t}.ts END");
181 v.boolv = format!(
182 "CASE {k} WHEN {K_BOOL} THEN {payload} WHEN {K_TYPED} THEN CASE WHEN {t}.dt = {} THEN {t}.num END END",
183 xsd_str(xsd::BOOLEAN)
184 );
185 v.tz = format!(
186 "CASE WHEN {k} = {K_TYPED} AND {t}.ts IS NOT NULL THEN {t}.dt || '|' || {t}.dir END"
187 );
188 v
189 }
190
191 pub(crate) fn from_term(term: &Term, id: i64) -> Result<Self> {
193 let mut v = Self::null();
194 v.id = Some(id.to_string());
195 v.decodable = false;
196 match term {
197 Term::NamedNode(n) => {
198 v.kind = K_IRI.to_string();
199 v.lex = sql_str(n.as_str());
200 v.stat = Stat::Iri;
201 }
202 Term::BlankNode(b) => {
203 v.kind = K_BNODE.to_string();
204 v.lex = sql_str(b.as_str());
205 }
206 Term::Literal(l) => {
207 let (lid, row) = encode_literal(l.as_ref());
208 debug_assert_eq!(lid, id);
209 let tag = crate::encoding::tag_of(id).unwrap_or(Tag::Typed);
210 v.kind = (tag as i64).to_string();
211 v.lex = sql_str(l.value());
212 v.dt = sql_str(l.datatype().as_str());
213 v.lang = match (l.language(), l.direction()) {
214 (Some(lang), Some(oxrdf::BaseDirection::Ltr)) => {
215 sql_str(&format!("{lang}--ltr"))
216 }
217 (Some(lang), Some(oxrdf::BaseDirection::Rtl)) => {
218 sql_str(&format!("{lang}--rtl"))
219 }
220 (Some(lang), None) => sql_str(lang),
221 (None, _) => "NULL".into(),
222 };
223 match tag {
224 Tag::Integer => {
225 v.num = l.value().to_string();
226 v.nt = "1".into();
227 v.stat = Stat::Numeric;
228 }
229 Tag::Boolean => {
230 v.boolv = if l.value() == "true" { "1" } else { "0" }.into();
231 v.stat = Stat::Bool;
232 }
233 Tag::String => v.stat = Stat::String,
234 Tag::LangString | Tag::DirLangString => v.stat = Stat::LangString,
235 _ => {
236 if let Some(row) = row {
237 if let (Some(num), Some(nt)) = (row.num, row.nt) {
238 v.num = sql_f64(num);
239 v.nt = nt.to_string();
240 v.stat = Stat::Numeric;
241 } else if l.datatype() == xsd::BOOLEAN {
242 v.boolv = row
243 .num
244 .map_or_else(|| "NULL".into(), |b| (b as i64).to_string());
245 v.stat = Stat::Bool;
246 }
247 if let Some(ts) = row.ts {
248 v.ts = sql_f64(ts);
249 v.tz = sql_str(&format!(
250 "{}|{}",
251 l.datatype().as_str(),
252 row.dir.unwrap_or(0)
253 ));
254 v.stat = Stat::DateTime;
255 }
256 }
257 }
258 }
259 }
260 Term::Triple(_) => {
261 v.kind = K_TRIPLE.to_string();
262 v.aux = sql_str(&crate::encoding::value_key(term.as_ref()));
263 }
264 }
265 Ok(v)
266 }
267
268 fn string(lex: String, like: Option<&V>) -> Self {
270 let mut v = Self::null();
271 v.id = None;
272 match like {
273 Some(l) if l.stat != Stat::String => {
274 v.kind = format!(
275 "CASE WHEN ({lex}) IS NOT NULL THEN CASE WHEN {} IN ({K_LANG}, {K_DIRLANG}) THEN {K_LANG} ELSE {K_STRING} END END",
276 l.kind
277 );
278 v.lang = format!(
279 "CASE WHEN {} IN ({K_LANG}, {K_DIRLANG}) THEN {} END",
280 l.kind, l.lang
281 );
282 v.dt = format!(
283 "CASE WHEN {} IN ({K_LANG}, {K_DIRLANG}) THEN {} ELSE {} END",
284 l.kind,
285 xsd_str(rdf::LANG_STRING),
286 xsd_str(xsd::STRING)
287 );
288 v.stat = Stat::Any;
289 }
290 _ => {
291 v.kind = format!("CASE WHEN ({lex}) IS NOT NULL THEN {K_STRING} END");
292 v.dt = xsd_str(xsd::STRING);
293 v.stat = Stat::String;
294 }
295 }
296 v.lex = lex;
297 v
298 }
299
300 pub(crate) fn numeric(num: String, nt: String) -> Self {
302 let mut v = Self::null();
303 v.id = None;
304 v.kind = format!(
305 "CASE WHEN ({num}) IS NULL THEN NULL WHEN ({nt}) = 1 THEN {K_INT} WHEN ({nt}) IS NOT NULL THEN {K_TYPED} END"
306 );
307 v.dt = format!(
308 "CASE ({nt}) WHEN 1 THEN {} WHEN 2 THEN {} WHEN 3 THEN {} WHEN 4 THEN {} END",
309 xsd_str(xsd::INTEGER),
310 xsd_str(xsd::DECIMAL),
311 xsd_str(xsd::FLOAT),
312 xsd_str(xsd::DOUBLE)
313 );
314 v.lex = format!(
315 "CASE WHEN ({nt}) = 1 THEN CAST(CAST({num} AS INTEGER) AS TEXT) ELSE CAST({num} AS TEXT) END"
316 );
317 v.num = num;
318 v.nt = nt;
319 v.stat = Stat::Numeric;
320 v.computed_num = true;
321 v
322 }
323
324 pub(crate) fn integer(num: String) -> Self {
326 let mut v = Self::numeric(num.clone(), "1".into());
327 v.id = Some(format!("({INT_BASE} + CAST({num} AS INTEGER))"));
328 v.kind = format!("CASE WHEN ({num}) IS NOT NULL THEN {K_INT} END");
329 v.dt = xsd_str(xsd::INTEGER);
330 v.lex = format!("CAST({num} AS TEXT)");
331 v.computed_num = false;
332 v.decodable = true;
333 v
334 }
335
336 pub(crate) fn boolean(b: &str) -> Self {
338 let mut v = Self::null();
339 v.id = Some(format!("({BOOL_BASE} + CAST({b} AS INTEGER))"));
340 v.kind = format!("CASE WHEN ({b}) IS NOT NULL THEN {K_BOOL} END");
341 v.lex = format!("CASE ({b}) WHEN 1 THEN 'true' WHEN 0 THEN 'false' END");
342 v.dt = xsd_str(xsd::BOOLEAN);
343 v.boolv = format!("({b})");
344 v.stat = Stat::Bool;
345 v.decodable = true;
346 v
347 }
348
349 fn iri(lex: String) -> Self {
351 let mut v = Self::null();
352 v.id = None;
353 v.kind = format!("CASE WHEN ({lex}) IS NOT NULL THEN {K_IRI} END");
354 v.lex = lex;
355 v.stat = Stat::Iri;
356 v
357 }
358
359 fn typed(lex: String, dt: &str) -> Self {
361 let mut v = Self::null();
362 v.id = None;
363 v.kind = format!("CASE WHEN ({lex}) IS NOT NULL THEN {K_TYPED} END");
364 v.dt = sql_str(dt);
365 v.lex = lex;
366 v
367 }
368
369 fn is_string_like(&self) -> String {
370 match self.stat {
371 Stat::String | Stat::LangString => "1".into(),
372 _ => format!("{} IN ({K_STRING}, {K_LANG}, {K_DIRLANG})", self.kind),
373 }
374 }
375
376 pub(crate) fn ebv(&self) -> String {
378 match self.stat {
379 Stat::Bool => format!("({})", self.boolv),
380 Stat::Numeric => format!("(({}) <> 0)", self.num),
381 Stat::String => format!("(({}) <> '')", self.lex),
382 Stat::Iri | Stat::LangString | Stat::DateTime => "NULL".into(),
383 Stat::Any => format!(
384 "(CASE WHEN ({b}) IS NOT NULL THEN ({b}) WHEN ({n}) IS NOT NULL THEN (({n}) <> 0) WHEN {k} = {K_STRING} THEN (({l}) <> '') END)",
385 b = self.boolv,
386 n = self.num,
387 k = self.kind,
388 l = self.lex
389 ),
390 }
391 }
392}
393
394pub(crate) enum E {
396 B(String),
397 T(V),
398}
399
400impl E {
401 pub(crate) fn bool_sql(self) -> String {
402 match self {
403 Self::B(b) => b,
404 Self::T(v) => v.ebv(),
405 }
406 }
407
408 pub(crate) fn term(self) -> V {
409 match self {
410 Self::B(b) => V::boolean(&b),
411 Self::T(v) => v,
412 }
413 }
414}
415
416#[derive(Clone, Copy, PartialEq, Eq)]
417enum Cmp {
418 Eq,
419 Lt,
420 Le,
421 Gt,
422 Ge,
423}
424
425impl Cmp {
426 fn op(self) -> &'static str {
427 match self {
428 Self::Eq => "=",
429 Self::Lt => "<",
430 Self::Le => "<=",
431 Self::Gt => ">",
432 Self::Ge => ">=",
433 }
434 }
435}
436
437fn tsk(v: &V) -> String {
439 if v.tz != "NULL" {
440 return format!("({})", v.tz);
441 }
442 format!(
443 "(({dt}) || CASE WHEN substr({l}, -1) = 'Z' OR (substr({l}, -6, 1) IN ('+', '-') AND substr({l}, -3, 1) = ':') THEN 'Z' ELSE '' END)",
444 dt = v.dt,
445 l = v.lex
446 )
447}
448
449fn may(s: Stat, want: Stat) -> bool {
450 s == Stat::Any || s == want
451}
452
453pub(crate) fn same_term(a: &V, b: &V) -> String {
454 match (&a.id, &b.id) {
455 (Some(x), Some(y)) => format!("(({x}) = ({y}))"),
456 _ => format!(
457 "(({ak}) = ({bk}) AND ({al}) = ({bl}) AND ({ad}) IS ({bd}) AND ({ag}) IS ({bg}))",
458 ak = a.kind,
459 bk = b.kind,
460 al = a.lex,
461 bl = b.lex,
462 ad = a.dt,
463 bd = b.dt,
464 ag = a.lang,
465 bg = b.lang
466 ),
467 }
468}
469
470fn compare(a: &V, b: &V, cmp: Cmp) -> String {
471 compare_inner(a, b, cmp, true)
472}
473
474fn compare_inner(a: &V, b: &V, cmp: Cmp, triples: bool) -> String {
475 let op = cmp.op();
476 let mut branches = Vec::new();
477 if cmp == Cmp::Eq {
478 if let (Some(x), Some(y)) = (&a.id, &b.id) {
479 branches.push(format!("WHEN ({x}) = ({y}) THEN 1"));
480 }
481 }
482 if may(a.stat, Stat::Numeric) && may(b.stat, Stat::Numeric) {
483 if a.stat == Stat::Numeric && b.stat == Stat::Numeric {
484 return format!("(({}) {op} ({}))", a.num, b.num);
485 }
486 branches.push(format!(
487 "WHEN ({an}) IS NOT NULL AND ({bn}) IS NOT NULL THEN ({an}) {op} ({bn})",
488 an = a.num,
489 bn = b.num
490 ));
491 }
492 if may(a.stat, Stat::String) && may(b.stat, Stat::String) {
493 if a.stat == Stat::String && b.stat == Stat::String {
494 return format!("(({}) {op} ({}))", a.lex, b.lex);
495 }
496 branches.push(format!(
497 "WHEN ({ak}) = {K_STRING} AND ({bk}) = {K_STRING} THEN ({al}) {op} ({bl})",
498 ak = a.kind,
499 bk = b.kind,
500 al = a.lex,
501 bl = b.lex
502 ));
503 }
504 if may(a.stat, Stat::DateTime) && may(b.stat, Stat::DateTime) {
505 let (at, bt) = (&a.ts, &b.ts);
508 let mixed = match cmp {
509 Cmp::Lt | Cmp::Le => format!(
510 "CASE WHEN ({at}) + 50400 < ({bt}) THEN 1 WHEN ({at}) - 50400 > ({bt}) THEN 0 END"
511 ),
512 Cmp::Gt | Cmp::Ge => format!(
513 "CASE WHEN ({at}) - 50400 > ({bt}) THEN 1 WHEN ({at}) + 50400 < ({bt}) THEN 0 END"
514 ),
515 Cmp::Eq => format!("CASE WHEN abs(({at}) - ({bt})) > 50400 THEN 0 END"),
516 };
517 branches.push(format!(
518 "WHEN ({at}) IS NOT NULL AND ({bt}) IS NOT NULL AND ({ad}) = ({bd}) THEN CASE WHEN {ak} = {bk} THEN ({at}) {op} ({bt}) ELSE {mixed} END",
519 ad = a.dt,
520 bd = b.dt,
521 ak = tsk(a),
522 bk = tsk(b)
523 ));
524 }
525 if may(a.stat, Stat::Bool) && may(b.stat, Stat::Bool) {
526 branches.push(format!(
527 "WHEN ({ab}) IS NOT NULL AND ({bb}) IS NOT NULL THEN ({ab}) {op} ({bb})",
528 ab = a.boolv,
529 bb = b.boolv
530 ));
531 }
532 if cmp != Cmp::Eq && may(a.stat, Stat::LangString) && may(b.stat, Stat::LangString) {
533 branches.push(format!(
535 "WHEN ({ak}) = {K_LANG} AND ({bk}) = {K_LANG} AND ({ag}) = ({bg}) THEN ({al}) {op} ({bl})",
536 ak = a.kind,
537 bk = b.kind,
538 ag = a.lang,
539 bg = b.lang,
540 al = a.lex,
541 bl = b.lex
542 ));
543 }
544 if cmp == Cmp::Eq {
545 if a.id.is_none() || b.id.is_none() {
546 branches.push(format!("WHEN {} THEN 1", same_term(a, b)));
547 }
548 if may(a.stat, Stat::LangString) && may(b.stat, Stat::LangString) {
549 branches.push(format!(
550 "WHEN ({ak}) = {K_LANG} AND ({bk}) = {K_LANG} THEN (({al}) = ({bl}) AND ({ag}) = ({bg}))",
551 ak = a.kind,
552 bk = b.kind,
553 al = a.lex,
554 bl = b.lex,
555 ag = a.lang,
556 bg = b.lang
557 ));
558 }
559 if triples && a.aux != "NULL" && b.aux != "NULL" {
560 branches.push(format!(
562 "WHEN ({}) = {K_TRIPLE} AND ({}) = {K_TRIPLE} THEN ({}) = ({})",
563 a.kind, b.kind, a.aux, b.aux
564 ));
565 }
566 branches.push(format!(
568 "WHEN ({}) IN ({K_IRI}, {K_BNODE}, {K_TRIPLE}) OR ({}) IN ({K_IRI}, {K_BNODE}, {K_TRIPLE}) THEN 0",
569 a.kind, b.kind
570 ));
571 }
572 if branches.is_empty() {
573 return "NULL".into();
574 }
575 format!("(CASE {} END)", branches.join(" "))
576}
577
578fn arith(a: &V, b: &V, op: char) -> V {
579 let nt = if op == '/' {
580 format!("max(2, max({}, {}))", a.nt, b.nt)
581 } else {
582 format!("max({}, {})", a.nt, b.nt)
583 };
584 let num = match op {
585 '/' => format!(
586 "(CASE WHEN ({bn}) = 0 AND max({ant}, {bnt}) < 3 THEN NULL ELSE CAST({an} AS REAL) / ({bn}) END)",
587 an = a.num,
588 bn = b.num,
589 ant = a.nt,
590 bnt = b.nt
591 ),
592 _ => format!("(({}) {op} ({}))", a.num, b.num),
593 };
594 V::numeric(num, nt)
595}
596
597fn simple_regex(pattern: &str) -> Option<(bool, String, bool)> {
599 let mut chars = pattern.chars().peekable();
600 let starts = chars.peek() == Some(&'^');
601 if starts {
602 chars.next();
603 }
604 let mut lit = String::new();
605 let mut ends = false;
606 while let Some(c) = chars.next() {
607 match c {
608 '\\' => match chars.next() {
609 Some(e) if ".*+?()[]{}|\\^$/-".contains(e) => lit.push(e),
610 _ => return None,
611 },
612 '$' if chars.peek().is_none() => ends = true,
613 '.' | '*' | '+' | '?' | '(' | ')' | '[' | ']' | '{' | '}' | '|' | '^' | '$' => {
614 return None
615 }
616 c => lit.push(c),
617 }
618 }
619 Some((starts, lit, ends))
620}
621
622impl Compiler<'_> {
623 pub(crate) fn var_value(&mut self, v: &Variable, cols: &BTreeMap<usize, Binding>) -> V {
625 let idx = self.var(v);
626 if let Some(b) = cols.get(&idx) {
627 let mut value = b.col.value();
628 if value.stat == Stat::Any {
629 if let Some(t) = self.options.var_types.get(v.as_str()) {
630 value.stat = match t {
631 super::ValueType::Numeric => Stat::Numeric,
632 super::ValueType::String => Stat::String,
633 super::ValueType::Boolean => Stat::Bool,
634 };
635 }
636 }
637 return value;
638 }
639 for scope in self.outer.iter().rev() {
640 if let Some(b) = scope.get(&idx) {
641 return b.col.value();
642 }
643 }
644 V::null()
645 }
646
647 fn constant(&mut self, t: Term) -> Result<V> {
648 let id = self.constant_id(&t)?;
649 V::from_term(&t, id)
650 }
651
652 pub(crate) fn expr_term(
653 &mut self,
654 e: &Expression,
655 cols: &BTreeMap<usize, Binding>,
656 ) -> Result<V> {
657 Ok(self.expr(e, cols)?.term())
658 }
659
660 pub(crate) fn expr_bool(
661 &mut self,
662 e: &Expression,
663 cols: &BTreeMap<usize, Binding>,
664 ) -> Result<String> {
665 Ok(self.expr(e, cols)?.bool_sql())
666 }
667
668 pub(crate) fn expr(&mut self, e: &Expression, cols: &BTreeMap<usize, Binding>) -> Result<E> {
669 Ok(match e {
670 Expression::NamedNode(n) => E::T(self.constant(n.clone().into())?),
671 Expression::Literal(l) => E::T(self.constant(l.clone().into())?),
672 Expression::Variable(v) => E::T(self.var_value(v, cols)),
673 Expression::Or(a, b) => E::B(format!(
674 "({} OR {})",
675 self.expr_bool(a, cols)?,
676 self.expr_bool(b, cols)?
677 )),
678 Expression::And(a, b) => E::B(format!(
679 "({} AND {})",
680 self.expr_bool(a, cols)?,
681 self.expr_bool(b, cols)?
682 )),
683 Expression::Not(a) => E::B(format!("(NOT {})", self.expr_bool(a, cols)?)),
684 Expression::Equal(a, b) => {
685 let (a, b) = (self.expr_term(a, cols)?, self.expr_term(b, cols)?);
686 E::B(compare(&a, &b, Cmp::Eq))
687 }
688 Expression::SameTerm(a, b) => {
689 let (a, b) = (self.expr_term(a, cols)?, self.expr_term(b, cols)?);
690 E::B(same_term(&a, &b))
691 }
692 Expression::Greater(a, b) => self.cmp(a, b, Cmp::Gt, cols)?,
693 Expression::GreaterOrEqual(a, b) => self.cmp(a, b, Cmp::Ge, cols)?,
694 Expression::Less(a, b) => self.cmp(a, b, Cmp::Lt, cols)?,
695 Expression::LessOrEqual(a, b) => self.cmp(a, b, Cmp::Le, cols)?,
696 Expression::In(a, list) => {
697 let a = self.expr_term(a, cols)?;
698 if list.is_empty() {
699 return Ok(E::B(format!(
701 "(CASE WHEN ({}) IS NOT NULL THEN 0 END)",
702 a.kind
703 )));
704 }
705 let mut parts = Vec::new();
706 for b in list {
707 let b = self.expr_term(b, cols)?;
708 parts.push(compare(&a, &b, Cmp::Eq));
709 }
710 E::B(format!("({})", parts.join(" OR ")))
711 }
712 Expression::Add(a, b) => self.arith(a, b, '+', cols)?,
713 Expression::Subtract(a, b) => self.arith(a, b, '-', cols)?,
714 Expression::Multiply(a, b) => self.arith(a, b, '*', cols)?,
715 Expression::Divide(a, b) => self.arith(a, b, '/', cols)?,
716 Expression::UnaryPlus(a) => {
717 let a = self.expr_term(a, cols)?;
718 E::T(V::numeric(a.num.clone(), a.nt.clone()))
719 }
720 Expression::UnaryMinus(a) => {
721 let a = self.expr_term(a, cols)?;
722 E::T(V::numeric(format!("(-({}))", a.num), a.nt.clone()))
723 }
724 Expression::Bound(v) => {
725 let v = self.var_value(v, cols);
726 E::B(format!("(({}) IS NOT NULL)", v.kind))
727 }
728 Expression::If(c, a, b) => {
729 let c = self.expr_bool(c, cols)?;
730 let a = self.expr_term(a, cols)?;
731 let b = self.expr_term(b, cols)?;
732 E::T(Self::choose(&c, &a, &b))
733 }
734 Expression::Coalesce(list) => {
735 let mut vals = Vec::new();
736 for e in list {
737 vals.push(self.expr_term(e, cols)?);
738 }
739 let Some(mut acc) = vals.pop() else {
740 return Ok(E::T(V::null()));
741 };
742 while let Some(v) = vals.pop() {
743 let c = format!("(({}) IS NOT NULL)", v.kind);
744 acc = Self::choose(&c, &v, &acc);
745 }
746 E::T(acc)
747 }
748 Expression::Exists(p) => E::B(self.exists(p, cols)?),
749 Expression::FunctionCall(f, args) => self.function(f, args, cols)?,
750 })
751 }
752
753 pub(crate) fn choose(c: &str, a: &V, b: &V) -> V {
754 let pick = |x: &str, y: &str| {
755 format!("(CASE WHEN ({c}) IS NULL THEN NULL WHEN ({c}) THEN {x} ELSE {y} END)")
756 };
757 V {
758 id: match (&a.id, &b.id) {
759 (Some(x), Some(y)) => Some(pick(x, y)),
760 _ => None,
761 },
762 kind: pick(&a.kind, &b.kind),
763 lex: pick(&a.lex, &b.lex),
764 dt: pick(&a.dt, &b.dt),
765 lang: pick(&a.lang, &b.lang),
766 num: pick(&a.num, &b.num),
767 nt: pick(&a.nt, &b.nt),
768 ts: pick(&a.ts, &b.ts),
769 boolv: pick(&a.boolv, &b.boolv),
770 stat: if a.stat == b.stat { a.stat } else { Stat::Any },
771 computed_num: a.computed_num || b.computed_num,
772 decodable: a.decodable && b.decodable,
773 aux: pick(&a.aux, &b.aux),
774 tz: pick(&a.tz, &b.tz),
775 }
776 }
777
778 fn cmp(
779 &mut self,
780 a: &Expression,
781 b: &Expression,
782 c: Cmp,
783 cols: &BTreeMap<usize, Binding>,
784 ) -> Result<E> {
785 let (a, b) = (self.expr_term(a, cols)?, self.expr_term(b, cols)?);
786 Ok(E::B(compare(&a, &b, c)))
787 }
788
789 fn arith(
790 &mut self,
791 a: &Expression,
792 b: &Expression,
793 op: char,
794 cols: &BTreeMap<usize, Binding>,
795 ) -> Result<E> {
796 let (a, b) = (self.expr_term(a, cols)?, self.expr_term(b, cols)?);
797 Ok(E::T(arith(&a, &b, op)))
798 }
799
800 fn exists(
801 &mut self,
802 p: &spargebra::algebra::GraphPattern,
803 cols: &BTreeMap<usize, Binding>,
804 ) -> Result<String> {
805 self.outer.push(cols.clone());
806 let inner = self.pattern(p);
807 self.outer.pop();
808 let mut inner = inner?;
809 if !inner.is_plain() || inner.from.is_empty() {
810 inner = self.seal(inner);
811 }
812 let mut conds = inner.wheres.clone();
814 for (idx, b) in &inner.cols {
815 if let Some(outer) = cols.get(idx) {
816 if b.correlated {
817 continue;
818 }
819 conds.push(Self::unify(b, outer).0);
820 }
821 }
822 Ok(format!(
823 "EXISTS (SELECT 1 FROM {}{})",
824 Block::render_from(&inner.from),
825 if conds.is_empty() {
826 String::new()
827 } else {
828 format!(" WHERE {}", conds.join(" AND "))
829 }
830 ))
831 }
832
833 fn args(&mut self, args: &[Expression], cols: &BTreeMap<usize, Binding>) -> Result<Vec<V>> {
834 args.iter().map(|a| self.expr_term(a, cols)).collect()
835 }
836
837 fn function(
838 &mut self,
839 f: &Function,
840 args: &[Expression],
841 cols: &BTreeMap<usize, Binding>,
842 ) -> Result<E> {
843 let udf = self.caps.udf;
844 Ok(match f {
845 Function::Str => {
846 let a = self.expr_term(&args[0], cols)?;
847 let lex = format!(
848 "CASE WHEN ({}) IN ({K_IRI}, {K_STRING}, {K_LANG}, {K_TYPED}, {K_INT}, {K_BOOL}, {K_DIRLANG}) THEN {} END",
849 a.kind, a.lex
850 );
851 if a.computed_num {
852 return Err(Error::unsupported("STR() of a computed number"));
854 }
855 E::T(V::string(lex, None))
856 }
857 Function::Lang => {
858 let a = self.expr_term(&args[0], cols)?;
859 E::T(V::string(
860 format!(
861 "CASE WHEN ({k}) = {K_LANG} THEN ({l}) WHEN ({k}) = {K_DIRLANG} THEN substr({l}, 1, instr({l}, '--') - 1) WHEN ({k}) IN ({K_STRING}, {K_TYPED}, {K_INT}, {K_BOOL}) THEN '' END",
862 k = a.kind,
863 l = a.lang
864 ),
865 None,
866 ))
867 }
868 Function::Datatype => {
869 let a = self.expr_term(&args[0], cols)?;
870 E::T(V::iri(format!(
872 "CASE WHEN ({}) IS NOT NULL THEN {} END",
873 a.kind, a.dt
874 )))
875 }
876 Function::IsIri => {
877 let a = self.expr_term(&args[0], cols)?;
878 E::B(format!("(({}) = {K_IRI})", a.kind))
879 }
880 Function::IsBlank => {
881 let a = self.expr_term(&args[0], cols)?;
882 E::B(format!("(({}) = {K_BNODE})", a.kind))
883 }
884 Function::IsLiteral => {
885 let a = self.expr_term(&args[0], cols)?;
886 E::B(format!(
887 "(({}) IN ({K_STRING}, {K_LANG}, {K_TYPED}, {K_INT}, {K_BOOL}, {K_DIRLANG}))",
888 a.kind
889 ))
890 }
891 Function::IsNumeric => {
892 let a = self.expr_term(&args[0], cols)?;
893 E::B(format!(
894 "(CASE WHEN ({}) IS NULL THEN NULL ELSE ({}) IS NOT NULL END)",
895 a.kind, a.nt
896 ))
897 }
898 Function::IsTriple => {
899 let a = self.expr_term(&args[0], cols)?;
900 E::B(format!("(({}) = {K_TRIPLE})", a.kind))
901 }
902 Function::StrLen => {
903 let a = self.expr_term(&args[0], cols)?;
904 E::T(V::integer(format!(
905 "CASE WHEN {} THEN length({}) END",
906 a.is_string_like(),
907 a.lex
908 )))
909 }
910 Function::UCase | Function::LCase => {
911 let a = self.expr_term(&args[0], cols)?;
912 let func = if matches!(f, Function::UCase) {
913 "upper"
914 } else {
915 "lower"
916 };
917 E::T(V::string(
918 format!(
919 "CASE WHEN {} THEN {func}({}) END",
920 a.is_string_like(),
921 a.lex
922 ),
923 Some(&a),
924 ))
925 }
926 Function::SubStr => {
927 let a = self.args(args, cols)?;
928 let start = format!("CAST(round({}) AS INTEGER)", a[1].num);
929 let lex = if let Some(len) = a.get(2) {
930 format!(
931 "CASE WHEN {} THEN substr({}, {start}, CAST(round({}) AS INTEGER)) END",
932 a[0].is_string_like(),
933 a[0].lex,
934 len.num
935 )
936 } else {
937 format!(
938 "CASE WHEN {} THEN substr({}, {start}) END",
939 a[0].is_string_like(),
940 a[0].lex
941 )
942 };
943 E::T(V::string(lex, Some(&a[0])))
944 }
945 Function::Concat => {
946 let a = self.args(args, cols)?;
947 if a.is_empty() {
948 E::T(V::string("''".into(), None))
949 } else {
950 let parts: Vec<String> = a
951 .iter()
952 .map(|v| format!("(CASE WHEN {} THEN {} END)", v.is_string_like(), v.lex))
953 .collect();
954 let lex = format!("({})", parts.join(" || "));
955 let same_lang = a
957 .iter()
958 .map(|v| {
959 format!(
960 "({k}) IN ({K_LANG}, {K_DIRLANG}) AND ({l}) = ({l0})",
961 k = v.kind,
962 l = v.lang,
963 l0 = a[0].lang
964 )
965 })
966 .collect::<Vec<_>>()
967 .join(" AND ");
968 let mut v = V::string(lex.clone(), None);
969 v.kind = format!(
970 "CASE WHEN ({lex}) IS NULL THEN NULL WHEN {same_lang} THEN ({k0}) ELSE {K_STRING} END",
971 k0 = a[0].kind
972 );
973 v.lang = format!("CASE WHEN {same_lang} THEN ({}) END", a[0].lang);
974 v.dt = format!(
975 "CASE WHEN {same_lang} THEN ({}) ELSE {} END",
976 a[0].dt,
977 xsd_str(xsd::STRING)
978 );
979 v.stat = Stat::Any;
980 E::T(v)
981 }
982 }
983 Function::Contains | Function::StrStarts | Function::StrEnds => {
984 let a = self.args(args, cols)?;
985 let (x, y) = (&a[0].lex, &a[1].lex);
986 let test = match f {
987 Function::Contains => format!("instr({x}, {y}) > 0"),
988 Function::StrStarts => format!("substr({x}, 1, length({y})) = {y}"),
989 _ => format!("(length({y}) = 0 OR substr({x}, -length({y})) = {y})"),
990 };
991 E::B(format!(
992 "(CASE WHEN {} AND {} THEN {test} END)",
993 a[0].is_string_like(),
994 a[1].is_string_like()
995 ))
996 }
997 Function::StrBefore | Function::StrAfter => {
998 let a = self.args(args, cols)?;
999 let (x, y) = (&a[0].lex, &a[1].lex);
1000 let compatible = format!(
1002 "({k0}) IN ({K_STRING}, {K_LANG}, {K_DIRLANG}) AND (({k1}) = {K_STRING} OR (({k1}) IN ({K_LANG}, {K_DIRLANG}) AND ({l1}) = ({l0})))",
1003 k0 = a[0].kind,
1004 k1 = a[1].kind,
1005 l0 = a[0].lang,
1006 l1 = a[1].lang
1007 );
1008 let found = format!("(length({y}) = 0 OR instr({x}, {y}) > 0)");
1009 let lex = if matches!(f, Function::StrBefore) {
1010 format!("CASE WHEN length({y}) = 0 THEN '' WHEN instr({x}, {y}) > 0 THEN substr({x}, 1, instr({x}, {y}) - 1) ELSE '' END")
1011 } else {
1012 format!("CASE WHEN length({y}) = 0 THEN {x} WHEN instr({x}, {y}) > 0 THEN substr({x}, instr({x}, {y}) + length({y})) ELSE '' END")
1013 };
1014 let lex = format!("CASE WHEN {compatible} THEN {lex} END");
1015 let mut v = V::string(lex.clone(), None);
1016 v.kind = format!(
1018 "CASE WHEN ({lex}) IS NULL THEN NULL WHEN {found} THEN ({k0}) ELSE {K_STRING} END",
1019 k0 = a[0].kind
1020 );
1021 v.lang = format!("CASE WHEN {found} THEN ({}) END", a[0].lang);
1022 v.dt = format!(
1023 "CASE WHEN {found} THEN ({}) ELSE {} END",
1024 a[0].dt,
1025 xsd_str(xsd::STRING)
1026 );
1027 v.stat = Stat::Any;
1028 E::T(v)
1029 }
1030 Function::LangMatches => {
1031 let a = self.args(args, cols)?;
1032 let (tag, range) = (&a[0].lex, &a[1].lex);
1033 E::B(format!(
1034 "(CASE WHEN ({range}) = '*' THEN ({tag}) <> '' ELSE lower({tag}) = lower({range}) OR lower({tag}) LIKE lower({range}) || '-%' END)"
1035 ))
1036 }
1037 Function::Regex => self.regex(args, cols)?,
1038 Function::Replace if udf => {
1039 let a = self.args(args, cols)?;
1040 let flags = a.get(3).map_or_else(|| "''".into(), |f| f.lex.clone());
1041 E::T(V::string(
1042 format!(
1043 "CASE WHEN {} THEN oxilite_replace({}, {}, {}, {flags}) END",
1044 a[0].is_string_like(),
1045 a[0].lex,
1046 a[1].lex,
1047 a[2].lex
1048 ),
1049 Some(&a[0]),
1050 ))
1051 }
1052 Function::EncodeForUri if udf => {
1053 let a = self.expr_term(&args[0], cols)?;
1054 E::T(V::string(
1055 format!("oxilite_encode_for_uri({})", a.lex),
1056 None,
1057 ))
1058 }
1059 Function::Md5
1060 | Function::Sha1
1061 | Function::Sha256
1062 | Function::Sha384
1063 | Function::Sha512
1064 if udf =>
1065 {
1066 let a = self.expr_term(&args[0], cols)?;
1067 let algo = match f {
1068 Function::Md5 => "md5",
1069 Function::Sha1 => "sha1",
1070 Function::Sha256 => "sha256",
1071 Function::Sha384 => "sha384",
1072 _ => "sha512",
1073 };
1074 E::T(V::string(
1075 format!(
1076 "CASE WHEN ({}) = {K_STRING} THEN oxilite_hash('{algo}', {}) END",
1077 a.kind, a.lex
1078 ),
1079 None,
1080 ))
1081 }
1082 Function::Abs | Function::Ceil | Function::Floor | Function::Round => {
1083 let a = self.expr_term(&args[0], cols)?;
1084 let n = &a.num;
1085 let floor = format!("(CAST({n} AS INTEGER) - (({n}) < CAST({n} AS INTEGER)))");
1086 let num = match f {
1087 Function::Abs => format!("abs({n})"),
1088 Function::Floor => floor,
1089 Function::Ceil => format!(
1090 "(-(CAST(-({n}) AS INTEGER) - ((-({n})) < CAST(-({n}) AS INTEGER))))"
1091 ),
1092 _ => {
1093 let m = format!("(({n}) + 0.5)");
1094 format!("(CAST({m} AS INTEGER) - (({m}) < CAST({m} AS INTEGER)))")
1095 }
1096 };
1097 E::T(V::numeric(num, a.nt.clone()))
1098 }
1099 Function::Year
1100 | Function::Month
1101 | Function::Day
1102 | Function::Hours
1103 | Function::Minutes => {
1104 let a = self.expr_term(&args[0], cols)?;
1105 let (start, len) = match f {
1106 Function::Year => (1, 4),
1107 Function::Month => (6, 2),
1108 Function::Day => (9, 2),
1109 Function::Hours => (12, 2),
1110 _ => (15, 2),
1111 };
1112 let neg = if matches!(f, Function::Year) {
1113 format!(
1115 "CASE WHEN substr({l}, 1, 1) = '-' THEN -CAST(substr({l}, 2, 4) AS INTEGER) ELSE CAST(substr({l}, 1, 4) AS INTEGER) END",
1116 l = a.lex
1117 )
1118 } else {
1119 format!("CAST(substr({}, {start}, {len}) AS INTEGER)", a.lex)
1120 };
1121 E::T(V::integer(format!(
1122 "CASE WHEN ({}) IS NOT NULL THEN {neg} END",
1123 a.ts
1124 )))
1125 }
1126 Function::Seconds => {
1127 let a = self.expr_term(&args[0], cols)?;
1128 E::T(V::numeric(
1130 format!(
1131 "CASE WHEN ({}) IS NOT NULL AND ({}) = {} THEN CAST(substr({l}, 18) AS REAL) END",
1132 a.ts,
1133 a.dt,
1134 xsd_str(xsd::DATE_TIME),
1135 l = a.lex
1136 ),
1137 "2".into(),
1138 ))
1139 }
1140 Function::Tz => {
1141 let a = self.expr_term(&args[0], cols)?;
1142 let l = &a.lex;
1143 E::T(V::string(
1144 format!(
1145 "CASE WHEN ({}) IS NULL THEN NULL WHEN substr({l}, -1) = 'Z' THEN 'Z' WHEN substr({l}, -6, 1) IN ('+', '-') AND substr({l}, -3, 1) = ':' THEN substr({l}, -6) ELSE '' END",
1146 a.ts
1147 ),
1148 None,
1149 ))
1150 }
1151 Function::Now => {
1152 let now = self.now.clone();
1153 E::T(self.constant(now.into())?)
1154 }
1155 Function::Rand => {
1156 let mut v = V::numeric(
1157 "((random() / 18446744073709551616.0) + 0.5)".into(),
1158 numeric_type::DOUBLE.to_string(),
1159 );
1160 v.computed_num = true;
1161 E::T(v)
1162 }
1163 Function::StrUuid | Function::Uuid => {
1164 let uuid = "lower(hex(randomblob(4)) || '-' || hex(randomblob(2)) || '-4' || substr(hex(randomblob(2)), 2) || '-' || substr('89ab', 1 + (abs(random()) % 4), 1) || substr(hex(randomblob(2)), 2) || '-' || hex(randomblob(6)))";
1165 if matches!(f, Function::Uuid) {
1166 E::T(V::iri(format!("('urn:uuid:' || {uuid})")))
1167 } else {
1168 E::T(V::string(uuid.into(), None))
1169 }
1170 }
1171 Function::Iri => match &args[0] {
1172 Expression::NamedNode(n) => E::T(self.constant(n.clone().into())?),
1173 Expression::Literal(l) if l.datatype() == xsd::STRING => {
1174 let iri = match &self.base_iri {
1175 Some(base) => oxiri::Iri::parse(base.as_str())
1176 .and_then(|b| b.resolve(l.value()))
1177 .map(oxiri::Iri::into_inner),
1178 None => {
1179 oxiri::Iri::parse(l.value().to_string()).map(oxiri::Iri::into_inner)
1180 }
1181 };
1182 match iri {
1183 Ok(i) => E::T(self.constant(NamedNode::new_unchecked(i).into())?),
1184 Err(_) => E::T(V::null()),
1185 }
1186 }
1187 _ if self.base_iri.is_some() => {
1188 return Err(Error::unsupported("IRI() of a non-constant with a BASE"))
1189 }
1190 _ => {
1191 let a = self.expr_term(&args[0], cols)?;
1192 E::T(V::iri(format!(
1193 "CASE WHEN ({k}) = {K_IRI} OR (({k}) = {K_STRING} AND instr({l}, ':') > 1) THEN {l} END",
1194 k = a.kind,
1195 l = a.lex
1196 )))
1197 }
1198 },
1199 Function::StrDt => {
1200 let a = self.args(args, cols)?;
1201 let Expression::NamedNode(dt) = &args[1] else {
1202 return Err(Error::unsupported("STRDT with a non-constant datatype"));
1203 };
1204 let lex = format!(
1205 "CASE WHEN ({}) = {K_STRING} THEN {} END",
1206 a[0].kind, a[0].lex
1207 );
1208 if dt.as_ref() == xsd::STRING {
1209 E::T(V::string(lex, None))
1210 } else {
1211 let mut v = V::typed(lex.clone(), dt.as_str());
1212 if let Some(rank) = numeric_rank(dt.as_str()) {
1213 v.num = format!("CASE WHEN trim({lex}) GLOB '*[0-9]*' THEN CAST(trim({lex}) AS REAL) END");
1214 v.nt = rank.to_string();
1215 v.stat = Stat::Numeric;
1216 }
1217 E::T(v)
1218 }
1219 }
1220 Function::StrLang => {
1221 let a = self.args(args, cols)?;
1222 let mut v = V::string(
1223 format!(
1224 "CASE WHEN ({}) = {K_STRING} THEN {} END",
1225 a[0].kind, a[0].lex
1226 ),
1227 None,
1228 );
1229 v.kind = format!("CASE WHEN ({}) = {K_STRING} THEN {K_LANG} END", a[0].kind);
1230 v.lang = format!("lower({})", a[1].lex);
1231 v.dt = xsd_str(rdf::LANG_STRING);
1232 v.stat = Stat::LangString;
1233 E::T(v)
1234 }
1235 Function::Custom(name) if name.as_str() == crate::text::TEXT_MATCH => {
1236 let [value, Expression::Literal(query)] = args else {
1237 return Err(Error::unsupported("textMatch with a non-constant query"));
1238 };
1239 if !self.stats.text_index {
1240 return Err(Error::unsupported(
1241 "textMatch without the text index (StoreOptions::text_index)",
1242 ));
1243 }
1244 let v = self.expr_term(value, cols)?;
1245 let Some(id) = v.id.clone() else {
1246 return Err(Error::unsupported("textMatch on a computed value"));
1247 };
1248 E::B(format!(
1249 "(({id}) IN (SELECT rowid FROM terms_fts WHERE terms_fts MATCH {}))",
1250 crate::sql::sql_str(query.value())
1251 ))
1252 }
1253 Function::Custom(name) => {
1254 let a = self.args(args, cols)?;
1255 if a.len() == 1
1256 && name
1257 .as_str()
1258 .starts_with("http://www.w3.org/2001/XMLSchema#")
1259 {
1260 E::T(cast(&a[0], name)?)
1261 } else {
1262 return Err(Error::unsupported(format!("custom function {name}")));
1263 }
1264 }
1265 Function::HasLang => {
1266 let a = self.expr_term(&args[0], cols)?;
1267 E::B(format!(
1268 "(CASE WHEN ({k}) IS NOT NULL THEN ({k}) IN ({K_LANG}, {K_DIRLANG}) END)",
1269 k = a.kind
1270 ))
1271 }
1272 Function::HasLangDir => {
1273 let a = self.expr_term(&args[0], cols)?;
1274 E::B(format!(
1275 "(CASE WHEN ({k}) IS NOT NULL THEN ({k}) = {K_DIRLANG} END)",
1276 k = a.kind
1277 ))
1278 }
1279 Function::LangDir => {
1280 let a = self.expr_term(&args[0], cols)?;
1281 E::T(V::string(
1282 format!(
1283 "CASE WHEN ({k}) = {K_DIRLANG} THEN substr({l}, instr({l}, '--') + 2) WHEN ({k}) IN ({K_STRING}, {K_LANG}, {K_TYPED}, {K_INT}, {K_BOOL}) THEN '' END",
1284 k = a.kind,
1285 l = a.lang
1286 ),
1287 None,
1288 ))
1289 }
1290 Function::StrLangDir => {
1291 let a = self.args(args, cols)?;
1292 let mut v = V::string(
1293 format!(
1294 "CASE WHEN ({}) = {K_STRING} AND ({}) = {K_STRING} AND ({d}) IN ('ltr', 'rtl') THEN {} END",
1295 a[0].kind,
1296 a[1].kind,
1297 a[0].lex,
1298 d = a[2].lex
1299 ),
1300 None,
1301 );
1302 v.kind = format!("CASE WHEN ({}) IS NOT NULL THEN {K_DIRLANG} END", v.lex);
1303 v.lang = format!("(lower({}) || '--' || ({}))", a[1].lex, a[2].lex);
1304 v.dt = sql_str("http://www.w3.org/1999/02/22-rdf-syntax-ns#dirLangString");
1305 v.stat = Stat::LangString;
1306 E::T(v)
1307 }
1308 Function::Subject | Function::Predicate | Function::Object => {
1309 let a = self.expr_term(&args[0], cols)?;
1310 let (Some(id), true) = (&a.id, a.decodable) else {
1311 return Err(Error::unsupported(
1312 "SUBJECT/PREDICATE/OBJECT of a computed triple",
1313 ));
1314 };
1315 let c = match f {
1316 Function::Subject => "s",
1317 Function::Predicate => "p",
1318 _ => "o",
1319 };
1320 E::T(V::from_id(&format!(
1321 "(CASE WHEN ({}) = {K_TRIPLE} THEN (SELECT {c} FROM triple_terms WHERE id = {id}) END)",
1322 a.kind
1323 )))
1324 }
1325 Function::Timezone => {
1326 let a = self.expr_term(&args[0], cols)?;
1327 let l = &a.lex;
1328 let h = format!("CAST(substr({l}, -5, 2) AS INTEGER)");
1330 let m = format!("CAST(substr({l}, -2, 2) AS INTEGER)");
1331 let lex = format!(
1332 "CASE WHEN ({ts}) IS NULL THEN NULL WHEN substr({l}, -1) = 'Z' THEN 'PT0S' WHEN substr({l}, -6, 1) IN ('+', '-') AND substr({l}, -3, 1) = ':' THEN (CASE WHEN substr({l}, -6, 1) = '-' AND ({h} > 0 OR {m} > 0) THEN '-' ELSE '' END) || 'PT' || CASE WHEN {h} = 0 AND {m} = 0 THEN '0S' ELSE (CASE WHEN {h} > 0 THEN {h} || 'H' ELSE '' END) || (CASE WHEN {m} > 0 THEN {m} || 'M' ELSE '' END) END END",
1333 ts = a.ts
1334 );
1335 E::T(V::typed(
1336 lex,
1337 "http://www.w3.org/2001/XMLSchema#dayTimeDuration",
1338 ))
1339 }
1340 other => return Err(Error::unsupported(format!("SPARQL function {other}"))),
1341 })
1342 }
1343
1344 fn regex(&mut self, args: &[Expression], cols: &BTreeMap<usize, Binding>) -> Result<E> {
1345 let text = self.expr_term(&args[0], cols)?;
1346 let flags = match args.get(2) {
1347 None => Some(String::new()),
1348 Some(Expression::Literal(l)) => Some(l.value().to_string()),
1349 Some(_) => None,
1350 };
1351 let guard = text.is_string_like();
1352 if let (Expression::Literal(p), Some(flags)) = (&args[1], &flags) {
1353 let ci = flags == "i";
1354 if flags.is_empty() || ci {
1355 if let Some((starts, lit, ends)) = simple_regex(p.value()) {
1356 let (t, l) = if ci {
1357 (format!("lower({})", text.lex), sql_str(&lit.to_lowercase()))
1358 } else {
1359 (text.lex.clone(), sql_str(&lit))
1360 };
1361 let test = match (starts, ends) {
1362 (true, true) => format!("{t} = {l}"),
1363 (true, false) => format!("substr({t}, 1, length({l})) = {l}"),
1364 (false, true) => {
1365 format!("(length({l}) = 0 OR substr({t}, -length({l})) = {l})")
1366 }
1367 (false, false) => format!("instr({t}, {l}) > 0"),
1368 };
1369 return Ok(E::B(format!("(CASE WHEN {guard} THEN {test} END)")));
1370 }
1371 }
1372 }
1373 if self.caps.udf {
1374 let pattern = self.expr_term(&args[1], cols)?;
1375 let flags = match args.get(2) {
1376 Some(f) => self.expr_term(f, cols)?.lex,
1377 None => "''".into(),
1378 };
1379 return Ok(E::B(format!(
1380 "(CASE WHEN {guard} THEN oxilite_regex({}, {}, {flags}) END)",
1381 text.lex, pattern.lex
1382 )));
1383 }
1384 Err(Error::unsupported(
1385 "REGEX with a non-literal pattern (no regex UDF on this backend)",
1386 ))
1387 }
1388}
1389
1390fn collapse(lex: &str) -> String {
1392 format!("trim({lex}, ' ' || char(9) || char(10) || char(13))")
1393}
1394
1395fn let_lex(t: &str, body: impl Fn(&str) -> String) -> String {
1399 format!("(SELECT {} FROM (SELECT {t} AS lx))", body("lx"))
1400}
1401
1402fn int_lex(t: &str) -> String {
1404 let u = format!("(CASE WHEN substr({t}, 1, 1) IN ('+', '-') THEN substr({t}, 2) ELSE {t} END)");
1405 format!("({u} <> '' AND {u} NOT GLOB '*[^0-9]*')")
1406}
1407
1408fn dec_lex(t: &str) -> String {
1410 let u = format!("(CASE WHEN substr({t}, 1, 1) IN ('+', '-') THEN substr({t}, 2) ELSE {t} END)");
1411 format!(
1412 "({u} <> '' AND {u} <> '.' AND {u} NOT GLOB '*[^0-9.]*' AND length({u}) - length(replace({u}, '.', '')) <= 1)"
1413 )
1414}
1415
1416fn dbl_lex(t: &str) -> String {
1418 let e = format!("instr(lower({t}), 'e')");
1419 let mantissa = format!("substr({t}, 1, {e} - 1)");
1420 let exponent = format!("substr({t}, {e} + 1)");
1421 format!(
1422 "({t} IN ('INF', '+INF', '-INF') OR {} OR ({e} > 0 AND {} AND {}))",
1423 dec_lex(t),
1424 dec_lex(&mantissa),
1425 int_lex(&exponent)
1426 )
1427}
1428
1429fn cast(a: &V, dt: &NamedNode) -> Result<V> {
1432 let t = collapse(&a.lex);
1433 let is_str = format!("({}) = {K_STRING}", a.kind);
1434 let is_num = format!("({}) IS NOT NULL", a.nt);
1435 let is_bool = format!("({}) IS NOT NULL", a.boolv);
1436 if a.computed_num {
1437 return Err(Error::unsupported("cast of a computed number"));
1438 }
1439 let dts = dt.as_str();
1440 Ok(match dts {
1441 "http://www.w3.org/2001/XMLSchema#string" => {
1442 let n = &a.num;
1444 V::string(
1445 format!(
1446 "CASE WHEN ({k}) IN ({K_IRI}, {K_STRING}) THEN {l} WHEN {is_bool} THEN CASE ({b}) WHEN 1 THEN 'true' ELSE 'false' END WHEN ({n}) IS NOT NULL AND ({n}) = CAST({n} AS INTEGER) AND abs({n}) < 1e15 THEN CAST(CAST({n} AS INTEGER) AS TEXT) WHEN ({n}) IS NOT NULL THEN CAST({n} AS TEXT) WHEN ({k}) = {K_TYPED} THEN {l} END",
1447 k = a.kind,
1448 l = a.lex,
1449 b = a.boolv
1450 ),
1451 None,
1452 )
1453 }
1454 "http://www.w3.org/2001/XMLSchema#boolean" => V::boolean(&format!(
1455 "CASE WHEN {is_bool} THEN ({b}) WHEN {is_num} THEN (({n}) IS NOT NULL AND ({n}) <> 0) WHEN {is_str} THEN CASE WHEN {t} IN ('true', '1') THEN 1 WHEN {t} IN ('false', '0') THEN 0 END END",
1456 b = a.boolv,
1457 n = a.num
1458 )),
1459 "http://www.w3.org/2001/XMLSchema#integer" => V::integer(format!(
1460 "CASE WHEN {is_bool} THEN ({b}) WHEN {is_num} THEN CASE WHEN abs({n}) < 9.2e18 THEN CAST({n} AS INTEGER) END WHEN {is_str} THEN {parse} END",
1461 b = a.boolv,
1462 n = a.num,
1463 parse = let_lex(&t, |x| format!("CASE WHEN {} THEN CAST({x} AS INTEGER) END", int_lex(x)))
1464 )),
1465 "http://www.w3.org/2001/XMLSchema#decimal" => V::numeric(
1466 format!(
1467 "CASE WHEN {is_bool} THEN ({b}) * 1.0 WHEN {is_num} THEN CASE WHEN abs({n}) < 9e999 THEN CAST({n} AS REAL) END WHEN {is_str} THEN {parse} END",
1468 b = a.boolv,
1469 n = a.num,
1470 parse = let_lex(&t, |x| format!("CASE WHEN {} THEN CAST({x} AS REAL) END", dec_lex(x)))
1471 ),
1472 numeric_type::DECIMAL.to_string(),
1473 ),
1474 "http://www.w3.org/2001/XMLSchema#float" | "http://www.w3.org/2001/XMLSchema#double" => V::numeric(
1475 format!(
1476 "CASE WHEN {is_bool} THEN ({b}) * 1.0 WHEN {is_num} THEN CAST({n} AS REAL) WHEN {is_str} THEN {parse} END",
1477 b = a.boolv,
1478 n = a.num,
1479 parse = let_lex(&t, |x| format!(
1480 "CASE WHEN {} THEN CASE WHEN {x} IN ('INF', '+INF') THEN 9e999 WHEN {x} = '-INF' THEN -9e999 ELSE CAST({x} AS REAL) END END",
1481 dbl_lex(x)
1482 ))
1483 ),
1484 if dts.ends_with("float") { numeric_type::FLOAT } else { numeric_type::DOUBLE }.to_string(),
1485 ),
1486 "http://www.w3.org/2001/XMLSchema#dateTime" => {
1487 let valid = format!(
1488 "(({dt}) = {dts_sql} OR ({is_str} AND substr({t}, 1, 10) GLOB '[0-9][0-9][0-9][0-9]-[0-1][0-9]-[0-3][0-9]' AND substr({t}, 11, 9) GLOB 'T[0-2][0-9]:[0-5][0-9]:[0-6][0-9]' AND julianday({t}) IS NOT NULL))",
1489 dt = a.dt,
1490 dts_sql = sql_str(dts)
1491 );
1492 let mut v = V::typed(format!("CASE WHEN {valid} THEN {t} END"), dts);
1493 v.ts = format!("CASE WHEN {valid} THEN (julianday({t}) - 2440587.5) * 86400.0 END");
1494 v.stat = Stat::DateTime;
1495 v
1496 }
1497 _ => return Err(Error::unsupported(format!("XSD cast {dt}"))),
1498 })
1499}
1500
1501pub(crate) fn format_number(num: f64, dt: &str) -> Literal {
1503 match numeric_rank(dt) {
1504 Some(numeric_type::INTEGER) => Literal::from(num as i64),
1505 Some(numeric_type::DECIMAL) => {
1506 let digits = if num == 0.0 {
1509 0
1510 } else {
1511 15 - (num.abs().log10().floor() as i32) - 1
1512 };
1513 let text = format!("{:.*}", digits.clamp(0, 30) as usize, num);
1514 let d = std::str::FromStr::from_str(&text)
1515 .or_else(|_| oxsdatatypes::Decimal::try_from(oxsdatatypes::Double::from(num)))
1516 .unwrap_or_default();
1517 Literal::from(d)
1518 }
1519 Some(numeric_type::FLOAT) => Literal::from(oxsdatatypes::Float::from(num as f32)),
1520 _ => Literal::from(oxsdatatypes::Double::from(num)),
1521 }
1522}
1523
1524#[cfg(test)]
1525mod tests {
1526 use super::simple_regex;
1527
1528 #[test]
1529 fn simple_regexes() {
1530 assert_eq!(simple_regex("^abc$"), Some((true, "abc".into(), true)));
1531 assert_eq!(simple_regex("a\\.b"), Some((false, "a.b".into(), false)));
1532 assert_eq!(simple_regex("a.b"), None);
1533 assert_eq!(simple_regex("(x|y)"), None);
1534 }
1535}