use super::basic::BasicPredicate;
use super::field_predicate::{FieldPredicate, LookupOp};
use crate::cacheable::Field;
use crate::jsahibon::{JObject, JSahibON, compare_jsahibon_numbers};
use std::any::Any;
use std::cmp::Ordering;
use std::marker::PhantomData;
use std::sync::Arc;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct JPath(Arc<[String]>);
impl JPath {
pub fn root() -> Self {
Self(Arc::from([]))
}
pub fn from_segments<I, S>(segments: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self(
segments
.into_iter()
.map(Into::into)
.collect::<Vec<_>>()
.into(),
)
}
pub fn parse_dotted(path: &'static str) -> Self {
let segments = path.split('.').collect::<Vec<_>>();
assert!(
segments.iter().all(|segment| valid_plain_segment(segment)),
"JSahibON dotted paths require non-empty ASCII identifier segments of at most 63 bytes"
);
Self::from_segments(segments)
}
pub fn segments(&self) -> &[String] {
&self.0
}
fn push(&self, key: String) -> Self {
let mut segments = Vec::with_capacity(self.0.len() + 1);
segments.extend(self.0.iter().cloned());
segments.push(key);
Self(segments.into())
}
fn extend<I, S>(&self, segments: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let mut next = Vec::from(self.0.as_ref());
next.extend(segments.into_iter().map(Into::into));
Self(next.into())
}
}
impl Default for JPath {
fn default() -> Self {
Self::root()
}
}
impl FromIterator<String> for JPath {
fn from_iter<T: IntoIterator<Item = String>>(iter: T) -> Self {
Self::from_segments(iter)
}
}
impl<'a> FromIterator<&'a str> for JPath {
fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
Self::from_segments(iter)
}
}
fn valid_plain_segment(segment: &str) -> bool {
let mut bytes = segment.bytes();
let Some(first) = bytes.next() else {
return false;
};
if segment.len() > 63 || !(first.is_ascii_alphabetic() || first == b'_') {
return false;
}
bytes.all(|byte| byte.is_ascii_alphanumeric() || byte == b'_')
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum JScalarKind {
I64,
U64,
F64,
String,
Bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum JTypeKind {
Null,
Bool,
Number,
String,
Array,
Object,
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum JScalarValue {
I64(i64),
U64(u64),
F64(crate::JFiniteF64),
String(String),
Bool(bool),
}
impl PartialEq for JScalarValue {
fn eq(&self, other: &Self) -> bool {
scalar_to_jsahibon(self) == scalar_to_jsahibon(other)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum JCompareOp {
Eq,
Neq,
Gt,
Gte,
Lt,
Lte,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum JInPolarity {
In,
NotIn,
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum JSahibONPredicateBody {
Exists {
path: JPath,
},
Missing {
path: JPath,
},
IsJsonNull {
path: JPath,
},
IsNotJsonNull {
path: JPath,
},
Type {
path: JPath,
kind: JTypeKind,
},
HasKey {
path: JPath,
key: Arc<String>,
},
HasAnyKey {
path: JPath,
keys: Arc<[String]>,
},
HasAllKeys {
path: JPath,
keys: Arc<[String]>,
},
ScalarCompare {
path: JPath,
op: JCompareOp,
scalar_kind: JScalarKind,
operand: JScalarValue,
},
ScalarIn {
path: JPath,
scalar_kind: JScalarKind,
operands: Arc<[JScalarValue]>,
polarity: JInPolarity,
},
ScalarBetween {
path: JPath,
scalar_kind: JScalarKind,
low: JScalarValue,
high: JScalarValue,
},
JsonEq {
path: JPath,
value: JSahibON,
},
JsonNeq {
path: JPath,
value: JSahibON,
},
ArrayContains {
path: JPath,
element: JSahibON,
},
ArrayLen {
path: JPath,
op: JCompareOp,
len: u64,
},
}
pub trait JScalar: private::Sealed + Send + Sync + 'static {
const KIND: JScalarKind;
fn into_scalar_value(self) -> JScalarValue;
}
pub trait JOrderedScalar: JScalar {}
mod private {
pub trait Sealed {}
}
impl private::Sealed for i64 {}
impl JScalar for i64 {
const KIND: JScalarKind = JScalarKind::I64;
fn into_scalar_value(self) -> JScalarValue {
JScalarValue::I64(self)
}
}
impl JOrderedScalar for i64 {}
impl private::Sealed for u64 {}
impl JScalar for u64 {
const KIND: JScalarKind = JScalarKind::U64;
fn into_scalar_value(self) -> JScalarValue {
JScalarValue::U64(self)
}
}
impl JOrderedScalar for u64 {}
impl private::Sealed for f64 {}
impl JScalar for f64 {
const KIND: JScalarKind = JScalarKind::F64;
fn into_scalar_value(self) -> JScalarValue {
JScalarValue::F64(
crate::JFiniteF64::try_new(self)
.expect("JSahibON scalar predicates only accept finite f64 operands"),
)
}
}
impl JOrderedScalar for f64 {}
impl private::Sealed for String {}
impl JScalar for String {
const KIND: JScalarKind = JScalarKind::String;
fn into_scalar_value(self) -> JScalarValue {
JScalarValue::String(self)
}
}
impl private::Sealed for bool {}
impl JScalar for bool {
const KIND: JScalarKind = JScalarKind::Bool;
fn into_scalar_value(self) -> JScalarValue {
JScalarValue::Bool(self)
}
}
enum JRoot<T> {
Required(fn(&T) -> &JSahibON),
Optional(fn(&T) -> &Option<JSahibON>),
}
impl<T> Copy for JRoot<T> {}
impl<T> Clone for JRoot<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> JRoot<T> {
fn resolve(self, value: &T) -> Option<&JSahibON> {
match self {
Self::Required(extract) => Some(extract(value)),
Self::Optional(extract) => extract(value).as_ref(),
}
}
}
pub struct JSahibONPathRef<T> {
field_name: &'static str,
root: JRoot<T>,
path: JPath,
}
pub struct JSahibONFieldRef<T> {
inner: JSahibONPathRef<T>,
}
pub struct JSahibONOptionFieldRef<T> {
inner: JSahibONPathRef<T>,
}
pub struct JSahibONValueRef<T, V> {
inner: JSahibONPathRef<T>,
_marker: PhantomData<V>,
}
impl<T> Clone for JSahibONPathRef<T> {
fn clone(&self) -> Self {
Self {
field_name: self.field_name,
root: self.root,
path: self.path.clone(),
}
}
}
impl<T> Clone for JSahibONFieldRef<T> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
impl<T> Clone for JSahibONOptionFieldRef<T> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
impl<T, V> Clone for JSahibONValueRef<T, V> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
_marker: PhantomData,
}
}
}
impl<T: 'static> Field<T, JSahibON> {
pub fn jsahibon(&self) -> JSahibONFieldRef<T> {
JSahibONFieldRef {
inner: JSahibONPathRef {
field_name: self.name,
root: JRoot::Required(self.extract),
path: JPath::root(),
},
}
}
}
impl<T: 'static> Field<T, Option<JSahibON>> {
pub fn jsahibon(&self) -> JSahibONOptionFieldRef<T> {
JSahibONOptionFieldRef {
inner: JSahibONPathRef {
field_name: self.name,
root: JRoot::Optional(self.extract),
path: JPath::root(),
},
}
}
}
macro_rules! delegate_json_ref_methods {
($ty:ident) => {
impl<T: 'static> $ty<T> {
pub fn root(&self) -> JSahibONPathRef<T> {
let mut inner = self.inner.clone();
inner.path = JPath::root();
inner
}
pub fn path(&self, dotted_plain_idents: &'static str) -> JSahibONPathRef<T> {
let mut inner = self.inner.clone();
inner.path = JPath::parse_dotted(dotted_plain_idents);
inner
}
pub fn key(&self, key: impl Into<String>) -> JSahibONPathRef<T> {
let mut inner = self.inner.clone();
inner.path = inner.path.push(key.into());
inner
}
pub fn path_segments<I, S>(&self, segments: I) -> JSahibONPathRef<T>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let mut inner = self.inner.clone();
inner.path = JPath::from_segments(segments);
inner
}
pub fn exists(&self) -> BasicPredicate<T> {
self.inner.exists()
}
pub fn missing(&self) -> BasicPredicate<T> {
self.inner.missing()
}
pub fn is_json_null(&self) -> BasicPredicate<T> {
self.inner.is_json_null()
}
pub fn is_not_json_null(&self) -> BasicPredicate<T> {
self.inner.is_not_json_null()
}
pub fn is_type(&self, kind: JTypeKind) -> BasicPredicate<T> {
self.inner.is_type(kind)
}
pub fn is_bool(&self) -> BasicPredicate<T> {
self.inner.is_bool()
}
pub fn is_number(&self) -> BasicPredicate<T> {
self.inner.is_number()
}
pub fn is_string(&self) -> BasicPredicate<T> {
self.inner.is_string()
}
pub fn is_array(&self) -> BasicPredicate<T> {
self.inner.is_array()
}
pub fn is_object(&self) -> BasicPredicate<T> {
self.inner.is_object()
}
pub fn has_key(&self, key: impl Into<String>) -> BasicPredicate<T> {
self.inner.has_key(key)
}
pub fn has_any_key<I, S>(&self, keys: I) -> BasicPredicate<T>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.inner.has_any_key(keys)
}
pub fn has_all_keys<I, S>(&self, keys: I) -> BasicPredicate<T>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.inner.has_all_keys(keys)
}
pub fn value<V: JScalar>(&self) -> JSahibONValueRef<T, V> {
self.inner.value()
}
pub fn eq_json(&self, value: JSahibON) -> BasicPredicate<T> {
self.inner.eq_json(value)
}
pub fn neq_json(&self, value: JSahibON) -> BasicPredicate<T> {
self.inner.neq_json(value)
}
pub fn array_contains(&self, element: JSahibON) -> BasicPredicate<T> {
self.inner.array_contains(element)
}
pub fn array_len_eq(&self, len: usize) -> BasicPredicate<T> {
self.inner.array_len_eq(len)
}
pub fn array_len_gt(&self, len: usize) -> BasicPredicate<T> {
self.inner.array_len_gt(len)
}
pub fn array_len_gte(&self, len: usize) -> BasicPredicate<T> {
self.inner.array_len_gte(len)
}
pub fn array_len_lt(&self, len: usize) -> BasicPredicate<T> {
self.inner.array_len_lt(len)
}
pub fn array_len_lte(&self, len: usize) -> BasicPredicate<T> {
self.inner.array_len_lte(len)
}
}
};
}
delegate_json_ref_methods!(JSahibONFieldRef);
delegate_json_ref_methods!(JSahibONOptionFieldRef);
impl<T: 'static> JSahibONPathRef<T> {
pub fn key(self, key: impl Into<String>) -> Self {
Self {
path: self.path.push(key.into()),
..self
}
}
pub fn path_segments<I, S>(self, segments: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
Self {
path: self.path.extend(segments),
..self
}
}
pub fn exists(&self) -> BasicPredicate<T> {
self.predicate(JSahibONPredicateBody::Exists {
path: self.path.clone(),
})
}
pub fn missing(&self) -> BasicPredicate<T> {
self.predicate(JSahibONPredicateBody::Missing {
path: self.path.clone(),
})
}
pub fn is_json_null(&self) -> BasicPredicate<T> {
self.predicate(JSahibONPredicateBody::IsJsonNull {
path: self.path.clone(),
})
}
pub fn is_not_json_null(&self) -> BasicPredicate<T> {
self.predicate(JSahibONPredicateBody::IsNotJsonNull {
path: self.path.clone(),
})
}
pub fn is_type(&self, kind: JTypeKind) -> BasicPredicate<T> {
self.predicate(JSahibONPredicateBody::Type {
path: self.path.clone(),
kind,
})
}
pub fn is_bool(&self) -> BasicPredicate<T> {
self.is_type(JTypeKind::Bool)
}
pub fn is_number(&self) -> BasicPredicate<T> {
self.is_type(JTypeKind::Number)
}
pub fn is_string(&self) -> BasicPredicate<T> {
self.is_type(JTypeKind::String)
}
pub fn is_array(&self) -> BasicPredicate<T> {
self.is_type(JTypeKind::Array)
}
pub fn is_object(&self) -> BasicPredicate<T> {
self.is_type(JTypeKind::Object)
}
pub fn has_key(&self, key: impl Into<String>) -> BasicPredicate<T> {
self.predicate(JSahibONPredicateBody::HasKey {
path: self.path.clone(),
key: Arc::new(key.into()),
})
}
pub fn has_any_key<I, S>(&self, keys: I) -> BasicPredicate<T>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.predicate(JSahibONPredicateBody::HasAnyKey {
path: self.path.clone(),
keys: keys.into_iter().map(Into::into).collect::<Vec<_>>().into(),
})
}
pub fn has_all_keys<I, S>(&self, keys: I) -> BasicPredicate<T>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.predicate(JSahibONPredicateBody::HasAllKeys {
path: self.path.clone(),
keys: keys.into_iter().map(Into::into).collect::<Vec<_>>().into(),
})
}
pub fn value<V: JScalar>(&self) -> JSahibONValueRef<T, V> {
JSahibONValueRef {
inner: self.clone(),
_marker: PhantomData,
}
}
pub fn eq_json(&self, value: JSahibON) -> BasicPredicate<T> {
self.predicate(JSahibONPredicateBody::JsonEq {
path: self.path.clone(),
value,
})
}
pub fn neq_json(&self, value: JSahibON) -> BasicPredicate<T> {
self.predicate(JSahibONPredicateBody::JsonNeq {
path: self.path.clone(),
value,
})
}
pub fn array_contains(&self, element: JSahibON) -> BasicPredicate<T> {
self.predicate(JSahibONPredicateBody::ArrayContains {
path: self.path.clone(),
element,
})
}
pub fn array_len_eq(&self, len: usize) -> BasicPredicate<T> {
self.array_len(JCompareOp::Eq, len)
}
pub fn array_len_gt(&self, len: usize) -> BasicPredicate<T> {
self.array_len(JCompareOp::Gt, len)
}
pub fn array_len_gte(&self, len: usize) -> BasicPredicate<T> {
self.array_len(JCompareOp::Gte, len)
}
pub fn array_len_lt(&self, len: usize) -> BasicPredicate<T> {
self.array_len(JCompareOp::Lt, len)
}
pub fn array_len_lte(&self, len: usize) -> BasicPredicate<T> {
self.array_len(JCompareOp::Lte, len)
}
fn array_len(&self, op: JCompareOp, len: usize) -> BasicPredicate<T> {
let len = u64::try_from(len).expect("array length predicate exceeds u64");
self.predicate(JSahibONPredicateBody::ArrayLen {
path: self.path.clone(),
op,
len,
})
}
fn predicate(&self, body: JSahibONPredicateBody) -> BasicPredicate<T> {
let root = self.root;
let body: Arc<JSahibONPredicateBody> = Arc::new(body);
let body_for_eval = body.clone();
let value: Arc<dyn Any + Send + Sync> = body;
BasicPredicate::Field(FieldPredicate::new(
self.field_name,
LookupOp::Json,
value,
move |entry| evaluate_jsahibon_predicate(root.resolve(entry), body_for_eval.as_ref()),
))
}
}
impl<T: 'static, V: JScalar> JSahibONValueRef<T, V> {
pub fn eq(&self, value: V) -> BasicPredicate<T> {
self.compare(JCompareOp::Eq, value)
}
pub fn neq(&self, value: V) -> BasicPredicate<T> {
self.compare(JCompareOp::Neq, value)
}
pub fn in_(&self, values: Vec<V>) -> BasicPredicate<T> {
self.in_predicate(values, JInPolarity::In)
}
pub fn not_in(&self, values: Vec<V>) -> BasicPredicate<T> {
self.in_predicate(values, JInPolarity::NotIn)
}
fn compare(&self, op: JCompareOp, value: V) -> BasicPredicate<T> {
self.inner.predicate(JSahibONPredicateBody::ScalarCompare {
path: self.inner.path.clone(),
op,
scalar_kind: V::KIND,
operand: value.into_scalar_value(),
})
}
fn in_predicate(&self, values: Vec<V>, polarity: JInPolarity) -> BasicPredicate<T> {
self.inner.predicate(JSahibONPredicateBody::ScalarIn {
path: self.inner.path.clone(),
scalar_kind: V::KIND,
operands: values
.into_iter()
.map(JScalar::into_scalar_value)
.collect::<Vec<_>>()
.into(),
polarity,
})
}
}
impl<T: 'static, V: JOrderedScalar> JSahibONValueRef<T, V> {
pub fn gt(&self, value: V) -> BasicPredicate<T> {
self.compare(JCompareOp::Gt, value)
}
pub fn gte(&self, value: V) -> BasicPredicate<T> {
self.compare(JCompareOp::Gte, value)
}
pub fn lt(&self, value: V) -> BasicPredicate<T> {
self.compare(JCompareOp::Lt, value)
}
pub fn lte(&self, value: V) -> BasicPredicate<T> {
self.compare(JCompareOp::Lte, value)
}
pub fn between(&self, low: V, high: V) -> BasicPredicate<T> {
self.inner.predicate(JSahibONPredicateBody::ScalarBetween {
path: self.inner.path.clone(),
scalar_kind: V::KIND,
low: low.into_scalar_value(),
high: high.into_scalar_value(),
})
}
}
pub fn evaluate_jsahibon_predicate(root: Option<&JSahibON>, body: &JSahibONPredicateBody) -> bool {
match body {
JSahibONPredicateBody::Exists { path } => resolve_path(root, path).is_some(),
JSahibONPredicateBody::Missing { path } => resolve_path(root, path).is_none(),
JSahibONPredicateBody::IsJsonNull { path } => {
matches!(resolve_path(root, path), Some(JSahibON::Null))
}
JSahibONPredicateBody::IsNotJsonNull { path } => {
resolve_path(root, path).is_some_and(|value| !matches!(value, JSahibON::Null))
}
JSahibONPredicateBody::Type { path, kind } => {
resolve_path(root, path).is_some_and(|value| matches_type(value, *kind))
}
JSahibONPredicateBody::HasKey { path, key } => {
object_at(root, path).is_some_and(|object| object.get(key.as_str()).is_some())
}
JSahibONPredicateBody::HasAnyKey { path, keys } => object_at(root, path)
.is_some_and(|object| keys.iter().any(|key| object.get(key.as_str()).is_some())),
JSahibONPredicateBody::HasAllKeys { path, keys } => object_at(root, path)
.is_some_and(|object| keys.iter().all(|key| object.get(key.as_str()).is_some())),
JSahibONPredicateBody::ScalarCompare {
path,
op,
scalar_kind,
operand,
} => scalar_at(root, path, *scalar_kind)
.is_some_and(|left| compare_scalar(&left, *op, operand)),
JSahibONPredicateBody::ScalarIn {
path,
scalar_kind,
operands,
polarity,
} => scalar_at(root, path, *scalar_kind).is_some_and(|left| {
let contains = operands.iter().any(|right| &left == right);
match polarity {
JInPolarity::In => contains,
JInPolarity::NotIn => !contains,
}
}),
JSahibONPredicateBody::ScalarBetween {
path,
scalar_kind,
low,
high,
} => scalar_at(root, path, *scalar_kind).is_some_and(|left| {
compare_scalar_order(&left, low).is_some_and(|ordering| ordering != Ordering::Less)
&& compare_scalar_order(&left, high)
.is_some_and(|ordering| ordering != Ordering::Greater)
}),
JSahibONPredicateBody::JsonEq { path, value } => {
resolve_path(root, path).is_some_and(|left| left == value)
}
JSahibONPredicateBody::JsonNeq { path, value } => {
resolve_path(root, path).is_some_and(|left| left != value)
}
JSahibONPredicateBody::ArrayContains { path, element } => match resolve_path(root, path) {
Some(JSahibON::Array(values)) => values.iter().any(|value| value == element),
_ => false,
},
JSahibONPredicateBody::ArrayLen { path, op, len } => match resolve_path(root, path) {
Some(JSahibON::Array(values)) => {
compare_u64(u64::try_from(values.len()).unwrap_or(u64::MAX), *op, *len)
}
_ => false,
},
}
}
fn matches_type(value: &JSahibON, kind: JTypeKind) -> bool {
matches!(
(value, kind),
(JSahibON::Null, JTypeKind::Null)
| (JSahibON::Bool(_), JTypeKind::Bool)
| (
JSahibON::I64(_) | JSahibON::U64(_) | JSahibON::F64(_),
JTypeKind::Number
)
| (JSahibON::String(_), JTypeKind::String)
| (JSahibON::Array(_), JTypeKind::Array)
| (JSahibON::Object(_), JTypeKind::Object)
)
}
fn resolve_path<'a>(root: Option<&'a JSahibON>, path: &JPath) -> Option<&'a JSahibON> {
let mut current = root?;
for segment in path.segments() {
let JSahibON::Object(object) = current else {
return None;
};
current = object.get(segment)?;
}
Some(current)
}
fn object_at<'a>(root: Option<&'a JSahibON>, path: &JPath) -> Option<&'a JObject> {
match resolve_path(root, path) {
Some(JSahibON::Object(object)) => Some(object),
_ => None,
}
}
fn scalar_at(root: Option<&JSahibON>, path: &JPath, kind: JScalarKind) -> Option<JScalarValue> {
let value = resolve_path(root, path)?;
match (kind, value) {
(JScalarKind::I64 | JScalarKind::U64 | JScalarKind::F64, JSahibON::I64(value)) => {
Some(JScalarValue::I64(*value))
}
(JScalarKind::I64 | JScalarKind::U64 | JScalarKind::F64, JSahibON::U64(value)) => {
Some(JScalarValue::U64(*value))
}
(JScalarKind::I64 | JScalarKind::U64 | JScalarKind::F64, JSahibON::F64(value)) => {
Some(JScalarValue::F64(*value))
}
(JScalarKind::String, JSahibON::String(value)) => Some(JScalarValue::String(value.clone())),
(JScalarKind::Bool, JSahibON::Bool(value)) => Some(JScalarValue::Bool(*value)),
_ => None,
}
}
fn compare_scalar(left: &JScalarValue, op: JCompareOp, right: &JScalarValue) -> bool {
match op {
JCompareOp::Eq => left == right,
JCompareOp::Neq => left != right,
JCompareOp::Gt => {
compare_scalar_order(left, right).is_some_and(|ordering| ordering == Ordering::Greater)
}
JCompareOp::Gte => {
compare_scalar_order(left, right).is_some_and(|ordering| ordering != Ordering::Less)
}
JCompareOp::Lt => {
compare_scalar_order(left, right).is_some_and(|ordering| ordering == Ordering::Less)
}
JCompareOp::Lte => {
compare_scalar_order(left, right).is_some_and(|ordering| ordering != Ordering::Greater)
}
}
}
fn compare_scalar_order(left: &JScalarValue, right: &JScalarValue) -> Option<Ordering> {
compare_jsahibon_numbers(&scalar_to_jsahibon(left), &scalar_to_jsahibon(right))
}
fn compare_u64(left: u64, op: JCompareOp, right: u64) -> bool {
match op {
JCompareOp::Eq => left == right,
JCompareOp::Neq => left != right,
JCompareOp::Gt => left > right,
JCompareOp::Gte => left >= right,
JCompareOp::Lt => left < right,
JCompareOp::Lte => left <= right,
}
}
fn scalar_to_jsahibon(value: &JScalarValue) -> JSahibON {
match value {
JScalarValue::I64(value) => JSahibON::I64(*value),
JScalarValue::U64(value) => JSahibON::U64(*value),
JScalarValue::F64(value) => JSahibON::F64(*value),
JScalarValue::String(value) => JSahibON::String(value.clone()),
JScalarValue::Bool(value) => JSahibON::Bool(*value),
}
}