use std::borrow::Cow;
use std::slice::Iter;
use anyhow::Result;
use reblessive::tree::Stk;
use revision::revisioned;
use surrealdb_types::{SqlFormat, ToSql};
use super::paths::ID;
use crate::ctx::FrozenContext;
use crate::dbs::Options;
use crate::doc::CursorDoc;
use crate::expr::{Expr, FlowResultExt as _, Function, Idiom, Part};
use crate::fnc::args::FromArgs;
use crate::syn;
use crate::val::{Array, Value};
#[revisioned(revision = 1)]
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub(crate) enum Fields {
Value(Box<Selector>),
Select(Vec<Field>),
}
impl ToSql for Fields {
fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
let fields: crate::sql::field::Fields = self.clone().into();
fields.fmt_sql(f, fmt);
}
}
impl Fields {
pub fn read_only(&self) -> bool {
match self {
Fields::Value(field) => field.read_only(),
Fields::Select(fields) => fields.iter().all(|x| x.read_only()),
}
}
pub fn all() -> Self {
Fields::Select(vec![Field::All])
}
pub fn has_all_selection(&self) -> bool {
match self {
Fields::Select(x) => x.iter().any(|x| matches!(x, Field::All)),
Fields::Value(_) => false,
}
}
pub(crate) fn value_id() -> Self {
Fields::Value(Box::new(Selector {
expr: Expr::Idiom(Idiom(ID.to_vec())),
alias: None,
}))
}
pub(crate) fn iter_non_all_fields(&self) -> FieldsIter<'_> {
match self {
Fields::Value(selector) => FieldsIter::Single(Some(selector)),
Fields::Select(fields) => FieldsIter::Multiple(fields.iter()),
}
}
pub fn is_single(&self) -> bool {
matches!(self, Fields::Value(_))
}
pub(crate) fn is_count_all_only(&self) -> bool {
fn field_is_count(f: &Field) -> bool {
match f {
Field::All => false,
Field::Single(selector) => selector_is_count(selector),
}
}
fn selector_is_count(f: &Selector) -> bool {
let Expr::FunctionCall(x) = &f.expr else {
return false;
};
if !x.arguments.is_empty() {
return false;
}
let Function::Normal(name) = &x.receiver else {
return false;
};
name == "count"
}
match self {
Fields::Value(field) => selector_is_count(field),
Fields::Select(fields) => !fields.is_empty() && fields.iter().all(field_is_count),
}
}
pub(crate) async fn compute(
&self,
stk: &mut Stk,
ctx: &FrozenContext,
opt: &Options,
doc: Option<&CursorDoc>,
) -> Result<Value> {
if let Some(doc) = doc {
self.compute_value(stk, ctx, opt, doc).await
} else {
let doc = Value::None.into();
self.compute_value(stk, ctx, opt, &doc).await
}
}
async fn compute_value(
&self,
stk: &mut Stk,
ctx: &FrozenContext,
opt: &Options,
doc: &CursorDoc,
) -> Result<Value> {
let mut out = if self.has_all_selection() {
doc.doc.as_ref().clone()
} else {
Value::empty_object()
};
for v in self.iter_non_all_fields() {
let name = v
.alias
.as_ref()
.map(Cow::Borrowed)
.unwrap_or_else(|| Cow::Owned(v.expr.to_idiom()));
match &v.expr {
Expr::Idiom(i) if i.is_multi_yield() => {
let mut res: Vec<(&[Part], Value)> = Vec::new();
for v in i.split_inclusive(Idiom::part_is_multi_yield) {
let x = match res.last() {
Some((_, r)) => r,
None => doc.doc.as_ref(),
};
let x = x
.get(stk, ctx, opt, Some(doc), v)
.await
.catch_return()?
.flatten();
res.push((v, x));
}
for (p, x) in res {
match p.last().expect("idiom is non-empty").alias() {
Some(a) => {
if let Some(i) = &v.alias {
out.set(stk, ctx, opt, &i.0, x.clone()).await?;
}
out.set(stk, ctx, opt, a, x).await?;
}
None => {
out.set(stk, ctx, opt, v.alias.as_ref().unwrap_or(i), x).await?
}
}
}
}
Expr::FunctionCall(f) => {
match f.receiver {
Function::Normal(ref x) if x == "type::fields" => {
let mut arguments = Vec::new();
for arg in f.arguments.iter() {
arguments.push(
stk.run(|stk| arg.compute(stk, ctx, opt, Some(doc)))
.await
.catch_return()?,
);
}
let (args,) = <(Vec<String>,)>::from_args("type::fields", arguments)?;
let mut idioms = Vec::<Idiom>::new();
for arg in args {
idioms.push(syn::idiom(&arg)?.into())
}
let mut idiom_results = Vec::new();
for idiom in idioms.iter() {
let res =
idiom.compute(stk, ctx, opt, Some(doc)).await.catch_return()?;
idiom_results.push(res);
}
if self.is_single() {
out = Value::Array(Array(idiom_results));
} else {
for (idiom, idiom_res) in idioms.iter().zip(idiom_results) {
out.set(stk, ctx, opt, &idiom.0, idiom_res).await?;
}
}
}
Function::Normal(ref x) if x == "type::field" => {
let mut arguments = Vec::new();
for arg in f.arguments.iter() {
arguments.push(
stk.run(|stk| arg.compute(stk, ctx, opt, Some(doc)))
.await
.catch_return()?,
);
}
let (arg,) = <(String,)>::from_args("type::field", arguments)?;
let idiom: Idiom = syn::idiom(&arg)?.into();
let res =
idiom.compute(stk, ctx, opt, Some(doc)).await.catch_return()?;
if let Some(alias) = &v.alias {
out.set(stk, ctx, opt, alias, res).await?;
} else if self.is_single() {
out = res
} else {
out.set(stk, ctx, opt, &idiom.0, res).await?;
}
}
_ => {
let expr = stk
.run(|stk| v.expr.compute(stk, ctx, opt, Some(doc)))
.await
.catch_return()?;
if self.is_single() {
out = expr;
} else {
out.set(stk, ctx, opt, name.as_ref(), expr).await?;
}
}
}
}
_ => {
let expr = stk
.run(|stk| v.expr.compute(stk, ctx, opt, Some(doc)))
.await
.catch_return()?;
if self.is_single() {
out = expr;
} else {
out.set(stk, ctx, opt, name.as_ref(), expr).await?;
}
}
}
}
Ok(out)
}
}
pub(crate) enum FieldsIter<'a> {
Single(Option<&'a Selector>),
Multiple(Iter<'a, Field>),
}
impl<'a> Iterator for FieldsIter<'a> {
type Item = &'a Selector;
fn next(&mut self) -> Option<Self::Item> {
match self {
FieldsIter::Single(field) => field.take(),
FieldsIter::Multiple(iter) => loop {
if let Field::Single(x) = iter.next()? {
return Some(x);
}
},
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self {
FieldsIter::Single(field) => {
if field.is_some() {
(1, Some(1))
} else {
(0, Some(0))
}
}
FieldsIter::Multiple(iter) => iter.size_hint(),
}
}
}
impl ExactSizeIterator for FieldsIter<'_> {}
#[revisioned(revision = 1)]
#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
pub(crate) enum Field {
#[default]
All,
Single(Selector),
}
impl Field {
pub fn read_only(&self) -> bool {
match self {
Field::All => true,
Field::Single(x) => x.read_only(),
}
}
}
impl ToSql for Field {
fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
match self {
Self::All => f.push('*'),
Self::Single(s) => s.fmt_sql(f, fmt),
}
}
}
#[revisioned(revision = 1)]
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub(crate) struct Selector {
pub expr: Expr,
pub alias: Option<Idiom>,
}
impl Selector {
pub fn read_only(&self) -> bool {
self.expr.read_only()
}
}
impl ToSql for Selector {
fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
self.expr.fmt_sql(f, fmt);
if let Some(alias) = &self.alias {
f.push_str(" AS ");
alias.fmt_sql(f, fmt);
}
}
}