diff --git a/.gitmodules b/.gitmodules index 79ace91d..7f3634a0 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "partiql-conformance-tests/partiql-tests"] path = partiql-conformance-tests/partiql-tests - url = git@github.com:partiql/partiql-tests.git + url = https://github.com/partiql/partiql-tests.git diff --git a/CHANGELOG.md b/CHANGELOG.md index bfca598e..fc612a92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,13 +11,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - partiql-ast: improved pretty-printing of `CASE` and various clauses - ### Added +- Added `partiql-common`. +- Added `NodeId` to `StaticType`. +- *BREAKING* Added thread-safe `PartiqlShapeBuilder` and automatic `NodeId` generation for the `StaticType`. +- Added a static thread safe `shape_builder` function that provides a convenient way for using `PartiqlShapeBuilder` for creating new shapes. -### Fixed +### Removed +- *BREAKING* Removed `partiql-source-map`. +- *BREAKING* Removed `const` PartiQL types under `partiql-types` in favor of `PartiqlShapeBuilder`. +- *BREAKING* Removed `StaticType`'s `new`, `new_non_nullable`, and `as_non-nullable` APIs in favor of `PartiqlShapeBuilder`. ## [0.10.0] ### Changed - *BREAKING:* partiql-ast: added modeling of `EXCLUDE` - *BREAKING:* partiql-ast: added pretty-printing of `EXCLUDE` +- *BREAKING* Moved some of the `PartiqlShape` APIs to the `PartiqlShapeBuilder`. +- *BREAKING* Prepended existing type macros with `type` to make macro names more friendly: e.g., `type_int!` +- *BREAKING* Moved node id generation and `partiql-source-map` to it. +- *BREAKING* Changed `AutoNodeIdGenerator` to a thread-safe version ### Added - *BREAKING:* partiql-parser: added parsing of `EXCLUDE` diff --git a/extension/partiql-extension-ddl/src/ddl.rs b/extension/partiql-extension-ddl/src/ddl.rs index b2f5a25d..02e83760 100644 --- a/extension/partiql-extension-ddl/src/ddl.rs +++ b/extension/partiql-extension-ddl/src/ddl.rs @@ -123,8 +123,8 @@ impl PartiqlBasicDdlEncoder { Static::Float64 => out.push_str("DOUBLE"), Static::String => out.push_str("VARCHAR"), Static::Struct(s) => out.push_str(&self.write_struct(s)?), - Static::Bag(b) => out.push_str(&self.write_bag(b)?), - Static::Array(a) => out.push_str(&self.write_array(a)?), + Static::Bag(b) => out.push_str(&self.write_type_bag(b)?), + Static::Array(a) => out.push_str(&self.write_type_array(a)?), // non-exhaustive catch-all _ => todo!("handle type for {}", ty), } @@ -136,12 +136,18 @@ impl PartiqlBasicDdlEncoder { Ok(out) } - fn write_bag(&self, bag: &BagType) -> ShapeDdlEncodeResult { - Ok(format!("BAG<{}>", self.write_shape(bag.element_type())?)) + fn write_type_bag(&self, type_bag: &BagType) -> ShapeDdlEncodeResult { + Ok(format!( + "type_bag<{}>", + self.write_shape(type_bag.element_type())? + )) } - fn write_array(&self, arr: &ArrayType) -> ShapeDdlEncodeResult { - Ok(format!("ARRAY<{}>", self.write_shape(arr.element_type())?)) + fn write_type_array(&self, arr: &ArrayType) -> ShapeDdlEncodeResult { + Ok(format!( + "type_array<{}>", + self.write_shape(arr.element_type())? + )) } fn write_struct(&self, strct: &StructType) -> ShapeDdlEncodeResult { @@ -189,8 +195,8 @@ impl PartiqlDdlEncoder for PartiqlBasicDdlEncoder { let mut output = String::new(); let ty = ty.expect_static()?; - if let Static::Bag(bag) = ty.ty() { - let s = bag.element_type().expect_struct()?; + if let Static::Bag(type_bag) = ty.ty() { + let s = type_bag.element_type().expect_struct()?; let mut fields = s.fields().peekable(); while let Some(field) = fields.next() { output.push_str(&format!("\"{}\" ", field.name())); @@ -223,41 +229,47 @@ impl PartiqlDdlEncoder for PartiqlBasicDdlEncoder { mod tests { use super::*; use indexmap::IndexSet; - use partiql_types::{array, bag, f64, int8, r#struct, str, struct_fields, StructConstraint}; + use partiql_types::{ + struct_fields, type_array, type_bag, type_float64, type_int8, type_string, type_struct, + PartiqlShapeBuilder, StructConstraint, + }; #[test] fn ddl_test() { let nested_attrs = struct_fields![ ( "a", - PartiqlShape::any_of(vec![ - PartiqlShape::new(Static::DecimalP(5, 4)), - PartiqlShape::new(Static::Int8), + PartiqlShapeBuilder::init_or_get().any_of(vec![ + PartiqlShapeBuilder::init_or_get().new_static(Static::DecimalP(5, 4)), + PartiqlShapeBuilder::init_or_get().new_static(Static::Int8), ]) ), - ("b", array![str![]]), - ("c", f64!()), + ("b", type_array![type_string![]]), + ("c", type_float64!()), ]; - let details = r#struct![IndexSet::from([nested_attrs])]; + let details = type_struct![IndexSet::from([nested_attrs])]; let fields = struct_fields![ - ("employee_id", int8![]), - ("full_name", str![]), - ("salary", PartiqlShape::new(Static::DecimalP(8, 2))), + ("employee_id", type_int8![]), + ("full_name", type_string![]), + ( + "salary", + PartiqlShapeBuilder::init_or_get().new_static(Static::DecimalP(8, 2)) + ), ("details", details), - ("dependents", array![str![]]) + ("dependents", type_array![type_string![]]) ]; - let ty = bag![r#struct![IndexSet::from([ + let ty = type_bag![type_struct![IndexSet::from([ fields, StructConstraint::Open(false) ])]]; - let expected_compact = r#""employee_id" TINYINT,"full_name" VARCHAR,"salary" DECIMAL(8, 2),"details" STRUCT<"a": UNION,"b": ARRAY,"c": DOUBLE>,"dependents" ARRAY"#; + let expected_compact = r#""employee_id" TINYINT,"full_name" VARCHAR,"salary" DECIMAL(8, 2),"details" STRUCT<"a": UNION,"b": type_array,"c": DOUBLE>,"dependents" type_array"#; let expected_pretty = r#""employee_id" TINYINT, "full_name" VARCHAR, "salary" DECIMAL(8, 2), -"details" STRUCT<"a": UNION,"b": ARRAY,"c": DOUBLE>, -"dependents" ARRAY"#; +"details" STRUCT<"a": UNION,"b": type_array,"c": DOUBLE>, +"dependents" type_array"#; let ddl_compact = PartiqlBasicDdlEncoder::new(DdlFormat::Compact); assert_eq!(ddl_compact.ddl(&ty).expect("write shape"), expected_compact); diff --git a/extension/partiql-extension-ddl/tests/ddl-tests.rs b/extension/partiql-extension-ddl/tests/ddl-tests.rs index 87c64d03..20072856 100644 --- a/extension/partiql-extension-ddl/tests/ddl-tests.rs +++ b/extension/partiql-extension-ddl/tests/ddl-tests.rs @@ -1,20 +1,26 @@ use indexmap::IndexSet; use partiql_extension_ddl::ddl::{DdlFormat, PartiqlBasicDdlEncoder, PartiqlDdlEncoder}; -use partiql_types::{bag, int, r#struct, str, struct_fields, StructConstraint, StructField}; -use partiql_types::{BagType, PartiqlShape, Static, StructType}; +use partiql_types::{ + struct_fields, type_bag, type_int, type_string, type_struct, PartiqlShapeBuilder, + StructConstraint, StructField, +}; +use partiql_types::{BagType, Static, StructType}; #[test] fn basic_ddl_test() { - let details_fields = struct_fields![("age", int!())]; - let details = r#struct![IndexSet::from([details_fields])]; + let details_fields = struct_fields![("age", type_int!())]; + let details = type_struct![IndexSet::from([details_fields])]; let fields = [ - StructField::new("id", int!()), - StructField::new("name", str!()), - StructField::new("address", PartiqlShape::new_non_nullable(Static::String)), + StructField::new("id", type_int!()), + StructField::new("name", type_string!()), + StructField::new( + "address", + PartiqlShapeBuilder::init_or_get().new_non_nullable_static(Static::String), + ), StructField::new_optional("details", details.clone()), ] .into(); - let shape = bag![r#struct![IndexSet::from([ + let shape = type_bag![type_struct![IndexSet::from([ StructConstraint::Fields(fields), StructConstraint::Open(false) ])]]; diff --git a/partiql-ast/src/builder.rs b/partiql-ast/src/builder.rs index b7973204..571b9796 100644 --- a/partiql-ast/src/builder.rs +++ b/partiql-ast/src/builder.rs @@ -17,7 +17,8 @@ where pub fn node(&mut self, node: T) -> AstNode { let id = self.id_gen.id(); - AstNode { id, node } + let id = id.read().expect("NodId read lock"); + AstNode { id: *id, node } } } diff --git a/partiql-common/src/node.rs b/partiql-common/src/node.rs index 30a81159..8f3b5425 100644 --- a/partiql-common/src/node.rs +++ b/partiql-common/src/node.rs @@ -1,5 +1,6 @@ use indexmap::IndexMap; use std::hash::Hash; +use std::sync::{Arc, RwLock}; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -12,27 +13,37 @@ pub struct NodeId(pub u32); /// Auto-incrementing [`NodeIdGenerator`] pub struct AutoNodeIdGenerator { - next_id: NodeId, + next_id: Arc>, } impl Default for AutoNodeIdGenerator { fn default() -> Self { - AutoNodeIdGenerator { next_id: NodeId(1) } + AutoNodeIdGenerator { + next_id: Arc::new(RwLock::from(NodeId(1))), + } } } /// A provider of 'fresh' [`NodeId`]s. pub trait NodeIdGenerator { + fn id(&self) -> Arc>; + /// Provides a 'fresh' [`NodeId`]. - fn id(&mut self) -> NodeId; + fn next_id(&self) -> NodeId; } impl NodeIdGenerator for AutoNodeIdGenerator { + fn id(&self) -> Arc> { + let id = self.next_id(); + let mut w = self.next_id.write().expect("NodId write lock"); + *w = id; + Arc::clone(&self.next_id) + } + #[inline] - fn id(&mut self) -> NodeId { - let mut next = NodeId(&self.next_id.0 + 1); - std::mem::swap(&mut self.next_id, &mut next); - next + fn next_id(&self) -> NodeId { + let id = &self.next_id.read().expect("NodId read lock"); + NodeId(id.0 + 1) } } @@ -41,7 +52,11 @@ impl NodeIdGenerator for AutoNodeIdGenerator { pub struct NullIdGenerator {} impl NodeIdGenerator for NullIdGenerator { - fn id(&mut self) -> NodeId { + fn id(&self) -> Arc> { + Arc::new(RwLock::from(self.next_id())) + } + + fn next_id(&self) -> NodeId { NodeId(0) } } diff --git a/partiql-eval/src/eval/eval_expr_wrapper.rs b/partiql-eval/src/eval/eval_expr_wrapper.rs index 7a0a2faa..b9a21d4f 100644 --- a/partiql-eval/src/eval/eval_expr_wrapper.rs +++ b/partiql-eval/src/eval/eval_expr_wrapper.rs @@ -4,7 +4,7 @@ use crate::eval::expr::{BindError, EvalExpr}; use crate::eval::EvalContext; use itertools::Itertools; -use partiql_types::{PartiqlShape, Static, TYPE_DYNAMIC}; +use partiql_types::{type_dynamic, PartiqlShape, Static, TYPE_DYNAMIC}; use partiql_value::Value::{Missing, Null}; use partiql_value::{Tuple, Value}; @@ -413,7 +413,7 @@ impl UnaryValueExpr { where F: 'static + Fn(&Value) -> Value, { - Self::create_typed::([TYPE_DYNAMIC; 1], args, f) + Self::create_typed::([type_dynamic!(); 1], args, f) } #[allow(dead_code)] diff --git a/partiql-eval/src/eval/expr/coll.rs b/partiql-eval/src/eval/expr/coll.rs index d3361187..5991f670 100644 --- a/partiql-eval/src/eval/expr/coll.rs +++ b/partiql-eval/src/eval/expr/coll.rs @@ -4,7 +4,9 @@ use crate::eval::expr::{BindError, BindEvalExpr, EvalExpr}; use itertools::{Itertools, Unique}; -use partiql_types::{ArrayType, BagType, PartiqlShape, Static, TYPE_BOOL, TYPE_NUMERIC_TYPES}; +use partiql_types::{ + type_bool, type_numeric, ArrayType, BagType, PartiqlShape, PartiqlShapeBuilder, Static, +}; use partiql_value::Value::{Missing, Null}; use partiql_value::{BinaryAnd, BinaryOr, Value, ValueIter}; @@ -49,21 +51,23 @@ impl BindEvalExpr for EvalCollFn { value.sequence_iter().map_or(Missing, &f) }) } - let boolean_elems = [PartiqlShape::any_of([ - PartiqlShape::new(Static::Array(ArrayType::new(Box::new(TYPE_BOOL)))), - PartiqlShape::new(Static::Bag(BagType::new(Box::new(TYPE_BOOL)))), + let boolean_elems = [PartiqlShapeBuilder::init_or_get().any_of([ + PartiqlShapeBuilder::init_or_get() + .new_static(Static::Array(ArrayType::new(Box::new(type_bool!())))), + PartiqlShapeBuilder::init_or_get() + .new_static(Static::Bag(BagType::new(Box::new(type_bool!())))), ])]; - let numeric_elems = [PartiqlShape::any_of([ - PartiqlShape::new(Static::Array(ArrayType::new(Box::new( - PartiqlShape::any_of(TYPE_NUMERIC_TYPES), + let numeric_elems = [PartiqlShapeBuilder::init_or_get().any_of([ + PartiqlShapeBuilder::init_or_get().new_static(Static::Array(ArrayType::new(Box::new( + PartiqlShapeBuilder::init_or_get().any_of(type_numeric!()), + )))), + PartiqlShapeBuilder::init_or_get().new_static(Static::Bag(BagType::new(Box::new( + PartiqlShapeBuilder::init_or_get().any_of(type_numeric!()), )))), - PartiqlShape::new(Static::Bag(BagType::new(Box::new(PartiqlShape::any_of( - TYPE_NUMERIC_TYPES, - ))))), ])]; - let any_elems = [PartiqlShape::any_of([ - PartiqlShape::new(Static::Array(ArrayType::new_any())), - PartiqlShape::new(Static::Bag(BagType::new_any())), + let any_elems = [PartiqlShapeBuilder::init_or_get().any_of([ + PartiqlShapeBuilder::init_or_get().new_static(Static::Array(ArrayType::new_any())), + PartiqlShapeBuilder::init_or_get().new_static(Static::Bag(BagType::new_any())), ])]; match *self { diff --git a/partiql-eval/src/eval/expr/datetime.rs b/partiql-eval/src/eval/expr/datetime.rs index 0b140bff..c90dd459 100644 --- a/partiql-eval/src/eval/expr/datetime.rs +++ b/partiql-eval/src/eval/expr/datetime.rs @@ -1,6 +1,6 @@ use crate::eval::expr::{BindError, BindEvalExpr, EvalExpr}; -use partiql_types::TYPE_DATETIME; +use partiql_types::type_datetime; use partiql_value::Value::Missing; use partiql_value::{DateTime, Value}; @@ -43,7 +43,7 @@ impl BindEvalExpr for EvalExtractFn { } let create = |f: fn(&DateTime) -> Value| { - UnaryValueExpr::create_typed::<{ STRICT }, _>([TYPE_DATETIME], args, move |value| { + UnaryValueExpr::create_typed::<{ STRICT }, _>([type_datetime!()], args, move |value| { match value { Value::DateTime(dt) => f(dt.as_ref()), _ => Missing, diff --git a/partiql-eval/src/eval/expr/operators.rs b/partiql-eval/src/eval/expr/operators.rs index 76f067b5..693bf695 100644 --- a/partiql-eval/src/eval/expr/operators.rs +++ b/partiql-eval/src/eval/expr/operators.rs @@ -8,8 +8,8 @@ use crate::eval::expr::{BindError, BindEvalExpr, EvalExpr}; use crate::eval::EvalContext; use partiql_types::{ - ArrayType, BagType, PartiqlShape, Static, StructType, TYPE_BOOL, TYPE_DYNAMIC, - TYPE_NUMERIC_TYPES, + type_bool, type_dynamic, type_numeric, ArrayType, BagType, PartiqlShape, PartiqlShapeBuilder, + Static, StructType, }; use partiql_value::Value::{Boolean, Missing, Null}; use partiql_value::{BinaryAnd, EqualityValue, NullableEq, NullableOrd, Tuple, Value}; @@ -80,7 +80,7 @@ impl BindEvalExpr for EvalOpUnary { &self, args: Vec>, ) -> Result, BindError> { - let any_num = PartiqlShape::any_of(TYPE_NUMERIC_TYPES); + let any_num = PartiqlShapeBuilder::init_or_get().any_of(type_numeric!()); let unop = |types, f: fn(&Value) -> Value| { UnaryValueExpr::create_typed::<{ STRICT }, _>(types, args, f) @@ -89,7 +89,7 @@ impl BindEvalExpr for EvalOpUnary { match self { EvalOpUnary::Pos => unop([any_num], std::clone::Clone::clone), EvalOpUnary::Neg => unop([any_num], |operand| -operand), - EvalOpUnary::Not => unop([TYPE_BOOL], |operand| !operand), + EvalOpUnary::Not => unop([type_bool!()], |operand| !operand), } } } @@ -167,19 +167,19 @@ impl BindEvalExpr for EvalOpBinary { macro_rules! logical { ($check: ty, $f:expr) => { - create!($check, [TYPE_BOOL, TYPE_BOOL], $f) + create!($check, [type_bool!(), type_bool!()], $f) }; } macro_rules! equality { ($f:expr) => { - create!(EqCheck, [TYPE_DYNAMIC, TYPE_DYNAMIC], $f) + create!(EqCheck, [type_dynamic!(), type_dynamic!()], $f) }; } macro_rules! math { ($f:expr) => {{ - let nums = PartiqlShape::any_of(TYPE_NUMERIC_TYPES); + let nums = PartiqlShapeBuilder::init_or_get().any_of(type_numeric!()); create!(MathCheck, [nums.clone(), nums], $f) }}; } @@ -209,10 +209,12 @@ impl BindEvalExpr for EvalOpBinary { create!( InCheck, [ - TYPE_DYNAMIC, - PartiqlShape::any_of([ - PartiqlShape::new(Static::Array(ArrayType::new_any())), - PartiqlShape::new(Static::Bag(BagType::new_any())), + type_dynamic!(), + PartiqlShapeBuilder::init_or_get().any_of([ + PartiqlShapeBuilder::init_or_get() + .new_static(Static::Array(ArrayType::new_any())), + PartiqlShapeBuilder::init_or_get() + .new_static(Static::Bag(BagType::new_any())), ]) ], |lhs, rhs| { @@ -250,20 +252,24 @@ impl BindEvalExpr for EvalOpBinary { ) } EvalOpBinary::Concat => { - create!(Check, [TYPE_DYNAMIC, TYPE_DYNAMIC], |lhs, rhs| { - // TODO non-naive concat (i.e., don't just use debug print for non-strings). - let lhs = if let Value::String(s) = lhs { - s.as_ref().clone() - } else { - format!("{lhs:?}") - }; - let rhs = if let Value::String(s) = rhs { - s.as_ref().clone() - } else { - format!("{rhs:?}") - }; - Value::String(Box::new(format!("{lhs}{rhs}"))) - }) + create!( + Check, + [type_dynamic!(), type_dynamic!()], + |lhs, rhs| { + // TODO non-naive concat (i.e., don't just use debug print for non-strings). + let lhs = if let Value::String(s) = lhs { + s.as_ref().clone() + } else { + format!("{lhs:?}") + }; + let rhs = if let Value::String(s) = rhs { + s.as_ref().clone() + } else { + format!("{rhs:?}") + }; + Value::String(Box::new(format!("{lhs}{rhs}"))) + } + ) } } } @@ -278,7 +284,7 @@ impl BindEvalExpr for EvalBetweenExpr { &self, args: Vec>, ) -> Result, BindError> { - let types = [TYPE_DYNAMIC, TYPE_DYNAMIC, TYPE_DYNAMIC]; + let types = [type_dynamic!(), type_dynamic!(), type_dynamic!()]; TernaryValueExpr::create_checked::<{ STRICT }, NullArgChecker, _>( types, args, @@ -316,7 +322,7 @@ impl BindEvalExpr for EvalFnAbs { &self, args: Vec>, ) -> Result, BindError> { - let nums = PartiqlShape::any_of(TYPE_NUMERIC_TYPES); + let nums = PartiqlShapeBuilder::init_or_get().any_of(type_numeric!()); UnaryValueExpr::create_typed::<{ STRICT }, _>([nums], args, |v| { match NullableOrd::lt(v, &Value::from(0)) { Null => Null, @@ -337,10 +343,11 @@ impl BindEvalExpr for EvalFnCardinality { &self, args: Vec>, ) -> Result, BindError> { - let collections = PartiqlShape::any_of([ - PartiqlShape::new(Static::Array(ArrayType::new_any())), - PartiqlShape::new(Static::Bag(BagType::new_any())), - PartiqlShape::new(Static::Struct(StructType::new_any())), + let shape_builder = PartiqlShapeBuilder::init_or_get(); + let collections = PartiqlShapeBuilder::init_or_get().any_of([ + shape_builder.new_static(Static::Array(ArrayType::new_any())), + shape_builder.new_static(Static::Bag(BagType::new_any())), + shape_builder.new_static(Static::Struct(StructType::new_any())), ]); UnaryValueExpr::create_typed::<{ STRICT }, _>([collections], args, |v| match v { diff --git a/partiql-eval/src/eval/expr/pattern_match.rs b/partiql-eval/src/eval/expr/pattern_match.rs index 056210a0..a6004e5b 100644 --- a/partiql-eval/src/eval/expr/pattern_match.rs +++ b/partiql-eval/src/eval/expr/pattern_match.rs @@ -2,7 +2,7 @@ use crate::error::PlanningError; use crate::eval::eval_expr_wrapper::{TernaryValueExpr, UnaryValueExpr}; use crate::eval::expr::{BindError, BindEvalExpr, EvalExpr}; -use partiql_types::TYPE_STRING; +use partiql_types::type_string; use partiql_value::Value; use partiql_value::Value::Missing; use regex::{Regex, RegexBuilder}; @@ -47,10 +47,11 @@ impl BindEvalExpr for EvalLikeMatch { args: Vec>, ) -> Result, BindError> { let pattern = self.pattern.clone(); - UnaryValueExpr::create_typed::<{ STRICT }, _>([TYPE_STRING], args, move |value| match value - { - Value::String(s) => Value::Boolean(pattern.is_match(s.as_ref())), - _ => Missing, + UnaryValueExpr::create_typed::<{ STRICT }, _>([type_string!()], args, move |value| { + match value { + Value::String(s) => Value::Boolean(pattern.is_match(s.as_ref())), + _ => Missing, + } }) } } @@ -65,7 +66,7 @@ impl BindEvalExpr for EvalLikeNonStringNonLiteralMatch { &self, args: Vec>, ) -> Result, BindError> { - let types = [TYPE_STRING, TYPE_STRING, TYPE_STRING]; + let types = [type_string!(), type_string!(), type_string!()]; TernaryValueExpr::create_typed::<{ STRICT }, _>( types, args, diff --git a/partiql-eval/src/eval/expr/strings.rs b/partiql-eval/src/eval/expr/strings.rs index 2d5f4c44..8e0d50a9 100644 --- a/partiql-eval/src/eval/expr/strings.rs +++ b/partiql-eval/src/eval/expr/strings.rs @@ -7,7 +7,7 @@ use crate::eval::expr::{BindError, BindEvalExpr, EvalExpr}; use crate::eval::EvalContext; use itertools::Itertools; -use partiql_types::{TYPE_INT, TYPE_STRING}; +use partiql_types::{type_int, type_string}; use partiql_value::Value; use partiql_value::Value::Missing; @@ -43,7 +43,7 @@ impl BindEvalExpr for EvalStringFn { F: Fn(&Box) -> R + 'static, R: Into + 'static, { - UnaryValueExpr::create_typed::<{ STRICT }, _>([TYPE_STRING], args, move |value| { + UnaryValueExpr::create_typed::<{ STRICT }, _>([type_string!()], args, move |value| { match value { Value::String(value) => (f(value)).into(), _ => Missing, @@ -99,7 +99,7 @@ impl BindEvalExpr for EvalTrimFn { ) -> Result, BindError> { let create = |f: for<'a> fn(&'a str, &'a str) -> &'a str| { BinaryValueExpr::create_typed::<{ STRICT }, _>( - [TYPE_STRING, TYPE_STRING], + [type_string!(), type_string!()], args, move |to_trim, value| match (to_trim, value) { (Value::String(to_trim), Value::String(value)) => { @@ -136,7 +136,7 @@ impl BindEvalExpr for EvalFnPosition { args: Vec>, ) -> Result, BindError> { BinaryValueExpr::create_typed::( - [TYPE_STRING, TYPE_STRING], + [type_string!(), type_string!()], args, |needle, haystack| match (needle, haystack) { (Value::String(needle), Value::String(haystack)) => { @@ -159,7 +159,7 @@ impl BindEvalExpr for EvalFnSubstring { ) -> Result, BindError> { match args.len() { 2 => BinaryValueExpr::create_typed::( - [TYPE_STRING, TYPE_INT], + [type_string!(), type_int!()], args, |value, offset| match (value, offset) { (Value::String(value), Value::Integer(offset)) => { @@ -171,7 +171,7 @@ impl BindEvalExpr for EvalFnSubstring { }, ), 3 => TernaryValueExpr::create_typed::( - [TYPE_STRING, TYPE_INT, TYPE_INT], + [type_string!(), type_int!(), type_int!()], args, |value, offset, length| match (value, offset, length) { (Value::String(value), Value::Integer(offset), Value::Integer(length)) => { @@ -222,7 +222,7 @@ impl BindEvalExpr for EvalFnOverlay { match args.len() { 3 => TernaryValueExpr::create_typed::( - [TYPE_STRING, TYPE_STRING, TYPE_INT], + [type_string!(), type_string!(), type_int!()], args, |value, replacement, offset| match (value, replacement, offset) { (Value::String(value), Value::String(replacement), Value::Integer(offset)) => { @@ -233,7 +233,7 @@ impl BindEvalExpr for EvalFnOverlay { }, ), 4 => QuaternaryValueExpr::create_typed::( - [TYPE_STRING, TYPE_STRING, TYPE_INT, TYPE_INT], + [type_string!(), type_string!(), type_int!(), type_int!()], args, |value, replacement, offset, length| match (value, replacement, offset, length) { ( diff --git a/partiql-logical-planner/src/lower.rs b/partiql-logical-planner/src/lower.rs index adcb5867..e23c8566 100644 --- a/partiql-logical-planner/src/lower.rs +++ b/partiql-logical-planner/src/lower.rs @@ -2007,7 +2007,7 @@ mod tests { use partiql_catalog::{PartiqlCatalog, TypeEnvEntry}; use partiql_logical::BindingsOp::Project; use partiql_logical::ValueExpr; - use partiql_types::dynamic; + use partiql_types::type_dynamic; #[test] fn test_plan_non_existent_fns() { @@ -2107,7 +2107,7 @@ mod tests { expected_logical.add_flow_with_branch_num(project, sink, 0); let mut catalog = PartiqlCatalog::default(); - let _oid = catalog.add_type_entry(TypeEnvEntry::new("customers", &[], dynamic!())); + let _oid = catalog.add_type_entry(TypeEnvEntry::new("customers", &[], type_dynamic!())); let statement = "SELECT c.id AS my_id, customers.name AS my_name FROM customers AS c"; let parsed = partiql_parser::Parser::default() .parse(statement) diff --git a/partiql-logical-planner/src/typer.rs b/partiql-logical-planner/src/typer.rs index 8c78800d..f42b8851 100644 --- a/partiql-logical-planner/src/typer.rs +++ b/partiql-logical-planner/src/typer.rs @@ -4,8 +4,9 @@ use partiql_ast::ast::{CaseSensitivity, SymbolPrimitive}; use partiql_catalog::Catalog; use partiql_logical::{BindingsOp, LogicalPlan, OpId, PathComponent, ValueExpr, VarRefType}; use partiql_types::{ - dynamic, undefined, ArrayType, BagType, PartiqlShape, ShapeResultError, Static, - StructConstraint, StructField, StructType, + type_array, type_bag, type_bool, type_decimal, type_dynamic, type_int, type_string, + type_struct, type_undefined, ArrayType, BagType, PartiqlShape, PartiqlShapeBuilder, + ShapeResultError, Static, StructConstraint, StructField, StructType, }; use partiql_value::{BindingsName, Value}; use petgraph::algo::toposort; @@ -107,7 +108,7 @@ impl Default for TypeEnvContext { fn default() -> Self { TypeEnvContext { env: LocalTypeEnv::new(), - derived_type: dynamic!(), + derived_type: type_dynamic!(), } } } @@ -175,7 +176,7 @@ impl<'c> PlanTyper<'c> { } if self.errors.is_empty() { - Ok(self.output.clone().unwrap_or(undefined!())) + Ok(self.output.clone().unwrap_or(type_undefined!())) } else { let output_schema = self.get_singleton_type_from_env(); Err(TypeErr { @@ -218,16 +219,17 @@ impl<'c> PlanTyper<'c> { StructField::new(k.as_str(), self.get_singleton_type_from_env()) }); - let ty = PartiqlShape::new_struct(StructType::new(IndexSet::from([ - StructConstraint::Fields(fields.collect()), - ]))); + let ty = PartiqlShapeBuilder::init_or_get().new_struct(StructType::new( + IndexSet::from([StructConstraint::Fields(fields.collect())]), + )); let derived_type_ctx = self.local_type_ctx(); let derived_type = &self.derived_type(&derived_type_ctx); let schema = if derived_type.is_ordered_collection() { - PartiqlShape::new_array(ArrayType::new(Box::new(ty))) + PartiqlShapeBuilder::init_or_get().new_array(ArrayType::new(Box::new(ty))) } else if derived_type.is_unordered_collection() { - PartiqlShape::new_bag(BagType::new(Box::new(ty))) + PartiqlShapeBuilder::init_or_get() + .new_static(Static::Bag(BagType::new(Box::new(ty)))) } else { self.errors.push(TypingError::IllegalState(format!( "Expecting Collection for the output Schema but found {:?}", @@ -304,8 +306,10 @@ impl<'c> PlanTyper<'c> { let ctx = ty_ctx![(&ty_env![(key_as_sym, ty.clone())], &ty)]; self.type_env_stack.push(ctx); } else { - let ctx = - ty_ctx![(&ty_env![(key_as_sym, undefined!())], &undefined!())]; + let ctx = ty_ctx![( + &ty_env![(key_as_sym, type_undefined!())], + &type_undefined!() + )]; self.type_env_stack.push(ctx); } } @@ -329,20 +333,20 @@ impl<'c> PlanTyper<'c> { } ValueExpr::Lit(v) => { let ty = match **v { - Value::Null => PartiqlShape::Undefined, - Value::Missing => PartiqlShape::Undefined, - Value::Integer(_) => PartiqlShape::new(Static::Int), - Value::Decimal(_) => PartiqlShape::new(Static::Decimal), - Value::Boolean(_) => PartiqlShape::new(Static::Bool), - Value::String(_) => PartiqlShape::new(Static::String), - Value::Tuple(_) => PartiqlShape::new(Static::Struct(StructType::new_any())), - Value::List(_) => PartiqlShape::new(Static::Array(ArrayType::new_any())), - Value::Bag(_) => PartiqlShape::new(Static::Bag(BagType::new_any())), + Value::Null => type_undefined!(), + Value::Missing => type_undefined!(), + Value::Integer(_) => type_int!(), + Value::Decimal(_) => type_decimal!(), + Value::Boolean(_) => type_bool!(), + Value::String(_) => type_string!(), + Value::Tuple(_) => type_struct!(), + Value::List(_) => type_array!(), + Value::Bag(_) => type_bag!(), _ => { self.errors.push(TypingError::NotYetImplemented( "Unsupported Literal".to_string(), )); - PartiqlShape::Undefined + type_undefined!() } }; @@ -410,14 +414,14 @@ impl<'c> PlanTyper<'c> { fn element_type<'a>(&'a mut self, ty: &'a PartiqlShape) -> PartiqlShape { match ty { - PartiqlShape::Dynamic => dynamic!(), + PartiqlShape::Dynamic => type_dynamic!(), PartiqlShape::Static(s) => match s.ty() { Static::Bag(b) => b.element_type().clone(), Static::Array(a) => a.element_type().clone(), _ => ty.clone(), }, - undefined!() => { - todo!("Undefined type in catalog") + type_undefined!() => { + todo!("type_undefined type in catalog") } PartiqlShape::AnyOf(_any_of) => ty.clone(), } @@ -432,10 +436,10 @@ impl<'c> PlanTyper<'c> { Some(ty.clone()) } else if let Ok(s) = derived_type.expect_struct() { if s.is_partial() { - Some(dynamic!()) + Some(type_dynamic!()) } else { match &self.typing_mode { - TypingMode::Permissive => Some(undefined!()), + TypingMode::Permissive => Some(type_undefined!()), TypingMode::Strict => { self.errors.push(TypingError::TypeCheck(format!( "No Typing Information for {:?} in closed Schema {:?}", @@ -446,7 +450,7 @@ impl<'c> PlanTyper<'c> { } } } else if derived_type.is_dynamic() { - Some(dynamic!()) + Some(type_dynamic!()) } else { self.errors.push(TypingError::IllegalState(format!( "Illegal Derive Type {:?}", @@ -510,7 +514,7 @@ impl<'c> PlanTyper<'c> { let ty = self.element_type(type_entry.ty()); ty } else { - undefined!() + type_undefined!() } } @@ -521,14 +525,17 @@ impl<'c> PlanTyper<'c> { } } - undefined!() + type_undefined!() } - fn type_with_undefined(&mut self, key: &SymbolPrimitive) { + fn type_with_type_undefined(&mut self, key: &SymbolPrimitive) { if let TypingMode::Permissive = &self.typing_mode { // TODO Revise this once the following discussion is conclusive and spec. is // in place: https://github.com/partiql/partiql-spec/discussions/64 - let type_ctx = ty_ctx![(&ty_env![(key.clone(), undefined!())], &undefined!())]; + let type_ctx = ty_ctx![( + &ty_env![(key.clone(), type_undefined!())], + &type_undefined!() + )]; self.type_env_stack.push(type_ctx); } @@ -544,7 +551,7 @@ impl<'c> PlanTyper<'c> { "Unexpected Typing Environment; expected typing environment with only one type but found {:?} types", &env.len() ))); - undefined!() + type_undefined!() } else { env[0].clone() } @@ -552,7 +559,7 @@ impl<'c> PlanTyper<'c> { fn type_varef(&mut self, key: &SymbolPrimitive, ty: &PartiqlShape) { if ty.is_undefined() { - self.type_with_undefined(key); + self.type_with_type_undefined(key); } else { let mut new_type_env = LocalTypeEnv::new(); if let Ok(s) = ty.expect_struct() { @@ -598,7 +605,10 @@ mod tests { use partiql_ast_passes::error::AstTransformationError; use partiql_catalog::{PartiqlCatalog, TypeEnvEntry}; use partiql_parser::{Parsed, Parser}; - use partiql_types::{bag, int, r#struct, str, struct_fields, BagType, StructType}; + use partiql_types::{ + struct_fields, type_bag, type_int_with_const_id, type_string_with_const_id, + type_struct_with_const_id, type_undefined, BagType, StructType, + }; #[test] fn simple_sfw() { @@ -609,15 +619,15 @@ mod tests { create_customer_schema( false, [ - StructField::new("id", int!()), - StructField::new("name", str!()), - StructField::new("age", dynamic!()), + StructField::new("id", type_int_with_const_id!()), + StructField::new("name", type_string_with_const_id!()), + StructField::new("age", type_dynamic!()), ] .into(), ), vec![ - StructField::new("id", int!()), - StructField::new("name", str!()), + StructField::new("id", type_int_with_const_id!()), + StructField::new("name", type_string_with_const_id!()), ], ) .expect("Type"); @@ -629,15 +639,15 @@ mod tests { create_customer_schema( false, [ - StructField::new("id", int!()), - StructField::new("name", str!()), - StructField::new("age", dynamic!()), + StructField::new("id", type_int_with_const_id!()), + StructField::new("name", type_string_with_const_id!()), + StructField::new("age", type_dynamic!()), ] .into(), ), vec![ - StructField::new("id", int!()), - StructField::new("name", str!()), + StructField::new("id", type_int_with_const_id!()), + StructField::new("name", type_string_with_const_id!()), ], ) .expect("Type"); @@ -649,16 +659,16 @@ mod tests { create_customer_schema( true, [ - StructField::new("id", int!()), - StructField::new("name", str!()), - StructField::new("age", dynamic!()), + StructField::new("id", type_int_with_const_id!()), + StructField::new("name", type_string_with_const_id!()), + StructField::new("age", type_dynamic!()), ] .into(), ), vec![ - StructField::new("id", int!()), - StructField::new("name", str!()), - StructField::new("age", dynamic!()), + StructField::new("id", type_int_with_const_id!()), + StructField::new("name", type_string_with_const_id!()), + StructField::new("age", type_dynamic!()), ], ) .expect("Type"); @@ -670,22 +680,22 @@ mod tests { create_customer_schema( false, [ - StructField::new("id", int!()), - StructField::new("name", str!()), + StructField::new("id", type_int_with_const_id!()), + StructField::new("name", type_string_with_const_id!()), ] .into(), ), vec![ - StructField::new("id", int!()), - StructField::new("name", str!()), - StructField::new("age", undefined!()), + StructField::new("id", type_int_with_const_id!()), + StructField::new("name", type_string_with_const_id!()), + StructField::new("age", type_undefined!()), ], ) .expect("Type"); // Open Schema with `Strict` typing mode and `age` in nested attribute. - let details_fields = struct_fields![("age", int!())]; - let details = r#struct![IndexSet::from([details_fields])]; + let details_fields = struct_fields![("age", type_int_with_const_id!())]; + let details = type_struct_with_const_id![IndexSet::from([details_fields])]; assert_query_typing( TypingMode::Strict, @@ -693,16 +703,16 @@ mod tests { create_customer_schema( true, [ - StructField::new("id", int!()), - StructField::new("name", str!()), + StructField::new("id", type_int_with_const_id!()), + StructField::new("name", type_string_with_const_id!()), StructField::new("details", details.clone()), ] .into(), ), vec![ - StructField::new("id", int!()), - StructField::new("name", str!()), - StructField::new("age", int!()), + StructField::new("id", type_int_with_const_id!()), + StructField::new("name", type_string_with_const_id!()), + StructField::new("age", type_int_with_const_id!()), ], ) .expect("Type"); @@ -712,15 +722,15 @@ mod tests { TypingMode::Strict, "SELECT customers.id, customers.name, customers.details.age, customers.details.foo.bar FROM customers", create_customer_schema(true, [ - StructField::new("id", int!()), - StructField::new("name", str!()), + StructField::new("id", type_int_with_const_id!()), + StructField::new("name", type_string_with_const_id!()), StructField::new("details", details.clone()), ].into()), vec![ - StructField::new("id", int!()), - StructField::new("name", str!()), - StructField::new("age", int!()), - StructField::new("bar", dynamic!()), + StructField::new("id", type_int_with_const_id!()), + StructField::new("name", type_string_with_const_id!()), + StructField::new("age", type_int_with_const_id!()), + StructField::new("bar", type_dynamic!()), ], ) .expect("Type"); @@ -729,8 +739,8 @@ mod tests { #[test] fn simple_sfw_with_alias() { // Open Schema with `Strict` typing mode and `age` in nested attribute. - let details_fields = struct_fields![("age", int!())]; - let details = r#struct![IndexSet::from([details_fields])]; + let details_fields = struct_fields![("age", type_int_with_const_id!())]; + let details = type_struct_with_const_id![IndexSet::from([details_fields])]; // TODO Revise this behavior once the following discussion is conclusive and spec. is // in place: https://github.com/partiql/partiql-spec/discussions/65 @@ -740,13 +750,13 @@ mod tests { create_customer_schema( true, [ - StructField::new("id", int!()), - StructField::new("name", str!()), + StructField::new("id", type_int_with_const_id!()), + StructField::new("name", type_string_with_const_id!()), StructField::new("details", details.clone()), ] .into(), ), - vec![StructField::new("age", int!())], + vec![StructField::new("age", type_int_with_const_id!())], ) .expect("Type"); @@ -757,15 +767,15 @@ mod tests { create_customer_schema( false, [ - StructField::new("id", int!()), - StructField::new("name", str!()), - StructField::new("age", dynamic!()), + StructField::new("id", type_int_with_const_id!()), + StructField::new("name", type_string_with_const_id!()), + StructField::new("age", type_dynamic!()), ] .into(), ), vec![ - StructField::new("my_id", int!()), - StructField::new("my_name", str!()), + StructField::new("my_id", type_int_with_const_id!()), + StructField::new("my_name", type_string_with_const_id!()), ], ) .expect("Type"); @@ -774,7 +784,7 @@ mod tests { #[test] fn simple_sfw_err() { // Closed Schema with `Strict` typing mode and `age` non-existent projection. - let err1 = r#"No Typing Information for SymbolPrimitive { value: "age", case: CaseInsensitive } in closed Schema Static(StaticType { ty: Struct(StructType { constraints: {Fields({StructField { optional: false, name: "id", ty: Static(StaticType { ty: Int, nullable: true }) }, StructField { optional: false, name: "name", ty: Static(StaticType { ty: String, nullable: true }) }}), Open(false)} }), nullable: true })"#; + let err1 = r#"No Typing Information for SymbolPrimitive { value: "age", case: CaseInsensitive } in closed Schema Static(StaticType { id: NodeId(1), ty: Struct(StructType { constraints: {Fields({StructField { optional: false, name: "id", ty: Static(StaticType { id: NodeId(1), ty: Int, nullable: true }) }, StructField { optional: false, name: "name", ty: Static(StaticType { id: NodeId(1), ty: String, nullable: true }) }}), Open(false)} }), nullable: true })"#; assert_err( assert_query_typing( @@ -783,32 +793,24 @@ mod tests { create_customer_schema( false, [ - StructField::new("id", int!()), - StructField::new("name", str!()), + StructField::new("id", type_int_with_const_id!()), + StructField::new("name", type_string_with_const_id!()), ] .into(), ), vec![], ), vec![TypingError::TypeCheck(err1.to_string())], - Some(bag![r#struct![IndexSet::from([StructConstraint::Fields( - [ - StructField::new("id", int!()), - StructField::new("name", str!()), - StructField::new("age", undefined!()), - ] - .into() - ),])]]), ); // Closed Schema with `Strict` typing mode and `bar` non-existent projection from closed nested `details`. - let details_fields = struct_fields![("age", int!())]; - let details = r#struct![IndexSet::from([ + let details_fields = struct_fields![("age", type_int_with_const_id!())]; + let details = type_struct_with_const_id![IndexSet::from([ details_fields, StructConstraint::Open(false) ])]; - let err1 = r#"No Typing Information for SymbolPrimitive { value: "details", case: CaseInsensitive } in closed Schema Static(StaticType { ty: Struct(StructType { constraints: {Fields({StructField { optional: false, name: "age", ty: Static(StaticType { ty: Int, nullable: true }) }}), Open(false)} }), nullable: true })"#; + let err1 = r#"No Typing Information for SymbolPrimitive { value: "details", case: CaseInsensitive } in closed Schema Static(StaticType { id: NodeId(1), ty: Struct(StructType { constraints: {Fields({StructField { optional: false, name: "age", ty: Static(StaticType { id: NodeId(1), ty: Int, nullable: true }) }}), Open(false)} }), nullable: true })"#; let err2 = r"Illegal Derive Type Undefined"; assert_err( @@ -818,8 +820,8 @@ mod tests { create_customer_schema( false, [ - StructField::new("id", int!()), - StructField::new("name", str!()), + StructField::new("id", type_int_with_const_id!()), + StructField::new("name", type_string_with_const_id!()), StructField::new("details", details), ] .into(), @@ -830,40 +832,22 @@ mod tests { TypingError::TypeCheck(err1.to_string()), TypingError::IllegalState(err2.to_string()), ], - Some(bag![r#struct![IndexSet::from([StructConstraint::Fields( - [ - StructField::new("id", int!()), - StructField::new("name", str!()), - StructField::new("bar", undefined!()), - ] - .into() - ),])]]), ); } - fn assert_err( - result: Result<(), TypeErr>, - expected_errors: Vec, - output: Option, - ) { + fn assert_err(result: Result<(), TypeErr>, expected_errors: Vec) { match result { Ok(()) => { panic!("Expected Error"); } Err(e) => { - assert_eq!( - e, - TypeErr { - errors: expected_errors, - output, - } - ); + assert_eq!(e.errors, expected_errors); } }; } fn create_customer_schema(is_open: bool, fields: IndexSet) -> PartiqlShape { - bag![r#struct![IndexSet::from([ + type_bag![type_struct_with_const_id![IndexSet::from([ StructConstraint::Fields(fields), StructConstraint::Open(is_open) ])]] diff --git a/partiql-types/Cargo.toml b/partiql-types/Cargo.toml index 84eed7b5..1c18d374 100644 --- a/partiql-types/Cargo.toml +++ b/partiql-types/Cargo.toml @@ -21,7 +21,7 @@ edition.workspace = true bench = false [dependencies] - +partiql-common = { path = "../partiql-common", version = "0.10.*"} ordered-float = "3.*" itertools = "0.10.*" unicase = "2.6" diff --git a/partiql-types/src/lib.rs b/partiql-types/src/lib.rs index 6a0b3616..50e58ce3 100644 --- a/partiql-types/src/lib.rs +++ b/partiql-types/src/lib.rs @@ -5,8 +5,11 @@ use derivative::Derivative; use indexmap::IndexSet; use itertools::Itertools; use miette::Diagnostic; +use partiql_common::node::{AutoNodeIdGenerator, NodeId, NodeIdGenerator}; +use std::collections::HashMap; use std::fmt::{Debug, Display, Formatter}; use std::hash::{Hash, Hasher}; +use std::sync::OnceLock; use thiserror::Error; #[derive(Debug, Clone, Eq, PartialEq, Hash, Error, Diagnostic)] @@ -35,84 +38,110 @@ where } #[macro_export] -macro_rules! dynamic { +macro_rules! type_dynamic { () => { - $crate::PartiqlShape::Dynamic + $crate::PartiqlShapeBuilder::init_or_get().new_dynamic() }; } #[macro_export] -macro_rules! int { +macro_rules! type_int { () => { - $crate::PartiqlShape::new($crate::Static::Int) + $crate::PartiqlShapeBuilder::init_or_get().new_static($crate::Static::Int) }; } #[macro_export] -macro_rules! int8 { +macro_rules! type_int8 { () => { - $crate::PartiqlShape::new($crate::Static::Int8) + $crate::PartiqlShapeBuilder::init_or_get().new_static($crate::Static::Int8) }; } #[macro_export] -macro_rules! int16 { +macro_rules! type_int16 { () => { - $crate::PartiqlShape::new($crate::Static::Int16) + $crate::PartiqlShapeBuilder::init_or_get().new_static($crate::Static::Int16) }; } #[macro_export] -macro_rules! int32 { +macro_rules! type_int32 { () => { - $crate::PartiqlShape::new($crate::Static::Int32) + $crate::PartiqlShapeBuilder::init_or_get().new_static($crate::Static::Int32) }; } #[macro_export] -macro_rules! int64 { +macro_rules! type_int64 { () => { - $crate::PartiqlShape::new($crate::Static::Int64) + $crate::PartiqlShapeBuilder::init_or_get().new_static($crate::Static::Int64) }; } #[macro_export] -macro_rules! dec { +macro_rules! type_decimal { () => { - $crate::PartiqlShape::new($crate::Static::Decimal) + $crate::PartiqlShapeBuilder::init_or_get().new_static($crate::Static::Decimal) }; } // TODO add macro_rule for Decimal with precision and scale #[macro_export] -macro_rules! f32 { +macro_rules! type_float32 { () => { - $crate::PartiqlShape::new($crate::Static::Float32) + $crate::PartiqlShapeBuilder::init_or_get().new_static($crate::Static::Float32) }; } #[macro_export] -macro_rules! f64 { +macro_rules! type_float64 { () => { - $crate::PartiqlShape::new($crate::Static::Float64) + $crate::PartiqlShapeBuilder::init_or_get().new_static($crate::Static::Float64) }; } #[macro_export] -macro_rules! str { +macro_rules! type_string { () => { - $crate::PartiqlShape::new($crate::Static::String) + $crate::PartiqlShapeBuilder::init_or_get().new_static($crate::Static::String) }; } #[macro_export] -macro_rules! r#struct { +macro_rules! type_bool { () => { - $crate::PartiqlShape::new_struct(StructType::new_any()) + $crate::PartiqlShapeBuilder::init_or_get().new_static($crate::Static::Bool) + }; +} + +#[macro_export] +macro_rules! type_numeric { + () => { + [ + $crate::PartiqlShapeBuilder::init_or_get().new_static($crate::Static::Int), + $crate::PartiqlShapeBuilder::init_or_get().new_static($crate::Static::Float32), + $crate::PartiqlShapeBuilder::init_or_get().new_static($crate::Static::Float64), + $crate::PartiqlShapeBuilder::init_or_get().new_static($crate::Static::Decimal), + ] + }; +} + +#[macro_export] +macro_rules! type_datetime { + () => { + $crate::PartiqlShapeBuilder::init_or_get().new_static($crate::Static::DateTime) + }; +} + +#[macro_export] +macro_rules! type_struct { + () => { + $crate::PartiqlShapeBuilder::init_or_get().new_struct(StructType::new_any()) }; ($elem:expr) => { - $crate::PartiqlShape::new_struct(StructType::new($elem)) + $crate::PartiqlShapeBuilder::init_or_get().new_struct(StructType::new($elem)) }; } @@ -124,32 +153,68 @@ macro_rules! struct_fields { } #[macro_export] -macro_rules! r#bag { +macro_rules! type_bag { () => { - $crate::PartiqlShape::new_bag(BagType::new_any()); + $crate::PartiqlShapeBuilder::init_or_get().new_bag(BagType::new_any()); }; ($elem:expr) => { - $crate::PartiqlShape::new_bag(BagType::new(Box::new($elem))) + $crate::PartiqlShapeBuilder::init_or_get().new_bag(BagType::new(Box::new($elem))) }; } #[macro_export] -macro_rules! r#array { +macro_rules! type_array { () => { - $crate::PartiqlShape::new_array(ArrayType::new_any()); + $crate::PartiqlShapeBuilder::init_or_get().new_array(ArrayType::new_any()); }; ($elem:expr) => { - $crate::PartiqlShape::new_array(ArrayType::new(Box::new($elem))) + $crate::PartiqlShapeBuilder::init_or_get().new_array(ArrayType::new(Box::new($elem))) }; } #[macro_export] -macro_rules! undefined { +macro_rules! type_undefined { () => { $crate::PartiqlShape::Undefined }; } +// Types with constant `NodeId`, e.g., `NodeId(1)` convenient for testing or use-cases with no +// requirement for unique node ids. + +#[macro_export] +macro_rules! type_int_with_const_id { + () => { + $crate::PartiqlShapeBuilder::init_or_get().new_static_with_const_id($crate::Static::Int) + }; +} + +#[macro_export] +macro_rules! type_float32_with_const_id { + () => { + $crate::PartiqlShapeBuilder::init_or_get().new_static_with_const_id($crate::Static::Float32) + }; +} + +#[macro_export] +macro_rules! type_string_with_const_id { + () => { + $crate::PartiqlShapeBuilder::init_or_get().new_static_with_const_id($crate::Static::String) + }; +} + +#[macro_export] +macro_rules! type_struct_with_const_id { + () => { + $crate::PartiqlShapeBuilder::init_or_get() + .new_static_with_const_id(Static::Struct(StructType::new_any())) + }; + ($elem:expr) => { + $crate::PartiqlShapeBuilder::init_or_get() + .new_static_with_const_id(Static::Struct(StructType::new($elem))) + }; +} + /// Represents a PartiQL Shape #[derive(Debug, Clone, Eq, PartialEq, Hash)] // With this implementation `Dynamic` and `AnyOf` cannot have `nullability`; this does not mean their @@ -162,240 +227,44 @@ pub enum PartiqlShape { Undefined, } -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub struct StaticType { - ty: Static, - nullable: bool, -} - -#[derive(Debug, Clone, Eq, PartialEq, Hash)] -pub enum Static { - // Scalar Types - Int, - Int8, - Int16, - Int32, - Int64, - Bool, - Decimal, - DecimalP(usize, usize), - - Float32, - Float64, - - String, - StringFixed(usize), - StringVarying(usize), - - DateTime, - - // Container Types - Struct(StructType), - Bag(BagType), - Array(ArrayType), - // TODO Add BitString, ByteString, Blob, Clob, and Graph types -} - -impl Static { - pub fn is_scalar(&self) -> bool { - !matches!(self, Static::Struct(_) | Static::Bag(_) | Static::Array(_)) - } - - pub fn is_sequence(&self) -> bool { - matches!(self, Static::Bag(_) | Static::Array(_)) - } - - pub fn is_struct(&self) -> bool { - matches!(self, Static::Struct(_)) - } -} - -impl StaticType { - #[must_use] - pub fn new(ty: Static) -> StaticType { - StaticType { ty, nullable: true } - } - - #[must_use] - pub fn new_non_nullable(ty: Static) -> StaticType { - StaticType { - ty, - nullable: false, - } - } - - #[must_use] - pub fn ty(&self) -> &Static { - &self.ty - } - - #[must_use] - pub fn is_nullable(&self) -> bool { - self.nullable - } - - #[must_use] - pub fn is_not_nullable(&self) -> bool { - !self.nullable - } - - pub fn is_scalar(&self) -> bool { - self.ty.is_scalar() - } - - pub fn is_sequence(&self) -> bool { - self.ty.is_sequence() - } - - pub fn is_struct(&self) -> bool { - self.ty.is_struct() - } -} - -impl Display for StaticType { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let nullable = if self.nullable { - "nullable" - } else { - "non_nullable" - }; - write!(f, "({}, {})", self.ty, nullable) - } -} - -impl Display for Static { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let x = match self { - Static::Int => "Int".to_string(), - Static::Int8 => "Int8".to_string(), - Static::Int16 => "Int16".to_string(), - Static::Int32 => "Int32".to_string(), - Static::Int64 => "Int64".to_string(), - Static::Bool => "Bool".to_string(), - Static::Decimal => "Decimal".to_string(), - Static::DecimalP(_, _) => { - todo!() - } - Static::Float32 => "Float32".to_string(), - Static::Float64 => "Float64".to_string(), - Static::String => "String".to_string(), - Static::StringFixed(_) => { - todo!() - } - Static::StringVarying(_) => { - todo!() - } - Static::DateTime => "DateTime".to_string(), - Static::Struct(_) => "Struct".to_string(), - Static::Bag(_) => "Bag".to_string(), - Static::Array(_) => "Array".to_string(), - }; - write!(f, "{x}") - } -} - -pub const TYPE_DYNAMIC: PartiqlShape = PartiqlShape::Dynamic; -pub const TYPE_BOOL: PartiqlShape = PartiqlShape::new(Static::Bool); -pub const TYPE_INT: PartiqlShape = PartiqlShape::new(Static::Int); -pub const TYPE_INT8: PartiqlShape = PartiqlShape::new(Static::Int8); -pub const TYPE_INT16: PartiqlShape = PartiqlShape::new(Static::Int16); -pub const TYPE_INT32: PartiqlShape = PartiqlShape::new(Static::Int32); -pub const TYPE_INT64: PartiqlShape = PartiqlShape::new(Static::Int64); -pub const TYPE_REAL: PartiqlShape = PartiqlShape::new(Static::Float32); -pub const TYPE_DOUBLE: PartiqlShape = PartiqlShape::new(Static::Float64); -pub const TYPE_DECIMAL: PartiqlShape = PartiqlShape::new(Static::Decimal); -pub const TYPE_STRING: PartiqlShape = PartiqlShape::new(Static::String); -pub const TYPE_DATETIME: PartiqlShape = PartiqlShape::new(Static::DateTime); -pub const TYPE_NUMERIC_TYPES: [PartiqlShape; 4] = [TYPE_INT, TYPE_REAL, TYPE_DOUBLE, TYPE_DECIMAL]; - #[allow(dead_code)] impl PartiqlShape { - #[must_use] - pub const fn new(ty: Static) -> PartiqlShape { - PartiqlShape::Static(StaticType { ty, nullable: true }) - } - #[must_use] - pub const fn new_non_nullable(ty: Static) -> PartiqlShape { - PartiqlShape::Static(StaticType { - ty, - nullable: false, - }) - } - - #[must_use] - pub fn as_non_nullable(&self) -> Option { - if let PartiqlShape::Static(stype) = self { - Some(PartiqlShape::Static(StaticType { - ty: stype.ty.clone(), - nullable: false, - })) - } else { - None - } - } - - #[must_use] - pub fn new_dynamic() -> PartiqlShape { - PartiqlShape::Dynamic - } - - #[must_use] - pub fn new_struct(s: StructType) -> PartiqlShape { - PartiqlShape::new(Static::Struct(s)) - } - - #[must_use] - pub fn new_bag(b: BagType) -> PartiqlShape { - PartiqlShape::new(Static::Bag(b)) - } - - #[must_use] - pub fn new_array(a: ArrayType) -> PartiqlShape { - PartiqlShape::new(Static::Array(a)) - } - - pub fn any_of(types: I) -> PartiqlShape - where - I: IntoIterator, - { - let any_of = AnyOf::from_iter(types); - match any_of.types.len() { - 0 => TYPE_DYNAMIC, - 1 => { - let AnyOf { types } = any_of; - types.into_iter().next().unwrap() - } - // TODO figure out what does it mean for a Union to be nullable or not - _ => PartiqlShape::AnyOf(any_of), - } - } - #[must_use] pub fn union_with(self, other: PartiqlShape) -> PartiqlShape { match (self, other) { - (PartiqlShape::Dynamic, _) | (_, PartiqlShape::Dynamic) => PartiqlShape::new_dynamic(), + (PartiqlShape::Dynamic, _) | (_, PartiqlShape::Dynamic) => PartiqlShape::Dynamic, (PartiqlShape::AnyOf(lhs), PartiqlShape::AnyOf(rhs)) => { - PartiqlShape::any_of(lhs.types.into_iter().chain(rhs.types)) + PartiqlShapeBuilder::init_or_get().any_of(lhs.types.into_iter().chain(rhs.types)) } (PartiqlShape::AnyOf(anyof), other) | (other, PartiqlShape::AnyOf(anyof)) => { let mut types = anyof.types; types.insert(other); - PartiqlShape::any_of(types) + PartiqlShapeBuilder::init_or_get().any_of(types) } (l, r) => { let types = [l, r]; - PartiqlShape::any_of(types) + PartiqlShapeBuilder::init_or_get().any_of(types) } } } + #[must_use] + pub fn static_type_id(&self) -> Option { + if let PartiqlShape::Static(StaticType { id, .. }) = self { + Some(*id) + } else { + None + } + } + #[must_use] pub fn is_string(&self) -> bool { matches!( &self, PartiqlShape::Static(StaticType { ty: Static::String, - nullable: true + nullable: true, + .. }) ) } @@ -406,7 +275,8 @@ impl PartiqlShape { *self, PartiqlShape::Static(StaticType { ty: Static::Struct(_), - nullable: true + nullable: true, + .. }) ) } @@ -417,13 +287,15 @@ impl PartiqlShape { *self, PartiqlShape::Static(StaticType { ty: Static::Bag(_), - nullable: true + nullable: true, + .. }) ) || matches!( *self, PartiqlShape::Static(StaticType { ty: Static::Array(_), - nullable: true + nullable: true, + .. }) ) } @@ -440,7 +312,8 @@ impl PartiqlShape { *self, PartiqlShape::Static(StaticType { ty: Static::Array(_), - nullable: true + nullable: true, + .. }) ) } @@ -451,7 +324,8 @@ impl PartiqlShape { *self, PartiqlShape::Static(StaticType { ty: Static::Bag(_), - nullable: true + nullable: true, + .. }) ) } @@ -462,7 +336,8 @@ impl PartiqlShape { *self, PartiqlShape::Static(StaticType { ty: Static::Array(_), - nullable: true + nullable: true, + .. }) ) } @@ -479,11 +354,13 @@ impl PartiqlShape { pub fn expect_bool(&self) -> ShapeResult { if let PartiqlShape::Static(StaticType { + id, ty: Static::Bool, nullable: n, }) = self { Ok(StaticType { + id: *id, ty: Static::Bool, nullable: *n, }) @@ -492,6 +369,18 @@ impl PartiqlShape { } } + pub fn expect_bag(&self) -> ShapeResult { + if let PartiqlShape::Static(StaticType { + ty: Static::Bag(bag), + .. + }) = self + { + Ok(bag.clone()) + } else { + Err(ShapeResultError::UnexpectedType(format!("{self}"))) + } + } + pub fn expect_struct(&self) -> ShapeResult { if let PartiqlShape::Static(StaticType { ty: Static::Struct(stct), @@ -543,6 +432,118 @@ impl Display for PartiqlShape { } } +#[derive(Default)] +pub struct PartiqlShapeBuilder { + id_gen: AutoNodeIdGenerator, +} + +impl PartiqlShapeBuilder { + /// A thread-safe method for creating PartiQL shapes with guaranteed uniqueness over + /// generated `NodeId`s. + #[track_caller] + pub fn init_or_get() -> &'static PartiqlShapeBuilder { + static SHAPE_BUILDER: OnceLock = OnceLock::new(); + SHAPE_BUILDER.get_or_init(PartiqlShapeBuilder::default) + } + + #[must_use] + pub fn new_static(&self, ty: Static) -> PartiqlShape { + let id = self.id_gen.id(); + let id = id.read().expect("NodeId read lock"); + PartiqlShape::Static(StaticType { + id: *id, + ty, + nullable: true, + }) + } + + #[must_use] + pub fn new_static_with_const_id(&self, ty: Static) -> PartiqlShape { + PartiqlShape::Static(StaticType { + id: NodeId(1), + ty, + nullable: true, + }) + } + + #[must_use] + pub fn new_non_nullable_static(&self, ty: Static) -> PartiqlShape { + let id = self.id_gen.id(); + let id = id.read().expect("NodeId read lock"); + PartiqlShape::Static(StaticType { + id: *id, + ty, + nullable: false, + }) + } + + #[must_use] + pub fn new_non_nullable_static_with_const_id(&self, ty: Static) -> PartiqlShape { + PartiqlShape::Static(StaticType { + id: NodeId(1), + ty, + nullable: false, + }) + } + + #[must_use] + pub fn new_dynamic(&self) -> PartiqlShape { + PartiqlShape::Dynamic + } + + #[must_use] + pub fn new_undefined(&self) -> PartiqlShape { + PartiqlShape::Dynamic + } + + #[must_use] + pub fn new_struct(&self, s: StructType) -> PartiqlShape { + self.new_static(Static::Struct(s)) + } + + #[must_use] + pub fn new_bag(&self, b: BagType) -> PartiqlShape { + self.new_static(Static::Bag(b)) + } + + #[must_use] + pub fn new_array(&self, a: ArrayType) -> PartiqlShape { + self.new_static(Static::Array(a)) + } + + // The AnyOf::from_iter(types) uses an IndexSet internally to + // deduplicate types, thus the match on any_of.types.len() could + // "flatten" AnyOfs that had duplicates. + // With the addition of IDs, this deduplication no longer happens. + // TODO revisit the current implementaion and consider an implementation + // that allows merging of the `metas` for the same type, e.g., with a + // user-defined control. + pub fn any_of(&self, types: I) -> PartiqlShape + where + I: IntoIterator, + { + let any_of = AnyOf::from_iter(types); + match any_of.types.len() { + 0 => type_dynamic!(), + 1 => { + let AnyOf { types } = any_of; + types.into_iter().next().unwrap() + } + // TODO figure out what does it mean for a Union to be nullable or not + _ => PartiqlShape::AnyOf(any_of), + } + } + + #[must_use] + pub fn as_non_nullable(&self, shape: &PartiqlShape) -> Option { + if let PartiqlShape::Static(stype) = shape { + Some(self.new_non_nullable_static(stype.ty.clone())) + } else { + None + } + } +} + #[derive(Derivative, Eq, Debug, Clone)] #[derivative(PartialEq, Hash)] #[allow(dead_code)] @@ -570,6 +571,134 @@ impl FromIterator for AnyOf { } } +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct StaticType { + id: NodeId, + ty: Static, + nullable: bool, +} + +impl StaticType { + #[must_use] + pub fn ty(&self) -> &Static { + &self.ty + } + + pub fn ty_id(&self) -> &NodeId { + &self.id + } + + #[must_use] + pub fn is_nullable(&self) -> bool { + self.nullable + } + + #[must_use] + pub fn is_not_nullable(&self) -> bool { + !self.nullable + } + + pub fn is_scalar(&self) -> bool { + self.ty.is_scalar() + } + + pub fn is_sequence(&self) -> bool { + self.ty.is_sequence() + } + + pub fn is_struct(&self) -> bool { + self.ty.is_struct() + } +} + +impl Display for StaticType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let nullable = if self.nullable { + "nullable" + } else { + "non_nullable" + }; + write!(f, "({}, {})", self.ty, nullable) + } +} + +pub type StaticTypeMetas = HashMap; + +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub enum Static { + // Scalar Types + Int, + Int8, + Int16, + Int32, + Int64, + Bool, + Decimal, + DecimalP(usize, usize), + + Float32, + Float64, + + String, + StringFixed(usize), + StringVarying(usize), + + DateTime, + + // Container Types + Struct(StructType), + Bag(BagType), + Array(ArrayType), + // TODO Add BitString, ByteString, Blob, Clob, and Graph types +} + +impl Static { + pub fn is_scalar(&self) -> bool { + !matches!(self, Static::Struct(_) | Static::Bag(_) | Static::Array(_)) + } + + pub fn is_sequence(&self) -> bool { + matches!(self, Static::Bag(_) | Static::Array(_)) + } + + pub fn is_struct(&self) -> bool { + matches!(self, Static::Struct(_)) + } +} + +impl Display for Static { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let x = match self { + Static::Int => "Int".to_string(), + Static::Int8 => "Int8".to_string(), + Static::Int16 => "Int16".to_string(), + Static::Int32 => "Int32".to_string(), + Static::Int64 => "Int64".to_string(), + Static::Bool => "Bool".to_string(), + Static::Decimal => "Decimal".to_string(), + Static::DecimalP(_, _) => { + todo!() + } + Static::Float32 => "Float32".to_string(), + Static::Float64 => "Float64".to_string(), + Static::String => "String".to_string(), + Static::StringFixed(_) => { + todo!() + } + Static::StringVarying(_) => { + todo!() + } + Static::DateTime => "DateTime".to_string(), + Static::Struct(_) => "Struct".to_string(), + Static::Bag(_) => "Bag".to_string(), + Static::Array(_) => "Array".to_string(), + }; + write!(f, "{x}") + } +} + +pub const TYPE_DYNAMIC: PartiqlShape = PartiqlShape::Dynamic; + #[derive(Derivative, Eq, Debug, Clone)] #[derivative(PartialEq, Hash)] #[allow(dead_code)] @@ -754,32 +883,93 @@ impl ArrayType { #[cfg(test)] mod tests { - use crate::{PartiqlShape, TYPE_INT, TYPE_REAL}; + use crate::{ + BagType, PartiqlShape, PartiqlShapeBuilder, Static, StructConstraint, StructField, + StructType, + }; + use indexmap::IndexSet; #[test] fn union() { - let expect_int = TYPE_INT; - assert_eq!(expect_int, TYPE_INT.union_with(TYPE_INT)); + let expect_int = type_int_with_const_id!(); + assert_eq!( + expect_int, + type_int_with_const_id!().union_with(type_int_with_const_id!()) + ); - let expect_nums = PartiqlShape::any_of([TYPE_INT, TYPE_REAL]); - assert_eq!(expect_nums, TYPE_INT.union_with(TYPE_REAL)); + let expect_nums = PartiqlShapeBuilder::init_or_get() + .any_of([type_int_with_const_id!(), type_float32_with_const_id!()]); assert_eq!( expect_nums, - PartiqlShape::any_of([ - TYPE_INT.union_with(TYPE_REAL), - TYPE_INT.union_with(TYPE_REAL) + type_int_with_const_id!().union_with(type_float32_with_const_id!()) + ); + assert_eq!( + expect_nums, + PartiqlShapeBuilder::init_or_get().any_of([ + type_int_with_const_id!().union_with(type_float32_with_const_id!()), + type_int_with_const_id!().union_with(type_float32_with_const_id!()) ]) ); assert_eq!( expect_nums, - PartiqlShape::any_of([ - TYPE_INT.union_with(TYPE_REAL), - TYPE_INT.union_with(TYPE_REAL), - PartiqlShape::any_of([ - TYPE_INT.union_with(TYPE_REAL), - TYPE_INT.union_with(TYPE_REAL) + PartiqlShapeBuilder::init_or_get().any_of([ + type_int_with_const_id!().union_with(type_float32_with_const_id!()), + type_int_with_const_id!().union_with(type_float32_with_const_id!()), + PartiqlShapeBuilder::init_or_get().any_of([ + type_int_with_const_id!().union_with(type_float32_with_const_id!()), + type_int_with_const_id!().union_with(type_float32_with_const_id!()) ]) ]) ); } + + #[test] + fn unique_node_ids() { + let age_field = struct_fields![("age", type_int!())]; + let details = type_struct![IndexSet::from([age_field])]; + + let fields = [ + StructField::new("id", type_int!()), + StructField::new("name", type_string!()), + StructField::new("details", details.clone()), + ]; + + let row = type_struct![IndexSet::from([ + StructConstraint::Fields(IndexSet::from(fields)), + StructConstraint::Open(false) + ])]; + + let shape = type_bag![row.clone()]; + + let mut ids = collect_ids(shape); + ids.sort_unstable(); + assert!(ids.windows(2).all(|w| w[0] != w[1])); + } + + fn collect_ids(row: PartiqlShape) -> Vec { + let mut out = vec![]; + match row { + PartiqlShape::Dynamic => {} + PartiqlShape::AnyOf(anyof) => { + for shape in anyof.types { + out.push(collect_ids(shape)); + } + } + PartiqlShape::Static(static_type) => { + out.push(vec![static_type.id.0]); + match static_type.ty { + Static::Struct(struct_type) => { + for f in struct_type.fields() { + out.push(collect_ids(f.ty.clone())); + } + } + Static::Bag(bag_type) => out.push(collect_ids(*bag_type.element_type)), + Static::Array(array_type) => out.push(collect_ids(*array_type.element_type)), + _ => {} + } + } + PartiqlShape::Undefined => {} + } + out.into_iter().flatten().collect() + } }