Skip to main content

surrealdb_expr/val/value/
walk.rs

1use crate::expr::idiom::Idiom;
2use crate::expr::part::{Next, Part};
3use crate::val::Value;
4
5impl Value {
6	pub fn walk(&self, path: &[Part]) -> Vec<(Idiom, Self)> {
7		self._walk(path, Idiom::default())
8	}
9	fn _walk(&self, path: &[Part], prev: Idiom) -> Vec<(Idiom, Self)> {
10		match path.first() {
11			// Get the current path part
12			Some(p) => match self {
13				// Current path part is an object
14				Value::Object(v) => match p {
15					Part::Field(f) => match v.get(f as &str) {
16						Some(v) => v._walk(path.next(), prev.push(p.clone())),
17						None => Value::None._walk(path.next(), prev.push(p.clone())),
18					},
19					Part::All => v
20						.iter()
21						.flat_map(|(field, v)| {
22							v._walk(path.next(), prev.clone().push(Part::Field(field.clone())))
23						})
24						.collect::<Vec<_>>(),
25					x => {
26						if let Some(idx) = x.as_old_index() {
27							match v.get(&idx.to_string()) {
28								Some(v) => v._walk(path.next(), prev.push(p.clone())),
29								None => Value::None._walk(path.next(), prev.push(p.clone())),
30							}
31						} else {
32							vec![]
33						}
34					}
35				},
36				// Current path part is an array
37				Value::Array(v) => match p {
38					Part::First => match v.first() {
39						Some(v) => v._walk(path.next(), prev.push(p.clone())),
40						None => vec![],
41					},
42					Part::Last => match v.last() {
43						Some(v) => v._walk(path.next(), prev.push(p.clone())),
44						None => vec![],
45					},
46					x => {
47						if let Some(idx) = x.as_old_index() {
48							match v.get(idx) {
49								Some(v) => v._walk(path.next(), prev.push(p.clone())),
50								None => vec![],
51							}
52						} else {
53							v.iter()
54								.enumerate()
55								.flat_map(|(i, v)| {
56									v._walk(
57										path.next(),
58										prev.clone().push(Part::index_int(i as i64)),
59									)
60								})
61								.collect::<Vec<_>>()
62						}
63					}
64				},
65				// Current path part is a set
66				Value::Set(v) => match p {
67					Part::First => match v.first() {
68						Some(v) => v._walk(path.next(), prev.push(p.clone())),
69						None => vec![],
70					},
71					Part::Last => match v.last() {
72						Some(v) => v._walk(path.next(), prev.push(p.clone())),
73						None => vec![],
74					},
75					x => {
76						if let Some(idx) = x.as_old_index() {
77							match v.nth(idx) {
78								Some(v) => v._walk(path.next(), prev.push(p.clone())),
79								None => vec![],
80							}
81						} else {
82							v.iter()
83								.enumerate()
84								.flat_map(|(i, v)| {
85									v._walk(
86										path.next(),
87										prev.clone().push(Part::index_int(i as i64)),
88									)
89								})
90								.collect::<Vec<_>>()
91						}
92					}
93				},
94				// Ignore everything else
95				_ => match p {
96					Part::Field(_) => Value::None._walk(path.next(), prev.push(p.clone())),
97					x => {
98						if x.as_old_index().is_some() {
99							Value::None._walk(path.next(), prev.push(p.clone()))
100						} else {
101							vec![]
102						}
103					}
104				},
105			},
106			// No more parts so get the value
107			None => vec![(prev, self.clone())],
108		}
109	}
110
111	/// Visits every value [`Value::walk`] would return, in the same order, by
112	/// reference.
113	///
114	/// `walk` answers with owned values and the paths that name them, so a
115	/// caller that only measures what it finds deep-clones every match — a
116	/// whole array for an array field — and drops it again. This is the same
117	/// traversal with the path and the clone left out, and the two must stay
118	/// in step: a measurement taken here has to be a measurement of what a
119	/// caller reading `walk` would see.
120	pub fn walk_ref(&self, path: &[Part], visit: &mut impl FnMut(&Self)) {
121		let Some(p) = path.first() else {
122			// No more parts so visit the value
123			visit(self);
124			return;
125		};
126		match self {
127			// Current path part is an object
128			Value::Object(v) => match p {
129				Part::Field(f) => match v.get(f as &str) {
130					Some(v) => v.walk_ref(path.next(), visit),
131					None => Value::None.walk_ref(path.next(), visit),
132				},
133				Part::All => {
134					for (_, v) in v.iter() {
135						v.walk_ref(path.next(), visit);
136					}
137				}
138				x => {
139					if let Some(idx) = x.as_old_index() {
140						match v.get(&idx.to_string()) {
141							Some(v) => v.walk_ref(path.next(), visit),
142							None => Value::None.walk_ref(path.next(), visit),
143						}
144					}
145				}
146			},
147			// Current path part is an array
148			Value::Array(v) => match p {
149				Part::First => {
150					if let Some(v) = v.first() {
151						v.walk_ref(path.next(), visit);
152					}
153				}
154				Part::Last => {
155					if let Some(v) = v.last() {
156						v.walk_ref(path.next(), visit);
157					}
158				}
159				x => match x.as_old_index() {
160					Some(idx) => {
161						if let Some(v) = v.get(idx) {
162							v.walk_ref(path.next(), visit);
163						}
164					}
165					None => {
166						for v in v.iter() {
167							v.walk_ref(path.next(), visit);
168						}
169					}
170				},
171			},
172			// Current path part is a set
173			Value::Set(v) => match p {
174				Part::First => {
175					if let Some(v) = v.first() {
176						v.walk_ref(path.next(), visit);
177					}
178				}
179				Part::Last => {
180					if let Some(v) = v.last() {
181						v.walk_ref(path.next(), visit);
182					}
183				}
184				x => match x.as_old_index() {
185					Some(idx) => {
186						if let Some(v) = v.nth(idx) {
187							v.walk_ref(path.next(), visit);
188						}
189					}
190					None => {
191						for v in v.iter() {
192							v.walk_ref(path.next(), visit);
193						}
194					}
195				},
196			},
197			// Ignore everything else
198			_ => match p {
199				Part::Field(_) => Value::None.walk_ref(path.next(), visit),
200				x => {
201					if x.as_old_index().is_some() {
202						Value::None.walk_ref(path.next(), visit);
203					}
204				}
205			},
206		}
207	}
208}
209
210#[cfg(test)]
211mod tests {
212
213	use super::*;
214	use crate::expr::Idiom;
215	use crate::syn;
216
217	macro_rules! parse_val {
218		($input:expr) => {
219			crate::val::convert_public_value_to_internal(syn::value($input).unwrap())
220		};
221	}
222
223	#[test]
224	fn walk_blank() {
225		let idi: Idiom = Default::default();
226		let val = parse_val!("{ test: { other: null, something: 123 } }");
227		let res: Vec<(Idiom, Value)> =
228			vec![(Idiom::default(), parse_val!("{ test: { other: null, something: 123 } }"))];
229		assert_eq!(res, val.walk(&idi));
230	}
231
232	#[test]
233	fn walk_basic() {
234		let idi: Idiom = syn::idiom("test.something").unwrap().into();
235		let val = parse_val!("{ test: { other: null, something: 123 } }");
236		let res: Vec<(Idiom, Value)> =
237			vec![(syn::idiom("test.something").unwrap().into(), Value::from(123))];
238		assert_eq!(res, val.walk(&idi));
239	}
240
241	#[test]
242	fn walk_empty() {
243		let idi: Idiom = syn::idiom("test.missing").unwrap().into();
244		let val = parse_val!("{ test: { other: null, something: 123 } }");
245		let res: Vec<(Idiom, Value)> =
246			vec![(syn::idiom("test.missing").unwrap().into(), Value::None)];
247		assert_eq!(res, val.walk(&idi));
248	}
249
250	#[test]
251	fn walk_empty_object() {
252		let idi: Idiom = syn::idiom("none.something.age").unwrap().into();
253		let val = parse_val!("{ test: { something: [{ age: 34 }, { age: 36 }] } }");
254		let res: Vec<(Idiom, Value)> =
255			vec![(syn::idiom("none.something.age").unwrap().into(), Value::None)];
256		assert_eq!(res, val.walk(&idi));
257	}
258
259	#[test]
260	fn walk_empty_array() {
261		let idi: Idiom = syn::idiom("none.something.*.age").unwrap().into();
262		let val = parse_val!("{ test: { something: [{ age: 34 }, { age: 36 }] } }");
263		let res: Vec<(Idiom, Value)> = vec![];
264		assert_eq!(res, val.walk(&idi));
265	}
266
267	#[test]
268	fn walk_empty_array_index() {
269		let idi: Idiom = syn::idiom("none.something[0].age").unwrap().into();
270		let val = parse_val!("{ test: { something: [{ age: 34 }, { age: 36 }] } }");
271		let res: Vec<(Idiom, Value)> =
272			vec![(syn::idiom("none.something[0].age").unwrap().into(), Value::None)];
273		assert_eq!(res, val.walk(&idi));
274	}
275
276	#[test]
277	fn walk_array() {
278		let idi: Idiom = syn::idiom("test.something").unwrap().into();
279		let val = parse_val!("{ test: { something: [{ age: 34 }, { age: 36 }] } }");
280		let res = vec![(
281			syn::idiom("test.something").unwrap().into(),
282			parse_val!("[{ age: 34 }, { age: 36 }]"),
283		)];
284		assert_eq!(res, val.walk(&idi));
285	}
286
287	#[test]
288	fn walk_array_field() {
289		let idi: Idiom = syn::idiom("test.something[*].age").unwrap().into();
290		let val = parse_val!("{ test: { something: [{ age: 34 }, { age: 36 }] } }");
291		let res: Vec<(Idiom, Value)> = vec![
292			(syn::idiom("test.something[0].age").unwrap().into(), Value::from(34)),
293			(syn::idiom("test.something[1].age").unwrap().into(), Value::from(36)),
294		];
295		assert_eq!(res, val.walk(&idi));
296	}
297
298	#[test]
299	fn walk_array_field_embedded() {
300		let idi: Idiom = syn::idiom("test.something[*].tags").unwrap().into();
301		let val = parse_val!(
302			"{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"
303		);
304		let res: Vec<(Idiom, Value)> = vec![
305			(
306				syn::idiom("test.something[0].tags").unwrap().into(),
307				parse_val!("['code', 'databases']"),
308			),
309			(
310				syn::idiom("test.something[1].tags").unwrap().into(),
311				parse_val!("['design', 'operations']"),
312			),
313		];
314		assert_eq!(res, val.walk(&idi));
315	}
316
317	#[test]
318	fn walk_array_field_embedded_index() {
319		let idi: Idiom = syn::idiom("test.something[*].tags[1]").unwrap().into();
320		let val = parse_val!(
321			"{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"
322		);
323		let res: Vec<(Idiom, Value)> = vec![
324			(syn::idiom("test.something[0].tags[1]").unwrap().into(), Value::from("databases")),
325			(syn::idiom("test.something[1].tags[1]").unwrap().into(), Value::from("operations")),
326		];
327		assert_eq!(res, val.walk(&idi));
328	}
329
330	#[test]
331	fn walk_set_field_all() {
332		let idi: Idiom = syn::idiom("test.*").unwrap().into();
333		let val = parse_val!("{ test: { ' hello ', } }");
334		let res: Vec<(Idiom, Value)> =
335			vec![(syn::idiom("test[0]").unwrap().into(), Value::from(" hello "))];
336		assert_eq!(res, val.walk(&idi));
337	}
338
339	#[test]
340	fn walk_array_field_embedded_index_all() {
341		let idi: Idiom = syn::idiom("test.something[*].tags[*]").unwrap().into();
342		let val = parse_val!(
343			"{ test: { something: [{ age: 34, tags: ['code', 'databases'] }, { age: 36, tags: ['design', 'operations'] }] } }"
344		);
345		let res: Vec<(Idiom, Value)> = vec![
346			(syn::idiom("test.something[0].tags[0]").unwrap().into(), Value::from("code")),
347			(syn::idiom("test.something[0].tags[1]").unwrap().into(), Value::from("databases")),
348			(syn::idiom("test.something[1].tags[0]").unwrap().into(), Value::from("design")),
349			(syn::idiom("test.something[1].tags[1]").unwrap().into(), Value::from("operations")),
350		];
351		assert_eq!(res, val.walk(&idi));
352	}
353
354	/// The borrowing walk is the owning one with the clone left out, so it must
355	/// visit exactly the values `walk` returns, in the same order. A caller
356	/// measuring through it is measuring what a caller reading `walk` sees.
357	#[test]
358	fn walk_ref_visits_what_walk_returns() {
359		let val = parse_val!(
360			"{ test: { other: null, something: [{ age: 34, tags: ['code', 'databases'] }, \
361			 { age: 36, tags: [] }] }, tags: ['a', 'b', 'c'], empty: [], set: { 'x', 'y', }, n: 1 }"
362		);
363		for path in [
364			"test",
365			"test.something",
366			"test.missing",
367			"test.*",
368			"test.something[*].age",
369			"test.something[0].tags[1]",
370			"test.something[*].tags[*]",
371			"tags",
372			"tags[*]",
373			"tags[1]",
374			"tags[5]",
375			"empty",
376			"empty[*]",
377			"empty[0]",
378			"set",
379			"set[*]",
380			"set[0]",
381			"n",
382			"n.missing",
383			"missing.deep[*]",
384		] {
385			let idi: Idiom = syn::idiom(path).unwrap().into();
386			let mut visited = Vec::new();
387			val.walk_ref(&idi, &mut |v| visited.push(v.clone()));
388			let walked: Vec<Value> = val.walk(&idi).into_iter().map(|(_, v)| v).collect();
389			assert_eq!(visited, walked, "the two walks must agree on `{path}`");
390		}
391	}
392}