Skip to main content

surrealdb_expr/val/value/
every.rs

1use crate::expr::idiom::Idiom;
2use crate::expr::part::Part;
3use crate::expr::{Expr, Literal};
4use crate::val::Value;
5
6impl Value {
7	/// Returns a list of idioms for then entries of a possibly nested value.
8	///
9	/// Exact behavior of this function is dictated by the ArrayBehaviour param
10	/// and steps. Steps enables intermediate idioms instead of only the leaf
11	/// values. For the changes in behavior with ArrayBehaviour see the docs
12	/// for that enum.
13	pub fn every(
14		&self,
15		path: Option<&[Part]>,
16		steps: bool,
17		behavior: ArrayBehaviour,
18	) -> Vec<Idiom> {
19		let mut accum = if let Some(x) = path {
20			let mut res = x.to_vec();
21			while res.ends_with(&[Part::All]) {
22				res.pop();
23			}
24			res
25		} else {
26			Vec::new()
27		};
28
29		let mut build = Vec::new();
30		match path {
31			Some(path) => self.pick(path)._every(steps, behavior, &mut accum, &mut build),
32			None => self._every(steps, behavior, &mut accum, &mut build),
33		}
34		build
35	}
36
37	/// Recursive version of public fn every.
38	fn _every(
39		&self,
40		steps: bool,
41		behavior: ArrayBehaviour,
42		accum: &mut Vec<Part>,
43		build: &mut Vec<Idiom>,
44	) {
45		match self {
46			// Current path part is an object and is not empty
47			Value::Object(v) => {
48				if (steps || v.is_empty()) && !accum.is_empty() {
49					build.push(Idiom(accum.clone()))
50				}
51
52				for (k, v) in v.0.iter() {
53					accum.push(Part::Field(k.clone()));
54					v._every(steps, behavior, accum, build);
55					accum.pop();
56				}
57			}
58			// Current path part is an array and is not empty
59			Value::Array(v) => {
60				if !accum.is_empty() {
61					build.push(Idiom(accum.clone()))
62				}
63
64				// Check if we should log individual array items
65				match behavior {
66					// Let's log all individual array items
67					ArrayBehaviour::Full => {
68						// For some reason this is in reverse order.
69						for (i, v) in v.iter().enumerate().rev() {
70							accum.push(Part::Value(Expr::Literal(Literal::Integer(i as i64))));
71							v._every(steps, behavior, accum, build);
72							accum.pop();
73						}
74					}
75					// Let's skip this array's values entirely
76					ArrayBehaviour::Ignore => {}
77				}
78			}
79			// Process every other path
80			_ => {
81				if !accum.is_empty() {
82					build.push(Idiom(accum.clone()))
83				}
84			}
85		}
86	}
87}
88
89// Assuming a value like: { foo: [{ bar: 123 }] }
90#[derive(Clone, Copy, Debug)]
91pub enum ArrayBehaviour {
92	// Do not process this array at all
93	// [foo]
94	Ignore,
95	// Give back all nested paths and all indexes of the array
96	// [foo, foo[0], foo[0].bar]
97	Full,
98}
99
100#[cfg(test)]
101mod tests {
102
103	use surrealdb_types::ToSql;
104
105	use super::*;
106	use crate::syn;
107
108	macro_rules! parse_val {
109		($input:expr) => {
110			crate::val::convert_public_value_to_internal(syn::value($input).unwrap())
111		};
112	}
113
114	#[test]
115	fn every_empty() {
116		let val = parse_val!("{}");
117		let res: Vec<Idiom> = vec![];
118		assert_eq!(res, val.every(None, false, ArrayBehaviour::Ignore));
119	}
120
121	#[test]
122	fn every_with_empty_objects_arrays() {
123		let val = parse_val!("{ test: {}, status: false, something: {age: 45}, tags: []}");
124		let res: Vec<Idiom> = vec![
125			syn::idiom("something.age").unwrap().into(),
126			syn::idiom("status").unwrap().into(),
127			syn::idiom("tags").unwrap().into(),
128			syn::idiom("test").unwrap().into(),
129		];
130		assert_eq!(res, val.every(None, false, ArrayBehaviour::Ignore));
131	}
132
133	#[test]
134	fn every_without_array_indexes() {
135		let val = parse_val!(
136			"{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"
137		);
138		let res: Vec<Idiom> = vec![syn::idiom("test.something").unwrap().into()];
139		assert_eq!(res, val.every(None, false, ArrayBehaviour::Ignore));
140	}
141
142	#[test]
143	fn every_including_array_indexes() {
144		let val = parse_val!(
145			"{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"
146		);
147		let res: Vec<Idiom> = vec![
148			syn::idiom("test.something").unwrap().into(),
149			syn::idiom("test.something[1].age").unwrap().into(),
150			syn::idiom("test.something[1].tags").unwrap().into(),
151			syn::idiom("test.something[1].tags[1]").unwrap().into(),
152			syn::idiom("test.something[1].tags[0]").unwrap().into(),
153			syn::idiom("test.something[0].age").unwrap().into(),
154			syn::idiom("test.something[0].tags").unwrap().into(),
155			syn::idiom("test.something[0].tags[1]").unwrap().into(),
156			syn::idiom("test.something[0].tags[0]").unwrap().into(),
157		];
158		assert_eq!(res, val.every(None, false, ArrayBehaviour::Full));
159	}
160
161	#[test]
162	fn every_including_intermediary_nodes_without_array_indexes() {
163		let val = parse_val!(
164			"{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"
165		);
166		let res: Vec<Idiom> =
167			vec![syn::idiom("test").unwrap().into(), syn::idiom("test.something").unwrap().into()];
168		assert_eq!(res, val.every(None, true, ArrayBehaviour::Ignore));
169	}
170
171	#[test]
172	fn every_including_intermediary_nodes_including_array_indexes() {
173		let val = parse_val!(
174			"{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"
175		);
176		let res: Vec<Idiom> = vec![
177			syn::idiom("test").unwrap().into(),
178			syn::idiom("test.something").unwrap().into(),
179			syn::idiom("test.something[1]").unwrap().into(),
180			syn::idiom("test.something[1].age").unwrap().into(),
181			syn::idiom("test.something[1].tags").unwrap().into(),
182			syn::idiom("test.something[1].tags[1]").unwrap().into(),
183			syn::idiom("test.something[1].tags[0]").unwrap().into(),
184			syn::idiom("test.something[0]").unwrap().into(),
185			syn::idiom("test.something[0].age").unwrap().into(),
186			syn::idiom("test.something[0].tags").unwrap().into(),
187			syn::idiom("test.something[0].tags[1]").unwrap().into(),
188			syn::idiom("test.something[0].tags[0]").unwrap().into(),
189		];
190		assert_eq!(res, val.every(None, true, ArrayBehaviour::Full));
191	}
192
193	#[test]
194	fn every_given_one_path_part() {
195		let val = parse_val!(
196			"{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"
197		);
198
199		let val =
200			val.every(Some(&Idiom::from(syn::idiom("test").unwrap())), true, ArrayBehaviour::Full);
201		for v in val.iter() {
202			println!("{}", v.to_sql());
203		}
204
205		let res: Vec<Idiom> = vec![
206			syn::idiom("test").unwrap().into(),
207			syn::idiom("test.something").unwrap().into(),
208			syn::idiom("test.something[1]").unwrap().into(),
209			syn::idiom("test.something[1].age").unwrap().into(),
210			syn::idiom("test.something[1].tags").unwrap().into(),
211			syn::idiom("test.something[1].tags[1]").unwrap().into(),
212			syn::idiom("test.something[1].tags[0]").unwrap().into(),
213			syn::idiom("test.something[0]").unwrap().into(),
214			syn::idiom("test.something[0].age").unwrap().into(),
215			syn::idiom("test.something[0].tags").unwrap().into(),
216			syn::idiom("test.something[0].tags[1]").unwrap().into(),
217			syn::idiom("test.something[0].tags[0]").unwrap().into(),
218		];
219		assert_eq!(res, val,);
220	}
221
222	#[test]
223	fn every_given_two_path_parts() {
224		let val = parse_val!(
225			"{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"
226		);
227		let res: Vec<Idiom> = vec![
228			syn::idiom("test.something").unwrap().into(),
229			syn::idiom("test.something[1]").unwrap().into(),
230			syn::idiom("test.something[1].age").unwrap().into(),
231			syn::idiom("test.something[1].tags").unwrap().into(),
232			syn::idiom("test.something[1].tags[1]").unwrap().into(),
233			syn::idiom("test.something[1].tags[0]").unwrap().into(),
234			syn::idiom("test.something[0]").unwrap().into(),
235			syn::idiom("test.something[0].age").unwrap().into(),
236			syn::idiom("test.something[0].tags").unwrap().into(),
237			syn::idiom("test.something[0].tags[1]").unwrap().into(),
238			syn::idiom("test.something[0].tags[0]").unwrap().into(),
239		];
240		assert_eq!(
241			res,
242			val.every(
243				Some(&Idiom::from(syn::idiom("test.something").unwrap())),
244				true,
245				ArrayBehaviour::Full
246			)
247		);
248	}
249
250	#[test]
251	fn every_including_intermediary_nodes_including_array_indexes_ending_all() {
252		let val = parse_val!(
253			"{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"
254		);
255		let res: Vec<Idiom> = vec![
256			syn::idiom("test.something").unwrap().into(),
257			syn::idiom("test.something[1]").unwrap().into(),
258			syn::idiom("test.something[1].age").unwrap().into(),
259			syn::idiom("test.something[1].tags").unwrap().into(),
260			syn::idiom("test.something[1].tags[1]").unwrap().into(),
261			syn::idiom("test.something[1].tags[0]").unwrap().into(),
262			syn::idiom("test.something[0]").unwrap().into(),
263			syn::idiom("test.something[0].age").unwrap().into(),
264			syn::idiom("test.something[0].tags").unwrap().into(),
265			syn::idiom("test.something[0].tags[1]").unwrap().into(),
266			syn::idiom("test.something[0].tags[0]").unwrap().into(),
267		];
268		assert_eq!(
269			res,
270			val.every(
271				Some(&Idiom::from(syn::idiom("test.something.*").unwrap())),
272				true,
273				ArrayBehaviour::Full
274			)
275		);
276	}
277
278	#[test]
279	fn every_wildcards() {
280		let val = parse_val!(
281			"{ test: { a: { color: 'red' }, b: { color: 'blue' }, c: { color: 'green' } } }"
282		);
283
284		let res: Vec<Idiom> = vec![
285			syn::idiom("test.*.color").unwrap().into(),
286			syn::idiom("test.*.color[2]").unwrap().into(),
287			syn::idiom("test.*.color[1]").unwrap().into(),
288			syn::idiom("test.*.color[0]").unwrap().into(),
289		];
290
291		assert_eq!(
292			res,
293			val.every(
294				Some(&Idiom::from(syn::idiom("test.*.color").unwrap())),
295				true,
296				ArrayBehaviour::Full
297			)
298		);
299	}
300}