use std::borrow::Cow;
use std::sync::Arc;
use anyhow::{Result, ensure};
use reblessive::tree::Stk;
use surrealdb_types::ToSql;
use crate::catalog::providers::{DatabaseProvider, NamespaceProvider};
use crate::ctx::FrozenContext;
use crate::dbs::{Iterator, Options, Statement};
use crate::doc::{CursorDoc, NsDbCtx};
use crate::exe::FlowResultExt as _;
use crate::exec::Error as ExecError;
use crate::expr::order::Ordering;
use crate::expr::statements::select::SelectStatement;
use crate::expr::{Expr, Field, Fields, Idiom};
use crate::idx::planner::{QueryPlanner, RecordStrategy, StatementContext};
use crate::val::{Datetime, Value};
fn uncovered_order_idiom(stm: &SelectStatement) -> Option<String> {
let Some(Ordering::Order(orders)) = &stm.order else {
return None;
};
let Fields::Select(fields) = &stm.fields else {
return None;
};
if stm.fields.has_all_selection() {
return None;
}
let covered = |idiom: &Idiom| {
fields.iter().any(|field| {
let Field::Single(selector) = field else {
return true;
};
if selector.alias.as_ref().is_some_and(|alias| alias == idiom) {
return true;
}
match &selector.expr {
Expr::Idiom(x) => x == idiom,
v => v.to_idiom() == *idiom,
}
})
};
orders.iter().find(|order| !covered(&order.value)).map(|order| order.value.to_sql())
}
#[instrument(level = "trace", name = "SelectStatement::compute", skip_all)]
pub(crate) async fn select_statement_compute(
this: &SelectStatement,
stk: &mut Stk,
ctx: &FrozenContext,
opt: &Options,
parent_doc: Option<&CursorDoc>,
) -> Result<Value> {
opt.valid_for_db()?;
crate::dbs::validate_for_update(this)?;
let stm = Statement::from_select(stk, ctx, opt, parent_doc, this).await?;
let mut iterator = Iterator::new();
let ts_impl = ctx.tx().timestamp_impl();
let version = stk
.run(|stk| crate::legacy::expr_compute(&this.version, stk, ctx, opt, parent_doc))
.await
.catch_return()?
.cast_to::<Option<Datetime>>()?
.map(|x| x.to_version_stamp(ts_impl.as_ref()))
.transpose()?;
let opt = Arc::new(
opt.clone().with_version(version.or(opt.version)).with_for_update(this.for_update),
);
ensure!(
!(this.for_update && opt.version.is_some()),
ExecError::Query {
message: crate::dbs::FOR_UPDATE_VERSION_ERROR.to_string(),
}
);
iterator.setup_limit(stk, ctx, &opt, &stm).await?;
ensure!(
!this.only || iterator.is_limit_one_or_zero() || this.what.len() <= 1,
ExecError::SingleOnlyOutput
);
let ctx = stm.setup_timeout(stk, ctx, &opt, parent_doc).await?;
let mut planner = QueryPlanner::new();
let stm_ctx = StatementContext::new(&ctx, &opt, &stm)?;
let txn = ctx.tx();
let ns = txn.expect_ns_by_name(opt.ns()?).await?;
let db = txn.expect_db_by_name(opt.ns()?, opt.db()?).await?;
let doc_ctx = NsDbCtx {
ns: Arc::clone(&ns),
db: Arc::clone(&db),
};
if let Some(idiom) = uncovered_order_idiom(this) {
return Err(anyhow::Error::new(ExecError::Query {
message: format!(
"Cannot ORDER BY `{idiom}` because it is not in the statement selection. \
Either add it to the selection, or use a planner strategy other than \
'compute-only'."
),
}));
}
if opt.version.is_some() {
for w in this.what.iter() {
if matches!(w, Expr::Select(_)) {
return Err(anyhow::Error::new(ExecError::Query {
message: "VERSION clause cannot be used with a subquery source. \
Place the VERSION clause inside the subquery instead."
.to_string(),
}));
}
}
}
let prepare_ctx: Cow<'_, FrozenContext> = CursorDoc::with_parent_ctx(&ctx, parent_doc);
for w in this.what.iter() {
iterator
.prepare(
stk,
prepare_ctx.as_ref(),
&opt,
parent_doc,
&mut planner,
&stm_ctx,
&doc_ctx,
w,
)
.await?;
}
CursorDoc::update_parent(prepare_ctx.as_ref(), None, async |ctx| {
let ctx = stm.setup_query_planner(planner, ctx);
let res =
iterator.output(stk, ctx.as_ref(), &opt, &stm, RecordStrategy::KeysAndValues).await?;
ctx.expect_not_timedout().await?;
if this.only {
match res {
Value::Array(mut array) => {
if array.is_empty() {
Ok(Value::None)
} else {
ensure!(array.len() == 1, ExecError::SingleOnlyOutput);
Ok(array.0.pop().expect("array has exactly one element"))
}
}
x => Ok(x),
}
} else {
Ok(res)
}
})
.await
}