use std::{
collections::HashSet,
num::NonZeroUsize,
ops::Range,
};
use jql_parser::{
group::split,
parser::parse,
tokens::{
Lens,
LensValue,
Token,
},
};
use serde::{
Deserialize,
de::IgnoredAny,
};
use serde_json::{
Map,
Number,
Value,
};
use simd_json::{
Buffers,
prelude::{
TypedValue,
ValueAsScalar,
ValueType,
},
tape::{
Object as TapeObject,
Value as TapeValue,
},
};
use crate::{
errors::JqlRunnerError,
runner,
};
pub fn raw(query: &str, json: &mut [u8]) -> Result<Value, JqlRunnerError> {
Evaluator::new().raw(query, json)
}
pub fn raw_all(query: &str, json: &mut [u8]) -> Result<Vec<Value>, JqlRunnerError> {
Evaluator::new().raw_all(query, json)
}
#[derive(Default)]
pub struct Evaluator {
buffers: Buffers,
}
impl std::fmt::Debug for Evaluator {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.debug_struct("Evaluator").finish_non_exhaustive()
}
}
impl Evaluator {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn raw(&mut self, query: &str, json: &mut [u8]) -> Result<Value, JqlRunnerError> {
if query.is_empty() {
return Err(JqlRunnerError::EmptyQueryError);
}
let tokens = parse(query)?;
if let Some(value) = self.evaluate(&tokens, json)?.value {
return Ok(value);
}
runner::token(&tokens, &deserialize(json)?)
}
pub fn raw_all(&mut self, query: &str, json: &mut [u8]) -> Result<Vec<Value>, JqlRunnerError> {
if query.is_empty() {
return Err(JqlRunnerError::EmptyQueryError);
}
let tokens = parse(query)?;
let declined = self.evaluate(&tokens, json)?;
if let Some(value) = declined.value {
return Ok(vec![value]);
}
if let Some(ranges) = declined.document_starts.map_or_else(
|| document_ranges(json),
|starts| Some(ranges_from_starts(&starts, json.len())),
) {
let mut values = Vec::with_capacity(ranges.len());
let mut rest = &mut *json;
for range in ranges {
let (document, tail) = rest.split_at_mut(range.len());
values.push(match self.evaluate(&tokens, document)?.value {
Some(value) => value,
None => runner::token(&tokens, &deserialize(document)?)?,
});
rest = tail;
}
return Ok(values);
}
Ok(vec![runner::token(&tokens, &deserialize(json)?)?])
}
fn evaluate(&mut self, tokens: &[Token], json: &mut [u8]) -> Result<Outcome, JqlRunnerError> {
if !can_lazy(tokens) {
return Ok(Outcome::fallback());
}
if lens_compares_numbers(tokens) && numbers_may_diverge(json) {
return Ok(Outcome::fallback());
}
let outcome = if memchr::memchr(b'\\', json).is_none() {
match simd_json::to_tape_with_buffers(json, &mut self.buffers) {
Ok(tape) => eval(tokens, tape.as_value()),
Err(_) => return Ok(self.declined(json)),
}
} else {
let mut scratch = json.to_vec();
match simd_json::to_tape_with_buffers(&mut scratch, &mut self.buffers) {
Ok(tape) => eval(tokens, tape.as_value()),
Err(_) => return Ok(self.declined(json)),
}
};
if outcome_may_diverge(&outcome, json) {
return Ok(Outcome::fallback());
}
outcome.map(|value| Outcome {
value: Some(value),
document_starts: None,
})
}
fn declined(&self, json: &[u8]) -> Outcome {
Outcome {
value: None,
document_starts: document_starts(self.buffers.structural_indexes(), json),
}
}
}
struct Outcome {
value: Option<Value>,
document_starts: Option<Vec<usize>>,
}
impl Outcome {
fn fallback() -> Self {
Self {
value: None,
document_starts: None,
}
}
}
fn outcome_may_diverge(outcome: &Result<Value, JqlRunnerError>, json: &[u8]) -> bool {
let (float, zero) = match outcome {
Ok(value) => number_hazards(value),
Err(error) => error_value(error).map_or((false, false), number_hazards),
};
(float && long_mantissa_may_diverge(json)) || (zero && negative_zero_may_diverge(json))
}
fn error_value(error: &JqlRunnerError) -> Option<&Value> {
match error {
JqlRunnerError::FlattenError(value)
| JqlRunnerError::InvalidArrayError(value)
| JqlRunnerError::InvalidObjectError(value)
| JqlRunnerError::PipeInError(value)
| JqlRunnerError::IndexOutOfBoundsError { parent: value, .. }
| JqlRunnerError::KeyNotFoundError { parent: value, .. }
| JqlRunnerError::MultiKeyNotFoundError { parent: value, .. }
| JqlRunnerError::RangeOutOfBoundsError { parent: value, .. } => Some(value),
_ => None,
}
}
fn number_hazards(value: &Value) -> (bool, bool) {
match value {
Value::Number(number) => (number.is_f64(), number.as_u64() == Some(0)),
Value::Array(values) => values.iter().fold((false, false), |acc, value| {
let hazards = number_hazards(value);
(acc.0 || hazards.0, acc.1 || hazards.1)
}),
Value::Object(entries) => entries.iter().fold((false, false), |acc, (_, value)| {
let hazards = number_hazards(value);
(acc.0 || hazards.0, acc.1 || hazards.1)
}),
Value::Bool(_) | Value::Null | Value::String(_) => (false, false),
}
}
fn lens_compares_numbers(tokens: &[Token]) -> bool {
tokens.iter().any(|token| match token {
Token::LensSelector(lenses) => lenses
.iter()
.any(|lens| matches!(lens.get_ref().1, Some(LensValue::Number(_)))),
_ => false,
})
}
fn document_starts(structural_indexes: &[u32], json: &[u8]) -> Option<Vec<usize>> {
let mut starts = Vec::new();
let mut depth = 0_usize;
for &index in structural_indexes {
let index = index as usize;
let byte = *json.get(index)?;
if depth == 0 {
if !matches!(byte, b'{' | b'[') {
return None;
}
starts.push(index);
}
match byte {
b'{' | b'[' => depth += 1,
b'}' | b']' => depth = depth.checked_sub(1)?,
_ => {}
}
}
(depth == 0 && starts.len() > 1).then_some(starts)
}
fn ranges_from_starts(starts: &[usize], len: usize) -> Vec<Range<usize>> {
starts
.iter()
.enumerate()
.map(|(position, &start)| {
let start = if position == 0 { 0 } else { start };
let end = starts.get(position + 1).copied().unwrap_or(len);
start..end
})
.collect()
}
fn document_ranges(json: &[u8]) -> Option<Vec<Range<usize>>> {
let mut stream = serde_json::Deserializer::from_slice(json).into_iter::<IgnoredAny>();
let mut ranges = Vec::new();
let mut start = 0;
while let Some(document) = stream.next() {
document.ok()?;
let end = stream.byte_offset();
ranges.push(start..end);
start = end;
}
(ranges.len() > 1).then_some(ranges)
}
const DIGIT_SCAN_CAP: usize = 24;
fn numbers_may_diverge(json: &[u8]) -> bool {
negative_zero_may_diverge(json) || long_mantissa_may_diverge(json)
}
fn negative_zero_may_diverge(json: &[u8]) -> bool {
memchr::memmem::find_iter(json, b"-0").any(|index| {
(index == 0 || !json[index - 1].is_ascii_digit())
&& !matches!(json.get(index + 2), Some(b'0'..=b'9' | b'.' | b'e' | b'E'))
})
}
fn long_mantissa_may_diverge(json: &[u8]) -> bool {
memchr::memchr_iter(b'.', json).any(|dot| {
if dot == 0
|| !json[dot - 1].is_ascii_digit()
|| !json.get(dot + 1).is_some_and(u8::is_ascii_digit)
{
return false;
}
let before = json[..dot]
.iter()
.rev()
.take(DIGIT_SCAN_CAP)
.take_while(|byte| byte.is_ascii_digit())
.count();
let after = json[dot + 1..]
.iter()
.take(DIGIT_SCAN_CAP)
.take_while(|byte| byte.is_ascii_digit())
.count();
before + after >= 16
})
}
fn deserialize(json: &[u8]) -> Result<Value, JqlRunnerError> {
let json = str::from_utf8(json).map_err(|_| JqlRunnerError::DeserializationError)?;
let mut deserializer = serde_json::Deserializer::from_str(json);
deserializer.disable_recursion_limit();
let value = Value::deserialize(serde_stacker::Deserializer::new(&mut deserializer))
.map_err(|_| JqlRunnerError::DeserializationError)?;
deserializer
.end()
.map_err(|_| JqlRunnerError::DeserializationError)?;
Ok(value)
}
fn can_lazy(tokens: &[Token]) -> bool {
let usable_first = tokens
.iter()
.find(|token| !matches!(token, Token::GroupSeparator))
.is_some_and(|token| !matches!(token, Token::FlattenOperator | Token::PipeOutOperator));
let pipe_ins = tokens
.iter()
.filter(|token| matches!(token, Token::PipeInOperator))
.count();
usable_first
&& pipe_ins <= 1
&& !tokens
.iter()
.any(|token| matches!(token, Token::FlattenOperator))
}
fn eval(tokens: &[Token], root: TapeValue) -> Result<Value, JqlRunnerError> {
let groups = split(tokens);
if groups.len() == 1 {
return eval_group(&groups[0], root);
}
groups
.iter()
.map(|group| eval_group(group, root))
.collect::<Result<Vec<Value>, _>>()
.map(Value::Array)
}
enum Cursor<'tape, 'input> {
Tape(TapeValue<'tape, 'input>),
Owned(Value),
}
impl Cursor<'_, '_> {
fn into_value(self) -> Value {
match self {
Cursor::Tape(value) => materialize(value),
Cursor::Owned(value) => value,
}
}
}
fn eval_group(tokens: &[&Token], root: TapeValue) -> Result<Value, JqlRunnerError> {
eval_tokens(tokens, Cursor::Tape(root))
}
fn eval_tokens<'tape, 'input>(
tokens: &[&Token],
mut cursor: Cursor<'tape, 'input>,
) -> Result<Value, JqlRunnerError> {
let mut index = 0;
while let Some(&token) = tokens.get(index) {
match token {
Token::PipeInOperator => {
let mut elements = cursor_elements(cursor).map_err(JqlRunnerError::PipeInError)?;
if elements.is_empty() {
return Ok(Value::Array(Vec::new()));
}
let rest = &tokens[index + 1..];
let body_len = rest
.iter()
.position(|token| matches!(token, Token::PipeOutOperator))
.unwrap_or(rest.len());
for &token in &rest[..body_len] {
elements = elements
.into_iter()
.map(|element| advance(element, token))
.collect::<Result<Vec<_>, _>>()?;
}
let mapped = elements.into_iter().map(Cursor::into_value).collect();
cursor = Cursor::Owned(Value::Array(mapped));
index += 1 + body_len;
if matches!(tokens.get(index), Some(Token::PipeOutOperator)) {
index += 1;
}
}
Token::PipeOutOperator => return Err(JqlRunnerError::PipeOutError),
Token::LensSelector(lenses) => {
let elements =
cursor_elements(cursor).map_err(JqlRunnerError::InvalidArrayError)?;
let kept = elements
.into_iter()
.filter(|element| lenses_match(lenses, element))
.map(Cursor::into_value)
.collect();
cursor = Cursor::Owned(Value::Array(kept));
index += 1;
}
_ => {
cursor = match cursor {
Cursor::Owned(value) => {
return runner::group_runner(&tokens[index..], &value);
}
Cursor::Tape(value) => match step(value, token)? {
Some(next) => next,
None => {
return runner::group_runner(&tokens[index..], &materialize(value));
}
},
};
index += 1;
}
}
}
Ok(cursor.into_value())
}
fn advance<'tape, 'input>(
cursor: Cursor<'tape, 'input>,
token: &Token,
) -> Result<Cursor<'tape, 'input>, JqlRunnerError> {
match cursor {
Cursor::Owned(value) => runner::group_runner(&[token], &value).map(Cursor::Owned),
Cursor::Tape(value) => match step(value, token)? {
Some(next) => Ok(next),
None => runner::group_runner(&[token], &materialize(value)).map(Cursor::Owned),
},
}
}
fn cursor_elements<'tape, 'input>(
cursor: Cursor<'tape, 'input>,
) -> Result<Vec<Cursor<'tape, 'input>>, Value> {
match cursor {
Cursor::Tape(value) => value.as_array().map_or_else(
|| Err(materialize(value)),
|array| Ok(array.iter().map(Cursor::Tape).collect()),
),
Cursor::Owned(Value::Array(items)) => Ok(items.into_iter().map(Cursor::Owned).collect()),
Cursor::Owned(other) => Err(other),
}
}
fn lenses_match(lenses: &[Lens], element: &Cursor) -> bool {
lenses.iter().any(|lens| {
let (tokens, expected) = lens.get_ref();
let tokens: Vec<&Token> = tokens.iter().collect();
let selected = match element {
Cursor::Tape(value) => eval_tokens(&tokens, Cursor::Tape(*value)),
Cursor::Owned(value) => runner::group_runner(&tokens, value),
};
selected.is_ok_and(|value| match expected {
Some(LensValue::Bool(expected)) => value.as_bool() == Some(*expected),
Some(LensValue::Null) => value.is_null(),
Some(LensValue::Number(expected)) => value.as_u64() == Some(*expected as u64),
Some(LensValue::String(expected)) => value == expected.as_ref(),
None => true,
})
})
}
#[allow(clippy::too_many_lines)]
fn step<'tape, 'input>(
value: TapeValue<'tape, 'input>,
token: &Token,
) -> Result<Option<Cursor<'tape, 'input>>, JqlRunnerError> {
if operates_on_object(token)
&& let Some(object) = value.as_object()
&& has_duplicate_keys(&object)
{
return Ok(None);
}
let cursor = match token {
Token::KeySelector(key) => {
let Some(object) = value.as_object() else {
return Err(JqlRunnerError::InvalidObjectError(materialize(value)));
};
let selected = object
.iter()
.filter(|(candidate, _)| candidate == key)
.map(|(_, child)| child)
.last();
Cursor::Tape(selected.ok_or_else(|| JqlRunnerError::KeyNotFoundError {
key: (*key).to_string(),
parent: materialize(value),
})?)
}
Token::ArrayIndexSelector(indexes) if indexes.len() == 1 => {
let index: usize = indexes[0].into();
Cursor::Tape(value.get_idx(index).ok_or_else(|| {
JqlRunnerError::IndexOutOfBoundsError {
index,
parent: materialize(value),
}
})?)
}
Token::ArrayIndexSelector(indexes) => {
let selected = indexes
.iter()
.map(|index| {
let index: usize = (*index).into();
value.get_idx(index).map(materialize).ok_or_else(|| {
JqlRunnerError::IndexOutOfBoundsError {
index,
parent: materialize(value),
}
})
})
.collect::<Result<Vec<Value>, _>>()?;
Cursor::Owned(Value::Array(selected))
}
Token::ArrayRangeSelector(range) => {
let Some(array) = value.as_array() else {
return Err(JqlRunnerError::InvalidArrayError(materialize(value)));
};
let Some(len) = NonZeroUsize::new(array.len()) else {
return Ok(Some(Cursor::Owned(Value::Array(Vec::new()))));
};
let (start, end) = range.to_boundaries(len);
if start >= len.get() || end >= len.get() {
return Err(JqlRunnerError::RangeOutOfBoundsError {
start,
end,
parent: materialize(value),
});
}
let (low, high, reverse) = span(start, end);
let mut selected: Vec<Value> = array
.iter()
.skip(low)
.take(high - low + 1)
.map(materialize)
.collect();
if reverse {
selected.reverse();
}
Cursor::Owned(Value::Array(selected))
}
Token::ObjectIndexSelector(indexes) => {
let Some(object) = value.as_object() else {
return Err(JqlRunnerError::InvalidObjectError(materialize(value)));
};
if object.is_empty() {
return Ok(Some(Cursor::Owned(Value::Object(Map::new()))));
}
let Some(&max) = indexes.iter().max() else {
return Ok(None);
};
let max: usize = max.into();
if max >= object.len() {
return Err(JqlRunnerError::IndexOutOfBoundsError {
index: max,
parent: materialize(value),
});
}
let entries: Vec<(&str, TapeValue)> = object.iter().collect();
let mut map = Map::with_capacity(indexes.len());
for index in indexes {
let index: usize = (*index).into();
if let Some(&(key, child)) = entries.get(index) {
map.insert(key.to_string(), materialize(child));
}
}
Cursor::Owned(Value::Object(map))
}
Token::ObjectRangeSelector(range) => {
let Some(object) = value.as_object() else {
return Err(JqlRunnerError::InvalidObjectError(materialize(value)));
};
let Some(len) = NonZeroUsize::new(object.len()) else {
return Ok(Some(Cursor::Owned(Value::Object(Map::new()))));
};
let (start, end) = range.to_boundaries(len);
if start >= len.get() || end >= len.get() {
return Err(JqlRunnerError::RangeOutOfBoundsError {
start,
end,
parent: materialize(value),
});
}
let (low, high, reverse) = span(start, end);
let mut entries: Vec<(&str, TapeValue)> =
object.iter().skip(low).take(high - low + 1).collect();
if reverse {
entries.reverse();
}
Cursor::Owned(Value::Object(
entries
.into_iter()
.map(|(key, child)| (key.to_string(), materialize(child)))
.collect(),
))
}
Token::MultiKeySelector(keys) => {
let Some(object) = value.as_object() else {
return Err(JqlRunnerError::InvalidObjectError(materialize(value)));
};
let mut missing: Vec<String> = keys
.iter()
.filter(|key| object.get(key.as_ref()).is_none())
.map(|key| (*key).to_string())
.collect();
if !missing.is_empty() {
missing.sort();
return Err(JqlRunnerError::MultiKeyNotFoundError {
keys: missing,
parent: materialize(value),
});
}
Cursor::Owned(Value::Object(
keys.iter()
.filter_map(|key| {
object
.get(key.as_ref())
.map(|child| (key.to_string(), materialize(child)))
})
.collect(),
))
}
Token::KeyOperator => Cursor::Owned(if let Some(object) = value.as_object() {
let mut keys: Vec<&str> = object.keys().collect();
keys.sort_unstable();
Value::Array(
keys.into_iter()
.map(|key| Value::String(key.to_string()))
.collect(),
)
} else if let Some(array) = value.as_array() {
Value::Array((0..array.len()).map(Value::from).collect())
} else {
materialize(value)
}),
Token::TruncateOperator => Cursor::Owned(match value.value_type() {
ValueType::Array => Value::Array(Vec::new()),
ValueType::Object => Value::Object(Map::new()),
_ => materialize(value),
}),
Token::GroupSeparator
| Token::FlattenOperator
| Token::LensSelector(_)
| Token::PipeInOperator
| Token::PipeOutOperator => return Ok(None),
};
Ok(Some(cursor))
}
fn operates_on_object(token: &Token) -> bool {
matches!(
token,
Token::MultiKeySelector(_)
| Token::ObjectIndexSelector(_)
| Token::ObjectRangeSelector(_)
| Token::KeyOperator
)
}
fn has_duplicate_keys(object: &TapeObject) -> bool {
let mut seen = HashSet::with_capacity(object.len());
object.keys().any(|key| !seen.insert(key))
}
fn span(start: usize, end: usize) -> (usize, usize, bool) {
if start < end {
(start, end, false)
} else {
(end, start, true)
}
}
fn materialize(value: TapeValue) -> Value {
if let Some(object) = value.as_object() {
let mut map = Map::with_capacity(object.len());
for (key, child) in &object {
map.insert(key.to_string(), materialize(child));
}
return Value::Object(map);
}
if let Some(array) = value.as_array() {
return Value::Array(array.iter().map(materialize).collect());
}
if value.as_null().is_some() {
return Value::Null;
}
if let Some(boolean) = value.as_bool() {
return Value::Bool(boolean);
}
if let Some(unsigned) = value.as_u64() {
return Value::Number(unsigned.into());
}
if let Some(signed) = value.as_i64() {
return Value::Number(signed.into());
}
if let Some(float) = value.as_f64() {
return Number::from_f64(float).map_or(Value::Null, Value::Number);
}
value
.as_str()
.map_or(Value::Null, |string| Value::String(string.to_string()))
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::raw;
use crate::errors::JqlRunnerError;
fn run(query: &str, json: &str) -> Result<serde_json::Value, JqlRunnerError> {
raw(query, &mut json.as_bytes().to_vec())
}
#[test]
fn tape_leaves_escape_free_input_intact() {
let mut json = br#"{ "a": [1, -2.5, true, null, "plain", { "b": "c" }] }"#.to_vec();
let expected = json.clone();
assert!(simd_json::to_tape(&mut json).is_ok());
assert_eq!(json, expected);
let mut escaped = br#"{ "a": "b\nc" }"#.to_vec();
let before = escaped.clone();
assert!(simd_json::to_tape(&mut escaped).is_ok());
assert_ne!(escaped, before);
}
#[test]
fn check_drill_down() {
assert_eq!(run(r#""a""b""#, r#"{ "a": { "b": 2 } }"#), Ok(json!(2)));
assert_eq!(
run(r#""a"[1]"c""#, r#"{ "a": [{ "c": 0 }, { "c": 1 }] }"#),
Ok(json!(1))
);
}
#[test]
fn check_selection_operators() {
assert_eq!(
run("[2,0]", r#"["a", "b", "c", "d"]"#),
Ok(json!(["c", "a"]))
);
assert_eq!(
run("[2:0]", r#"["a", "b", "c"]"#),
Ok(json!(["c", "b", "a"]))
);
assert_eq!(
run(r#"{"c","a"}"#, r#"{ "a": 1, "b": 2, "c": 3 }"#),
Ok(json!({ "c": 3, "a": 1 }))
);
assert_eq!(
run(r#""a"@"#, r#"{ "a": { "y": 1, "x": 2 } }"#),
Ok(json!(["x", "y"]))
);
assert_eq!(run(r#""a"!"#, r#"{ "a": [1, 2, 3] }"#), Ok(json!([])));
}
#[test]
fn check_groups() {
assert_eq!(
run(r#""a","b""#, r#"{ "a": 1, "b": 2 }"#),
Ok(json!([1, 2]))
);
}
#[test]
fn check_errors() {
assert_eq!(
run(r#""b""#, r#"{ "a": 1 }"#),
Err(JqlRunnerError::KeyNotFoundError {
key: "b".to_string(),
parent: json!({ "a": 1 }),
})
);
assert_eq!(
run("[1]", r#"["a"]"#),
Err(JqlRunnerError::IndexOutOfBoundsError {
index: 1,
parent: json!(["a"]),
})
);
}
#[test]
fn check_delegates_flatten() {
assert_eq!(
run(r#""a"..[0]"#, r#"{ "a": [1, [2], [[3]]] }"#),
Ok(json!(1))
);
}
#[test]
fn check_empty_query() {
assert_eq!(run("", "{}"), Err(JqlRunnerError::EmptyQueryError));
}
}