Skip to main content

surrealdb_expr/expr/
kind.rs

1use std::cmp::Ordering;
2use std::collections::{BTreeMap, HashSet};
3use std::fmt::{self, Display, Formatter};
4use std::hash::{Hash, Hasher};
5use std::str::FromStr;
6
7use geo::{LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon};
8use revision::revisioned;
9use rust_decimal::Decimal;
10use surrealdb_strand::Strand;
11use surrealdb_types::{SqlFormat, ToSql};
12
13use crate::expr::statements::info::InfoStructure;
14use crate::expr::{Expr, Literal, Part, Value};
15use crate::val::table_name_public::{IntoPublicTable, IntoTableName};
16use crate::val::{
17	Array, Bytes, Closure, Datetime, Duration, File, Geometry, Number, Range, RecordId, Regex, Set,
18	TableName, Uuid,
19};
20
21#[revisioned(revision = 1)]
22#[derive(Clone, Debug, Eq, PartialEq, Hash)]
23pub enum GeometryKind {
24	Point,
25	Line,
26	Polygon,
27	MultiPoint,
28	MultiLine,
29	MultiPolygon,
30	Collection,
31}
32
33impl ToSql for GeometryKind {
34	fn fmt_sql(&self, f: &mut String, _fmt: SqlFormat) {
35		match self {
36			GeometryKind::Point => f.push_str("point"),
37			GeometryKind::Line => f.push_str("line"),
38			GeometryKind::Polygon => f.push_str("polygon"),
39			GeometryKind::MultiPoint => f.push_str("multipoint"),
40			GeometryKind::MultiLine => f.push_str("multiline"),
41			GeometryKind::MultiPolygon => f.push_str("multipolygon"),
42			GeometryKind::Collection => f.push_str("collection"),
43		}
44	}
45}
46
47impl FromStr for GeometryKind {
48	type Err = anyhow::Error;
49
50	fn from_str(s: &str) -> Result<Self, Self::Err> {
51		match s {
52			"point" => Ok(GeometryKind::Point),
53			"line" => Ok(GeometryKind::Line),
54			"polygon" => Ok(GeometryKind::Polygon),
55			"multipoint" => Ok(GeometryKind::MultiPoint),
56			"multiline" => Ok(GeometryKind::MultiLine),
57			"multipolygon" => Ok(GeometryKind::MultiPolygon),
58			"collection" => Ok(GeometryKind::Collection),
59			_ => Err(anyhow::anyhow!("invalid geometry kind: {s}")),
60		}
61	}
62}
63
64impl From<GeometryKind> for crate::types::PublicGeometryKind {
65	fn from(k: GeometryKind) -> Self {
66		match k {
67			GeometryKind::Point => crate::types::PublicGeometryKind::Point,
68			GeometryKind::Line => crate::types::PublicGeometryKind::Line,
69			GeometryKind::Polygon => crate::types::PublicGeometryKind::Polygon,
70			GeometryKind::MultiPoint => crate::types::PublicGeometryKind::MultiPoint,
71			GeometryKind::MultiLine => crate::types::PublicGeometryKind::MultiLine,
72			GeometryKind::MultiPolygon => crate::types::PublicGeometryKind::MultiPolygon,
73			GeometryKind::Collection => crate::types::PublicGeometryKind::Collection,
74		}
75	}
76}
77
78impl From<crate::types::PublicGeometryKind> for GeometryKind {
79	fn from(k: crate::types::PublicGeometryKind) -> Self {
80		match k {
81			crate::types::PublicGeometryKind::Point => GeometryKind::Point,
82			crate::types::PublicGeometryKind::Line => GeometryKind::Line,
83			crate::types::PublicGeometryKind::Polygon => GeometryKind::Polygon,
84			crate::types::PublicGeometryKind::MultiPoint => GeometryKind::MultiPoint,
85			crate::types::PublicGeometryKind::MultiLine => GeometryKind::MultiLine,
86			crate::types::PublicGeometryKind::MultiPolygon => GeometryKind::MultiPolygon,
87			crate::types::PublicGeometryKind::Collection => GeometryKind::Collection,
88		}
89	}
90}
91
92/// The kind, or data type, of a value or field.
93#[revisioned(revision = 1)]
94#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)]
95pub enum Kind {
96	/// The most generic type, can be anything.
97	#[default]
98	Any,
99	/// None type.
100	None,
101	/// Null type.
102	Null,
103	/// Boolean type.
104	Bool,
105	/// Bytes type.
106	Bytes,
107	/// Datetime type.
108	Datetime,
109	/// Decimal type.
110	Decimal,
111	/// Duration type.
112	Duration,
113	/// 64-bit floating point type.
114	Float,
115	/// 64-bit signed integer type.
116	Int,
117	/// Number type, can be either a float, int or decimal.
118	/// This is the most generic type for numbers.
119	Number,
120	/// Object type.
121	Object,
122	/// String type.
123	String,
124	/// UUID type.
125	Uuid,
126	/// Regular expression type.
127	Regex,
128	/// A table type.
129	Table(Vec<TableName>),
130	/// A record type.
131	Record(Vec<TableName>),
132	/// A geometry type.
133	/// The vec contains the geometry types as strings, for example `"point"` or
134	/// `"polygon"`.
135	Geometry(Vec<GeometryKind>),
136	/// An either type.
137	/// Can be any of the kinds in the vec.
138	Either(Vec<Kind>),
139	/// A set type.
140	Set(Box<Kind>, Option<u64>),
141	/// An array type.
142	Array(Box<Kind>, Option<u64>),
143	/// A function type.
144	/// The first option is the argument types, the second is the optional
145	/// return type.
146	Function(Option<Vec<Kind>>, Option<Box<Kind>>),
147	/// A range type.
148	Range,
149	/// A literal type.
150	/// The literal type is used to represent a type that can only be a single
151	/// value. For example, `"a"` is a literal type which can only ever be
152	/// `"a"`. This can be used in the `Kind::Either` type to represent an
153	/// enum.
154	Literal(KindLiteral),
155	/// A file type.
156	/// If the kind was specified without a bucket the vec will be empty.
157	/// So `<file>` is just `Kind::File(Vec::new())`
158	File(Vec<String>),
159}
160
161impl Kind {
162	/// Returns the kind of a type.
163	pub fn of<T: HasKind>() -> Kind {
164		T::kind()
165	}
166
167	/// Returns true if this type is an `any`
168	pub fn is_any(&self) -> bool {
169		matches!(self, Kind::Any)
170	}
171
172	/// Returns true if this type is a record
173	pub fn is_record(&self) -> bool {
174		matches!(self, Kind::Record(_))
175	}
176
177	/// Returns `true` if a `DEFINE FIELD ... REFERENCE` declared with this kind
178	/// could hold a record id belonging to `table`.
179	///
180	/// A reference key is only ever written (by `process_reference_clause`)
181	/// when a reference field holds a record id, and the field's declared kind
182	/// constrains which tables those ids may belong to via TYPE coercion. So
183	/// this answers, from the schema alone, whether records in `table` can have
184	/// incoming reference keys — used by the DELETE purge path to skip the
185	/// per-record reference scan when they provably cannot.
186	///
187	/// The matched shapes mirror exactly the kinds accepted for reference
188	/// fields by [`crate::sql::statements::DefineFieldStatement::validate_reference_options`]:
189	/// `record<..>`, `array`/`set` of those, `option<..>` (an `Either`
190	/// containing `None`), and literal arrays of those. An untyped `record`
191	/// (`Kind::Record` with an empty table list) can hold a record of *any*
192	/// table, so it always matches. `Kind::Any` is rejected for reference
193	/// fields at definition time but is treated as a match here defensively —
194	/// erring towards running the scan is always safe.
195	pub fn reference_can_target(&self, table: &TableName) -> bool {
196		match self {
197			Kind::Record(tables) => tables.is_empty() || tables.iter().any(|t| t == table),
198			Kind::Array(inner, _) | Kind::Set(inner, _) => inner.reference_can_target(table),
199			Kind::Either(kinds) => kinds.iter().any(|k| k.reference_can_target(table)),
200			Kind::Literal(KindLiteral::Array(kinds)) => {
201				kinds.iter().any(|k| k.reference_can_target(table))
202			}
203			Kind::Any => true,
204			_ => false,
205		}
206	}
207
208	/// Collects the concrete record tables a `REFERENCE` field of this kind can
209	/// target into `out`, returning `true` if the kind can target *any* table
210	/// (an untyped `record` / `record<>`, or — defensively — `any`), in which
211	/// case `out` is not exhaustive and every table must be treated as
212	/// potentially referenced.
213	///
214	/// This is the enumerating dual of [`Self::reference_can_target`]: it lets a
215	/// caller build a database-wide set of referenceable tables once (e.g. the
216	/// memoized DELETE purge gate) rather than re-deciding per table. The
217	/// matched shapes are identical to that method's.
218	pub fn collect_reference_target_tables(&self, out: &mut HashSet<TableName>) -> bool {
219		match self {
220			Kind::Record(tables) => {
221				if tables.is_empty() {
222					return true;
223				}
224				out.extend(tables.iter().cloned());
225				false
226			}
227			Kind::Array(inner, _) | Kind::Set(inner, _) => {
228				inner.collect_reference_target_tables(out)
229			}
230			Kind::Either(kinds) => {
231				let mut unbounded = false;
232				for k in kinds {
233					unbounded |= k.collect_reference_target_tables(out);
234				}
235				unbounded
236			}
237			Kind::Literal(KindLiteral::Array(kinds)) => {
238				let mut unbounded = false;
239				for k in kinds {
240					unbounded |= k.collect_reference_target_tables(out);
241				}
242				unbounded
243			}
244			Kind::Any => true,
245			_ => false,
246		}
247	}
248
249	/// Returns true if this type is optional
250	pub fn can_be_none(&self) -> bool {
251		match self {
252			Kind::None | Kind::Any => true,
253			Kind::Either(x) => x.iter().any(|x| x.can_be_none()),
254			_ => false,
255		}
256	}
257
258	/// Returns true if this type is a literal, or contains a literal
259	pub fn contains_literal(&self) -> bool {
260		match self {
261			Kind::Literal(_) => true,
262			Kind::Either(x) => x.iter().any(|x| x.contains_literal()),
263			_ => false,
264		}
265	}
266
267	/// Returns true if this type permits access to named sub-fields.
268	///
269	/// This covers `object`, `any`, literal object types (e.g. `{ key: string }`),
270	/// and union types where every variant other than `none` and `null` permits
271	/// sub-field access and at least one variant does.
272	pub fn allows_sub_fields(&self) -> bool {
273		match self {
274			Kind::Any | Kind::Object | Kind::Array(..) | Kind::Set(..) => true,
275			Kind::Literal(KindLiteral::Object(_) | KindLiteral::Array(_)) => true,
276			Kind::Either(kinds) => {
277				kinds.iter().any(|k| k.allows_sub_fields())
278					&& kinds
279						.iter()
280						.all(|k| matches!(k, Kind::None | Kind::Null) || k.allows_sub_fields())
281			}
282			_ => false,
283		}
284	}
285
286	// Return the kind of the contained value.
287	//
288	// For example: for `array<number>` or `set<number>` this returns `number`.
289	// For `array<number> | set<float>` this returns `number | float`.
290	pub fn inner_kind(&self) -> Option<Kind> {
291		match self {
292			Kind::Any
293			| Kind::None
294			| Kind::Null
295			| Kind::Bool
296			| Kind::Bytes
297			| Kind::Datetime
298			| Kind::Decimal
299			| Kind::Duration
300			| Kind::Float
301			| Kind::Int
302			| Kind::Number
303			| Kind::Object
304			| Kind::String
305			| Kind::Uuid
306			| Kind::Regex
307			| Kind::Table(_)
308			| Kind::Record(_)
309			| Kind::Geometry(_)
310			| Kind::Function(_, _)
311			| Kind::Range
312			| Kind::Literal(_)
313			| Kind::File(_) => None,
314			Kind::Array(x, _) | Kind::Set(x, _) => Some(x.as_ref().clone()),
315			Kind::Either(x) => {
316				// a either shouldn't be able to contain a either itself so recursing here
317				// should be fine.
318				let kinds: Vec<Kind> = x.iter().filter_map(Self::inner_kind).collect();
319				if kinds.is_empty() {
320					None
321				} else {
322					Some(Kind::Either(kinds))
323				}
324			}
325		}
326	}
327
328	pub fn allows_nested_kind(&self, path: &[Part], kind: &Kind) -> bool {
329		// ANY type won't cause a mismatch
330		if self.is_any() || kind.is_any() {
331			return true;
332		}
333
334		if !path.is_empty() {
335			match self {
336				Kind::Object => return matches!(path.first(), Some(Part::Field(_) | Part::All)),
337				Kind::Either(kinds) => {
338					return kinds
339						.iter()
340						.all(|k| matches!(k, Kind::None) || k.allows_nested_kind(path, kind));
341				}
342				Kind::Array(inner, len) | Kind::Set(inner, len) => {
343					return match path.first() {
344						Some(Part::All) => inner.allows_nested_kind(&path[1..], kind),
345						Some(Part::Value(Expr::Literal(Literal::Integer(i)))) => {
346							if let Some(len) = len
347								&& *i >= *len as i64
348							{
349								return false;
350							}
351
352							inner.allows_nested_kind(&path[1..], kind)
353						}
354						_ => false,
355					};
356				}
357				_ => (),
358			}
359		}
360
361		match self {
362			// Check if the two kinds match when we reach the end of the path
363			_ if path.is_empty() && self == kind => true,
364			// Check if the literal matches the kind
365			Kind::Literal(lit) => lit.allows_nested_kind(path, kind),
366			// Check if any of the kinds in the either match the kind
367			Kind::Either(kinds) => {
368				kinds.iter().all(|k| matches!(k, Kind::None) || k.allows_nested_kind(path, kind))
369			}
370			_ => false,
371		}
372	}
373
374	pub fn flatten(self) -> Vec<Kind> {
375		match self {
376			Kind::Either(x) => x.into_iter().flat_map(|k| k.flatten()).collect(),
377			_ => vec![self],
378		}
379	}
380
381	pub fn either(kinds: Vec<Kind>) -> Kind {
382		let mut seen = HashSet::new();
383		let mut kinds = kinds
384			.into_iter()
385			.flat_map(|k| k.flatten())
386			.filter(|k| seen.insert(k.clone()))
387			.collect::<Vec<_>>();
388		match kinds.len() {
389			0 => Kind::None,
390			1 => kinds.remove(0),
391			_ => Kind::Either(kinds),
392		}
393	}
394
395	pub fn option(kind: Kind) -> Kind {
396		Kind::either(vec![Kind::None, kind])
397	}
398}
399
400/// Trait for retrieving the `kind` equivalent of a rust type.
401///
402/// Returns the most general kind for a type.
403/// For example Number could be either number or float or int or decimal but the
404/// most general is number.
405///
406/// This trait is only implemented for types which can only be retrieve from
407pub trait HasKind {
408	fn kind() -> Kind;
409}
410
411impl<T: HasKind> HasKind for Option<T> {
412	fn kind() -> Kind {
413		Kind::option(T::kind())
414	}
415}
416
417impl<T: HasKind> HasKind for Vec<T> {
418	fn kind() -> Kind {
419		let kind = T::kind();
420		Kind::Array(Box::new(kind), None)
421	}
422}
423
424impl HasKind for Array {
425	fn kind() -> Kind {
426		Kind::Array(Box::new(Kind::Any), None)
427	}
428}
429
430impl HasKind for Set {
431	fn kind() -> Kind {
432		Kind::Set(Box::new(Kind::Any), None)
433	}
434}
435
436impl<T: HasKind, const SIZE: usize> HasKind for [T; SIZE] {
437	fn kind() -> Kind {
438		let kind = T::kind();
439		Kind::Array(Box::new(kind), Some(SIZE as u64))
440	}
441}
442
443impl HasKind for RecordId {
444	fn kind() -> Kind {
445		Kind::Record(Vec::new())
446	}
447}
448
449impl HasKind for Geometry {
450	fn kind() -> Kind {
451		Kind::Geometry(Vec::new())
452	}
453}
454
455impl HasKind for Closure {
456	fn kind() -> Kind {
457		// The inner values of function are currently completely unused.
458		Kind::Function(None, None)
459	}
460}
461
462impl HasKind for Regex {
463	fn kind() -> Kind {
464		Kind::Regex
465	}
466}
467
468impl HasKind for File {
469	fn kind() -> Kind {
470		Kind::File(Vec::new())
471	}
472}
473
474impl HasKind for TableName {
475	fn kind() -> Kind {
476		Kind::Table(Vec::new())
477	}
478}
479
480macro_rules! impl_basic_has_kind{
481	($($name:ident => $kind:ident),*$(,)?) => {
482		$(
483			impl HasKind for $name{
484				fn kind() -> Kind{
485					Kind::$kind
486				}
487			}
488		)*
489	}
490}
491
492impl_basic_has_kind! {
493	bool => Bool,
494
495	i64 => Int,
496	f64 => Float,
497	Decimal => Decimal,
498
499	String => String,
500	Bytes => Bytes,
501	Number => Number,
502	Datetime => Datetime,
503	Duration => Duration,
504	Uuid => Uuid,
505	Range => Range,
506}
507
508impl HasKind for crate::val::Object {
509	fn kind() -> Kind {
510		Kind::Object
511	}
512}
513
514macro_rules! impl_geometry_has_kind{
515	($($name:ty => $kind:expr),*$(,)?) => {
516		$(
517			impl HasKind for $name{
518				fn kind() -> Kind{
519					Kind::Geometry(vec![$kind])
520				}
521			}
522		)*
523	}
524}
525impl_geometry_has_kind! {
526	Point<f64> => GeometryKind::Point,
527	LineString<f64> => GeometryKind::Line,
528	MultiPoint<f64> => GeometryKind::MultiPoint,
529	Polygon<f64> => GeometryKind::Polygon,
530	MultiLineString<f64> => GeometryKind::MultiLine,
531	MultiPolygon<f64> => GeometryKind::MultiPolygon,
532}
533
534impl From<&Kind> for Box<Kind> {
535	#[inline]
536	fn from(v: &Kind) -> Self {
537		Box::new(v.clone())
538	}
539}
540
541impl ToSql for Kind {
542	fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
543		let kind: crate::sql::Kind = self.clone().into();
544		kind.fmt_sql(f, fmt);
545	}
546}
547
548impl Display for Kind {
549	fn fmt(&self, f: &mut Formatter) -> fmt::Result {
550		write!(f, "{}", self.to_sql())
551	}
552}
553
554impl InfoStructure for Kind {
555	fn structure(self) -> Value {
556		self.to_string().into()
557	}
558}
559
560impl From<crate::types::PublicKind> for Kind {
561	fn from(v: crate::types::PublicKind) -> Self {
562		match v {
563			crate::types::PublicKind::Any => Kind::Any,
564			crate::types::PublicKind::None => Kind::None,
565			crate::types::PublicKind::Null => Kind::Null,
566			crate::types::PublicKind::Bool => Kind::Bool,
567			crate::types::PublicKind::Bytes => Kind::Bytes,
568			crate::types::PublicKind::Datetime => Kind::Datetime,
569			crate::types::PublicKind::Decimal => Kind::Decimal,
570			crate::types::PublicKind::Duration => Kind::Duration,
571			crate::types::PublicKind::Float => Kind::Float,
572			crate::types::PublicKind::Int => Kind::Int,
573			crate::types::PublicKind::Number => Kind::Number,
574			crate::types::PublicKind::Object => Kind::Object,
575			crate::types::PublicKind::String => Kind::String,
576			crate::types::PublicKind::Uuid => Kind::Uuid,
577			crate::types::PublicKind::Regex => Kind::Regex,
578			crate::types::PublicKind::Range => Kind::Range,
579			crate::types::PublicKind::Table(table) => {
580				Kind::Table(table.into_iter().map(IntoTableName::into_table_name).collect())
581			}
582			crate::types::PublicKind::Record(tables) => {
583				Kind::Record(tables.into_iter().map(IntoTableName::into_table_name).collect())
584			}
585			crate::types::PublicKind::Geometry(kinds) => {
586				Kind::Geometry(kinds.into_iter().map(Into::into).collect())
587			}
588			crate::types::PublicKind::Either(kinds) => {
589				Kind::Either(kinds.into_iter().map(Kind::from).collect())
590			}
591			crate::types::PublicKind::Set(kind, size) => {
592				Kind::Set(Box::new(Kind::from(*kind)), size)
593			}
594			crate::types::PublicKind::Array(kind, size) => {
595				Kind::Array(Box::new(Kind::from(*kind)), size)
596			}
597			crate::types::PublicKind::Function(args, ret) => Kind::Function(
598				args.map(|a| a.into_iter().map(Kind::from).collect()),
599				ret.map(|r| Box::new(Kind::from(*r))),
600			),
601			crate::types::PublicKind::File(bucket) => Kind::File(bucket),
602			crate::types::PublicKind::Literal(lit) => Kind::Literal(lit.into()),
603		}
604	}
605}
606
607impl From<Kind> for crate::types::PublicKind {
608	fn from(v: Kind) -> Self {
609		match v {
610			Kind::Any => crate::types::PublicKind::Any,
611			Kind::None => crate::types::PublicKind::None,
612			Kind::Null => crate::types::PublicKind::Null,
613			Kind::Bool => crate::types::PublicKind::Bool,
614			Kind::Bytes => crate::types::PublicKind::Bytes,
615			Kind::Datetime => crate::types::PublicKind::Datetime,
616			Kind::Decimal => crate::types::PublicKind::Decimal,
617			Kind::Duration => crate::types::PublicKind::Duration,
618			Kind::Float => crate::types::PublicKind::Float,
619			Kind::Int => crate::types::PublicKind::Int,
620			Kind::Number => crate::types::PublicKind::Number,
621			Kind::Object => crate::types::PublicKind::Object,
622			Kind::String => crate::types::PublicKind::String,
623			Kind::Uuid => crate::types::PublicKind::Uuid,
624			Kind::Regex => crate::types::PublicKind::Regex,
625			Kind::Range => crate::types::PublicKind::Range,
626			Kind::Table(tables) => crate::types::PublicKind::Table(
627				tables.into_iter().map(IntoPublicTable::into_public_table).collect(),
628			),
629			Kind::Record(tables) => crate::types::PublicKind::Record(
630				tables.into_iter().map(IntoPublicTable::into_public_table).collect(),
631			),
632			Kind::Geometry(kinds) => {
633				crate::types::PublicKind::Geometry(kinds.into_iter().map(Into::into).collect())
634			}
635			Kind::Either(kinds) => {
636				crate::types::PublicKind::Either(kinds.into_iter().map(Into::into).collect())
637			}
638			Kind::Set(kind, size) => crate::types::PublicKind::Set(Box::new((*kind).into()), size),
639			Kind::Array(kind, size) => {
640				crate::types::PublicKind::Array(Box::new((*kind).into()), size)
641			}
642			Kind::Function(args, ret) => crate::types::PublicKind::Function(
643				args.map(|a| a.into_iter().map(Into::into).collect()),
644				ret.map(|r| Box::new((*r).into())),
645			),
646			Kind::File(bucket) => crate::types::PublicKind::File(bucket),
647			Kind::Literal(lit) => crate::types::PublicKind::Literal(lit.into()),
648		}
649	}
650}
651
652#[revisioned(revision = 1)]
653#[derive(Clone, Debug)]
654pub enum KindLiteral {
655	String(Strand),
656	Integer(i64),
657	Float(f64),
658	Decimal(Decimal),
659	Duration(Duration),
660	Array(Vec<Kind>),
661	Object(BTreeMap<Strand, Kind>),
662	// This variant is just for a performance optimization.
663	// Should probably be removed when we have a planner.
664	//DiscriminatedObject(String, Vec<BTreeMap<String, Kind>>),
665	Bool(bool),
666}
667
668impl From<crate::types::PublicKindLiteral> for KindLiteral {
669	fn from(v: crate::types::PublicKindLiteral) -> Self {
670		match v {
671			crate::types::PublicKindLiteral::String(s) => KindLiteral::String(s.into()),
672			crate::types::PublicKindLiteral::Integer(i) => KindLiteral::Integer(i),
673			crate::types::PublicKindLiteral::Float(f) => KindLiteral::Float(f),
674			crate::types::PublicKindLiteral::Decimal(d) => KindLiteral::Decimal(d),
675			crate::types::PublicKindLiteral::Duration(d) => {
676				KindLiteral::Duration(crate::val::Duration(*d))
677			}
678			crate::types::PublicKindLiteral::Array(kinds) => {
679				KindLiteral::Array(kinds.into_iter().map(Kind::from).collect())
680			}
681			crate::types::PublicKindLiteral::Object(obj) => KindLiteral::Object(
682				obj.into_iter().map(|(k, v)| (k.into(), Kind::from(v))).collect(),
683			),
684			crate::types::PublicKindLiteral::Bool(b) => KindLiteral::Bool(b),
685		}
686	}
687}
688
689impl From<KindLiteral> for crate::types::PublicKindLiteral {
690	fn from(v: KindLiteral) -> Self {
691		match v {
692			KindLiteral::String(s) => crate::types::PublicKindLiteral::String(s.into_string()),
693			KindLiteral::Integer(i) => crate::types::PublicKindLiteral::Integer(i),
694			KindLiteral::Float(f) => crate::types::PublicKindLiteral::Float(f),
695			KindLiteral::Decimal(d) => crate::types::PublicKindLiteral::Decimal(d),
696			KindLiteral::Duration(d) => {
697				crate::types::PublicKindLiteral::Duration(crate::types::PublicDuration::from(d.0))
698			}
699			KindLiteral::Array(kinds) => {
700				crate::types::PublicKindLiteral::Array(kinds.into_iter().map(Into::into).collect())
701			}
702			KindLiteral::Object(obj) => crate::types::PublicKindLiteral::Object(
703				obj.into_iter().map(|(k, v)| (k.into_string(), v.into())).collect(),
704			),
705			KindLiteral::Bool(b) => crate::types::PublicKindLiteral::Bool(b),
706		}
707	}
708}
709
710impl PartialEq for KindLiteral {
711	fn eq(&self, other: &Self) -> bool {
712		match self {
713			KindLiteral::String(strand) => {
714				if let KindLiteral::String(other) = other {
715					strand == other
716				} else {
717					false
718				}
719			}
720			KindLiteral::Integer(x) => {
721				if let KindLiteral::Integer(other) = other {
722					x == other
723				} else {
724					false
725				}
726			}
727			KindLiteral::Float(x) => {
728				if let KindLiteral::Float(other) = other {
729					x.to_bits() == other.to_bits()
730				} else {
731					false
732				}
733			}
734			KindLiteral::Decimal(decimal) => {
735				if let KindLiteral::Decimal(other) = other {
736					decimal == other
737				} else {
738					false
739				}
740			}
741			KindLiteral::Duration(duration) => {
742				if let KindLiteral::Duration(other) = other {
743					duration == other
744				} else {
745					false
746				}
747			}
748			KindLiteral::Array(kinds) => {
749				if let KindLiteral::Array(other) = other {
750					kinds == other
751				} else {
752					false
753				}
754			}
755			KindLiteral::Object(btree_map) => {
756				if let KindLiteral::Object(other) = other {
757					btree_map == other
758				} else {
759					false
760				}
761			}
762			/*
763			KindLiteral::DiscriminatedObject(a, b) => {
764				if let KindLiteral::DiscriminatedObject(c, d) = other {
765					a == c && b == d
766				} else {
767					false
768				}
769			}
770			*/
771			KindLiteral::Bool(a) => {
772				if let KindLiteral::Bool(b) = other {
773					a == b
774				} else {
775					false
776				}
777			}
778		}
779	}
780}
781impl Eq for KindLiteral {}
782impl Hash for KindLiteral {
783	fn hash<H: Hasher>(&self, state: &mut H) {
784		std::mem::discriminant(self).hash(state);
785		match self {
786			KindLiteral::String(strand) => strand.hash(state),
787			KindLiteral::Integer(x) => x.hash(state),
788			KindLiteral::Float(x) => x.to_bits().hash(state),
789			KindLiteral::Decimal(decimal) => decimal.hash(state),
790			KindLiteral::Duration(duration) => duration.hash(state),
791			KindLiteral::Array(kinds) => kinds.hash(state),
792			KindLiteral::Object(btree_map) => btree_map.hash(state),
793			/*
794			KindLiteral::DiscriminatedObject(a, b) => {
795				a.hash(state);
796				b.hash(state);
797			}
798			*/
799			KindLiteral::Bool(x) => x.hash(state),
800		}
801	}
802}
803
804impl KindLiteral {
805	pub fn to_kind(&self) -> Kind {
806		match self {
807			Self::String(_) => Kind::String,
808			Self::Integer(_) | Self::Float(_) | Self::Decimal(_) => Kind::Number,
809			Self::Duration(_) => Kind::Duration,
810			Self::Array(a) => {
811				if let Some(inner) = a.first()
812					&& a.iter().all(|x| x == inner)
813				{
814					return Kind::Array(Box::new(inner.to_owned()), Some(a.len() as u64));
815				}
816
817				Kind::Array(Box::new(Kind::Any), None)
818			}
819			Self::Object(_) => Kind::Object,
820			//Self::DiscriminatedObject(_, _) => Kind::Object,
821			Self::Bool(_) => Kind::Bool,
822		}
823	}
824
825	pub fn validate_value(&self, value: &Value) -> bool {
826		match self {
827			Self::String(v) => match value {
828				Value::String(s) => s == v,
829				_ => false,
830			},
831			Self::Integer(v) => match value {
832				Value::Number(n) => *n == Number::Int(*v),
833				_ => false,
834			},
835			Self::Float(v) => match value {
836				Value::Number(n) => *n == Number::Float(*v),
837				_ => false,
838			},
839			Self::Decimal(v) => match value {
840				Value::Number(n) => *n == Number::Decimal(*v),
841				_ => false,
842			},
843			Self::Duration(v) => match value {
844				Value::Duration(n) => n == v,
845				_ => false,
846			},
847			Self::Bool(v) => match value {
848				Value::Bool(b) => b == v,
849				_ => false,
850			},
851			Self::Array(a) => match value {
852				Value::Array(x) => {
853					if a.len() != x.len() {
854						return false;
855					}
856
857					for (i, inner) in a.iter().enumerate() {
858						if let Some(value) = x.get(i) {
859							if !value.can_coerce_to_kind(inner) {
860								return false;
861							}
862						} else {
863							return false;
864						}
865					}
866
867					true
868				}
869				_ => false,
870			},
871			Self::Object(lit) => match value {
872				Value::Object(val) => {
873					let mut lit_iter = lit.iter();
874					let mut val_iter = val.iter();
875
876					let mut lit_next = lit_iter.next();
877					let mut val_next = val_iter.next();
878
879					while lit_next.is_some() || val_next.is_some() {
880						match (lit_next, val_next) {
881							(Some((lit_k, lit_kind)), Some((val_k, val_v))) => {
882								match lit_k.as_str().cmp(val_k.as_str()) {
883									Ordering::Less => {
884										// Key in type but not in value - check if optional
885										if !lit_kind.can_be_none() {
886											return false;
887										}
888										lit_next = lit_iter.next();
889									}
890									Ordering::Equal => {
891										// Key in both - validate type
892										if !val_v.can_coerce_to_kind(lit_kind) {
893											return false;
894										}
895										lit_next = lit_iter.next();
896										val_next = val_iter.next();
897									}
898									Ordering::Greater => {
899										// Key in value but not in type - extra key!
900										return false;
901									}
902								}
903							}
904							(Some((_, lit_kind)), None) => {
905								// Remaining keys in type but not in value - check if optional
906								if !lit_kind.can_be_none() {
907									return false;
908								}
909								lit_next = lit_iter.next();
910							}
911							(None, Some(_)) => {
912								// Remaining keys in value but not in type - extra keys!
913								return false;
914							}
915							(None, None) => break,
916						}
917					}
918
919					true
920				}
921				_ => false,
922			},
923			/*
924			Self::DiscriminatedObject(key, discriminants) => match value {
925				Value::Object(x) => {
926					let Some(value) = x.get(key) else {
927						return false;
928					};
929					if let Some(o) =
930						discriminants.iter().find(|o| value.can_coerce_to_kind(&o[key]))
931					{
932						if o.len() < x.len() {
933							return false;
934						}
935
936						for (k, v) in o.iter() {
937							if let Some(value) = x.get(k) {
938								if !value.can_coerce_to_kind(v) {
939									return false;
940								}
941							} else if !v.can_be_none() {
942								return false;
943							}
944						}
945
946						true
947					} else {
948						false
949					}
950				}
951				_ => false,
952			},
953			*/
954		}
955	}
956
957	pub fn allows_nested_kind(&self, path: &[Part], kind: &Kind) -> bool {
958		// ANY type won't cause a mismatch
959		if kind.is_any() {
960			return true;
961		}
962
963		// We reached the end of the path
964		// Check if the literal is equal to the kind
965		if path.is_empty() {
966			return match kind {
967				Kind::Literal(lit) => self == lit,
968				_ => &self.to_kind() == kind,
969			};
970		}
971
972		match self {
973			KindLiteral::Array(x) => match path.first() {
974				Some(Part::All) => x.iter().all(|y| y.allows_nested_kind(&path[1..], kind)),
975				Some(part) => part
976					.as_old_index()
977					.and_then(|idx| x.get(idx))
978					.map(|x| x.allows_nested_kind(&path[1..], kind))
979					.unwrap_or(false),
980				None => false,
981			},
982			KindLiteral::Object(x) => match path.first() {
983				Some(Part::All) => x.iter().all(|(_, y)| y.allows_nested_kind(&path[1..], kind)),
984				Some(Part::Field(k)) => {
985					if let Some(y) = x.get(k.as_str()) {
986						y.allows_nested_kind(&path[1..], kind)
987					} else {
988						false
989					}
990				}
991				_ => false,
992			},
993			/*
994			KindLiteral::DiscriminatedObject(_, discriminants) => match path.first() {
995				Some(Part::All) => discriminants
996					.iter()
997					.all(|o| o.iter().all(|(_, y)| y.allows_nested_kind(&path[1..], kind))),
998				Some(Part::Field(k)) => discriminants.iter().all(|o| {
999					if let Some(y) = o.get(&**k) {
1000						y.allows_nested_kind(&path[1..], kind)
1001					} else {
1002						false
1003					}
1004				}),
1005				_ => false,
1006			},
1007			*/
1008			_ => false,
1009		}
1010	}
1011}
1012
1013impl ToSql for KindLiteral {
1014	fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
1015		let lit: crate::sql::kind::KindLiteral = self.clone().into();
1016		lit.fmt_sql(f, fmt)
1017	}
1018}
1019
1020#[cfg(test)]
1021mod reference_target_tests {
1022	use super::*;
1023
1024	fn tb(name: &str) -> TableName {
1025		name.into()
1026	}
1027
1028	#[test]
1029	fn typed_record_targets_only_its_table() {
1030		let k = Kind::Record(vec![tb("person")]);
1031		assert!(k.reference_can_target(&tb("person")));
1032		assert!(!k.reference_can_target(&tb("comment")));
1033	}
1034
1035	#[test]
1036	fn untyped_record_targets_any_table() {
1037		// `record` / `record<>` has no table constraint, so it can hold a
1038		// record of any table and must never let the scan be skipped.
1039		let k = Kind::Record(Vec::new());
1040		assert!(k.reference_can_target(&tb("person")));
1041		assert!(k.reference_can_target(&tb("anything")));
1042	}
1043
1044	#[test]
1045	fn multi_table_record_targets_each() {
1046		let k = Kind::Record(vec![tb("person"), tb("robot")]);
1047		assert!(k.reference_can_target(&tb("person")));
1048		assert!(k.reference_can_target(&tb("robot")));
1049		assert!(!k.reference_can_target(&tb("comment")));
1050	}
1051
1052	#[test]
1053	fn array_and_set_of_records() {
1054		let arr = Kind::Array(Box::new(Kind::Record(vec![tb("house")])), None);
1055		assert!(arr.reference_can_target(&tb("house")));
1056		assert!(!arr.reference_can_target(&tb("person")));
1057
1058		let set = Kind::Set(Box::new(Kind::Record(vec![tb("house")])), None);
1059		assert!(set.reference_can_target(&tb("house")));
1060		assert!(!set.reference_can_target(&tb("person")));
1061	}
1062
1063	#[test]
1064	fn option_record_is_either_none_record() {
1065		// `option<record<person>>` is represented as `Either([None, Record([person])])`.
1066		let k = Kind::Either(vec![Kind::None, Kind::Record(vec![tb("person")])]);
1067		assert!(k.reference_can_target(&tb("person")));
1068		assert!(!k.reference_can_target(&tb("comment")));
1069	}
1070
1071	#[test]
1072	fn either_of_records_targets_each() {
1073		let k =
1074			Kind::Either(vec![Kind::Record(vec![tb("person")]), Kind::Record(vec![tb("robot")])]);
1075		assert!(k.reference_can_target(&tb("person")));
1076		assert!(k.reference_can_target(&tb("robot")));
1077		assert!(!k.reference_can_target(&tb("comment")));
1078	}
1079
1080	#[test]
1081	fn literal_array_of_records() {
1082		let k = Kind::Literal(KindLiteral::Array(vec![Kind::Record(vec![tb("a")])]));
1083		assert!(k.reference_can_target(&tb("a")));
1084		assert!(!k.reference_can_target(&tb("b")));
1085	}
1086
1087	#[test]
1088	fn nested_option_array_record() {
1089		// `option<array<record<house>>>` => `Either([None, Array(Record([house]))])`.
1090		let k = Kind::Either(vec![
1091			Kind::None,
1092			Kind::Array(Box::new(Kind::Record(vec![tb("house")])), None),
1093		]);
1094		assert!(k.reference_can_target(&tb("house")));
1095		assert!(!k.reference_can_target(&tb("person")));
1096	}
1097
1098	#[test]
1099	fn non_record_kinds_never_target() {
1100		assert!(!Kind::String.reference_can_target(&tb("person")));
1101		assert!(!Kind::Int.reference_can_target(&tb("person")));
1102		assert!(!Kind::None.reference_can_target(&tb("person")));
1103		assert!(!Kind::Object.reference_can_target(&tb("person")));
1104	}
1105
1106	#[test]
1107	fn any_kind_matches_defensively() {
1108		// `Kind::Any` is rejected for reference fields at DEFINE time, but the
1109		// walker treats it as a match so the scan still runs if it ever occurs.
1110		assert!(Kind::Any.reference_can_target(&tb("person")));
1111	}
1112
1113	fn collect(kind: &Kind) -> (bool, HashSet<TableName>) {
1114		let mut out = HashSet::new();
1115		let unbounded = kind.collect_reference_target_tables(&mut out);
1116		(unbounded, out)
1117	}
1118
1119	#[test]
1120	fn collect_typed_record() {
1121		let (unbounded, tables) = collect(&Kind::Record(vec![tb("person")]));
1122		assert!(!unbounded);
1123		assert_eq!(tables, HashSet::from([tb("person")]));
1124	}
1125
1126	#[test]
1127	fn collect_untyped_record_is_unbounded() {
1128		let (unbounded, _) = collect(&Kind::Record(Vec::new()));
1129		assert!(unbounded);
1130	}
1131
1132	#[test]
1133	fn collect_either_union_and_containers() {
1134		// either<array<record<a>> | record<b>> => {a, b}
1135		let k = Kind::Either(vec![
1136			Kind::Array(Box::new(Kind::Record(vec![tb("a")])), None),
1137			Kind::Record(vec![tb("b")]),
1138		]);
1139		let (unbounded, tables) = collect(&k);
1140		assert!(!unbounded);
1141		assert_eq!(tables, HashSet::from([tb("a"), tb("b")]));
1142	}
1143
1144	#[test]
1145	fn collect_mixed_typed_and_untyped_is_unbounded() {
1146		// A union that includes an untyped record is unbounded, even though the
1147		// typed arm's table is still collected.
1148		let k = Kind::Either(vec![Kind::Record(vec![tb("a")]), Kind::Record(Vec::new())]);
1149		let (unbounded, tables) = collect(&k);
1150		assert!(unbounded);
1151		assert!(tables.contains(&tb("a")));
1152	}
1153
1154	#[test]
1155	fn collect_non_record_is_empty() {
1156		let (unbounded, tables) = collect(&Kind::String);
1157		assert!(!unbounded);
1158		assert!(tables.is_empty());
1159	}
1160}
1161
1162/// Exhaustive-over-variants render/parse round-trip corpus for `Kind`.
1163///
1164/// Catalog definitions store kinds as canonical SurrealQL text
1165/// (`Kind::to_sql()`) and re-parse via `syn::kind_for_definition()` on read.
1166/// Every variant (including recursive nestings) must survive that round trip
1167/// byte-for-byte in structure, or a stored `DEFINE FIELD ... TYPE ...` would
1168/// silently change meaning.
1169#[cfg(test)]
1170mod round_trip_tests {
1171	use rstest::rstest;
1172	use surrealdb_types::ToSql;
1173
1174	use super::*;
1175
1176	fn tb(name: &str) -> TableName {
1177		name.into()
1178	}
1179
1180	fn round_trip(k: &Kind) {
1181		let text = k.to_sql();
1182		let reparsed: Kind = crate::syn::kind_for_definition(&text)
1183			.unwrap_or_else(|e| panic!("failed to reparse kind text {text:?}: {e}"))
1184			.into();
1185		assert_eq!(&reparsed, k, "round trip mismatch via text {text:?}");
1186	}
1187
1188	#[rstest]
1189	#[case::any(Kind::Any)]
1190	#[case::none(Kind::None)]
1191	#[case::null(Kind::Null)]
1192	#[case::bool(Kind::Bool)]
1193	#[case::bytes(Kind::Bytes)]
1194	#[case::datetime(Kind::Datetime)]
1195	#[case::decimal(Kind::Decimal)]
1196	#[case::duration(Kind::Duration)]
1197	#[case::float(Kind::Float)]
1198	#[case::int(Kind::Int)]
1199	#[case::number(Kind::Number)]
1200	#[case::object(Kind::Object)]
1201	#[case::string(Kind::String)]
1202	#[case::uuid(Kind::Uuid)]
1203	#[case::regex(Kind::Regex)]
1204	#[case::range(Kind::Range)]
1205	#[case::table_untyped(Kind::Table(vec![]))]
1206	#[case::table_typed(Kind::Table(vec![tb("person"), tb("robot")]))]
1207	#[case::record_untyped(Kind::Record(vec![]))]
1208	#[case::record_typed(Kind::Record(vec![tb("person")]))]
1209	#[case::geometry_untyped(Kind::Geometry(vec![]))]
1210	#[case::geometry_typed(Kind::Geometry(vec![GeometryKind::Point, GeometryKind::MultiPolygon]))]
1211	#[case::either(Kind::Either(vec![Kind::String, Kind::Int, Kind::None]))]
1212	#[case::option_record(Kind::Either(vec![Kind::None, Kind::Record(vec![tb("person")])]))]
1213	#[case::set_unbounded(Kind::Set(Box::new(Kind::String), None))]
1214	#[case::set_bounded(Kind::Set(Box::new(Kind::Int), Some(10)))]
1215	#[case::array_unbounded(Kind::Array(Box::new(Kind::Any), None))]
1216	#[case::array_bounded(Kind::Array(Box::new(Kind::Record(vec![tb("a")])), Some(3)))]
1217	#[case::nested_array(Kind::Array(Box::new(Kind::Array(Box::new(Kind::Int), None)), None))]
1218	// `Kind::Function`'s argument/return payload has no surface grammar (the
1219	// parser only ever produces `Function(None, None)` from the `function`
1220	// keyword, and `ToSql` renders every payload as just `"function"`) — this
1221	// predates the catalog text migration and is unrelated to it, so only the
1222	// parseable/renderable shape is covered here.
1223	#[case::function_untyped(Kind::Function(None, None))]
1224	#[case::file_untyped(Kind::File(vec![]))]
1225	#[case::file_typed(Kind::File(vec!["bucket1".to_string(), "bucket2".to_string()]))]
1226	#[case::literal_string(Kind::Literal(KindLiteral::String(Strand::new_static("a"))))]
1227	#[case::literal_integer(Kind::Literal(KindLiteral::Integer(42)))]
1228	#[case::literal_float(Kind::Literal(KindLiteral::Float(1.5)))]
1229	#[case::literal_decimal(Kind::Literal(KindLiteral::Decimal(Decimal::new(125, 2))))]
1230	#[case::literal_duration(Kind::Literal(KindLiteral::Duration(Duration(
1231		std::time::Duration::from_secs(3600)
1232	))))]
1233	#[case::literal_bool(Kind::Literal(KindLiteral::Bool(true)))]
1234	#[case::literal_array(Kind::Literal(KindLiteral::Array(vec![Kind::String, Kind::Int])))]
1235	#[case::literal_object(Kind::Literal(KindLiteral::Object(BTreeMap::from([(
1236		Strand::new_static("a"),
1237		Kind::String,
1238	)]))))]
1239	#[case::deeply_nested(Kind::Either(vec![
1240		Kind::Array(Box::new(Kind::Record(vec![tb("a"), tb("b")])), Some(5)),
1241		Kind::Set(Box::new(Kind::Literal(KindLiteral::Integer(1))), None),
1242		Kind::None,
1243	]))]
1244	fn kind_round_trips_through_text(#[case] k: Kind) {
1245		round_trip(&k);
1246	}
1247}