Skip to main content

surrealdb_expr/val/
mod.rs

1#![allow(clippy::derive_ord_xor_partial_ord)]
2
3use std::cmp::Ordering;
4use std::collections::{BTreeMap, HashMap};
5use std::ops::Bound;
6use std::time::Duration as StdDuration;
7
8use anyhow::{Result, bail};
9use chrono::{DateTime, Utc};
10use common::fmt::{EscapeIdent, QuoteStr};
11use geo::Point;
12use revision::revisioned;
13use rust_decimal::prelude::*;
14use storekey::{BorrowDecode, Encode};
15use surrealdb_collections::VecMap;
16pub use surrealdb_strand::Strand;
17use surrealdb_types::{SqlFormat, ToSql, write_sql};
18
19use crate::expr;
20use crate::expr::Error;
21use crate::expr::kind::GeometryKind;
22use crate::expr::statements::info::InfoStructure;
23use crate::val::table_name_public::IntoPublicTable;
24
25pub mod array;
26pub mod bytes;
27pub mod closure;
28pub mod convert_public;
29pub mod datetime;
30pub mod duration;
31pub mod evaluator;
32pub mod field_path;
33pub mod file;
34pub mod geometry;
35pub mod number;
36pub mod object;
37pub mod range;
38pub mod record_id;
39pub mod regex;
40pub mod rnd;
41pub mod set;
42pub mod table_name_public;
43pub mod uuid;
44pub mod value;
45
46pub use surrealdb_strand::TableName;
47
48pub use self::array::Array;
49pub use self::bytes::Bytes;
50pub use self::closure::Closure;
51pub use self::convert_public::convert_public_value_to_internal;
52pub use self::datetime::Datetime;
53pub use self::duration::Duration;
54pub use self::evaluator::{BoxInvokeFut, ClosureEvaluator};
55pub use self::file::File;
56pub use self::geometry::Geometry;
57pub use self::number::Number;
58pub use self::object::Object;
59pub use self::range::Range;
60pub use self::record_id::{RecordId, RecordIdKey, RecordIdKeyRange, RecordIdentity};
61pub use self::regex::Regex;
62pub use self::set::Set;
63pub use self::uuid::Uuid;
64pub use self::value::{CastError, CoerceError};
65
66/// Marker type for a different serialization format for value which does not encode type
67/// information which is not required for indexing.
68pub enum IndexFormat {}
69
70/// Marker type for value conversions from Value::None
71#[derive(Clone, Copy, Eq, PartialEq, PartialOrd)]
72pub struct SqlNone;
73
74/// Marker type for value conversions from Value::Null
75#[derive(Clone, Copy, Eq, PartialEq, PartialOrd)]
76pub struct Null;
77
78// `revision(1)` is the legacy on-disk format (varint discriminant +
79// payload). `revision(2, optimised)` wraps each value in a
80// 1-byte tag (variant id + size class) so walkers can skip any value in
81// O(1) via the `u32_le` length prefix on varlen variants.
82//
83// Optimised enum walkers are "materialised" — they don't expose
84// `into_<variant>()` for streaming descent. Instead the macro emits
85// `<variant>_view()` accessors that return `OwnedVariantView<T>` carrying
86// a `Cow<'r, [u8]>` over the variant body. With the `WalkRevisioned:
87// BorrowedReader` bound the source is slice-backed, so the Cow is
88// borrowed — no `Vec<u8>` allocation per descent. The caller then
89// constructs a fresh walker on the borrowed bytes:
90//
91//     let view = walker.object_view()?;          // zero-alloc borrow
92//     let mut r: &[u8] = view.as_bytes();
93//     let inner = Object::walk_revisioned(&mut r)?; // streaming Object walker
94//
95// This recovers fully streaming descent at the Value level while picking
96// up the rev-2 wire compactness. Combined with rev-2 `Object`/`Array`/
97// `Set` indexed prologues, the planner hot path gets O(log n) Map
98// binary search, O(1) Array random access, and O(1) skip past any Value
99// — all zero-alloc through the descent chain.
100#[revisioned(revision(1), revision(2, optimised))]
101#[derive(Clone, Debug, Default, PartialEq, PartialOrd, Hash, Encode, BorrowDecode)]
102#[storekey(format = "()")]
103#[storekey(format = "IndexFormat")]
104pub enum Value {
105	#[default]
106	#[revision(size = "inline")]
107	None,
108	#[revision(size = "inline")]
109	Null,
110	#[revision(size = "fixed(1)")]
111	Bool(bool),
112	#[revision(size = "varlen")]
113	Number(Number),
114	#[revision(size = "varlen")]
115	String(Strand),
116	#[revision(size = "varlen")]
117	Duration(Duration),
118	#[revision(size = "varlen")]
119	Datetime(Datetime),
120	#[revision(size = "varlen")]
121	Uuid(Uuid),
122	#[revision(size = "varlen")]
123	Array(Array),
124	#[revision(size = "varlen")]
125	Set(Set),
126	#[revision(size = "varlen")]
127	Object(Object),
128	#[revision(size = "varlen")]
129	Geometry(Geometry),
130	#[revision(size = "varlen")]
131	Bytes(Bytes),
132	#[revision(size = "varlen")]
133	Table(TableName),
134	#[revision(size = "varlen")]
135	RecordId(RecordId),
136	#[revision(size = "varlen")]
137	File(File),
138	#[revision(size = "varlen")]
139	Regex(Regex),
140	#[revision(size = "varlen")]
141	Range(Box<Range>),
142	// Closure's manual `SerializeRevisioned` impl errors regardless of the
143	// envelope. Mark `varlen` for completeness; the macro requires every
144	// variant to declare a size class under `optimised`.
145	#[revision(size = "varlen")]
146	Closure(Box<Closure>),
147	// Add new variants here
148}
149
150impl Eq for Value {}
151
152impl Ord for Value {
153	fn cmp(&self, other: &Self) -> Ordering {
154		self.partial_cmp(other).unwrap_or(Ordering::Equal)
155	}
156}
157
158impl Value {
159	/// Whether a `HashMap`/`HashSet` **miss** on this value proves the collection
160	/// does not hold it — i.e. whether this value's [`Hash`](std::hash::Hash) is
161	/// consistent with its [`PartialEq`].
162	///
163	/// [`Number`] is the one leaf where the two disagree, and it cannot be
164	/// repaired: see [`Number`]'s `Hash` impl for why. A value that can reach a
165	/// [`Number`] may hash into a different bucket than an equal value, so
166	/// `contains` answers `false` for something the collection holds.
167	///
168	/// For `==` a probe is decisive as soon as *either* side is number-free,
169	/// because `==` requires matching variants at every level: two values can
170	/// only hash apart while comparing equal if both carry a [`Number`] in the
171	/// same position. So a caller judging `==` may trust a miss when this returns
172	/// `true` for the probed value, *or* when it returns `true` for every element
173	/// of the collection.
174	///
175	/// [`Value::equal`] — what `=`, `IN` and the `CONTAINS` family compare by —
176	/// is wider than `==` in one place: it matches a top-level [`Value::String`]
177	/// against a top-level [`Value::Regex`] by running the regex, and the two
178	/// hash apart. [`Value::Regex`] reports `false` for that reason, although its
179	/// own hash and `==` agree. A caller judging `equal` may trust a miss when
180	/// this returns `true` on *both* sides. The either-side rule does not carry
181	/// over, since a string reports `true`: it would trust a regex's miss against
182	/// a set of the strings it matches, unless the caller rules that pairing out
183	/// itself.
184	///
185	/// Variants whose hash/equality pairing is not established — [`Value::Geometry`]
186	/// (hashes `f64` coordinates), [`Value::Range`] and [`Value::Closure`] —
187	/// report `false` so they take the same conservative path.
188	pub fn hash_agrees_with_eq(&self) -> bool {
189		match self {
190			Value::None
191			| Value::Null
192			| Value::Bool(_)
193			| Value::String(_)
194			| Value::Duration(_)
195			| Value::Datetime(_)
196			| Value::Uuid(_)
197			| Value::Bytes(_)
198			| Value::Table(_)
199			| Value::File(_) => true,
200			Value::Number(_)
201			| Value::Geometry(_)
202			| Value::Regex(_)
203			| Value::Range(_)
204			| Value::Closure(_) => false,
205			Value::Array(a) => a.iter().all(Value::hash_agrees_with_eq),
206			Value::Set(s) => s.iter().all(Value::hash_agrees_with_eq),
207			// An `Object`'s equality covers its keys too, but those are `Strand`
208			// (hash is `as_str().hash`), so only the values can carry a `Number`.
209			Value::Object(o) => o.values().all(Value::hash_agrees_with_eq),
210			Value::RecordId(r) => r.key.hash_agrees_with_eq(),
211		}
212	}
213
214	pub const NONE: Self = Self::None;
215
216	// -----------------------------------
217	// Initial record value
218	// -----------------------------------
219
220	/// Create an empty Object Value
221	pub fn empty_object() -> Self {
222		Value::Object(Object::default())
223	}
224
225	// -----------------------------------
226	// Simple value detection
227	// -----------------------------------
228
229	/// Check if this Value is not NONE
230	pub fn is_some(&self) -> bool {
231		!matches!(self, Value::None)
232	}
233
234	/// Check if this Value is NONE or NULL
235	pub fn is_nullish(&self) -> bool {
236		matches!(self, Value::None | Value::Null)
237	}
238
239	/// Check if this Value is NONE
240	pub fn is_empty_array(&self) -> bool {
241		if let Value::Array(v) = self {
242			v.is_empty()
243		} else {
244			false
245		}
246	}
247
248	/// Check if this Value is truthy
249	pub fn is_truthy(&self) -> bool {
250		match self {
251			Value::Bool(v) => *v,
252			Value::Uuid(_) => true,
253			Value::RecordId(_) => true,
254			Value::Geometry(_) => true,
255			Value::Datetime(_) => true,
256			Value::Bytes(v) => !v.is_empty(),
257			Value::Array(v) => !v.is_empty(),
258			Value::Set(v) => !v.is_empty(),
259			Value::Object(v) => !v.is_empty(),
260			Value::String(v) => !v.is_empty(),
261			Value::Number(v) => v.is_truthy(),
262			Value::Duration(v) => v.as_nanos() > 0,
263			_ => false,
264		}
265	}
266
267	/// Check if this Value is an int Number
268	pub fn is_int(&self) -> bool {
269		matches!(self, Value::Number(Number::Int(_)))
270	}
271
272	/// Check if this Value is a float Number
273	pub fn is_float(&self) -> bool {
274		matches!(self, Value::Number(Number::Float(_)))
275	}
276
277	/// Check if this Value is a decimal Number
278	pub fn is_decimal(&self) -> bool {
279		matches!(self, Value::Number(Number::Decimal(_)))
280	}
281
282	/// Check if this Value is a RecordId of a specific type
283	pub fn is_record_type(&self, types: &[TableName]) -> bool {
284		match self {
285			Value::RecordId(v) => v.is_table_type(types),
286			_ => false,
287		}
288	}
289
290	/// Check if this Value is a Geometry of a specific type
291	pub fn is_geometry_type(&self, types: &[GeometryKind]) -> bool {
292		match self {
293			Value::Geometry(Geometry::Point(_)) => {
294				types.iter().any(|t| matches!(t, GeometryKind::Point))
295			}
296			Value::Geometry(Geometry::Line(_)) => {
297				types.iter().any(|t| matches!(t, GeometryKind::Line))
298			}
299			Value::Geometry(Geometry::Polygon(_)) => {
300				types.iter().any(|t| matches!(t, GeometryKind::Polygon))
301			}
302			Value::Geometry(Geometry::MultiPoint(_)) => {
303				types.iter().any(|t| matches!(t, GeometryKind::MultiPoint))
304			}
305			Value::Geometry(Geometry::MultiLine(_)) => {
306				types.iter().any(|t| matches!(t, GeometryKind::MultiLine))
307			}
308			Value::Geometry(Geometry::MultiPolygon(_)) => {
309				types.iter().any(|t| matches!(t, GeometryKind::MultiPolygon))
310			}
311			Value::Geometry(Geometry::Collection(_)) => {
312				types.iter().any(|t| matches!(t, GeometryKind::Collection))
313			}
314			_ => false,
315		}
316	}
317
318	// -----------------------------------
319	// Simple conversion of value
320	// -----------------------------------
321
322	/// Converts this Value into an unquoted String
323	pub fn into_raw_string(self) -> String {
324		match self {
325			Value::String(v) => v.into_string(),
326			Value::Uuid(v) => v.to_string(),
327			Value::Datetime(v) => v.to_string(),
328			_ => self.to_sql(),
329		}
330	}
331
332	/// Converts this Value into an unquoted String
333	pub fn to_raw_string(&self) -> String {
334		match self {
335			Value::String(v) => v.as_str().to_owned(),
336			Value::Uuid(v) => v.to_string(),
337			Value::Datetime(v) => v.to_string(),
338			_ => self.to_sql(),
339		}
340	}
341
342	/// Returns the surql representation of the kind of the value as a string.
343	///
344	/// # Warning
345	/// This function is not fully implement for all variants, make sure you
346	/// don't accidentally use it where it can return an invalid value.
347	pub fn kind_of(&self) -> &'static str {
348		match self {
349			Self::None => "none",
350			Self::Null => "null",
351			Self::Bool(_) => "bool",
352			Self::Uuid(_) => "uuid",
353			Self::Array(_) => "array",
354			Self::Set(_) => "set",
355			Self::Object(_) => "object",
356			Self::String(_) => "string",
357			Self::Duration(_) => "duration",
358			Self::Datetime(_) => "datetime",
359			Self::Closure(_) => "function",
360			Self::Number(Number::Int(_)) => "int",
361			Self::Number(Number::Float(_)) => "float",
362			Self::Number(Number::Decimal(_)) => "decimal",
363			Self::Geometry(Geometry::Point(_)) => "geometry<point>",
364			Self::Geometry(Geometry::Line(_)) => "geometry<line>",
365			Self::Geometry(Geometry::Polygon(_)) => "geometry<polygon>",
366			Self::Geometry(Geometry::MultiPoint(_)) => "geometry<multipoint>",
367			Self::Geometry(Geometry::MultiLine(_)) => "geometry<multiline>",
368			Self::Geometry(Geometry::MultiPolygon(_)) => "geometry<multipolygon>",
369			Self::Geometry(Geometry::Collection(_)) => "geometry<collection>",
370			Self::Regex(_) => "regex",
371			Self::File(_) => "file",
372			Self::Bytes(_) => "bytes",
373			Self::Range(_) => "range",
374			Self::RecordId(_) => "record",
375			Self::Table(_) => "table",
376		}
377	}
378
379	// -----------------------------------
380	// Value operations
381	// -----------------------------------
382
383	/// Check if this Value is equal to another Value
384	pub fn equal(&self, other: &Value) -> bool {
385		match self {
386			Value::None => other.is_none(),
387			Value::Null => other.is_null(),
388			Value::Bool(v) => match other {
389				Value::Bool(w) => v == w,
390				_ => false,
391			},
392			Value::Uuid(v) => match other {
393				Value::Uuid(w) => v == w,
394				_ => false,
395			},
396			Value::RecordId(v) => match other {
397				Value::RecordId(w) => v == w,
398				_ => false,
399			},
400			Value::String(v) => match other {
401				Value::String(w) => v == w,
402				Value::Regex(w) => w.inner().is_match(v.as_str()),
403				_ => false,
404			},
405			Value::Regex(v) => match other {
406				Value::Regex(w) => v == w,
407				Value::String(w) => v.inner().is_match(w.as_str()),
408				_ => false,
409			},
410			Value::Array(v) => match other {
411				Value::Array(w) => v == w,
412				_ => false,
413			},
414			Value::Set(v) => match other {
415				Value::Set(w) => v == w,
416				_ => false,
417			},
418			Value::Object(v) => match other {
419				Value::Object(w) => v == w,
420				_ => false,
421			},
422			Value::Number(v) => match other {
423				Value::Number(w) => v == w,
424				_ => false,
425			},
426			Value::Geometry(v) => match other {
427				Value::Geometry(w) => v == w,
428				_ => false,
429			},
430			Value::Duration(v) => match other {
431				Value::Duration(w) => v == w,
432				_ => false,
433			},
434			Value::Datetime(v) => match other {
435				Value::Datetime(w) => v == w,
436				_ => false,
437			},
438			_ => self == other,
439		}
440	}
441
442	/// Check if all Values in an Array are equal to another Value
443	pub fn all_equal(&self, other: &Value) -> bool {
444		match self {
445			Value::Array(v) => v.iter().all(|v| v.equal(other)),
446			Value::Set(v) => v.iter().all(|v| v.equal(other)),
447			_ => self.equal(other),
448		}
449	}
450
451	/// Check if any Values in an Array are equal to another Value
452	pub fn any_equal(&self, other: &Value) -> bool {
453		match self {
454			Value::Array(v) => v.iter().any(|v| v.equal(other)),
455			Value::Set(v) => v.iter().any(|v| v.equal(other)),
456			_ => self.equal(other),
457		}
458	}
459
460	/// Check if this Value contains another Value
461	pub fn contains(&self, other: &Value) -> bool {
462		match self {
463			Value::Array(v) => v.iter().any(|v| v.equal(other)),
464			Value::Set(v) => v.contains(other),
465			Value::Uuid(v) => match other {
466				Value::String(w) => v.to_string().contains(w.as_str()),
467				_ => false,
468			},
469			Value::String(v) => match other {
470				Value::String(w) => v.contains(w.as_str()),
471				_ => false,
472			},
473			Value::Geometry(v) => match other {
474				Value::Geometry(w) => v.contains(w),
475				_ => false,
476			},
477			Value::Object(v) => match other {
478				Value::String(w) => v.contains_key(w.as_str()),
479				_ => false,
480			},
481			Value::Range(r) => {
482				let beg = match &r.start {
483					Bound::Unbounded => true,
484					Bound::Included(beg) => beg.le(other),
485					Bound::Excluded(beg) => beg.lt(other),
486				};
487
488				beg && match &r.end {
489					Bound::Unbounded => true,
490					Bound::Included(end) => end.ge(other),
491					Bound::Excluded(end) => end.gt(other),
492				}
493			}
494			_ => false,
495		}
496	}
497
498	/// Check if all Values in an Array, Set or String contain another Value
499	pub fn contains_all(&self, other: &Value) -> bool {
500		match other {
501			Value::Array(v) if v.iter().all(|v| v.is_strand()) && self.is_strand() => {
502				// confirmed as strand so all return false is unreachable
503				let Value::String(this) = self else {
504					return false;
505				};
506				v.iter().all(|s| {
507					let Value::String(other_string) = s else {
508						return false;
509					};
510					this.contains(&**other_string)
511				})
512			}
513			Value::Set(v) if v.iter().all(|v| v.is_strand()) && self.is_strand() => {
514				// confirmed as strand so all return false is unreachable
515				let Value::String(this) = self else {
516					return false;
517				};
518				v.iter().all(|s| {
519					let Value::String(other_string) = s else {
520						return false;
521					};
522					this.contains(&**other_string)
523				})
524			}
525			Value::Array(v) => v.iter().all(|v| match self {
526				Value::Array(w) => w.iter().any(|w| v.equal(w)),
527				Value::Set(w) => w.iter().any(|w| v.equal(w)),
528				Value::Geometry(_) => self.contains(v),
529				_ => false,
530			}),
531			Value::Set(v) => v.iter().all(|v| match self {
532				Value::Array(w) => w.iter().any(|w| v.equal(w)),
533				Value::Set(w) => w.iter().any(|w| v.equal(w)),
534				Value::Geometry(_) => self.contains(v),
535				_ => false,
536			}),
537			Value::String(other_strand) => match self {
538				Value::String(s) => s.contains(&**other_strand),
539				_ => false,
540			},
541			_ => false,
542		}
543	}
544
545	/// Check if any Values in an Array, Set or String contain another Value
546	pub fn contains_any(&self, other: &Value) -> bool {
547		match other {
548			Value::Array(v) if v.iter().all(|v| v.is_strand()) && self.is_strand() => {
549				// confirmed as strand so all return false is unreachable
550				let Value::String(this) = self else {
551					return false;
552				};
553				v.iter().any(|s| {
554					let Value::String(other_string) = s else {
555						return false;
556					};
557					this.contains(&**other_string)
558				})
559			}
560			Value::Set(v) if v.iter().all(|v| v.is_strand()) && self.is_strand() => {
561				// confirmed as strand so all return false is unreachable
562				let Value::String(this) = self else {
563					return false;
564				};
565				v.iter().any(|s| {
566					let Value::String(other_string) = s else {
567						return false;
568					};
569					this.contains(&**other_string)
570				})
571			}
572			Value::Array(v) => v.iter().any(|v| match self {
573				Value::Array(w) => w.iter().any(|w| v.equal(w)),
574				Value::Set(w) => w.iter().any(|w| v.equal(w)),
575				Value::Geometry(_) => self.contains(v),
576				_ => false,
577			}),
578			Value::Set(v) => v.iter().any(|v| match self {
579				Value::Array(w) => w.iter().any(|w| v.equal(w)),
580				Value::Set(w) => w.iter().any(|w| v.equal(w)),
581				Value::Geometry(_) => self.contains(v),
582				_ => false,
583			}),
584			Value::String(other_strand) => match self {
585				Value::String(s) => s.contains(&**other_strand),
586				_ => false,
587			},
588			_ => false,
589		}
590	}
591
592	/// Check if this Geometry intersects another Value
593	pub fn intersects(&self, other: &Value) -> bool {
594		match self {
595			Value::Geometry(v) => match other {
596				Value::Geometry(w) => v.intersects(w),
597				_ => false,
598			},
599			_ => false,
600		}
601	}
602
603	// -----------------------------------
604	// Sorting operations
605	// -----------------------------------
606
607	/// Compare this Value to another Value lexicographically
608	pub fn lexical_cmp(&self, other: &Value) -> Option<Ordering> {
609		match (self, other) {
610			(Value::String(a), Value::String(b)) => Some(lexicmp::lexical_cmp(a, b)),
611			_ => self.partial_cmp(other),
612		}
613	}
614
615	/// Compare this Value to another Value using natural numerical comparison
616	pub fn natural_cmp(&self, other: &Value) -> Option<Ordering> {
617		match (self, other) {
618			(Value::String(a), Value::String(b)) => Some(lexicmp::natural_cmp(a, b)),
619			_ => self.partial_cmp(other),
620		}
621	}
622
623	/// Compare this Value to another Value lexicographically and using natural
624	/// numerical comparison
625	pub fn natural_lexical_cmp(&self, other: &Value) -> Option<Ordering> {
626		match (self, other) {
627			(Value::String(a), Value::String(b)) => Some(lexicmp::natural_lexical_cmp(a, b)),
628			_ => self.partial_cmp(other),
629		}
630	}
631
632	/// Turns this value into a literal evaluating to the same value.
633	pub fn into_literal(self) -> expr::Expr {
634		match self {
635			Value::None => expr::Expr::Literal(expr::Literal::None),
636			Value::Null => expr::Expr::Literal(expr::Literal::Null),
637			Value::Bool(x) => expr::Expr::Literal(expr::Literal::Bool(x)),
638			Value::Number(Number::Int(i)) => expr::Expr::Literal(expr::Literal::Integer(i)),
639			Value::Number(Number::Float(f)) => expr::Expr::Literal(expr::Literal::Float(f)),
640			Value::Number(Number::Decimal(d)) => expr::Expr::Literal(expr::Literal::Decimal(d)),
641			Value::String(strand) => expr::Expr::Literal(expr::Literal::String(strand)),
642			Value::Duration(duration) => expr::Expr::Literal(expr::Literal::Duration(duration)),
643			Value::Datetime(datetime) => expr::Expr::Literal(expr::Literal::Datetime(datetime)),
644			Value::Uuid(uuid) => expr::Expr::Literal(expr::Literal::Uuid(uuid)),
645			Value::Array(array) => expr::Expr::Literal(expr::Literal::Array(array.into_literal())),
646			Value::Set(set) => {
647				// Convert set to array for literal representation since there's no set literal
648				// syntax
649				expr::Expr::Literal(expr::Literal::Array(set.into_literal()))
650			}
651			Value::Object(object) => {
652				expr::Expr::Literal(expr::Literal::Object(object.into_literal()))
653			}
654			Value::Geometry(geometry) => expr::Expr::Literal(expr::Literal::Geometry(geometry)),
655			Value::Bytes(bytes) => expr::Expr::Literal(expr::Literal::Bytes(bytes)),
656			Value::RecordId(record_id) => {
657				expr::Expr::Literal(expr::Literal::RecordId(record_id.into_literal()))
658			}
659			Value::Regex(regex) => expr::Expr::Literal(expr::Literal::Regex(regex)),
660			Value::File(file) => expr::Expr::Literal(expr::Literal::File(file)),
661			Value::Closure(closure) => closure.into_expr(),
662			Value::Range(range) => range.into_literal(),
663			Value::Table(t) => expr::Expr::Table(t),
664		}
665	}
666}
667
668impl ToSql for Value {
669	fn fmt_sql(&self, f: &mut String, sql_fmt: SqlFormat) {
670		match self {
671			Value::None => f.push_str("NONE"),
672			Value::Null => f.push_str("NULL"),
673			Value::Bool(v) => v.fmt_sql(f, sql_fmt),
674			Value::Number(v) => v.fmt_sql(f, sql_fmt),
675			Value::String(v) => write_sql!(f, sql_fmt, "{}", QuoteStr(v.as_str())),
676			Value::Duration(v) => v.fmt_sql(f, sql_fmt),
677			Value::Datetime(v) => v.fmt_sql(f, sql_fmt),
678			Value::Uuid(v) => v.fmt_sql(f, sql_fmt),
679			Value::Array(v) => v.fmt_sql(f, sql_fmt),
680			Value::Set(v) => v.fmt_sql(f, sql_fmt),
681			Value::Object(v) => v.fmt_sql(f, sql_fmt),
682			Value::Geometry(v) => v.fmt_sql(f, sql_fmt),
683			Value::Bytes(v) => v.fmt_sql(f, sql_fmt),
684			// Escaped explicitly rather than through the name's own `ToSql`: the
685			// engine's `TableName` deliberately has none, and it derefs to `str`,
686			// so a bare `fmt_sql` here would silently resolve to the unescaped
687			// string impl and emit a name that cannot be read back.
688			Value::Table(v) => EscapeIdent(v.as_str()).fmt_sql(f, sql_fmt),
689			Value::RecordId(v) => v.fmt_sql(f, sql_fmt),
690			Value::File(v) => v.fmt_sql(f, sql_fmt),
691			Value::Regex(v) => v.fmt_sql(f, sql_fmt),
692			Value::Range(v) => v.fmt_sql(f, sql_fmt),
693			Value::Closure(v) => v.fmt_sql(f, sql_fmt),
694		}
695	}
696}
697
698impl InfoStructure for Value {
699	fn structure(self) -> Value {
700		self.to_sql().into()
701	}
702}
703
704// ------------------------------
705
706pub trait TryAdd<Rhs = Self> {
707	type Output;
708	fn try_add(self, rhs: Rhs) -> Result<Self::Output>;
709}
710
711use std::ops::Add;
712
713impl TryAdd for Value {
714	type Output = Self;
715	fn try_add(self, other: Self) -> Result<Self> {
716		Ok(match (self, other) {
717			(Self::Number(v), Self::Number(w)) => Self::Number(v.try_add(w)?),
718			(Self::String(v), Self::String(w)) => {
719				let mut s = String::with_capacity(v.len() + w.len());
720				s.push_str(v.as_str());
721				s.push_str(w.as_str());
722				Value::String(s.into())
723			}
724			(Self::Datetime(v), Self::Duration(w)) => Self::Datetime(w.try_add(v)?),
725			(Self::Duration(v), Self::Datetime(w)) => Self::Datetime(v.try_add(w)?),
726			(Self::Duration(v), Self::Duration(w)) => Self::Duration(v.try_add(w)?),
727			(Self::Array(v), Self::Array(w)) => Self::Array(v.concat(w)),
728			(Self::Array(v), Self::Set(w)) => Self::Array(v.concat_set(w)),
729
730			(Self::Set(mut v), Self::Set(w)) => {
731				v.0.extend(w.0);
732				Self::Set(v)
733			}
734			(Self::Set(mut v), Self::Array(w)) => {
735				v.0.extend(w.0);
736				Self::Set(v)
737			}
738			(Self::Object(v), Self::Object(w)) => Self::Object(v.add(w)),
739			(v, w) => bail!(Error::TryAdd(v.kind_of().to_owned(), w.kind_of().to_owned())),
740		})
741	}
742}
743
744// ------------------------------
745
746pub trait TrySub<Rhs = Self> {
747	type Output;
748	fn try_sub(self, v: Rhs) -> Result<Self::Output>;
749}
750
751impl TrySub for Value {
752	type Output = Self;
753	fn try_sub(self, other: Self) -> Result<Self> {
754		Ok(match (self, other) {
755			(Self::Number(v), Self::Number(w)) => Self::Number(v.try_sub(w)?),
756			(Self::Datetime(v), Self::Datetime(w)) => Self::Duration(v.try_sub(w)?),
757			(Self::Datetime(v), Self::Duration(w)) => Self::Datetime(w.try_sub(v)?),
758			(Self::Duration(v), Self::Datetime(w)) => Self::Datetime(v.try_sub(w)?),
759			(Self::Duration(v), Self::Duration(w)) => Self::Duration(v.try_sub(w)?),
760			(Self::Array(v), Self::Array(x)) => Self::from(v.remove_all(&x.0)),
761			(Self::Array(v), Self::Set(x)) => Self::from(v.remove_all_set(&x)),
762			(Self::Set(mut v), Self::Array(x)) => {
763				for item in x.0 {
764					v.remove(&item);
765				}
766				Self::from(v)
767			}
768			(Self::Set(mut v), Self::Set(x)) => {
769				for item in x {
770					v.remove(&item);
771				}
772				Self::from(v)
773			}
774			(v, w) => bail!(Error::TrySub(v.kind_of().to_owned(), w.kind_of().to_owned())),
775		})
776	}
777}
778
779// ------------------------------
780
781pub trait TryMul<Rhs = Self> {
782	type Output;
783	fn try_mul(self, v: Self) -> Result<Self::Output>;
784}
785
786impl TryMul for Value {
787	type Output = Self;
788	fn try_mul(self, other: Self) -> Result<Self> {
789		match (self, other) {
790			(Self::Number(v), Self::Number(w)) => Ok(Self::Number(v.try_mul(w)?)),
791			(Self::Duration(d), Self::Number(Number::Int(i))) => {
792				if i < 0 {
793					bail!("Cannot multiply a duration with a negative number");
794				}
795
796				let factor: u32 = i.try_into()?;
797
798				let res =
799					d.0.checked_mul(factor)
800						.ok_or_else(|| Error::ArithmeticOverflow(format!("{d} * {i}")))?;
801
802				Ok(Value::Duration(Duration(res)))
803			}
804			(Self::Duration(d), Self::Number(Number::Float(f))) => {
805				if !(f.is_finite() && f >= 0.0) {
806					bail!("Cannot multiply a duration with a non-finite or negative number");
807				}
808
809				let secs = d.0.as_secs_f64() * f;
810				let res = StdDuration::try_from_secs_f64(secs)
811					.map_err(|_| Error::ArithmeticOverflow(format!("{d} * {f}")))?;
812
813				Ok(Value::Duration(Duration(res)))
814			}
815			(Self::Duration(d), Self::Number(Number::Decimal(dec))) => {
816				if !dec.is_sign_positive() && !dec.is_zero() {
817					bail!("Cannot multiply a duration with a negative number");
818				}
819
820				let nanos: Decimal = d.nanos().into();
821				let scaled = (nanos * dec).trunc();
822
823				let scaled_nanos: u128 = scaled
824					.to_u128()
825					.ok_or_else(|| Error::ArithmeticOverflow(format!("{d} * {dec}")))?;
826
827				let secs: u64 = (scaled_nanos / 1_000_000_000)
828					.try_into()
829					.map_err(|_| Error::ArithmeticOverflow(format!("{d} * {dec}")))?;
830
831				let subsec_nanos: u32 = (scaled_nanos % 1_000_000_000).try_into()?;
832
833				Ok(Value::Duration(Duration(StdDuration::new(secs, subsec_nanos))))
834			}
835			(v, w) => bail!(Error::TryMul(v.kind_of().to_owned(), w.kind_of().to_owned())),
836		}
837	}
838}
839
840// ------------------------------
841
842pub trait TryDiv<Rhs = Self> {
843	type Output;
844	fn try_div(self, v: Self) -> Result<Self::Output>;
845}
846
847impl TryDiv for Value {
848	type Output = Self;
849	fn try_div(self, other: Self) -> Result<Self> {
850		match (self, other) {
851			(Self::Number(v), Self::Number(w)) => Ok(Self::Number(v.try_div(w)?)),
852			(Self::Duration(d), Self::Number(Number::Int(i))) => {
853				if i <= 0 {
854					bail!("A duration can only be divided by a value greater than 0.");
855				}
856
857				let denom: u32 = i.try_into()?;
858				let res = d.0 / denom;
859				Ok(Value::Duration(Duration(res)))
860			}
861			(Self::Duration(d), Self::Number(Number::Float(f))) => {
862				if !(f.is_finite() && f > 0.0) {
863					bail!("A duration can only be divided by a finite value greater than 0.");
864				}
865				let secs = d.as_secs_f64() / f;
866
867				let res = StdDuration::try_from_secs_f64(secs)?;
868				Ok(Value::Duration(Duration(res)))
869			}
870			(Self::Duration(d), Self::Number(Number::Decimal(dec))) => {
871				if !dec.is_sign_positive() || dec.is_zero() {
872					bail!("A duration can only be divided by a value greater than 0.");
873				}
874
875				let nanos: Decimal = d.nanos().into();
876
877				let divided = (nanos / dec).trunc();
878
879				let divided_nanos: u128 = divided
880					.to_u128()
881					.ok_or_else(|| anyhow::anyhow!("Resulting duration is out of range"))?;
882
883				let secs: u64 = (divided_nanos / 1_000_000_000)
884					.try_into()
885					.map_err(|_| anyhow::anyhow!("Resulting duration seconds out of range"))?;
886
887				let subsec_nanos: u32 = (divided_nanos % 1_000_000_000).try_into()?;
888
889				Ok(Value::Duration(Duration(StdDuration::new(secs, subsec_nanos))))
890			}
891
892			(v, w) => bail!(Error::TryDiv(v.kind_of().to_owned(), w.kind_of().to_owned())),
893		}
894	}
895}
896
897// ------------------------------
898
899pub trait TryFloatDiv<Rhs = Self> {
900	type Output;
901	fn try_float_div(self, v: Self) -> Result<Self::Output>;
902}
903
904impl TryFloatDiv for Value {
905	type Output = Self;
906	fn try_float_div(self, other: Self) -> Result<Self::Output> {
907		Ok(match (self, other) {
908			(Self::Number(v), Self::Number(w)) => Self::Number(v.try_float_div(w)?),
909			(v, w) => bail!(Error::TryDiv(v.kind_of().to_owned(), w.kind_of().to_owned())),
910		})
911	}
912}
913
914// ------------------------------
915
916pub trait TryRem<Rhs = Self> {
917	type Output;
918	fn try_rem(self, v: Self) -> Result<Self::Output>;
919}
920
921impl TryRem for Value {
922	type Output = Self;
923	fn try_rem(self, other: Self) -> Result<Self> {
924		Ok(match (self, other) {
925			(Self::Number(v), Self::Number(w)) => Self::Number(v.try_rem(w)?),
926			(v, w) => bail!(Error::TryRem(v.kind_of().to_owned(), w.kind_of().to_owned())),
927		})
928	}
929}
930
931// ------------------------------
932
933pub trait TryPow<Rhs = Self> {
934	type Output;
935	fn try_pow(self, v: Self) -> Result<Self::Output>;
936}
937
938impl TryPow for Value {
939	type Output = Self;
940	fn try_pow(self, other: Self) -> Result<Self> {
941		Ok(match (self, other) {
942			(Value::Number(v), Value::Number(w)) => Self::Number(v.try_pow(w)?),
943			(v, w) => bail!(Error::TryPow(v.kind_of().to_owned(), w.kind_of().to_owned())),
944		})
945	}
946}
947
948// ------------------------------
949
950pub trait TryNeg<Rhs = Self> {
951	type Output;
952	fn try_neg(self) -> Result<Self::Output>;
953}
954
955impl TryNeg for Value {
956	type Output = Self;
957	fn try_neg(self) -> Result<Self> {
958		Ok(match self {
959			Self::Number(n) => Self::Number(n.try_neg()?),
960			v => bail!(Error::TryNeg(v.kind_of().to_owned())),
961		})
962	}
963}
964
965// Conversion methods.
966
967/// Macro implementing conversion methods for the variants of the value enum.
968macro_rules! subtypes {
969	($($name:ident$( ( $($t:tt)* ) )? => ($is:ident,$as:ident,$into:ident)),*$(,)?) => {
970		impl Value {
971			$(
972				subtypes!{@method $name $( ($($t)*) )? => $is,$as,$into}
973			)*
974		}
975
976			$(
977				subtypes!{@from $name $( ($($t)*) )? => $is,$as,$into}
978			)*
979
980	};
981
982	(@pat $name:ident($t:ty)) => {
983		Value::$name(_)
984	};
985
986	(@pat $name:ident) => {
987		Value::$name
988	};
989
990	(@method $name:ident($t:ty) => $is:ident,$as:ident,$into:ident) => {
991		#[doc = concat!("Check if the value is a [`",stringify!($name),"`]")]
992		#[allow(dead_code)]
993		pub fn $is(&self) -> bool{
994			matches!(self,Value::$name(_))
995		}
996
997		#[doc = concat!("Return a reference to [`",stringify!($name),"`] if the value is of that type")]
998		#[allow(dead_code)]
999		pub fn $as(&self) -> Option<&$t>{
1000			if let Value::$name(x) = self{
1001				Some(x)
1002			}else{
1003				None
1004			}
1005		}
1006
1007		#[doc = concat!("Turns the value into a [`",stringify!($name),"`] returning None if the value is not of that type")]
1008		#[allow(dead_code)]
1009		pub fn $into(self) -> Option<$t>{
1010			if let Value::$name(x) = self{
1011				Some(x)
1012			}else{
1013				None
1014			}
1015		}
1016	};
1017
1018	(@method $name:ident => $is:ident,$as:ident,$into:ident) => {
1019		#[doc = concat!("Check if the value is a [`",stringify!($name),"`]")]
1020		#[allow(dead_code)]
1021		pub fn $is(&self) -> bool{
1022			matches!(self,Value::$name)
1023		}
1024	};
1025
1026
1027	(@from $name:ident(Box<$inner:ident>) => $is:ident,$as:ident,$into:ident) => {
1028		impl From<$inner> for Value {
1029			fn from(v: $inner) -> Self{
1030				Value::$name(Box::new(v))
1031			}
1032		}
1033
1034		impl From<Box<$inner>> for Value {
1035			fn from(v: Box<$inner>) -> Self{
1036				Value::$name(v)
1037			}
1038		}
1039	};
1040
1041	(@from $name:ident($t:ident) => $is:ident,$as:ident,$into:ident) => {
1042		impl From<$t> for Value {
1043			fn from(v: $t) -> Self{
1044				Value::$name(v)
1045			}
1046		}
1047	};
1048
1049	(@from $name:ident => $is:ident,$as:ident,$into:ident) => {
1050		// skip
1051	};
1052
1053}
1054
1055subtypes! {
1056	None => (is_none,_unused,_unused),
1057	Null => (is_null,_unused,_unused),
1058	Bool(bool) => (is_bool,as_bool,into_bool),
1059	Number(Number) => (is_number,as_number,into_number),
1060	String(Strand) => (is_strand,as_strand,into_strand),
1061	Table(TableName) => (is_table,as_table,into_table),
1062	Duration(Duration) => (is_duration,as_duration,into_duration),
1063	Datetime(Datetime) => (is_datetime,as_datetime,into_datetime),
1064	Uuid(Uuid) => (is_uuid,as_uuid,into_uuid),
1065	Array(Array) => (is_array,as_array,into_array),
1066	Set(Set) => (is_set,as_set,into_set),
1067	Object(Object) => (is_object,as_object,into_object),
1068	Geometry(Geometry) => (is_geometry,as_geometry,into_geometry),
1069	Bytes(Bytes) => (is_bytes,as_bytes,into_bytes),
1070	RecordId(RecordId) => (is_record,as_record,into_record),
1071	Regex(Regex) => (is_regex,as_regex,into_regex),
1072	Range(Box<Range>) => (is_range,as_range,into_range),
1073	Closure(Box<Closure>) => (is_closure,as_closure,into_closure),
1074	File(File) => (is_file,as_file,into_file),
1075}
1076
1077macro_rules! impl_from_number {
1078	($($n:ident),*$(,)?) => {
1079		$(
1080			impl From<$n> for Value{
1081				fn from(v: $n) -> Self{
1082					Value::Number(Number::from(v))
1083				}
1084			}
1085		)*
1086	};
1087}
1088impl_from_number!(i8, i16, i32, i64, u8, u16, u32, isize, f32, f64, Decimal);
1089
1090impl From<Vec<Value>> for Value {
1091	fn from(value: Vec<Value>) -> Self {
1092		Value::Array(Array(value))
1093	}
1094}
1095
1096impl From<Null> for Value {
1097	fn from(_v: Null) -> Self {
1098		Value::Null
1099	}
1100}
1101
1102// `u64`/`usize` always fit losslessly into a Number (as an Int, or a Decimal
1103// when they exceed i64), so these conversions are infallible.
1104impl From<u64> for Value {
1105	fn from(v: u64) -> Self {
1106		Value::Number(Number::from(v))
1107	}
1108}
1109
1110impl From<usize> for Value {
1111	fn from(v: usize) -> Self {
1112		Value::Number(Number::from(v))
1113	}
1114}
1115
1116// `i128`/`u128` can exceed Decimal's range, so these conversions are fallible
1117// and error rather than truncate when the value is too large to represent.
1118impl TryFrom<i128> for Value {
1119	type Error = Error;
1120	fn try_from(v: i128) -> Result<Self, Self::Error> {
1121		Ok(Value::Number(Number::try_from(v)?))
1122	}
1123}
1124
1125impl TryFrom<u128> for Value {
1126	type Error = Error;
1127	fn try_from(v: u128) -> Result<Self, Self::Error> {
1128		Ok(Value::Number(Number::try_from(v)?))
1129	}
1130}
1131
1132impl From<&str> for Value {
1133	fn from(v: &str) -> Self {
1134		Self::String(Strand::from(v))
1135	}
1136}
1137
1138impl From<String> for Value {
1139	fn from(v: String) -> Self {
1140		Self::String(Strand::from(v))
1141	}
1142}
1143
1144impl From<::uuid::Uuid> for Value {
1145	fn from(v: ::uuid::Uuid) -> Self {
1146		Value::Uuid(Uuid::from(v))
1147	}
1148}
1149
1150impl From<DateTime<Utc>> for Value {
1151	fn from(v: DateTime<Utc>) -> Self {
1152		Value::Datetime(Datetime::from(v))
1153	}
1154}
1155
1156impl From<Point<f64>> for Value {
1157	fn from(v: Point<f64>) -> Self {
1158		Value::Geometry(Geometry::from(v))
1159	}
1160}
1161
1162impl From<HashMap<&str, Value>> for Value {
1163	fn from(v: HashMap<&str, Value>) -> Self {
1164		Value::Object(Object::from(v))
1165	}
1166}
1167
1168impl From<HashMap<String, Value>> for Value {
1169	fn from(v: HashMap<String, Value>) -> Self {
1170		Value::Object(Object::from(v))
1171	}
1172}
1173
1174impl From<BTreeMap<String, Value>> for Value {
1175	fn from(v: BTreeMap<String, Value>) -> Self {
1176		Value::Object(Object::from(v))
1177	}
1178}
1179
1180impl From<VecMap<String, Value>> for Value {
1181	fn from(v: VecMap<String, Value>) -> Self {
1182		Value::Object(Object::from(v))
1183	}
1184}
1185
1186impl From<VecMap<Strand, Value>> for Value {
1187	fn from(v: VecMap<Strand, Value>) -> Self {
1188		Value::Object(Object(v))
1189	}
1190}
1191
1192impl From<VecMap<&str, Value>> for Value {
1193	fn from(v: VecMap<&str, Value>) -> Self {
1194		Value::Object(Object::from(v))
1195	}
1196}
1197
1198impl From<BTreeMap<&str, Value>> for Value {
1199	fn from(v: BTreeMap<&str, Value>) -> Self {
1200		Value::Object(Object::from(v))
1201	}
1202}
1203
1204impl TryFrom<Value> for crate::types::PublicValue {
1205	type Error = anyhow::Error;
1206
1207	fn try_from(s: Value) -> Result<Self, Self::Error> {
1208		convert_value_to_public_value(s)
1209	}
1210}
1211
1212impl From<crate::types::PublicValue> for Value {
1213	fn from(s: crate::types::PublicValue) -> Self {
1214		convert_public_value_to_internal(s)
1215	}
1216}
1217
1218impl FromIterator<Value> for Value {
1219	fn from_iter<I: IntoIterator<Item = Value>>(iter: I) -> Self {
1220		Value::Array(Array(iter.into_iter().collect()))
1221	}
1222}
1223
1224impl FromIterator<(String, Value)> for Value {
1225	fn from_iter<I: IntoIterator<Item = (String, Value)>>(iter: I) -> Self {
1226		Value::Object(Object::from_iter(iter))
1227	}
1228}
1229
1230/// Convert our internal value `crate::val::Value` to the public value `surrealdb_types::Value`.
1231///
1232/// In the future, as the two types diverge, this function will need access to the context in order
1233/// to convert certain values to the public value.
1234/// Maximum nesting depth a [`Value`] may carry.
1235///
1236/// Several code paths walk a value with native recursion — the public-value
1237/// conversion every query result passes through, the SQL formatter used by
1238/// export and `INFO`, and the derived `revision` storage codec. None can carry
1239/// its own cumulative depth counter across the others, and `surrealdb-core` is
1240/// built with `panic = "abort"` under the release profile, so a stack overflow
1241/// in any of them is an unconditional process abort rather than a failed
1242/// request.
1243///
1244/// The parser's own caps do not bound this: they are per-parse, and a value is
1245/// grown one level per request by a read-modify-write loop
1246/// (`UPDATE deep:1 SET v = [v]`) that stays far under them at every step. The
1247/// compute cap (`max_computation_depth`) bounds expression nesting, not the
1248/// runtime nesting of a value being cloned or serialised.
1249///
1250/// Sized well above any legitimate document and far below the depth at which
1251/// the recursive walks approach a thread stack.
1252pub const MAX_VALUE_DEPTH: usize = 256;
1253
1254/// A node in the iterative depth walk. Nesting reaches a `Value` through several
1255/// wrappers that are not themselves `Value`s - a record id's key, a
1256/// `GeometryCollection`, and a closure's captured variables - so the worklist
1257/// carries any of them.
1258enum DepthNode<'a> {
1259	Value(&'a Value),
1260	Key(&'a crate::val::RecordIdKey),
1261	Geo(&'a crate::val::Geometry),
1262	Closure(&'a crate::val::Closure),
1263}
1264
1265/// Rejects a [`Value`] nested deeper than [`MAX_VALUE_DEPTH`].
1266///
1267/// Deliberately iterative: a recursive checker would overflow the stack on
1268/// exactly the input it exists to reject. Traversal stops at the first node
1269/// past the cap, so a deeply-nested value is rejected without being walked in
1270/// full.
1271///
1272/// A record id's key is walked too, and a geometry's `GeometryCollection`
1273/// nesting. The derived storage codec, the SQL formatter and the recursive drop
1274/// all descend into a compound key (`t:[[[...]]]`) and into a nested collection,
1275/// so anything deep enough to overflow them must be rejected at the same point a
1276/// deep field value is.
1277pub fn check_value_depth(value: &Value) -> Result<()> {
1278	use crate::val::RecordIdKey;
1279	// Fast path: a value that nests nothing is depth 1 and cannot exceed the
1280	// cap, so skip allocating the traversal stack. This keeps the check
1281	// allocation-free for the common scalar case - e.g. a `fold` / `reduce`
1282	// accumulator that reduces to a number, which is checked every iteration.
1283	// `Geometry` (a `GeometryCollection` nests) and `Closure` (its captures hold
1284	// values) are containers here.
1285	if !matches!(
1286		value,
1287		Value::Array(_)
1288			| Value::Set(_)
1289			| Value::Object(_)
1290			| Value::Range(_)
1291			| Value::RecordId(_)
1292			| Value::Geometry(_)
1293			| Value::Closure(_)
1294	) {
1295		return Ok(());
1296	}
1297	// (node, depth of that node)
1298	let mut stack: Vec<(DepthNode<'_>, usize)> = vec![(DepthNode::Value(value), 1)];
1299	while let Some((node, depth)) = stack.pop() {
1300		if depth > MAX_VALUE_DEPTH {
1301			bail!(Error::ValueTooDeep {
1302				max: MAX_VALUE_DEPTH,
1303			});
1304		}
1305		match node {
1306			DepthNode::Value(value) => match value {
1307				Value::Array(v) => {
1308					stack.extend(v.0.iter().map(|v| (DepthNode::Value(v), depth + 1)))
1309				}
1310				Value::Set(v) => stack.extend(v.0.iter().map(|v| (DepthNode::Value(v), depth + 1))),
1311				Value::Object(v) => {
1312					stack.extend(v.0.values().map(|v| (DepthNode::Value(v), depth + 1)))
1313				}
1314				Value::Range(v) => {
1315					for bound in [&v.start, &v.end] {
1316						match bound {
1317							Bound::Included(v) | Bound::Excluded(v) => {
1318								stack.push((DepthNode::Value(v), depth + 1))
1319							}
1320							Bound::Unbounded => {}
1321						}
1322					}
1323				}
1324				Value::RecordId(v) => stack.push((DepthNode::Key(&v.key), depth + 1)),
1325				// A geometry and a closure nest within themselves, not within
1326				// `Value`, so the wrapper is pushed at `depth` (it is not itself a
1327				// level) and recurses via its own node below.
1328				Value::Geometry(g) => stack.push((DepthNode::Geo(g), depth)),
1329				Value::Closure(c) => stack.push((DepthNode::Closure(c), depth)),
1330				// Leaves, listed explicitly so a new nesting `Value` variant is a
1331				// compile error here rather than a silent hole.
1332				Value::None
1333				| Value::Null
1334				| Value::Bool(_)
1335				| Value::Number(_)
1336				| Value::String(_)
1337				| Value::Duration(_)
1338				| Value::Datetime(_)
1339				| Value::Uuid(_)
1340				| Value::Bytes(_)
1341				| Value::Table(_)
1342				| Value::File(_)
1343				| Value::Regex(_) => {}
1344			},
1345			DepthNode::Key(key) => match key {
1346				RecordIdKey::Array(a) => {
1347					stack.extend(a.0.iter().map(|v| (DepthNode::Value(v), depth + 1)))
1348				}
1349				RecordIdKey::Object(o) => {
1350					stack.extend(o.0.values().map(|v| (DepthNode::Value(v), depth + 1)))
1351				}
1352				RecordIdKey::Range(r) => {
1353					for bound in [&r.start, &r.end] {
1354						match bound {
1355							Bound::Included(k) | Bound::Excluded(k) => {
1356								stack.push((DepthNode::Key(k), depth + 1))
1357							}
1358							Bound::Unbounded => {}
1359						}
1360					}
1361				}
1362				RecordIdKey::Number(_) | RecordIdKey::String(_) | RecordIdKey::Uuid(_) => {}
1363			},
1364			DepthNode::Geo(geo) => {
1365				// Only a `GeometryCollection` nests further geometries; the
1366				// point/line/polygon variants are bounded-depth primitives.
1367				if let crate::val::Geometry::Collection(geoms) = geo {
1368					stack.extend(geoms.iter().map(|g| (DepthNode::Geo(g), depth + 1)));
1369				}
1370			}
1371			DepthNode::Closure(closure) => {
1372				// A closure captures the values it references, so a closure
1373				// capturing a closure nests. The `body` / `args` are parse-bounded
1374				// AST, not runtime values; only the captures grow. A `Builtin`
1375				// closure (the compiled form the streaming engine produces) holds
1376				// its captures behind the trait, so walk them through
1377				// `captured_values` rather than treating it as a leaf.
1378				match closure {
1379					crate::val::Closure::Expr {
1380						captures,
1381						..
1382					} => {
1383						stack.extend(captures.0.values().map(|v| (DepthNode::Value(v), depth + 1)));
1384					}
1385					crate::val::Closure::Builtin(builtin) => {
1386						stack.extend(
1387							builtin
1388								.captured_values()
1389								.into_iter()
1390								.map(|v| (DepthNode::Value(v), depth + 1)),
1391						);
1392					}
1393				}
1394			}
1395		}
1396	}
1397	Ok(())
1398}
1399
1400pub fn convert_value_to_public_value(value: crate::val::Value) -> Result<surrealdb_types::Value> {
1401	convert_value_to_public_value_at(value, 1)
1402}
1403
1404/// Converts a row-shaped statement result (the array of rows a non-`ONLY`
1405/// `SELECT` and friends emit) to a public value, treating the enclosing array as
1406/// a transport envelope rather than a nesting level: each row is converted as
1407/// its own root at depth 1.
1408///
1409/// This is the anchor the storage write guard uses - `check_value_depth` runs on
1410/// a record's value with the record at depth 1 - and the one the streaming read
1411/// path already uses (it converts each row as it is emitted). Converting the
1412/// whole result array at depth 1 instead would charge every row an extra level,
1413/// so a record whose deepest field sits exactly at [`MAX_VALUE_DEPTH`] would
1414/// store and stream fine yet fail a collected read. A `Scalar` result (a single
1415/// value, e.g. `SELECT ... ONLY` or `RETURN`) is its own root and must go
1416/// through [`convert_value_to_public_value`] instead.
1417pub fn convert_result_rows_to_public(value: crate::val::Value) -> Result<surrealdb_types::Value> {
1418	let crate::val::Value::Array(rows) = value else {
1419		// A row-shaped result is always the array of emitted rows; any other
1420		// shape is converted as its own root (defensive - not expected).
1421		return convert_value_to_public_value(value);
1422	};
1423	let converted =
1424		rows.0.into_iter().map(convert_value_to_public_value).collect::<Result<Vec<_>>>()?;
1425	Ok(surrealdb_types::Value::Array(surrealdb_types::Array::from(converted)))
1426}
1427
1428/// [`convert_value_to_public_value`] carrying the depth of `value`.
1429///
1430/// The depth is threaded through the mutual recursion rather than checked by a
1431/// separate pass so the universal query-result path pays only a counter, and so
1432/// a value already in storage from before [`MAX_VALUE_DEPTH`] was enforced
1433/// surfaces an error here instead of aborting the process.
1434fn convert_value_to_public_value_at(
1435	value: crate::val::Value,
1436	depth: usize,
1437) -> Result<surrealdb_types::Value> {
1438	if depth > MAX_VALUE_DEPTH {
1439		bail!(Error::ValueTooDeep {
1440			max: MAX_VALUE_DEPTH,
1441		});
1442	}
1443	match value {
1444		crate::val::Value::None => Ok(surrealdb_types::Value::None),
1445		crate::val::Value::Null => Ok(surrealdb_types::Value::Null),
1446		crate::val::Value::Bool(value) => Ok(surrealdb_types::Value::Bool(value)),
1447		crate::val::Value::Number(value) => convert_number_to_public(value),
1448		crate::val::Value::String(value) => Ok(surrealdb_types::Value::String(value.into())),
1449		crate::val::Value::Datetime(value) => convert_datetime_to_public(value),
1450		crate::val::Value::Duration(value) => convert_duration_to_public(value),
1451		crate::val::Value::Uuid(value) => convert_uuid_to_public(value),
1452		crate::val::Value::Array(value) => convert_array_to_public(value, depth),
1453		crate::val::Value::Set(value) => convert_set_to_public(value, depth),
1454		crate::val::Value::Object(value) => convert_object_to_public(value, depth),
1455		crate::val::Value::Geometry(value) => convert_geometry_to_public(value, depth),
1456		crate::val::Value::Bytes(value) => convert_bytes_to_public(value),
1457		crate::val::Value::RecordId(value) => convert_record_id_to_public(value, depth),
1458		crate::val::Value::File(value) => convert_file_to_public(value),
1459		crate::val::Value::Range(value) => convert_range_to_public(*value, depth),
1460		crate::val::Value::Regex(value) => convert_regex_to_public(value),
1461		crate::val::Value::Table(value) => {
1462			Ok(surrealdb_types::Value::Table(value.into_public_table()))
1463		}
1464		crate::val::Value::Closure(_) => {
1465			Err(anyhow::anyhow!("Closure values cannot be converted to public value"))
1466		}
1467	}
1468}
1469
1470fn convert_number_to_public(value: crate::val::Number) -> Result<surrealdb_types::Value> {
1471	let number = match value {
1472		crate::val::Number::Int(i) => surrealdb_types::Number::Int(i),
1473		crate::val::Number::Float(f) => surrealdb_types::Number::Float(f),
1474		crate::val::Number::Decimal(d) => surrealdb_types::Number::Decimal(d),
1475	};
1476	Ok(surrealdb_types::Value::Number(number))
1477}
1478
1479fn convert_datetime_to_public(value: crate::val::Datetime) -> Result<surrealdb_types::Value> {
1480	Ok(surrealdb_types::Value::Datetime(surrealdb_types::Datetime::from(value.0)))
1481}
1482
1483fn convert_duration_to_public(value: crate::val::Duration) -> Result<surrealdb_types::Value> {
1484	Ok(surrealdb_types::Value::Duration(surrealdb_types::Duration::from_std(value.0)))
1485}
1486
1487fn convert_uuid_to_public(value: crate::val::Uuid) -> Result<surrealdb_types::Value> {
1488	Ok(surrealdb_types::Value::Uuid(surrealdb_types::Uuid::from(value.0)))
1489}
1490
1491fn convert_bytes_to_public(value: crate::val::Bytes) -> Result<surrealdb_types::Value> {
1492	Ok(surrealdb_types::Value::Bytes(surrealdb_types::Bytes::from(value.0)))
1493}
1494
1495fn convert_regex_to_public(value: crate::val::Regex) -> Result<surrealdb_types::Value> {
1496	Ok(surrealdb_types::Value::Regex(surrealdb_types::Regex::from(value.0)))
1497}
1498
1499fn convert_file_to_public(value: crate::val::File) -> Result<surrealdb_types::Value> {
1500	Ok(surrealdb_types::Value::File(surrealdb_types::File::new(value.bucket, value.key)))
1501}
1502
1503fn convert_geometry_to_public(
1504	value: crate::val::Geometry,
1505	depth: usize,
1506) -> Result<surrealdb_types::Value> {
1507	// A `GeometryCollection` nests, so carry the depth through it as the other
1508	// container arms do; the storage guard (`check_value_depth`) rejects a deep
1509	// geometry at write, and this keeps the read path in step.
1510	if depth > MAX_VALUE_DEPTH {
1511		bail!(Error::ValueTooDeep {
1512			max: MAX_VALUE_DEPTH,
1513		});
1514	}
1515	use surrealdb_types::Geometry as PublicGeometry;
1516	let geometry = match value {
1517		crate::val::Geometry::Point(p) => PublicGeometry::Point(p),
1518		crate::val::Geometry::Line(l) => PublicGeometry::Line(l),
1519		crate::val::Geometry::Polygon(p) => PublicGeometry::Polygon(p),
1520		crate::val::Geometry::MultiPoint(mp) => PublicGeometry::MultiPoint(mp),
1521		crate::val::Geometry::MultiLine(ml) => PublicGeometry::MultiLine(ml),
1522		crate::val::Geometry::MultiPolygon(mp) => PublicGeometry::MultiPolygon(mp),
1523		crate::val::Geometry::Collection(c) => {
1524			let converted: Result<Vec<_>> = c
1525				.into_iter()
1526				.map(|g| {
1527					if let surrealdb_types::Value::Geometry(g) =
1528						convert_geometry_to_public(g, depth + 1)?
1529					{
1530						Ok(g)
1531					} else {
1532						Err(anyhow::anyhow!("Failed to convert geometry collection item"))
1533					}
1534				})
1535				.collect();
1536			PublicGeometry::Collection(converted?)
1537		}
1538	};
1539	Ok(surrealdb_types::Value::Geometry(geometry))
1540}
1541
1542fn convert_array_to_public(
1543	value: crate::val::Array,
1544	depth: usize,
1545) -> Result<surrealdb_types::Value> {
1546	let converted: Result<Vec<_>> =
1547		value.0.into_iter().map(|v| convert_value_to_public_value_at(v, depth + 1)).collect();
1548	Ok(surrealdb_types::Value::Array(surrealdb_types::Array::from(converted?)))
1549}
1550
1551fn convert_set_to_public(value: crate::val::Set, depth: usize) -> Result<surrealdb_types::Value> {
1552	// Set is stored as a sorted Vec internally
1553	let converted: Result<Vec<surrealdb_types::Value>> =
1554		value.0.into_iter().map(|v| convert_value_to_public_value_at(v, depth + 1)).collect();
1555	Ok(surrealdb_types::Value::Set(surrealdb_types::Set::from(converted?)))
1556}
1557
1558fn convert_object_to_public(
1559	value: crate::val::Object,
1560	depth: usize,
1561) -> Result<surrealdb_types::Value> {
1562	let converted = convert_object_to_public_map_at(value, depth)?;
1563	Ok(surrealdb_types::Value::Object(surrealdb_types::Object::from(converted)))
1564}
1565
1566pub fn convert_object_to_public_map(
1567	value: crate::val::Object,
1568) -> Result<BTreeMap<String, surrealdb_types::Value>> {
1569	convert_object_to_public_map_at(value, 1)
1570}
1571
1572fn convert_object_to_public_map_at(
1573	value: crate::val::Object,
1574	depth: usize,
1575) -> Result<BTreeMap<String, surrealdb_types::Value>> {
1576	value
1577		.0
1578		.into_iter()
1579		.map(|(k, v)| convert_value_to_public_value_at(v, depth + 1).map(|v| (k.into_string(), v)))
1580		.collect()
1581}
1582
1583fn convert_record_id_to_public(
1584	value: crate::val::RecordId,
1585	depth: usize,
1586) -> Result<surrealdb_types::Value> {
1587	// A record id's key nests one level under it, matching `check_value_depth`'s
1588	// `Key` node at `depth + 1`, so continue the count there. Without the `+ 1`
1589	// the read path accepts ~2x the record-id nesting the storage guard rejects.
1590	let key = convert_record_id_key_to_public(value.key, depth + 1)?;
1591	Ok(surrealdb_types::Value::RecordId(surrealdb_types::RecordId {
1592		table: value.table.into_public_table(),
1593		key,
1594	}))
1595}
1596
1597fn convert_record_id_key_to_public(
1598	key: crate::val::RecordIdKey,
1599	depth: usize,
1600) -> Result<surrealdb_types::RecordIdKey> {
1601	if depth > MAX_VALUE_DEPTH {
1602		bail!(Error::ValueTooDeep {
1603			max: MAX_VALUE_DEPTH,
1604		});
1605	}
1606	match key {
1607		crate::val::RecordIdKey::Number(n) => Ok(surrealdb_types::RecordIdKey::Number(n)),
1608		crate::val::RecordIdKey::String(s) => Ok(surrealdb_types::RecordIdKey::String(s.into())),
1609		crate::val::RecordIdKey::Uuid(u) => {
1610			Ok(surrealdb_types::RecordIdKey::Uuid(surrealdb_types::Uuid::from(u.0)))
1611		}
1612		crate::val::RecordIdKey::Array(a) => {
1613			let converted_array = convert_array_to_public(a, depth)?;
1614			if let surrealdb_types::Value::Array(arr) = converted_array {
1615				Ok(surrealdb_types::RecordIdKey::Array(arr))
1616			} else {
1617				Err(anyhow::anyhow!("Failed to convert record id key array"))
1618			}
1619		}
1620		crate::val::RecordIdKey::Object(o) => {
1621			let converted_object = convert_object_to_public(o, depth)?;
1622			if let surrealdb_types::Value::Object(obj) = converted_object {
1623				Ok(surrealdb_types::RecordIdKey::Object(obj))
1624			} else {
1625				Err(anyhow::anyhow!("Failed to convert record id key object"))
1626			}
1627		}
1628		crate::val::RecordIdKey::Range(r) => {
1629			Ok(surrealdb_types::RecordIdKey::Range(Box::new(surrealdb_types::RecordIdKeyRange {
1630				start: match r.start {
1631					Bound::Included(k) => {
1632						Bound::Included(convert_record_id_key_to_public(k, depth + 1)?)
1633					}
1634					Bound::Excluded(k) => {
1635						Bound::Excluded(convert_record_id_key_to_public(k, depth + 1)?)
1636					}
1637					Bound::Unbounded => Bound::Unbounded,
1638				},
1639				end: match r.end {
1640					Bound::Included(k) => {
1641						Bound::Included(convert_record_id_key_to_public(k, depth + 1)?)
1642					}
1643					Bound::Excluded(k) => {
1644						Bound::Excluded(convert_record_id_key_to_public(k, depth + 1)?)
1645					}
1646					Bound::Unbounded => Bound::Unbounded,
1647				},
1648			})))
1649		}
1650	}
1651}
1652
1653fn convert_range_to_public(
1654	value: crate::val::Range,
1655	depth: usize,
1656) -> Result<surrealdb_types::Value> {
1657	// A range's bounds nest one level under it, so continue the depth count
1658	// through them (`depth + 1`) as the array/object arms do. This keeps the read
1659	// path in agreement with `check_value_depth`, which also descends into range
1660	// bounds, so a bound whose value sits at the cap makes the range one past it
1661	// and is rejected rather than converted through a deeper recursion.
1662	Ok(surrealdb_types::Value::Range(Box::new(surrealdb_types::Range {
1663		start: match value.start {
1664			Bound::Included(v) => Bound::Included(convert_value_to_public_value_at(v, depth + 1)?),
1665			Bound::Excluded(v) => Bound::Excluded(convert_value_to_public_value_at(v, depth + 1)?),
1666			Bound::Unbounded => Bound::Unbounded,
1667		},
1668		end: match value.end {
1669			Bound::Included(v) => Bound::Included(convert_value_to_public_value_at(v, depth + 1)?),
1670			Bound::Excluded(v) => Bound::Excluded(convert_value_to_public_value_at(v, depth + 1)?),
1671			Bound::Unbounded => Bound::Unbounded,
1672		},
1673	})))
1674}
1675
1676#[cfg(test)]
1677mod tests {
1678	use super::{MAX_VALUE_DEPTH, check_value_depth, convert_value_to_public_value};
1679	use crate::val::{Array, Object, Value};
1680
1681	/// Builds a value nested `depth` levels deep: `depth` of 1 is a bare
1682	/// integer, 2 is `[1]`, 3 is `[[1]]`, and so on.
1683	fn nested_array(depth: usize) -> Value {
1684		let mut value = Value::from(1i64);
1685		for _ in 1..depth {
1686			value = Value::Array(Array::from(vec![value]));
1687		}
1688		value
1689	}
1690
1691	fn nested_object(depth: usize) -> Value {
1692		let mut value = Value::from(1i64);
1693		for _ in 1..depth {
1694			let mut obj = Object::default();
1695			obj.insert("v".to_owned(), value);
1696			value = Value::Object(obj);
1697		}
1698		value
1699	}
1700
1701	// The recursion sinks that walk a `Value` — the public-value conversion, the
1702	// SQL formatter, and the derived storage codec — carry no depth guard of
1703	// their own, and a stack overflow in any of them aborts the process under
1704	// the release profile's `panic = "abort"`. A value is grown one level per
1705	// request by a read-modify-write loop, so the parser's per-parse caps never
1706	// fire during accumulation; the depth of the value itself has to be bounded.
1707	#[test]
1708	fn value_depth_within_the_cap_is_accepted() {
1709		check_value_depth(&nested_array(MAX_VALUE_DEPTH))
1710			.expect("a value exactly at the cap must be accepted");
1711		check_value_depth(&nested_object(MAX_VALUE_DEPTH))
1712			.expect("objects nest the same way as arrays");
1713		check_value_depth(&Value::from(1i64)).expect("a scalar is depth 1");
1714	}
1715
1716	#[test]
1717	fn value_depth_past_the_cap_is_rejected() {
1718		let err = check_value_depth(&nested_array(MAX_VALUE_DEPTH + 1))
1719			.expect_err("a value one level past the cap must be rejected");
1720		assert!(err.to_string().contains("nested too deeply"), "unexpected error: {err}");
1721		assert!(check_value_depth(&nested_object(MAX_VALUE_DEPTH + 1)).is_err());
1722	}
1723
1724	// The check must survive the input it exists to reject: a recursive
1725	// implementation would overflow the stack rather than return an error. The
1726	// depth used is far past any thread stack's tolerance for a recursive walk,
1727	// so this test aborts rather than fails if the check regresses to recursion.
1728	//
1729	// The value is leaked deliberately. `Value`'s derived `Drop` is itself
1730	// recursive, so releasing a value this deep overflows the stack whatever
1731	// this check does — which is the reason the depth cap is enforced when a
1732	// record is written rather than only where the recursive walks are: a value
1733	// past the cap must never come to exist, because merely dropping one is
1734	// fatal.
1735	#[test]
1736	fn value_depth_check_does_not_recurse() {
1737		let deep = std::mem::ManuallyDrop::new(nested_array(200_000));
1738		assert!(
1739			check_value_depth(&deep).is_err(),
1740			"a very deeply nested value must be rejected, not overflow the stack"
1741		);
1742	}
1743
1744	// The conversion every query result passes through carries the depth
1745	// through its own mutual recursion, so a value already in storage from
1746	// before the cap was enforced surfaces an error rather than aborting.
1747	#[test]
1748	fn public_value_conversion_rejects_values_past_the_cap() {
1749		convert_value_to_public_value(nested_array(MAX_VALUE_DEPTH))
1750			.expect("a value at the cap must still convert");
1751		let err = convert_value_to_public_value(nested_array(MAX_VALUE_DEPTH + 1))
1752			.expect_err("a value past the cap must not convert");
1753		assert!(err.to_string().contains("nested too deeply"), "unexpected error: {err}");
1754	}
1755
1756	// A collected row-shaped result is an array of rows, each its own root at the
1757	// depth the storage guard used (a record at depth 1). Converting the whole
1758	// result array charges every row an extra level, so a row at exactly the cap
1759	// fails that way but must convert when treated as a row - the collected-read
1760	// half of the storage/read agreement.
1761	#[test]
1762	fn result_rows_conversion_does_not_charge_the_envelope() {
1763		use super::convert_result_rows_to_public;
1764
1765		let row = nested_array(MAX_VALUE_DEPTH);
1766		let result = Value::Array(Array::from(vec![row]));
1767
1768		// Whole-array conversion puts the row at depth 2 and rejects it.
1769		assert!(
1770			convert_value_to_public_value(result.clone()).is_err(),
1771			"the envelope-charging path must reject a row at the cap"
1772		);
1773		// Per-row conversion accepts it, matching storage and the streaming path.
1774		convert_result_rows_to_public(result).expect("a row at the cap must convert as a row");
1775	}
1776
1777	// A range's bounds nest one level under it, so the conversion carries the
1778	// depth through them: a bound holding a value already at the cap makes the
1779	// whole value one past it and must be rejected rather than converted through
1780	// a deeper recursion.
1781	#[test]
1782	fn public_value_conversion_threads_depth_through_ranges() {
1783		use std::ops::Bound;
1784
1785		use crate::val::Range;
1786
1787		let range_bound = |v: Value| {
1788			Value::Range(Box::new(Range {
1789				start: Bound::Included(v),
1790				end: Bound::Unbounded,
1791			}))
1792		};
1793
1794		// A shallow value inside a range still converts.
1795		convert_value_to_public_value(range_bound(nested_array(8)))
1796			.expect("a shallow range-nested value must convert");
1797		// A range whose bound is at the cap makes the whole value one past it.
1798		let err = convert_value_to_public_value(range_bound(nested_array(MAX_VALUE_DEPTH)))
1799			.expect_err("a range-nested value past the cap must not convert");
1800		assert!(err.to_string().contains("nested too deeply"), "unexpected error: {err}");
1801	}
1802
1803	/// A `GeometryCollection` nests further geometries, and both the SQL
1804	/// formatter and the derived `Drop` recurse into it, so the depth guard must
1805	/// walk it rather than treat a `Geometry` as a leaf, and the conversion must
1806	/// carry the depth through it.
1807	#[test]
1808	fn geometry_collection_depth_is_walked() {
1809		use crate::val::Geometry;
1810
1811		// `depth` collections deep, innermost an empty collection.
1812		fn nested_geometry(depth: usize) -> Value {
1813			let mut g = Geometry::Collection(vec![]);
1814			for _ in 1..depth {
1815				g = Geometry::Collection(vec![g]);
1816			}
1817			Value::Geometry(g)
1818		}
1819
1820		// The `Value::Geometry` wrapper and its outermost collection share a level
1821		// (the geometry nests within itself, not within `Value`), so `depth`
1822		// collections deep is exactly `depth` levels: the cap is the boundary for
1823		// both the walk and the conversion, and they must agree to the level.
1824		check_value_depth(&nested_geometry(MAX_VALUE_DEPTH))
1825			.expect("a geometry collection exactly at the cap must be accepted");
1826		convert_value_to_public_value(nested_geometry(MAX_VALUE_DEPTH))
1827			.expect("a geometry collection at the cap must also convert");
1828		let err = check_value_depth(&nested_geometry(MAX_VALUE_DEPTH + 1))
1829			.expect_err("a geometry one level past the cap must be rejected");
1830		assert!(err.to_string().contains("nested too deeply"), "unexpected error: {err}");
1831		let err = convert_value_to_public_value(nested_geometry(MAX_VALUE_DEPTH + 1))
1832			.expect_err("a geometry one level past the cap must not convert");
1833		assert!(err.to_string().contains("nested too deeply"), "unexpected error: {err}");
1834
1835		// The walk stays iterative: a geometry too deep to drop recursively is
1836		// still rejected rather than overflowing the checker. Leaked because its
1837		// own `Drop` is recursive.
1838		let deep = std::mem::ManuallyDrop::new(nested_geometry(200_000));
1839		assert!(
1840			check_value_depth(&deep).is_err(),
1841			"a very deep geometry must be rejected, not overflow the stack"
1842		);
1843	}
1844
1845	/// A record id nests through its key, which the storage codec and the
1846	/// recursive drop both descend into, so the key must be walked like any other
1847	/// nested value rather than treated as a leaf. The walk and the conversion
1848	/// must agree on the boundary: a record id counts one level for the id itself
1849	/// and one for the key array before its nested contents, so a key holding a
1850	/// value nested `MAX_VALUE_DEPTH - 2` levels sits exactly at the cap.
1851	#[test]
1852	fn record_id_key_depth_is_walked() {
1853		use crate::val::{RecordId, RecordIdKey};
1854
1855		// A record id whose key wraps an array nested `inner` levels.
1856		fn record_id_with_nested_key(inner: usize) -> Value {
1857			Value::RecordId(RecordId {
1858				table: "t".into(),
1859				key: RecordIdKey::Array(Array::from(vec![nested_array(inner)])),
1860			})
1861		}
1862
1863		check_value_depth(&record_id_with_nested_key(MAX_VALUE_DEPTH - 2))
1864			.expect("a compound key exactly at the cap must be accepted");
1865		convert_value_to_public_value(record_id_with_nested_key(MAX_VALUE_DEPTH - 2))
1866			.expect("a compound key at the cap must also convert");
1867		let err = check_value_depth(&record_id_with_nested_key(MAX_VALUE_DEPTH - 1))
1868			.expect_err("a record id whose key nests one past the cap must be rejected");
1869		assert!(err.to_string().contains("nested too deeply"), "unexpected error: {err}");
1870		let err = convert_value_to_public_value(record_id_with_nested_key(MAX_VALUE_DEPTH - 1))
1871			.expect_err("a record id whose key nests one past the cap must not convert");
1872		assert!(err.to_string().contains("nested too deeply"), "unexpected error: {err}");
1873	}
1874
1875	/// `array::fold` can thread a closure-valued accumulator that captures itself
1876	/// each turn (`|$a, $_| |$x| $a`), growing the closure's capture nesting
1877	/// without bound, so the walk must descend into a closure's captures rather
1878	/// than treat it as a leaf. A closure cannot be converted to a public value
1879	/// or stored, so the walk the fold cap runs is the only guard on this shape.
1880	#[test]
1881	fn closure_capture_depth_is_walked() {
1882		use crate::expr::Expr;
1883		use crate::expr::variables::Variables;
1884		use crate::val::Closure;
1885
1886		// A closure whose single capture holds a value nested `inner` levels. The
1887		// closure shares a level with its `Value` wrapper and the capture nests
1888		// one under it, so a capture at `MAX_VALUE_DEPTH - 1` sits at the cap.
1889		fn closure_with_nested_capture(inner: usize) -> Value {
1890			let mut captures = Variables::default();
1891			captures.insert("a", nested_array(inner));
1892			Value::Closure(Box::new(Closure::Expr {
1893				args: vec![],
1894				returns: None,
1895				body: Expr::Break,
1896				captures,
1897			}))
1898		}
1899
1900		check_value_depth(&closure_with_nested_capture(MAX_VALUE_DEPTH - 1))
1901			.expect("a closure whose capture sits at the cap must be accepted");
1902		let err = check_value_depth(&closure_with_nested_capture(MAX_VALUE_DEPTH))
1903			.expect_err("a closure whose capture nests past the cap must be rejected");
1904		assert!(err.to_string().contains("nested too deeply"), "unexpected error: {err}");
1905
1906		// The walk stays iterative on a closure too deep to drop recursively.
1907		let deep = std::mem::ManuallyDrop::new(closure_with_nested_capture(200_000));
1908		assert!(
1909			check_value_depth(&deep).is_err(),
1910			"a very deep closure capture must be rejected, not overflow the stack"
1911		);
1912	}
1913
1914	/// The streaming engine represents a runtime closure as a `Closure::Builtin`
1915	/// whose captures live behind the `BuiltinClosureObj` trait, not a
1916	/// `Closure::Expr`, so the walk must reach them through `captured_values` or a
1917	/// closure-valued accumulator escapes the cap on that engine. The captures
1918	/// nest exactly as `Closure::Expr`'s do, so the boundary is the same.
1919	#[test]
1920	fn builtin_closure_captures_are_walked() {
1921		use std::sync::Arc;
1922
1923		use crate::val::Closure;
1924		use crate::val::closure::BuiltinClosureObj;
1925
1926		// A builtin closure that captured `values` by cloning them, the shape a
1927		// compiled closure takes.
1928		#[derive(Debug)]
1929		struct Captured(Vec<Value>);
1930		impl BuiltinClosureObj for Captured {
1931			fn as_any(&self) -> &dyn std::any::Any {
1932				self
1933			}
1934			fn captured_values(&self) -> Vec<&Value> {
1935				self.0.iter().collect()
1936			}
1937		}
1938		fn builtin_capturing(inner: usize) -> Value {
1939			Value::Closure(Box::new(Closure::Builtin(Arc::new(Captured(vec![nested_array(
1940				inner,
1941			)])))))
1942		}
1943
1944		check_value_depth(&builtin_capturing(MAX_VALUE_DEPTH - 1))
1945			.expect("a builtin closure whose capture sits at the cap must be accepted");
1946		let err = check_value_depth(&builtin_capturing(MAX_VALUE_DEPTH))
1947			.expect_err("a builtin closure whose capture nests past the cap must be rejected");
1948		assert!(err.to_string().contains("nested too deeply"), "unexpected error: {err}");
1949	}
1950
1951	/// A table name that collides with a keyword must render quoted, or the
1952	/// output cannot be parsed back. `TableName` derefs to `str`, so the
1953	/// rendering here cannot rely on method resolution finding an escaping
1954	/// impl — this pins the escaping itself rather than which impl provides it.
1955	/// `Number: Hash` disagrees with `Number: PartialEq`, so any value that can
1956	/// reach a number must report that a hash miss on it proves nothing.
1957	#[test]
1958	fn hash_agrees_with_eq_flags_numbers_at_any_depth() {
1959		use std::collections::BTreeMap;
1960
1961		use crate::val::{Number, Object, RecordId, RecordIdKey, Value};
1962
1963		let num = Value::Number(Number::Float(0.1));
1964		let nested =
1965			|v: Value| Value::Object(Object::from(BTreeMap::from([(Strand::from("v"), v)])));
1966
1967		for v in [
1968			Value::None,
1969			Value::Null,
1970			Value::Bool(true),
1971			Value::String("x".into()),
1972			Value::from(vec![Value::String("x".into())]),
1973			nested(Value::String("x".into())),
1974			Value::RecordId(RecordId {
1975				table: "t".into(),
1976				key: RecordIdKey::Number(1),
1977			}),
1978		] {
1979			assert!(v.hash_agrees_with_eq(), "{v:?}");
1980		}
1981
1982		for v in [
1983			num.clone(),
1984			Value::from(vec![num.clone()]),
1985			nested(num.clone()),
1986			nested(Value::from(vec![num.clone()])),
1987			Value::RecordId(RecordId {
1988				table: "t".into(),
1989				key: RecordIdKey::Array(vec![num].into()),
1990			}),
1991		] {
1992			assert!(!v.hash_agrees_with_eq(), "{v:?}");
1993		}
1994	}
1995
1996	#[test]
1997	fn table_value_renders_escaped() {
1998		use surrealdb_types::ToSql;
1999
2000		use crate::val::{TableName, Value};
2001
2002		assert_eq!(Value::Table(TableName::from("users")).to_sql(), "users");
2003		assert_eq!(Value::Table(TableName::from("table")).to_sql(), "`table`");
2004		assert_eq!(Value::Table(TableName::from("a b")).to_sql(), "`a b`");
2005	}
2006	use chrono::{TimeZone, Utc};
2007	use geo::{MultiLineString, MultiPoint, MultiPolygon, line_string, point, polygon};
2008	use rstest::rstest;
2009	use rust_decimal::Decimal;
2010	use serde_json::{Value as Json, json};
2011
2012	use super::*;
2013	use crate::syn;
2014	use crate::types::{
2015		PublicArray, PublicBytes, PublicDatetime, PublicDuration, PublicGeometry, PublicNumber,
2016		PublicObject, PublicRecordId, PublicRecordIdKey, PublicUuid, PublicValue,
2017	};
2018	use crate::val::Uuid;
2019
2020	macro_rules! parse_val {
2021		($input:expr) => {
2022			crate::val::convert_public_value_to_internal(syn::value($input).unwrap())
2023		};
2024	}
2025
2026	#[test]
2027	fn check_none() {
2028		assert!(Value::None.is_none());
2029		assert!(!Value::Null.is_none());
2030		assert!(!Value::from(1).is_none());
2031	}
2032
2033	#[test]
2034	fn check_null() {
2035		assert!(Value::Null.is_null());
2036		assert!(!Value::None.is_null());
2037		assert!(!Value::from(1).is_null());
2038	}
2039
2040	#[test]
2041	fn convert_truthy() {
2042		assert!(!Value::None.is_truthy());
2043		assert!(!Value::Null.is_truthy());
2044		assert!(Value::Bool(true).is_truthy());
2045		assert!(!Value::Bool(false).is_truthy());
2046		assert!(!Value::from(0).is_truthy());
2047		assert!(Value::from(1).is_truthy());
2048		assert!(Value::from(-1).is_truthy());
2049		assert!(Value::from(1.1).is_truthy());
2050		assert!(Value::from(-1.1).is_truthy());
2051		assert!(Value::from("true").is_truthy());
2052		assert!(Value::from("false").is_truthy());
2053		assert!(Value::from("falsey").is_truthy());
2054		assert!(Value::from("something").is_truthy());
2055		assert!(Value::from(Uuid::nil()).is_truthy());
2056		assert!(Value::from(Utc.with_ymd_and_hms(1948, 12, 3, 0, 0, 0).unwrap()).is_truthy());
2057	}
2058
2059	#[test]
2060	fn convert_string() {
2061		assert_eq!(String::from("NONE"), Value::None.into_raw_string());
2062		assert_eq!(String::from("NULL"), Value::Null.into_raw_string());
2063		assert_eq!(String::from("true"), Value::Bool(true).into_raw_string());
2064		assert_eq!(String::from("false"), Value::Bool(false).into_raw_string());
2065		assert_eq!(String::from("0"), Value::from(0).into_raw_string());
2066		assert_eq!(String::from("1"), Value::from(1).into_raw_string());
2067		assert_eq!(String::from("-1"), Value::from(-1).into_raw_string());
2068		assert_eq!(String::from("1.1f"), Value::from(1.1).into_raw_string());
2069		assert_eq!(String::from("-1.1f"), Value::from(-1.1).into_raw_string());
2070		assert_eq!(String::from("3"), Value::from("3").into_raw_string());
2071		assert_eq!(String::from("true"), Value::from("true").into_raw_string());
2072		assert_eq!(String::from("false"), Value::from("false").into_raw_string());
2073		assert_eq!(String::from("something"), Value::from("something").into_raw_string());
2074	}
2075
2076	#[test]
2077	fn check_size() {
2078		assert!(64 >= std::mem::size_of::<Value>(), "size of value too big");
2079	}
2080
2081	#[rstest]
2082	// Rev-2 Value envelope overhead: every varlen variant pays 1 tag byte
2083	// + 4 bytes `u32_le` payload length on top of the legacy `u16 rev` +
2084	// `u32 disc`. Inline variants (`None`, `Null`) are the entire encoding
2085	// in a single tag byte. `Bool` is `fixed(1)` -> tag + 1 byte.
2086	#[case::none(Value::None, 2)]
2087	#[case::null(Value::Null, 2)]
2088	#[case::bool(Value::Bool(true), 3)]
2089	#[case::bool(Value::Bool(false), 3)]
2090	// String wire shape under rev-2 varlen: rev (1) + tag (1) + u32_le
2091	// payload_length (4) + Strand wire (`usize len || utf8`) = 6 + Strand.
2092	// "test" is 4 utf8 bytes + 1 length varint = 5 bytes, so 11 total.
2093	#[case::string(Value::from("test"), 11)]
2094	// Objects under rev-2 Value + rev-2 Object `indexed_map`:
2095	//   Value envelope:  rev=2 (1) + tag (1) + u32_le payload_length (4) = 6
2096	//   Object envelope: rev=2 (1) + u32_le payload_length (4) = 5
2097	//   indexed_map body (sub-threshold): flag (1) + varint len + (K, V)*
2098	// Total delta vs rev-1 Value + rev-1 Object: +13 bytes per Object Value
2099	// (Value envelope +6, Object envelope +5, indexed_map flag +1, minus
2100	// 4 bytes the rev-1 framing was paying). The Wire-repr zero-copy
2101	// `walk_field_0` removed the need for a single-field offset table.
2102	#[case::object(Value::from(syn::value("{ hello: 'world' }").unwrap()), 31)]
2103	#[case::object(Value::from(syn::value("{ compact: true, schema: 0 }").unwrap()), 40)]
2104	fn check_serialize(#[case] value: Value, #[case] expected: usize) {
2105		let enc: Vec<u8> = revision::to_vec(&value).unwrap();
2106		assert_eq!(expected, enc.len());
2107	}
2108
2109	#[test]
2110	fn serialize_deserialize() {
2111		let val = parse_val!(
2112			"{ test: { something: [1, 'two', null, test:tobie, { trueee: false, noneee: null }] } }"
2113		);
2114		let res = parse_val!(
2115			"{ test: { something: [1, 'two', null, test:tobie, { trueee: false, noneee: null }] } }"
2116		);
2117		let enc: Vec<u8> = revision::to_vec(&val).unwrap();
2118		let dec: Value = revision::from_slice(&enc).unwrap();
2119		assert_eq!(res, dec);
2120	}
2121
2122	#[rstest]
2123	#[case::none(PublicValue::None, json!(null), PublicValue::Null)]
2124	#[case::null(PublicValue::Null, json!(null), PublicValue::Null)]
2125	#[case::bool(PublicValue::Bool(true), json!(true), PublicValue::Bool(true))]
2126	#[case::bool(PublicValue::Bool(false), json!(false), PublicValue::Bool(false))]
2127	#[case::number(
2128		PublicValue::Number(PublicNumber::Int(i64::MIN)),
2129		json!(i64::MIN),
2130		PublicValue::Number(PublicNumber::Int(i64::MIN)),
2131	)]
2132	#[case::number(
2133		PublicValue::Number(PublicNumber::Int(i64::MAX)),
2134		json!(i64::MAX),
2135		PublicValue::Number(PublicNumber::Int(i64::MAX)),
2136	)]
2137	#[case::number(
2138		PublicValue::Number(PublicNumber::Float(1.23)),
2139		json!(1.23),
2140		PublicValue::Number(PublicNumber::Float(1.23)),
2141	)]
2142	#[case::number(
2143		PublicValue::Number(PublicNumber::Float(f64::NEG_INFINITY)),
2144		json!(null),
2145		PublicValue::Null,
2146	)]
2147	#[case::number(
2148		PublicValue::Number(PublicNumber::Float(f64::MIN)),
2149		json!(-1.7976931348623157e308),
2150		PublicValue::Number(PublicNumber::Float(f64::MIN)),
2151	)]
2152	#[case::number(
2153		PublicValue::Number(PublicNumber::Float(0.0)),
2154		json!(0.0),
2155		PublicValue::Number(PublicNumber::Float(0.0)),
2156	)]
2157	#[case::number(
2158		PublicValue::Number(PublicNumber::Float(f64::MAX)),
2159		json!(1.7976931348623157e308),
2160		PublicValue::Number(PublicNumber::Float(f64::MAX)),
2161	)]
2162	#[case::number(
2163		PublicValue::Number(PublicNumber::Float(f64::INFINITY)),
2164		json!(null),
2165		PublicValue::Null,
2166	)]
2167	#[case::number(
2168		PublicValue::Number(PublicNumber::Float(f64::NAN)),
2169		json!(null),
2170		PublicValue::Null,
2171	)]
2172	#[case::number(
2173		PublicValue::Number(PublicNumber::Decimal(Decimal::new(123, 2))),
2174		json!("1.23"),
2175		PublicValue::String("1.23".to_string()),
2176	)]
2177	#[case::strand(
2178		PublicValue::String("".to_string()),
2179		json!(""),
2180		PublicValue::String("".to_string()),
2181	)]
2182	#[case::strand(
2183		PublicValue::String("foo".to_string()),
2184		json!("foo"),
2185		PublicValue::String("foo".to_string()),
2186	)]
2187	#[case::duration(
2188		PublicValue::Duration(PublicDuration::ZERO),
2189		json!("0ns"),
2190		PublicValue::String("0ns".to_string()),
2191	)]
2192	#[case::duration(
2193		PublicValue::Duration(PublicDuration::MAX),
2194		json!("584942417355y3w5d7h15s999ms999µs999ns"),
2195		PublicValue::String("584942417355y3w5d7h15s999ms999µs999ns".to_string()),
2196	)]
2197	#[case::datetime(
2198		PublicValue::Datetime(PublicDatetime::MIN_UTC),
2199		json!("-262143-01-01T00:00:00Z"),
2200		PublicValue::Datetime(PublicDatetime::MIN_UTC),
2201	)]
2202	#[case::datetime(
2203		PublicValue::Datetime(PublicDatetime::MAX_UTC),
2204		json!("+262142-12-31T23:59:59.999999999Z"),
2205		PublicValue::Datetime(PublicDatetime::MAX_UTC),
2206	)]
2207	#[case::uuid(
2208		PublicValue::Uuid(PublicUuid::nil()),
2209		json!("00000000-0000-0000-0000-000000000000"),
2210		PublicValue::Uuid(PublicUuid::nil()),
2211	)]
2212	#[case::uuid(
2213		PublicValue::Uuid(PublicUuid::max()),
2214		json!("ffffffff-ffff-ffff-ffff-ffffffffffff"),
2215		PublicValue::Uuid(PublicUuid::max()),
2216	)]
2217	#[case::bytes(
2218		PublicValue::Bytes(PublicBytes::default()),
2219		json!([]),
2220		PublicValue::Array(PublicArray::new()),
2221	)]
2222	#[case::bytes(
2223		PublicValue::Bytes(PublicBytes::from(b"foo".to_vec())),
2224		json!([102, 111, 111]),
2225		PublicValue::Array(PublicArray::from(vec![
2226			PublicValue::Number(PublicNumber::Int(102)),
2227			PublicValue::Number(PublicNumber::Int(111)),
2228			PublicValue::Number(PublicNumber::Int(111)),
2229		])),
2230	)]
2231	#[case::record_id(
2232		PublicValue::RecordId(PublicRecordId::new("foo", PublicRecordIdKey::String("bar".to_string()))) ,
2233		json!("foo:bar"),
2234		PublicValue::RecordId(PublicRecordId::new("foo", PublicRecordIdKey::String("bar".to_string()))) ,
2235	)]
2236	#[case::array(
2237		PublicValue::Array(PublicArray::new()),
2238		json!([]),
2239		PublicValue::Array(PublicArray::new()),
2240	)]
2241	#[case::array(
2242		PublicValue::Array(PublicArray::from(vec![PublicValue::Bool(true), PublicValue::Bool(false)])),
2243		json!([true, false]),
2244		PublicValue::Array(PublicArray::from(vec![PublicValue::Bool(true), PublicValue::Bool(false)])),
2245	)]
2246	#[case::object(
2247		PublicValue::Object(PublicObject::new()),
2248		json!({}),
2249		PublicValue::Object(PublicObject::new()),
2250	)]
2251	#[case::object(
2252		PublicValue::Object(PublicObject::from_iter([("done".to_owned(), PublicValue::Bool(true))])),
2253		json!({"done": true}),
2254		PublicValue::Object(PublicObject::from_iter([("done".to_owned(), PublicValue::Bool(true))])),
2255	)]
2256	#[case::geometry_point(
2257		PublicValue::Geometry(PublicGeometry::Point(point! { x: 10., y: 20. })),
2258		json!({ "type": "Point", "coordinates": [10., 20.]}),
2259		PublicValue::Geometry(PublicGeometry::Point(point! { x: 10., y: 20. })),
2260	)]
2261	#[case::geometry_line(
2262		PublicValue::Geometry(PublicGeometry::Line(line_string![
2263			( x: 0., y: 0. ),
2264			( x: 10., y: 0. ),
2265		])),
2266		json!({ "type": "LineString", "coordinates": [[0., 0.], [10., 0.]]}),
2267		PublicValue::Geometry(PublicGeometry::Line(line_string![
2268			( x: 0., y: 0. ),
2269			( x: 10., y: 0. ),
2270		])),
2271	)]
2272	#[case::geometry_polygon(
2273		PublicValue::Geometry(PublicGeometry::Polygon(polygon![
2274			(x: -111., y: 45.),
2275			(x: -111., y: 41.),
2276			(x: -104., y: 41.),
2277			(x: -104., y: 45.),
2278		])),
2279		json!({ "type": "Polygon", "coordinates": [[
2280			[-111., 45.],
2281			[-111., 41.],
2282			[-104., 41.],
2283			[-104., 45.],
2284			[-111., 45.],
2285		]]}),
2286		PublicValue::Geometry(PublicGeometry::Polygon(polygon![
2287			(x: -111., y: 45.),
2288			(x: -111., y: 41.),
2289			(x: -104., y: 41.),
2290			(x: -104., y: 45.),
2291		])),
2292	)]
2293	#[case::geometry_multi_point(
2294		PublicValue::Geometry(PublicGeometry::MultiPoint(MultiPoint::new(vec![
2295			point! { x: 0., y: 0. },
2296			point! { x: 1., y: 2. },
2297		]))),
2298		json!({ "type": "MultiPoint", "coordinates": [[0., 0.], [1., 2.]]}),
2299		PublicValue::Geometry(PublicGeometry::MultiPoint(MultiPoint::new(vec![
2300			point! { x: 0., y: 0. },
2301			point! { x: 1., y: 2. },
2302		]))),
2303	)]
2304	#[case::geometry_multi_line(
2305		PublicValue::Geometry(
2306			PublicGeometry::MultiLine(
2307				MultiLineString::new(vec![
2308					line_string![( x: 0., y: 0. ), ( x: 1., y: 2. )],
2309				])
2310			)
2311		),
2312		json!({ "type": "MultiLineString", "coordinates": [[[0., 0.], [1., 2.]]]}),
2313		PublicValue::Geometry(
2314			PublicGeometry::MultiLine(
2315				MultiLineString::new(vec![
2316					line_string![( x: 0., y: 0. ), ( x: 1., y: 2. )],
2317				])
2318			)
2319		),
2320	)]
2321	#[case::geometry_multi_polygon(
2322		PublicValue::Geometry(PublicGeometry::MultiPolygon(MultiPolygon::new(vec![
2323			polygon![
2324				(x: -111., y: 45.),
2325				(x: -111., y: 41.),
2326				(x: -104., y: 41.),
2327				(x: -104., y: 45.),
2328			],
2329		]))),
2330		json!({ "type": "MultiPolygon", "coordinates": [[[
2331			[-111., 45.],
2332			[-111., 41.],
2333			[-104., 41.],
2334			[-104., 45.],
2335			[-111., 45.],
2336		]]]})
2337	,	PublicValue::Geometry(PublicGeometry::MultiPolygon(MultiPolygon::new(vec![
2338			polygon![
2339				(x: -111., y: 45.),
2340				(x: -111., y: 41.),
2341				(x: -104., y: 41.),
2342				(x: -104., y: 45.),
2343			],
2344		]))),
2345	)]
2346	#[case::geometry_collection(
2347		PublicValue::Geometry(PublicGeometry::Collection(vec![])),
2348		json!({
2349			"type": "GeometryCollection",
2350			"geometries": [],
2351		}),
2352		PublicValue::Geometry(PublicGeometry::Collection(vec![])),
2353	)]
2354	#[case::geometry_collection_with_point(
2355		PublicValue::Geometry(PublicGeometry::Collection(vec![PublicGeometry::Point(point! { x: 10., y: 20. })])),
2356		json!({
2357		"type": "GeometryCollection",
2358		"geometries": [ { "type": "Point", "coordinates": [10., 20.] } ],
2359	}),
2360		PublicValue::Geometry(PublicGeometry::Collection(vec![PublicGeometry::Point(point! { x: 10., y: 20. })])),
2361	)]
2362	#[case::geometry_collection_with_line(
2363		PublicValue::Geometry(PublicGeometry::Collection(vec![PublicGeometry::Line(line_string![
2364			( x: 0., y: 0. ),
2365			( x: 10., y: 0. ),
2366		])])),
2367		json!({
2368			"type": "GeometryCollection",
2369			"geometries": [ { "type": "LineString", "coordinates": [[0., 0.], [10., 0.]] } ],
2370		}),
2371		PublicValue::Geometry(PublicGeometry::Collection(vec![PublicGeometry::Line(line_string![
2372			( x: 0., y: 0. ),
2373			( x: 10., y: 0. ),
2374		])])),
2375	)]
2376
2377	fn test_json(
2378		#[case] value: PublicValue,
2379		#[case] expected: Json,
2380		#[case] expected_deserialized: PublicValue,
2381	) {
2382		use surrealdb_syn::ParserConfig;
2383
2384		let json_value = value.into_json_value();
2385		assert_eq!(json_value, expected);
2386
2387		let json_str = serde_json::to_string(&json_value).expect("Failed to serialize to JSON");
2388		let deserialized_sql_value =
2389			crate::syn::value_legacy_strand(&json_str, &ParserConfig::default()).unwrap();
2390		assert_eq!(deserialized_sql_value, expected_deserialized);
2391	}
2392}