1use crate::compiler::expr::format_number;
6use crate::compiler::{Block, Col, Compiler, QueryOptions, VAL_FIELDS};
7use crate::encoding::{blank_node_id, tag_of, Tag, DEFAULT_GRAPH_ID};
8use crate::error::{Error, Result};
9use crate::job::{Job, Step};
10use crate::resolve::{join, TermResolver};
11use crate::sql::{Capabilities, Request, Response, SqlValue, Statement};
12use crate::stats::Stats;
13use oxrdf::{BlankNode, Literal, NamedNode, NamedOrBlankNode, Term, Triple, Variable};
14use spargebra::algebra::GraphPattern;
15use spargebra::term::{NamedNodePattern, TermPattern, TriplePattern};
16use spargebra::Query;
17use std::collections::{HashMap, HashSet};
18
19#[derive(Debug, Clone, PartialEq)]
21pub enum QueryOutput {
22 Solutions {
23 variables: Vec<Variable>,
24 rows: Vec<Vec<Option<Term>>>,
25 },
26 Boolean(bool),
27 Graph(Vec<Triple>),
28}
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31enum Out {
32 Id,
33 Val,
34}
35
36#[derive(Debug, Clone)]
37enum Form {
38 Select,
39 Ask,
40 Construct(Vec<TriplePattern>),
41 Describe,
42}
43
44#[derive(Debug, Clone)]
46pub struct CompiledQuery {
47 pub sql: String,
49 form: Form,
50 variables: Vec<Variable>,
51 layout: Vec<Out>,
52 constants: HashMap<i64, Term>,
53 union_default_graph: bool,
54 source: String,
56 pub notes: Vec<String>,
58}
59
60impl CompiledQuery {
61 pub fn explain(&self) -> String {
64 let mut out = format!(
65 "-- oxilite: fully compiled to SQL ({} output variable(s))\n",
66 self.variables.len()
67 );
68 for n in &self.notes {
69 out.push_str("-- ");
70 out.push_str(n);
71 out.push('\n');
72 }
73 out.push_str(&self.sql);
74 out
75 }
76}
77
78fn projected(p: &GraphPattern) -> Option<&[Variable]> {
79 match p {
80 GraphPattern::Project { variables, .. } => Some(variables),
81 GraphPattern::Slice { inner, .. }
82 | GraphPattern::Distinct { inner }
83 | GraphPattern::Reduced { inner }
84 | GraphPattern::OrderBy { inner, .. } => projected(inner),
85 _ => None,
86 }
87}
88
89fn template_vars(template: &[TriplePattern], out: &mut Vec<Variable>) {
90 fn tp(t: &TermPattern, out: &mut Vec<Variable>) {
91 match t {
92 TermPattern::Variable(v) if !out.contains(v) => out.push(v.clone()),
93 TermPattern::Triple(t) => {
94 tp(&t.subject, out);
95 if let NamedNodePattern::Variable(v) = &t.predicate {
96 if !out.contains(v) {
97 out.push(v.clone());
98 }
99 }
100 tp(&t.object, out);
101 }
102 _ => {}
103 }
104 }
105 for t in template {
106 tp(&t.subject, out);
107 if let NamedNodePattern::Variable(v) = &t.predicate {
108 if !out.contains(v) {
109 out.push(v.clone());
110 }
111 }
112 tp(&t.object, out);
113 }
114}
115
116pub fn compile_query(
119 query: &Query,
120 stats: &Stats,
121 caps: &Capabilities,
122 options: &QueryOptions,
123) -> Result<CompiledQuery> {
124 let (dataset, pattern, base, form) = match query {
125 Query::Select {
126 dataset,
127 pattern,
128 base_iri,
129 } => (dataset, pattern, base_iri, Form::Select),
130 Query::Ask {
131 dataset,
132 pattern,
133 base_iri,
134 } => (dataset, pattern, base_iri, Form::Ask),
135 Query::Construct {
136 template,
137 dataset,
138 pattern,
139 base_iri,
140 } => (
141 dataset,
142 pattern,
143 base_iri,
144 Form::Construct(template.clone()),
145 ),
146 Query::Describe {
147 dataset,
148 pattern,
149 base_iri,
150 } => (dataset, pattern, base_iri, Form::Describe),
151 };
152 crate::version::check_query_options(stats, options)?;
153 let mut c = Compiler::new(
154 stats,
155 caps,
156 options,
157 dataset.as_ref(),
158 base.as_ref().map(|b| b.as_str().to_string()),
159 );
160 let block = c.pattern(pattern)?;
161 let variables: Vec<Variable> = match &form {
162 Form::Construct(t) => {
163 let mut v = Vec::new();
164 template_vars(t, &mut v);
165 v
166 }
167 _ => match projected(pattern) {
168 Some(v) => v.to_vec(),
169 None => block
170 .cols
171 .keys()
172 .map(|i| c.var_names[*i].clone())
173 .filter(|v| !v.as_str().starts_with('\u{1}'))
174 .collect(),
175 },
176 };
177 let idxs: Vec<usize> = variables.iter().map(|v| c.var(v)).collect();
178 let layout = idxs
179 .iter()
180 .map(|i| match block.cols.get(i).map(|b| &b.col) {
181 Some(Col::Val(_)) => Out::Val,
182 _ => Out::Id,
183 })
184 .collect();
185 let sql = match form {
186 Form::Ask => {
187 let inner = block.to_select(Some(&[]), false);
188 format!("SELECT EXISTS ({inner})")
189 }
190 _ => final_select(&block, &idxs, caps.int64_as_text),
191 };
192 if sql.len() > caps.max_sql_len {
193 return Err(Error::unsupported(format!(
194 "generated SQL is {} bytes, over the backend limit of {}",
195 sql.len(),
196 caps.max_sql_len
197 )));
198 }
199 Ok(CompiledQuery {
200 sql,
201 form,
202 variables,
203 layout,
204 constants: c.constants,
205 union_default_graph: options.union_default_graph,
206 source: options.as_of_tick.map_or_else(
207 || "quads".to_owned(),
208 |t| crate::version::as_of_sql(&t.to_string()),
209 ),
210 notes: c.notes,
211 })
212}
213
214fn final_select(block: &Block, idxs: &[usize], text_ids: bool) -> String {
215 block.to_select(Some(idxs), text_ids)
216}
217
218fn decode_val(cells: &[SqlValue]) -> Option<Term> {
220 let get = |name: &str| {
221 let i = VAL_FIELDS.iter().position(|f| *f == name).expect("field");
222 &cells[i]
223 };
224 let kind = get("k").as_i64()?;
225 let lex = get("l").clone().into_string();
226 let dt = get("d").clone().into_string();
227 let lang = get("g").clone().into_string();
228 let num = get("n").as_f64();
229 Some(match Tag::from_u8(kind as u8)? {
230 Tag::Iri => NamedNode::new(lex?).ok()?.into(),
231 Tag::BlankNode => BlankNode::new_unchecked(lex?).into(),
232 Tag::String => Literal::new_simple_literal(lex?).into(),
233 Tag::LangString => Literal::new_language_tagged_literal(lex?, lang?)
234 .ok()?
235 .into(),
236 Tag::DirLangString => {
237 let lang = lang?;
238 let (tag, dir) = lang.split_once("--")?;
239 let dir = if dir == "rtl" {
240 oxrdf::BaseDirection::Rtl
241 } else {
242 oxrdf::BaseDirection::Ltr
243 };
244 Literal::new_directional_language_tagged_literal(lex?, tag, dir)
245 .ok()?
246 .into()
247 }
248 Tag::Integer => match lex {
249 Some(l) => Literal::new_typed_literal(l, oxrdf::vocab::xsd::INTEGER).into(),
250 None => Literal::from(num? as i64).into(),
251 },
252 Tag::Boolean => Literal::from(get("b").as_i64()? != 0).into(),
253 Tag::Typed => {
254 let dt = dt?;
255 if let Some((sum, count)) = get("x")
256 .as_str()
257 .and_then(|a| a.strip_prefix("avg:"))
258 .and_then(|a| a.split_once('/'))
259 {
260 let sum = oxsdatatypes::Decimal::from(sum.parse::<i64>().ok()?);
262 let count = oxsdatatypes::Decimal::from(count.parse::<i64>().ok()?);
263 return Some(Literal::from(sum.checked_div(count)?).into());
264 }
265 match lex {
266 Some(l) => Literal::new_typed_literal(l, NamedNode::new_unchecked(dt)).into(),
267 None => format_number(num?, &dt).into(),
268 }
269 }
270 Tag::Triple | Tag::Default => return None,
271 })
272}
273
274enum Cell {
275 Id(i64),
276 Term(Term),
277 Unbound,
278}
279
280enum State {
281 Start,
282 Main,
283 Resolving,
284 Describe,
285 DescribeResolving,
286}
287
288pub struct QueryJob {
290 compiled: CompiledQuery,
291 caps: Capabilities,
292 state: State,
293 resolver: TermResolver,
294 cells: Vec<Vec<Cell>>,
295 describe_done: HashSet<i64>,
296 describe_quads: Vec<[i64; 3]>,
297}
298
299impl QueryJob {
300 pub fn new(compiled: CompiledQuery, caps: Capabilities) -> Self {
301 let resolver = TermResolver::with_constants(compiled.constants.clone());
302 Self {
303 compiled,
304 caps,
305 state: State::Start,
306 resolver,
307 cells: Vec::new(),
308 describe_done: HashSet::new(),
309 describe_quads: Vec::new(),
310 }
311 }
312
313 pub fn compiled(&self) -> &CompiledQuery {
314 &self.compiled
315 }
316
317 fn absorb_main(&mut self, response: Response) -> Result<()> {
318 let rs = response
319 .into_iter()
320 .next()
321 .ok_or_else(|| Error::backend("empty response"))?;
322 for row in rs.rows {
323 let mut out = Vec::with_capacity(self.compiled.layout.len());
324 let mut i = 0;
325 for l in &self.compiled.layout {
326 match l {
327 Out::Id => {
328 let cell = match row.get(i).and_then(SqlValue::as_i64) {
329 Some(id) => {
330 self.resolver.want(id);
331 Cell::Id(id)
332 }
333 None => Cell::Unbound,
334 };
335 out.push(cell);
336 i += 1;
337 }
338 Out::Val => {
339 let cells = row
340 .get(i..i + VAL_FIELDS.len())
341 .ok_or_else(|| Error::corrupted("short row"))?;
342 out.push(decode_val(cells).map_or(Cell::Unbound, Cell::Term));
343 i += VAL_FIELDS.len();
344 }
345 }
346 }
347 self.cells.push(out);
348 }
349 Ok(())
350 }
351
352 fn solutions(&self) -> Result<Vec<Vec<Option<Term>>>> {
353 self.cells
354 .iter()
355 .map(|row| {
356 row.iter()
357 .map(|c| match c {
358 Cell::Id(id) => self.resolver.get(*id).map(Some),
359 Cell::Term(t) => Ok(Some(t.clone())),
360 Cell::Unbound => Ok(None),
361 })
362 .collect()
363 })
364 .collect()
365 }
366
367 fn describe_request(&mut self, ids: Vec<i64>) -> Option<Request> {
368 let ids: Vec<i64> = ids
369 .into_iter()
370 .filter(|id| {
371 matches!(tag_of(*id), Some(Tag::Iri | Tag::BlankNode))
372 && self.describe_done.insert(*id)
373 })
374 .collect();
375 if ids.is_empty() {
376 return None;
377 }
378 let c = |x: &str| {
379 if self.caps.int64_as_text {
380 format!("CAST({x} AS TEXT)")
381 } else {
382 x.into()
383 }
384 };
385 let graph = if self.compiled.union_default_graph {
386 String::new()
387 } else {
388 format!(" AND g = {DEFAULT_GRAPH_ID}")
389 };
390 let stmts = ids
391 .chunks(400)
392 .map(|chunk| {
393 Statement::new(format!(
394 "SELECT DISTINCT {}, {}, {} FROM {} x WHERE s IN ({}){graph}",
395 c("s"),
396 c("p"),
397 c("o"),
398 self.compiled.source,
399 join(chunk)
400 ))
401 })
402 .collect();
403 Some(Request::read(stmts))
404 }
405
406 fn finish(&mut self) -> Result<QueryOutput> {
407 let rows = self.solutions()?;
408 Ok(match &self.compiled.form {
409 Form::Select => QueryOutput::Solutions {
410 variables: self.compiled.variables.clone(),
411 rows,
412 },
413 Form::Ask => unreachable!("handled on the main response"),
414 Form::Construct(template) => {
415 let mut seen = HashSet::new();
416 let mut out = Vec::new();
417 for row in rows {
418 let mut bnodes = HashMap::new();
419 for t in template {
420 if let Some(triple) =
421 instantiate(t, &self.compiled.variables, &row, &mut bnodes)
422 {
423 if seen.insert(triple.clone()) {
424 out.push(triple);
425 }
426 }
427 }
428 }
429 QueryOutput::Graph(out)
430 }
431 Form::Describe => {
432 let mut out = Vec::new();
433 let mut seen = HashSet::new();
434 for [s, p, o] in &self.describe_quads {
435 let (Ok(s), Ok(p), Ok(o)) = (
436 self.resolver.get(*s),
437 self.resolver.get(*p),
438 self.resolver.get(*o),
439 ) else {
440 continue;
441 };
442 let (Ok(s), Term::NamedNode(p)) = (crate::encoding::to_subject(s), p) else {
443 continue;
444 };
445 let t = Triple::new(s, p, o);
446 if seen.insert(t.clone()) {
447 out.push(t);
448 }
449 }
450 QueryOutput::Graph(out)
451 }
452 })
453 }
454
455 fn after_resolution(&mut self) -> Result<Step<QueryOutput>> {
456 if let Some(r) = self.resolver.request(&self.caps) {
457 return Ok(Step::Execute(r));
458 }
459 if let (Form::Describe, State::Resolving) = (&self.compiled.form, &self.state) {
460 let ids: Vec<i64> = self
461 .cells
462 .iter()
463 .flatten()
464 .filter_map(|c| match c {
465 Cell::Id(id) => Some(*id),
466 Cell::Term(t) => match t {
467 Term::NamedNode(n) => Some(crate::encoding::named_node_id(n.as_str())),
468 Term::BlankNode(b) => Some(blank_node_id(b.as_str())),
469 _ => None,
470 },
471 Cell::Unbound => None,
472 })
473 .collect();
474 self.state = State::Describe;
475 if let Some(r) = self.describe_request(ids) {
476 return Ok(Step::Execute(r));
477 }
478 }
479 Ok(Step::Done(self.finish()?))
480 }
481}
482
483fn instantiate(
484 t: &TriplePattern,
485 vars: &[Variable],
486 row: &[Option<Term>],
487 bnodes: &mut HashMap<String, BlankNode>,
488) -> Option<Triple> {
489 fn term(
490 t: &TermPattern,
491 vars: &[Variable],
492 row: &[Option<Term>],
493 bnodes: &mut HashMap<String, BlankNode>,
494 ) -> Option<Term> {
495 Some(match t {
496 TermPattern::NamedNode(n) => n.clone().into(),
497 TermPattern::Literal(l) => l.clone().into(),
498 TermPattern::BlankNode(b) => bnodes
499 .entry(b.as_str().to_string())
500 .or_default()
501 .clone()
502 .into(),
503 TermPattern::Variable(v) => row[vars.iter().position(|x| x == v)?].clone()?,
504 TermPattern::Triple(tp) => instantiate(tp, vars, row, bnodes)?.into(),
505 })
506 }
507 let s = match term(&t.subject, vars, row, bnodes)? {
508 Term::NamedNode(n) => NamedOrBlankNode::from(n),
509 Term::BlankNode(b) => NamedOrBlankNode::from(b),
510 _ => return None,
511 };
512 let p = match &t.predicate {
513 NamedNodePattern::NamedNode(n) => n.clone(),
514 NamedNodePattern::Variable(v) => match row[vars.iter().position(|x| x == v)?].clone()? {
515 Term::NamedNode(n) => n,
516 _ => return None,
517 },
518 };
519 let o = term(&t.object, vars, row, bnodes)?;
520 Some(Triple::new(s, p, o))
521}
522
523impl Job for QueryJob {
524 type Output = QueryOutput;
525
526 fn step(&mut self, response: Option<Response>) -> Result<Step<QueryOutput>> {
527 match (&self.state, response) {
528 (State::Start, _) => {
529 self.state = State::Main;
530 Ok(Step::Execute(Request::read(vec![Statement::new(
531 self.compiled.sql.clone(),
532 )])))
533 }
534 (State::Main, Some(r)) => {
535 if let Form::Ask = self.compiled.form {
536 let b = r
537 .first()
538 .and_then(|rs| rs.rows.first())
539 .and_then(|row| row.first())
540 .and_then(SqlValue::as_i64)
541 .unwrap_or(0);
542 return Ok(Step::Done(QueryOutput::Boolean(b != 0)));
543 }
544 self.absorb_main(r)?;
545 self.state = State::Resolving;
546 self.after_resolution()
547 }
548 (State::Resolving, Some(r)) => {
549 self.resolver.absorb(r)?;
550 self.after_resolution()
551 }
552 (State::Describe, Some(r)) => {
553 let mut next = Vec::new();
554 for rs in r {
555 for row in rs.rows {
556 let ids: Vec<i64> = row.iter().filter_map(SqlValue::as_i64).collect();
557 if let [s, p, o] = ids[..] {
558 self.resolver.want(s);
559 self.resolver.want(p);
560 self.resolver.want(o);
561 self.describe_quads.push([s, p, o]);
562 if tag_of(o) == Some(Tag::BlankNode) {
563 next.push(o);
564 }
565 }
566 }
567 }
568 if let Some(r) = self.describe_request(next) {
570 return Ok(Step::Execute(r));
571 }
572 self.state = State::DescribeResolving;
573 match self.resolver.request(&self.caps) {
574 Some(r) => Ok(Step::Execute(r)),
575 None => Ok(Step::Done(self.finish()?)),
576 }
577 }
578 (State::DescribeResolving, Some(r)) => {
579 self.resolver.absorb(r)?;
580 match self.resolver.request(&self.caps) {
581 Some(r) => Ok(Step::Execute(r)),
582 None => Ok(Step::Done(self.finish()?)),
583 }
584 }
585 (_, None) => Err(Error::Other("query job resumed without a response".into())),
586 }
587 }
588}