Skip to main content

surrealdb_expr/val/value/
pick.rs

1use crate::expr::part::{Next, Part};
2use crate::val::Value;
3
4impl Value {
5	/// Synchronous method for getting a field from a `Value`
6	pub fn pick(&self, path: &[Part]) -> Self {
7		match path.first() {
8			// Get the current value at path
9			Some(p) => match self {
10				// Current value at path is an object
11				Value::Object(v) => match p {
12					Part::Field(f) => match v.get(f as &str) {
13						Some(v) => v.pick(path.next()),
14						None => Value::None,
15					},
16					Part::All => v.values().map(|v| v.pick(path.next())).collect::<Vec<_>>().into(),
17					x => {
18						if let Some(idx) = x.as_old_index() {
19							match v.get(&idx.to_string()) {
20								Some(v) => v.pick(path.next()),
21								None => Value::None,
22							}
23						} else {
24							Value::None
25						}
26					}
27				},
28				// Current value at path is an array
29				Value::Array(v) => match p {
30					Part::All => v.iter().map(|v| v.pick(path.next())).collect::<Vec<_>>().into(),
31					Part::First => match v.first() {
32						Some(v) => v.pick(path.next()),
33						None => Value::None,
34					},
35					Part::Last => match v.last() {
36						Some(v) => v.pick(path.next()),
37						None => Value::None,
38					},
39					x => {
40						if let Some(idx) = x.as_old_index() {
41							match v.get(idx) {
42								Some(v) => v.pick(path.next()),
43								None => Value::None,
44							}
45						} else {
46							v.iter().map(|v| v.pick(path)).collect::<Vec<_>>().into()
47						}
48					}
49				},
50				// Current value at path is a set
51				Value::Set(v) => match p {
52					Part::All => Value::Set(v.iter().map(|v| v.pick(path.next())).collect()),
53					Part::First => match v.first() {
54						Some(v) => v.pick(path.next()),
55						None => Value::None,
56					},
57					Part::Last => match v.last() {
58						Some(v) => v.pick(path.next()),
59						None => Value::None,
60					},
61					x => {
62						if let Some(idx) = x.as_old_index() {
63							match v.nth(idx) {
64								Some(v) => v.pick(path.next()),
65								None => Value::None,
66							}
67						} else {
68							Value::Set(v.iter().map(|v| v.pick(path)).collect())
69						}
70					}
71				},
72				// Ignore everything else
73				_ => Value::None,
74			},
75			// No more parts so get the value
76			None => self.clone(),
77		}
78	}
79}
80
81#[cfg(test)]
82mod tests {
83	use surrealdb_strand::Strand;
84
85	use super::*;
86	use crate::expr::idiom::Idiom;
87	use crate::sql::idiom::Idiom as SqlIdiom;
88	use crate::syn;
89	use crate::val::{RecordId, RecordIdKey};
90
91	macro_rules! parse_val {
92		($input:expr) => {
93			crate::val::convert_public_value_to_internal(syn::value($input).unwrap())
94		};
95	}
96
97	#[test]
98	fn pick_none() {
99		let idi: Idiom = SqlIdiom::default().into();
100		let val = parse_val!("{ test: { other: null, something: 123 } }");
101		let res = val.pick(&idi);
102		assert_eq!(res, val);
103	}
104
105	#[test]
106	fn pick_basic() {
107		let idi: Idiom = syn::idiom("test.something").unwrap().into();
108		let val = parse_val!("{ test: { other: null, something: 123 } }");
109		let res = val.pick(&idi);
110		assert_eq!(res, Value::from(123));
111	}
112
113	#[test]
114	fn pick_thing() {
115		let idi: Idiom = syn::idiom("test.other").unwrap().into();
116		let val = parse_val!("{ test: { other: test:tobie, something: 123 } }");
117		let res = val.pick(&idi);
118		assert_eq!(
119			res,
120			Value::from(RecordId {
121				table: "test".into(),
122				key: RecordIdKey::String(Strand::new_static("tobie"))
123			})
124		);
125	}
126
127	#[test]
128	fn pick_array() {
129		let idi: Idiom = syn::idiom("test.something[1]").unwrap().into();
130		let val = parse_val!("{ test: { something: [123, 456, 789] } }");
131		let res = val.pick(&idi);
132		assert_eq!(res, Value::from(456));
133	}
134
135	#[test]
136	fn pick_array_thing() {
137		let idi: Idiom = syn::idiom("test.something[1]").unwrap().into();
138		let val = parse_val!("{ test: { something: [test:tobie, test:jaime] } }");
139		let res = val.pick(&idi);
140		assert_eq!(
141			res,
142			Value::from(RecordId {
143				table: "test".into(),
144				key: RecordIdKey::String(Strand::new_static("jaime"))
145			})
146		);
147	}
148
149	#[test]
150	fn pick_array_field() {
151		let idi: Idiom = syn::idiom("test.something[1].age").unwrap().into();
152		let val = parse_val!("{ test: { something: [{ age: 34 }, { age: 36 }] } }");
153		let res = val.pick(&idi);
154		assert_eq!(res, Value::from(36));
155	}
156
157	#[test]
158	fn pick_array_fields() {
159		let idi: Idiom = syn::idiom("test.something[*].age").unwrap().into();
160		let val = parse_val!("{ test: { something: [{ age: 34 }, { age: 36 }] } }");
161		let res = val.pick(&idi);
162		assert_eq!(res, [Value::from(34i64), Value::from(36i64)].into_iter().collect::<Value>());
163	}
164
165	#[test]
166	fn pick_array_fields_flat() {
167		let idi: Idiom = syn::idiom("test.something.age").unwrap().into();
168		let val = parse_val!("{ test: { something: [{ age: 34 }, { age: 36 }] } }");
169		let res = val.pick(&idi);
170		assert_eq!(res, [Value::from(34i64), Value::from(36i64)].into_iter().collect::<Value>());
171	}
172}