Skip to content

Commit

Permalink
Rename Type::Any to Type::ExtendedJSON (#30)
Browse files Browse the repository at this point in the history
* Rename Type::Any to Type::ExtendedJSON

* Add changelog
  • Loading branch information
dmoverton authored Apr 5, 2024
1 parent bb1a45f commit 3d151cf
Show file tree
Hide file tree
Showing 10 changed files with 392 additions and 245 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ This changelog documents the changes between release versions.
- Queries that attempt to compare a column to a column in the query root table, or a related table, will now fail instead of giving the incorrect result ([#22](https://github.com/hasura/ndc-mongodb/pull/22))
- Fix bug in v2 to v3 conversion of query responses containing nested objects ([PR #27](https://github.com/hasura/ndc-mongodb/pull/27))
- Fixed bug where use of aggregate functions in queries would fail ([#26](https://github.com/hasura/ndc-mongodb/pull/26))
- Rename Any type to ExtendedJSON to make its representation clearer ([#30](https://github.com/hasura/ndc-mongodb/pull/30))

## [0.0.3] - 2024-03-28
- Use separate schema files for each collection ([PR #14](https://github.com/hasura/ndc-mongodb/pull/14))
Expand Down
10 changes: 3 additions & 7 deletions crates/cli/src/introspection/sampling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,7 @@ pub fn type_from_bson(
(WithName::into_map(object_types), t)
}

fn make_field_type(
object_type_name: &str,
field_value: &Bson,
) -> (Vec<ObjectType>, Type) {
fn make_field_type(object_type_name: &str, field_value: &Bson) -> (Vec<ObjectType>, Type) {
fn scalar(t: BsonScalarType) -> (Vec<ObjectType>, Type) {
(vec![], Type::Scalar(t))
}
Expand All @@ -138,8 +135,7 @@ fn make_field_type(
let mut collected_otds = vec![];
let mut result_type = Type::Scalar(Undefined);
for elem in arr {
let (elem_collected_otds, elem_type) =
make_field_type(object_type_name, elem);
let (elem_collected_otds, elem_type) = make_field_type(object_type_name, elem);
collected_otds = if collected_otds.is_empty() {
elem_collected_otds
} else {
Expand Down Expand Up @@ -301,7 +297,7 @@ mod tests {
(
"bar".to_owned(),
ObjectField {
r#type: Type::Any,
r#type: Type::ExtendedJSON,
description: None,
},
),
Expand Down
32 changes: 16 additions & 16 deletions crates/cli/src/introspection/type_unification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ type ObjectType = WithName<schema::ObjectType>;

/// Unify two types.
/// This is computing the join (or least upper bound) of the two types in a lattice
/// where `Any` is the Top element, Scalar(Undefined) is the Bottom element, and Nullable(T) >= T for all T.
/// where `ExtendedJSON` is the Top element, Scalar(Undefined) is the Bottom element, and Nullable(T) >= T for all T.
pub fn unify_type(type_a: Type, type_b: Type) -> Type {
let result_type = match (type_a, type_b) {
// Union of any type with Any is Any
(Type::Any, _) => Type::Any,
(_, Type::Any) => Type::Any,
// Union of any type with ExtendedJSON is ExtendedJSON
(Type::ExtendedJSON, _) => Type::ExtendedJSON,
(_, Type::ExtendedJSON) => Type::ExtendedJSON,

// If one type is undefined, the union is the other type.
// This is used as the base case when inferring array types from documents.
Expand All @@ -44,22 +44,22 @@ pub fn unify_type(type_a: Type, type_b: Type) -> Type {
(type_a, Type::Scalar(Null)) => type_a.make_nullable(),

// Scalar types unify if they are the same type.
// If they are diffferent then the union is Any.
// If they are diffferent then the union is ExtendedJSON.
(Type::Scalar(scalar_a), Type::Scalar(scalar_b)) => {
if scalar_a == scalar_b {
Type::Scalar(scalar_a)
} else {
Type::Any
Type::ExtendedJSON
}
}

// Object types unify if they have the same name.
// If they are diffferent then the union is Any.
// If they are diffferent then the union is ExtendedJSON.
(Type::Object(object_a), Type::Object(object_b)) => {
if object_a == object_b {
Type::Object(object_a)
} else {
Type::Any
Type::ExtendedJSON
}
}

Expand All @@ -69,8 +69,8 @@ pub fn unify_type(type_a: Type, type_b: Type) -> Type {
Type::ArrayOf(Box::new(elem_type))
}

// Anything else gives Any
(_, _) => Type::Any,
// Anything else gives ExtendedJSON
(_, _) => Type::ExtendedJSON,
};
result_type.normalize_type()
}
Expand Down Expand Up @@ -194,7 +194,7 @@ mod tests {

#[test]
fn test_unify_scalar_error() -> Result<(), anyhow::Error> {
let expected = Type::Any;
let expected = Type::ExtendedJSON;
let actual = unify_type(
Type::Scalar(BsonScalarType::Int),
Type::Scalar(BsonScalarType::String),
Expand All @@ -206,7 +206,7 @@ mod tests {
fn is_nullable(t: &Type) -> bool {
matches!(
t,
Type::Scalar(BsonScalarType::Null) | Type::Nullable(_) | Type::Any
Type::Scalar(BsonScalarType::Null) | Type::Nullable(_) | Type::ExtendedJSON
)
}

Expand Down Expand Up @@ -255,16 +255,16 @@ mod tests {
proptest! {
#[test]
fn test_any_left(t in arb_type()) {
let u = unify_type(Type::Any, t);
prop_assert_eq!(Type::Any, u)
let u = unify_type(Type::ExtendedJSON, t);
prop_assert_eq!(Type::ExtendedJSON, u)
}
}

proptest! {
#[test]
fn test_any_right(t in arb_type()) {
let u = unify_type(t, Type::Any);
prop_assert_eq!(Type::Any, u)
let u = unify_type(t, Type::ExtendedJSON);
prop_assert_eq!(Type::ExtendedJSON, u)
}
}

Expand Down
16 changes: 10 additions & 6 deletions crates/configuration/src/schema/database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ pub struct Collection {
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub enum Type {
/// Any BSON value. To be used when we don't have any more information
/// Any BSON value, represented as Extended JSON.
/// To be used when we don't have any more information
/// about the types of values that a column, field or argument can take.
/// Also used when we unifying two incompatible types in schemas derived
/// from sample documents.
Any,
ExtendedJSON,
/// One of the predefined BSON scalar types
Scalar(BsonScalarType),
/// The name of an object type declared in `objectTypes`
Expand All @@ -37,17 +38,20 @@ pub enum Type {

impl Type {
pub fn is_nullable(&self) -> bool {
matches!(self, Type::Any | Type::Nullable(_) | Type::Scalar(BsonScalarType::Null))
matches!(
self,
Type::ExtendedJSON | Type::Nullable(_) | Type::Scalar(BsonScalarType::Null)
)
}

pub fn normalize_type(self) -> Type {
match self {
Type::Any => Type::Any,
Type::ExtendedJSON => Type::ExtendedJSON,
Type::Scalar(s) => Type::Scalar(s),
Type::Object(o) => Type::Object(o),
Type::ArrayOf(a) => Type::ArrayOf(Box::new((*a).normalize_type())),
Type::Nullable(n) => match *n {
Type::Any => Type::Any,
Type::ExtendedJSON => Type::ExtendedJSON,
Type::Scalar(BsonScalarType::Null) => Type::Scalar(BsonScalarType::Null),
Type::Nullable(t) => Type::Nullable(t).normalize_type(),
t => Type::Nullable(Box::new(t.normalize_type())),
Expand All @@ -57,7 +61,7 @@ impl Type {

pub fn make_nullable(self) -> Type {
match self {
Type::Any => Type::Any,
Type::ExtendedJSON => Type::ExtendedJSON,
Type::Nullable(t) => Type::Nullable(t),
Type::Scalar(BsonScalarType::Null) => Type::Scalar(BsonScalarType::Null),
t => Type::Nullable(Box::new(t)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ type Result<T> = std::result::Result<T, BsonToJsonError>;
/// The BSON library already has a `Serialize` impl that can convert to JSON. But that
/// implementation emits Extended JSON which includes inline type tags in JSON output to
/// disambiguate types on the BSON side. We don't want those tags because we communicate type
/// information out of band. That is except for the `Type::Any` type where we do want to emit
/// information out of band. That is except for the `Type::ExtendedJSON` type where we do want to emit
/// Extended JSON because we don't have out-of-band information in that case.
pub fn bson_to_json(
expected_type: &Type,
object_types: &BTreeMap<String, ObjectType>,
value: Bson,
) -> Result<Value> {
match expected_type {
Type::Any => Ok(value.into_canonical_extjson()),
Type::ExtendedJSON => Ok(value.into_canonical_extjson()),
Type::Scalar(scalar_type) => bson_scalar_to_json(*scalar_type, value),
Type::Object(object_type_name) => {
let object_type = object_types
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,9 @@ pub fn json_to_bson(
value: Value,
) -> Result<Bson> {
match expected_type {
Type::Any => serde_json::from_value::<Bson>(value).map_err(JsonToBsonError::SerdeError),
Type::ExtendedJSON => {
serde_json::from_value::<Bson>(value).map_err(JsonToBsonError::SerdeError)
}
Type::Scalar(t) => json_to_bson_scalar(*t, value),
Type::Object(object_type_name) => {
let object_type = object_types
Expand Down
5 changes: 4 additions & 1 deletion crates/mongodb-agent-common/src/scalar_types_capabilities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ pub fn scalar_types_capabilities() -> HashMap<String, ScalarTypeCapabilities> {
let mut map = all::<BsonScalarType>()
.map(|t| (t.graphql_name(), capabilities(t)))
.collect::<HashMap<_, _>>();
map.insert(mongodb_support::ANY_TYPE_NAME.to_owned(), ScalarTypeCapabilities::new());
map.insert(
mongodb_support::EXTENDED_JSON_TYPE_NAME.to_owned(),
ScalarTypeCapabilities::new(),
);
map
}

Expand Down
Loading

0 comments on commit 3d151cf

Please sign in to comment.