use std::collections::{BTreeMap, HashSet};
use std::fmt::Display;
use std::hash;
use rust_decimal::Decimal;
use surrealdb_strand::Strand;
use surrealdb_types::{SqlFormat, ToSql, write_sql};
use crate::fmt::{EscapeKwFreeIdent, EscapeObjectKey, Float, Fmt, QuoteStr};
use crate::types::PublicDuration;
use crate::val::TableName;
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub enum GeometryKind {
Point,
Line,
Polygon,
MultiPoint,
MultiLine,
MultiPolygon,
Collection,
}
impl ToSql for GeometryKind {
fn fmt_sql(&self, f: &mut String, _fmt: SqlFormat) {
match self {
GeometryKind::Point => f.push_str("point"),
GeometryKind::Line => f.push_str("line"),
GeometryKind::Polygon => f.push_str("polygon"),
GeometryKind::MultiPoint => f.push_str("multipoint"),
GeometryKind::MultiLine => f.push_str("multiline"),
GeometryKind::MultiPolygon => f.push_str("multipolygon"),
GeometryKind::Collection => f.push_str("collection"),
}
}
}
impl From<GeometryKind> for crate::expr::kind::GeometryKind {
fn from(v: GeometryKind) -> Self {
match v {
GeometryKind::Point => crate::expr::kind::GeometryKind::Point,
GeometryKind::Line => crate::expr::kind::GeometryKind::Line,
GeometryKind::Polygon => crate::expr::kind::GeometryKind::Polygon,
GeometryKind::MultiPoint => crate::expr::kind::GeometryKind::MultiPoint,
GeometryKind::MultiLine => crate::expr::kind::GeometryKind::MultiLine,
GeometryKind::MultiPolygon => crate::expr::kind::GeometryKind::MultiPolygon,
GeometryKind::Collection => crate::expr::kind::GeometryKind::Collection,
}
}
}
impl From<crate::expr::kind::GeometryKind> for GeometryKind {
fn from(v: crate::expr::kind::GeometryKind) -> Self {
match v {
crate::expr::kind::GeometryKind::Point => GeometryKind::Point,
crate::expr::kind::GeometryKind::Line => GeometryKind::Line,
crate::expr::kind::GeometryKind::Polygon => GeometryKind::Polygon,
crate::expr::kind::GeometryKind::MultiPoint => GeometryKind::MultiPoint,
crate::expr::kind::GeometryKind::MultiLine => GeometryKind::MultiLine,
crate::expr::kind::GeometryKind::MultiPolygon => GeometryKind::MultiPolygon,
crate::expr::kind::GeometryKind::Collection => GeometryKind::Collection,
}
}
}
impl From<GeometryKind> for crate::types::PublicGeometryKind {
fn from(v: GeometryKind) -> Self {
match v {
GeometryKind::Point => crate::types::PublicGeometryKind::Point,
GeometryKind::Line => crate::types::PublicGeometryKind::Line,
GeometryKind::Polygon => crate::types::PublicGeometryKind::Polygon,
GeometryKind::MultiPoint => crate::types::PublicGeometryKind::MultiPoint,
GeometryKind::MultiLine => crate::types::PublicGeometryKind::MultiLine,
GeometryKind::MultiPolygon => crate::types::PublicGeometryKind::MultiPolygon,
GeometryKind::Collection => crate::types::PublicGeometryKind::Collection,
}
}
}
impl From<crate::types::PublicGeometryKind> for GeometryKind {
fn from(v: crate::types::PublicGeometryKind) -> Self {
match v {
crate::types::PublicGeometryKind::Point => GeometryKind::Point,
crate::types::PublicGeometryKind::Line => GeometryKind::Line,
crate::types::PublicGeometryKind::Polygon => GeometryKind::Polygon,
crate::types::PublicGeometryKind::MultiPoint => GeometryKind::MultiPoint,
crate::types::PublicGeometryKind::MultiLine => GeometryKind::MultiLine,
crate::types::PublicGeometryKind::MultiPolygon => GeometryKind::MultiPolygon,
crate::types::PublicGeometryKind::Collection => GeometryKind::Collection,
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub enum Kind {
#[default]
Any,
None,
Null,
Bool,
Bytes,
Datetime,
Decimal,
Duration,
Float,
Int,
Number,
Object,
String,
Uuid,
Regex,
Table(Vec<TableName>),
Record(Vec<TableName>),
Geometry(Vec<GeometryKind>),
Either(
#[cfg_attr(feature = "arbitrary", arbitrary(with = crate::sql::arbitrary::either_kind))]
Vec<Kind>,
),
Set(Box<Kind>, Option<u64>),
Array(Box<Kind>, Option<u64>),
Function(Option<Vec<Kind>>, Option<Box<Kind>>),
Range,
Literal(KindLiteral),
File(Vec<String>),
}
impl Kind {
pub(crate) fn flatten(self) -> Vec<Kind> {
match self {
Kind::Either(x) => x.into_iter().flat_map(|k| k.flatten()).collect(),
_ => vec![self],
}
}
pub(crate) fn either(kinds: Vec<Kind>) -> Kind {
let mut seen = HashSet::new();
let mut kinds = kinds
.into_iter()
.flat_map(|k| k.flatten())
.filter(|k| seen.insert(k.clone()))
.collect::<Vec<_>>();
match kinds.len() {
0 => Kind::None,
1 => kinds.remove(0),
_ => Kind::Either(kinds),
}
}
}
impl From<Kind> for crate::expr::Kind {
fn from(v: Kind) -> Self {
match v {
Kind::Any => crate::expr::Kind::Any,
Kind::None => crate::expr::Kind::None,
Kind::Null => crate::expr::Kind::Null,
Kind::Bool => crate::expr::Kind::Bool,
Kind::Bytes => crate::expr::Kind::Bytes,
Kind::Datetime => crate::expr::Kind::Datetime,
Kind::Decimal => crate::expr::Kind::Decimal,
Kind::Duration => crate::expr::Kind::Duration,
Kind::Float => crate::expr::Kind::Float,
Kind::Int => crate::expr::Kind::Int,
Kind::Number => crate::expr::Kind::Number,
Kind::Object => crate::expr::Kind::Object,
Kind::String => crate::expr::Kind::String,
Kind::Uuid => crate::expr::Kind::Uuid,
Kind::Regex => crate::expr::Kind::Regex,
Kind::Table(tables) => crate::expr::Kind::Table(tables),
Kind::Record(tables) => crate::expr::Kind::Record(tables),
Kind::Geometry(geometries) => {
crate::expr::Kind::Geometry(geometries.into_iter().map(Into::into).collect())
}
Kind::Either(kinds) => {
crate::expr::Kind::Either(kinds.into_iter().map(Into::into).collect())
}
Kind::Set(k, l) => crate::expr::Kind::Set(Box::new(k.as_ref().clone().into()), l),
Kind::Array(k, l) => crate::expr::Kind::Array(Box::new(k.as_ref().clone().into()), l),
Kind::Function(args, ret) => crate::expr::Kind::Function(
args.map(|args| args.into_iter().map(Into::into).collect()),
ret.map(|ret| Box::new((*ret).into())),
),
Kind::Range => crate::expr::Kind::Range,
Kind::Literal(l) => crate::expr::Kind::Literal(l.into()),
Kind::File(k) => crate::expr::Kind::File(k),
}
}
}
impl From<crate::expr::Kind> for Kind {
fn from(v: crate::expr::Kind) -> Self {
match v {
crate::expr::Kind::Any => Kind::Any,
crate::expr::Kind::None => Kind::None,
crate::expr::Kind::Null => Kind::Null,
crate::expr::Kind::Bool => Kind::Bool,
crate::expr::Kind::Bytes => Kind::Bytes,
crate::expr::Kind::Datetime => Kind::Datetime,
crate::expr::Kind::Decimal => Kind::Decimal,
crate::expr::Kind::Duration => Kind::Duration,
crate::expr::Kind::Float => Kind::Float,
crate::expr::Kind::Int => Kind::Int,
crate::expr::Kind::Number => Kind::Number,
crate::expr::Kind::Object => Kind::Object,
crate::expr::Kind::String => Kind::String,
crate::expr::Kind::Uuid => Kind::Uuid,
crate::expr::Kind::Regex => Kind::Regex,
crate::expr::Kind::Table(tables) => Kind::Table(tables),
crate::expr::Kind::Record(tables) => Kind::Record(tables),
crate::expr::Kind::Geometry(geometries) => {
Kind::Geometry(geometries.into_iter().map(Into::into).collect())
}
crate::expr::Kind::Either(kinds) => {
let kinds: Vec<Kind> = kinds.into_iter().map(Into::into).collect();
if kinds.is_empty() {
return Self::Either(vec![Self::Any]);
}
Self::Either(kinds)
}
crate::expr::Kind::Set(k, l) => Self::Set(Box::new((*k).into()), l),
crate::expr::Kind::Array(k, l) => Self::Array(Box::new((*k).into()), l),
crate::expr::Kind::Function(args, ret) => Self::Function(
args.map(|args| args.into_iter().map(Into::into).collect()),
ret.map(|ret| Box::new((*ret).into())),
),
crate::expr::Kind::Range => Self::Range,
crate::expr::Kind::Literal(l) => Self::Literal(l.into()),
crate::expr::Kind::File(k) => Kind::File(k),
}
}
}
impl From<Kind> for crate::types::PublicKind {
fn from(v: Kind) -> Self {
match v {
Kind::Any => crate::types::PublicKind::Any,
Kind::None => crate::types::PublicKind::None,
Kind::Null => crate::types::PublicKind::Null,
Kind::Bool => crate::types::PublicKind::Bool,
Kind::Bytes => crate::types::PublicKind::Bytes,
Kind::Datetime => crate::types::PublicKind::Datetime,
Kind::Decimal => crate::types::PublicKind::Decimal,
Kind::Duration => crate::types::PublicKind::Duration,
Kind::Float => crate::types::PublicKind::Float,
Kind::Int => crate::types::PublicKind::Int,
Kind::Number => crate::types::PublicKind::Number,
Kind::Object => crate::types::PublicKind::Object,
Kind::String => crate::types::PublicKind::String,
Kind::Uuid => crate::types::PublicKind::Uuid,
Kind::Regex => crate::types::PublicKind::Regex,
Kind::Table(k) => {
crate::types::PublicKind::Table(k.into_iter().map(Into::into).collect())
}
Kind::Record(k) => {
crate::types::PublicKind::Record(k.into_iter().map(Into::into).collect())
}
Kind::Geometry(k) => {
crate::types::PublicKind::Geometry(k.into_iter().map(Into::into).collect())
}
Kind::Either(k) => {
crate::types::PublicKind::Either(k.into_iter().map(Into::into).collect())
}
Kind::Set(k, l) => crate::types::PublicKind::Set(Box::new((*k).into()), l),
Kind::Array(k, l) => crate::types::PublicKind::Array(Box::new((*k).into()), l),
Kind::Function(args, ret) => crate::types::PublicKind::Function(
args.map(|args| args.into_iter().map(Into::into).collect()),
ret.map(|ret| Box::new((*ret).into())),
),
Kind::Range => crate::types::PublicKind::Range,
Kind::Literal(l) => crate::types::PublicKind::Literal(l.into()),
Kind::File(k) => crate::types::PublicKind::File(k),
}
}
}
impl From<crate::types::PublicKind> for Kind {
fn from(v: crate::types::PublicKind) -> Self {
match v {
crate::types::PublicKind::None => Kind::None,
crate::types::PublicKind::Null => Kind::Null,
crate::types::PublicKind::Any => Kind::Any,
crate::types::PublicKind::Bool => Kind::Bool,
crate::types::PublicKind::Bytes => Kind::Bytes,
crate::types::PublicKind::Datetime => Kind::Datetime,
crate::types::PublicKind::Decimal => Kind::Decimal,
crate::types::PublicKind::Duration => Kind::Duration,
crate::types::PublicKind::Float => Kind::Float,
crate::types::PublicKind::Int => Kind::Int,
crate::types::PublicKind::Number => Kind::Number,
crate::types::PublicKind::Object => Kind::Object,
crate::types::PublicKind::String => Kind::String,
crate::types::PublicKind::Uuid => Kind::Uuid,
crate::types::PublicKind::Regex => Kind::Regex,
crate::types::PublicKind::Table(k) => {
Kind::Table(k.into_iter().map(Into::into).collect())
}
crate::types::PublicKind::Record(k) => {
Kind::Record(k.into_iter().map(Into::into).collect())
}
crate::types::PublicKind::Geometry(k) => {
Kind::Geometry(k.into_iter().map(Into::into).collect())
}
crate::types::PublicKind::Either(k) => {
Kind::Either(k.into_iter().map(Into::into).collect())
}
crate::types::PublicKind::Set(k, l) => Kind::Set(Box::new((*k).into()), l),
crate::types::PublicKind::Array(k, l) => Kind::Array(Box::new((*k).into()), l),
crate::types::PublicKind::Function(args, ret) => Kind::Function(
args.map(|args| args.into_iter().map(Into::into).collect()),
ret.map(|ret| Box::new((*ret).into())),
),
crate::types::PublicKind::Range => Kind::Range,
crate::types::PublicKind::Literal(l) => Kind::Literal(l.into()),
crate::types::PublicKind::File(k) => Kind::File(k),
}
}
}
impl ToSql for Kind {
fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
match self {
Kind::Any => f.push_str("any"),
Kind::None => f.push_str("none"),
Kind::Null => f.push_str("null"),
Kind::Bool => f.push_str("bool"),
Kind::Bytes => f.push_str("bytes"),
Kind::Datetime => f.push_str("datetime"),
Kind::Decimal => f.push_str("decimal"),
Kind::Duration => f.push_str("duration"),
Kind::Float => f.push_str("float"),
Kind::Int => f.push_str("int"),
Kind::Number => f.push_str("number"),
Kind::Object => f.push_str("object"),
Kind::String => f.push_str("string"),
Kind::Uuid => f.push_str("uuid"),
Kind::Regex => f.push_str("regex"),
Kind::Function(_, _) => f.push_str("function"),
Kind::Table(k) => {
if k.is_empty() {
f.push_str("table");
} else {
write_sql!(
f,
fmt,
"table<{}>",
Fmt::verbar_separated(k.iter().map(|x| EscapeKwFreeIdent(x.as_str())))
);
}
}
Kind::Record(k) => {
if k.is_empty() {
f.push_str("record");
} else {
write_sql!(
f,
fmt,
"record<{}>",
Fmt::verbar_separated(k.iter().map(|x| EscapeKwFreeIdent(x.as_str())))
);
}
}
Kind::Geometry(k) => {
if k.is_empty() {
f.push_str("geometry");
} else {
write_sql!(f, fmt, "geometry<{}>", Fmt::verbar_separated(k));
}
}
Kind::Set(k, l) => match (k, l) {
(k, None) if matches!(**k, Kind::Any) => f.push_str("set"),
(k, None) => write_sql!(f, fmt, "set<{k}>"),
(k, Some(l)) => write_sql!(f, fmt, "set<{k}, {l}>"),
},
Kind::Array(k, l) => match (k, l) {
(k, None) if matches!(**k, Kind::Any) => f.push_str("array"),
(k, None) => write_sql!(f, fmt, "array<{k}>"),
(k, Some(l)) => write_sql!(f, fmt, "array<{k}, {l}>"),
},
Kind::Either(k) => write_sql!(f, fmt, "{}", Fmt::verbar_separated(k)),
Kind::Range => f.push_str("range"),
Kind::Literal(l) => l.fmt_sql(f, fmt),
Kind::File(k) => {
if k.is_empty() {
f.push_str("file");
} else {
write_sql!(
f,
fmt,
"file<{}>",
Fmt::verbar_separated(k.iter().map(|x| EscapeKwFreeIdent(x)))
);
}
}
}
}
}
#[derive(Clone, Debug)]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub enum KindLiteral {
String(Strand),
Integer(i64),
Float(f64),
Decimal(Decimal),
Duration(PublicDuration),
Array(Vec<Kind>),
Object(BTreeMap<Strand, Kind>),
Bool(bool),
}
impl hash::Hash for KindLiteral {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
match self {
Self::String(v) => v.hash(state),
Self::Integer(v) => v.hash(state),
Self::Float(v) => v.to_bits().hash(state),
Self::Decimal(v) => v.hash(state),
Self::Duration(v) => v.hash(state),
Self::Array(v) => v.hash(state),
Self::Object(v) => v.hash(state),
Self::Bool(v) => v.hash(state),
}
}
}
impl PartialEq for KindLiteral {
fn eq(&self, other: &Self) -> bool {
match self {
KindLiteral::String(a) => {
if let KindLiteral::String(b) = other {
a == b
} else {
false
}
}
KindLiteral::Integer(a) => {
if let KindLiteral::Integer(b) = other {
a == b
} else {
false
}
}
KindLiteral::Float(a) => {
if let KindLiteral::Float(b) = other {
a.to_bits() == b.to_bits()
} else {
false
}
}
KindLiteral::Decimal(a) => {
if let KindLiteral::Decimal(b) = other {
a == b
} else {
false
}
}
KindLiteral::Duration(a) => {
if let KindLiteral::Duration(b) = other {
a == b
} else {
false
}
}
KindLiteral::Array(a) => {
if let KindLiteral::Array(b) = other {
a == b
} else {
false
}
}
KindLiteral::Object(a) => {
if let KindLiteral::Object(b) = other {
a == b
} else {
false
}
}
KindLiteral::Bool(a) => {
if let KindLiteral::Bool(b) = other {
a == b
} else {
false
}
}
}
}
}
impl Eq for KindLiteral {}
impl ToSql for KindLiteral {
fn fmt_sql(&self, f: &mut String, fmt: SqlFormat) {
match self {
KindLiteral::String(s) => write_sql!(f, fmt, "{}", QuoteStr(s)),
KindLiteral::Integer(n) => write_sql!(f, fmt, "{}", n),
KindLiteral::Float(n) => write_sql!(f, fmt, " {}", Float(*n)),
KindLiteral::Decimal(n) => write_sql!(f, fmt, " {}", n),
KindLiteral::Duration(d) => write_sql!(f, fmt, "{}", d),
KindLiteral::Bool(b) => write_sql!(f, fmt, "{}", b),
KindLiteral::Array(a) => {
f.push('[');
if !a.is_empty() {
let fmt = fmt.increment();
write_sql!(f, fmt, "{}", Fmt::pretty_comma_separated(a.as_slice()));
}
f.push(']');
}
KindLiteral::Object(o) => {
if fmt.is_pretty() {
f.push('{');
} else {
f.push_str("{ ");
}
if !o.is_empty() {
let fmt = fmt.increment();
write_sql!(
f,
fmt,
"{}",
Fmt::pretty_comma_separated(o.iter().map(|args| Fmt::new(
args,
|(k, v), f, fmt| {
write_sql!(f, fmt, "{}: {}", EscapeObjectKey(k), v)
}
)),)
);
}
if fmt.is_pretty() {
f.push('}');
} else {
f.push_str(" }");
}
}
}
}
}
impl Display for Kind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.to_sql())
}
}
impl From<KindLiteral> for crate::expr::kind::KindLiteral {
fn from(v: KindLiteral) -> Self {
match v {
KindLiteral::String(s) => crate::expr::kind::KindLiteral::String(s),
KindLiteral::Integer(i) => crate::expr::kind::KindLiteral::Integer(i),
KindLiteral::Float(f) => crate::expr::kind::KindLiteral::Float(f),
KindLiteral::Decimal(d) => crate::expr::kind::KindLiteral::Decimal(d),
KindLiteral::Duration(d) => crate::expr::kind::KindLiteral::Duration(d.into()),
KindLiteral::Array(a) => {
crate::expr::kind::KindLiteral::Array(a.into_iter().map(Into::into).collect())
}
KindLiteral::Object(o) => crate::expr::kind::KindLiteral::Object(
o.into_iter().map(|(k, v)| (k, v.into())).collect(),
),
KindLiteral::Bool(b) => crate::expr::kind::KindLiteral::Bool(b),
}
}
}
impl From<crate::expr::kind::KindLiteral> for KindLiteral {
fn from(v: crate::expr::kind::KindLiteral) -> Self {
match v {
crate::expr::kind::KindLiteral::String(s) => Self::String(s),
crate::expr::kind::KindLiteral::Integer(i) => Self::Integer(i),
crate::expr::kind::KindLiteral::Float(f) => Self::Float(f),
crate::expr::kind::KindLiteral::Decimal(d) => Self::Decimal(d),
crate::expr::kind::KindLiteral::Duration(d) => Self::Duration(d.into()),
crate::expr::kind::KindLiteral::Array(a) => {
Self::Array(a.into_iter().map(Into::into).collect())
}
crate::expr::kind::KindLiteral::Object(o) => {
Self::Object(o.into_iter().map(|(k, v)| (k, v.into())).collect())
}
crate::expr::kind::KindLiteral::Bool(b) => Self::Bool(b),
}
}
}
impl From<KindLiteral> for crate::types::PublicKindLiteral {
fn from(v: KindLiteral) -> Self {
match v {
KindLiteral::Bool(b) => crate::types::PublicKindLiteral::Bool(b),
KindLiteral::Integer(i) => crate::types::PublicKindLiteral::Integer(i),
KindLiteral::Float(f) => crate::types::PublicKindLiteral::Float(f),
KindLiteral::Decimal(d) => crate::types::PublicKindLiteral::Decimal(d),
KindLiteral::String(s) => crate::types::PublicKindLiteral::String(s.into_string()),
KindLiteral::Duration(d) => crate::types::PublicKindLiteral::Duration(d),
KindLiteral::Array(a) => {
crate::types::PublicKindLiteral::Array(a.into_iter().map(Into::into).collect())
}
KindLiteral::Object(o) => crate::types::PublicKindLiteral::Object(
o.into_iter().map(|(k, v)| (k.into_string(), v.into())).collect(),
),
}
}
}
impl From<crate::types::PublicKindLiteral> for KindLiteral {
fn from(v: crate::types::PublicKindLiteral) -> Self {
match v {
crate::types::PublicKindLiteral::Bool(b) => Self::Bool(b),
crate::types::PublicKindLiteral::Integer(i) => Self::Integer(i),
crate::types::PublicKindLiteral::Float(f) => Self::Float(f),
crate::types::PublicKindLiteral::Decimal(d) => Self::Decimal(d),
crate::types::PublicKindLiteral::String(s) => Self::String(s.into()),
crate::types::PublicKindLiteral::Duration(d) => Self::Duration(d),
crate::types::PublicKindLiteral::Array(a) => {
Self::Array(a.into_iter().map(Into::into).collect())
}
crate::types::PublicKindLiteral::Object(o) => {
Self::Object(o.into_iter().map(|(k, v)| (k.into(), v.into())).collect())
}
}
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
#[case::any(Kind::Any, "any")]
#[case::none(Kind::None, "none")]
#[case::null(Kind::Null, "null")]
#[case::bool(Kind::Bool, "bool")]
#[case::bytes(Kind::Bytes, "bytes")]
#[case::datetime(Kind::Datetime, "datetime")]
#[case::decimal(Kind::Decimal, "decimal")]
#[case::duration(Kind::Duration, "duration")]
#[case::float(Kind::Float, "float")]
#[case::int(Kind::Int, "int")]
#[case::number(Kind::Number, "number")]
#[case::object(Kind::Object, "object")]
#[case::string(Kind::String, "string")]
#[case::uuid(Kind::Uuid, "uuid")]
#[case::regex(Kind::Regex, "regex")]
#[case::range(Kind::Range, "range")]
#[case::function(Kind::Function(None, None), "function")]
#[case::table_empty(Kind::Table(vec![]), "table")]
#[case::table_single(Kind::Table(vec!["users".into()]), "table<users>")]
#[case::table_multiple(Kind::Table(vec!["users".into(), "posts".into()]), "table<users | posts>")]
#[case::record_empty(Kind::Record(vec![]), "record")]
#[case::record_single(Kind::Record(vec!["users".into()]), "record<users>")]
#[case::geometry_empty(Kind::Geometry(vec![]), "geometry")]
#[case::geometry_single(Kind::Geometry(vec![GeometryKind::Point]), "geometry<point>")]
#[case::set_any(Kind::Set(Box::new(Kind::Any), None), "set")]
#[case::set_typed(Kind::Set(Box::new(Kind::String), None), "set<string>")]
#[case::array_any(Kind::Array(Box::new(Kind::Any), None), "array")]
#[case::array_typed(Kind::Array(Box::new(Kind::String), Some(5)), "array<string, 5>")]
#[case::either(Kind::Either(vec![Kind::String, Kind::Int]), "string | int")]
#[case::file_empty(Kind::File(vec![]), "file")]
#[case::file_single(Kind::File(vec!["bucket".to_string()]), "file<bucket>")]
fn test_kind_to_sql(#[case] kind: Kind, #[case] expected: &str) {
assert_eq!(kind.to_sql(), expected);
assert_eq!(kind.to_string(), expected);
}
#[rstest]
#[case::any(Kind::Any)]
#[case::none(Kind::None)]
#[case::null(Kind::Null)]
#[case::bool(Kind::Bool)]
#[case::bytes(Kind::Bytes)]
#[case::datetime(Kind::Datetime)]
#[case::decimal(Kind::Decimal)]
#[case::duration(Kind::Duration)]
#[case::float(Kind::Float)]
#[case::int(Kind::Int)]
#[case::number(Kind::Number)]
#[case::object(Kind::Object)]
#[case::string(Kind::String)]
#[case::uuid(Kind::Uuid)]
#[case::regex(Kind::Regex)]
#[case::range(Kind::Range)]
#[case::table(Kind::Table(vec!["users".into()]))]
#[case::record(Kind::Record(vec!["users".into()]))]
#[case::geometry(Kind::Geometry(vec![GeometryKind::Point]))]
#[case::set(Kind::Set(Box::new(Kind::String), None))]
#[case::array(Kind::Array(Box::new(Kind::String), None))]
#[case::either(Kind::Either(vec![Kind::String, Kind::Int]))]
#[case::file(Kind::File(vec!["bucket".to_string()]))]
fn test_kind_conversions_expr(#[case] sql_kind: Kind) {
let expr_kind: crate::expr::Kind = sql_kind.clone().into();
let back_to_sql: Kind = expr_kind.into();
assert_eq!(sql_kind, back_to_sql);
}
#[rstest]
#[case::any(Kind::Any)]
#[case::none(Kind::None)]
#[case::null(Kind::Null)]
#[case::bool(Kind::Bool)]
#[case::bytes(Kind::Bytes)]
#[case::datetime(Kind::Datetime)]
#[case::decimal(Kind::Decimal)]
#[case::duration(Kind::Duration)]
#[case::float(Kind::Float)]
#[case::int(Kind::Int)]
#[case::number(Kind::Number)]
#[case::object(Kind::Object)]
#[case::string(Kind::String)]
#[case::uuid(Kind::Uuid)]
#[case::regex(Kind::Regex)]
#[case::range(Kind::Range)]
#[case::table(Kind::Table(vec!["users".into()]))]
#[case::record(Kind::Record(vec!["users".into()]))]
#[case::geometry(Kind::Geometry(vec![GeometryKind::Point]))]
#[case::set(Kind::Set(Box::new(Kind::String), None))]
#[case::array(Kind::Array(Box::new(Kind::String), None))]
#[case::either(Kind::Either(vec![Kind::String, Kind::Int]))]
#[case::file(Kind::File(vec!["bucket".to_string()]))]
fn test_kind_conversions_public(#[case] sql_kind: Kind) {
let public_kind: crate::types::PublicKind = sql_kind.clone().into();
let back_to_sql: Kind = public_kind.into();
assert_eq!(sql_kind, back_to_sql);
}
#[rstest]
#[case::any(Kind::Any)]
#[case::table(Kind::Table(vec!["users".into()]))]
#[case::record(Kind::Record(vec!["users".into()]))]
#[case::geometry(Kind::Geometry(vec![GeometryKind::Point]))]
fn test_kind_flatten(#[case] kind: Kind) {
let flattened = kind.clone().flatten();
assert_eq!(flattened.len(), 1);
assert_eq!(flattened[0], kind);
}
#[test]
fn test_kind_either() {
let kinds = vec![Kind::Table(vec!["users".into()]), Kind::Table(vec!["posts".into()])];
let either = Kind::either(kinds);
assert!(matches!(either, Kind::Either(_)));
if let Kind::Either(inner) = either {
assert_eq!(inner.len(), 2);
}
}
}