use super::super::types::Context;
use super::super::QueryExecutor;
use crate::error::DbResult;
use crate::sdbql::ast::{BodyClause, Expression, ForClause, Query};
use crate::storage::raw_rows::RawShape;
fn raw_plan(query: &Query) -> Option<(&ForClause, RawShape)> {
if !query.set_operations.is_empty()
|| !query.join_clauses.is_empty()
|| query.with_clause.is_some()
|| query.window_clause.is_some()
|| query.create_stream_clause.is_some()
|| query.create_materialized_view_clause.is_some()
|| query.refresh_materialized_view_clause.is_some()
|| !query.let_clauses.is_empty()
|| !query.post_limit_lets.is_empty()
|| query.sort_clause.is_some()
{
return None;
}
let [BodyClause::For(for_clause)] = query.body_clauses.as_slice() else {
return None;
};
if for_clause.source_expression.is_some()
|| for_clause.system_time.is_some()
|| for_clause.valid_time.is_some()
|| for_clause.options.is_some()
|| for_clause
.source_variable
.as_ref()
.is_some_and(|s| s != &for_clause.collection)
{
return None;
}
let ret = query.return_clause.as_ref()?;
if ret.distinct {
return None;
}
let var = for_clause.variable.as_str();
let field_of = |expr: &Expression| -> Option<String> {
match expr {
Expression::FieldAccess(base, field) | Expression::OptionalFieldAccess(base, field)
if !field.contains('.')
&& matches!(base.as_ref(), Expression::Variable(v) if v == var) =>
{
Some(field.clone())
}
_ => None,
}
};
let shape = match &ret.expression {
Expression::Variable(v) if v == var => RawShape::Whole,
Expression::Object(pairs) if !pairs.is_empty() => {
let mut fields: Vec<(String, String)> = Vec::with_capacity(pairs.len());
for (key, value) in pairs {
if fields.iter().any(|(k, _)| k == key) {
return None;
}
fields.push((key.clone(), field_of(value)?));
}
fields.sort_unstable_by(|a, b| a.0.cmp(&b.0));
RawShape::Fields(fields)
}
other => RawShape::Single(field_of(other)?),
};
Some((for_clause, shape))
}
impl QueryExecutor<'_> {
pub fn execute_raw_msgpack(&self, query: &Query) -> DbResult<Option<Vec<u8>>> {
let Some((for_clause, shape)) = raw_plan(query) else {
return Ok(None);
};
self.reset_query_caches();
let Some(ref db_name) = self.database else {
return Ok(None);
};
let name = &for_clause.collection;
if self.row_policy_applies(name) {
return Ok(None);
}
let Ok(database) = self.storage.get_database(db_name) else {
return Ok(None);
};
if database.is_columnar_collection(name)
|| !matches!(self.resolve_search_view_collection(name), Ok(None))
{
return Ok(None);
}
let Ok(collection) = self.get_collection(name) else {
return Ok(None);
};
if collection
.get_shard_config()
.is_some_and(|c| c.num_shards > 0)
{
return Ok(None);
}
let (offset, count) = match &query.limit_clause {
Some(limit) => self.eval_limit(limit, &Context::default()),
None => (0, None),
};
let ceiling = self.max_intermediate_rows.saturating_add(1);
let limit = Some(count.map_or(ceiling, |n| n.min(ceiling)));
let Some((rows, body)) =
collection.scan_raw(offset, limit, &shape, |n| self.check_budget(n))?
else {
return Ok(None);
};
if rows > self.max_intermediate_rows {
return Ok(None);
}
let mut out = Vec::with_capacity(body.len() + 5);
if rmp::encode::write_array_len(&mut out, rows as u32).is_err() {
return Ok(None);
}
out.extend_from_slice(&body);
Ok(Some(out))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sdbql::parser::parse;
fn plan(q: &str) -> Option<String> {
let query = parse(q).unwrap();
raw_plan(&query).map(|(_, shape)| format!("{shape:?}"))
}
#[test]
fn shapes_it_answers() {
assert_eq!(plan("FOR d IN c RETURN d").as_deref(), Some("Whole"));
assert_eq!(
plan("FOR d IN c LIMIT 5 RETURN d").as_deref(),
Some("Whole")
);
assert_eq!(
plan("FOR d IN c RETURN d.title").as_deref(),
Some("Single(\"title\")")
);
assert_eq!(
plan("FOR d IN c LIMIT @o, @n RETURN {z: d.a, b: d._key}").as_deref(),
Some("Fields([(\"b\", \"_key\"), (\"z\", \"a\")])")
);
}
#[test]
fn everything_else_is_left_to_the_ordinary_path() {
for q in [
"FOR d IN c FILTER d.a > 1 RETURN d",
"FOR d IN c SORT d.a RETURN d",
"FOR d IN c RETURN DISTINCT d.a",
"FOR d IN c RETURN {a: d.a, a: d.b}",
"FOR d IN c RETURN {a: d.a.b}",
"FOR d IN c RETURN {a: d.a, n: 1}",
"FOR d IN c RETURN MERGE(d, {x: 1})",
"FOR d IN c LET x = 1 RETURN d",
"LET x = 1 FOR d IN c RETURN d",
"FOR d IN c FOR e IN c RETURN d",
"FOR d IN 1..3 RETURN d",
"FOR d IN c COLLECT a = d.a RETURN a",
"FOR d IN c RETURN e",
] {
assert_eq!(plan(q), None, "{q}");
}
}
}