#![allow(clippy::too_many_arguments)]
#![allow(clippy::similar_names)]
pub mod expr;
mod imut_expr;
pub use self::expr::Cont;
use crate::{
ast::{
ArrayPattern, ArrayPredicatePattern, BaseExpr, BinOpKind, ExprPath, GroupBy, ImutExpr,
InvokeAggrFn, NodeMeta, Patch, PatchOperation, Path, Pattern, PredicatePattern,
RecordPattern, ReservedPath, RunConsts, Segment, TuplePattern, UnaryOpKind,
},
ctx::NO_CONTEXT,
errors::{
err_need_obj, error_array_out_of_bound, error_bad_array_index, error_bad_key,
error_bad_key_err, error_decreasing_range, error_division_by_zero, error_guard_not_bool,
error_invalid_binary, error_invalid_bitshift, error_need_arr, error_need_int,
error_need_obj, error_need_str, error_oops, error_overflow, error_patch_key_exists,
error_patch_merge_type_conflict, error_patch_update_key_missing, unknown_local, Result,
},
prelude::*,
stry, EventContext, Value, NO_AGGRS, NO_CONSTS,
};
use simd_json::StaticNode;
use std::{
borrow::{Borrow, Cow},
convert::TryInto,
iter::Iterator,
};
pub const TRUE: Value<'static> = Value::Static(StaticNode::Bool(true));
pub const FALSE: Value<'static> = Value::Static(StaticNode::Bool(false));
pub const NULL: Value<'static> = Value::Static(StaticNode::Null);
#[macro_export]
macro_rules! static_bool {
($e:expr) => {
#[allow(clippy::if_not_else)]
{
if $e {
Cow::Borrowed(&TRUE)
} else {
Cow::Borrowed(&FALSE)
}
}
};
}
pub struct Env<'run, 'event>
where
'event: 'run,
{
pub context: &'run EventContext<'run>,
pub consts: RunConsts<'run, 'event>,
pub aggrs: &'run [InvokeAggrFn<'event>],
pub recursion_limit: u32,
}
impl Default for Env<'static, 'static> {
fn default() -> Self {
Self {
context: &NO_CONTEXT,
consts: NO_CONSTS.run(),
aggrs: &NO_AGGRS,
recursion_limit: crate::recursion_limit(),
}
}
}
#[derive(Default, Debug)]
pub struct LocalStack<'stack> {
pub(crate) values: Vec<Option<Value<'stack>>>,
}
impl<'stack> LocalStack<'stack> {
#[must_use]
pub fn with_size(size: usize) -> Self {
Self {
values: vec![None; size],
}
}
pub fn get<O>(&self, idx: usize, o: &O, m: &NodeMeta) -> Result<&Option<Value<'stack>>>
where
O: BaseExpr,
{
self.values.get(idx).ok_or_else(|| unknown_local(o, m))
}
pub fn get_mut<O>(
&mut self,
idx: usize,
o: &O,
m: &NodeMeta,
) -> Result<&mut Option<Value<'stack>>>
where
O: BaseExpr,
{
self.values.get_mut(idx).ok_or_else(|| unknown_local(o, m))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AggrType {
Tick,
Emit,
}
#[derive(Clone, Copy, Debug)]
pub struct ExecOpts {
pub result_needed: bool,
pub aggr: AggrType,
}
impl ExecOpts {
pub(crate) fn without_result(mut self) -> Self {
self.result_needed = false;
self
}
pub(crate) fn with_result(mut self) -> Self {
self.result_needed = true;
self
}
}
#[inline]
#[allow(clippy::cast_precision_loss)]
pub(crate) fn val_eq<'event>(lhs: &Value<'event>, rhs: &Value<'event>) -> bool {
use Value::{Array, Bytes, Object, Static, String};
let error = std::f64::EPSILON;
match (lhs, rhs) {
(Object(l), Object(r)) => {
if l.len() == r.len() {
l.iter()
.all(|(k, lv)| r.get(k).map(|rv| val_eq(lv, rv)) == Some(true))
} else {
false
}
}
(Array(l), Array(r)) => {
if l.len() == r.len() {
l.iter().zip(r.iter()).all(|(l, r)| val_eq(l, r))
} else {
false
}
}
(Static(StaticNode::Bool(l)), Static(StaticNode::Bool(r))) => *l == *r,
(Static(StaticNode::Null), Static(StaticNode::Null)) => true,
(String(l), String(r)) => *l == *r,
(Bytes(l), Bytes(r)) => *l == *r,
(String(l), Bytes(r)) => *l.as_bytes() == *r,
(Bytes(l), String(r)) => *l == *r.as_bytes(),
(l, r) => {
if let (Some(l), Some(r)) = (l.as_u64(), r.as_u64()) {
l == r
} else if let (Some(l), Some(r)) = (l.as_i64(), r.as_i64()) {
l == r
} else if let (Some(l), Some(r)) = (l.cast_f64(), r.cast_f64()) {
(l - r).abs() < error
} else {
false
}
}
}
}
#[inline]
fn value_to_index<OuterExpr, InnerExpr>(
outer: &OuterExpr,
inner: &InnerExpr,
val: &Value,
path: &Path,
array: &[Value],
) -> Result<usize>
where
OuterExpr: BaseExpr,
InnerExpr: BaseExpr,
{
match val.as_usize() {
Some(n) => Ok(n),
None if val.is_integer() => {
error_bad_array_index(outer, inner, path, val.borrow(), array.len())
}
None => error_need_int(outer, inner, val.value_type()),
}
}
fn try_math<'run, 'event, T, O, I>(
f: impl Fn(T, T) -> Option<T>,
l: T,
r: T,
outer: &O,
inner: &I,
op: BinOpKind,
) -> Result<Cow<'run, Value<'event>>>
where
O: Ranged,
I: Ranged,
Value<'static>: From<T>,
'event: 'run,
{
f(l, r).map_or_else(
|| error_overflow(outer, inner, op),
|n| Ok(Cow::Owned(Value::from(n))),
)
}
#[inline]
#[allow(clippy::cast_precision_loss, clippy::too_many_lines)]
fn exec_binary_numeric<'run, 'event, OuterExpr, InnerExpr>(
outer: &OuterExpr,
inner: &InnerExpr,
op: BinOpKind,
lhs: &Value<'event>,
rhs: &Value<'event>,
) -> Result<Cow<'run, Value<'event>>>
where
OuterExpr: BaseExpr,
InnerExpr: BaseExpr,
'event: 'run,
{
use BinOpKind::{
Add, BitAnd, BitXor, Div, Gt, Gte, LBitShift, Lt, Lte, Mod, Mul, RBitShiftSigned,
RBitShiftUnsigned, Sub,
};
if let (Some(l), Some(r)) = (lhs.as_u64(), rhs.as_u64()) {
match op {
BitAnd => Ok(Cow::Owned(Value::from(l & r))),
BitXor => Ok(Cow::Owned(Value::from(l ^ r))),
Gt => Ok(static_bool!(l > r)),
Gte => Ok(static_bool!(l >= r)),
Lt => Ok(static_bool!(l < r)),
Lte => Ok(static_bool!(l <= r)),
Add => try_math(u64::checked_add, l, r, outer, inner, op),
Sub if l >= r => Ok(Cow::Owned(Value::from(l - r))), Sub => {
let d = r - l;
d.try_into().ok().and_then(i64::checked_neg).map_or_else(
|| error_overflow(outer, inner, op),
|res| Ok(Cow::Owned(Value::from(res))),
)
}
Mul => try_math(u64::checked_mul, l, r, outer, inner, op),
Div if r > 0 => Ok(Cow::Owned(Value::from((l as f64) / (r as f64)))),
Mod if r > 0 => Ok(Cow::Owned(Value::from(l % r))),
Mod | Div => error_division_by_zero(outer, inner, op),
RBitShiftUnsigned | RBitShiftSigned => u32::try_from(r)
.ok()
.and_then(|r| l.checked_shr(r))
.map_or_else(
|| error_invalid_bitshift(outer, inner),
|n| Ok(Cow::Owned(Value::from(n))),
),
LBitShift => u32::try_from(r)
.ok()
.and_then(|r| l.checked_shl(r))
.map_or_else(
|| error_invalid_bitshift(outer, inner),
|n| Ok(Cow::Owned(Value::from(n))),
),
_ => error_invalid_binary(outer, inner, op, lhs, rhs),
}
} else if let (Some(l), Some(r)) = (lhs.as_i64(), rhs.as_i64()) {
match op {
BitAnd => Ok(Cow::Owned(Value::from(l & r))),
BitXor => Ok(Cow::Owned(Value::from(l ^ r))),
Gt => Ok(static_bool!(l > r)),
Gte => Ok(static_bool!(l >= r)),
Lt => Ok(static_bool!(l < r)),
Lte => Ok(static_bool!(l <= r)),
Add => try_math(i64::checked_add, l, r, outer, inner, op),
Sub => try_math(i64::checked_sub, l, r, outer, inner, op),
Mul => try_math(i64::checked_mul, l, r, outer, inner, op),
Div if r != 0 => Ok(Cow::Owned(Value::from((l as f64) / (r as f64)))),
Mod if r != 0 => Ok(Cow::Owned(Value::from(l % r))),
Mod | Div => error_division_by_zero(outer, inner, op),
RBitShiftSigned => u32::try_from(r)
.ok()
.and_then(|r| l.checked_shr(r))
.map_or_else(
|| error_invalid_bitshift(outer, inner),
|n| Ok(Cow::Owned(Value::from(n))),
),
#[allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_possible_wrap
)]
RBitShiftUnsigned => u32::try_from(r)
.ok()
.and_then(|r| (l as u64).checked_shr(r))
.map_or_else(
|| error_invalid_bitshift(outer, inner),
|n| Ok(Cow::Owned(Value::from(n as i64))),
),
LBitShift => u32::try_from(r)
.ok()
.and_then(|r| l.checked_shl(r))
.map_or_else(
|| error_invalid_bitshift(outer, inner),
|n| Ok(Cow::Owned(Value::from(n))),
),
_ => error_invalid_binary(outer, inner, op, lhs, rhs),
}
} else if let (Some(l), Some(r)) = (lhs.cast_f64(), rhs.cast_f64()) {
match op {
Gte => Ok(static_bool!(l >= r)),
Gt => Ok(static_bool!(l > r)),
Lt => Ok(static_bool!(l < r)),
Lte => Ok(static_bool!(l <= r)),
Add => Ok(Cow::Owned(Value::from(l + r))),
Sub => Ok(Cow::Owned(Value::from(l - r))),
Mul => Ok(Cow::Owned(Value::from(l * r))),
Div => Ok(Cow::Owned(Value::from(l / r))),
_ => error_invalid_binary(outer, inner, op, lhs, rhs),
}
} else {
error_invalid_binary(outer, inner, op, lhs, rhs)
}
}
#[inline]
pub(crate) fn exec_binary<'run, 'event, OuterExpr, InnerExpr>(
outer: &OuterExpr,
inner: &InnerExpr,
op: BinOpKind,
lhs: &Value<'event>,
rhs: &Value<'event>,
) -> Result<Cow<'run, Value<'event>>>
where
OuterExpr: BaseExpr,
InnerExpr: BaseExpr,
'event: 'run,
{
use BinOpKind::{Add, BitAnd, BitXor, Eq, Gt, Gte, Lt, Lte, NotEq};
use StaticNode::Bool;
use Value::{Array, Bytes, Static, String};
match (op, lhs, rhs) {
(Eq, Static(StaticNode::Null), Static(StaticNode::Null)) => Ok(static_bool!(true)),
(NotEq, Static(StaticNode::Null), Static(StaticNode::Null)) => Ok(static_bool!(false)),
(Eq, l, r) => Ok(static_bool!(val_eq(l, r))),
(NotEq, l, r) => Ok(static_bool!(!val_eq(l, r))),
(BitAnd, Static(Bool(l)), Static(Bool(r))) => Ok(static_bool!(*l && *r)),
(BitXor, Static(Bool(l)), Static(Bool(r))) => Ok(static_bool!(*l != *r)),
(Gt, Bytes(l), Bytes(r)) => Ok(static_bool!(l > r)),
(Gte, Bytes(l), Bytes(r)) => Ok(static_bool!(l >= r)),
(Lt, Bytes(l), Bytes(r)) => Ok(static_bool!(l < r)),
(Lte, Bytes(l), Bytes(r)) => Ok(static_bool!(l <= r)),
(Gt, Bytes(l), String(r)) => {
let l: &[u8] = l;
Ok(static_bool!(l > r.as_bytes()))
}
(Gte, Bytes(l), String(r)) => {
let l: &[u8] = l;
Ok(static_bool!(l >= r.as_bytes()))
}
(Lt, Bytes(l), String(r)) => {
let l: &[u8] = l;
Ok(static_bool!(r.as_bytes() > l))
}
(Lte, Bytes(l), String(r)) => {
let l: &[u8] = l;
Ok(static_bool!(r.as_bytes() >= l))
}
(Gt, String(l), Bytes(r)) => {
let r: &[u8] = r;
Ok(static_bool!(l.as_bytes() > r))
}
(Gte, String(l), Bytes(r)) => {
let r: &[u8] = r;
Ok(static_bool!(l.as_bytes() >= r))
}
(Lt, String(l), Bytes(r)) => {
let r: &[u8] = r;
Ok(static_bool!(l.as_bytes() < r))
}
(Lte, String(l), Bytes(r)) => {
let r: &[u8] = r;
Ok(static_bool!(l.as_bytes() <= r))
}
(Gt, String(l), String(r)) => Ok(static_bool!(l > r)),
(Gte, String(l), String(r)) => Ok(static_bool!(l >= r)),
(Lt, String(l), String(r)) => Ok(static_bool!(l < r)),
(Lte, String(l), String(r)) => Ok(static_bool!(l <= r)),
(Add, String(l), String(r)) => Ok(Cow::Owned(format!("{}{}", *l, *r).into())),
(Add, Array(l), Array(r)) => {
let mut result = l.clone();
result.extend_from_slice(r);
Ok(Cow::Owned(Value::from(result)))
}
(op, Bytes(_) | String(_), Bytes(_) | String(_))
| (op, Static(Bool(_)), Static(Bool(_))) => {
error_invalid_binary(outer, inner, op, lhs, rhs)
}
(op, l, r) => exec_binary_numeric(outer, inner, op, l, r),
}
}
#[inline]
pub(crate) fn exec_unary<'run, 'event: 'run>(
op: UnaryOpKind,
val: &Value<'event>,
) -> Option<Cow<'run, Value<'event>>> {
use UnaryOpKind::{BitNot, Minus, Not, Plus};
if let Some(x) = val.as_f64() {
match &op {
Minus => Some(Cow::Owned(Value::from(-x))),
Plus => Some(Cow::Owned(Value::from(x))),
_ => None,
}
} else if let Some(x) = val.as_u64() {
match &op {
Minus => {
if x == 9_223_372_036_854_775_808 {
Some(Cow::Owned(Value::from(i64::MIN)))
} else {
x.try_into()
.ok()
.and_then(i64::checked_neg)
.map(Value::from)
.map(Cow::Owned)
}
}
Plus => Some(Cow::Owned(Value::from(x))),
BitNot => Some(Cow::Owned(Value::from(!x))),
Not => None,
}
} else if let Some(x) = val.as_i64() {
match &op {
Minus => x.checked_neg().map(Value::from).map(Cow::Owned),
Plus => Some(Cow::Owned(Value::from(x))),
BitNot => Some(Cow::Owned(Value::from(!x))),
Not => None,
}
} else if let Some(x) = val.as_bool() {
match &op {
BitNot | Not => Some(static_bool!(!x)),
_ => None,
}
} else {
None
}
}
#[inline]
#[allow(clippy::too_many_lines)]
pub(crate) fn resolve<'run, 'event, Expr>(
outer: &'run Expr,
opts: ExecOpts,
env: &'run Env<'run, 'event>,
event: &'run Value<'event>,
state: &'run Value<'static>,
meta: &'run Value<'event>,
local: &'run LocalStack<'event>,
path: &'run Path<'event>,
) -> Result<Cow<'run, Value<'event>>>
where
Expr: BaseExpr,
'event: 'run,
{
let base_value: &Value = match path {
Path::Local(lpath) => {
if let Some(l) = stry!(local.get(lpath.idx, outer, &lpath.mid)) {
l
} else {
let key = lpath.mid.name_dflt().to_string();
return error_bad_key(outer, lpath, path, key, vec![]);
}
}
Path::Meta(_path) => meta,
Path::Event(_path) => event,
Path::State(_path) => state,
Path::Expr(ExprPath { expr, var, .. }) => {
match expr.run(opts, env, event, state, meta, local)? {
Cow::Borrowed(p) => p,
Cow::Owned(o) => set_local_shadow(outer, local, *var, o)?,
}
}
Path::Reserved(ReservedPath::Args { .. }) => env.consts.args,
Path::Reserved(ReservedPath::Group { .. }) => env.consts.group,
Path::Reserved(ReservedPath::Window { .. }) => env.consts.window,
};
resolve_value(
outer, opts, env, event, state, meta, local, path, base_value,
)
}
#[inline]
#[allow(clippy::too_many_lines)]
pub(crate) fn resolve_value<'run, 'event, Expr>(
outer: &'run Expr,
opts: ExecOpts,
env: &'run Env<'run, 'event>,
event: &'run Value<'event>,
state: &'run Value<'static>,
meta: &'run Value<'event>,
local: &'run LocalStack<'event>,
path: &'run Path<'event>,
base_value: &'run Value<'event>,
) -> Result<Cow<'run, Value<'event>>>
where
Expr: BaseExpr,
'event: 'run,
{
let mut subrange: Option<&[Value<'event>]> = None;
let mut current: &'run Value<'event> = base_value;
for segment in path.segments() {
match segment {
Segment::Id { key, .. } => {
subrange = None;
current = stry!(key.lookup(current).ok_or_else(|| {
current.as_object().map_or_else(
|| err_need_obj(outer, segment, current.value_type()),
|o| {
let key = key.key().to_string();
let options = o.keys().map(ToString::to_string).collect();
error_bad_key_err(
outer, segment, path, key, options,
)
},
)
}));
continue;
}
Segment::Idx { idx, .. } => {
if let Some(a) = current.as_array() {
let range_to_consider = subrange.unwrap_or(a.as_slice());
let idx = *idx;
if let Some(c) = range_to_consider.get(idx) {
current = c;
subrange = None;
continue;
}
let r = idx..idx;
let l = range_to_consider.len();
return error_array_out_of_bound(outer, segment, path, r, l);
}
return error_need_arr(outer, segment, current.value_type());
}
Segment::RangeExpr { start, end, .. } => {
if let Some(a) = current.as_array() {
let array = subrange.unwrap_or(a.as_slice());
let start = stry!(start
.eval_to_index(outer, opts, env, event, state, meta, local, path, array));
let end = stry!(
end.eval_to_index(outer, opts, env, event, state, meta, local, path, array)
);
if end < start {
return error_decreasing_range(outer, segment, path, start, end);
} else if end > array.len() {
let r = start..end;
let l = array.len();
return error_array_out_of_bound(outer, segment, path, r, l);
}
subrange = array.get(start..end);
continue;
};
return error_need_arr(outer, segment, current.value_type());
}
Segment::Range { start, end, .. } => {
if let Some(a) = current.as_array() {
let array = subrange.unwrap_or(a.as_slice());
let start = *start;
let end = *end;
if end > array.len() {
let r = start..end;
let l = array.len();
return error_array_out_of_bound(outer, segment, path, r, l);
}
subrange = array.get(start..end);
continue;
};
return error_need_arr(outer, segment, current.value_type());
}
Segment::Element { expr, .. } => {
let key = stry!(expr.run(opts, env, event, state, meta, local));
match (current, key.borrow()) {
(Value::Object(o), Value::String(id)) => {
if let Some(v) = o.get(id) {
current = v;
subrange = None;
continue;
};
let key = id.to_string();
let options = o.keys().map(ToString::to_string).collect();
return error_bad_key(outer, segment, path, key, options);
}
(Value::Object(_), other) => {
return error_need_str(outer, segment, other.value_type())
}
(Value::Array(a), idx) => {
let array = subrange.unwrap_or(a.as_slice());
let idx = stry!(value_to_index(outer, segment, idx, path, array));
if let Some(v) = array.get(idx) {
current = v;
subrange = None;
continue;
};
let r = idx..idx;
let l = array.len();
return error_array_out_of_bound(outer, segment, path, r, l);
}
(other, key) if key.is_str() => {
return error_need_obj(outer, segment, other.value_type());
}
(other, key) if key.is_usize() => {
return error_need_arr(outer, segment, other.value_type());
}
_ => return error_oops(outer, 0xdead_0003, "Bad path segments"),
}
}
}
}
Ok(subrange.map_or_else(
|| Cow::Borrowed(current),
|range_to_consider| Cow::Owned(Value::from(range_to_consider.to_vec())),
))
}
fn merge_values<'event>(value: &mut Value<'event>, replacement: &Value<'event>) -> Result<()> {
if let Some((rep, map)) = replacement.as_object().zip(value.as_object_mut()) {
for (k, v) in rep {
if let Some(k) = map.get_mut(k) {
stry!(merge_values(k, v));
} else {
map.insert(k.clone(), v.clone());
}
}
} else {
*value = replacement.clone();
}
Ok(())
}
enum PreEvaluatedPatchOperation<'event, 'run> {
Insert {
cow: beef::Cow<'event, str>,
value: Value<'event>,
mid: &'run NodeMeta,
},
Update {
cow: beef::Cow<'event, str>,
value: Value<'event>,
mid: &'run NodeMeta,
},
Upsert {
cow: beef::Cow<'event, str>,
value: Value<'event>,
},
Erase {
cow: beef::Cow<'event, str>,
},
Copy {
from: beef::Cow<'event, str>,
to: beef::Cow<'event, str>,
mid: &'run NodeMeta,
},
Move {
from: beef::Cow<'event, str>,
to: beef::Cow<'event, str>,
mid: &'run NodeMeta,
},
Merge {
cow: beef::Cow<'event, str>,
mvalue: Value<'event>,
mid: &'run NodeMeta,
},
MergeRecord {
mvalue: Value<'event>,
mid: &'run NodeMeta,
},
Default {
cow: beef::Cow<'event, str>,
expr: &'run ImutExpr<'event>,
},
DefaultRecord {
expr: &'run ImutExpr<'event>,
mid: &'run NodeMeta,
},
}
impl<'event, 'run> PreEvaluatedPatchOperation<'event, 'run> {
fn from(
patch_op: &'run PatchOperation<'event>,
opts: ExecOpts,
env: &Env<'run, 'event>,
event: &Value<'event>,
state: &Value<'static>,
meta: &Value<'event>,
local: &LocalStack<'event>,
) -> Result<Self> {
Ok(match patch_op {
PatchOperation::Insert { ident, expr, mid } => PreEvaluatedPatchOperation::Insert {
cow: stry!(ident.run(opts, env, event, state, meta, local)),
value: stry!(expr.run(opts, env, event, state, meta, local)).into_owned(),
mid,
},
PatchOperation::Update { ident, expr, mid } => PreEvaluatedPatchOperation::Update {
cow: stry!(ident.run(opts, env, event, state, meta, local)),
value: stry!(expr.run(opts, env, event, state, meta, local)).into_owned(),
mid,
},
PatchOperation::Upsert { ident, expr, .. } => PreEvaluatedPatchOperation::Upsert {
cow: stry!(ident.run(opts, env, event, state, meta, local)),
value: stry!(expr.run(opts, env, event, state, meta, local)).into_owned(),
},
PatchOperation::Erase { ident, .. } => PreEvaluatedPatchOperation::Erase {
cow: stry!(ident.run(opts, env, event, state, meta, local)),
},
PatchOperation::Copy { from, to, mid } => PreEvaluatedPatchOperation::Copy {
from: stry!(from.run(opts, env, event, state, meta, local)),
to: stry!(to.run(opts, env, event, state, meta, local)),
mid,
},
PatchOperation::Move { from, to, mid } => PreEvaluatedPatchOperation::Move {
from: stry!(from.run(opts, env, event, state, meta, local)),
to: stry!(to.run(opts, env, event, state, meta, local)),
mid,
},
PatchOperation::Merge { ident, expr, mid } => PreEvaluatedPatchOperation::Merge {
cow: stry!(ident.run(opts, env, event, state, meta, local)),
mvalue: stry!(expr.run(opts, env, event, state, meta, local)).into_owned(),
mid,
},
PatchOperation::MergeRecord { expr, mid } => PreEvaluatedPatchOperation::MergeRecord {
mvalue: stry!(expr.run(opts, env, event, state, meta, local)).into_owned(),
mid,
},
PatchOperation::Default { ident, expr, .. } => PreEvaluatedPatchOperation::Default {
cow: stry!(ident.run(opts, env, event, state, meta, local)),
expr,
},
PatchOperation::DefaultRecord { expr, mid } => {
PreEvaluatedPatchOperation::DefaultRecord { expr, mid }
}
})
}
}
#[inline]
#[allow(clippy::too_many_lines)]
fn patch_value<'run, 'event>(
opts: ExecOpts,
env: &Env<'run, 'event>,
event: &Value<'event>,
state: &Value<'static>,
meta: &Value<'event>,
local: &LocalStack<'event>,
target: &mut Value<'event>,
expr: &Patch<'event>,
) -> Result<()> {
use PreEvaluatedPatchOperation::{
self as Pepo, Copy, Default, DefaultRecord, Erase, Insert, Merge, MergeRecord, Move,
Update, Upsert,
};
let patch_expr = expr;
let mut evaluated: Vec<_> = Vec::with_capacity(expr.operations.len());
for op in &expr.operations {
evaluated.push(stry!(Pepo::from(op, opts, env, event, state, meta, local,)));
}
for const_op in evaluated {
let t = target.value_type();
let obj = target
.as_object_mut()
.ok_or_else(|| err_need_obj(patch_expr, &expr.target, t))?;
match const_op {
Insert {
cow, value, mid, ..
} => {
if obj.contains_key(&cow) {
let key = cow.to_string();
return error_patch_key_exists(patch_expr, mid, key);
};
obj.insert(cow, value);
}
Update {
cow, value, mid, ..
} => {
if obj.contains_key(&cow) {
obj.insert(cow, value);
} else {
let key = cow.to_string();
return error_patch_update_key_missing(patch_expr, mid, key);
}
}
Upsert { cow, value, .. } => {
obj.insert(cow, value);
}
Erase { cow, .. } => {
obj.remove(&cow);
}
Copy { from, to, mid } => {
if obj.contains_key(&to) {
return error_patch_key_exists(patch_expr, mid, to.to_string());
}
if let Some(old) = obj.get(&from) {
let old = old.clone();
obj.insert(to, old);
}
}
Move { from, to, mid } => {
if obj.contains_key(&to) {
return error_patch_key_exists(patch_expr, mid, to.to_string());
}
if let Some(old) = obj.remove(&from) {
obj.insert(to, old);
}
}
Merge {
cow, mvalue, mid, ..
} => match obj.get_mut(&cow) {
Some(value) if value.is_object() && mvalue.is_object() => {
stry!(merge_values(value, &mvalue));
}
Some(other) => {
let key = cow.to_string();
return error_patch_merge_type_conflict(patch_expr, mid, key, other);
}
None => {
let mut new_value = Value::object();
stry!(merge_values(&mut new_value, &mvalue));
obj.insert(cow, new_value);
}
},
MergeRecord { mvalue, mid, .. } => {
if mvalue.is_object() {
stry!(merge_values(target, &mvalue));
} else {
return error_patch_merge_type_conflict(
patch_expr,
mid,
"<target>".into(),
&mvalue,
);
}
}
Default { cow, expr, .. } => {
if !obj.contains_key(&cow) {
let default_value = stry!(expr.run(opts, env, event, state, meta, local));
obj.insert(cow, default_value.into_owned());
};
}
DefaultRecord { expr: inner, mid } => {
let default_value = stry!(inner.run(opts, env, event, state, meta, local));
if let Some(dflt) = default_value.as_object() {
apply_default(obj, dflt);
} else {
return error_need_obj(expr, mid, default_value.value_type());
}
}
}
}
Ok(())
}
fn apply_default<'event>(
target: &mut <Value<'event> as ValueAccess>::Object,
dflt: &<Value<'event> as ValueAccess>::Object,
) {
for (k, v) in dflt {
if !target.contains_key(k) {
target.insert(k.clone(), v.clone());
} else if let Some((target, dflt)) = target
.get_mut(k)
.and_then(Value::as_object_mut)
.zip(v.as_object())
{
apply_default(target, dflt);
}
}
}
#[inline]
fn test_guard<Expr>(
outer: &Expr,
opts: ExecOpts,
env: &Env,
event: &Value,
state: &Value<'static>,
meta: &Value,
local: &LocalStack,
guard: &Option<ImutExpr>,
) -> Result<bool>
where
Expr: BaseExpr,
{
guard.as_ref().map_or_else(
|| Ok(true),
|guard| {
let test = stry!(guard.run(opts, env, event, state, meta, local));
test.as_bool()
.map_or_else(|| error_guard_not_bool(outer, guard, &test), Result::Ok)
},
)
}
#[inline]
#[allow(clippy::too_many_lines)]
pub(crate) fn test_predicate_expr<Expr>(
outer: &Expr,
opts: ExecOpts,
env: &Env,
event: &Value,
state: &Value<'static>,
meta: &Value,
local: &LocalStack,
target: &Value,
pattern: &Pattern,
guard: &Option<ImutExpr>,
) -> Result<bool>
where
Expr: BaseExpr,
{
match pattern {
Pattern::Extract(test) => {
if test
.extractor
.extract(false, target, env.context)
.is_match()
{
test_guard(outer, opts, env, event, state, meta, local, guard)
} else {
Ok(false)
}
}
Pattern::DoNotCare => test_guard(outer, opts, env, event, state, meta, local, guard),
Pattern::Tuple(ref tp) => {
let opts_wo = opts.without_result();
let res = match_tp_expr(outer, opts_wo, env, event, state, meta, local, target, tp);
if stry!(res).is_some() {
test_guard(outer, opts, env, event, state, meta, local, guard)
} else {
Ok(false)
}
}
Pattern::Record(ref rp) => {
let opts_wo = opts.without_result();
let res = match_rp_expr(outer, opts_wo, env, event, state, meta, local, target, rp);
if stry!(res).is_some() {
test_guard(outer, opts, env, event, state, meta, local, guard)
} else {
Ok(false)
}
}
Pattern::Array(ref ap) => {
let opts_wo = opts.without_result();
let res = match_ap_expr(outer, opts_wo, env, event, state, meta, local, target, ap);
if stry!(res).is_some() {
test_guard(outer, opts, env, event, state, meta, local, guard)
} else {
Ok(false)
}
}
Pattern::Expr(ref expr) => {
let v = stry!(expr.run(opts, env, event, state, meta, local));
let vb: &Value = v.borrow();
if val_eq(target, vb) {
test_guard(outer, opts, env, event, state, meta, local, guard)
} else {
Ok(false)
}
}
Pattern::Assign(ref a) => {
let o_w = opts.with_result();
match *a.pattern {
Pattern::Extract(ref test) => {
if let Some(v) = test
.extractor
.extract(true, target, env.context)
.into_match()
{
stry!(set_local_shadow(outer, local, a.idx, v));
test_guard(outer, opts, env, event, state, meta, local, guard)
} else {
Ok(false)
}
}
Pattern::DoNotCare => {
let v = target.clone();
stry!(set_local_shadow(outer, local, a.idx, v));
test_guard(outer, opts, env, event, state, meta, local, guard)
}
Pattern::Array(ref ap) => {
let res = match_ap_expr(outer, o_w, env, event, state, meta, local, target, ap);
stry!(res).map_or(Ok(false), |v| {
stry!(set_local_shadow(outer, local, a.idx, v));
test_guard(outer, opts, env, event, state, meta, local, guard)
})
}
Pattern::Record(ref rp) => {
let res = match_rp_expr(outer, o_w, env, event, state, meta, local, target, rp);
stry!(res).map_or(Ok(false), |v| {
stry!(set_local_shadow(outer, local, a.idx, v));
test_guard(outer, opts, env, event, state, meta, local, guard)
})
}
Pattern::Expr(ref expr) => {
let v = stry!(expr.run(opts, env, event, state, meta, local));
let vb: &Value = v.borrow();
if val_eq(target, vb) {
let v = v.into_owned();
stry!(set_local_shadow(outer, local, a.idx, v));
test_guard(outer, opts, env, event, state, meta, local, guard)
} else {
Ok(false)
}
}
Pattern::Tuple(ref tp) => {
let res = match_tp_expr(outer, o_w, env, event, state, meta, local, target, tp);
stry!(res).map_or(Ok(false), |v| {
stry!(set_local_shadow(outer, local, a.idx, v));
test_guard(outer, opts, env, event, state, meta, local, guard)
})
}
Pattern::Assign(_) => error_oops(outer, 0xdead_0004, "nested assign pattern"),
}
}
}
}
#[inline]
#[allow(clippy::too_many_lines)]
fn match_rp_expr<'event, Expr>(
outer: &Expr,
opts: ExecOpts,
env: &Env<'_, 'event>,
event: &Value<'event>,
state: &Value<'static>,
meta: &Value<'event>,
local: &LocalStack<'event>,
target: &Value<'event>,
rp: &RecordPattern<'event>,
) -> Result<Option<Value<'event>>>
where
Expr: BaseExpr,
{
let res = if let Some(record) = target.as_object() {
let mut acc: Value<'event> = Value::object_with_capacity(if opts.result_needed {
rp.fields.len()
} else {
0
});
for pp in &rp.fields {
let known_key = pp.key();
match pp {
PredicatePattern::FieldPresent { .. } => {
if let Some(v) = known_key.map_lookup(record) {
if opts.result_needed {
known_key.insert(&mut acc, v.clone())?;
};
} else {
return Ok(None);
}
}
PredicatePattern::FieldAbsent { .. } => {
if known_key.map_lookup(record).is_some() {
return Ok(None);
}
}
PredicatePattern::TildeEq { test, .. } => {
let testee = if let Some(v) = known_key.map_lookup(record) {
v
} else {
return Ok(None);
};
if let Some(x) = test
.extractor
.extract(opts.result_needed, testee, env.context)
.into_match()
{
if opts.result_needed {
known_key.insert(&mut acc, x)?;
};
} else {
return Ok(None);
}
}
PredicatePattern::Bin { rhs, kind, .. } => {
let testee = if let Some(v) = known_key.map_lookup(record) {
v
} else {
return Ok(None);
};
let rhs = stry!(rhs.run(opts, env, event, state, meta, local));
let vb: &Value = rhs.borrow();
let r = stry!(exec_binary(outer, outer, *kind, testee, vb));
if !r.as_bool().unwrap_or_default() {
return Ok(None);
}
}
PredicatePattern::RecordPatternEq { pattern, .. } => {
let testee = if let Some(v) = known_key.map_lookup(record) {
v
} else {
return Ok(None);
};
if testee.is_object() {
if let Some(m) = stry!(match_rp_expr(
outer, opts, env, event, state, meta, local, testee, pattern,
)) {
if opts.result_needed {
known_key.insert(&mut acc, m)?;
};
} else {
return Ok(None);
}
} else {
return Ok(None);
}
}
PredicatePattern::ArrayPatternEq { pattern, .. } => {
let testee = if let Some(v) = known_key.map_lookup(record) {
v
} else {
return Ok(None);
};
if testee.is_array() {
if let Some(r) = stry!(match_ap_expr(
outer, opts, env, event, state, meta, local, testee, pattern,
)) {
if opts.result_needed {
known_key.insert(&mut acc, r)?;
};
} else {
return Ok(None);
}
} else {
return Ok(None);
}
}
PredicatePattern::TuplePatternEq { pattern, .. } => {
let testee = if let Some(v) = known_key.map_lookup(record) {
v
} else {
return Ok(None);
};
if testee.is_array() {
if let Some(r) = stry!(match_tp_expr(
outer, opts, env, event, state, meta, local, testee, pattern,
)) {
if opts.result_needed {
known_key.insert(&mut acc, r)?;
};
} else {
return Ok(None);
}
} else {
return Ok(None);
}
}
}
}
Some(acc)
} else {
None
};
Ok(res)
}
#[inline]
fn match_ap_expr<'event, Expr>(
outer: &Expr,
opts: ExecOpts,
env: &Env<'_, 'event>,
event: &Value<'event>,
state: &Value<'static>,
meta: &Value<'event>,
local: &LocalStack<'event>,
target: &Value<'event>,
ap: &ArrayPattern<'event>,
) -> Result<Option<Value<'event>>>
where
Expr: BaseExpr,
{
let res = if let Some(a) = target.as_array() {
if ap.exprs.is_empty() {
Some(Value::array_with_capacity(0))
} else {
let mut acc = Vec::with_capacity(if opts.result_needed { a.len() } else { 0 });
for expr in &ap.exprs {
let mut matched = false;
match expr {
ArrayPredicatePattern::Ignore => {
matched = !a.is_empty();
}
ArrayPredicatePattern::Expr(e) => {
'inner_expr: for (idx, candidate) in a.iter().enumerate() {
let r = stry!(e.run(opts, env, event, state, meta, local));
let vb: &Value = r.borrow();
let expr_matches = val_eq(candidate, vb);
matched |= expr_matches;
if expr_matches {
if opts.result_needed {
acc.push(Value::from(vec![Value::from(idx), r.into_owned()]));
} else {
break 'inner_expr;
}
}
}
}
ArrayPredicatePattern::Tilde(test) => {
'inner_tilde: for (idx, candidate) in a.iter().enumerate() {
if let Some(r) = test
.extractor
.extract(opts.result_needed, candidate, env.context)
.into_match()
{
matched |= true;
if opts.result_needed {
acc.push(Value::from(vec![Value::from(idx), r]));
} else {
break 'inner_tilde;
}
}
}
}
ArrayPredicatePattern::Record(rp) => {
'inner_rec: for (idx, candidate) in a.iter().enumerate() {
if let Some(r) = stry!(match_rp_expr(
outer, opts, env, event, state, meta, local, candidate, rp,
)) {
matched |= true;
if opts.result_needed {
acc.push(Value::from(vec![Value::from(idx), r]));
} else {
break 'inner_rec;
};
}
}
}
}
if !matched {
return Ok(None);
}
}
Some(Value::from(acc))
}
} else {
None
};
Ok(res)
}
#[inline]
fn match_tp_expr<'event, Expr>(
outer: &Expr,
opts: ExecOpts,
env: &Env<'_, 'event>,
event: &Value<'event>,
state: &Value<'static>,
meta: &Value<'event>,
local: &LocalStack<'event>,
target: &Value<'event>,
tp: &TuplePattern<'event>,
) -> Result<Option<Value<'event>>>
where
Expr: BaseExpr,
{
if let Some(a) = target.as_array() {
if (tp.open && a.len() < tp.exprs.len()) || (!tp.open && a.len() != tp.exprs.len()) {
return Ok(None);
}
let mut acc = Vec::with_capacity(if opts.result_needed { a.len() } else { 0 });
let cases = tp.exprs.iter().zip(a.iter());
for (case, candidate) in cases {
match case {
ArrayPredicatePattern::Ignore => {
if opts.result_needed {
acc.push(candidate.clone());
}
}
ArrayPredicatePattern::Expr(e) => {
let r = stry!(e.run(opts, env, event, state, meta, local));
let vb: &Value = r.borrow();
if val_eq(candidate, vb) {
if opts.result_needed {
acc.push(r.into_owned());
}
} else {
return Ok(None);
}
}
ArrayPredicatePattern::Tilde(test) => {
if let Some(r) = test
.extractor
.extract(opts.result_needed, candidate, env.context)
.into_match()
{
if opts.result_needed {
acc.push(r);
}
} else {
return Ok(None);
}
}
ArrayPredicatePattern::Record(rp) => {
if let Some(r) = stry!(match_rp_expr(
outer, opts, env, event, state, meta, local, candidate, rp,
)) {
if opts.result_needed {
acc.push(r);
};
} else {
return Ok(None);
}
}
}
}
Ok(Some(Value::from(acc)))
} else {
Ok(None)
}
}
#[inline]
#[allow(mutable_transmutes, clippy::transmute_ptr_to_ptr)]
fn set_local_shadow<'local, 'event, Expr>(
outer: &Expr,
local: &LocalStack<'event>,
idx: usize,
v: Value<'event>,
) -> Result<&'local mut Value<'event>>
where
Expr: BaseExpr,
{
use std::mem;
let local: &mut LocalStack<'event> = unsafe { mem::transmute(local) };
local.values.get_mut(idx).map_or_else(
|| {
error_oops(
outer,
0xdead_0006,
"Unknown local variable in set_local_shadow",
)
},
|d| Ok(d.insert(v)),
)
}
impl<'script> GroupBy<'script> {
pub fn generate_groups<'event>(
&self,
ctx: &EventContext,
event: &Value<'event>,
meta: &Value<'event>,
) -> Result<Vec<Vec<Value<'static>>>>
where
'script: 'event,
{
let mut groups = Vec::with_capacity(16);
stry!(self.generate_groups_inner(ctx, event, &NULL, meta, &mut groups));
Ok(groups)
}
fn generate_groups_inner<'event>(
&self,
ctx: &EventContext,
event: &Value<'event>,
state: &Value<'static>,
meta: &Value<'event>,
groups: &mut Vec<Vec<Value<'static>>>,
) -> Result<()>
where
'script: 'event,
{
let opts = ExecOpts {
result_needed: true,
aggr: AggrType::Emit,
};
let local_stack = LocalStack::with_size(0);
let env = Env {
consts: NO_CONSTS.run(),
context: ctx,
aggrs: &NO_AGGRS,
recursion_limit: crate::recursion_limit(),
};
match self {
GroupBy::Expr { expr, .. } => {
let v = stry!(expr.run(opts, &env, event, state, meta, &local_stack));
if let Some((last_group, other_groups)) = groups.split_last_mut() {
other_groups
.iter_mut()
.for_each(|g| g.push(v.clone_static()));
last_group.push(v.clone_static());
} else {
groups.push(vec![v.clone_static()]);
}
Ok(())
}
GroupBy::Set { items, .. } => {
for item in items {
stry!(item.generate_groups_inner(ctx, event, state, meta, groups));
}
Ok(())
}
GroupBy::Each { expr, .. } => {
let v = stry!(expr.run(opts, &env, event, state, meta, &local_stack));
if let Some(each) = v.as_array() {
if groups.is_empty() {
for e in each {
groups.push(vec![e.clone_static()]);
}
} else {
let mut new_groups = Vec::with_capacity(each.len() * groups.len());
for mut g in groups.drain(..) {
if let Some((last, rest)) = each.split_last() {
for e in rest {
let mut g = g.clone();
g.push(e.clone_static());
new_groups.push(g);
}
g.push(last.clone_static());
new_groups.push(g);
}
}
std::mem::swap(groups, &mut new_groups);
}
Ok(())
} else {
error_need_arr(self, self, v.value_type())
}
}
}
}
}