Skip to main content

surrealdb_expr/val/value/
each.rs

1use crate::expr::idiom::Idiom;
2use crate::expr::part::Part;
3use crate::val::Value;
4
5impl Value {
6	/// Widens an idiom into a list of idioms with the given object.
7	/// Resolving .* to all the different fields or indexes in that position.
8	///
9	/// For example `a.*.*` with the object `{ a: { b:  [1,2], c: 1} }` resolves
10	/// to a.b[0], a.b[1], a.c.
11	/// and `a.$` with object `{ a: [1,2,3] }` resolves to `a[2]`.
12	pub fn each(&self, path: &[Part]) -> Vec<Idiom> {
13		let mut accum = Vec::new();
14		let mut build = Vec::new();
15		self._each(path, &mut accum, &mut build);
16		build
17	}
18
19	fn _each(&self, path: &[Part], accum: &mut Vec<Part>, build: &mut Vec<Idiom>) {
20		let Some((first, rest)) = path.split_first() else {
21			build.push(Idiom(accum.clone()));
22			return;
23		};
24
25		// Get the current path part
26		match self {
27			// Current path part is an object
28			Value::Object(v) => match first {
29				Part::Field(f) => {
30					if let Some(v) = v.get(f) {
31						accum.push(Part::Field(f.clone()));
32						v._each(rest, accum, build);
33						accum.pop();
34					}
35				}
36				Part::All => {
37					for (k, v) in v.iter() {
38						accum.push(Part::Field(k.clone()));
39						v._each(rest, accum, build);
40						accum.pop();
41					}
42				}
43				_ => {}
44			},
45			// Current path part is an array
46			Value::Array(v) => match first {
47				Part::All => {
48					for (idx, v) in v.iter().enumerate() {
49						accum.push(Part::index_int(idx as i64));
50						v._each(rest, accum, build);
51						accum.pop();
52					}
53				}
54				Part::First => {
55					if !v.is_empty() {
56						accum.push(Part::index_int(0));
57						v[0]._each(rest, accum, build);
58						accum.pop();
59					}
60				}
61				Part::Last => {
62					let len = v.len();
63					if len > 0 {
64						accum.push(Part::index_int(len as i64 - 1));
65						v[len - 1]._each(rest, accum, build);
66						accum.pop();
67					}
68				}
69				x => {
70					if let Some(idx) = x.as_old_index() {
71						if let Some(v) = v.get(idx) {
72							accum.push(x.clone());
73							v._each(rest, accum, build);
74							accum.pop();
75						}
76					} else {
77						// A part that does not address an element addresses
78						// something *within* each element, so it is carried into
79						// the recursion instead of being consumed here — the same
80						// distribution `Value::pick` and `Value::cut` apply, and
81						// what makes `a.b` on an array of objects widen to
82						// `a[0].b`, `a[1].b`. Consuming it would instead yield
83						// each element's own path, so a caller cutting those
84						// paths would remove whole elements in place of the one
85						// field this part names.
86						for (idx, v) in v.iter().enumerate() {
87							accum.push(Part::index_int(idx as i64));
88							v._each(path, accum, build);
89							accum.pop();
90						}
91					}
92				}
93			},
94			// Current path part is a set
95			Value::Set(v) => match first {
96				Part::All => {
97					for (idx, v) in v.iter().enumerate() {
98						accum.push(Part::index_int(idx as i64));
99						v._each(rest, accum, build);
100						accum.pop();
101					}
102				}
103				Part::First => {
104					if let Some(v) = v.first() {
105						accum.push(Part::First);
106						v._each(rest, accum, build);
107						accum.pop();
108					}
109				}
110				Part::Last => {
111					if let Some(v) = v.last() {
112						accum.push(Part::Last);
113						v._each(rest, accum, build);
114						accum.pop();
115					}
116				}
117				x => {
118					if let Some(idx) = x.as_old_index() {
119						if let Some(v) = v.nth(idx) {
120							accum.push(x.clone());
121							v._each(rest, accum, build);
122							accum.pop();
123						}
124					} else {
125						// Carried into the recursion, not consumed — see the
126						// array arm above.
127						for (idx, v) in v.iter().enumerate() {
128							accum.push(Part::index_int(idx as i64));
129							v._each(path, accum, build);
130							accum.pop();
131						}
132					}
133				}
134			},
135			// Ignore everything else
136			_ => {}
137		}
138	}
139}
140
141#[cfg(test)]
142mod tests {
143
144	use super::*;
145	use crate::syn;
146
147	macro_rules! parse_val {
148		($input:expr) => {
149			crate::val::convert_public_value_to_internal(syn::value($input).unwrap())
150		};
151	}
152
153	#[test]
154	fn each_none() {
155		let idi: Idiom = Idiom::default();
156		let val = parse_val!("{ test: { other: null, something: 123 } }");
157		let res: Vec<Idiom> = vec![Idiom::default()];
158		assert_eq!(res, val.each(&idi));
159		assert_eq!(val.pick(&res[0]), parse_val!("{ test: { other: null, something: 123 } }"));
160	}
161
162	#[test]
163	fn each_basic() {
164		let idi: Idiom = syn::idiom("test.something").unwrap().into();
165		let val = parse_val!("{ test: { other: null, something: 123 } }");
166		let res: Vec<Idiom> = vec![syn::idiom("test.something").unwrap().into()];
167		assert_eq!(res, val.each(&idi));
168		assert_eq!(val.pick(&res[0]), Value::from(123));
169	}
170
171	#[test]
172	fn each_array() {
173		let idi: Idiom = syn::idiom("test.something").unwrap().into();
174		let val = parse_val!("{ test: { something: [{ age: 34 }, { age: 36 }] } }");
175		let res: Vec<Idiom> = vec![syn::idiom("test.something").unwrap().into()];
176		assert_eq!(res, val.each(&idi));
177		assert_eq!(val.pick(&res[0]), parse_val!("[{ age: 34 }, { age: 36 }]"));
178	}
179
180	#[test]
181	fn each_array_implicit_field() {
182		// Without `[*]`, a part at an array position still addresses something
183		// inside each element, so it widens the same way `pick` and `cut`
184		// distribute it — never to the elements' own paths, which a caller
185		// cutting the result would remove whole.
186		let idi: Idiom = syn::idiom("test.something.age").unwrap().into();
187		let val = parse_val!("{ test: { something: [{ age: 34 }, { age: 36 }] } }");
188		let res: Vec<Idiom> = vec![
189			syn::idiom("test.something[0].age").unwrap().into(),
190			syn::idiom("test.something[1].age").unwrap().into(),
191		];
192		assert_eq!(res, val.each(&idi));
193		assert_eq!(val.pick(&res[0]), Value::from(34));
194		assert_eq!(val.pick(&res[1]), Value::from(36));
195	}
196
197	#[test]
198	fn each_array_implicit_field_absent() {
199		// A part no element carries matches nothing at all. Yielding the
200		// elements' paths here is what let a field-permission pass erase an
201		// entire array for a field none of its objects even define.
202		let idi: Idiom = syn::idiom("test.something.missing").unwrap().into();
203		let val = parse_val!("{ test: { something: [{ age: 34 }, { age: 36 }] } }");
204		assert_eq!(Vec::<Idiom>::new(), val.each(&idi));
205	}
206
207	#[test]
208	fn each_array_implicit_field_partial() {
209		// Only the elements that carry the part are addressed.
210		let idi: Idiom = syn::idiom("test.something.age").unwrap().into();
211		let val = parse_val!("{ test: { something: [{ age: 34 }, { name: 'tobie' }] } }");
212		let res: Vec<Idiom> = vec![syn::idiom("test.something[0].age").unwrap().into()];
213		assert_eq!(res, val.each(&idi));
214		assert_eq!(val.pick(&res[0]), Value::from(34));
215	}
216
217	#[test]
218	fn each_array_field() {
219		let idi: Idiom = syn::idiom("test.something[*].age").unwrap().into();
220		let val = parse_val!("{ test: { something: [{ age: 34 }, { age: 36 }] } }");
221		let res: Vec<Idiom> = vec![
222			syn::idiom("test.something[0].age").unwrap().into(),
223			syn::idiom("test.something[1].age").unwrap().into(),
224		];
225		assert_eq!(res, val.each(&idi));
226		assert_eq!(val.pick(&res[0]), Value::from(34));
227		assert_eq!(val.pick(&res[1]), Value::from(36));
228	}
229
230	#[test]
231	fn each_array_field_embedded() {
232		let idi: Idiom = syn::idiom("test.something[*].tags").unwrap().into();
233		let val = parse_val!(
234			"{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"
235		);
236		let res: Vec<Idiom> = vec![
237			syn::idiom("test.something[0].tags").unwrap().into(),
238			syn::idiom("test.something[1].tags").unwrap().into(),
239		];
240		assert_eq!(res, val.each(&idi));
241		assert_eq!(val.pick(&res[0]), parse_val!("['code', 'databases']"));
242		assert_eq!(val.pick(&res[1]), parse_val!("['design', 'operations']"));
243	}
244
245	#[test]
246	fn each_array_field_embedded_index() {
247		let idi: Idiom = syn::idiom("test.something[*].tags[1]").unwrap().into();
248		let val = parse_val!(
249			"{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"
250		);
251		let res: Vec<Idiom> = vec![
252			syn::idiom("test.something[0].tags[1]").unwrap().into(),
253			syn::idiom("test.something[1].tags[1]").unwrap().into(),
254		];
255		assert_eq!(res, val.each(&idi));
256		assert_eq!(val.pick(&res[0]), Value::from("databases"));
257		assert_eq!(val.pick(&res[1]), Value::from("operations"));
258	}
259
260	#[test]
261	fn each_array_field_embedded_index_all() {
262		let idi: Idiom = syn::idiom("test.something[*].tags[*]").unwrap().into();
263		let val = parse_val!(
264			"{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"
265		);
266		let res: Vec<Idiom> = vec![
267			syn::idiom("test.something[0].tags[0]").unwrap().into(),
268			syn::idiom("test.something[0].tags[1]").unwrap().into(),
269			syn::idiom("test.something[1].tags[0]").unwrap().into(),
270			syn::idiom("test.something[1].tags[1]").unwrap().into(),
271		];
272		assert_eq!(res, val.each(&idi));
273		assert_eq!(val.pick(&res[0]), Value::from("code"));
274		assert_eq!(val.pick(&res[1]), Value::from("databases"));
275		assert_eq!(val.pick(&res[2]), Value::from("design"));
276		assert_eq!(val.pick(&res[3]), Value::from("operations"));
277	}
278
279	#[test]
280	fn each_wildcards() {
281		let val = parse_val!(
282			"{ test: { a: { color: 'red' }, b: { color: 'blue' }, c: { color: 'green' } } }"
283		);
284
285		let res: Vec<Idiom> = vec![
286			syn::idiom("test.a.color").unwrap().into(),
287			syn::idiom("test.b.color").unwrap().into(),
288			syn::idiom("test.c.color").unwrap().into(),
289		];
290
291		assert_eq!(res, val.each(&Idiom::from(syn::idiom("test.*.color").unwrap())));
292	}
293
294	/// Regression test for an off-by-one in the `Part::Last` arm of the array branch: the earlier
295	/// code indexed `v[len]` (one past the last valid index), so any `idiom[$]` traversal of a
296	/// non-empty array panicked via `<[T]>::index`. Now it reads `v[len - 1]` and yields the
297	/// idiom pointing at the final element.
298	#[test]
299	fn each_array_last() {
300		let idi: Idiom = syn::idiom("test.something[$]").unwrap().into();
301		let val = parse_val!("{ test: { something: [{ age: 34 }, { age: 36 }, { age: 42 }] } }");
302		let res: Vec<Idiom> = vec![syn::idiom("test.something[2]").unwrap().into()];
303		assert_eq!(res, val.each(&idi));
304		assert_eq!(val.pick(&res[0]), parse_val!("{ age: 42 }"));
305	}
306
307	/// `Part::Last` on an empty array must resolve to no idioms and must not panic.
308	#[test]
309	fn each_array_last_empty() {
310		let idi: Idiom = syn::idiom("test.something[$]").unwrap().into();
311		let val = parse_val!("{ test: { something: [] } }");
312		assert_eq!(val.each(&idi), Vec::<Idiom>::new());
313	}
314
315	#[test]
316	fn each_set_last() {
317		let idi: Idiom = syn::idiom("tags[$]").unwrap().into();
318		let val = parse_val!("{ tags: {1, 3, 5} }");
319		let res: Vec<Idiom> = vec![syn::idiom("tags[$]").unwrap().into()];
320		assert_eq!(res, val.each(&idi));
321		assert_eq!(val.pick(&res[0]), Value::from(5));
322	}
323}