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 non-none variant permits sub-field access.
271	pub fn allows_sub_fields(&self) -> bool {
272		match self {
273			Kind::Any | Kind::Object | Kind::Array(..) | Kind::Set(..) => true,
274			Kind::Literal(KindLiteral::Object(_) | KindLiteral::Array(_)) => true,
275			Kind::Either(kinds) => {
276				kinds.iter().all(|k| matches!(k, Kind::None) || k.allows_sub_fields())
277			}
278			_ => false,
279		}
280	}
281
282	// Return the kind of the contained value.
283	//
284	// For example: for `array<number>` or `set<number>` this returns `number`.
285	// For `array<number> | set<float>` this returns `number | float`.
286	pub fn inner_kind(&self) -> Option<Kind> {
287		match self {
288			Kind::Any
289			| Kind::None
290			| Kind::Null
291			| Kind::Bool
292			| Kind::Bytes
293			| Kind::Datetime
294			| Kind::Decimal
295			| Kind::Duration
296			| Kind::Float
297			| Kind::Int
298			| Kind::Number
299			| Kind::Object
300			| Kind::String
301			| Kind::Uuid
302			| Kind::Regex
303			| Kind::Table(_)
304			| Kind::Record(_)
305			| Kind::Geometry(_)
306			| Kind::Function(_, _)
307			| Kind::Range
308			| Kind::Literal(_)
309			| Kind::File(_) => None,
310			Kind::Array(x, _) | Kind::Set(x, _) => Some(x.as_ref().clone()),
311			Kind::Either(x) => {
312				// a either shouldn't be able to contain a either itself so recursing here
313				// should be fine.
314				let kinds: Vec<Kind> = x.iter().filter_map(Self::inner_kind).collect();
315				if kinds.is_empty() {
316					None
317				} else {
318					Some(Kind::Either(kinds))
319				}
320			}
321		}
322	}
323
324	pub fn allows_nested_kind(&self, path: &[Part], kind: &Kind) -> bool {
325		// ANY type won't cause a mismatch
326		if self.is_any() || kind.is_any() {
327			return true;
328		}
329
330		if !path.is_empty() {
331			match self {
332				Kind::Object => return matches!(path.first(), Some(Part::Field(_) | Part::All)),
333				Kind::Either(kinds) => {
334					return kinds
335						.iter()
336						.all(|k| matches!(k, Kind::None) || k.allows_nested_kind(path, kind));
337				}
338				Kind::Array(inner, len) | Kind::Set(inner, len) => {
339					return match path.first() {
340						Some(Part::All) => inner.allows_nested_kind(&path[1..], kind),
341						Some(Part::Value(Expr::Literal(Literal::Integer(i)))) => {
342							if let Some(len) = len
343								&& *i >= *len as i64
344							{
345								return false;
346							}
347
348							inner.allows_nested_kind(&path[1..], kind)
349						}
350						_ => false,
351					};
352				}
353				_ => (),
354			}
355		}
356
357		match self {
358			// Check if the two kinds match when we reach the end of the path
359			_ if path.is_empty() && self == kind => true,
360			// Check if the literal matches the kind
361			Kind::Literal(lit) => lit.allows_nested_kind(path, kind),
362			// Check if any of the kinds in the either match the kind
363			Kind::Either(kinds) => {
364				kinds.iter().all(|k| matches!(k, Kind::None) || k.allows_nested_kind(path, kind))
365			}
366			_ => false,
367		}
368	}
369
370	pub fn flatten(self) -> Vec<Kind> {
371		match self {
372			Kind::Either(x) => x.into_iter().flat_map(|k| k.flatten()).collect(),
373			_ => vec![self],
374		}
375	}
376
377	pub fn either(kinds: Vec<Kind>) -> Kind {
378		let mut seen = HashSet::new();
379		let mut kinds = kinds
380			.into_iter()
381			.flat_map(|k| k.flatten())
382			.filter(|k| seen.insert(k.clone()))
383			.collect::<Vec<_>>();
384		match kinds.len() {
385			0 => Kind::None,
386			1 => kinds.remove(0),
387			_ => Kind::Either(kinds),
388		}
389	}
390
391	pub fn option(kind: Kind) -> Kind {
392		Kind::either(vec![Kind::None, kind])
393	}
394}
395
396/// Trait for retrieving the `kind` equivalent of a rust type.
397///
398/// Returns the most general kind for a type.
399/// For example Number could be either number or float or int or decimal but the
400/// most general is number.
401///
402/// This trait is only implemented for types which can only be retrieve from
403pub trait HasKind {
404	fn kind() -> Kind;
405}
406
407impl<T: HasKind> HasKind for Option<T> {
408	fn kind() -> Kind {
409		Kind::option(T::kind())
410	}
411}
412
413impl<T: HasKind> HasKind for Vec<T> {
414	fn kind() -> Kind {
415		let kind = T::kind();
416		Kind::Array(Box::new(kind), None)
417	}
418}
419
420impl HasKind for Array {
421	fn kind() -> Kind {
422		Kind::Array(Box::new(Kind::Any), None)
423	}
424}
425
426impl HasKind for Set {
427	fn kind() -> Kind {
428		Kind::Set(Box::new(Kind::Any), None)
429	}
430}
431
432impl<T: HasKind, const SIZE: usize> HasKind for [T; SIZE] {
433	fn kind() -> Kind {
434		let kind = T::kind();
435		Kind::Array(Box::new(kind), Some(SIZE as u64))
436	}
437}
438
439impl HasKind for RecordId {
440	fn kind() -> Kind {
441		Kind::Record(Vec::new())
442	}
443}
444
445impl HasKind for Geometry {
446	fn kind() -> Kind {
447		Kind::Geometry(Vec::new())
448	}
449}
450
451impl HasKind for Closure {
452	fn kind() -> Kind {
453		// The inner values of function are currently completely unused.
454		Kind::Function(None, None)
455	}
456}
457
458impl HasKind for Regex {
459	fn kind() -> Kind {
460		Kind::Regex
461	}
462}
463
464impl HasKind for File {
465	fn kind() -> Kind {
466		Kind::File(Vec::new())
467	}
468}
469
470impl HasKind for TableName {
471	fn kind() -> Kind {
472		Kind::Table(Vec::new())
473	}
474}
475
476macro_rules! impl_basic_has_kind{
477	($($name:ident => $kind:ident),*$(,)?) => {
478		$(
479			impl HasKind for $name{
480				fn kind() -> Kind{
481					Kind::$kind
482				}
483			}
484		)*
485	}
486}
487
488impl_basic_has_kind! {
489	bool => Bool,
490
491	i64 => Int,
492	f64 => Float,
493	Decimal => Decimal,
494
495	String => String,
496	Bytes => Bytes,
497	Number => Number,
498	Datetime => Datetime,
499	Duration => Duration,
500	Uuid => Uuid,
501	Range => Range,
502}
503
504impl HasKind for crate::val::Object {
505	fn kind() -> Kind {
506		Kind::Object
507	}
508}
509
510macro_rules! impl_geometry_has_kind{
511	($($name:ty => $kind:expr),*$(,)?) => {
512		$(
513			impl HasKind for $name{
514				fn kind() -> Kind{
515					Kind::Geometry(vec![$kind])
516				}
517			}
518		)*
519	}
520}
521impl_geometry_has_kind! {
522	Point<f64> => GeometryKind::Point,
523	LineString<f64> => GeometryKind::Line,
524	MultiPoint<f64> => GeometryKind::MultiPoint,
525	Polygon<f64> => GeometryKind::Polygon,
526	MultiLineString<f64> => GeometryKind::MultiLine,
527	MultiPolygon<f64> => GeometryKind::MultiPolygon,
528}
529
530impl From<&Kind> for Box<Kind> {
531	#[inline]
532	fn from(v: &Kind) -> Self {
533		Box::new(v.clone())
534	}
535}
536
537impl ToSql for Kind {
538	fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
539		let kind: crate::sql::Kind = self.clone().into();
540		kind.fmt_sql(f, fmt);
541	}
542}
543
544impl Display for Kind {
545	fn fmt(&self, f: &mut Formatter) -> fmt::Result {
546		write!(f, "{}", self.to_sql())
547	}
548}
549
550impl InfoStructure for Kind {
551	fn structure(self) -> Value {
552		self.to_string().into()
553	}
554}
555
556impl From<crate::types::PublicKind> for Kind {
557	fn from(v: crate::types::PublicKind) -> Self {
558		match v {
559			crate::types::PublicKind::Any => Kind::Any,
560			crate::types::PublicKind::None => Kind::None,
561			crate::types::PublicKind::Null => Kind::Null,
562			crate::types::PublicKind::Bool => Kind::Bool,
563			crate::types::PublicKind::Bytes => Kind::Bytes,
564			crate::types::PublicKind::Datetime => Kind::Datetime,
565			crate::types::PublicKind::Decimal => Kind::Decimal,
566			crate::types::PublicKind::Duration => Kind::Duration,
567			crate::types::PublicKind::Float => Kind::Float,
568			crate::types::PublicKind::Int => Kind::Int,
569			crate::types::PublicKind::Number => Kind::Number,
570			crate::types::PublicKind::Object => Kind::Object,
571			crate::types::PublicKind::String => Kind::String,
572			crate::types::PublicKind::Uuid => Kind::Uuid,
573			crate::types::PublicKind::Regex => Kind::Regex,
574			crate::types::PublicKind::Range => Kind::Range,
575			crate::types::PublicKind::Table(table) => {
576				Kind::Table(table.into_iter().map(IntoTableName::into_table_name).collect())
577			}
578			crate::types::PublicKind::Record(tables) => {
579				Kind::Record(tables.into_iter().map(IntoTableName::into_table_name).collect())
580			}
581			crate::types::PublicKind::Geometry(kinds) => {
582				Kind::Geometry(kinds.into_iter().map(Into::into).collect())
583			}
584			crate::types::PublicKind::Either(kinds) => {
585				Kind::Either(kinds.into_iter().map(Kind::from).collect())
586			}
587			crate::types::PublicKind::Set(kind, size) => {
588				Kind::Set(Box::new(Kind::from(*kind)), size)
589			}
590			crate::types::PublicKind::Array(kind, size) => {
591				Kind::Array(Box::new(Kind::from(*kind)), size)
592			}
593			crate::types::PublicKind::Function(args, ret) => Kind::Function(
594				args.map(|a| a.into_iter().map(Kind::from).collect()),
595				ret.map(|r| Box::new(Kind::from(*r))),
596			),
597			crate::types::PublicKind::File(bucket) => Kind::File(bucket),
598			crate::types::PublicKind::Literal(lit) => Kind::Literal(lit.into()),
599		}
600	}
601}
602
603impl From<Kind> for crate::types::PublicKind {
604	fn from(v: Kind) -> Self {
605		match v {
606			Kind::Any => crate::types::PublicKind::Any,
607			Kind::None => crate::types::PublicKind::None,
608			Kind::Null => crate::types::PublicKind::Null,
609			Kind::Bool => crate::types::PublicKind::Bool,
610			Kind::Bytes => crate::types::PublicKind::Bytes,
611			Kind::Datetime => crate::types::PublicKind::Datetime,
612			Kind::Decimal => crate::types::PublicKind::Decimal,
613			Kind::Duration => crate::types::PublicKind::Duration,
614			Kind::Float => crate::types::PublicKind::Float,
615			Kind::Int => crate::types::PublicKind::Int,
616			Kind::Number => crate::types::PublicKind::Number,
617			Kind::Object => crate::types::PublicKind::Object,
618			Kind::String => crate::types::PublicKind::String,
619			Kind::Uuid => crate::types::PublicKind::Uuid,
620			Kind::Regex => crate::types::PublicKind::Regex,
621			Kind::Range => crate::types::PublicKind::Range,
622			Kind::Table(tables) => crate::types::PublicKind::Table(
623				tables.into_iter().map(IntoPublicTable::into_public_table).collect(),
624			),
625			Kind::Record(tables) => crate::types::PublicKind::Record(
626				tables.into_iter().map(IntoPublicTable::into_public_table).collect(),
627			),
628			Kind::Geometry(kinds) => {
629				crate::types::PublicKind::Geometry(kinds.into_iter().map(Into::into).collect())
630			}
631			Kind::Either(kinds) => {
632				crate::types::PublicKind::Either(kinds.into_iter().map(Into::into).collect())
633			}
634			Kind::Set(kind, size) => crate::types::PublicKind::Set(Box::new((*kind).into()), size),
635			Kind::Array(kind, size) => {
636				crate::types::PublicKind::Array(Box::new((*kind).into()), size)
637			}
638			Kind::Function(args, ret) => crate::types::PublicKind::Function(
639				args.map(|a| a.into_iter().map(Into::into).collect()),
640				ret.map(|r| Box::new((*r).into())),
641			),
642			Kind::File(bucket) => crate::types::PublicKind::File(bucket),
643			Kind::Literal(lit) => crate::types::PublicKind::Literal(lit.into()),
644		}
645	}
646}
647
648#[revisioned(revision = 1)]
649#[derive(Clone, Debug)]
650pub enum KindLiteral {
651	String(Strand),
652	Integer(i64),
653	Float(f64),
654	Decimal(Decimal),
655	Duration(Duration),
656	Array(Vec<Kind>),
657	Object(BTreeMap<Strand, Kind>),
658	// This variant is just for a performance optimization.
659	// Should probably be removed when we have a planner.
660	//DiscriminatedObject(String, Vec<BTreeMap<String, Kind>>),
661	Bool(bool),
662}
663
664impl From<crate::types::PublicKindLiteral> for KindLiteral {
665	fn from(v: crate::types::PublicKindLiteral) -> Self {
666		match v {
667			crate::types::PublicKindLiteral::String(s) => KindLiteral::String(s.into()),
668			crate::types::PublicKindLiteral::Integer(i) => KindLiteral::Integer(i),
669			crate::types::PublicKindLiteral::Float(f) => KindLiteral::Float(f),
670			crate::types::PublicKindLiteral::Decimal(d) => KindLiteral::Decimal(d),
671			crate::types::PublicKindLiteral::Duration(d) => {
672				KindLiteral::Duration(crate::val::Duration(*d))
673			}
674			crate::types::PublicKindLiteral::Array(kinds) => {
675				KindLiteral::Array(kinds.into_iter().map(Kind::from).collect())
676			}
677			crate::types::PublicKindLiteral::Object(obj) => KindLiteral::Object(
678				obj.into_iter().map(|(k, v)| (k.into(), Kind::from(v))).collect(),
679			),
680			crate::types::PublicKindLiteral::Bool(b) => KindLiteral::Bool(b),
681		}
682	}
683}
684
685impl From<KindLiteral> for crate::types::PublicKindLiteral {
686	fn from(v: KindLiteral) -> Self {
687		match v {
688			KindLiteral::String(s) => crate::types::PublicKindLiteral::String(s.into_string()),
689			KindLiteral::Integer(i) => crate::types::PublicKindLiteral::Integer(i),
690			KindLiteral::Float(f) => crate::types::PublicKindLiteral::Float(f),
691			KindLiteral::Decimal(d) => crate::types::PublicKindLiteral::Decimal(d),
692			KindLiteral::Duration(d) => {
693				crate::types::PublicKindLiteral::Duration(crate::types::PublicDuration::from(d.0))
694			}
695			KindLiteral::Array(kinds) => {
696				crate::types::PublicKindLiteral::Array(kinds.into_iter().map(Into::into).collect())
697			}
698			KindLiteral::Object(obj) => crate::types::PublicKindLiteral::Object(
699				obj.into_iter().map(|(k, v)| (k.into_string(), v.into())).collect(),
700			),
701			KindLiteral::Bool(b) => crate::types::PublicKindLiteral::Bool(b),
702		}
703	}
704}
705
706impl PartialEq for KindLiteral {
707	fn eq(&self, other: &Self) -> bool {
708		match self {
709			KindLiteral::String(strand) => {
710				if let KindLiteral::String(other) = other {
711					strand == other
712				} else {
713					false
714				}
715			}
716			KindLiteral::Integer(x) => {
717				if let KindLiteral::Integer(other) = other {
718					x == other
719				} else {
720					false
721				}
722			}
723			KindLiteral::Float(x) => {
724				if let KindLiteral::Float(other) = other {
725					x.to_bits() == other.to_bits()
726				} else {
727					false
728				}
729			}
730			KindLiteral::Decimal(decimal) => {
731				if let KindLiteral::Decimal(other) = other {
732					decimal == other
733				} else {
734					false
735				}
736			}
737			KindLiteral::Duration(duration) => {
738				if let KindLiteral::Duration(other) = other {
739					duration == other
740				} else {
741					false
742				}
743			}
744			KindLiteral::Array(kinds) => {
745				if let KindLiteral::Array(other) = other {
746					kinds == other
747				} else {
748					false
749				}
750			}
751			KindLiteral::Object(btree_map) => {
752				if let KindLiteral::Object(other) = other {
753					btree_map == other
754				} else {
755					false
756				}
757			}
758			/*
759			KindLiteral::DiscriminatedObject(a, b) => {
760				if let KindLiteral::DiscriminatedObject(c, d) = other {
761					a == c && b == d
762				} else {
763					false
764				}
765			}
766			*/
767			KindLiteral::Bool(a) => {
768				if let KindLiteral::Bool(b) = other {
769					a == b
770				} else {
771					false
772				}
773			}
774		}
775	}
776}
777impl Eq for KindLiteral {}
778impl Hash for KindLiteral {
779	fn hash<H: Hasher>(&self, state: &mut H) {
780		std::mem::discriminant(self).hash(state);
781		match self {
782			KindLiteral::String(strand) => strand.hash(state),
783			KindLiteral::Integer(x) => x.hash(state),
784			KindLiteral::Float(x) => x.to_bits().hash(state),
785			KindLiteral::Decimal(decimal) => decimal.hash(state),
786			KindLiteral::Duration(duration) => duration.hash(state),
787			KindLiteral::Array(kinds) => kinds.hash(state),
788			KindLiteral::Object(btree_map) => btree_map.hash(state),
789			/*
790			KindLiteral::DiscriminatedObject(a, b) => {
791				a.hash(state);
792				b.hash(state);
793			}
794			*/
795			KindLiteral::Bool(x) => x.hash(state),
796		}
797	}
798}
799
800impl KindLiteral {
801	pub fn to_kind(&self) -> Kind {
802		match self {
803			Self::String(_) => Kind::String,
804			Self::Integer(_) | Self::Float(_) | Self::Decimal(_) => Kind::Number,
805			Self::Duration(_) => Kind::Duration,
806			Self::Array(a) => {
807				if let Some(inner) = a.first()
808					&& a.iter().all(|x| x == inner)
809				{
810					return Kind::Array(Box::new(inner.to_owned()), Some(a.len() as u64));
811				}
812
813				Kind::Array(Box::new(Kind::Any), None)
814			}
815			Self::Object(_) => Kind::Object,
816			//Self::DiscriminatedObject(_, _) => Kind::Object,
817			Self::Bool(_) => Kind::Bool,
818		}
819	}
820
821	pub fn validate_value(&self, value: &Value) -> bool {
822		match self {
823			Self::String(v) => match value {
824				Value::String(s) => s == v,
825				_ => false,
826			},
827			Self::Integer(v) => match value {
828				Value::Number(n) => *n == Number::Int(*v),
829				_ => false,
830			},
831			Self::Float(v) => match value {
832				Value::Number(n) => *n == Number::Float(*v),
833				_ => false,
834			},
835			Self::Decimal(v) => match value {
836				Value::Number(n) => *n == Number::Decimal(*v),
837				_ => false,
838			},
839			Self::Duration(v) => match value {
840				Value::Duration(n) => n == v,
841				_ => false,
842			},
843			Self::Bool(v) => match value {
844				Value::Bool(b) => b == v,
845				_ => false,
846			},
847			Self::Array(a) => match value {
848				Value::Array(x) => {
849					if a.len() != x.len() {
850						return false;
851					}
852
853					for (i, inner) in a.iter().enumerate() {
854						if let Some(value) = x.get(i) {
855							if !value.can_coerce_to_kind(inner) {
856								return false;
857							}
858						} else {
859							return false;
860						}
861					}
862
863					true
864				}
865				_ => false,
866			},
867			Self::Object(lit) => match value {
868				Value::Object(val) => {
869					let mut lit_iter = lit.iter();
870					let mut val_iter = val.iter();
871
872					let mut lit_next = lit_iter.next();
873					let mut val_next = val_iter.next();
874
875					while lit_next.is_some() || val_next.is_some() {
876						match (lit_next, val_next) {
877							(Some((lit_k, lit_kind)), Some((val_k, val_v))) => {
878								match lit_k.as_str().cmp(val_k.as_str()) {
879									Ordering::Less => {
880										// Key in type but not in value - check if optional
881										if !lit_kind.can_be_none() {
882											return false;
883										}
884										lit_next = lit_iter.next();
885									}
886									Ordering::Equal => {
887										// Key in both - validate type
888										if !val_v.can_coerce_to_kind(lit_kind) {
889											return false;
890										}
891										lit_next = lit_iter.next();
892										val_next = val_iter.next();
893									}
894									Ordering::Greater => {
895										// Key in value but not in type - extra key!
896										return false;
897									}
898								}
899							}
900							(Some((_, lit_kind)), None) => {
901								// Remaining keys in type but not in value - check if optional
902								if !lit_kind.can_be_none() {
903									return false;
904								}
905								lit_next = lit_iter.next();
906							}
907							(None, Some(_)) => {
908								// Remaining keys in value but not in type - extra keys!
909								return false;
910							}
911							(None, None) => break,
912						}
913					}
914
915					true
916				}
917				_ => false,
918			},
919			/*
920			Self::DiscriminatedObject(key, discriminants) => match value {
921				Value::Object(x) => {
922					let Some(value) = x.get(key) else {
923						return false;
924					};
925					if let Some(o) =
926						discriminants.iter().find(|o| value.can_coerce_to_kind(&o[key]))
927					{
928						if o.len() < x.len() {
929							return false;
930						}
931
932						for (k, v) in o.iter() {
933							if let Some(value) = x.get(k) {
934								if !value.can_coerce_to_kind(v) {
935									return false;
936								}
937							} else if !v.can_be_none() {
938								return false;
939							}
940						}
941
942						true
943					} else {
944						false
945					}
946				}
947				_ => false,
948			},
949			*/
950		}
951	}
952
953	pub fn allows_nested_kind(&self, path: &[Part], kind: &Kind) -> bool {
954		// ANY type won't cause a mismatch
955		if kind.is_any() {
956			return true;
957		}
958
959		// We reached the end of the path
960		// Check if the literal is equal to the kind
961		if path.is_empty() {
962			return match kind {
963				Kind::Literal(lit) => self == lit,
964				_ => &self.to_kind() == kind,
965			};
966		}
967
968		match self {
969			KindLiteral::Array(x) => match path.first() {
970				Some(Part::All) => x.iter().all(|y| y.allows_nested_kind(&path[1..], kind)),
971				Some(part) => part
972					.as_old_index()
973					.and_then(|idx| x.get(idx))
974					.map(|x| x.allows_nested_kind(&path[1..], kind))
975					.unwrap_or(false),
976				None => false,
977			},
978			KindLiteral::Object(x) => match path.first() {
979				Some(Part::All) => x.iter().all(|(_, y)| y.allows_nested_kind(&path[1..], kind)),
980				Some(Part::Field(k)) => {
981					if let Some(y) = x.get(k.as_str()) {
982						y.allows_nested_kind(&path[1..], kind)
983					} else {
984						false
985					}
986				}
987				_ => false,
988			},
989			/*
990			KindLiteral::DiscriminatedObject(_, discriminants) => match path.first() {
991				Some(Part::All) => discriminants
992					.iter()
993					.all(|o| o.iter().all(|(_, y)| y.allows_nested_kind(&path[1..], kind))),
994				Some(Part::Field(k)) => discriminants.iter().all(|o| {
995					if let Some(y) = o.get(&**k) {
996						y.allows_nested_kind(&path[1..], kind)
997					} else {
998						false
999					}
1000				}),
1001				_ => false,
1002			},
1003			*/
1004			_ => false,
1005		}
1006	}
1007}
1008
1009impl ToSql for KindLiteral {
1010	fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
1011		let lit: crate::sql::kind::KindLiteral = self.clone().into();
1012		lit.fmt_sql(f, fmt)
1013	}
1014}
1015
1016#[cfg(test)]
1017mod reference_target_tests {
1018	use super::*;
1019
1020	fn tb(name: &str) -> TableName {
1021		name.into()
1022	}
1023
1024	#[test]
1025	fn typed_record_targets_only_its_table() {
1026		let k = Kind::Record(vec![tb("person")]);
1027		assert!(k.reference_can_target(&tb("person")));
1028		assert!(!k.reference_can_target(&tb("comment")));
1029	}
1030
1031	#[test]
1032	fn untyped_record_targets_any_table() {
1033		// `record` / `record<>` has no table constraint, so it can hold a
1034		// record of any table and must never let the scan be skipped.
1035		let k = Kind::Record(Vec::new());
1036		assert!(k.reference_can_target(&tb("person")));
1037		assert!(k.reference_can_target(&tb("anything")));
1038	}
1039
1040	#[test]
1041	fn multi_table_record_targets_each() {
1042		let k = Kind::Record(vec![tb("person"), tb("robot")]);
1043		assert!(k.reference_can_target(&tb("person")));
1044		assert!(k.reference_can_target(&tb("robot")));
1045		assert!(!k.reference_can_target(&tb("comment")));
1046	}
1047
1048	#[test]
1049	fn array_and_set_of_records() {
1050		let arr = Kind::Array(Box::new(Kind::Record(vec![tb("house")])), None);
1051		assert!(arr.reference_can_target(&tb("house")));
1052		assert!(!arr.reference_can_target(&tb("person")));
1053
1054		let set = Kind::Set(Box::new(Kind::Record(vec![tb("house")])), None);
1055		assert!(set.reference_can_target(&tb("house")));
1056		assert!(!set.reference_can_target(&tb("person")));
1057	}
1058
1059	#[test]
1060	fn option_record_is_either_none_record() {
1061		// `option<record<person>>` is represented as `Either([None, Record([person])])`.
1062		let k = Kind::Either(vec![Kind::None, Kind::Record(vec![tb("person")])]);
1063		assert!(k.reference_can_target(&tb("person")));
1064		assert!(!k.reference_can_target(&tb("comment")));
1065	}
1066
1067	#[test]
1068	fn either_of_records_targets_each() {
1069		let k =
1070			Kind::Either(vec![Kind::Record(vec![tb("person")]), Kind::Record(vec![tb("robot")])]);
1071		assert!(k.reference_can_target(&tb("person")));
1072		assert!(k.reference_can_target(&tb("robot")));
1073		assert!(!k.reference_can_target(&tb("comment")));
1074	}
1075
1076	#[test]
1077	fn literal_array_of_records() {
1078		let k = Kind::Literal(KindLiteral::Array(vec![Kind::Record(vec![tb("a")])]));
1079		assert!(k.reference_can_target(&tb("a")));
1080		assert!(!k.reference_can_target(&tb("b")));
1081	}
1082
1083	#[test]
1084	fn nested_option_array_record() {
1085		// `option<array<record<house>>>` => `Either([None, Array(Record([house]))])`.
1086		let k = Kind::Either(vec![
1087			Kind::None,
1088			Kind::Array(Box::new(Kind::Record(vec![tb("house")])), None),
1089		]);
1090		assert!(k.reference_can_target(&tb("house")));
1091		assert!(!k.reference_can_target(&tb("person")));
1092	}
1093
1094	#[test]
1095	fn non_record_kinds_never_target() {
1096		assert!(!Kind::String.reference_can_target(&tb("person")));
1097		assert!(!Kind::Int.reference_can_target(&tb("person")));
1098		assert!(!Kind::None.reference_can_target(&tb("person")));
1099		assert!(!Kind::Object.reference_can_target(&tb("person")));
1100	}
1101
1102	#[test]
1103	fn any_kind_matches_defensively() {
1104		// `Kind::Any` is rejected for reference fields at DEFINE time, but the
1105		// walker treats it as a match so the scan still runs if it ever occurs.
1106		assert!(Kind::Any.reference_can_target(&tb("person")));
1107	}
1108
1109	fn collect(kind: &Kind) -> (bool, HashSet<TableName>) {
1110		let mut out = HashSet::new();
1111		let unbounded = kind.collect_reference_target_tables(&mut out);
1112		(unbounded, out)
1113	}
1114
1115	#[test]
1116	fn collect_typed_record() {
1117		let (unbounded, tables) = collect(&Kind::Record(vec![tb("person")]));
1118		assert!(!unbounded);
1119		assert_eq!(tables, HashSet::from([tb("person")]));
1120	}
1121
1122	#[test]
1123	fn collect_untyped_record_is_unbounded() {
1124		let (unbounded, _) = collect(&Kind::Record(Vec::new()));
1125		assert!(unbounded);
1126	}
1127
1128	#[test]
1129	fn collect_either_union_and_containers() {
1130		// either<array<record<a>> | record<b>> => {a, b}
1131		let k = Kind::Either(vec![
1132			Kind::Array(Box::new(Kind::Record(vec![tb("a")])), None),
1133			Kind::Record(vec![tb("b")]),
1134		]);
1135		let (unbounded, tables) = collect(&k);
1136		assert!(!unbounded);
1137		assert_eq!(tables, HashSet::from([tb("a"), tb("b")]));
1138	}
1139
1140	#[test]
1141	fn collect_mixed_typed_and_untyped_is_unbounded() {
1142		// A union that includes an untyped record is unbounded, even though the
1143		// typed arm's table is still collected.
1144		let k = Kind::Either(vec![Kind::Record(vec![tb("a")]), Kind::Record(Vec::new())]);
1145		let (unbounded, tables) = collect(&k);
1146		assert!(unbounded);
1147		assert!(tables.contains(&tb("a")));
1148	}
1149
1150	#[test]
1151	fn collect_non_record_is_empty() {
1152		let (unbounded, tables) = collect(&Kind::String);
1153		assert!(!unbounded);
1154		assert!(tables.is_empty());
1155	}
1156}
1157
1158/// Exhaustive-over-variants render/parse round-trip corpus for `Kind`.
1159///
1160/// Catalog definitions store kinds as canonical SurrealQL text
1161/// (`Kind::to_sql()`) and re-parse via `syn::kind_for_definition()` on read.
1162/// Every variant (including recursive nestings) must survive that round trip
1163/// byte-for-byte in structure, or a stored `DEFINE FIELD ... TYPE ...` would
1164/// silently change meaning.
1165#[cfg(test)]
1166mod round_trip_tests {
1167	use rstest::rstest;
1168	use surrealdb_types::ToSql;
1169
1170	use super::*;
1171
1172	fn tb(name: &str) -> TableName {
1173		name.into()
1174	}
1175
1176	fn round_trip(k: &Kind) {
1177		let text = k.to_sql();
1178		let reparsed: Kind = crate::syn::kind_for_definition(&text)
1179			.unwrap_or_else(|e| panic!("failed to reparse kind text {text:?}: {e}"))
1180			.into();
1181		assert_eq!(&reparsed, k, "round trip mismatch via text {text:?}");
1182	}
1183
1184	#[rstest]
1185	#[case::any(Kind::Any)]
1186	#[case::none(Kind::None)]
1187	#[case::null(Kind::Null)]
1188	#[case::bool(Kind::Bool)]
1189	#[case::bytes(Kind::Bytes)]
1190	#[case::datetime(Kind::Datetime)]
1191	#[case::decimal(Kind::Decimal)]
1192	#[case::duration(Kind::Duration)]
1193	#[case::float(Kind::Float)]
1194	#[case::int(Kind::Int)]
1195	#[case::number(Kind::Number)]
1196	#[case::object(Kind::Object)]
1197	#[case::string(Kind::String)]
1198	#[case::uuid(Kind::Uuid)]
1199	#[case::regex(Kind::Regex)]
1200	#[case::range(Kind::Range)]
1201	#[case::table_untyped(Kind::Table(vec![]))]
1202	#[case::table_typed(Kind::Table(vec![tb("person"), tb("robot")]))]
1203	#[case::record_untyped(Kind::Record(vec![]))]
1204	#[case::record_typed(Kind::Record(vec![tb("person")]))]
1205	#[case::geometry_untyped(Kind::Geometry(vec![]))]
1206	#[case::geometry_typed(Kind::Geometry(vec![GeometryKind::Point, GeometryKind::MultiPolygon]))]
1207	#[case::either(Kind::Either(vec![Kind::String, Kind::Int, Kind::None]))]
1208	#[case::option_record(Kind::Either(vec![Kind::None, Kind::Record(vec![tb("person")])]))]
1209	#[case::set_unbounded(Kind::Set(Box::new(Kind::String), None))]
1210	#[case::set_bounded(Kind::Set(Box::new(Kind::Int), Some(10)))]
1211	#[case::array_unbounded(Kind::Array(Box::new(Kind::Any), None))]
1212	#[case::array_bounded(Kind::Array(Box::new(Kind::Record(vec![tb("a")])), Some(3)))]
1213	#[case::nested_array(Kind::Array(Box::new(Kind::Array(Box::new(Kind::Int), None)), None))]
1214	// `Kind::Function`'s argument/return payload has no surface grammar (the
1215	// parser only ever produces `Function(None, None)` from the `function`
1216	// keyword, and `ToSql` renders every payload as just `"function"`) — this
1217	// predates the catalog text migration and is unrelated to it, so only the
1218	// parseable/renderable shape is covered here.
1219	#[case::function_untyped(Kind::Function(None, None))]
1220	#[case::file_untyped(Kind::File(vec![]))]
1221	#[case::file_typed(Kind::File(vec!["bucket1".to_string(), "bucket2".to_string()]))]
1222	#[case::literal_string(Kind::Literal(KindLiteral::String(Strand::new_static("a"))))]
1223	#[case::literal_integer(Kind::Literal(KindLiteral::Integer(42)))]
1224	#[case::literal_float(Kind::Literal(KindLiteral::Float(1.5)))]
1225	#[case::literal_decimal(Kind::Literal(KindLiteral::Decimal(Decimal::new(125, 2))))]
1226	#[case::literal_duration(Kind::Literal(KindLiteral::Duration(Duration(
1227		std::time::Duration::from_secs(3600)
1228	))))]
1229	#[case::literal_bool(Kind::Literal(KindLiteral::Bool(true)))]
1230	#[case::literal_array(Kind::Literal(KindLiteral::Array(vec![Kind::String, Kind::Int])))]
1231	#[case::literal_object(Kind::Literal(KindLiteral::Object(BTreeMap::from([(
1232		Strand::new_static("a"),
1233		Kind::String,
1234	)]))))]
1235	#[case::deeply_nested(Kind::Either(vec![
1236		Kind::Array(Box::new(Kind::Record(vec![tb("a"), tb("b")])), Some(5)),
1237		Kind::Set(Box::new(Kind::Literal(KindLiteral::Integer(1))), None),
1238		Kind::None,
1239	]))]
1240	fn kind_round_trips_through_text(#[case] k: Kind) {
1241		round_trip(&k);
1242	}
1243}