use crate::expr::idiom::Idiom;
use crate::expr::part::Part;
use crate::expr::{Expr, Literal};
use crate::val::Value;
impl Value {
pub(crate) fn every(
&self,
path: Option<&[Part]>,
steps: bool,
behavior: ArrayBehaviour,
) -> Vec<Idiom> {
let mut accum = if let Some(x) = path {
let mut res = x.to_vec();
while res.ends_with(&[Part::All]) {
res.pop();
}
res
} else {
Vec::new()
};
let mut build = Vec::new();
match path {
Some(path) => self.pick(path)._every(steps, behavior, &mut accum, &mut build),
None => self._every(steps, behavior, &mut accum, &mut build),
}
build
}
fn _every(
&self,
steps: bool,
behavior: ArrayBehaviour,
accum: &mut Vec<Part>,
build: &mut Vec<Idiom>,
) {
match self {
Value::Object(v) => {
if (steps || v.is_empty()) && !accum.is_empty() {
build.push(Idiom(accum.clone()))
}
for (k, v) in v.0.iter() {
accum.push(Part::Field(k.clone()));
v._every(steps, behavior, accum, build);
accum.pop();
}
}
Value::Array(v) => {
if !accum.is_empty() {
build.push(Idiom(accum.clone()))
}
match behavior {
ArrayBehaviour::Full => {
for (i, v) in v.iter().enumerate().rev() {
accum.push(Part::Value(Expr::Literal(Literal::Integer(i as i64))));
v._every(steps, behavior, accum, build);
accum.pop();
}
}
ArrayBehaviour::Ignore => {}
}
}
_ => {
if !accum.is_empty() {
build.push(Idiom(accum.clone()))
}
}
}
}
}
#[derive(Clone, Copy, Debug)]
pub enum ArrayBehaviour {
Ignore,
Full,
}
#[cfg(test)]
mod tests {
use surrealdb_types::ToSql;
use super::*;
use crate::syn;
macro_rules! parse_val {
($input:expr) => {
crate::val::convert_public_value_to_internal(syn::value($input).unwrap())
};
}
#[test]
fn every_empty() {
let val = parse_val!("{}");
let res: Vec<Idiom> = vec![];
assert_eq!(res, val.every(None, false, ArrayBehaviour::Ignore));
}
#[test]
fn every_with_empty_objects_arrays() {
let val = parse_val!("{ test: {}, status: false, something: {age: 45}, tags: []}");
let res: Vec<Idiom> = vec![
syn::idiom("something.age").unwrap().into(),
syn::idiom("status").unwrap().into(),
syn::idiom("tags").unwrap().into(),
syn::idiom("test").unwrap().into(),
];
assert_eq!(res, val.every(None, false, ArrayBehaviour::Ignore));
}
#[test]
fn every_without_array_indexes() {
let val = parse_val!(
"{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"
);
let res: Vec<Idiom> = vec![syn::idiom("test.something").unwrap().into()];
assert_eq!(res, val.every(None, false, ArrayBehaviour::Ignore));
}
#[test]
fn every_including_array_indexes() {
let val = parse_val!(
"{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"
);
let res: Vec<Idiom> = vec![
syn::idiom("test.something").unwrap().into(),
syn::idiom("test.something[1].age").unwrap().into(),
syn::idiom("test.something[1].tags").unwrap().into(),
syn::idiom("test.something[1].tags[1]").unwrap().into(),
syn::idiom("test.something[1].tags[0]").unwrap().into(),
syn::idiom("test.something[0].age").unwrap().into(),
syn::idiom("test.something[0].tags").unwrap().into(),
syn::idiom("test.something[0].tags[1]").unwrap().into(),
syn::idiom("test.something[0].tags[0]").unwrap().into(),
];
assert_eq!(res, val.every(None, false, ArrayBehaviour::Full));
}
#[test]
fn every_including_intermediary_nodes_without_array_indexes() {
let val = parse_val!(
"{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"
);
let res: Vec<Idiom> =
vec![syn::idiom("test").unwrap().into(), syn::idiom("test.something").unwrap().into()];
assert_eq!(res, val.every(None, true, ArrayBehaviour::Ignore));
}
#[test]
fn every_including_intermediary_nodes_including_array_indexes() {
let val = parse_val!(
"{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"
);
let res: Vec<Idiom> = vec![
syn::idiom("test").unwrap().into(),
syn::idiom("test.something").unwrap().into(),
syn::idiom("test.something[1]").unwrap().into(),
syn::idiom("test.something[1].age").unwrap().into(),
syn::idiom("test.something[1].tags").unwrap().into(),
syn::idiom("test.something[1].tags[1]").unwrap().into(),
syn::idiom("test.something[1].tags[0]").unwrap().into(),
syn::idiom("test.something[0]").unwrap().into(),
syn::idiom("test.something[0].age").unwrap().into(),
syn::idiom("test.something[0].tags").unwrap().into(),
syn::idiom("test.something[0].tags[1]").unwrap().into(),
syn::idiom("test.something[0].tags[0]").unwrap().into(),
];
assert_eq!(res, val.every(None, true, ArrayBehaviour::Full));
}
#[test]
fn every_given_one_path_part() {
let val = parse_val!(
"{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"
);
let val =
val.every(Some(&Idiom::from(syn::idiom("test").unwrap())), true, ArrayBehaviour::Full);
for v in val.iter() {
println!("{}", v.to_sql());
}
let res: Vec<Idiom> = vec![
syn::idiom("test").unwrap().into(),
syn::idiom("test.something").unwrap().into(),
syn::idiom("test.something[1]").unwrap().into(),
syn::idiom("test.something[1].age").unwrap().into(),
syn::idiom("test.something[1].tags").unwrap().into(),
syn::idiom("test.something[1].tags[1]").unwrap().into(),
syn::idiom("test.something[1].tags[0]").unwrap().into(),
syn::idiom("test.something[0]").unwrap().into(),
syn::idiom("test.something[0].age").unwrap().into(),
syn::idiom("test.something[0].tags").unwrap().into(),
syn::idiom("test.something[0].tags[1]").unwrap().into(),
syn::idiom("test.something[0].tags[0]").unwrap().into(),
];
assert_eq!(res, val,);
}
#[test]
fn every_given_two_path_parts() {
let val = parse_val!(
"{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"
);
let res: Vec<Idiom> = vec![
syn::idiom("test.something").unwrap().into(),
syn::idiom("test.something[1]").unwrap().into(),
syn::idiom("test.something[1].age").unwrap().into(),
syn::idiom("test.something[1].tags").unwrap().into(),
syn::idiom("test.something[1].tags[1]").unwrap().into(),
syn::idiom("test.something[1].tags[0]").unwrap().into(),
syn::idiom("test.something[0]").unwrap().into(),
syn::idiom("test.something[0].age").unwrap().into(),
syn::idiom("test.something[0].tags").unwrap().into(),
syn::idiom("test.something[0].tags[1]").unwrap().into(),
syn::idiom("test.something[0].tags[0]").unwrap().into(),
];
assert_eq!(
res,
val.every(
Some(&Idiom::from(syn::idiom("test.something").unwrap())),
true,
ArrayBehaviour::Full
)
);
}
#[test]
fn every_including_intermediary_nodes_including_array_indexes_ending_all() {
let val = parse_val!(
"{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"
);
let res: Vec<Idiom> = vec![
syn::idiom("test.something").unwrap().into(),
syn::idiom("test.something[1]").unwrap().into(),
syn::idiom("test.something[1].age").unwrap().into(),
syn::idiom("test.something[1].tags").unwrap().into(),
syn::idiom("test.something[1].tags[1]").unwrap().into(),
syn::idiom("test.something[1].tags[0]").unwrap().into(),
syn::idiom("test.something[0]").unwrap().into(),
syn::idiom("test.something[0].age").unwrap().into(),
syn::idiom("test.something[0].tags").unwrap().into(),
syn::idiom("test.something[0].tags[1]").unwrap().into(),
syn::idiom("test.something[0].tags[0]").unwrap().into(),
];
assert_eq!(
res,
val.every(
Some(&Idiom::from(syn::idiom("test.something.*").unwrap())),
true,
ArrayBehaviour::Full
)
);
}
#[test]
fn every_wildcards() {
let val = parse_val!(
"{ test: { a: { color: 'red' }, b: { color: 'blue' }, c: { color: 'green' } } }"
);
let res: Vec<Idiom> = vec![
syn::idiom("test.*.color").unwrap().into(),
syn::idiom("test.*.color[2]").unwrap().into(),
syn::idiom("test.*.color[1]").unwrap().into(),
syn::idiom("test.*.color[0]").unwrap().into(),
];
assert_eq!(
res,
val.every(
Some(&Idiom::from(syn::idiom("test.*.color").unwrap())),
true,
ArrayBehaviour::Full
)
);
}
}