From 2931b2c2f4b47f2a0884fd31af7543920c03e84f Mon Sep 17 00:00:00 2001 From: Daniel Chambers Date: Wed, 17 Apr 2024 22:38:51 +1000 Subject: [PATCH] Update Rust SDK and add NDC v0.1.2 support (#42) --- Cargo.lock | 22 ++-- .../src/scalar_types_capabilities.rs | 34 +----- .../src/api_type_conversions/capabilities.rs | 60 ---------- .../src/api_type_conversions/mod.rs | 2 - .../src/api_type_conversions/query_request.rs | 2 +- crates/mongodb-connector/src/capabilities.rs | 105 ++++++++++++++++-- crates/mongodb-support/src/bson_type.rs | 72 ++++++++++++ 7 files changed, 183 insertions(+), 114 deletions(-) delete mode 100644 crates/mongodb-connector/src/api_type_conversions/capabilities.rs diff --git a/Cargo.lock b/Cargo.lock index 0e5b7bb6..1aad36ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1595,26 +1595,21 @@ dependencies = [ ] [[package]] -name = "ndc-client" -version = "0.1.1" -source = "git+http://github.com/hasura/ndc-spec.git?tag=v0.1.1#17c61946cc9a3ff6dcee1d535af33141213b639a" +name = "ndc-models" +version = "0.1.2" +source = "git+http://github.com/hasura/ndc-spec.git?tag=v0.1.2#6e7d12a31787d5f618099a42ddc0bea786438c00" dependencies = [ - "async-trait", "indexmap 2.2.5", - "opentelemetry", - "reqwest", "schemars", "serde", - "serde_derive", "serde_json", "serde_with 2.3.3", - "url", ] [[package]] name = "ndc-sdk" version = "0.1.0" -source = "git+https://github.com/hasura/ndc-sdk-rs.git#7b56fac3aba2bc6533d3163111377fd5fbeb3011" +source = "git+https://github.com/hasura/ndc-sdk-rs.git#7409334d2ec2ca1d05fb341e69c9f07af520d8e0" dependencies = [ "async-trait", "axum", @@ -1623,7 +1618,7 @@ dependencies = [ "clap", "http", "mime", - "ndc-client", + "ndc-models", "ndc-test", "opentelemetry", "opentelemetry-http", @@ -1645,14 +1640,14 @@ dependencies = [ [[package]] name = "ndc-test" -version = "0.1.1" -source = "git+http://github.com/hasura/ndc-spec.git?tag=v0.1.1#17c61946cc9a3ff6dcee1d535af33141213b639a" +version = "0.1.2" +source = "git+http://github.com/hasura/ndc-spec.git?tag=v0.1.2#6e7d12a31787d5f618099a42ddc0bea786438c00" dependencies = [ "async-trait", "clap", "colorful", "indexmap 2.2.5", - "ndc-client", + "ndc-models", "rand", "reqwest", "semver 1.0.20", @@ -1660,6 +1655,7 @@ dependencies = [ "serde_json", "thiserror", "tokio", + "url", ] [[package]] diff --git a/crates/mongodb-agent-common/src/scalar_types_capabilities.rs b/crates/mongodb-agent-common/src/scalar_types_capabilities.rs index faf79480..ea4bba6e 100644 --- a/crates/mongodb-agent-common/src/scalar_types_capabilities.rs +++ b/crates/mongodb-agent-common/src/scalar_types_capabilities.rs @@ -27,13 +27,13 @@ pub fn aggregate_functions( [(A::Count, S::Int)] .into_iter() .chain(iter_if( - is_ordered(scalar_type), + scalar_type.is_orderable(), [A::Min, A::Max] .into_iter() .map(move |op| (op, scalar_type)), )) .chain(iter_if( - is_numeric(scalar_type), + scalar_type.is_numeric(), [A::Avg, A::Sum] .into_iter() .map(move |op| (op, scalar_type)), @@ -44,11 +44,11 @@ pub fn comparison_operators( scalar_type: BsonScalarType, ) -> impl Iterator { iter_if( - is_comparable(scalar_type), + scalar_type.is_comparable(), [(C::Equal, scalar_type), (C::NotEqual, scalar_type)].into_iter(), ) .chain(iter_if( - is_ordered(scalar_type), + scalar_type.is_orderable(), [ C::LessThan, C::LessThanOrEqual, @@ -83,32 +83,6 @@ fn capabilities(scalar_type: BsonScalarType) -> ScalarTypeCapabilities { } } -fn numeric_types() -> [BsonScalarType; 4] { - [S::Double, S::Int, S::Long, S::Decimal] -} - -fn is_numeric(scalar_type: BsonScalarType) -> bool { - numeric_types().contains(&scalar_type) -} - -fn is_comparable(scalar_type: BsonScalarType) -> bool { - let not_comparable = [S::Regex, S::Javascript, S::JavascriptWithScope]; - !not_comparable.contains(&scalar_type) -} - -fn is_ordered(scalar_type: BsonScalarType) -> bool { - let ordered = [ - S::Double, - S::Decimal, - S::Int, - S::Long, - S::String, - S::Date, - S::Timestamp, - ]; - ordered.contains(&scalar_type) -} - /// If `condition` is true returns an iterator with the same items as the given `iter` input. /// Otherwise returns an empty iterator. fn iter_if(condition: bool, iter: impl Iterator) -> impl Iterator { diff --git a/crates/mongodb-connector/src/api_type_conversions/capabilities.rs b/crates/mongodb-connector/src/api_type_conversions/capabilities.rs deleted file mode 100644 index b2e83f81..00000000 --- a/crates/mongodb-connector/src/api_type_conversions/capabilities.rs +++ /dev/null @@ -1,60 +0,0 @@ -use std::collections::{BTreeMap, HashMap}; - -use dc_api_types as v2; -use mongodb_agent_common::comparison_function::ComparisonFunction; -use ndc_sdk::models as v3; - -pub fn v2_to_v3_scalar_type_capabilities( - scalar_types: HashMap, -) -> BTreeMap { - scalar_types - .into_iter() - .map(|(name, capabilities)| (name, v2_to_v3_capabilities(capabilities))) - .collect() -} - -fn v2_to_v3_capabilities(capabilities: v2::ScalarTypeCapabilities) -> v3::ScalarType { - v3::ScalarType { - representation: capabilities.graphql_type.as_ref().map(graphql_type_to_representation), - aggregate_functions: capabilities - .aggregate_functions - .unwrap_or_default() - .into_iter() - .map(|(name, result_type)| { - ( - name, - v3::AggregateFunctionDefinition { - result_type: v3::Type::Named { name: result_type }, - }, - ) - }) - .collect(), - comparison_operators: capabilities - .comparison_operators - .unwrap_or_default() - .into_iter() - .map(|(name, argument_type)| { - let definition = match ComparisonFunction::from_graphql_name(&name).ok() { - Some(ComparisonFunction::Equal) => v3::ComparisonOperatorDefinition::Equal, - // TODO: Handle "In" NDC-393 - _ => v3::ComparisonOperatorDefinition::Custom { - argument_type: v3::Type::Named { - name: argument_type, - }, - }, - }; - (name, definition) - }) - .collect(), - } -} - -fn graphql_type_to_representation(graphql_type: &v2::GraphQlType) -> v3::TypeRepresentation { - match graphql_type { - v2::GraphQlType::Int => v3::TypeRepresentation::Integer, - v2::GraphQlType::Float => v3::TypeRepresentation::Number, - v2::GraphQlType::String => v3::TypeRepresentation::String, - v2::GraphQlType::Boolean => v3::TypeRepresentation::Boolean, - v2::GraphQlType::Id => v3::TypeRepresentation::String, - } -} diff --git a/crates/mongodb-connector/src/api_type_conversions/mod.rs b/crates/mongodb-connector/src/api_type_conversions/mod.rs index d9ab3a60..4b77162e 100644 --- a/crates/mongodb-connector/src/api_type_conversions/mod.rs +++ b/crates/mongodb-connector/src/api_type_conversions/mod.rs @@ -1,4 +1,3 @@ -mod capabilities; mod conversion_error; mod helpers; mod query_request; @@ -7,7 +6,6 @@ mod query_traversal; #[allow(unused_imports)] pub use self::{ - capabilities::v2_to_v3_scalar_type_capabilities, conversion_error::ConversionError, query_request::{v3_to_v2_query_request, QueryContext}, query_response::{v2_to_v3_explain_response, v2_to_v3_query_response}, diff --git a/crates/mongodb-connector/src/api_type_conversions/query_request.rs b/crates/mongodb-connector/src/api_type_conversions/query_request.rs index 86a96cf4..7a9c4759 100644 --- a/crates/mongodb-connector/src/api_type_conversions/query_request.rs +++ b/crates/mongodb-connector/src/api_type_conversions/query_request.rs @@ -1270,7 +1270,7 @@ mod tests { ( "Int".to_owned(), ScalarType { - representation: Some(TypeRepresentation::Integer), + representation: Some(TypeRepresentation::Int32), aggregate_functions: BTreeMap::from([( "avg".into(), AggregateFunctionDefinition { diff --git a/crates/mongodb-connector/src/capabilities.rs b/crates/mongodb-connector/src/capabilities.rs index 925cb5d7..cdd9f4e6 100644 --- a/crates/mongodb-connector/src/capabilities.rs +++ b/crates/mongodb-connector/src/capabilities.rs @@ -1,16 +1,19 @@ use std::collections::BTreeMap; -use mongodb_agent_common::scalar_types_capabilities::scalar_types_capabilities; +use mongodb_agent_common::{ + comparison_function::ComparisonFunction, + scalar_types_capabilities::{aggregate_functions, comparison_operators}, +}; +use mongodb_support::BsonScalarType; use ndc_sdk::models::{ - Capabilities, CapabilitiesResponse, LeafCapability, QueryCapabilities, - RelationshipCapabilities, ScalarType, + AggregateFunctionDefinition, Capabilities, CapabilitiesResponse, ComparisonOperatorDefinition, + LeafCapability, QueryCapabilities, RelationshipCapabilities, ScalarType, Type, + TypeRepresentation, }; -use crate::api_type_conversions::v2_to_v3_scalar_type_capabilities; - pub fn mongo_capabilities_response() -> CapabilitiesResponse { ndc_sdk::models::CapabilitiesResponse { - version: "0.1.1".to_owned(), + version: "0.1.2".to_owned(), capabilities: Capabilities { query: QueryCapabilities { aggregates: Some(LeafCapability {}), @@ -29,6 +32,92 @@ pub fn mongo_capabilities_response() -> CapabilitiesResponse { } } -pub fn scalar_types() -> BTreeMap { - v2_to_v3_scalar_type_capabilities(scalar_types_capabilities()) +pub fn scalar_types() -> BTreeMap { + enum_iterator::all::() + .map(make_scalar_type) + .chain([extended_json_scalar_type()]) + .collect::>() +} + +fn extended_json_scalar_type() -> (String, ScalarType) { + ( + mongodb_support::EXTENDED_JSON_TYPE_NAME.to_owned(), + ScalarType { + representation: Some(TypeRepresentation::JSON), + aggregate_functions: BTreeMap::new(), + comparison_operators: BTreeMap::new(), + }, + ) +} + +fn make_scalar_type(bson_scalar_type: BsonScalarType) -> (String, ScalarType) { + let scalar_type_name = bson_scalar_type.graphql_name(); + let scalar_type = ScalarType { + representation: bson_scalar_type_representation(bson_scalar_type), + aggregate_functions: bson_aggregation_functions(bson_scalar_type), + comparison_operators: bson_comparison_operators(bson_scalar_type), + }; + (scalar_type_name, scalar_type) +} + +fn bson_scalar_type_representation(bson_scalar_type: BsonScalarType) -> Option { + match bson_scalar_type { + BsonScalarType::Double => Some(TypeRepresentation::Float64), + BsonScalarType::Decimal => Some(TypeRepresentation::BigDecimal), // Not quite.... Mongo Decimal is 128-bit, BigDecimal is unlimited + BsonScalarType::Int => Some(TypeRepresentation::Int32), + BsonScalarType::Long => Some(TypeRepresentation::Int64), + BsonScalarType::String => Some(TypeRepresentation::String), + BsonScalarType::Date => Some(TypeRepresentation::Timestamp), // Mongo Date is milliseconds since unix epoch + BsonScalarType::Timestamp => None, // Internal Mongo timestamp type + BsonScalarType::BinData => None, + BsonScalarType::ObjectId => Some(TypeRepresentation::String), // Mongo ObjectId is usually expressed as a 24 char hex string (12 byte number) + BsonScalarType::Bool => Some(TypeRepresentation::Boolean), + BsonScalarType::Null => None, + BsonScalarType::Regex => None, + BsonScalarType::Javascript => None, + BsonScalarType::JavascriptWithScope => None, + BsonScalarType::MinKey => None, + BsonScalarType::MaxKey => None, + BsonScalarType::Undefined => None, + BsonScalarType::DbPointer => None, + BsonScalarType::Symbol => None, + } +} + +fn bson_aggregation_functions( + bson_scalar_type: BsonScalarType, +) -> BTreeMap { + aggregate_functions(bson_scalar_type) + .map(|(fn_name, result_type)| { + let aggregation_definition = AggregateFunctionDefinition { + result_type: bson_to_named_type(result_type), + }; + (fn_name.graphql_name().to_owned(), aggregation_definition) + }) + .collect() +} + +fn bson_comparison_operators( + bson_scalar_type: BsonScalarType, +) -> BTreeMap { + comparison_operators(bson_scalar_type) + .map(|(comparison_fn, arg_type)| { + let fn_name = comparison_fn.graphql_name().to_owned(); + match comparison_fn { + ComparisonFunction::Equal => (fn_name, ComparisonOperatorDefinition::Equal), + _ => ( + fn_name, + ComparisonOperatorDefinition::Custom { + argument_type: bson_to_named_type(arg_type), + }, + ), + } + }) + .collect() +} + +fn bson_to_named_type(bson_scalar_type: BsonScalarType) -> Type { + Type::Named { + name: bson_scalar_type.graphql_name(), + } } diff --git a/crates/mongodb-support/src/bson_type.rs b/crates/mongodb-support/src/bson_type.rs index b7fb52ac..c504bc89 100644 --- a/crates/mongodb-support/src/bson_type.rs +++ b/crates/mongodb-support/src/bson_type.rs @@ -170,6 +170,78 @@ impl BsonScalarType { all::().find(|s| s.bson_name().eq_ignore_ascii_case(name)); scalar_type.ok_or_else(|| Error::UnknownScalarType(name.to_owned())) } + + pub fn is_orderable(self) -> bool { + match self { + S::Double => true, + S::Decimal => true, + S::Int => true, + S::Long => true, + S::String => true, + S::Date => true, + S::Timestamp => true, + S::BinData => false, + S::ObjectId => false, + S::Bool => false, + S::Null => false, + S::Regex => false, + S::Javascript => false, + S::JavascriptWithScope => false, + S::MinKey => false, + S::MaxKey => false, + S::Undefined => false, + S::DbPointer => false, + S::Symbol => false, + } + } + + pub fn is_numeric(self) -> bool { + match self { + S::Double => true, + S::Decimal => true, + S::Int => true, + S::Long => true, + S::String => false, + S::Date => false, + S::Timestamp => false, + S::BinData => false, + S::ObjectId => false, + S::Bool => false, + S::Null => false, + S::Regex => false, + S::Javascript => false, + S::JavascriptWithScope => false, + S::MinKey => false, + S::MaxKey => false, + S::Undefined => false, + S::DbPointer => false, + S::Symbol => false, + } + } + + pub fn is_comparable(self) -> bool { + match self { + S::Double => true, + S::Decimal => true, + S::Int => true, + S::Long => true, + S::String => true, + S::Date => true, + S::Timestamp => true, + S::BinData => true, + S::ObjectId => true, + S::Bool => true, + S::Null => true, + S::Regex => false, + S::Javascript => false, + S::JavascriptWithScope => false, + S::MinKey => true, + S::MaxKey => true, + S::Undefined => true, + S::DbPointer => true, + S::Symbol => true, + } + } } impl std::fmt::Display for BsonScalarType {