Implement embedded javascript script functions
This commit is contained in:
parent
5425d0b550
commit
a78df680d2
9 changed files with 659 additions and 54 deletions
164
Cargo.lock
generated
164
Cargo.lock
generated
|
@ -8,6 +8,17 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||
dependencies = [
|
||||
"getrandom 0.2.6",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.18"
|
||||
|
@ -251,6 +262,70 @@ dependencies = [
|
|||
"generic-array 0.14.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "boa_engine"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ad69bff25e81f00e3938f24494cfaf628806bcb21c67da454d432f43431f8c8"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"boa_gc",
|
||||
"boa_interner",
|
||||
"boa_profiler",
|
||||
"boa_unicode",
|
||||
"chrono",
|
||||
"dyn-clone",
|
||||
"fast-float",
|
||||
"gc",
|
||||
"indexmap",
|
||||
"num-bigint",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"once_cell",
|
||||
"rand 0.8.5",
|
||||
"regress",
|
||||
"rustc-hash",
|
||||
"ryu-js",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tap",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "boa_gc"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a502c6d959fbbe7a8bf5ac51f0d32aa24594bda40e89cc8e71a3def9d24f4711"
|
||||
dependencies = [
|
||||
"gc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "boa_interner"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e6d756c9183252a8ba3f434552766288f031a6aad6a7629fb84ddf49a7095f7"
|
||||
dependencies = [
|
||||
"gc",
|
||||
"string-interner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "boa_profiler"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be6644386d5b0c20bf502b7214f5c3ba4664e2c0db20b13a834d37cdd2120500"
|
||||
|
||||
[[package]]
|
||||
name = "boa_unicode"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eddf0958b0fc991b4d626aa28ca2d838b0e2160a716388aeae70c6d48684bc6b"
|
||||
dependencies = [
|
||||
"unicode-general-category",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "boringssl-src"
|
||||
version = "0.2.0"
|
||||
|
@ -525,6 +600,12 @@ dependencies = [
|
|||
"urlencoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28"
|
||||
|
||||
[[package]]
|
||||
name = "echodb"
|
||||
version = "0.3.0"
|
||||
|
@ -569,6 +650,12 @@ dependencies = [
|
|||
"rand 0.7.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fast-float"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95765f67b4b18863968b4a1bd5bb576f732b29a4a28c7cd84c09fa3e2875f33c"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.7.0"
|
||||
|
@ -770,6 +857,27 @@ dependencies = [
|
|||
"thread_local",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gc"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3edaac0f5832202ebc99520cb77c932248010c4645d20be1dc62d6579f5b3752"
|
||||
dependencies = [
|
||||
"gc_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gc_derive"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60df8444f094ff7885631d80e78eb7d88c3c2361a98daaabb06256e4500db941"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.12.4"
|
||||
|
@ -946,6 +1054,9 @@ name = "hashbrown"
|
|||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "headers"
|
||||
|
@ -1439,6 +1550,7 @@ dependencies = [
|
|||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1998,6 +2110,15 @@ version = "0.6.25"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||
|
||||
[[package]]
|
||||
name = "regress"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a92ff21fe8026ce3f2627faaf43606f0b67b014dbc9ccf027181a804f75d92e"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.3"
|
||||
|
@ -2138,6 +2259,12 @@ version = "1.0.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
|
||||
|
||||
[[package]]
|
||||
name = "ryu-js"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6518fc26bced4d53678a22d6e423e9d8716377def84545fe328236e3af070e7f"
|
||||
|
||||
[[package]]
|
||||
name = "safemem"
|
||||
version = "0.3.3"
|
||||
|
@ -2400,6 +2527,17 @@ dependencies = [
|
|||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "string-interner"
|
||||
version = "0.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e2531d8525b29b514d25e275a43581320d587b86db302b9a7e464bac579648"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"hashbrown",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.10.0"
|
||||
|
@ -2449,11 +2587,13 @@ dependencies = [
|
|||
"async-executor",
|
||||
"async-recursion",
|
||||
"bigdecimal",
|
||||
"boa_engine",
|
||||
"chrono",
|
||||
"dmp",
|
||||
"echodb",
|
||||
"futures 0.3.21",
|
||||
"fuzzy-matcher",
|
||||
"gc",
|
||||
"geo",
|
||||
"indxdb",
|
||||
"lexical-sort",
|
||||
|
@ -2503,6 +2643,24 @@ dependencies = [
|
|||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.12.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.3.0"
|
||||
|
@ -2899,6 +3057,12 @@ version = "0.3.8"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-general-category"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1218098468b8085b19a2824104c70d976491d247ce194bbd9dc77181150cdfd6"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.19"
|
||||
|
|
|
@ -6,17 +6,19 @@ version = "0.1.0"
|
|||
authors = ["Tobie Morgan Hitchcock <tobie@surrealdb.com>"]
|
||||
|
||||
[features]
|
||||
default = ["parallel", "kv-tikv", "kv-echodb", "kv-yokudb"]
|
||||
default = ["parallel", "kv-tikv", "kv-echodb", "kv-yokudb", "scripting"]
|
||||
parallel = ["executor"]
|
||||
kv-tikv = ["tikv"]
|
||||
kv-echodb = ["echodb"]
|
||||
kv-indxdb = ["indxdb"]
|
||||
kv-yokudb = []
|
||||
scripting = ["boa", "gc"]
|
||||
|
||||
[dependencies]
|
||||
argon2 = "0.4.0"
|
||||
async-recursion = "1.0.0"
|
||||
bigdecimal = { version = "0.3.0", features = ["serde", "string-only"] }
|
||||
boa = { version = "0.14.0", package = "boa_engine", optional = true }
|
||||
channel = { version = "1.6.1", package = "async-channel" }
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
derive = { version = "0.1.2", package = "surrealdb-derive" }
|
||||
|
@ -25,6 +27,7 @@ echodb = { version = "0.3.0", optional = true }
|
|||
executor = { version = "1.4.1", package = "async-executor", optional = true }
|
||||
futures = "0.3.21"
|
||||
fuzzy-matcher = "0.3.7"
|
||||
gc = { version = "0.4.1", optional = true }
|
||||
geo = { version = "0.20.1", features = ["use-serde"] }
|
||||
indxdb = { version = "0.2.0", optional = true }
|
||||
lexical-sort = "0.3.1"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::err::Error;
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
|
||||
|
@ -16,6 +17,15 @@ impl fmt::Display for Reason {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Reason> for Error {
|
||||
fn from(reason: Reason) -> Self {
|
||||
match reason {
|
||||
Reason::Timedout => Error::QueryTimedout,
|
||||
Reason::Canceled => Error::QueryCancelled,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Reason> for io::Error {
|
||||
fn from(reason: Reason) -> Self {
|
||||
let kind = match reason {
|
||||
|
|
|
@ -1,10 +1,291 @@
|
|||
#![cfg(feature = "scripting")]
|
||||
|
||||
use crate::ctx::Context;
|
||||
use crate::err::Error;
|
||||
use crate::sql::script::Script;
|
||||
use crate::sql::array::Array;
|
||||
use crate::sql::datetime::Datetime;
|
||||
use crate::sql::duration::Duration;
|
||||
use crate::sql::number::Number;
|
||||
use crate::sql::object::Object;
|
||||
use crate::sql::thing::Thing;
|
||||
use crate::sql::value::Value;
|
||||
use bigdecimal::ToPrimitive;
|
||||
use boa::builtins::date::Date;
|
||||
use boa::class::Class;
|
||||
use boa::class::ClassBuilder;
|
||||
use boa::object::JsArray;
|
||||
use boa::object::JsObject;
|
||||
use boa::object::ObjectData;
|
||||
use boa::object::ObjectKind;
|
||||
use boa::property::Attribute;
|
||||
use boa::Context as Boa;
|
||||
use boa::JsResult;
|
||||
use boa::JsString;
|
||||
use boa::JsValue;
|
||||
use chrono::Datelike;
|
||||
use chrono::Timelike;
|
||||
use gc::Finalize;
|
||||
use gc::Trace;
|
||||
|
||||
pub fn run(_ctx: &Context, _expr: Script) -> Result<Value, Error> {
|
||||
Err(Error::InvalidScript {
|
||||
message: String::from("Embedded functions are not yet supported."),
|
||||
})
|
||||
#[derive(Debug, Trace, Finalize)]
|
||||
pub struct JsDuration {
|
||||
value: String,
|
||||
}
|
||||
|
||||
impl Class for JsDuration {
|
||||
const NAME: &'static str = "Duration";
|
||||
const LENGTH: usize = 1;
|
||||
fn constructor(_this: &JsValue, args: &[JsValue], ctx: &mut Boa) -> JsResult<Self> {
|
||||
Ok(JsDuration {
|
||||
value: args.get(0).cloned().unwrap_or_default().to_string(ctx)?.to_string(),
|
||||
})
|
||||
}
|
||||
fn init(class: &mut ClassBuilder) -> JsResult<()> {
|
||||
class.method("value", 0, |this, _, _| {
|
||||
if let Some(v) = this.as_object() {
|
||||
if let Some(v) = v.downcast_ref::<JsDuration>() {
|
||||
return Ok(v.value.clone().into());
|
||||
}
|
||||
}
|
||||
Ok(JsValue::Undefined)
|
||||
});
|
||||
class.method("toString", 0, |this, _, _| {
|
||||
if let Some(v) = this.as_object() {
|
||||
if let Some(v) = v.downcast_ref::<JsDuration>() {
|
||||
return Ok(v.value.clone().into());
|
||||
}
|
||||
}
|
||||
Ok(JsValue::Undefined)
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Trace, Finalize)]
|
||||
pub struct JsRecord {
|
||||
tb: String,
|
||||
id: String,
|
||||
}
|
||||
|
||||
impl Class for JsRecord {
|
||||
const NAME: &'static str = "Record";
|
||||
const LENGTH: usize = 2;
|
||||
fn constructor(_this: &JsValue, args: &[JsValue], ctx: &mut Boa) -> JsResult<Self> {
|
||||
Ok(JsRecord {
|
||||
tb: args.get(0).cloned().unwrap_or_default().to_string(ctx)?.to_string(),
|
||||
id: args.get(1).cloned().unwrap_or_default().to_string(ctx)?.to_string(),
|
||||
})
|
||||
}
|
||||
fn init(class: &mut ClassBuilder) -> JsResult<()> {
|
||||
class.method("tb", 0, |this, _, _| {
|
||||
if let Some(v) = this.as_object() {
|
||||
if let Some(v) = v.downcast_ref::<JsRecord>() {
|
||||
return Ok(v.tb.clone().into());
|
||||
}
|
||||
}
|
||||
Ok(JsValue::Undefined)
|
||||
});
|
||||
class.method("id", 0, |this, _, _| {
|
||||
if let Some(v) = this.as_object() {
|
||||
if let Some(v) = v.downcast_ref::<JsRecord>() {
|
||||
return Ok(v.id.clone().into());
|
||||
}
|
||||
}
|
||||
Ok(JsValue::Undefined)
|
||||
});
|
||||
class.method("toString", 0, |this, _, _| {
|
||||
if let Some(v) = this.as_object() {
|
||||
if let Some(v) = v.downcast_ref::<JsRecord>() {
|
||||
return Ok(format!("{}:{}", v.tb, v.id).into());
|
||||
}
|
||||
}
|
||||
Ok(JsValue::Undefined)
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(ctx: &Context, doc: Option<&Value>, src: &str) -> Result<Value, Error> {
|
||||
let _ = ctx.check()?;
|
||||
// Create an execution context
|
||||
let mut ctx = Boa::default();
|
||||
// Retrieve the current document
|
||||
let obj = doc.map_or(JsValue::Undefined, JsValue::from);
|
||||
// Create the main function structure
|
||||
let src = format!("(function() {{ {} }}).call(document)", src);
|
||||
// Register the current document as a global object
|
||||
ctx.register_global_property("document", obj, Attribute::default());
|
||||
// Register the JsDuration type as a global class
|
||||
ctx.register_global_class::<JsDuration>().unwrap();
|
||||
// Register the JsRecord type as a global class
|
||||
ctx.register_global_class::<JsRecord>().unwrap();
|
||||
// Attempt to execute the script
|
||||
match ctx.eval(src.as_bytes()) {
|
||||
// The script executed successfully
|
||||
Ok(ref v) => Ok(v.into()),
|
||||
// There was an error running the script
|
||||
Err(e) => Err(Error::InvalidScript {
|
||||
message: e.display().to_string(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Datetime> for Date {
|
||||
fn from(v: &Datetime) -> Self {
|
||||
let mut obj = Self::default();
|
||||
obj.set_components(
|
||||
true,
|
||||
Some(v.year() as f64),
|
||||
Some(v.month0() as f64),
|
||||
Some(v.day() as f64),
|
||||
Some(v.hour() as f64),
|
||||
Some(v.minute() as f64),
|
||||
Some(v.second() as f64),
|
||||
Some((v.nanosecond() / 1_000_000) as f64),
|
||||
);
|
||||
obj
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Value> for JsValue {
|
||||
fn from(v: &Value) -> Self {
|
||||
match v {
|
||||
Value::Null => JsValue::Null,
|
||||
Value::Void => JsValue::Undefined,
|
||||
Value::None => JsValue::Undefined,
|
||||
Value::True => JsValue::Boolean(true),
|
||||
Value::False => JsValue::Boolean(false),
|
||||
Value::Strand(v) => JsValue::String(v.as_str().into()),
|
||||
Value::Number(Number::Int(v)) => JsValue::Integer(*v as i32),
|
||||
Value::Number(Number::Float(v)) => JsValue::Rational(*v as f64),
|
||||
Value::Number(Number::Decimal(v)) => match v.is_integer() {
|
||||
true => JsValue::BigInt(v.to_i64().unwrap_or_default().into()),
|
||||
false => JsValue::Rational(v.to_f64().unwrap_or_default()),
|
||||
},
|
||||
Value::Datetime(v) => JsValue::from(JsObject::from_proto_and_data(
|
||||
Boa::default().intrinsics().constructors().object().prototype(),
|
||||
ObjectData::date(v.into()),
|
||||
)),
|
||||
Value::Duration(v) => JsValue::from(JsObject::from_proto_and_data(
|
||||
Boa::default().intrinsics().constructors().object().prototype(),
|
||||
ObjectData::native_object(Box::new(JsDuration {
|
||||
value: v.to_string(),
|
||||
})),
|
||||
)),
|
||||
Value::Thing(v) => JsValue::from(JsObject::from_proto_and_data(
|
||||
Boa::default().intrinsics().constructors().object().prototype(),
|
||||
ObjectData::native_object(Box::new(JsRecord {
|
||||
tb: v.tb.to_string(),
|
||||
id: v.id.to_string(),
|
||||
})),
|
||||
)),
|
||||
Value::Array(v) => {
|
||||
let ctx = &mut Boa::default();
|
||||
let arr = JsArray::new(ctx);
|
||||
for v in v.iter() {
|
||||
arr.push(JsValue::from(v), ctx).unwrap();
|
||||
}
|
||||
JsValue::from(arr)
|
||||
}
|
||||
Value::Object(v) => {
|
||||
let ctx = &mut Boa::default();
|
||||
let obj = JsObject::default();
|
||||
for (k, v) in v.iter() {
|
||||
let k = JsString::from(k.as_str());
|
||||
let v = JsValue::from(v);
|
||||
obj.set(k, v, true, ctx).unwrap();
|
||||
}
|
||||
JsValue::from(obj)
|
||||
}
|
||||
_ => JsValue::Null,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&JsValue> for Value {
|
||||
fn from(v: &JsValue) -> Self {
|
||||
match v {
|
||||
JsValue::Null => Value::Null,
|
||||
JsValue::Undefined => Value::None,
|
||||
JsValue::Boolean(v) => Value::from(*v),
|
||||
JsValue::String(v) => Value::from(v.as_str()),
|
||||
JsValue::Integer(v) => Value::from(Number::Int(*v as i64)),
|
||||
JsValue::Rational(v) => Value::from(Number::Float(*v as f64)),
|
||||
JsValue::BigInt(v) => Value::from(Number::from(v.clone().to_string())),
|
||||
JsValue::Object(v) => {
|
||||
// Check to see if this object is a duration
|
||||
if v.is::<JsDuration>() {
|
||||
if let Some(v) = v.downcast_ref::<JsDuration>() {
|
||||
let v = v.value.clone();
|
||||
return Duration::from(v).into();
|
||||
}
|
||||
}
|
||||
// Check to see if this object is a record
|
||||
if v.is::<JsRecord>() {
|
||||
if let Some(v) = v.downcast_ref::<JsRecord>() {
|
||||
let v = (v.tb.clone(), v.id.clone());
|
||||
return Thing::from(v).into();
|
||||
}
|
||||
}
|
||||
// Check to see if this object is a date
|
||||
if let Some(v) = v.borrow().as_date() {
|
||||
if let Some(v) = v.to_utc() {
|
||||
return Datetime::from(v).into();
|
||||
}
|
||||
}
|
||||
// Get a borrowed reference to the object
|
||||
let o = v.borrow();
|
||||
// Check to see if this is a normal type
|
||||
match o.kind() {
|
||||
// This object is a Javascript Array
|
||||
ObjectKind::Array => {
|
||||
let mut x = Array::default();
|
||||
let ctx = &mut Boa::default();
|
||||
let len = v.get("length", ctx).unwrap().to_u32(ctx).unwrap();
|
||||
for i in 0..len {
|
||||
let v = o.properties().get(&i.into()).unwrap();
|
||||
if let Some(v) = v.value() {
|
||||
let v = Value::from(v);
|
||||
x.push(v);
|
||||
}
|
||||
}
|
||||
x.into()
|
||||
}
|
||||
// This object is a Javascript Object
|
||||
ObjectKind::Ordinary => {
|
||||
let mut x = Object::default();
|
||||
for (k, v) in o.properties().iter() {
|
||||
if let Some(v) = v.value() {
|
||||
let k = k.to_string();
|
||||
let v = Value::from(v);
|
||||
x.insert(k, v);
|
||||
}
|
||||
}
|
||||
x.into()
|
||||
}
|
||||
// This object is a Javascript Map
|
||||
ObjectKind::Map(v) => {
|
||||
let mut x = Object::default();
|
||||
for (k, v) in v.iter() {
|
||||
let k = Value::from(k).as_string();
|
||||
let v = Value::from(v);
|
||||
x.insert(k, v);
|
||||
}
|
||||
x.into()
|
||||
}
|
||||
// This object is a Javascript Set
|
||||
ObjectKind::Set(v) => {
|
||||
let mut x = Array::default();
|
||||
for v in v.iter() {
|
||||
let v = Value::from(v);
|
||||
x.push(v);
|
||||
}
|
||||
x.into()
|
||||
}
|
||||
_ => Value::Null,
|
||||
}
|
||||
}
|
||||
_ => Value::Null,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,6 +27,15 @@ impl From<time::Duration> for Duration {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<String> for Duration {
|
||||
fn from(s: String) -> Self {
|
||||
match duration(s.as_ref()) {
|
||||
Ok((_, v)) => v,
|
||||
Err(_) => Duration::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Duration {
|
||||
fn from(s: &str) -> Self {
|
||||
match duration(s) {
|
||||
|
|
|
@ -5,7 +5,6 @@ use thiserror::Error;
|
|||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum Error<I> {
|
||||
ScriptError(String),
|
||||
ParserError(I),
|
||||
}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ use crate::sql::value::{single, value, Value};
|
|||
use nom::branch::alt;
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::character::complete::char;
|
||||
use nom::combinator::opt;
|
||||
use nom::multi::separated_list0;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::cmp::Ordering;
|
||||
|
@ -110,27 +111,32 @@ impl Function {
|
|||
doc: Option<&Value>,
|
||||
) -> Result<Value, Error> {
|
||||
match self {
|
||||
Function::Future(ref e) => match opt.futures {
|
||||
Function::Cast(s, e) => {
|
||||
let a = e.compute(ctx, opt, txn, doc).await?;
|
||||
fnc::cast::run(ctx, s, a)
|
||||
}
|
||||
Function::Normal(s, e) => {
|
||||
let mut a: Vec<Value> = Vec::with_capacity(e.len());
|
||||
for v in e {
|
||||
a.push(v.compute(ctx, opt, txn, doc).await?);
|
||||
}
|
||||
fnc::run(ctx, s, a).await
|
||||
}
|
||||
Function::Future(e) => match opt.futures {
|
||||
true => {
|
||||
let a = e.compute(ctx, opt, txn, doc).await?;
|
||||
fnc::future::run(ctx, a)
|
||||
}
|
||||
false => Ok(self.to_owned().into()),
|
||||
},
|
||||
Function::Script(ref s) => {
|
||||
let a = s.to_owned();
|
||||
fnc::script::run(ctx, a)
|
||||
}
|
||||
Function::Cast(ref s, ref e) => {
|
||||
let a = e.compute(ctx, opt, txn, doc).await?;
|
||||
fnc::cast::run(ctx, s, a)
|
||||
}
|
||||
Function::Normal(ref s, ref e) => {
|
||||
let mut a: Vec<Value> = Vec::with_capacity(e.len());
|
||||
for v in e {
|
||||
a.push(v.compute(ctx, opt, txn, doc).await?);
|
||||
}
|
||||
fnc::run(ctx, s, a).await
|
||||
#[allow(unused_variables)]
|
||||
Function::Script(s) => {
|
||||
#[cfg(feature = "scripting")]
|
||||
return fnc::script::run(ctx, doc, s);
|
||||
#[cfg(not(feature = "scripting"))]
|
||||
return Err(Error::InvalidScript {
|
||||
message: String::from("Embedded functions are not enabled."),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -140,7 +146,7 @@ impl fmt::Display for Function {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Function::Future(ref e) => write!(f, "fn::future -> {{ {} }}", e),
|
||||
Function::Script(ref s) => write!(f, "fn::script -> {{ {} }}", s),
|
||||
Function::Script(ref s) => write!(f, "fn::script -> {{{}}}", s),
|
||||
Function::Cast(ref s, ref e) => write!(f, "<{}> {}", s, e),
|
||||
Function::Normal(ref s, ref e) => write!(
|
||||
f,
|
||||
|
@ -153,7 +159,21 @@ impl fmt::Display for Function {
|
|||
}
|
||||
|
||||
pub fn function(i: &str) -> IResult<&str, Function> {
|
||||
alt((casts, embed, future, normal))(i)
|
||||
alt((scripts, future, casts, normal))(i)
|
||||
}
|
||||
|
||||
fn scripts(i: &str) -> IResult<&str, Function> {
|
||||
let (i, _) = tag("fn::script")(i)?;
|
||||
let (i, _) = mightbespace(i)?;
|
||||
let (i, _) = char('-')(i)?;
|
||||
let (i, _) = char('>')(i)?;
|
||||
let (i, _) = mightbespace(i)?;
|
||||
let (i, _) = opt(tag("function()"))(i)?;
|
||||
let (i, _) = mightbespace(i)?;
|
||||
let (i, _) = char('{')(i)?;
|
||||
let (i, v) = script(i)?;
|
||||
let (i, _) = char('}')(i)?;
|
||||
Ok((i, Function::Script(v)))
|
||||
}
|
||||
|
||||
fn future(i: &str) -> IResult<&str, Function> {
|
||||
|
@ -170,20 +190,6 @@ fn future(i: &str) -> IResult<&str, Function> {
|
|||
Ok((i, Function::Future(v)))
|
||||
}
|
||||
|
||||
fn embed(i: &str) -> IResult<&str, Function> {
|
||||
let (i, _) = tag("fn::script")(i)?;
|
||||
let (i, _) = mightbespace(i)?;
|
||||
let (i, _) = char('-')(i)?;
|
||||
let (i, _) = char('>')(i)?;
|
||||
let (i, _) = mightbespace(i)?;
|
||||
let (i, _) = char('{')(i)?;
|
||||
let (i, _) = mightbespace(i)?;
|
||||
let (i, v) = script(i)?;
|
||||
let (i, _) = mightbespace(i)?;
|
||||
let (i, _) = char('}')(i)?;
|
||||
Ok((i, Function::Script(v)))
|
||||
}
|
||||
|
||||
fn casts(i: &str) -> IResult<&str, Function> {
|
||||
let (i, _) = char('<')(i)?;
|
||||
let (i, s) = function_casts(i)?;
|
||||
|
@ -486,4 +492,22 @@ mod tests {
|
|||
assert_eq!("fn::future -> { 1.2345 + 5.4321 }", format!("{}", out));
|
||||
assert_eq!(out, Function::Future(Value::from(Expression::parse("1.2345 + 5.4321"))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn function_script_expression() {
|
||||
let sql = "fn::script -> { return this.tags.filter(t => { return t.length > 3; }); }";
|
||||
let res = function(sql);
|
||||
assert!(res.is_ok());
|
||||
let out = res.unwrap().1;
|
||||
assert_eq!(
|
||||
"fn::script -> { return this.tags.filter(t => { return t.length > 3; }); }",
|
||||
format!("{}", out)
|
||||
);
|
||||
assert_eq!(
|
||||
out,
|
||||
Function::Script(Script::parse(
|
||||
" return this.tags.filter(t => { return t.length > 3; }); "
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use crate::err::Error;
|
||||
use crate::sql::error::Error::ParserError;
|
||||
use crate::sql::error::Error::ScriptError;
|
||||
use crate::sql::query::{query, Query};
|
||||
use crate::sql::value::{json as value, Value};
|
||||
use nom::Err;
|
||||
|
@ -20,9 +19,6 @@ pub fn parse(input: &str) -> Result<Query, Error> {
|
|||
sql: s.to_string(),
|
||||
})
|
||||
}
|
||||
ScriptError(e) => Err(Error::InvalidScript {
|
||||
message: e,
|
||||
}),
|
||||
},
|
||||
Err(Err::Failure(e)) => match e {
|
||||
ParserError(e) => {
|
||||
|
@ -33,9 +29,6 @@ pub fn parse(input: &str) -> Result<Query, Error> {
|
|||
sql: s.to_string(),
|
||||
})
|
||||
}
|
||||
ScriptError(e) => Err(Error::InvalidScript {
|
||||
message: e,
|
||||
}),
|
||||
},
|
||||
_ => unreachable!(),
|
||||
},
|
||||
|
@ -56,9 +49,6 @@ pub fn json(input: &str) -> Result<Value, Error> {
|
|||
sql: s.to_string(),
|
||||
})
|
||||
}
|
||||
ScriptError(e) => Err(Error::InvalidScript {
|
||||
message: e,
|
||||
}),
|
||||
},
|
||||
Err(Err::Failure(e)) => match e {
|
||||
ParserError(e) => {
|
||||
|
@ -69,9 +59,6 @@ pub fn json(input: &str) -> Result<Value, Error> {
|
|||
sql: s.to_string(),
|
||||
})
|
||||
}
|
||||
ScriptError(e) => Err(Error::InvalidScript {
|
||||
message: e,
|
||||
}),
|
||||
},
|
||||
_ => unreachable!(),
|
||||
},
|
||||
|
|
|
@ -1,12 +1,43 @@
|
|||
use crate::sql::error::IResult;
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::escaped;
|
||||
use nom::bytes::complete::is_not;
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::character::complete::one_of;
|
||||
use nom::combinator::recognize;
|
||||
use nom::multi::many1;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use std::ops::Deref;
|
||||
use std::str;
|
||||
|
||||
const SINGLE: &str = r#"'"#;
|
||||
const SINGLE_ESC: &str = r#"\'"#;
|
||||
|
||||
const DOUBLE: &str = r#"""#;
|
||||
const DOUBLE_ESC: &str = r#"\""#;
|
||||
|
||||
const BACKTICK: &str = r#"`"#;
|
||||
const BACKTICK_ESC: &str = r#"\`"#;
|
||||
|
||||
const OBJECT_BEG: &str = "{";
|
||||
const OBJECT_END: &str = "}";
|
||||
|
||||
#[derive(Clone, Debug, Default, Eq, PartialEq, PartialOrd, Serialize, Deserialize)]
|
||||
pub struct Script(pub String);
|
||||
|
||||
impl From<String> for Script {
|
||||
fn from(s: String) -> Self {
|
||||
Script(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a str> for Script {
|
||||
fn from(s: &str) -> Self {
|
||||
Script(String::from(s))
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Script {
|
||||
type Target = String;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
|
@ -16,10 +47,107 @@ impl Deref for Script {
|
|||
|
||||
impl fmt::Display for Script {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "\"{}\"", self.0)
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn script(_: &str) -> IResult<&str, Script> {
|
||||
unimplemented!()
|
||||
pub fn script(i: &str) -> IResult<&str, Script> {
|
||||
let (i, v) = recognize(script_raw)(i)?;
|
||||
Ok((i, Script(String::from(v))))
|
||||
}
|
||||
|
||||
pub fn script_raw(i: &str) -> IResult<&str, &str> {
|
||||
recognize(many1(alt((char_any, char_object, string_single, string_double, string_backtick))))(i)
|
||||
}
|
||||
|
||||
fn char_any(i: &str) -> IResult<&str, &str> {
|
||||
is_not("{}'`\"")(i)
|
||||
}
|
||||
|
||||
fn char_object(i: &str) -> IResult<&str, &str> {
|
||||
let (i, _) = tag(OBJECT_BEG)(i)?;
|
||||
let (i, v) = script_raw(i)?;
|
||||
let (i, _) = tag(OBJECT_END)(i)?;
|
||||
Ok((i, v))
|
||||
}
|
||||
|
||||
fn string_single(i: &str) -> IResult<&str, &str> {
|
||||
let (i, _) = tag(SINGLE)(i)?;
|
||||
let (i, v) = alt((escaped(is_not(SINGLE_ESC), '\\', one_of(SINGLE)), tag("")))(i)?;
|
||||
let (i, _) = tag(SINGLE)(i)?;
|
||||
Ok((i, v))
|
||||
}
|
||||
|
||||
fn string_double(i: &str) -> IResult<&str, &str> {
|
||||
let (i, _) = tag(DOUBLE)(i)?;
|
||||
let (i, v) = alt((escaped(is_not(DOUBLE_ESC), '\\', one_of(DOUBLE)), tag("")))(i)?;
|
||||
let (i, _) = tag(DOUBLE)(i)?;
|
||||
Ok((i, v))
|
||||
}
|
||||
|
||||
fn string_backtick(i: &str) -> IResult<&str, &str> {
|
||||
let (i, _) = tag(BACKTICK)(i)?;
|
||||
let (i, v) = alt((escaped(is_not(BACKTICK_ESC), '\\', one_of(BACKTICK)), tag("")))(i)?;
|
||||
let (i, _) = tag(BACKTICK)(i)?;
|
||||
Ok((i, v))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn script_basic() {
|
||||
let sql = "return true;";
|
||||
let res = script(sql);
|
||||
assert!(res.is_ok());
|
||||
let out = res.unwrap().1;
|
||||
assert_eq!("return true;", format!("{}", out));
|
||||
assert_eq!(out, Script::from("return true;"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_object() {
|
||||
let sql = "return { test: true, something: { other: true } };";
|
||||
let res = script(sql);
|
||||
assert!(res.is_ok());
|
||||
let out = res.unwrap().1;
|
||||
assert_eq!("return { test: true, something: { other: true } };", format!("{}", out));
|
||||
assert_eq!(out, Script::from("return { test: true, something: { other: true } };"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_closure() {
|
||||
let sql = "return this.values.map(v => `This value is ${Number(v * 3)}`);";
|
||||
let res = script(sql);
|
||||
assert!(res.is_ok());
|
||||
let out = res.unwrap().1;
|
||||
assert_eq!(
|
||||
"return this.values.map(v => `This value is ${Number(v * 3)}`);",
|
||||
format!("{}", out)
|
||||
);
|
||||
assert_eq!(
|
||||
out,
|
||||
Script::from("return this.values.map(v => `This value is ${Number(v * 3)}`);")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn script_complex() {
|
||||
let sql = r#"return { test: true, some: { object: "some text with uneven {{{ {} \" brackets", else: false } };"#;
|
||||
let res = script(sql);
|
||||
assert!(res.is_ok());
|
||||
let out = res.unwrap().1;
|
||||
assert_eq!(
|
||||
r#"return { test: true, some: { object: "some text with uneven {{{ {} \" brackets", else: false } };"#,
|
||||
format!("{}", out)
|
||||
);
|
||||
assert_eq!(
|
||||
out,
|
||||
Script::from(
|
||||
r#"return { test: true, some: { object: "some text with uneven {{{ {} \" brackets", else: false } };"#
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue