Skip to main content

surrealdb_expr/val/value/
merge.rs

1use anyhow::{Result, ensure};
2
3use crate::expr::Error;
4use crate::val::Value;
5use crate::val::value::every::ArrayBehaviour;
6
7impl Value {
8	pub fn merge(&mut self, val: Value) -> Result<()> {
9		// If this value is not an object, then error
10		ensure!(
11			val.is_object(),
12			Error::InvalidMerge {
13				value: val,
14			}
15		);
16		// Otherwise loop through every object field
17		for k in val.every(None, true, ArrayBehaviour::Ignore).iter() {
18			// Because we iterate every step, we need to this check
19			// If old & new are both objects, we do not want to completely
20			// replace the old object with the new object as that will drop
21			// all the fields in the old object that are not in the new object
22			let old = self.pick(k);
23			let new = val.pick(k);
24			if old.is_object() && new.is_object() {
25				continue;
26			}
27
28			match new {
29				Value::None => self.cut(k),
30				v => self.put(k, v),
31			}
32		}
33		Ok(())
34	}
35}
36
37#[cfg(test)]
38mod tests {
39
40	use super::*;
41	use crate::syn;
42
43	macro_rules! parse_val {
44		($input:expr) => {
45			crate::val::convert_public_value_to_internal(syn::value($input).unwrap())
46		};
47	}
48
49	#[tokio::test]
50	async fn merge_none() {
51		let mut res = parse_val!(
52			"{
53				test: true,
54				name: {
55					first: 'Tobie',
56					last: 'Morgan Hitchcock',
57					initials: 'TMH',
58				},
59			}"
60		);
61		match res.merge(Value::NONE.clone()).unwrap_err().downcast() {
62			Ok(Error::InvalidMerge {
63				value,
64			}) => assert_eq!(value, Value::NONE),
65			Ok(error) => panic!("unexpected error: {error:?}"),
66			Err(error) => panic!("unexpected error: {error:?}"),
67		}
68	}
69
70	#[tokio::test]
71	async fn merge_empty() {
72		let mut res = parse_val!(
73			"{
74				test: true,
75				name: {
76					first: 'Tobie',
77					last: 'Morgan Hitchcock',
78					initials: 'TMH',
79				},
80			}"
81		);
82		let val = parse_val!(
83			"{
84				test: true,
85				name: {
86					first: 'Tobie',
87					last: 'Morgan Hitchcock',
88					initials: 'TMH',
89				},
90			}"
91		);
92		let mrg = Value::Object(Default::default());
93		res.merge(mrg).unwrap();
94		assert_eq!(res, val);
95	}
96
97	#[tokio::test]
98	async fn merge_basic() {
99		let mut res = parse_val!(
100			"{
101				test: true,
102				name: {
103					first: 'Tobie',
104					last: 'Morgan Hitchcock',
105					initials: 'TMH',
106				},
107			}"
108		);
109		let mrg = parse_val!(
110			"{
111				name: {
112					title: 'Mr',
113					initials: NONE,
114				},
115				tags: ['Rust', 'Golang', 'JavaScript'],
116			}"
117		);
118		let val = parse_val!(
119			"{
120				test: true,
121				name: {
122					title: 'Mr',
123					first: 'Tobie',
124					last: 'Morgan Hitchcock',
125				},
126				tags: ['Rust', 'Golang', 'JavaScript'],
127			}"
128		);
129		res.merge(mrg).unwrap();
130		assert_eq!(res, val);
131	}
132
133	#[tokio::test]
134	async fn merge_new_object() {
135		let mut res = parse_val!(
136			"{
137				test: true,
138				name: 'Tobie',
139				obj: {
140					a: 1,
141					b: 2,
142				}
143			}"
144		);
145		let mrg = parse_val!(
146			"{
147				name: {
148					title: 'Mr',
149					initials: NONE,
150				},
151				obj: {
152					a: 2,
153					b: NONE,
154				}
155			}"
156		);
157		let val = parse_val!(
158			"{
159				test: true,
160				name: {
161					title: 'Mr',
162				},
163				obj: {
164					a: 2,
165				},
166			}"
167		);
168		res.merge(mrg).unwrap();
169		assert_eq!(res, val);
170	}
171}