use js::class::Trace;
use js::prelude::{Coerced, Opt};
use js::{Array, Ctx, Exception, FromJs, JsLifetime, Result, Value};
use surrealdb_types::ToSql;
use crate::dbs::Variables;
use crate::expr::Expr;
use crate::syn;
use crate::val::Value as SurValue;
#[js::class]
#[derive(Trace, Clone, JsLifetime)]
pub(crate) struct Query {
#[qjs(skip_trace)]
pub(crate) query: Expr,
#[qjs(skip_trace)]
pub(crate) vars: Option<Variables>,
}
#[derive(Default, Clone)]
pub(crate) struct QueryVariables(pub(crate) Variables);
impl QueryVariables {
pub fn new() -> Self {
QueryVariables(Variables::new())
}
pub fn from_value<'js>(ctx: &Ctx<'js>, val: Value<'js>) -> Result<Self> {
static INVALID_ERROR: &str = "Query argument was neither sequence<[String,SurValue]> or record<ByteString, SurValue>";
let mut res = Self::new();
if let Some(array) = val.as_array() {
for v in array.iter::<Array>() {
let v = match v {
Ok(x) => x,
Err(e) => {
if e.is_from_js() {
return Err(Exception::throw_type(ctx, INVALID_ERROR));
}
return Err(e);
}
};
let key = match v.get::<Coerced<String>>(0) {
Ok(x) => x,
Err(e) => {
if e.is_from_js() {
return Err(Exception::throw_type(ctx, INVALID_ERROR));
}
return Err(e);
}
};
let value = match v.get::<SurValue>(1) {
Ok(x) => x,
Err(e) => {
if e.is_from_js() {
return Err(Exception::throw_type(ctx, INVALID_ERROR));
}
return Err(e);
}
};
res.0.insert(key.0, value);
}
} else if let Some(obj) = val.as_object() {
for prop in obj.props::<String, SurValue>() {
let (key, value) = match prop {
Ok(x) => x,
Err(e) => {
if e.is_from_js() {
return Err(Exception::throw_type(ctx, INVALID_ERROR));
}
return Err(e);
}
};
res.0.insert(key, value);
}
} else {
return Err(Exception::throw_type(ctx, INVALID_ERROR));
}
Ok(res)
}
}
impl<'js> FromJs<'js> for QueryVariables {
fn from_js(ctx: &Ctx<'js>, value: Value<'js>) -> Result<Self> {
QueryVariables::from_value(ctx, value)
}
}
#[js::methods]
impl Query {
#[qjs(constructor)]
pub fn new(ctx: Ctx<'_>, text: String, variables: Opt<QueryVariables>) -> Result<Self> {
let query = syn::expr(&text)
.map_err(|e| {
let error_text = format!("{}", e);
Exception::throw_type(&ctx, &error_text)
})?
.into();
let vars = variables.into_inner().map(|x| x.0);
Ok(Query {
query,
vars,
})
}
#[qjs(rename = "toString")]
pub fn js_to_string(&self) -> String {
self.query.to_sql()
}
pub fn bind(&mut self, key: Coerced<String>, value: SurValue) {
self.vars.get_or_insert_with(Variables::new).insert(key.0, value);
}
}