From f3de9095ae6492ad5fe4f4e0bd74d78e47f3e618 Mon Sep 17 00:00:00 2001 From: Tobie Morgan Hitchcock Date: Thu, 13 Jan 2022 17:37:46 +0000 Subject: [PATCH] Add further SQL function implementations --- src/fnc/args.rs | 91 ++++++ src/fnc/array.rs | 73 +++++ src/fnc/cast.rs | 64 +++-- src/fnc/count.rs | 17 ++ src/fnc/crypto.rs | 135 +++++++++ src/fnc/future.rs | 6 +- src/fnc/geo.rs | 116 ++++++++ src/fnc/http.rs | 27 ++ src/fnc/is.rs | 81 ++++++ src/fnc/math.rs | 178 ++++++++++++ src/fnc/mod.rs | 163 ++++++++++- src/fnc/operate.rs | 444 ++++++++++------------------- src/fnc/parse.rs | 144 ++++++++++ src/fnc/rand.rs | 140 +++++++++ src/fnc/script.rs | 10 + src/fnc/string.rs | 99 +++++++ src/fnc/time.rs | 154 ++++++++++ src/fnc/type.rs | 106 +++++++ src/fnc/util/geo/mod.rs | 101 +++++++ src/fnc/util/http/mod.rs | 1 + src/fnc/util/math/bottom.rs | 11 + src/fnc/util/math/deviation.rs | 11 + src/fnc/util/math/interquartile.rs | 11 + src/fnc/util/math/mean.rs | 13 + src/fnc/util/math/median.rs | 12 + src/fnc/util/math/midhinge.rs | 11 + src/fnc/util/math/mod.rs | 18 ++ src/fnc/util/math/mode.rs | 11 + src/fnc/util/math/nearestrank.rs | 11 + src/fnc/util/math/percentile.rs | 11 + src/fnc/util/math/quartile.rs | 11 + src/fnc/util/math/spread.rs | 11 + src/fnc/util/math/top.rs | 11 + src/fnc/util/math/trimean.rs | 11 + src/fnc/util/math/variance.rs | 11 + src/fnc/util/mod.rs | 3 + 36 files changed, 2006 insertions(+), 322 deletions(-) create mode 100644 src/fnc/args.rs create mode 100644 src/fnc/array.rs create mode 100644 src/fnc/count.rs create mode 100644 src/fnc/crypto.rs create mode 100644 src/fnc/geo.rs create mode 100644 src/fnc/http.rs create mode 100644 src/fnc/is.rs create mode 100644 src/fnc/math.rs create mode 100644 src/fnc/parse.rs create mode 100644 src/fnc/rand.rs create mode 100644 src/fnc/script.rs create mode 100644 src/fnc/string.rs create mode 100644 src/fnc/time.rs create mode 100644 src/fnc/type.rs create mode 100644 src/fnc/util/geo/mod.rs create mode 100644 src/fnc/util/http/mod.rs create mode 100644 src/fnc/util/math/bottom.rs create mode 100644 src/fnc/util/math/deviation.rs create mode 100644 src/fnc/util/math/interquartile.rs create mode 100644 src/fnc/util/math/mean.rs create mode 100644 src/fnc/util/math/median.rs create mode 100644 src/fnc/util/math/midhinge.rs create mode 100644 src/fnc/util/math/mod.rs create mode 100644 src/fnc/util/math/mode.rs create mode 100644 src/fnc/util/math/nearestrank.rs create mode 100644 src/fnc/util/math/percentile.rs create mode 100644 src/fnc/util/math/quartile.rs create mode 100644 src/fnc/util/math/spread.rs create mode 100644 src/fnc/util/math/top.rs create mode 100644 src/fnc/util/math/trimean.rs create mode 100644 src/fnc/util/math/variance.rs create mode 100644 src/fnc/util/mod.rs diff --git a/src/fnc/args.rs b/src/fnc/args.rs new file mode 100644 index 00000000..a4b6346a --- /dev/null +++ b/src/fnc/args.rs @@ -0,0 +1,91 @@ +use crate::dbs::Runtime; +use crate::err::Error; +use crate::sql::value::Value; + +pub enum Args { + None, + Any, + One, + Two, + Three, + NoneOne, + NoneTwo, + NoneOneTwo, + OneTwo, + OneTwoThree, +} + +pub fn check( + ctx: &Runtime, + name: &String, + args: Vec, + size: Args, + func: fn(&Runtime, Vec) -> Result, +) -> Result { + match size { + Args::None => match args.len() { + 0 => func(ctx, args), + _ => Err(Error::ArgumentsError { + name: name.to_owned(), + message: String::from("The function does not expect any arguments."), + }), + }, + Args::One => match args.len() { + 1 => func(ctx, args), + _ => Err(Error::ArgumentsError { + name: name.to_owned(), + message: String::from("The function expects 1 argument."), + }), + }, + Args::Two => match args.len() { + 2 => func(ctx, args), + _ => Err(Error::ArgumentsError { + name: name.to_owned(), + message: String::from("The function expects 2 arguments."), + }), + }, + Args::Three => match args.len() { + 3 => func(ctx, args), + _ => Err(Error::ArgumentsError { + name: name.to_owned(), + message: String::from("The function expects 3 arguments."), + }), + }, + Args::NoneOne => match args.len() { + 0 | 1 => func(ctx, args), + _ => Err(Error::ArgumentsError { + name: name.to_owned(), + message: String::from("The function expects 0 or 1 arguments."), + }), + }, + Args::NoneTwo => match args.len() { + 0 | 2 => func(ctx, args), + _ => Err(Error::ArgumentsError { + name: name.to_owned(), + message: String::from("The function expects 0 or 2 arguments."), + }), + }, + Args::NoneOneTwo => match args.len() { + 0 | 1 | 2 => func(ctx, args), + _ => Err(Error::ArgumentsError { + name: name.to_owned(), + message: String::from("The function expects 0, 1, or 2 arguments."), + }), + }, + Args::OneTwo => match args.len() { + 1 | 2 => func(ctx, args), + _ => Err(Error::ArgumentsError { + name: name.to_owned(), + message: String::from("The function expects 1 or 2 arguments."), + }), + }, + Args::OneTwoThree => match args.len() { + 1 | 2 | 3 => func(ctx, args), + _ => Err(Error::ArgumentsError { + name: name.to_owned(), + message: String::from("The function expects 1, 2, or 3 arguments."), + }), + }, + Args::Any => func(ctx, args), + } +} diff --git a/src/fnc/array.rs b/src/fnc/array.rs new file mode 100644 index 00000000..4465e818 --- /dev/null +++ b/src/fnc/array.rs @@ -0,0 +1,73 @@ +use crate::dbs::Runtime; +use crate::err::Error; +use crate::sql::array::Combine; +use crate::sql::array::Concat; +use crate::sql::array::Difference; +use crate::sql::array::Intersect; +use crate::sql::array::Union; +use crate::sql::array::Uniq; +use crate::sql::value::Value; + +pub fn concat(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Array(v) => match args.remove(0) { + Value::Array(w) => Ok(v.value.concat(w.value).into()), + _ => Ok(Value::None), + }, + _ => Ok(Value::None), + } +} + +pub fn combine(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Array(v) => match args.remove(0) { + Value::Array(w) => Ok(v.value.combine(w.value).into()), + _ => Ok(Value::None), + }, + _ => Ok(Value::None), + } +} + +pub fn difference(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Array(v) => match args.remove(0) { + Value::Array(w) => Ok(v.value.difference(w.value).into()), + _ => Ok(Value::None), + }, + _ => Ok(Value::None), + } +} + +pub fn distinct(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Array(v) => Ok(v.value.uniq().into()), + _ => Ok(Value::None), + } +} + +pub fn intersect(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Array(v) => match args.remove(0) { + Value::Array(w) => Ok(v.value.intersect(w.value).into()), + _ => Ok(Value::None), + }, + _ => Ok(Value::None), + } +} + +pub fn len(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Array(v) => Ok(v.value.len().into()), + _ => Ok(Value::None), + } +} + +pub fn union(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Array(v) => match args.remove(0) { + Value::Array(w) => Ok(v.value.union(w.value).into()), + _ => Ok(Value::None), + }, + _ => Ok(Value::None), + } +} diff --git a/src/fnc/cast.rs b/src/fnc/cast.rs index 3ffe7944..63e949e4 100644 --- a/src/fnc/cast.rs +++ b/src/fnc/cast.rs @@ -1,48 +1,74 @@ use crate::dbs::Runtime; use crate::err::Error; -use crate::sql::literal::Literal; +use crate::sql::number::Number; +use crate::sql::value::Value; -pub fn run(ctx: &Runtime, name: &String, val: Literal) -> Result { +pub fn run(ctx: &Runtime, name: &String, val: Value) -> Result { match name.as_str() { "bool" => bool(ctx, val), "int" => int(ctx, val), "float" => float(ctx, val), "string" => string(ctx, val), "number" => number(ctx, val), - "decimal" => number(ctx, val), + "decimal" => decimal(ctx, val), "datetime" => datetime(ctx, val), "duration" => duration(ctx, val), _ => Ok(val), } } -pub fn bool(ctx: &Runtime, val: Literal) -> Result { - match val.as_bool() { - true => Ok(Literal::True), - false => Ok(Literal::False), +pub fn bool(_: &Runtime, val: Value) -> Result { + match val.is_truthy() { + true => Ok(Value::True), + false => Ok(Value::False), } } -pub fn int(ctx: &Runtime, val: Literal) -> Result { - Ok(Literal::Int(val.as_int())) +pub fn int(_: &Runtime, val: Value) -> Result { + match val { + Value::Number(Number::Int(_)) => Ok(val), + _ => Ok(Value::Number(Number::Int(val.as_int()))), + } } -pub fn float(ctx: &Runtime, val: Literal) -> Result { - Ok(Literal::Float(val.as_float())) +pub fn float(_: &Runtime, val: Value) -> Result { + match val { + Value::Number(Number::Float(_)) => Ok(val), + _ => Ok(Value::Number(Number::Float(val.as_float()))), + } } -pub fn string(ctx: &Runtime, val: Literal) -> Result { - Ok(Literal::Strand(val.as_strand())) +pub fn number(_: &Runtime, val: Value) -> Result { + match val { + Value::Number(Number::Decimal(_)) => Ok(val), + _ => Ok(Value::Number(Number::Decimal(val.as_decimal()))), + } } -pub fn number(ctx: &Runtime, val: Literal) -> Result { - Ok(Literal::Number(val.as_number())) +pub fn decimal(_: &Runtime, val: Value) -> Result { + match val { + Value::Number(Number::Decimal(_)) => Ok(val), + _ => Ok(Value::Number(Number::Decimal(val.as_decimal()))), + } } -pub fn datetime(ctx: &Runtime, val: Literal) -> Result { - Ok(Literal::Datetime(val.as_datetime())) +pub fn string(_: &Runtime, val: Value) -> Result { + match val { + Value::Strand(_) => Ok(val), + _ => Ok(Value::Strand(val.as_strand())), + } } -pub fn duration(ctx: &Runtime, val: Literal) -> Result { - Ok(Literal::Duration(val.as_duration())) +pub fn datetime(_: &Runtime, val: Value) -> Result { + match val { + Value::Datetime(_) => Ok(val), + _ => Ok(Value::Datetime(val.as_datetime())), + } +} + +pub fn duration(_: &Runtime, val: Value) -> Result { + match val { + Value::Duration(_) => Ok(val), + _ => Ok(Value::Duration(val.as_duration())), + } } diff --git a/src/fnc/count.rs b/src/fnc/count.rs new file mode 100644 index 00000000..b45c6640 --- /dev/null +++ b/src/fnc/count.rs @@ -0,0 +1,17 @@ +use crate::dbs::Runtime; +use crate::err::Error; +use crate::sql::value::Value; + +pub fn count(_: &Runtime, mut args: Vec) -> Result { + match args.len() { + 1 => match args.remove(0) { + Value::Array(v) => Ok(v.value.iter().filter(|v| v.is_truthy()).count().into()), + v => match v.is_truthy() { + true => Ok(1.into()), + false => Ok(0.into()), + }, + }, + 0 => Ok(1.into()), + _ => unreachable!(), + } +} diff --git a/src/fnc/crypto.rs b/src/fnc/crypto.rs new file mode 100644 index 00000000..29fa69bf --- /dev/null +++ b/src/fnc/crypto.rs @@ -0,0 +1,135 @@ +use crate::dbs::Runtime; +use crate::err::Error; +use crate::sql::value::Value; +use md5::Digest; +use md5::Md5; +use sha1::Sha1; +use sha2::Sha256; +use sha2::Sha512; + +pub fn md5(_: &Runtime, mut args: Vec) -> Result { + let mut hasher = Md5::new(); + hasher.update(args.remove(0).as_strand().as_str()); + let val = hasher.finalize(); + let val = format!("{:x}", val); + Ok(val.into()) +} + +pub fn sha1(_: &Runtime, mut args: Vec) -> Result { + let mut hasher = Sha1::new(); + hasher.update(args.remove(0).as_strand().as_str()); + let val = hasher.finalize(); + let val = format!("{:x}", val); + Ok(val.into()) +} + +pub fn sha256(_: &Runtime, mut args: Vec) -> Result { + let mut hasher = Sha256::new(); + hasher.update(args.remove(0).as_strand().as_str()); + let val = hasher.finalize(); + let val = format!("{:x}", val); + Ok(val.into()) +} + +pub fn sha512(_: &Runtime, mut args: Vec) -> Result { + let mut hasher = Sha512::new(); + hasher.update(args.remove(0).as_strand().as_str()); + let val = hasher.finalize(); + let val = format!("{:x}", val); + Ok(val.into()) +} + +pub mod argon2 { + + use crate::dbs::Runtime; + use crate::err::Error; + use crate::sql::value::Value; + use argon2::{ + password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, + Argon2, + }; + use rand::rngs::OsRng; + + pub fn cmp(_: &Runtime, mut args: Vec) -> Result { + let algo = Argon2::default(); + let hash = args.remove(0).as_strand().value; + let pass = args.remove(0).as_strand().value; + let test = PasswordHash::new(&hash).unwrap(); + Ok(algo.verify_password(pass.as_ref(), &test).is_ok().into()) + } + + pub fn gen(_: &Runtime, mut args: Vec) -> Result { + let algo = Argon2::default(); + let pass = args.remove(0).as_strand().value; + let salt = SaltString::generate(&mut OsRng); + let hash = algo.hash_password(pass.as_ref(), salt.as_ref()).unwrap().to_string(); + Ok(hash.into()) + } +} + +pub mod bcrypt { + + use crate::dbs::Runtime; + use crate::err::Error; + use crate::sql::value::Value; + + pub fn cmp(_: &Runtime, _args: Vec) -> Result { + todo!() + } + + pub fn gen(_: &Runtime, _args: Vec) -> Result { + todo!() + } +} + +pub mod pbkdf2 { + + use crate::dbs::Runtime; + use crate::err::Error; + use crate::sql::value::Value; + use pbkdf2::{ + password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, + Pbkdf2, + }; + use rand::rngs::OsRng; + + pub fn cmp(_: &Runtime, mut args: Vec) -> Result { + let hash = args.remove(0).as_strand().value; + let pass = args.remove(0).as_strand().value; + let test = PasswordHash::new(&hash).unwrap(); + Ok(Pbkdf2.verify_password(pass.as_ref(), &test).is_ok().into()) + } + + pub fn gen(_: &Runtime, mut args: Vec) -> Result { + let pass = args.remove(0).as_strand().value; + let salt = SaltString::generate(&mut OsRng); + let hash = Pbkdf2.hash_password(pass.as_ref(), salt.as_ref()).unwrap().to_string(); + Ok(hash.into()) + } +} + +pub mod scrypt { + + use crate::dbs::Runtime; + use crate::err::Error; + use crate::sql::value::Value; + use rand::rngs::OsRng; + use scrypt::{ + password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, + Scrypt, + }; + + pub fn cmp(_: &Runtime, mut args: Vec) -> Result { + let hash = args.remove(0).as_strand().value; + let pass = args.remove(0).as_strand().value; + let test = PasswordHash::new(&hash).unwrap(); + Ok(Scrypt.verify_password(pass.as_ref(), &test).is_ok().into()) + } + + pub fn gen(_: &Runtime, mut args: Vec) -> Result { + let pass = args.remove(0).as_strand().value; + let salt = SaltString::generate(&mut OsRng); + let hash = Scrypt.hash_password(pass.as_ref(), salt.as_ref()).unwrap().to_string(); + Ok(hash.into()) + } +} diff --git a/src/fnc/future.rs b/src/fnc/future.rs index 6a8c8f7c..7f44fd38 100644 --- a/src/fnc/future.rs +++ b/src/fnc/future.rs @@ -1,7 +1,7 @@ use crate::dbs::Runtime; use crate::err::Error; -use crate::sql::literal::Literal; +use crate::sql::value::Value; -pub fn run(ctx: &Runtime, args: Literal) -> Result { - todo!() +pub fn run(_: &Runtime, expr: Value) -> Result { + Ok(expr) } diff --git a/src/fnc/geo.rs b/src/fnc/geo.rs new file mode 100644 index 00000000..30168b2f --- /dev/null +++ b/src/fnc/geo.rs @@ -0,0 +1,116 @@ +use crate::dbs::Runtime; +use crate::err::Error; +use crate::sql::geometry::Geometry; +use crate::sql::value::Value; +use geo::algorithm::area::Area; +use geo::algorithm::bearing::Bearing; +use geo::algorithm::centroid::Centroid; +use geo::algorithm::haversine_distance::HaversineDistance; + +pub fn area(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Geometry(v) => match v { + Geometry::Point(v) => Ok(v.signed_area().into()), + Geometry::Line(v) => Ok(v.signed_area().into()), + Geometry::Polygon(v) => Ok(v.signed_area().into()), + Geometry::MultiPoint(v) => Ok(v.signed_area().into()), + Geometry::MultiLine(v) => Ok(v.signed_area().into()), + Geometry::MultiPolygon(v) => Ok(v.signed_area().into()), + Geometry::Collection(v) => { + Ok(v.into_iter().collect::>().signed_area().into()) + } + }, + _ => Ok(Value::None), + } +} + +pub fn bearing(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Geometry(Geometry::Point(v)) => match args.remove(0) { + Value::Geometry(Geometry::Point(w)) => Ok(v.bearing(w).into()), + _ => Ok(Value::None), + }, + _ => Ok(Value::None), + } +} + +pub fn centroid(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Geometry(v) => match v { + Geometry::Point(v) => Ok(v.centroid().into()), + Geometry::Line(v) => match v.centroid() { + Some(x) => Ok(x.into()), + None => Ok(Value::None), + }, + Geometry::Polygon(v) => match v.centroid() { + Some(x) => Ok(x.into()), + None => Ok(Value::None), + }, + Geometry::MultiPoint(v) => match v.centroid() { + Some(x) => Ok(x.into()), + None => Ok(Value::None), + }, + Geometry::MultiLine(v) => match v.centroid() { + Some(x) => Ok(x.into()), + None => Ok(Value::None), + }, + Geometry::MultiPolygon(v) => match v.centroid() { + Some(x) => Ok(x.into()), + None => Ok(Value::None), + }, + Geometry::Collection(v) => { + match v.into_iter().collect::>().centroid() { + Some(x) => Ok(x.into()), + None => Ok(Value::None), + } + } + }, + _ => Ok(Value::None), + } +} + +pub fn distance(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Geometry(Geometry::Point(v)) => match args.remove(0) { + Value::Geometry(Geometry::Point(w)) => Ok(v.haversine_distance(&w).into()), + _ => Ok(Value::None), + }, + _ => Ok(Value::None), + } +} + +pub mod hash { + + use crate::dbs::Runtime; + use crate::err::Error; + use crate::fnc::util::geo; + use crate::sql::geometry::Geometry; + use crate::sql::value::Value; + + pub fn encode(_: &Runtime, mut args: Vec) -> Result { + match args.len() { + 2 => match args.remove(0) { + Value::Geometry(Geometry::Point(v)) => match args.remove(0).as_int() { + l if l > 0 && l <= 12 => Ok(geo::encode(v, l as usize).into()), + _ => Err(Error::ArgumentsError { + name: String::from("geo::encode"), + message: String::from("The second argument must be an integer greater than 0 and less than or equal to 12."), + }), + }, + _ => Ok(Value::None), + }, + 1 => match args.remove(0) { + Value::Geometry(Geometry::Point(v)) => Ok(geo::encode(v, 12 as usize).into()), + _ => Ok(Value::None), + }, + _ => unreachable!(), + } + } + + pub fn decode(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Strand(v) => Ok(geo::decode(v).into()), + _ => Ok(Value::None), + } + } +} diff --git a/src/fnc/http.rs b/src/fnc/http.rs new file mode 100644 index 00000000..7b151a50 --- /dev/null +++ b/src/fnc/http.rs @@ -0,0 +1,27 @@ +use crate::dbs::Runtime; +use crate::err::Error; +use crate::sql::value::Value; + +pub fn head(_ctx: &Runtime, _args: Vec) -> Result { + todo!() +} + +pub fn get(_ctx: &Runtime, _args: Vec) -> Result { + todo!() +} + +pub fn put(_ctx: &Runtime, _args: Vec) -> Result { + todo!() +} + +pub fn post(_ctx: &Runtime, _args: Vec) -> Result { + todo!() +} + +pub fn patch(_ctx: &Runtime, _args: Vec) -> Result { + todo!() +} + +pub fn delete(_ctx: &Runtime, _args: Vec) -> Result { + todo!() +} diff --git a/src/fnc/is.rs b/src/fnc/is.rs new file mode 100644 index 00000000..93df6e8b --- /dev/null +++ b/src/fnc/is.rs @@ -0,0 +1,81 @@ +use crate::dbs::Runtime; +use crate::err::Error; +use crate::sql::value::Value; +use once_cell::sync::Lazy; +use regex::Regex; +use std::char; + +#[rustfmt::skip] static UUID_RE: Lazy = Lazy::new(|| Regex::new(r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$").unwrap()); +#[rustfmt::skip] static USER_RE: Lazy = Lazy::new(|| Regex::new(r"^(?i)[a-z0-9.!#$%&'*+/=?^_`{|}~-]+\z").unwrap()); +#[rustfmt::skip] static HOST_RE: Lazy = Lazy::new(|| Regex::new(r"(?i)^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$").unwrap()); +#[rustfmt::skip] static DOMAIN_RE: Lazy = Lazy::new(|| Regex::new(r"^([a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62}){1}(\.[a-zA-Z0-9_]{1}[a-zA-Z0-9_-]{0,62})*[\._]?$",).unwrap()); +#[rustfmt::skip] static SEMVER_RE: Lazy = Lazy::new(|| Regex::new("^v?(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)(-(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(\\.(0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\\+[0-9a-zA-Z-]+(\\.[0-9a-zA-Z-]+)*)?$").unwrap()); +#[rustfmt::skip] static LATITUDE_RE: Lazy = Lazy::new(|| Regex::new("^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$").unwrap()); +#[rustfmt::skip] static LONGITUDE_RE: Lazy = Lazy::new(|| Regex::new("^[-+]?([1-8]?\\d(\\.\\d+)?|90(\\.0+)?)$").unwrap()); + +pub fn alphanum(_: &Runtime, mut args: Vec) -> Result { + Ok(args.remove(0).as_strand().value.chars().all(char::is_alphanumeric).into()) +} + +pub fn alpha(_: &Runtime, mut args: Vec) -> Result { + Ok(args.remove(0).as_strand().value.chars().all(char::is_alphabetic).into()) +} + +pub fn ascii(_: &Runtime, mut args: Vec) -> Result { + Ok(args.remove(0).as_strand().value.chars().all(|x| char::is_ascii(&x)).into()) +} + +pub fn domain(_: &Runtime, mut args: Vec) -> Result { + Ok(DOMAIN_RE.is_match(args.remove(0).as_strand().as_str()).into()) +} + +pub fn email(_: &Runtime, mut args: Vec) -> Result { + // Convert to a String + let val = args.remove(0).as_strand().value; + // Convert to a &str + let val = val.as_str(); + // Check if value is empty + if val.is_empty() { + return Ok(Value::False); + } + // Ensure the value contains @ + if !val.contains('@') { + return Ok(Value::False); + } + // Reverse split the value by @ + let parts: Vec<&str> = val.rsplitn(2, '@').collect(); + // Check the first part matches + if !USER_RE.is_match(parts[1]) { + return Ok(Value::False); + } + // Check the second part matches + if !HOST_RE.is_match(parts[0]) { + return Ok(Value::False); + } + // The email is valid + Ok(Value::True) +} + +pub fn hexadecimal(_: &Runtime, mut args: Vec) -> Result { + Ok(args.remove(0).as_strand().value.chars().all(|x| char::is_ascii_hexdigit(&x)).into()) +} + +pub fn latitude(_: &Runtime, mut args: Vec) -> Result { + Ok(LATITUDE_RE.is_match(args.remove(0).as_strand().as_str()).into()) +} + +pub fn longitude(_: &Runtime, mut args: Vec) -> Result { + Ok(LONGITUDE_RE.is_match(args.remove(0).as_strand().as_str()).into()) +} + +pub fn numeric(_: &Runtime, mut args: Vec) -> Result { + Ok(args.remove(0).as_strand().value.chars().all(char::is_numeric).into()) +} + +pub fn semver(_: &Runtime, mut args: Vec) -> Result { + Ok(SEMVER_RE.is_match(args.remove(0).as_strand().as_str()).into()) +} + +pub fn uuid(_: &Runtime, mut args: Vec) -> Result { + Ok(UUID_RE.is_match(args.remove(0).as_strand().as_str()).into()) +} diff --git a/src/fnc/math.rs b/src/fnc/math.rs new file mode 100644 index 00000000..1d3eccae --- /dev/null +++ b/src/fnc/math.rs @@ -0,0 +1,178 @@ +use crate::dbs::Runtime; +use crate::err::Error; +use crate::fnc::util::math::bottom::Bottom; +use crate::fnc::util::math::deviation::Deviation; +use crate::fnc::util::math::interquartile::Interquartile; +use crate::fnc::util::math::mean::Mean; +use crate::fnc::util::math::median::Median; +use crate::fnc::util::math::midhinge::Midhinge; +use crate::fnc::util::math::mode::Mode; +use crate::fnc::util::math::nearestrank::Nearestrank; +use crate::fnc::util::math::percentile::Percentile; +use crate::fnc::util::math::spread::Spread; +use crate::fnc::util::math::top::Top; +use crate::fnc::util::math::trimean::Trimean; +use crate::fnc::util::math::variance::Variance; +use crate::sql::number::Number; +use crate::sql::value::Value; + +pub fn abs(_: &Runtime, mut args: Vec) -> Result { + Ok(args.remove(0).as_number().abs().into()) +} + +pub fn bottom(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Array(v) => match args.remove(0).as_int() { + c => Ok(v.as_numbers().bottom(c).into()), + }, + _ => Ok(Value::None), + } +} + +pub fn ceil(_: &Runtime, mut args: Vec) -> Result { + Ok(args.remove(0).as_number().ceil().into()) +} + +pub fn fixed(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + v => match args.remove(0).as_int() { + p if p > 0 => Ok(v.as_number().fixed(p as usize).into()), + _ => Err(Error::ArgumentsError { + name: String::from("math::fixed"), + message: String::from("The second argument must be an integer greater than 0."), + }), + }, + } +} + +pub fn floor(_: &Runtime, mut args: Vec) -> Result { + Ok(args.remove(0).as_number().floor().into()) +} + +pub fn interquartile(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Array(v) => Ok(v.as_numbers().interquartile().into()), + _ => Ok(Value::None), + } +} + +pub fn max(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Array(v) => match v.as_numbers().into_iter().max() { + Some(v) => Ok(v.into()), + None => Ok(Value::None), + }, + v => Ok(v), + } +} + +pub fn mean(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Array(v) => Ok(v.as_numbers().mean().into()), + _ => Ok(Value::None), + } +} + +pub fn median(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Array(v) => Ok(v.as_numbers().median().into()), + _ => Ok(Value::None), + } +} + +pub fn midhinge(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Array(v) => Ok(v.as_numbers().midhinge().into()), + _ => Ok(Value::None), + } +} + +pub fn min(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Array(v) => match v.as_numbers().into_iter().min() { + Some(v) => Ok(v.into()), + None => Ok(Value::None), + }, + v => Ok(v), + } +} + +pub fn mode(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Array(v) => Ok(v.as_numbers().mode().into()), + _ => Ok(Value::None), + } +} + +pub fn nearestrank(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Array(v) => Ok(v.as_numbers().nearestrank(args.remove(0).as_number()).into()), + _ => Ok(Value::None), + } +} + +pub fn percentile(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Array(v) => Ok(v.as_numbers().percentile(args.remove(0).as_number()).into()), + _ => Ok(Value::None), + } +} + +pub fn product(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Array(v) => Ok(v.as_numbers().into_iter().product::().into()), + _ => Ok(Value::None), + } +} + +pub fn round(_: &Runtime, mut args: Vec) -> Result { + Ok(args.remove(0).as_number().round().into()) +} + +pub fn spread(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Array(v) => Ok(v.as_numbers().spread().into()), + _ => Ok(Value::None), + } +} + +pub fn sqrt(_: &Runtime, mut args: Vec) -> Result { + Ok(args.remove(0).as_number().sqrt().into()) +} + +pub fn stddev(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Array(v) => Ok(v.as_numbers().deviation().into()), + _ => Ok(Value::None), + } +} + +pub fn sum(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Array(v) => Ok(v.as_numbers().into_iter().sum::().into()), + _ => Ok(Value::None), + } +} + +pub fn top(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Array(v) => match args.remove(0).as_int() { + c => Ok(v.as_numbers().top(c).into()), + }, + _ => Ok(Value::None), + } +} + +pub fn trimean(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Array(v) => Ok(v.as_numbers().trimean().into()), + _ => Ok(Value::None), + } +} + +pub fn variance(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Array(v) => Ok(v.as_numbers().variance().into()), + _ => Ok(Value::None), + } +} diff --git a/src/fnc/mod.rs b/src/fnc/mod.rs index 01007797..ba39eb6e 100644 --- a/src/fnc/mod.rs +++ b/src/fnc/mod.rs @@ -1,11 +1,168 @@ use crate::dbs::Runtime; use crate::err::Error; -use crate::sql::literal::Literal; +use crate::fnc::args::Args; +use crate::sql::value::Value; +pub mod args; +pub mod array; pub mod cast; +pub mod count; +pub mod crypto; pub mod future; +pub mod geo; +pub mod http; +pub mod is; +pub mod math; pub mod operate; +pub mod parse; +pub mod rand; +pub mod script; +pub mod string; +pub mod time; +pub mod r#type; +pub mod util; -pub fn run(ctx: &Runtime, name: &String, args: Vec) -> Result { - todo!() +pub fn run(ctx: &Runtime, name: &String, args: Vec) -> Result { + match name.as_ref() { + // + "array::combine" => args::check(ctx, name, args, Args::Two, array::combine), + "array::concat" => args::check(ctx, name, args, Args::Two, array::concat), + "array::difference" => args::check(ctx, name, args, Args::Two, array::difference), + "array::distinct" => args::check(ctx, name, args, Args::One, array::distinct), + "array::intersect" => args::check(ctx, name, args, Args::Two, array::intersect), + "array::len" => args::check(ctx, name, args, Args::One, array::len), + "array::union" => args::check(ctx, name, args, Args::Two, array::union), + // + "count" => args::check(ctx, name, args, Args::NoneOne, count::count), + // + "crypto::md5" => args::check(ctx, name, args, Args::One, crypto::md5), + "crypto::sha1" => args::check(ctx, name, args, Args::One, crypto::sha1), + "crypto::sha256" => args::check(ctx, name, args, Args::One, crypto::sha256), + "crypto::sha512" => args::check(ctx, name, args, Args::One, crypto::sha512), + "crypto::argon2::compare" => args::check(ctx, name, args, Args::Two, crypto::argon2::cmp), + "crypto::argon2::generate" => args::check(ctx, name, args, Args::One, crypto::argon2::gen), + "crypto::bcrypt::compare" => args::check(ctx, name, args, Args::Two, crypto::bcrypt::cmp), + "crypto::bcrypt::generate" => args::check(ctx, name, args, Args::One, crypto::bcrypt::gen), + "crypto::pbkdf2::compare" => args::check(ctx, name, args, Args::Two, crypto::pbkdf2::cmp), + "crypto::pbkdf2::generate" => args::check(ctx, name, args, Args::One, crypto::pbkdf2::gen), + "crypto::scrypt::compare" => args::check(ctx, name, args, Args::Two, crypto::scrypt::cmp), + "crypto::scrypt::generate" => args::check(ctx, name, args, Args::One, crypto::scrypt::gen), + // + "geo::area" => args::check(ctx, name, args, Args::One, geo::area), + "geo::bearing" => args::check(ctx, name, args, Args::Two, geo::bearing), + "geo::centroid" => args::check(ctx, name, args, Args::One, geo::centroid), + "geo::distance" => args::check(ctx, name, args, Args::Two, geo::distance), + "geo::hash::decode" => args::check(ctx, name, args, Args::One, geo::hash::decode), + "geo::hash::encode" => args::check(ctx, name, args, Args::OneTwo, geo::hash::encode), + // + "http::head" => args::check(ctx, name, args, Args::OneTwo, http::head), + "http::get" => args::check(ctx, name, args, Args::OneTwo, http::get), + "http::put" => args::check(ctx, name, args, Args::OneTwoThree, http::put), + "http::post" => args::check(ctx, name, args, Args::OneTwoThree, http::post), + "http::patch" => args::check(ctx, name, args, Args::OneTwoThree, http::patch), + "http::delete" => args::check(ctx, name, args, Args::OneTwo, http::delete), + // + "is::alphanum" => args::check(ctx, name, args, Args::One, is::alphanum), + "is::alpha" => args::check(ctx, name, args, Args::One, is::alpha), + "is::ascii" => args::check(ctx, name, args, Args::One, is::ascii), + "is::domain" => args::check(ctx, name, args, Args::One, is::domain), + "is::email" => args::check(ctx, name, args, Args::One, is::email), + "is::hexadecimal" => args::check(ctx, name, args, Args::One, is::hexadecimal), + "is::latitude" => args::check(ctx, name, args, Args::One, is::latitude), + "is::longitude" => args::check(ctx, name, args, Args::One, is::longitude), + "is::numeric" => args::check(ctx, name, args, Args::One, is::numeric), + "is::semver" => args::check(ctx, name, args, Args::One, is::semver), + "is::uuid" => args::check(ctx, name, args, Args::One, is::uuid), + // + "math::abs" => args::check(ctx, name, args, Args::One, math::abs), + "math::bottom" => args::check(ctx, name, args, Args::Two, math::bottom), + "math::ceil" => args::check(ctx, name, args, Args::One, math::ceil), + "math::fixed" => args::check(ctx, name, args, Args::Two, math::fixed), + "math::floor" => args::check(ctx, name, args, Args::One, math::floor), + "math::interquartile" => args::check(ctx, name, args, Args::One, math::interquartile), + "math::max" => args::check(ctx, name, args, Args::One, math::max), + "math::mean" => args::check(ctx, name, args, Args::One, math::mean), + "math::median" => args::check(ctx, name, args, Args::One, math::median), + "math::midhinge" => args::check(ctx, name, args, Args::One, math::midhinge), + "math::min" => args::check(ctx, name, args, Args::One, math::min), + "math::mode" => args::check(ctx, name, args, Args::One, math::mode), + "math::nearestrank" => args::check(ctx, name, args, Args::Two, math::nearestrank), + "math::percentile" => args::check(ctx, name, args, Args::Two, math::percentile), + "math::product" => args::check(ctx, name, args, Args::One, math::product), + "math::round" => args::check(ctx, name, args, Args::One, math::round), + "math::spread" => args::check(ctx, name, args, Args::One, math::spread), + "math::sqrt" => args::check(ctx, name, args, Args::One, math::sqrt), + "math::stddev" => args::check(ctx, name, args, Args::One, math::stddev), + "math::sum" => args::check(ctx, name, args, Args::One, math::sum), + "math::top" => args::check(ctx, name, args, Args::Two, math::top), + "math::trimean" => args::check(ctx, name, args, Args::One, math::trimean), + "math::variance" => args::check(ctx, name, args, Args::One, math::variance), + // + "parse::email::domain" => args::check(ctx, name, args, Args::One, parse::email::domain), + "parse::email::user" => args::check(ctx, name, args, Args::One, parse::email::user), + "parse::url::domain" => args::check(ctx, name, args, Args::One, parse::url::domain), + "parse::url::fragment" => args::check(ctx, name, args, Args::One, parse::url::fragment), + "parse::url::host" => args::check(ctx, name, args, Args::One, parse::url::host), + "parse::url::path" => args::check(ctx, name, args, Args::One, parse::url::path), + "parse::url::port" => args::check(ctx, name, args, Args::One, parse::url::port), + "parse::url::query" => args::check(ctx, name, args, Args::One, parse::url::query), + // + "rand::bool" => args::check(ctx, name, args, Args::None, rand::bool), + "rand::enum" => args::check(ctx, name, args, Args::Any, rand::r#enum), + "rand::float" => args::check(ctx, name, args, Args::NoneTwo, rand::float), + "rand::guid" => args::check(ctx, name, args, Args::None, rand::guid), + "rand::int" => args::check(ctx, name, args, Args::NoneTwo, rand::int), + "rand::string" => args::check(ctx, name, args, Args::NoneOneTwo, rand::string), + "rand::time" => args::check(ctx, name, args, Args::NoneTwo, rand::time), + "rand::uuid" => args::check(ctx, name, args, Args::None, rand::uuid), + "rand" => args::check(ctx, name, args, Args::None, rand::rand), + // + "string::concat" => args::check(ctx, name, args, Args::Any, string::concat), + "string::contains" => args::check(ctx, name, args, Args::Two, string::contains), + "string::endsWith" => args::check(ctx, name, args, Args::Two, string::ends_with), + "string::join" => args::check(ctx, name, args, Args::Any, string::join), + "string::length" => args::check(ctx, name, args, Args::One, string::length), + "string::lowercase" => args::check(ctx, name, args, Args::One, string::lowercase), + "string::repeat" => args::check(ctx, name, args, Args::Two, string::repeat), + "string::replace" => args::check(ctx, name, args, Args::Three, string::replace), + "string::reverse" => args::check(ctx, name, args, Args::One, string::reverse), + "string::slice" => args::check(ctx, name, args, Args::Three, string::slice), + "string::slug" => args::check(ctx, name, args, Args::OneTwo, string::slug), + "string::split" => args::check(ctx, name, args, Args::Two, string::split), + "string::startsWith" => args::check(ctx, name, args, Args::Two, string::starts_with), + "string::substr" => args::check(ctx, name, args, Args::Three, string::substr), + "string::trim" => args::check(ctx, name, args, Args::One, string::trim), + "string::uppercase" => args::check(ctx, name, args, Args::One, string::uppercase), + "string::words" => args::check(ctx, name, args, Args::One, string::words), + // + "time::day" => args::check(ctx, name, args, Args::NoneOne, time::day), + "time::floor" => args::check(ctx, name, args, Args::Two, time::floor), + "time::hour" => args::check(ctx, name, args, Args::NoneOne, time::hour), + "time::mins" => args::check(ctx, name, args, Args::NoneOne, time::mins), + "time::month" => args::check(ctx, name, args, Args::NoneOne, time::month), + "time::nano" => args::check(ctx, name, args, Args::NoneOne, time::nano), + "time::now" => args::check(ctx, name, args, Args::None, time::now), + "time::round" => args::check(ctx, name, args, Args::Two, time::round), + "time::secs" => args::check(ctx, name, args, Args::NoneOne, time::secs), + "time::unix" => args::check(ctx, name, args, Args::NoneOne, time::unix), + "time::wday" => args::check(ctx, name, args, Args::NoneOne, time::wday), + "time::week" => args::check(ctx, name, args, Args::NoneOne, time::week), + "time::yday" => args::check(ctx, name, args, Args::NoneOne, time::yday), + "time::year" => args::check(ctx, name, args, Args::NoneOne, time::year), + // + "type::bool" => args::check(ctx, name, args, Args::One, r#type::bool), + "type::datetime" => args::check(ctx, name, args, Args::One, r#type::datetime), + "type::decimal" => args::check(ctx, name, args, Args::One, r#type::decimal), + "type::duration" => args::check(ctx, name, args, Args::One, r#type::duration), + "type::float" => args::check(ctx, name, args, Args::One, r#type::float), + "type::int" => args::check(ctx, name, args, Args::One, r#type::int), + "type::number" => args::check(ctx, name, args, Args::One, r#type::number), + "type::point" => args::check(ctx, name, args, Args::OneTwo, r#type::point), + "type::regex" => args::check(ctx, name, args, Args::One, r#type::regex), + "type::string" => args::check(ctx, name, args, Args::One, r#type::string), + "type::table" => args::check(ctx, name, args, Args::One, r#type::table), + "type::thing" => args::check(ctx, name, args, Args::Two, r#type::thing), + // + _ => unreachable!(), + } } diff --git a/src/fnc/operate.rs b/src/fnc/operate.rs index 2cfde74f..08ee5d2d 100644 --- a/src/fnc/operate.rs +++ b/src/fnc/operate.rs @@ -1,352 +1,202 @@ use crate::err::Error; -use crate::sql::literal::Literal; use crate::sql::value::Value; +use std::ops::Add; +use std::ops::Div; +use std::ops::Mul; +use std::ops::Sub; -pub fn or(a: Literal, b: Literal) -> Result { - match a.as_bool() { +pub fn or(a: Value, b: Value) -> Result { + match a.is_truthy() { true => Ok(a), false => Ok(b), } } -pub fn and(a: Literal, b: Literal) -> Result { - match a.as_bool() { - true => match b.as_bool() { - _ => Ok(b), - }, +pub fn and(a: Value, b: Value) -> Result { + match a.is_truthy() { + true => Ok(b), false => Ok(a), } } -pub fn add(a: &Literal, b: &Literal) -> Result { - let a = a.as_number(); - let b = b.as_number(); - Ok(Literal::from(a + b)) +pub fn add(a: Value, b: Value) -> Result { + Ok(a.add(b)) } -pub fn sub(a: &Literal, b: &Literal) -> Result { - let a = a.as_number(); - let b = b.as_number(); - Ok(Literal::from(a - b)) +pub fn sub(a: Value, b: Value) -> Result { + Ok(a.sub(b)) } -pub fn mul(a: &Literal, b: &Literal) -> Result { - let a = a.as_number(); - let b = b.as_number(); - Ok(Literal::from(a * b)) +pub fn mul(a: Value, b: Value) -> Result { + Ok(a.mul(b)) } -pub fn div(a: &Literal, b: &Literal) -> Result { - let a = a.as_number(); - let b = b.as_number(); - Ok(Literal::from(a / b)) +pub fn div(a: Value, b: Value) -> Result { + Ok(a.div(b)) } -pub fn exact(a: &Literal, b: &Literal) -> Result { - Ok(Literal::from(a == b)) +pub fn exact(a: &Value, b: &Value) -> Result { + Ok(Value::from(a == b)) } -pub fn equal(a: &Literal, b: &Literal) -> Result { - match a { - Literal::None => Ok(Literal::from(b.is_none() == true)), - Literal::Null => Ok(Literal::from(b.is_null() == true)), - Literal::Void => Ok(Literal::from(b.is_void() == true)), - Literal::True => Ok(Literal::from(b.is_true() == true)), - Literal::False => Ok(Literal::from(b.is_false() == true)), - Literal::Int(v) => Ok(Literal::from(v == &b.as_int())), - Literal::Float(v) => Ok(Literal::from(v == &b.as_float())), - Literal::Thing(v) => match b { - Literal::Thing(w) => Ok(Literal::from(v == w)), - _ => Ok(Literal::True), - }, - Literal::Regex(v) => match b { - Literal::Regex(w) => Ok(Literal::from(v == w)), - Literal::Strand(w) => match v.value { - Some(ref r) => Ok(Literal::from(r.is_match(w.value.as_str()) == true)), - None => Ok(Literal::False), - }, - _ => Ok(Literal::False), - }, - Literal::Point(v) => match b { - Literal::Point(w) => Ok(Literal::from(v == w)), - _ => Ok(Literal::False), - }, - Literal::Array(v) => match b { - Literal::Array(w) => Ok(Literal::from(v == w)), - _ => Ok(Literal::False), - }, - Literal::Object(v) => match b { - Literal::Object(w) => Ok(Literal::from(v == w)), - _ => Ok(Literal::False), - }, - Literal::Strand(v) => match b { - Literal::Strand(w) => Ok(Literal::from(v == w)), - Literal::Regex(w) => match w.value { - Some(ref r) => Ok(Literal::from(r.is_match(v.value.as_str()) == true)), - None => Ok(Literal::False), - }, - _ => Ok(Literal::from(v == &b.as_strand())), - }, - Literal::Number(v) => Ok(Literal::from(v == &b.as_number())), - Literal::Polygon(v) => match b { - Literal::Polygon(w) => Ok(Literal::from(v == w)), - _ => Ok(Literal::False), - }, - Literal::Duration(v) => match b { - Literal::Duration(w) => Ok(Literal::from(v == w)), - _ => Ok(Literal::False), - }, - Literal::Datetime(v) => match b { - Literal::Datetime(w) => Ok(Literal::from(v == w)), - _ => Ok(Literal::False), - }, - _ => unreachable!(), +pub fn equal(a: &Value, b: &Value) -> Result { + match a.equal(b) { + true => Ok(Value::True), + false => Ok(Value::False), } } -pub fn not_equal(a: &Literal, b: &Literal) -> Result { - match a { - Literal::None => Ok(Literal::from(b.is_none() != true)), - Literal::Null => Ok(Literal::from(b.is_null() != true)), - Literal::Void => Ok(Literal::from(b.is_void() != true)), - Literal::True => Ok(Literal::from(b.is_true() != true)), - Literal::False => Ok(Literal::from(b.is_false() != true)), - Literal::Int(v) => Ok(Literal::from(v != &b.as_int())), - Literal::Float(v) => Ok(Literal::from(v != &b.as_float())), - Literal::Thing(v) => match b { - Literal::Thing(w) => Ok(Literal::from(v != w)), - _ => Ok(Literal::True), - }, - Literal::Regex(v) => match b { - Literal::Regex(w) => Ok(Literal::from(v != w)), - Literal::Strand(w) => match v.value { - Some(ref r) => Ok(Literal::from(r.is_match(w.value.as_str()) != true)), - None => Ok(Literal::True), - }, - _ => Ok(Literal::True), - }, - Literal::Point(v) => match b { - Literal::Point(w) => Ok(Literal::from(v != w)), - _ => Ok(Literal::True), - }, - Literal::Array(v) => match b { - Literal::Array(w) => Ok(Literal::from(v != w)), - _ => Ok(Literal::True), - }, - Literal::Object(v) => match b { - Literal::Object(w) => Ok(Literal::from(v != w)), - _ => Ok(Literal::True), - }, - Literal::Number(v) => match b { - Literal::Number(w) => Ok(Literal::from(v != w)), - _ => Ok(Literal::True), - }, - Literal::Strand(v) => match b { - Literal::Strand(w) => Ok(Literal::from(v != w)), - Literal::Regex(w) => match w.value { - Some(ref r) => Ok(Literal::from(r.is_match(v.value.as_str()) != true)), - None => Ok(Literal::False), - }, - _ => Ok(Literal::from(v != &b.as_strand())), - }, - Literal::Polygon(v) => match b { - Literal::Polygon(w) => Ok(Literal::from(v != w)), - _ => Ok(Literal::True), - }, - Literal::Duration(v) => match b { - Literal::Duration(w) => Ok(Literal::from(v != w)), - _ => Ok(Literal::True), - }, - Literal::Datetime(v) => match b { - Literal::Datetime(w) => Ok(Literal::from(v != w)), - _ => Ok(Literal::True), - }, - _ => unreachable!(), +pub fn not_equal(a: &Value, b: &Value) -> Result { + match a.equal(b) { + true => Ok(Value::False), + false => Ok(Value::True), } } -pub fn all_equal(a: &Literal, b: &Literal) -> Result { - match a { - Literal::Array(ref v) => match v.value.iter().all(|x| match x { - Value::Literal(v) => equal(v, b).is_ok(), - _ => unreachable!(), - }) { - true => Ok(Literal::True), - false => Ok(Literal::False), - }, - _ => equal(a, b), +pub fn all_equal(a: &Value, b: &Value) -> Result { + match a.all_equal(b) { + true => Ok(Value::True), + false => Ok(Value::False), } } -pub fn any_equal(a: &Literal, b: &Literal) -> Result { - match a { - Literal::Array(ref v) => match v.value.iter().any(|x| match x { - Value::Literal(v) => equal(v, b).is_ok(), - _ => unreachable!(), - }) { - true => Ok(Literal::True), - false => Ok(Literal::False), - }, - _ => equal(a, b), +pub fn any_equal(a: &Value, b: &Value) -> Result { + match a.any_equal(b) { + true => Ok(Value::True), + false => Ok(Value::False), } } -pub fn like(a: &Literal, b: &Literal) -> Result { - todo!() -} - -pub fn not_like(a: &Literal, b: &Literal) -> Result { - todo!() -} - -pub fn all_like(a: &Literal, b: &Literal) -> Result { - todo!() -} - -pub fn any_like(a: &Literal, b: &Literal) -> Result { - todo!() -} - -pub fn less_than(a: &Literal, b: &Literal) -> Result { - match a.lt(&b) { - true => Ok(Literal::True), - false => Ok(Literal::False), +pub fn like(a: &Value, b: &Value) -> Result { + match a.fuzzy(b) { + true => Ok(Value::True), + false => Ok(Value::False), } } -pub fn less_than_or_equal(a: &Literal, b: &Literal) -> Result { - match a.le(&b) { - true => Ok(Literal::True), - false => Ok(Literal::False), +pub fn not_like(a: &Value, b: &Value) -> Result { + match a.fuzzy(b) { + true => Ok(Value::False), + false => Ok(Value::True), } } -pub fn more_than(a: &Literal, b: &Literal) -> Result { - match a.gt(&b) { - true => Ok(Literal::True), - false => Ok(Literal::False), +pub fn all_like(a: &Value, b: &Value) -> Result { + match a.all_fuzzy(b) { + true => Ok(Value::True), + false => Ok(Value::False), } } -pub fn more_than_or_equal(a: &Literal, b: &Literal) -> Result { - match a.ge(&b) { - true => Ok(Literal::True), - false => Ok(Literal::False), +pub fn any_like(a: &Value, b: &Value) -> Result { + match a.any_fuzzy(b) { + true => Ok(Value::True), + false => Ok(Value::False), } } -pub fn contain(a: &Literal, b: &Literal) -> Result { - match a { - Literal::Array(v) => match v.value.iter().any(|x| match x { - Value::Literal(v) => equal(v, b).is_ok(), - _ => unreachable!(), - }) { - true => Ok(Literal::True), - false => Ok(Literal::False), - }, - Literal::Strand(v) => match b { - Literal::Strand(w) => Ok(Literal::from(v.value.contains(w.value.as_str()) == true)), - _ => Ok(Literal::from(v.value.contains(&b.as_strand().value.as_str()) == true)), - }, - Literal::Polygon(v) => todo!(), - _ => Ok(Literal::False), +pub fn less_than(a: &Value, b: &Value) -> Result { + match a.lt(b) { + true => Ok(Value::True), + false => Ok(Value::False), } } -pub fn not_contain(a: &Literal, b: &Literal) -> Result { - match a { - Literal::Array(v) => match v.value.iter().any(|x| match x { - Value::Literal(v) => equal(v, b).is_ok(), - _ => unreachable!(), - }) { - true => Ok(Literal::False), - false => Ok(Literal::True), - }, - Literal::Strand(v) => match b { - Literal::Strand(w) => Ok(Literal::from(v.value.contains(w.value.as_str()) == false)), - _ => Ok(Literal::from(v.value.contains(&b.as_strand().value.as_str()) == false)), - }, - Literal::Polygon(v) => todo!(), - _ => Ok(Literal::False), +pub fn less_than_or_equal(a: &Value, b: &Value) -> Result { + match a.le(b) { + true => Ok(Value::True), + false => Ok(Value::False), } } -pub fn contain_all(a: &Literal, b: &Literal) -> Result { - match a { - Literal::Array(v) => todo!(), - Literal::Strand(v) => todo!(), - Literal::Polygon(v) => todo!(), - _ => Ok(Literal::False), +pub fn more_than(a: &Value, b: &Value) -> Result { + match a.gt(b) { + true => Ok(Value::True), + false => Ok(Value::False), } } -pub fn contain_some(a: &Literal, b: &Literal) -> Result { - match a { - Literal::Array(v) => todo!(), - Literal::Strand(v) => todo!(), - Literal::Polygon(v) => todo!(), - _ => Ok(Literal::False), +pub fn more_than_or_equal(a: &Value, b: &Value) -> Result { + match a.ge(b) { + true => Ok(Value::True), + false => Ok(Value::False), } } -pub fn contain_none(a: &Literal, b: &Literal) -> Result { - match a { - Literal::Array(v) => todo!(), - Literal::Strand(v) => todo!(), - Literal::Polygon(v) => todo!(), - _ => Ok(Literal::False), +pub fn contain(a: &Value, b: &Value) -> Result { + match a.contains(b) { + true => Ok(Value::True), + false => Ok(Value::False), } } -pub fn inside(a: &Literal, b: &Literal) -> Result { - match b { - Literal::Array(v) => todo!(), - Literal::Strand(v) => todo!(), - Literal::Polygon(v) => todo!(), - _ => Ok(Literal::False), +pub fn not_contain(a: &Value, b: &Value) -> Result { + match a.contains(b) { + true => Ok(Value::False), + false => Ok(Value::True), } } -pub fn not_inside(a: &Literal, b: &Literal) -> Result { - match b { - Literal::Array(v) => todo!(), - Literal::Strand(v) => todo!(), - Literal::Polygon(v) => todo!(), - _ => Ok(Literal::False), +pub fn contain_all(a: &Value, b: &Value) -> Result { + match a.contains_all(b) { + true => Ok(Value::True), + false => Ok(Value::False), } } -pub fn inside_all(a: &Literal, b: &Literal) -> Result { - match b { - Literal::Array(v) => todo!(), - Literal::Strand(v) => todo!(), - Literal::Polygon(v) => todo!(), - _ => Ok(Literal::False), +pub fn contain_any(a: &Value, b: &Value) -> Result { + match a.contains_any(b) { + true => Ok(Value::True), + false => Ok(Value::False), } } -pub fn inside_some(a: &Literal, b: &Literal) -> Result { - match b { - Literal::Array(v) => todo!(), - Literal::Strand(v) => todo!(), - Literal::Polygon(v) => todo!(), - _ => Ok(Literal::False), +pub fn contain_none(a: &Value, b: &Value) -> Result { + match a.contains_any(b) { + true => Ok(Value::False), + false => Ok(Value::True), } } -pub fn inside_none(a: &Literal, b: &Literal) -> Result { - match b { - Literal::Array(v) => todo!(), - Literal::Strand(v) => todo!(), - Literal::Polygon(v) => todo!(), - _ => Ok(Literal::False), +pub fn inside(a: &Value, b: &Value) -> Result { + match b.contains(a) { + true => Ok(Value::True), + false => Ok(Value::False), } } -pub fn intersects(a: &Literal, b: &Literal) -> Result { - match b { - Literal::Polygon(v) => todo!(), - _ => Ok(Literal::False), +pub fn not_inside(a: &Value, b: &Value) -> Result { + match b.contains(a) { + true => Ok(Value::False), + false => Ok(Value::True), + } +} + +pub fn inside_all(a: &Value, b: &Value) -> Result { + match b.contains_all(a) { + true => Ok(Value::True), + false => Ok(Value::False), + } +} + +pub fn inside_any(a: &Value, b: &Value) -> Result { + match b.contains_any(a) { + true => Ok(Value::True), + false => Ok(Value::False), + } +} + +pub fn inside_none(a: &Value, b: &Value) -> Result { + match b.contains_any(a) { + true => Ok(Value::False), + false => Ok(Value::True), + } +} + +pub fn intersects(a: &Value, b: &Value) -> Result { + match a.intersects(b) { + true => Ok(Value::True), + false => Ok(Value::False), } } @@ -357,8 +207,8 @@ mod tests { #[test] fn or_true() { - let one = Literal::from(1); - let two = Literal::from(2); + let one = Value::from(1); + let two = Value::from(2); let res = or(one, two); assert!(res.is_ok()); let out = res.unwrap(); @@ -367,8 +217,8 @@ mod tests { #[test] fn or_false_one() { - let one = Literal::from(0); - let two = Literal::from(1); + let one = Value::from(0); + let two = Value::from(1); let res = or(one, two); assert!(res.is_ok()); let out = res.unwrap(); @@ -377,8 +227,8 @@ mod tests { #[test] fn or_false_two() { - let one = Literal::from(1); - let two = Literal::from(0); + let one = Value::from(1); + let two = Value::from(0); let res = or(one, two); assert!(res.is_ok()); let out = res.unwrap(); @@ -387,8 +237,8 @@ mod tests { #[test] fn and_true() { - let one = Literal::from(1); - let two = Literal::from(2); + let one = Value::from(1); + let two = Value::from(2); let res = and(one, two); assert!(res.is_ok()); let out = res.unwrap(); @@ -397,8 +247,8 @@ mod tests { #[test] fn and_false_one() { - let one = Literal::from(0); - let two = Literal::from(1); + let one = Value::from(0); + let two = Value::from(1); let res = and(one, two); assert!(res.is_ok()); let out = res.unwrap(); @@ -407,8 +257,8 @@ mod tests { #[test] fn and_false_two() { - let one = Literal::from(1); - let two = Literal::from(0); + let one = Value::from(1); + let two = Value::from(0); let res = and(one, two); assert!(res.is_ok()); let out = res.unwrap(); @@ -417,9 +267,9 @@ mod tests { #[test] fn add_basic() { - let one = Literal::from(5); - let two = Literal::from(4); - let res = add(&one, &two); + let one = Value::from(5); + let two = Value::from(4); + let res = add(one, two); assert!(res.is_ok()); let out = res.unwrap(); assert_eq!("9", format!("{}", out)); @@ -427,9 +277,9 @@ mod tests { #[test] fn sub_basic() { - let one = Literal::from(5); - let two = Literal::from(4); - let res = sub(&one, &two); + let one = Value::from(5); + let two = Value::from(4); + let res = sub(one, two); assert!(res.is_ok()); let out = res.unwrap(); assert_eq!("1", format!("{}", out)); @@ -437,9 +287,9 @@ mod tests { #[test] fn mul_basic() { - let one = Literal::from(5); - let two = Literal::from(4); - let res = mul(&one, &two); + let one = Value::from(5); + let two = Value::from(4); + let res = mul(one, two); assert!(res.is_ok()); let out = res.unwrap(); assert_eq!("20", format!("{}", out)); @@ -447,9 +297,9 @@ mod tests { #[test] fn div_basic() { - let one = Literal::from(5); - let two = Literal::from(4); - let res = div(&one, &two); + let one = Value::from(5); + let two = Value::from(4); + let res = div(one, two); assert!(res.is_ok()); let out = res.unwrap(); assert_eq!("1.25", format!("{}", out)); diff --git a/src/fnc/parse.rs b/src/fnc/parse.rs new file mode 100644 index 00000000..467aeb8a --- /dev/null +++ b/src/fnc/parse.rs @@ -0,0 +1,144 @@ +pub mod email { + + use crate::dbs::Runtime; + use crate::err::Error; + use crate::sql::value::Value; + use once_cell::sync::Lazy; + use regex::Regex; + + #[rustfmt::skip] static USER_RE: Lazy = Lazy::new(|| Regex::new(r"^(?i)[a-z0-9.!#$%&'*+/=?^_`{|}~-]+\z").unwrap()); + #[rustfmt::skip] static HOST_RE: Lazy = Lazy::new(|| Regex::new(r"(?i)^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$",).unwrap()); + + pub fn domain(_: &Runtime, mut args: Vec) -> Result { + // Convert to a String + let val = args.remove(0).as_strand().value; + // Check if value is empty + if val.is_empty() { + return Ok(Value::None); + } + // Ensure the value contains @ + if !val.contains('@') { + return Ok(Value::None); + } + // Reverse split the value by @ + let parts: Vec<&str> = val.rsplitn(2, '@').collect(); + // Check the first part matches + if !USER_RE.is_match(parts[1]) { + return Ok(Value::None); + } + // Check the second part matches + if !HOST_RE.is_match(parts[0]) { + return Ok(Value::None); + } + // Return the domain + Ok(parts[0].into()) + } + + pub fn user(_: &Runtime, mut args: Vec) -> Result { + // Convert to a String + let val = args.remove(0).as_strand().value; + // Check if value is empty + if val.is_empty() { + return Ok(Value::None); + } + // Ensure the value contains @ + if !val.contains('@') { + return Ok(Value::None); + } + // Reverse split the value by @ + let parts: Vec<&str> = val.rsplitn(2, '@').collect(); + // Check the first part matches + if !USER_RE.is_match(parts[1]) { + return Ok(Value::None); + } + // Check the second part matches + if !HOST_RE.is_match(parts[0]) { + return Ok(Value::None); + } + // Return the domain + Ok(parts[1].into()) + } +} + +pub mod url { + + use crate::dbs::Runtime; + use crate::err::Error; + use crate::sql::value::Value; + use url::Url; + + pub fn domain(_: &Runtime, mut args: Vec) -> Result { + // Convert to a String + let val = args.remove(0).as_strand().value; + // Parse the URL + match Url::parse(&val) { + Ok(v) => match v.domain() { + Some(v) => Ok(v.into()), + None => Ok(Value::None), + }, + Err(_) => Ok(Value::None), + } + } + + pub fn fragment(_: &Runtime, mut args: Vec) -> Result { + // Convert to a String + let val = args.remove(0).as_strand().value; + // Parse the URL + match Url::parse(&val) { + Ok(v) => match v.fragment() { + Some(v) => Ok(v.into()), + None => Ok(Value::None), + }, + Err(_) => Ok(Value::None), + } + } + + pub fn host(_: &Runtime, mut args: Vec) -> Result { + // Convert to a String + let val = args.remove(0).as_strand().value; + // Parse the URL + match Url::parse(&val) { + Ok(v) => match v.host_str() { + Some(v) => Ok(v.into()), + None => Ok(Value::None), + }, + Err(_) => Ok(Value::None), + } + } + + pub fn path(_: &Runtime, mut args: Vec) -> Result { + // Convert to a String + let val = args.remove(0).as_strand().value; + // Parse the URL + match Url::parse(&val) { + Ok(v) => Ok(v.path().into()), + Err(_) => Ok(Value::None), + } + } + + pub fn port(_: &Runtime, mut args: Vec) -> Result { + // Convert to a String + let val = args.remove(0).as_strand().value; + // Parse the URL + match Url::parse(&val) { + Ok(v) => match v.port() { + Some(v) => Ok(v.into()), + None => Ok(Value::None), + }, + Err(_) => Ok(Value::None), + } + } + + pub fn query(_: &Runtime, mut args: Vec) -> Result { + // Convert to a String + let val = args.remove(0).as_strand().value; + // Parse the URL + match Url::parse(&val) { + Ok(v) => match v.query() { + Some(v) => Ok(v.into()), + None => Ok(Value::None), + }, + Err(_) => Ok(Value::None), + } + } +} diff --git a/src/fnc/rand.rs b/src/fnc/rand.rs new file mode 100644 index 00000000..2948f96f --- /dev/null +++ b/src/fnc/rand.rs @@ -0,0 +1,140 @@ +use crate::dbs::Runtime; +use crate::err::Error; +use crate::sql::datetime::Datetime; +use crate::sql::value::Value; +use rand::distributions::Alphanumeric; +use rand::Rng; +use uuid::Uuid; +use xid; + +pub fn rand(_: &Runtime, _: Vec) -> Result { + Ok(rand::random::().into()) +} + +pub fn bool(_: &Runtime, _: Vec) -> Result { + Ok(rand::random::().into()) +} + +pub fn r#enum(_: &Runtime, mut args: Vec) -> Result { + match args.len() { + 0 => Ok(Value::None), + 1 => match args.remove(0) { + Value::Array(mut v) => match v.value.len() { + 0 => Ok(Value::None), + n => { + let i = rand::thread_rng().gen_range(0..n); + Ok(v.value.remove(i)) + } + }, + v => Ok(v), + }, + n => { + let i = rand::thread_rng().gen_range(0..n); + Ok(args.remove(i)) + } + } +} + +pub fn float(_: &Runtime, mut args: Vec) -> Result { + match args.len() { + 2 => match args.remove(0).as_float() { + min => match args.remove(0).as_float() { + max if max < min => Ok(rand::thread_rng().gen_range(max..=min).into()), + max => Ok(rand::thread_rng().gen_range(min..=max).into()), + }, + }, + 0 => Ok(rand::random::().into()), + _ => unreachable!(), + } +} + +pub fn guid(_: &Runtime, _: Vec) -> Result { + Ok(xid::new().to_string().into()) +} + +pub fn int(_: &Runtime, mut args: Vec) -> Result { + match args.len() { + 2 => match args.remove(0).as_int() { + min => match args.remove(0).as_int() { + max if max < min => Ok(rand::thread_rng().gen_range(max..=min).into()), + max => Ok(rand::thread_rng().gen_range(min..=max).into()), + }, + }, + 0 => Ok(rand::random::().into()), + _ => unreachable!(), + } +} + +pub fn string(_: &Runtime, mut args: Vec) -> Result { + match args.len() { + 2 => match args.remove(0).as_int() { + min if min >= 0 => match args.remove(0).as_int() { + max if max >= 0 && max < min => Ok(rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(rand::thread_rng().gen_range(max as usize..=min as usize)) + .map(char::from) + .collect::() + .into()), + max if max >= 0 => Ok(rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(rand::thread_rng().gen_range(min as usize..=max as usize)) + .map(char::from) + .collect::() + .into()), + _ => Err(Error::ArgumentsError { + name: String::from("rand::string"), + message: String::from("To generate a string of between X and Y characters in length, the 2 arguments must be positive numbers."), + }), + }, + _ => Err(Error::ArgumentsError { + name: String::from("rand::string"), + message: String::from("To generate a string of between X and Y characters in length, the 2 arguments must be positive numbers."), + }), + }, + 1 => match args.remove(0).as_int() { + x if x >= 0 => Ok(rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(x as usize) + .map(char::from) + .collect::() + .into()), + _ => Err(Error::ArgumentsError { + name: String::from("rand::string"), + message: String::from("To generate a string of X characters in length, the argument must be a positive number."), + }), + }, + 0 => Ok(rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(32) + .map(char::from) + .collect::() + .into()), + _ => unreachable!(), + } +} + +pub fn time(_: &Runtime, mut args: Vec) -> Result { + match args.len() { + 2 => match args.remove(0).as_int() { + min => match args.remove(0).as_int() { + max if max < min => { + let i = rand::thread_rng().gen_range(max..=min); + Ok(Datetime::from(i).into()) + } + max => { + let i = rand::thread_rng().gen_range(min..=max); + Ok(Datetime::from(i).into()) + } + }, + }, + 0 => { + let i = rand::random::(); + Ok(Datetime::from(i).into()) + } + _ => unreachable!(), + } +} + +pub fn uuid(_: &Runtime, _: Vec) -> Result { + Ok(Uuid::new_v4().to_string().into()) +} diff --git a/src/fnc/script.rs b/src/fnc/script.rs new file mode 100644 index 00000000..415c90f2 --- /dev/null +++ b/src/fnc/script.rs @@ -0,0 +1,10 @@ +use crate::dbs::Runtime; +use crate::err::Error; +use crate::sql::script::Script; +use crate::sql::value::Value; + +pub fn run(ctx: &Runtime, expr: Script) -> Result { + Err(Error::LanguageError { + message: String::from("Embedded functions are not yet supported."), + }) +} diff --git a/src/fnc/string.rs b/src/fnc/string.rs new file mode 100644 index 00000000..68d4da81 --- /dev/null +++ b/src/fnc/string.rs @@ -0,0 +1,99 @@ +use crate::dbs::Runtime; +use crate::err::Error; +use crate::sql::value::Value; +use slug::slugify; + +pub fn concat(_: &Runtime, args: Vec) -> Result { + Ok(args.into_iter().map(|x| x.as_strand().value).collect::>().concat().into()) +} + +pub fn contains(_: &Runtime, mut args: Vec) -> Result { + let val = args.remove(0).as_strand().value; + let str = args.remove(0).as_strand().value; + Ok(val.contains(&str).into()) +} + +pub fn ends_with(_: &Runtime, mut args: Vec) -> Result { + let val = args.remove(0).as_strand().value; + let chr = args.remove(0).as_strand().value; + Ok(val.ends_with(&chr).into()) +} + +pub fn join(_: &Runtime, mut args: Vec) -> Result { + let chr = args.remove(0).as_strand().value; + let val = args.into_iter().map(|x| x.as_strand().value); + let val = val.collect::>().join(&chr); + Ok(val.into()) +} + +pub fn length(_: &Runtime, mut args: Vec) -> Result { + let val = args.remove(0).as_strand().value; + let num = val.chars().count() as i64; + Ok(num.into()) +} + +pub fn lowercase(_: &Runtime, mut args: Vec) -> Result { + Ok(args.remove(0).as_strand().value.to_lowercase().into()) +} + +pub fn repeat(_: &Runtime, mut args: Vec) -> Result { + let val = args.remove(0).as_strand().value; + let num = args.remove(0).as_int() as usize; + Ok(val.repeat(num).into()) +} + +pub fn replace(_: &Runtime, mut args: Vec) -> Result { + let val = args.remove(0).as_strand().value; + let old = args.remove(0).as_strand().value; + let new = args.remove(0).as_strand().value; + Ok(val.replace(&old, &new).into()) +} + +pub fn reverse(_: &Runtime, mut args: Vec) -> Result { + Ok(args.remove(0).as_strand().value.chars().rev().collect::().into()) +} + +pub fn slice(_: &Runtime, mut args: Vec) -> Result { + let val = args.remove(0).as_strand().value; + let beg = args.remove(0).as_int() as usize; + let lim = args.remove(0).as_int() as usize; + let val = val.chars().skip(beg).take(lim).collect::(); + Ok(val.into()) +} + +pub fn slug(_: &Runtime, mut args: Vec) -> Result { + Ok(slugify(&args.remove(0).as_strand().value).into()) +} + +pub fn split(_: &Runtime, mut args: Vec) -> Result { + let val = args.remove(0).as_strand().value; + let chr = args.remove(0).as_strand().value; + let val = val.split(&chr).collect::>(); + Ok(val.into()) +} + +pub fn starts_with(_: &Runtime, mut args: Vec) -> Result { + let val = args.remove(0).as_strand().value; + let chr = args.remove(0).as_strand().value; + Ok(val.starts_with(&chr).into()) +} + +pub fn substr(_: &Runtime, mut args: Vec) -> Result { + let val = args.remove(0).as_strand(); + let beg = args.remove(0).as_int() as usize; + let lim = args.remove(0).as_int() as usize; + let val = val.value.chars().skip(beg).take(lim).collect::(); + Ok(val.into()) +} + +pub fn trim(_: &Runtime, mut args: Vec) -> Result { + Ok(args.remove(0).as_strand().value.trim().into()) +} + +pub fn uppercase(_: &Runtime, mut args: Vec) -> Result { + Ok(args.remove(0).as_strand().value.to_uppercase().into()) +} + +pub fn words(_: &Runtime, mut args: Vec) -> Result { + Ok(args.remove(0).as_strand().value.split(" ").collect::>().into()) +} diff --git a/src/fnc/time.rs b/src/fnc/time.rs new file mode 100644 index 00000000..6abb103c --- /dev/null +++ b/src/fnc/time.rs @@ -0,0 +1,154 @@ +use crate::dbs::Runtime; +use crate::err::Error; +use crate::sql::datetime::Datetime; +use crate::sql::value::Value; +use chrono::Datelike; +use chrono::DurationRound; +use chrono::Timelike; +use chrono::Utc; + +pub fn day(_: &Runtime, mut args: Vec) -> Result { + match args.len() { + 0 => Ok(Utc::now().day().into()), + _ => match args.remove(0) { + Value::Datetime(v) => Ok(v.value.day().into()), + _ => Ok(Value::None), + }, + } +} + +pub fn floor(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Datetime(v) => match args.remove(0) { + Value::Duration(w) => match chrono::Duration::from_std(w.value) { + Ok(d) => match v.value.duration_trunc(d) { + Ok(v) => Ok(v.into()), + _ => Ok(Value::None), + }, + _ => Ok(Value::None), + }, + _ => Ok(Value::None), + }, + _ => Ok(Value::None), + } +} + +pub fn hour(_: &Runtime, mut args: Vec) -> Result { + match args.len() { + 0 => Ok(Utc::now().hour().into()), + _ => match args.remove(0) { + Value::Datetime(v) => Ok(v.value.hour().into()), + _ => Ok(Value::None), + }, + } +} + +pub fn mins(_: &Runtime, mut args: Vec) -> Result { + match args.len() { + 0 => Ok(Utc::now().minute().into()), + _ => match args.remove(0) { + Value::Datetime(v) => Ok(v.value.minute().into()), + _ => Ok(Value::None), + }, + } +} + +pub fn month(_: &Runtime, mut args: Vec) -> Result { + match args.len() { + 0 => Ok(Utc::now().day().into()), + _ => match args.remove(0) { + Value::Datetime(v) => Ok(v.value.day().into()), + _ => Ok(Value::None), + }, + } +} + +pub fn nano(_: &Runtime, mut args: Vec) -> Result { + match args.len() { + 0 => Ok(Utc::now().timestamp_nanos().into()), + _ => match args.remove(0) { + Value::Datetime(v) => Ok(v.value.timestamp_nanos().into()), + _ => Ok(Value::None), + }, + } +} + +pub fn now(_: &Runtime, _: Vec) -> Result { + Ok(Datetime::default().into()) +} + +pub fn round(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Datetime(v) => match args.remove(0) { + Value::Duration(w) => match chrono::Duration::from_std(w.value) { + Ok(d) => match v.value.duration_round(d) { + Ok(v) => Ok(v.into()), + _ => Ok(Value::None), + }, + _ => Ok(Value::None), + }, + _ => Ok(Value::None), + }, + _ => Ok(Value::None), + } +} + +pub fn secs(_: &Runtime, mut args: Vec) -> Result { + match args.len() { + 0 => Ok(Utc::now().second().into()), + _ => match args.remove(0) { + Value::Datetime(v) => Ok(v.value.second().into()), + _ => Ok(Value::None), + }, + } +} + +pub fn unix(_: &Runtime, mut args: Vec) -> Result { + match args.len() { + 0 => Ok(Utc::now().timestamp().into()), + _ => match args.remove(0) { + Value::Datetime(v) => Ok(v.value.timestamp().into()), + _ => Ok(Value::None), + }, + } +} + +pub fn wday(_: &Runtime, mut args: Vec) -> Result { + match args.len() { + 0 => Ok(Utc::now().weekday().number_from_monday().into()), + _ => match args.remove(0) { + Value::Datetime(v) => Ok(v.value.weekday().number_from_monday().into()), + _ => Ok(Value::None), + }, + } +} + +pub fn week(_: &Runtime, mut args: Vec) -> Result { + match args.len() { + 0 => Ok(Utc::now().iso_week().week().into()), + _ => match args.remove(0) { + Value::Datetime(v) => Ok(v.value.iso_week().week().into()), + _ => Ok(Value::None), + }, + } +} + +pub fn yday(_: &Runtime, mut args: Vec) -> Result { + match args.len() { + 0 => Ok(Utc::now().ordinal().into()), + _ => match args.remove(0) { + Value::Datetime(v) => Ok(v.value.ordinal().into()), + _ => Ok(Value::None), + }, + } +} + +pub fn year(_: &Runtime, mut args: Vec) -> Result { + match args.len() { + 0 => Ok(Utc::now().year().into()), + _ => match args.remove(0) { + Value::Datetime(v) => Ok(v.value.year().into()), + _ => Ok(Value::None), + }, + } +} diff --git a/src/fnc/type.rs b/src/fnc/type.rs new file mode 100644 index 00000000..96d1a96e --- /dev/null +++ b/src/fnc/type.rs @@ -0,0 +1,106 @@ +use crate::dbs::Runtime; +use crate::err::Error; +use crate::sql::geometry::Geometry; +use crate::sql::number::Number; +use crate::sql::thing::Thing; +use crate::sql::value::Value; + +pub fn bool(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0).is_truthy() { + true => Ok(Value::True), + false => Ok(Value::False), + } +} + +pub fn datetime(_: &Runtime, mut args: Vec) -> Result { + let val = args.remove(0); + match val { + Value::Datetime(_) => Ok(val), + _ => Ok(Value::Datetime(val.as_datetime())), + } +} + +pub fn decimal(_: &Runtime, mut args: Vec) -> Result { + let val = args.remove(0); + match val { + Value::Number(Number::Decimal(_)) => Ok(val), + _ => Ok(Value::Number(Number::Decimal(val.as_decimal()))), + } +} + +pub fn duration(_: &Runtime, mut args: Vec) -> Result { + let val = args.remove(0); + match val { + Value::Duration(_) => Ok(val), + _ => Ok(Value::Duration(val.as_duration())), + } +} + +pub fn float(_: &Runtime, mut args: Vec) -> Result { + let val = args.remove(0); + match val { + Value::Number(Number::Float(_)) => Ok(val), + _ => Ok(Value::Number(Number::Float(val.as_float()))), + } +} + +pub fn int(_: &Runtime, mut args: Vec) -> Result { + let val = args.remove(0); + match val { + Value::Number(Number::Int(_)) => Ok(val), + _ => Ok(Value::Number(Number::Int(val.as_int()))), + } +} + +pub fn number(_: &Runtime, mut args: Vec) -> Result { + let val = args.remove(0); + match val { + Value::Number(_) => Ok(val), + _ => Ok(Value::Number(val.as_number())), + } +} + +pub fn point(_: &Runtime, mut args: Vec) -> Result { + match args.len() { + 2 => match args.remove(0) { + x => match args.remove(0) { + y => Ok((x.as_float(), y.as_float()).into()), + }, + }, + 1 => match args.remove(0) { + Value::Array(v) if v.len() == 2 => Ok(v.as_point().into()), + Value::Geometry(v) => match v { + Geometry::Point(v) => Ok(v.into()), + _ => Ok(Value::None), + }, + _ => Ok(Value::None), + }, + _ => unreachable!(), + } +} + +pub fn regex(_: &Runtime, mut args: Vec) -> Result { + match args.remove(0) { + Value::Strand(v) => Ok(Value::Regex(v.as_str().into())), + _ => Ok(Value::None), + } +} + +pub fn string(_: &Runtime, mut args: Vec) -> Result { + let val = args.remove(0); + match val { + Value::Strand(_) => Ok(val), + _ => Ok(Value::Strand(val.as_strand())), + } +} + +pub fn table(_: &Runtime, mut args: Vec) -> Result { + Ok(Value::Table(args.remove(0).as_strand().value.into())) +} + +pub fn thing(_: &Runtime, mut args: Vec) -> Result { + Ok(Value::Thing(Thing { + tb: args.remove(0).as_strand().value, + id: args.remove(0).as_strand().value, + })) +} diff --git a/src/fnc/util/geo/mod.rs b/src/fnc/util/geo/mod.rs new file mode 100644 index 00000000..29848e47 --- /dev/null +++ b/src/fnc/util/geo/mod.rs @@ -0,0 +1,101 @@ +use crate::sql::geometry::Geometry; +use crate::sql::strand::Strand; +use geo::Point; + +static BASE32: &[char] = &[ + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', + 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', +]; + +pub fn encode(v: Point, l: usize) -> Strand { + let mut max_lat = 90f64; + let mut min_lat = -90f64; + let mut max_lon = 180f64; + let mut min_lon = -180f64; + + let mut bits: i8 = 0; + let mut hash: usize = 0; + let mut out = String::with_capacity(l); + + while out.len() < l { + for _ in 0..5 { + if bits % 2 == 0 { + let mid = (max_lon + min_lon) / 2f64; + if v.x() > mid { + hash = (hash << 1) + 1usize; + min_lon = mid; + } else { + hash <<= 1; + max_lon = mid; + } + } else { + let mid = (max_lat + min_lat) / 2f64; + if v.y() > mid { + hash = (hash << 1) + 1usize; + min_lat = mid; + } else { + hash <<= 1; + max_lat = mid; + } + } + bits += 1; + } + out.push(BASE32[hash]); + hash = 0; + } + + Strand::from(out) +} + +pub fn decode(v: Strand) -> Geometry { + let mut max_lat = 90f64; + let mut min_lat = -90f64; + let mut max_lon = 180f64; + let mut min_lon = -180f64; + + let mut mid: f64; + let mut long: bool = true; + + for c in v.as_str().chars() { + let ord = c as usize; + + let val = if (48..=57).contains(&ord) { + ord - 48 + } else if (98..=104).contains(&ord) { + ord - 88 + } else if (106..=107).contains(&ord) { + ord - 89 + } else if (109..=110).contains(&ord) { + ord - 90 + } else if (112..=122).contains(&ord) { + ord - 91 + } else { + ord + }; + + for i in 0..5 { + let bit = (val >> (4 - i)) & 1usize; + if long { + mid = (max_lon + min_lon) / 2f64; + if bit == 1 { + min_lon = mid; + } else { + max_lon = mid; + } + } else { + mid = (max_lat + min_lat) / 2f64; + if bit == 1 { + min_lat = mid; + } else { + max_lat = mid; + } + } + long = !long; + } + } + + let x = (min_lon + max_lon) / 2f64; + let y = (min_lat + max_lat) / 2f64; + + (x, y).into() +} diff --git a/src/fnc/util/http/mod.rs b/src/fnc/util/http/mod.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/fnc/util/http/mod.rs @@ -0,0 +1 @@ + diff --git a/src/fnc/util/math/bottom.rs b/src/fnc/util/math/bottom.rs new file mode 100644 index 00000000..af6eece4 --- /dev/null +++ b/src/fnc/util/math/bottom.rs @@ -0,0 +1,11 @@ +use crate::sql::number::Number; + +pub trait Bottom { + fn bottom(self, _c: i64) -> Number; +} + +impl Bottom for Vec { + fn bottom(self, _c: i64) -> Number { + todo!() + } +} diff --git a/src/fnc/util/math/deviation.rs b/src/fnc/util/math/deviation.rs new file mode 100644 index 00000000..bdd4e414 --- /dev/null +++ b/src/fnc/util/math/deviation.rs @@ -0,0 +1,11 @@ +use crate::sql::number::Number; + +pub trait Deviation { + fn deviation(self) -> Number; +} + +impl Deviation for Vec { + fn deviation(self) -> Number { + todo!() + } +} diff --git a/src/fnc/util/math/interquartile.rs b/src/fnc/util/math/interquartile.rs new file mode 100644 index 00000000..ad2a57c6 --- /dev/null +++ b/src/fnc/util/math/interquartile.rs @@ -0,0 +1,11 @@ +use crate::sql::number::Number; + +pub trait Interquartile { + fn interquartile(self) -> Number; +} + +impl Interquartile for Vec { + fn interquartile(self) -> Number { + todo!() + } +} diff --git a/src/fnc/util/math/mean.rs b/src/fnc/util/math/mean.rs new file mode 100644 index 00000000..240c0145 --- /dev/null +++ b/src/fnc/util/math/mean.rs @@ -0,0 +1,13 @@ +use crate::sql::number::Number; + +pub trait Mean { + fn mean(&self) -> Number; +} + +impl Mean for Vec { + fn mean(&self) -> Number { + let len = Number::from(self.len()); + let sum = self.iter().sum::(); + sum / len + } +} diff --git a/src/fnc/util/math/median.rs b/src/fnc/util/math/median.rs new file mode 100644 index 00000000..c080cd29 --- /dev/null +++ b/src/fnc/util/math/median.rs @@ -0,0 +1,12 @@ +use crate::sql::number::Number; + +pub trait Median { + fn median(&mut self) -> Number; +} + +impl Median for Vec { + fn median(&mut self) -> Number { + self.sort(); + self.remove(self.len() / 2) + } +} diff --git a/src/fnc/util/math/midhinge.rs b/src/fnc/util/math/midhinge.rs new file mode 100644 index 00000000..cc0e3a66 --- /dev/null +++ b/src/fnc/util/math/midhinge.rs @@ -0,0 +1,11 @@ +use crate::sql::number::Number; + +pub trait Midhinge { + fn midhinge(self) -> Number; +} + +impl Midhinge for Vec { + fn midhinge(self) -> Number { + todo!() + } +} diff --git a/src/fnc/util/math/mod.rs b/src/fnc/util/math/mod.rs new file mode 100644 index 00000000..c96a22bd --- /dev/null +++ b/src/fnc/util/math/mod.rs @@ -0,0 +1,18 @@ +// TODO +// https://docs.rs/statistical/1.0.0/src/statistical/stats_.rs.html +// https://rust-lang-nursery.github.io/rust-cookbook/science/mathematics/statistics.html + +pub mod bottom; +pub mod deviation; +pub mod interquartile; +pub mod mean; +pub mod median; +pub mod midhinge; +pub mod mode; +pub mod nearestrank; +pub mod percentile; +pub mod quartile; +pub mod spread; +pub mod top; +pub mod trimean; +pub mod variance; diff --git a/src/fnc/util/math/mode.rs b/src/fnc/util/math/mode.rs new file mode 100644 index 00000000..61d50ac7 --- /dev/null +++ b/src/fnc/util/math/mode.rs @@ -0,0 +1,11 @@ +use crate::sql::number::Number; + +pub trait Mode { + fn mode(self) -> Number; +} + +impl Mode for Vec { + fn mode(self) -> Number { + todo!() + } +} diff --git a/src/fnc/util/math/nearestrank.rs b/src/fnc/util/math/nearestrank.rs new file mode 100644 index 00000000..025adeda --- /dev/null +++ b/src/fnc/util/math/nearestrank.rs @@ -0,0 +1,11 @@ +use crate::sql::number::Number; + +pub trait Nearestrank { + fn nearestrank(self, _: Number) -> Number; +} + +impl Nearestrank for Vec { + fn nearestrank(self, _: Number) -> Number { + todo!() + } +} diff --git a/src/fnc/util/math/percentile.rs b/src/fnc/util/math/percentile.rs new file mode 100644 index 00000000..9e738446 --- /dev/null +++ b/src/fnc/util/math/percentile.rs @@ -0,0 +1,11 @@ +use crate::sql::number::Number; + +pub trait Percentile { + fn percentile(self, _: Number) -> Number; +} + +impl Percentile for Vec { + fn percentile(self, _: Number) -> Number { + todo!() + } +} diff --git a/src/fnc/util/math/quartile.rs b/src/fnc/util/math/quartile.rs new file mode 100644 index 00000000..13496fa4 --- /dev/null +++ b/src/fnc/util/math/quartile.rs @@ -0,0 +1,11 @@ +use crate::sql::number::Number; + +pub trait Quartile { + fn quartile(self) -> Number; +} + +impl Quartile for Vec { + fn quartile(self) -> Number { + todo!() + } +} diff --git a/src/fnc/util/math/spread.rs b/src/fnc/util/math/spread.rs new file mode 100644 index 00000000..da542e2b --- /dev/null +++ b/src/fnc/util/math/spread.rs @@ -0,0 +1,11 @@ +use crate::sql::number::Number; + +pub trait Spread { + fn spread(self) -> Number; +} + +impl Spread for Vec { + fn spread(self) -> Number { + todo!() + } +} diff --git a/src/fnc/util/math/top.rs b/src/fnc/util/math/top.rs new file mode 100644 index 00000000..6bdaa1d3 --- /dev/null +++ b/src/fnc/util/math/top.rs @@ -0,0 +1,11 @@ +use crate::sql::number::Number; + +pub trait Top { + fn top(self, _c: i64) -> Number; +} + +impl Top for Vec { + fn top(self, _c: i64) -> Number { + todo!() + } +} diff --git a/src/fnc/util/math/trimean.rs b/src/fnc/util/math/trimean.rs new file mode 100644 index 00000000..5321cab8 --- /dev/null +++ b/src/fnc/util/math/trimean.rs @@ -0,0 +1,11 @@ +use crate::sql::number::Number; + +pub trait Trimean { + fn trimean(self) -> Number; +} + +impl Trimean for Vec { + fn trimean(self) -> Number { + todo!() + } +} diff --git a/src/fnc/util/math/variance.rs b/src/fnc/util/math/variance.rs new file mode 100644 index 00000000..b23e70dd --- /dev/null +++ b/src/fnc/util/math/variance.rs @@ -0,0 +1,11 @@ +use crate::sql::number::Number; + +pub trait Variance { + fn variance(self) -> Number; +} + +impl Variance for Vec { + fn variance(self) -> Number { + todo!() + } +} diff --git a/src/fnc/util/mod.rs b/src/fnc/util/mod.rs new file mode 100644 index 00000000..a529dc13 --- /dev/null +++ b/src/fnc/util/mod.rs @@ -0,0 +1,3 @@ +pub mod geo; +pub mod http; +pub mod math;