partiql_types/
lib.rs

1#![deny(rust_2018_idioms)]
2#![deny(clippy::all)]
3
4use educe::Educe;
5use indexmap::IndexSet;
6use itertools::Itertools;
7use miette::Diagnostic;
8use partiql_common::node::{AutoNodeIdGenerator, NodeId, NodeIdGenerator, NullIdGenerator};
9use std::fmt::{Debug, Display, Formatter};
10use std::hash::{Hash, Hasher};
11use std::sync::OnceLock;
12use thiserror::Error;
13
14#[derive(Debug, Clone, Eq, PartialEq, Hash, Error, Diagnostic)]
15#[error("ShapeResult Error")]
16#[non_exhaustive]
17pub enum ShapeResultError {
18    #[error("Unexpected type `{0:?}` for static type bool")]
19    UnexpectedType(String),
20}
21
22/// Result of attempts to encode to Ion.
23pub type ShapeResult<T> = Result<T, ShapeResultError>;
24
25pub trait Type {}
26
27impl Type for StaticType {}
28
29fn indexset_hash<H, T>(set: &IndexSet<T>, state: &mut H)
30where
31    H: Hasher,
32    T: Hash,
33{
34    for val in set {
35        val.hash(state)
36    }
37}
38
39#[macro_export]
40macro_rules! type_dynamic {
41    ($bld:expr) => {
42        $bld.new_dynamic()
43    };
44}
45
46#[macro_export]
47macro_rules! type_int {
48    ($bld:expr) => {
49        $bld.new_static($crate::Static::Int)
50    };
51}
52
53#[macro_export]
54macro_rules! type_int8 {
55    ($bld:expr) => {
56        $bld.new_static($crate::Static::Int8)
57    };
58}
59
60#[macro_export]
61macro_rules! type_int16 {
62    ($bld:expr) => {
63        $bld.new_static($crate::Static::Int16)
64    };
65}
66
67#[macro_export]
68macro_rules! type_int32 {
69    ($bld:expr) => {
70        $bld.new_static($crate::Static::Int32)
71    };
72}
73
74#[macro_export]
75macro_rules! type_int64 {
76    ($bld:expr) => {
77        $bld.new_static($crate::Static::Int64)
78    };
79}
80
81#[macro_export]
82macro_rules! type_decimal {
83    ($bld:expr) => {
84        $bld.new_static($crate::Static::Decimal)
85    };
86}
87
88// TODO add macro_rule for Decimal with precision and scale
89
90#[macro_export]
91macro_rules! type_float32 {
92    ($bld:expr) => {
93        $bld.new_static($crate::Static::Float32)
94    };
95}
96
97#[macro_export]
98macro_rules! type_float64 {
99    ($bld:expr) => {
100        $bld.new_static($crate::Static::Float64)
101    };
102}
103
104#[macro_export]
105macro_rules! type_string {
106    ($bld:expr) => {
107        $bld.new_static($crate::Static::String)
108    };
109}
110
111#[macro_export]
112macro_rules! type_bool {
113    ($bld:expr) => {
114        $bld.new_static($crate::Static::Bool)
115    };
116}
117
118#[macro_export]
119macro_rules! type_numeric {
120    ($bld:expr) => {
121        [
122            $crate::type_int!($bld),
123            $crate::type_float32!($bld),
124            $crate::type_float64!($bld),
125            $crate::type_decimal!($bld),
126        ]
127        .into_any_of($bld)
128    };
129}
130
131#[macro_export]
132macro_rules! type_datetime {
133    ($bld:expr) => {
134        $bld.new_static($crate::Static::DateTime)
135    };
136}
137
138#[macro_export]
139macro_rules! type_struct {
140    ($bld:expr) => {
141        $bld.new_struct(StructType::new_any())
142    };
143    ($bld:expr, $elem:expr) => {{
144        let elem = $elem;
145        $bld.new_struct(StructType::new(elem))
146    }};
147}
148
149#[macro_export]
150macro_rules! type_graph {
151    ($bld:expr) => {
152        $bld.new_graph()
153    };
154}
155
156#[macro_export]
157macro_rules! struct_fields {
158    ($(($x:expr, $y:expr)),+ $(,)?) => (
159        $crate::StructConstraint::Fields([$(($x, $y).into()),+].into())
160    );
161}
162
163#[macro_export]
164macro_rules! type_bag {
165    ($bld:expr) => {
166        $bld.new_bag(BagType::new_any());
167    };
168    ($bld:expr, $elem:expr) => {{
169        let elem = $elem;
170        $bld.new_bag($crate::BagType::new(Box::new(elem)))
171    }};
172}
173
174#[macro_export]
175macro_rules! type_array {
176    ($bld:expr) => {
177        $bld.new_array(ArrayType::new_any());
178    };
179    ($bld:expr, $elem:expr) => {{
180        let elem = $elem;
181        $bld.new_array($crate::ArrayType::new(Box::new(elem)))
182    }};
183}
184
185/// Represents a PartiQL Shape
186#[derive(Debug, Clone, Eq, PartialEq, Hash)]
187// With this implementation `Dynamic` and `AnyOf` cannot have `nullability`; this does not mean their
188// `null` value at runtime cannot belong to their domain.
189// TODO adopt the correct model Pending PartiQL Types semantics finalization: https://github.com/partiql/partiql-lang/issues/18
190pub enum PartiqlShape {
191    Dynamic,
192    AnyOf(AnyOf),
193    Static(StaticType),
194    Undefined,
195}
196
197#[allow(dead_code)]
198impl PartiqlShape {
199    #[inline]
200    pub fn static_type_id(&self) -> Option<NodeId> {
201        if let PartiqlShape::Static(StaticType { id, .. }) = self {
202            Some(*id)
203        } else {
204            None
205        }
206    }
207
208    #[inline]
209    pub fn is_string(&self) -> bool {
210        matches!(
211            &self,
212            PartiqlShape::Static(StaticType {
213                ty: Static::String,
214                nullable: true,
215                ..
216            })
217        )
218    }
219
220    #[inline]
221    pub fn is_struct(&self) -> bool {
222        matches!(
223            *self,
224            PartiqlShape::Static(StaticType {
225                ty: Static::Struct(_),
226                nullable: true,
227                ..
228            })
229        )
230    }
231
232    #[inline]
233    pub fn is_collection(&self) -> bool {
234        matches!(
235            *self,
236            PartiqlShape::Static(StaticType {
237                ty: Static::Bag(_),
238                nullable: true,
239                ..
240            })
241        ) || matches!(
242            *self,
243            PartiqlShape::Static(StaticType {
244                ty: Static::Array(_),
245                nullable: true,
246                ..
247            })
248        )
249    }
250
251    #[inline]
252    pub fn is_unordered_collection(&self) -> bool {
253        !self.is_ordered_collection()
254    }
255
256    #[inline]
257    pub fn is_ordered_collection(&self) -> bool {
258        // TODO Add Sexp when added
259        matches!(
260            *self,
261            PartiqlShape::Static(StaticType {
262                ty: Static::Array(_),
263                nullable: true,
264                ..
265            })
266        )
267    }
268
269    #[inline]
270    pub fn is_graph(&self) -> bool {
271        matches!(
272            *self,
273            PartiqlShape::Static(StaticType {
274                ty: Static::Graph(),
275                nullable: true,
276                ..
277            })
278        )
279    }
280
281    #[inline]
282    pub fn is_bag(&self) -> bool {
283        matches!(
284            *self,
285            PartiqlShape::Static(StaticType {
286                ty: Static::Bag(_),
287                nullable: true,
288                ..
289            })
290        )
291    }
292
293    #[inline]
294    pub fn is_array(&self) -> bool {
295        matches!(
296            *self,
297            PartiqlShape::Static(StaticType {
298                ty: Static::Array(_),
299                nullable: true,
300                ..
301            })
302        )
303    }
304
305    #[inline]
306    pub fn is_dynamic(&self) -> bool {
307        matches!(*self, PartiqlShape::Dynamic)
308    }
309
310    #[inline]
311    pub fn is_undefined(&self) -> bool {
312        matches!(*self, PartiqlShape::Undefined)
313    }
314
315    pub fn expect_bool(&self) -> ShapeResult<StaticType> {
316        if let PartiqlShape::Static(StaticType {
317            id,
318            ty: Static::Bool,
319            nullable: n,
320        }) = self
321        {
322            Ok(StaticType {
323                id: *id,
324                ty: Static::Bool,
325                nullable: *n,
326            })
327        } else {
328            Err(ShapeResultError::UnexpectedType(format!("{self}")))
329        }
330    }
331
332    pub fn expect_bag(&self) -> ShapeResult<BagType> {
333        if let PartiqlShape::Static(StaticType {
334            ty: Static::Bag(bag),
335            ..
336        }) = self
337        {
338            Ok(bag.clone())
339        } else {
340            Err(ShapeResultError::UnexpectedType(format!("{self}")))
341        }
342    }
343
344    pub fn expect_struct(&self) -> ShapeResult<StructType> {
345        if let PartiqlShape::Static(StaticType {
346            ty: Static::Struct(stct),
347            ..
348        }) = self
349        {
350            Ok(stct.clone())
351        } else {
352            Err(ShapeResultError::UnexpectedType(format!("{self}")))
353        }
354    }
355
356    pub fn expect_static(&self) -> ShapeResult<StaticType> {
357        if let PartiqlShape::Static(s) = self {
358            Ok(s.clone())
359        } else {
360            Err(ShapeResultError::UnexpectedType(format!("{self}")))
361        }
362    }
363
364    pub fn expect_dynamic_type(&self) -> ShapeResult<PartiqlShape> {
365        if let PartiqlShape::Dynamic = self {
366            Ok(PartiqlShape::Dynamic)
367        } else {
368            Err(ShapeResultError::UnexpectedType(format!("{self}")))
369        }
370    }
371
372    pub fn expect_undefined(&self) -> ShapeResult<PartiqlShape> {
373        if let PartiqlShape::Undefined = self {
374            Ok(PartiqlShape::Undefined)
375        } else {
376            Err(ShapeResultError::UnexpectedType(format!("{self}")))
377        }
378    }
379}
380
381impl Display for PartiqlShape {
382    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
383        let x = match self {
384            PartiqlShape::Dynamic => "Dynamic".to_string(),
385            PartiqlShape::AnyOf(anyof) => {
386                format!("AnyOf({})", anyof.types.iter().cloned().join(", "))
387            }
388            PartiqlShape::Static(s) => format!("{s}"),
389            PartiqlShape::Undefined => "Undefined".to_string(),
390        };
391        write!(f, "{x}")
392    }
393}
394
395#[derive(Default, Debug)]
396pub struct ShapeBuilder<Id: NodeIdGenerator> {
397    id_gen: Id,
398}
399
400pub type PartiqlShapeBuilder = ShapeBuilder<AutoNodeIdGenerator>;
401pub type PartiqlNoIdShapeBuilder = ShapeBuilder<NullIdGenerator>;
402
403impl<Id: NodeIdGenerator + Default> ShapeBuilder<Id> {
404    /// A thread-safe method for creating PartiQL shapes with guaranteed uniqueness over
405    /// generated `NodeId`s.
406    #[track_caller]
407    pub fn singleton() -> &'static PartiqlShapeBuilder {
408        static SHAPE_BUILDER: OnceLock<PartiqlShapeBuilder> = OnceLock::new();
409        SHAPE_BUILDER.get_or_init(PartiqlShapeBuilder::default)
410    }
411
412    #[track_caller]
413    pub fn dummy_singleton() -> &'static PartiqlNoIdShapeBuilder {
414        static SHAPE_BUILDER: OnceLock<PartiqlNoIdShapeBuilder> = OnceLock::new();
415        SHAPE_BUILDER.get_or_init(PartiqlNoIdShapeBuilder::default)
416    }
417}
418
419impl<Id: NodeIdGenerator> ShapeBuilder<Id> {
420    #[inline]
421    pub fn new_static(&mut self, ty: Static) -> PartiqlShape {
422        let id = self.id_gen.next_id();
423        PartiqlShape::Static(StaticType {
424            id,
425            ty,
426            nullable: true,
427        })
428    }
429
430    #[inline]
431    pub fn new_non_nullable_static(&mut self, ty: Static) -> PartiqlShape {
432        let id = self.id_gen.next_id();
433        PartiqlShape::Static(StaticType {
434            id,
435            ty,
436            nullable: false,
437        })
438    }
439
440    #[inline]
441    pub fn new_dynamic(&mut self) -> PartiqlShape {
442        PartiqlShape::Dynamic
443    }
444
445    #[inline]
446    pub fn new_undefined(&mut self) -> PartiqlShape {
447        PartiqlShape::Undefined
448    }
449
450    #[inline]
451    pub fn new_struct(&mut self, s: StructType) -> PartiqlShape {
452        self.new_static(Static::Struct(s))
453    }
454
455    #[inline]
456    pub fn new_graph(&mut self) -> PartiqlShape {
457        self.new_static(Static::Graph())
458    }
459
460    #[inline]
461    pub fn new_struct_of_dyn(&mut self) -> PartiqlShape {
462        self.new_struct(StructType::new_any())
463    }
464
465    #[inline]
466    pub fn new_bag(&mut self, b: BagType) -> PartiqlShape {
467        self.new_static(Static::Bag(b))
468    }
469
470    #[inline]
471    pub fn new_bag_of<E>(&mut self, element_type: E) -> PartiqlShape
472    where
473        E: Into<PartiqlShape>,
474    {
475        self.new_bag(BagType::new_of(element_type))
476    }
477
478    #[inline]
479    pub fn new_bag_of_dyn(&mut self) -> PartiqlShape {
480        let element_type = self.new_dynamic();
481        self.new_bag_of(element_type)
482    }
483
484    #[inline]
485    pub fn new_bag_of_static(&mut self, ty: Static) -> PartiqlShape {
486        let element_type = self.new_static(ty);
487        self.new_bag_of(element_type)
488    }
489
490    #[inline]
491    pub fn new_bag_any_of<I>(&mut self, types: I) -> PartiqlShape
492    where
493        I: IntoIterator<Item = PartiqlShape>,
494    {
495        let shape = self.any_of(types);
496        let bag_type = BagType::new(Box::new(shape));
497        self.new_bag(bag_type)
498    }
499
500    #[inline]
501    pub fn new_array(&mut self, a: ArrayType) -> PartiqlShape {
502        self.new_static(Static::Array(a))
503    }
504
505    #[inline]
506    pub fn new_array_of<E>(&mut self, element_type: E) -> PartiqlShape
507    where
508        E: Into<PartiqlShape>,
509    {
510        self.new_array(ArrayType::new_of(element_type))
511    }
512
513    #[inline]
514    pub fn new_array_of_dyn(&mut self) -> PartiqlShape {
515        let element_type = self.new_dynamic();
516        self.new_array_of(element_type)
517    }
518
519    #[inline]
520    pub fn new_array_of_static(&mut self, ty: Static) -> PartiqlShape {
521        let element_type = self.new_static(ty);
522        self.new_array_of(element_type)
523    }
524
525    #[inline]
526    pub fn new_array_any_of<I>(&mut self, types: I) -> PartiqlShape
527    where
528        I: IntoIterator<Item = PartiqlShape>,
529    {
530        let shape = self.any_of(types);
531        let array_type = ArrayType::new(Box::new(shape));
532        self.new_array(array_type)
533    }
534
535    // The AnyOf::from_iter(types) uses an IndexSet internally to
536    // deduplicate types, thus the match on any_of.types.len() could
537    // "flatten" AnyOfs that had duplicates.
538    // With the addition of IDs, this deduplication no longer happens.
539    // TODO revisit the current implementaion and consider an implementation
540    // that allows merging of the `metas` for the same type, e.g., with a
541    // user-defined control.
542    pub fn any_of<I>(&mut self, types: I) -> PartiqlShape
543    where
544        I: IntoIterator<Item = PartiqlShape>,
545    {
546        let any_of = AnyOf::from_iter(types);
547        match any_of.types.len() {
548            0 => self.new_dynamic(),
549            1 => {
550                let AnyOf { types } = any_of;
551                types.into_iter().next().unwrap()
552            }
553            // TODO figure out what does it mean for a Union to be nullable or not
554            _ => PartiqlShape::AnyOf(any_of),
555        }
556    }
557
558    #[inline]
559    pub fn union(&mut self, lhs: PartiqlShape, rhs: PartiqlShape) -> PartiqlShape {
560        match (lhs, rhs) {
561            (PartiqlShape::Dynamic, _) | (_, PartiqlShape::Dynamic) => PartiqlShape::Dynamic,
562            (PartiqlShape::AnyOf(lhs), PartiqlShape::AnyOf(rhs)) => {
563                self.any_of(lhs.types.into_iter().chain(rhs.types))
564            }
565            (PartiqlShape::AnyOf(anyof), other) | (other, PartiqlShape::AnyOf(anyof)) => {
566                let mut types = anyof.types;
567                types.insert(other);
568                self.any_of(types)
569            }
570            (l, r) => {
571                let types = [l, r];
572                self.any_of(types)
573            }
574        }
575    }
576
577    pub fn union_of<I>(&mut self, types: I) -> PartiqlShape
578    where
579        I: IntoIterator<Item = PartiqlShape>,
580    {
581        let types: Vec<_> = types.into_iter().collect();
582        match types.len() {
583            0 => self.new_undefined(),
584            1 => types.into_iter().next().unwrap(),
585            _ => types.into_iter().reduce(|l, r| self.union(l, r)).unwrap(),
586        }
587    }
588
589    #[inline]
590    pub fn as_non_nullable(&mut self, shape: &PartiqlShape) -> Option<PartiqlShape> {
591        if let PartiqlShape::Static(stype) = shape {
592            Some(self.new_non_nullable_static(stype.ty.clone()))
593        } else {
594            None
595        }
596    }
597}
598
599pub trait ShapeBuilderExtensions<Id: NodeIdGenerator> {
600    fn into_union(self, bld: &mut ShapeBuilder<Id>) -> PartiqlShape;
601
602    fn into_any_of(self, bld: &mut ShapeBuilder<Id>) -> PartiqlShape;
603
604    fn into_array(self, bld: &mut ShapeBuilder<Id>) -> PartiqlShape;
605
606    fn into_bag(self, bld: &mut ShapeBuilder<Id>) -> PartiqlShape;
607}
608
609impl<const N: usize, Id: NodeIdGenerator, E: Into<PartiqlShape>> ShapeBuilderExtensions<Id>
610    for [E; N]
611{
612    #[inline]
613    fn into_union(self, bld: &mut ShapeBuilder<Id>) -> PartiqlShape {
614        bld.union_of(self.into_iter().map(|e| e.into()))
615    }
616
617    #[inline]
618    fn into_any_of(self, bld: &mut ShapeBuilder<Id>) -> PartiqlShape {
619        bld.any_of(self.into_iter().map(|e| e.into()))
620    }
621
622    #[inline]
623    fn into_array(self, bld: &mut ShapeBuilder<Id>) -> PartiqlShape {
624        let ty = self.into_any_of(bld);
625        bld.new_array_of(ty)
626    }
627
628    #[inline]
629    fn into_bag(self, bld: &mut ShapeBuilder<Id>) -> PartiqlShape {
630        let ty = self.into_any_of(bld);
631        bld.new_bag_of(ty)
632    }
633}
634
635impl<Id: NodeIdGenerator, E: Into<PartiqlShape>> ShapeBuilderExtensions<Id> for E {
636    #[inline]
637    fn into_union(self, bld: &mut ShapeBuilder<Id>) -> PartiqlShape {
638        [self].into_union(bld)
639    }
640
641    #[inline]
642    fn into_any_of(self, bld: &mut ShapeBuilder<Id>) -> PartiqlShape {
643        [self].into_any_of(bld)
644    }
645
646    #[inline]
647    fn into_array(self, bld: &mut ShapeBuilder<Id>) -> PartiqlShape {
648        bld.new_array_of(self)
649    }
650
651    #[inline]
652    fn into_bag(self, bld: &mut ShapeBuilder<Id>) -> PartiqlShape {
653        bld.new_bag_of(self)
654    }
655}
656
657#[derive(Educe, Eq, Debug, Clone)]
658#[educe(PartialEq, Hash)]
659#[allow(dead_code)]
660pub struct AnyOf {
661    #[educe(Hash(method(indexset_hash)))]
662    types: IndexSet<PartiqlShape>,
663}
664
665impl AnyOf {
666    #[inline]
667    pub const fn new(types: IndexSet<PartiqlShape>) -> Self {
668        AnyOf { types }
669    }
670
671    pub fn types(&self) -> impl Iterator<Item = &PartiqlShape> {
672        self.types.iter()
673    }
674}
675
676impl FromIterator<PartiqlShape> for AnyOf {
677    fn from_iter<T: IntoIterator<Item = PartiqlShape>>(iter: T) -> Self {
678        AnyOf {
679            types: iter.into_iter().collect(),
680        }
681    }
682}
683
684#[derive(Debug, Clone, Eq, PartialEq, Hash)]
685pub struct StaticType {
686    id: NodeId,
687    ty: Static,
688    nullable: bool,
689}
690
691impl StaticType {
692    #[inline]
693    pub fn ty(&self) -> &Static {
694        &self.ty
695    }
696
697    pub fn ty_id(&self) -> &NodeId {
698        &self.id
699    }
700
701    #[inline]
702    pub fn is_nullable(&self) -> bool {
703        self.nullable
704    }
705
706    #[inline]
707    pub fn is_not_nullable(&self) -> bool {
708        !self.nullable
709    }
710
711    pub fn is_scalar(&self) -> bool {
712        self.ty.is_scalar()
713    }
714
715    pub fn is_sequence(&self) -> bool {
716        self.ty.is_sequence()
717    }
718
719    pub fn is_struct(&self) -> bool {
720        self.ty.is_struct()
721    }
722}
723
724impl From<StaticType> for PartiqlShape {
725    fn from(value: StaticType) -> Self {
726        PartiqlShape::Static(value)
727    }
728}
729
730impl Display for StaticType {
731    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
732        let ty = &self.ty;
733        if self.nullable {
734            write!(f, "{ty}")
735        } else {
736            write!(f, "NOT NULL {ty}")
737        }
738    }
739}
740
741#[derive(Debug, Clone, Eq, PartialEq, Hash)]
742pub enum Static {
743    // Scalar Types
744    Int,
745    Int8,
746    Int16,
747    Int32,
748    Int64,
749    Bool,
750    Decimal,
751    DecimalP(usize, usize),
752
753    Float32,
754    Float64,
755
756    String,
757    StringFixed(usize),
758    StringVarying(usize),
759
760    DateTime,
761
762    // Container Types
763    Struct(StructType),
764    Bag(BagType),
765    Array(ArrayType),
766
767    Graph(
768        /* TODO: https://github.com/partiql/partiql-lang/blob/main/RFCs/0025-graph-data-model.md */
769    ),
770    // TODO Add BitString, ByteString, Blob, Clob, and Graph types
771}
772
773pub enum StaticCategory<'a> {
774    Graph(),
775    Tuple(),
776    Sequence(&'a PartiqlShape),
777    Scalar(&'a Static),
778}
779
780impl Static {
781    pub fn is_scalar(&self) -> bool {
782        !matches!(self, Static::Struct(_) | Static::Bag(_) | Static::Array(_))
783    }
784
785    pub fn is_sequence(&self) -> bool {
786        matches!(self, Static::Bag(_) | Static::Array(_))
787    }
788
789    pub fn is_struct(&self) -> bool {
790        matches!(self, Static::Struct(_))
791    }
792
793    pub fn category(&self) -> StaticCategory<'_> {
794        match self {
795            Static::Struct(_) => StaticCategory::Tuple(),
796            Static::Bag(b) => StaticCategory::Sequence(b.element_type()),
797            Static::Array(b) => StaticCategory::Sequence(b.element_type()),
798            Static::Graph() => StaticCategory::Graph(),
799            _ => StaticCategory::Scalar(self),
800        }
801    }
802}
803
804// TODO, this should probably be via a prettyprint...
805impl Display for Static {
806    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
807        match self {
808            Static::Int => write!(f, "Int"),
809            Static::Int8 => write!(f, "Int8"),
810            Static::Int16 => write!(f, "Int16"),
811            Static::Int32 => write!(f, "Int32"),
812            Static::Int64 => write!(f, "Int64"),
813            Static::Bool => write!(f, "Bool"),
814            Static::Decimal => write!(f, "Decimal"),
815            Static::DecimalP(p, s) => {
816                write!(f, "Decimal({p},{s})")
817            }
818            Static::Float32 => write!(f, "Float32"),
819            Static::Float64 => write!(f, "Float64"),
820            Static::String => write!(f, "String"),
821            Static::StringFixed(_) => {
822                todo!()
823            }
824            Static::StringVarying(_) => {
825                todo!()
826            }
827            Static::DateTime => write!(f, "DateTime"),
828            Static::Struct(inner) => std::fmt::Display::fmt(inner, f),
829            Static::Bag(inner) => std::fmt::Display::fmt(inner, f),
830            Static::Array(inner) => std::fmt::Display::fmt(inner, f),
831            Static::Graph() => write!(f, "Graph"),
832        }
833    }
834}
835
836pub const TYPE_DYNAMIC: PartiqlShape = PartiqlShape::Dynamic;
837
838#[derive(Educe, Eq, Debug, Clone)]
839#[educe(PartialEq, Hash)]
840#[allow(dead_code)]
841pub struct StructType {
842    #[educe(Hash(method(indexset_hash)))]
843    constraints: IndexSet<StructConstraint>,
844}
845
846impl StructType {
847    #[inline]
848    pub fn new(constraints: IndexSet<StructConstraint>) -> Self {
849        StructType { constraints }
850    }
851
852    #[inline]
853    pub fn new_any() -> Self {
854        StructType {
855            constraints: Default::default(),
856        }
857    }
858
859    pub fn fields_set(&self) -> IndexSet<StructField> {
860        self.constraints
861            .iter()
862            .flat_map(|c| {
863                if let StructConstraint::Fields(fields) = c.clone() {
864                    fields
865                } else {
866                    Default::default()
867                }
868            })
869            .collect()
870    }
871
872    pub fn fields(&self) -> impl Iterator<Item = &StructField> {
873        self.constraints
874            .iter()
875            .filter_map(|c| {
876                if let StructConstraint::Fields(fields) = c {
877                    Some(fields)
878                } else {
879                    None
880                }
881            })
882            .flat_map(|f| f.iter())
883    }
884
885    #[inline]
886    pub fn is_partial(&self) -> bool {
887        !self.is_closed()
888    }
889
890    #[inline]
891    pub fn is_closed(&self) -> bool {
892        self.constraints.contains(&StructConstraint::Open(false))
893    }
894}
895
896impl Display for StructType {
897    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
898        let partial = self.is_partial();
899        write!(f, "{{")?;
900        let mut first = true;
901        for StructField { name, ty, optional } in self.fields() {
902            if !first {
903                write!(f, ", ")?;
904            }
905            if *optional {
906                write!(f, "{name}?: {ty}")?;
907            } else {
908                write!(f, "{name}: {ty}")?;
909            }
910            first = false
911        }
912        if partial {
913            if !first {
914                write!(f, ", ")?;
915            }
916            write!(f, "...")?;
917        }
918        write!(f, "}}")
919    }
920}
921
922#[derive(Educe, Eq, Debug, Clone)]
923#[educe(PartialEq, Hash)]
924#[allow(dead_code)]
925#[non_exhaustive]
926pub enum StructConstraint {
927    Open(bool),
928    Ordered(bool),
929    DuplicateAttrs(bool),
930    Fields(#[educe(Hash(method(indexset_hash)))] IndexSet<StructField>),
931}
932
933#[derive(Debug, Clone, Hash, Eq, PartialEq)]
934#[allow(dead_code)]
935pub struct StructField {
936    optional: bool,
937    name: String,
938    ty: PartiqlShape,
939}
940
941impl StructField {
942    #[inline]
943    pub fn new(name: &str, ty: PartiqlShape) -> Self {
944        StructField {
945            name: name.to_string(),
946            ty,
947            optional: false,
948        }
949    }
950
951    #[inline]
952    pub fn new_optional(name: &str, ty: PartiqlShape) -> Self {
953        StructField {
954            name: name.to_string(),
955            ty,
956            optional: true,
957        }
958    }
959
960    #[inline]
961    pub fn name(&self) -> &str {
962        self.name.as_str()
963    }
964
965    #[inline]
966    pub fn ty(&self) -> &PartiqlShape {
967        &self.ty
968    }
969
970    #[inline]
971    pub fn is_optional(&self) -> bool {
972        self.optional
973    }
974}
975
976impl From<(&str, PartiqlShape)> for StructField {
977    fn from(value: (&str, PartiqlShape)) -> Self {
978        StructField {
979            name: value.0.to_string(),
980            ty: value.1,
981            optional: false,
982        }
983    }
984}
985
986impl From<(&str, PartiqlShape, bool)> for StructField {
987    fn from(value: (&str, PartiqlShape, bool)) -> Self {
988        StructField {
989            name: value.0.to_string(),
990            ty: value.1,
991            optional: value.2,
992        }
993    }
994}
995
996#[derive(Educe, Eq, Debug, Clone)]
997#[educe(PartialEq, Hash)]
998#[allow(dead_code)]
999pub struct BagType {
1000    element_type: Box<PartiqlShape>,
1001}
1002
1003impl BagType {
1004    #[inline]
1005    pub fn new(typ: Box<PartiqlShape>) -> Self {
1006        BagType { element_type: typ }
1007    }
1008
1009    #[inline]
1010    pub fn new_of<E>(element_type: E) -> Self
1011    where
1012        E: Into<PartiqlShape>,
1013    {
1014        Self::new(Box::new(element_type.into()))
1015    }
1016
1017    #[inline]
1018    pub fn new_any() -> Self {
1019        Self::new_of(PartiqlShape::Dynamic)
1020    }
1021
1022    pub fn element_type(&self) -> &PartiqlShape {
1023        &self.element_type
1024    }
1025}
1026
1027impl Display for BagType {
1028    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1029        let ty = &self.element_type;
1030        write!(f, "<<{ty}>>")
1031    }
1032}
1033
1034#[derive(Educe, Eq, Debug, Clone)]
1035#[educe(PartialEq, Hash)]
1036#[allow(dead_code)]
1037pub struct ArrayType {
1038    element_type: Box<PartiqlShape>,
1039    // TODO Add Array constraint once we have Schema Specification:
1040    // https://github.com/partiql/partiql-spec/issues/49
1041}
1042
1043impl ArrayType {
1044    #[inline]
1045    pub fn new(typ: Box<PartiqlShape>) -> Self {
1046        ArrayType { element_type: typ }
1047    }
1048
1049    #[inline]
1050    pub fn new_of<E>(element_type: E) -> Self
1051    where
1052        E: Into<PartiqlShape>,
1053    {
1054        Self::new(Box::new(element_type.into()))
1055    }
1056
1057    #[inline]
1058    pub fn new_any() -> Self {
1059        Self::new_of(PartiqlShape::Dynamic)
1060    }
1061
1062    #[inline]
1063    pub fn element_type(&self) -> &PartiqlShape {
1064        &self.element_type
1065    }
1066}
1067
1068impl Display for ArrayType {
1069    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
1070        let ty = &self.element_type;
1071        write!(f, "[{ty}]")
1072    }
1073}
1074
1075#[cfg(test)]
1076mod tests {
1077    use crate::{
1078        PartiqlNoIdShapeBuilder, PartiqlShape, PartiqlShapeBuilder, ShapeBuilderExtensions, Static,
1079        StructConstraint, StructField, StructType,
1080    };
1081    use indexmap::IndexSet;
1082
1083    #[test]
1084    fn union() {
1085        let mut bld = PartiqlNoIdShapeBuilder::default();
1086
1087        let expect_int = bld.new_static(Static::Int);
1088        assert_eq!(
1089            expect_int,
1090            [type_int!(bld), type_int!(bld)].into_union(&mut bld)
1091        );
1092
1093        let numbers = [bld.new_static(Static::Int), bld.new_static(Static::Float32)];
1094        let expect_nums = bld.any_of(numbers);
1095        assert_eq!(
1096            expect_nums,
1097            [type_int!(bld), type_float32!(bld)].into_union(&mut bld)
1098        );
1099        assert_eq!(
1100            expect_nums,
1101            [
1102                [type_int!(bld), type_float32!(bld)].into_union(&mut bld),
1103                [type_int!(bld), type_float32!(bld)].into_union(&mut bld),
1104            ]
1105            .into_any_of(&mut bld)
1106        );
1107        assert_eq!(
1108            expect_nums,
1109            [
1110                [type_int!(bld), type_float32!(bld)].into_union(&mut bld),
1111                [type_int!(bld), type_float32!(bld)].into_union(&mut bld),
1112                [
1113                    [type_int!(bld), type_float32!(bld)].into_union(&mut bld),
1114                    [type_int!(bld), type_float32!(bld)].into_union(&mut bld)
1115                ]
1116                .into_any_of(&mut bld)
1117            ]
1118            .into_any_of(&mut bld)
1119        );
1120    }
1121
1122    #[test]
1123    fn unique_node_ids() {
1124        let mut bld = PartiqlShapeBuilder::default();
1125        let age_field = struct_fields![("age", type_int!(bld))];
1126        let details = type_struct![bld, IndexSet::from([age_field])];
1127
1128        let fields = [
1129            StructField::new("id", type_int!(bld)),
1130            StructField::new("name", type_string!(bld)),
1131            StructField::new("details", details.clone()),
1132        ];
1133
1134        let row = type_struct![
1135            bld,
1136            IndexSet::from([
1137                StructConstraint::Fields(IndexSet::from(fields)),
1138                StructConstraint::Open(false)
1139            ])
1140        ];
1141
1142        let shape = type_bag![bld, row.clone()];
1143
1144        let mut ids = collect_ids(shape);
1145        ids.sort_unstable();
1146        assert!(ids.windows(2).all(|w| w[0] != w[1]));
1147    }
1148
1149    fn collect_ids(row: PartiqlShape) -> Vec<u32> {
1150        let mut out = vec![];
1151        match row {
1152            PartiqlShape::Dynamic => {}
1153            PartiqlShape::AnyOf(anyof) => {
1154                for shape in anyof.types {
1155                    out.push(collect_ids(shape));
1156                }
1157            }
1158            PartiqlShape::Static(static_type) => {
1159                out.push(vec![static_type.id.0]);
1160                match static_type.ty {
1161                    Static::Struct(struct_type) => {
1162                        for f in struct_type.fields() {
1163                            out.push(collect_ids(f.ty.clone()));
1164                        }
1165                    }
1166                    Static::Bag(bag_type) => out.push(collect_ids(*bag_type.element_type)),
1167                    Static::Array(array_type) => out.push(collect_ids(*array_type.element_type)),
1168                    _ => {}
1169                }
1170            }
1171            PartiqlShape::Undefined => {}
1172        }
1173        out.into_iter().flatten().collect()
1174    }
1175}