use crate::db::schema::AcceptedFieldKind;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) enum AcceptedScalarClass {
Account,
Blob,
Bool,
Date,
Decimal,
Duration,
Enum,
Float32,
Float64,
Signed64,
Signed128,
SignedBig,
Principal,
Subaccount,
Text,
Timestamp,
Unsigned64,
Unsigned128,
UnsignedBig,
Ulid,
Unit,
}
impl AcceptedScalarClass {
#[must_use]
const fn is_numeric(self) -> bool {
matches!(
self,
Self::Decimal
| Self::Duration
| Self::Float32
| Self::Float64
| Self::Signed64
| Self::Signed128
| Self::SignedBig
| Self::Timestamp
| Self::Unsigned64
| Self::Unsigned128
| Self::UnsignedBig
)
}
#[must_use]
const fn is_signed_numeric(self) -> bool {
matches!(self, Self::Signed64 | Self::Signed128 | Self::SignedBig)
}
#[must_use]
const fn is_unsigned_numeric(self) -> bool {
matches!(
self,
Self::Unsigned64 | Self::Unsigned128 | Self::UnsignedBig
)
}
#[must_use]
const fn supports_arithmetic_numeric(self) -> bool {
matches!(
self,
Self::Decimal
| Self::Float32
| Self::Float64
| Self::Signed64
| Self::Signed128
| Self::SignedBig
| Self::Unsigned64
| Self::Unsigned128
| Self::UnsignedBig
)
}
#[must_use]
const fn is_orderable(self) -> bool {
!matches!(self, Self::Blob | Self::Enum | Self::Unit)
}
#[must_use]
const fn is_sql_comparable(self) -> bool {
!matches!(self, Self::Unit)
}
#[must_use]
const fn is_relation_key_eligible(self) -> bool {
matches!(
self,
Self::Account
| Self::Signed64
| Self::Signed128
| Self::Principal
| Self::Subaccount
| Self::Timestamp
| Self::Unsigned64
| Self::Unsigned128
| Self::Ulid
| Self::Unit
)
}
#[must_use]
const fn supports_stable_group_key(self) -> bool {
!matches!(self, Self::Enum | Self::Unit)
}
#[must_use]
const fn supports_predicate_numeric_widen(self) -> bool {
matches!(
self,
Self::Decimal | Self::Float32 | Self::Float64 | Self::Signed64 | Self::Unsigned64
)
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) enum AcceptedFieldKindCategory {
Scalar(AcceptedScalarClass),
Relation(Option<AcceptedScalarClass>),
Collection,
Structured { queryable: bool },
}
impl AcceptedFieldKindCategory {
#[must_use]
const fn scalar_class(self) -> Option<AcceptedScalarClass> {
match self {
Self::Scalar(class) | Self::Relation(Some(class)) => Some(class),
Self::Relation(None) | Self::Collection | Self::Structured { .. } => None,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::db) struct AcceptedFieldKindSemantics {
category: AcceptedFieldKindCategory,
}
impl AcceptedFieldKindSemantics {
#[must_use]
const fn new(category: AcceptedFieldKindCategory) -> Self {
Self { category }
}
#[must_use]
pub(in crate::db) const fn category(self) -> AcceptedFieldKindCategory {
self.category
}
#[must_use]
pub(in crate::db) const fn is_scalar(self) -> bool {
matches!(self.category, AcceptedFieldKindCategory::Scalar(_))
}
#[must_use]
pub(in crate::db) const fn is_numeric(self) -> bool {
match self.category.scalar_class() {
Some(class) => class.is_numeric(),
None => false,
}
}
#[must_use]
pub(in crate::db) const fn is_signed_numeric(self) -> bool {
match self.category.scalar_class() {
Some(class) => class.is_signed_numeric(),
None => false,
}
}
#[must_use]
pub(in crate::db) const fn is_unsigned_numeric(self) -> bool {
match self.category.scalar_class() {
Some(class) => class.is_unsigned_numeric(),
None => false,
}
}
#[must_use]
pub(in crate::db) const fn supports_arithmetic_numeric(self) -> bool {
match self.category.scalar_class() {
Some(class) => class.supports_arithmetic_numeric(),
None => false,
}
}
#[must_use]
pub(in crate::db) const fn supports_predicate_numeric_widen(self) -> bool {
match self.category.scalar_class() {
Some(class) => class.supports_predicate_numeric_widen(),
None => false,
}
}
#[must_use]
pub(in crate::db) const fn is_orderable(self) -> bool {
match self.category.scalar_class() {
Some(class) => class.is_orderable(),
None => false,
}
}
#[must_use]
pub(in crate::db) const fn is_sql_comparable(self) -> bool {
match self.category.scalar_class() {
Some(class) => class.is_sql_comparable(),
None => false,
}
}
#[must_use]
pub(in crate::db) const fn is_relation_key_eligible(self) -> bool {
match self.category.scalar_class() {
Some(class) => class.is_relation_key_eligible(),
None => false,
}
}
#[must_use]
pub(in crate::db) const fn supports_stable_group_key(self) -> bool {
match self.category.scalar_class() {
Some(class) => class.supports_stable_group_key(),
None => false,
}
}
#[must_use]
pub(in crate::db) const fn is_collection(self) -> bool {
matches!(self.category, AcceptedFieldKindCategory::Collection)
}
#[must_use]
pub(in crate::db) const fn is_structured(self) -> bool {
matches!(self.category, AcceptedFieldKindCategory::Structured { .. })
}
}
#[must_use]
pub(in crate::db) const fn classify_accepted_field_kind(
kind: &AcceptedFieldKind,
) -> AcceptedFieldKindSemantics {
match kind {
AcceptedFieldKind::Account => AcceptedFieldKindSemantics::new(
AcceptedFieldKindCategory::Scalar(AcceptedScalarClass::Account),
),
AcceptedFieldKind::Blob { .. } => AcceptedFieldKindSemantics::new(
AcceptedFieldKindCategory::Scalar(AcceptedScalarClass::Blob),
),
AcceptedFieldKind::Bool => AcceptedFieldKindSemantics::new(
AcceptedFieldKindCategory::Scalar(AcceptedScalarClass::Bool),
),
AcceptedFieldKind::Date => AcceptedFieldKindSemantics::new(
AcceptedFieldKindCategory::Scalar(AcceptedScalarClass::Date),
),
AcceptedFieldKind::Decimal { .. } => AcceptedFieldKindSemantics::new(
AcceptedFieldKindCategory::Scalar(AcceptedScalarClass::Decimal),
),
AcceptedFieldKind::Duration => AcceptedFieldKindSemantics::new(
AcceptedFieldKindCategory::Scalar(AcceptedScalarClass::Duration),
),
AcceptedFieldKind::Enum { .. } => AcceptedFieldKindSemantics::new(
AcceptedFieldKindCategory::Scalar(AcceptedScalarClass::Enum),
),
AcceptedFieldKind::Float32 => AcceptedFieldKindSemantics::new(
AcceptedFieldKindCategory::Scalar(AcceptedScalarClass::Float32),
),
AcceptedFieldKind::Float64 => AcceptedFieldKindSemantics::new(
AcceptedFieldKindCategory::Scalar(AcceptedScalarClass::Float64),
),
AcceptedFieldKind::Int8
| AcceptedFieldKind::Int16
| AcceptedFieldKind::Int32
| AcceptedFieldKind::Int64 => AcceptedFieldKindSemantics::new(
AcceptedFieldKindCategory::Scalar(AcceptedScalarClass::Signed64),
),
AcceptedFieldKind::Int128 => AcceptedFieldKindSemantics::new(
AcceptedFieldKindCategory::Scalar(AcceptedScalarClass::Signed128),
),
AcceptedFieldKind::IntBig { .. } => AcceptedFieldKindSemantics::new(
AcceptedFieldKindCategory::Scalar(AcceptedScalarClass::SignedBig),
),
AcceptedFieldKind::Principal => AcceptedFieldKindSemantics::new(
AcceptedFieldKindCategory::Scalar(AcceptedScalarClass::Principal),
),
AcceptedFieldKind::Subaccount => AcceptedFieldKindSemantics::new(
AcceptedFieldKindCategory::Scalar(AcceptedScalarClass::Subaccount),
),
AcceptedFieldKind::Text { .. } => AcceptedFieldKindSemantics::new(
AcceptedFieldKindCategory::Scalar(AcceptedScalarClass::Text),
),
AcceptedFieldKind::Timestamp => AcceptedFieldKindSemantics::new(
AcceptedFieldKindCategory::Scalar(AcceptedScalarClass::Timestamp),
),
AcceptedFieldKind::Nat8
| AcceptedFieldKind::Nat16
| AcceptedFieldKind::Nat32
| AcceptedFieldKind::Nat64 => AcceptedFieldKindSemantics::new(
AcceptedFieldKindCategory::Scalar(AcceptedScalarClass::Unsigned64),
),
AcceptedFieldKind::Nat128 => AcceptedFieldKindSemantics::new(
AcceptedFieldKindCategory::Scalar(AcceptedScalarClass::Unsigned128),
),
AcceptedFieldKind::NatBig { .. } => AcceptedFieldKindSemantics::new(
AcceptedFieldKindCategory::Scalar(AcceptedScalarClass::UnsignedBig),
),
AcceptedFieldKind::Ulid => AcceptedFieldKindSemantics::new(
AcceptedFieldKindCategory::Scalar(AcceptedScalarClass::Ulid),
),
AcceptedFieldKind::Unit => AcceptedFieldKindSemantics::new(
AcceptedFieldKindCategory::Scalar(AcceptedScalarClass::Unit),
),
AcceptedFieldKind::Relation { key_kind, .. } => AcceptedFieldKindSemantics::new(
AcceptedFieldKindCategory::Relation(classify_relation_scalar_class(key_kind)),
),
AcceptedFieldKind::List(_) | AcceptedFieldKind::Set(_) | AcceptedFieldKind::Map { .. } => {
AcceptedFieldKindSemantics::new(AcceptedFieldKindCategory::Collection)
}
AcceptedFieldKind::Structured { queryable } => {
AcceptedFieldKindSemantics::new(AcceptedFieldKindCategory::Structured {
queryable: *queryable,
})
}
}
}
const fn classify_relation_scalar_class(kind: &AcceptedFieldKind) -> Option<AcceptedScalarClass> {
match classify_accepted_field_kind(kind).category() {
AcceptedFieldKindCategory::Scalar(class)
| AcceptedFieldKindCategory::Relation(Some(class)) => Some(class),
AcceptedFieldKindCategory::Relation(None)
| AcceptedFieldKindCategory::Collection
| AcceptedFieldKindCategory::Structured { .. } => None,
}
}
#[cfg(test)]
mod tests {
use super::{AcceptedFieldKindCategory, AcceptedScalarClass, classify_accepted_field_kind};
use crate::{
db::schema::{AcceptedFieldKind, AcceptedRelationStrength},
types::EntityTag,
};
fn relation_to_key(key_kind: AcceptedFieldKind) -> AcceptedFieldKind {
AcceptedFieldKind::Relation {
target_path: "target::Entity".into(),
target_entity_name: "Target".into(),
target_entity_tag: EntityTag::new(77),
target_store_path: "target::Store".into(),
key_kind: Box::new(key_kind),
strength: AcceptedRelationStrength::Weak,
}
}
#[test]
fn classify_persisted_numeric_scalar_kind() {
let semantics = classify_accepted_field_kind(&AcceptedFieldKind::Nat64);
assert_eq!(
semantics.category(),
AcceptedFieldKindCategory::Scalar(AcceptedScalarClass::Unsigned64),
);
assert!(semantics.is_scalar());
assert!(semantics.is_numeric());
assert!(!semantics.is_signed_numeric());
assert!(semantics.is_unsigned_numeric());
assert!(semantics.is_orderable());
assert!(semantics.is_sql_comparable());
assert!(semantics.is_relation_key_eligible());
}
#[test]
fn classify_relation_delegates_to_key_semantics_without_becoming_scalar() {
let relation = relation_to_key(AcceptedFieldKind::Nat128);
let semantics = classify_accepted_field_kind(&relation);
assert_eq!(
semantics.category(),
AcceptedFieldKindCategory::Relation(Some(AcceptedScalarClass::Unsigned128)),
);
assert!(!semantics.is_scalar());
assert!(semantics.is_numeric());
assert!(semantics.is_unsigned_numeric());
assert!(semantics.is_orderable());
assert!(semantics.is_sql_comparable());
assert!(semantics.is_relation_key_eligible());
}
#[test]
fn classify_collection_and_structured_kinds_stay_non_scalar() {
let collection = classify_accepted_field_kind(&AcceptedFieldKind::List(Box::new(
AcceptedFieldKind::Text { max_len: None },
)));
let structured =
classify_accepted_field_kind(&AcceptedFieldKind::Structured { queryable: true });
assert!(collection.is_collection());
assert!(!collection.is_scalar());
assert!(!collection.is_sql_comparable());
assert!(structured.is_structured());
assert!(!structured.is_collection());
assert!(!structured.is_orderable());
}
#[test]
fn classify_scalar_edges_match_current_persisted_contracts() {
let blob = classify_accepted_field_kind(&AcceptedFieldKind::Blob { max_len: None });
let unit = classify_accepted_field_kind(&AcceptedFieldKind::Unit);
let date = classify_accepted_field_kind(&AcceptedFieldKind::Date);
let timestamp = classify_accepted_field_kind(&AcceptedFieldKind::Timestamp);
let bigint = classify_accepted_field_kind(&AcceptedFieldKind::IntBig { max_bytes: 32 });
assert!(blob.is_sql_comparable());
assert!(!blob.is_orderable());
assert!(!unit.is_sql_comparable());
assert!(unit.is_relation_key_eligible());
assert!(date.is_orderable());
assert!(!date.is_relation_key_eligible());
assert!(timestamp.is_numeric());
assert!(timestamp.is_relation_key_eligible());
assert!(bigint.is_signed_numeric());
assert!(!bigint.is_relation_key_eligible());
}
}