From 8a66721d6b971d357ea4bf8d2bd39b72481158f0 Mon Sep 17 00:00:00 2001 From: Islam Aliev Date: Tue, 30 Jan 2024 23:03:33 +0100 Subject: [PATCH] feat: Allow setting null values on doc fields (#2273) ## Relevant issue(s) Resolves #2272 ## Description Allows setting of null values for nillable fields. --- client/ctype.go | 2 +- client/descriptions.go | 60 +++++++++--------- client/document.go | 30 +++++++-- client/document_test.go | 4 +- core/encoding.go | 6 +- db/index.go | 12 ++-- merkle/crdt/merklecrdt.go | 4 +- net/client_test.go | 2 +- planner/sum.go | 4 +- request/graphql/schema/collection.go | 14 ++--- request/graphql/schema/descriptions.go | 42 ++++++------- request/graphql/schema/descriptions_test.go | 62 +++++++++---------- tests/gen/gen_auto.go | 8 +-- tests/gen/gen_auto_config.go | 6 +- tests/gen/gen_auto_test.go | 12 ++-- .../mutation/update/field_kinds/blob_test.go | 41 ++++++++++++ .../mutation/update/field_kinds/bool_test.go | 58 +++++++++++++++++ .../update/field_kinds/date_time_test.go | 41 ++++++++++++ .../mutation/update/field_kinds/float_test.go | 58 +++++++++++++++++ .../mutation/update/field_kinds/int_test.go | 58 +++++++++++++++++ .../mutation/update/field_kinds/json_test.go | 58 +++++++++++++++++ .../update/field_kinds/string_test.go | 58 +++++++++++++++++ tests/integration/schema/crdt_type_test.go | 4 +- tests/integration/schema/get_schema_test.go | 6 +- .../schema/updates/add/field/simple_test.go | 8 +-- tests/integration/utils2.go | 6 +- 26 files changed, 529 insertions(+), 135 deletions(-) create mode 100644 tests/integration/mutation/update/field_kinds/bool_test.go create mode 100644 tests/integration/mutation/update/field_kinds/float_test.go create mode 100644 tests/integration/mutation/update/field_kinds/int_test.go create mode 100644 tests/integration/mutation/update/field_kinds/json_test.go create mode 100644 tests/integration/mutation/update/field_kinds/string_test.go diff --git a/client/ctype.go b/client/ctype.go index 7c194c73bf..c5f792df86 100644 --- a/client/ctype.go +++ b/client/ctype.go @@ -39,7 +39,7 @@ func (t CType) IsSupportedFieldCType() bool { func (t CType) IsCompatibleWith(kind FieldKind) bool { switch t { case PN_COUNTER: - if kind == FieldKind_INT || kind == FieldKind_FLOAT { + if kind == FieldKind_NILLABLE_INT || kind == FieldKind_NILLABLE_FLOAT { return true } return false diff --git a/client/descriptions.go b/client/descriptions.go index ca88f9362e..2cc66de85b 100644 --- a/client/descriptions.go +++ b/client/descriptions.go @@ -155,35 +155,35 @@ func (f FieldKind) String() string { switch f { case FieldKind_DocID: return "ID" - case FieldKind_BOOL: + case FieldKind_NILLABLE_BOOL: return "Boolean" case FieldKind_NILLABLE_BOOL_ARRAY: return "[Boolean]" case FieldKind_BOOL_ARRAY: return "[Boolean!]" - case FieldKind_INT: + case FieldKind_NILLABLE_INT: return "Int" case FieldKind_NILLABLE_INT_ARRAY: return "[Int]" case FieldKind_INT_ARRAY: return "[Int!]" - case FieldKind_DATETIME: + case FieldKind_NILLABLE_DATETIME: return "DateTime" - case FieldKind_FLOAT: + case FieldKind_NILLABLE_FLOAT: return "Float" case FieldKind_NILLABLE_FLOAT_ARRAY: return "[Float]" case FieldKind_FLOAT_ARRAY: return "[Float!]" - case FieldKind_STRING: + case FieldKind_NILLABLE_STRING: return "String" case FieldKind_NILLABLE_STRING_ARRAY: return "[String]" case FieldKind_STRING_ARRAY: return "[String!]" - case FieldKind_BLOB: + case FieldKind_NILLABLE_BLOB: return "Blob" - case FieldKind_JSON: + case FieldKind_NILLABLE_JSON: return "JSON" default: return fmt.Sprint(uint8(f)) @@ -192,22 +192,22 @@ func (f FieldKind) String() string { // Note: These values are serialized and persisted in the database, avoid modifying existing values. const ( - FieldKind_None FieldKind = 0 - FieldKind_DocID FieldKind = 1 - FieldKind_BOOL FieldKind = 2 - FieldKind_BOOL_ARRAY FieldKind = 3 - FieldKind_INT FieldKind = 4 - FieldKind_INT_ARRAY FieldKind = 5 - FieldKind_FLOAT FieldKind = 6 - FieldKind_FLOAT_ARRAY FieldKind = 7 - _ FieldKind = 8 // safe to repurpose (was never used) - _ FieldKind = 9 // safe to repurpose (previously old field) - FieldKind_DATETIME FieldKind = 10 - FieldKind_STRING FieldKind = 11 - FieldKind_STRING_ARRAY FieldKind = 12 - FieldKind_BLOB FieldKind = 13 - FieldKind_JSON FieldKind = 14 - _ FieldKind = 15 // safe to repurpose (was never used) + FieldKind_None FieldKind = 0 + FieldKind_DocID FieldKind = 1 + FieldKind_NILLABLE_BOOL FieldKind = 2 + FieldKind_BOOL_ARRAY FieldKind = 3 + FieldKind_NILLABLE_INT FieldKind = 4 + FieldKind_INT_ARRAY FieldKind = 5 + FieldKind_NILLABLE_FLOAT FieldKind = 6 + FieldKind_FLOAT_ARRAY FieldKind = 7 + _ FieldKind = 8 // safe to repurpose (was never used) + _ FieldKind = 9 // safe to repurpose (previously old field) + FieldKind_NILLABLE_DATETIME FieldKind = 10 + FieldKind_NILLABLE_STRING FieldKind = 11 + FieldKind_STRING_ARRAY FieldKind = 12 + FieldKind_NILLABLE_BLOB FieldKind = 13 + FieldKind_NILLABLE_JSON FieldKind = 14 + _ FieldKind = 15 // safe to repurpose (was never used) // Embedded object, but accessed via foreign keys FieldKind_FOREIGN_OBJECT FieldKind = 16 @@ -230,21 +230,21 @@ const ( // equality is not guaranteed. var FieldKindStringToEnumMapping = map[string]FieldKind{ "ID": FieldKind_DocID, - "Boolean": FieldKind_BOOL, + "Boolean": FieldKind_NILLABLE_BOOL, "[Boolean]": FieldKind_NILLABLE_BOOL_ARRAY, "[Boolean!]": FieldKind_BOOL_ARRAY, - "Int": FieldKind_INT, + "Int": FieldKind_NILLABLE_INT, "[Int]": FieldKind_NILLABLE_INT_ARRAY, "[Int!]": FieldKind_INT_ARRAY, - "DateTime": FieldKind_DATETIME, - "Float": FieldKind_FLOAT, + "DateTime": FieldKind_NILLABLE_DATETIME, + "Float": FieldKind_NILLABLE_FLOAT, "[Float]": FieldKind_NILLABLE_FLOAT_ARRAY, "[Float!]": FieldKind_FLOAT_ARRAY, - "String": FieldKind_STRING, + "String": FieldKind_NILLABLE_STRING, "[String]": FieldKind_NILLABLE_STRING_ARRAY, "[String!]": FieldKind_STRING_ARRAY, - "Blob": FieldKind_BLOB, - "JSON": FieldKind_JSON, + "Blob": FieldKind_NILLABLE_BLOB, + "JSON": FieldKind_NILLABLE_JSON, } // RelationType describes the type of relation between two types. diff --git a/client/document.go b/client/document.go index 7345f10d09..caafb13df6 100644 --- a/client/document.go +++ b/client/document.go @@ -171,13 +171,33 @@ func NewDocsFromJSON(obj []byte, sd SchemaDescription) ([]*Document, error) { return docs, nil } +func isNillableKind(kind FieldKind) bool { + switch kind { + case FieldKind_NILLABLE_STRING, FieldKind_NILLABLE_BLOB, FieldKind_NILLABLE_JSON, + FieldKind_NILLABLE_BOOL, FieldKind_NILLABLE_FLOAT, FieldKind_NILLABLE_DATETIME, + FieldKind_NILLABLE_INT: + return true + default: + return false + } +} + // validateFieldSchema takes a given value as an interface, // and ensures it matches the supplied field description. // It will do any minor parsing, like dates, and return // the typed value again as an interface. func validateFieldSchema(val any, field FieldDescription) (any, error) { + if isNillableKind(field.Kind) { + if val == nil { + return nil, nil + } + if v, ok := val.(*fastjson.Value); ok && v.Type() == fastjson.TypeNull { + return nil, nil + } + } + switch field.Kind { - case FieldKind_DocID, FieldKind_STRING, FieldKind_BLOB, FieldKind_JSON: + case FieldKind_DocID, FieldKind_NILLABLE_STRING, FieldKind_NILLABLE_BLOB, FieldKind_NILLABLE_JSON: return getString(val) case FieldKind_STRING_ARRAY: @@ -186,7 +206,7 @@ func validateFieldSchema(val any, field FieldDescription) (any, error) { case FieldKind_NILLABLE_STRING_ARRAY: return getNillableArray(val, getString) - case FieldKind_BOOL: + case FieldKind_NILLABLE_BOOL: return getBool(val) case FieldKind_BOOL_ARRAY: @@ -195,7 +215,7 @@ func validateFieldSchema(val any, field FieldDescription) (any, error) { case FieldKind_NILLABLE_BOOL_ARRAY: return getNillableArray(val, getBool) - case FieldKind_FLOAT: + case FieldKind_NILLABLE_FLOAT: return getFloat64(val) case FieldKind_FLOAT_ARRAY: @@ -204,10 +224,10 @@ func validateFieldSchema(val any, field FieldDescription) (any, error) { case FieldKind_NILLABLE_FLOAT_ARRAY: return getNillableArray(val, getFloat64) - case FieldKind_DATETIME: + case FieldKind_NILLABLE_DATETIME: return getDateTime(val) - case FieldKind_INT: + case FieldKind_NILLABLE_INT: return getInt64(val) case FieldKind_INT_ARRAY: diff --git a/client/document_test.go b/client/document_test.go index dc5867b562..a6168f1082 100644 --- a/client/document_test.go +++ b/client/document_test.go @@ -34,12 +34,12 @@ var ( { Name: "Name", Typ: LWW_REGISTER, - Kind: FieldKind_STRING, + Kind: FieldKind_NILLABLE_STRING, }, { Name: "Age", Typ: LWW_REGISTER, - Kind: FieldKind_INT, + Kind: FieldKind_NILLABLE_INT, }, }, }, diff --git a/core/encoding.go b/core/encoding.go index f6b46a4381..1f15eb70f3 100644 --- a/core/encoding.go +++ b/core/encoding.go @@ -96,7 +96,7 @@ func DecodeFieldValue(fieldDesc client.FieldDescription, val any) (any, error) { } } else { // CBOR often encodes values typed as floats as ints switch fieldDesc.Kind { - case client.FieldKind_FLOAT: + case client.FieldKind_NILLABLE_FLOAT: switch v := val.(type) { case int64: return float64(v), nil @@ -107,7 +107,7 @@ func DecodeFieldValue(fieldDesc client.FieldDescription, val any) (any, error) { case uint: return float64(v), nil } - case client.FieldKind_INT: + case client.FieldKind_NILLABLE_INT: switch v := val.(type) { case float64: return int64(v), nil @@ -120,7 +120,7 @@ func DecodeFieldValue(fieldDesc client.FieldDescription, val any) (any, error) { case uint: return int64(v), nil } - case client.FieldKind_DATETIME: + case client.FieldKind_NILLABLE_DATETIME: switch v := val.(type) { case string: return time.Parse(time.RFC3339, v) diff --git a/db/index.go b/db/index.go index d76573de65..c89e159368 100644 --- a/db/index.go +++ b/db/index.go @@ -44,15 +44,15 @@ func canConvertIndexFieldValue[T any](val any) bool { func getValidateIndexFieldFunc(kind client.FieldKind) func(any) bool { switch kind { - case client.FieldKind_STRING, client.FieldKind_FOREIGN_OBJECT: + case client.FieldKind_NILLABLE_STRING, client.FieldKind_FOREIGN_OBJECT: return canConvertIndexFieldValue[string] - case client.FieldKind_INT: + case client.FieldKind_NILLABLE_INT: return canConvertIndexFieldValue[int64] - case client.FieldKind_FLOAT: + case client.FieldKind_NILLABLE_FLOAT: return canConvertIndexFieldValue[float64] - case client.FieldKind_BOOL: + case client.FieldKind_NILLABLE_BOOL: return canConvertIndexFieldValue[bool] - case client.FieldKind_BLOB: + case client.FieldKind_NILLABLE_BLOB: return func(val any) bool { blobStrVal, ok := val.(string) if !ok { @@ -60,7 +60,7 @@ func getValidateIndexFieldFunc(kind client.FieldKind) func(any) bool { } return types.BlobPattern.MatchString(blobStrVal) } - case client.FieldKind_DATETIME: + case client.FieldKind_NILLABLE_DATETIME: return func(val any) bool { timeStrVal, ok := val.(string) if !ok { diff --git a/merkle/crdt/merklecrdt.go b/merkle/crdt/merklecrdt.go index ba7fd5648d..b52fb7cf6d 100644 --- a/merkle/crdt/merklecrdt.go +++ b/merkle/crdt/merklecrdt.go @@ -86,14 +86,14 @@ func InstanceWithStore( ), nil case client.PN_COUNTER: switch kind { - case client.FieldKind_INT: + case client.FieldKind_NILLABLE_INT: return NewMerklePNCounter[int64]( store, schemaVersionKey, key, fieldName, ), nil - case client.FieldKind_FLOAT: + case client.FieldKind_NILLABLE_FLOAT: return NewMerklePNCounter[float64]( store, schemaVersionKey, diff --git a/net/client_test.go b/net/client_test.go index 5db18c4a07..b260baceaf 100644 --- a/net/client_test.go +++ b/net/client_test.go @@ -27,7 +27,7 @@ var sd = client.SchemaDescription{ Fields: []client.FieldDescription{ { Name: "test", - Kind: client.FieldKind_STRING, + Kind: client.FieldKind_NILLABLE_STRING, Typ: client.LWW_REGISTER, }, }, diff --git a/planner/sum.go b/planner/sum.go index 85371e5a30..02da507df8 100644 --- a/planner/sum.go +++ b/planner/sum.go @@ -87,7 +87,7 @@ func (p *Planner) isValueFloat( return false, client.NewErrFieldNotExist(source.Name) } return fieldDescription.Kind == client.FieldKind_FLOAT_ARRAY || - fieldDescription.Kind == client.FieldKind_FLOAT || + fieldDescription.Kind == client.FieldKind_NILLABLE_FLOAT || fieldDescription.Kind == client.FieldKind_NILLABLE_FLOAT_ARRAY, nil } @@ -136,7 +136,7 @@ func (p *Planner) isValueFloat( } return fieldDescription.Kind == client.FieldKind_FLOAT_ARRAY || - fieldDescription.Kind == client.FieldKind_FLOAT || + fieldDescription.Kind == client.FieldKind_NILLABLE_FLOAT || fieldDescription.Kind == client.FieldKind_NILLABLE_FLOAT_ARRAY, nil } diff --git a/request/graphql/schema/collection.go b/request/graphql/schema/collection.go index ad4f7bb855..a1d5bd5c54 100644 --- a/request/graphql/schema/collection.go +++ b/request/graphql/schema/collection.go @@ -455,19 +455,19 @@ func astTypeToKind(t ast.Type) (client.FieldKind, error) { case typeID: return client.FieldKind_DocID, nil case typeBoolean: - return client.FieldKind_BOOL, nil + return client.FieldKind_NILLABLE_BOOL, nil case typeInt: - return client.FieldKind_INT, nil + return client.FieldKind_NILLABLE_INT, nil case typeFloat: - return client.FieldKind_FLOAT, nil + return client.FieldKind_NILLABLE_FLOAT, nil case typeDateTime: - return client.FieldKind_DATETIME, nil + return client.FieldKind_NILLABLE_DATETIME, nil case typeString: - return client.FieldKind_STRING, nil + return client.FieldKind_NILLABLE_STRING, nil case typeBlob: - return client.FieldKind_BLOB, nil + return client.FieldKind_NILLABLE_BLOB, nil case typeJSON: - return client.FieldKind_JSON, nil + return client.FieldKind_NILLABLE_JSON, nil default: return client.FieldKind_FOREIGN_OBJECT, nil } diff --git a/request/graphql/schema/descriptions.go b/request/graphql/schema/descriptions.go index 01b1d8b0cb..cb19140d26 100644 --- a/request/graphql/schema/descriptions.go +++ b/request/graphql/schema/descriptions.go @@ -25,16 +25,16 @@ var ( //nolint:unused gqlTypeToFieldKindReference = map[gql.Type]client.FieldKind{ gql.ID: client.FieldKind_DocID, - gql.Boolean: client.FieldKind_BOOL, - gql.Int: client.FieldKind_INT, - gql.Float: client.FieldKind_FLOAT, - gql.DateTime: client.FieldKind_DATETIME, - gql.String: client.FieldKind_STRING, + gql.Boolean: client.FieldKind_NILLABLE_BOOL, + gql.Int: client.FieldKind_NILLABLE_INT, + gql.Float: client.FieldKind_NILLABLE_FLOAT, + gql.DateTime: client.FieldKind_NILLABLE_DATETIME, + gql.String: client.FieldKind_NILLABLE_STRING, &gql.Object{}: client.FieldKind_FOREIGN_OBJECT, &gql.List{}: client.FieldKind_FOREIGN_OBJECT_ARRAY, // Custom scalars - schemaTypes.BlobScalarType: client.FieldKind_BLOB, - schemaTypes.JSONScalarType: client.FieldKind_JSON, + schemaTypes.BlobScalarType: client.FieldKind_NILLABLE_BLOB, + schemaTypes.JSONScalarType: client.FieldKind_NILLABLE_JSON, // More custom ones to come // - JSON // - Counters @@ -42,41 +42,41 @@ var ( fieldKindToGQLType = map[client.FieldKind]gql.Type{ client.FieldKind_DocID: gql.ID, - client.FieldKind_BOOL: gql.Boolean, + client.FieldKind_NILLABLE_BOOL: gql.Boolean, client.FieldKind_BOOL_ARRAY: gql.NewList(gql.NewNonNull(gql.Boolean)), client.FieldKind_NILLABLE_BOOL_ARRAY: gql.NewList(gql.Boolean), - client.FieldKind_INT: gql.Int, + client.FieldKind_NILLABLE_INT: gql.Int, client.FieldKind_INT_ARRAY: gql.NewList(gql.NewNonNull(gql.Int)), client.FieldKind_NILLABLE_INT_ARRAY: gql.NewList(gql.Int), - client.FieldKind_FLOAT: gql.Float, + client.FieldKind_NILLABLE_FLOAT: gql.Float, client.FieldKind_FLOAT_ARRAY: gql.NewList(gql.NewNonNull(gql.Float)), client.FieldKind_NILLABLE_FLOAT_ARRAY: gql.NewList(gql.Float), - client.FieldKind_DATETIME: gql.DateTime, - client.FieldKind_STRING: gql.String, + client.FieldKind_NILLABLE_DATETIME: gql.DateTime, + client.FieldKind_NILLABLE_STRING: gql.String, client.FieldKind_STRING_ARRAY: gql.NewList(gql.NewNonNull(gql.String)), client.FieldKind_NILLABLE_STRING_ARRAY: gql.NewList(gql.String), - client.FieldKind_BLOB: schemaTypes.BlobScalarType, - client.FieldKind_JSON: schemaTypes.JSONScalarType, + client.FieldKind_NILLABLE_BLOB: schemaTypes.BlobScalarType, + client.FieldKind_NILLABLE_JSON: schemaTypes.JSONScalarType, } // This map is fine to use defaultCRDTForFieldKind = map[client.FieldKind]client.CType{ client.FieldKind_DocID: client.LWW_REGISTER, - client.FieldKind_BOOL: client.LWW_REGISTER, + client.FieldKind_NILLABLE_BOOL: client.LWW_REGISTER, client.FieldKind_BOOL_ARRAY: client.LWW_REGISTER, client.FieldKind_NILLABLE_BOOL_ARRAY: client.LWW_REGISTER, - client.FieldKind_INT: client.LWW_REGISTER, + client.FieldKind_NILLABLE_INT: client.LWW_REGISTER, client.FieldKind_INT_ARRAY: client.LWW_REGISTER, client.FieldKind_NILLABLE_INT_ARRAY: client.LWW_REGISTER, - client.FieldKind_FLOAT: client.LWW_REGISTER, + client.FieldKind_NILLABLE_FLOAT: client.LWW_REGISTER, client.FieldKind_FLOAT_ARRAY: client.LWW_REGISTER, client.FieldKind_NILLABLE_FLOAT_ARRAY: client.LWW_REGISTER, - client.FieldKind_DATETIME: client.LWW_REGISTER, - client.FieldKind_STRING: client.LWW_REGISTER, + client.FieldKind_NILLABLE_DATETIME: client.LWW_REGISTER, + client.FieldKind_NILLABLE_STRING: client.LWW_REGISTER, client.FieldKind_STRING_ARRAY: client.LWW_REGISTER, client.FieldKind_NILLABLE_STRING_ARRAY: client.LWW_REGISTER, - client.FieldKind_BLOB: client.LWW_REGISTER, - client.FieldKind_JSON: client.LWW_REGISTER, + client.FieldKind_NILLABLE_BLOB: client.LWW_REGISTER, + client.FieldKind_NILLABLE_JSON: client.LWW_REGISTER, client.FieldKind_FOREIGN_OBJECT: client.LWW_REGISTER, client.FieldKind_FOREIGN_OBJECT_ARRAY: client.NONE_CRDT, } diff --git a/request/graphql/schema/descriptions_test.go b/request/graphql/schema/descriptions_test.go index 39a7b5cce2..5a168fcf11 100644 --- a/request/graphql/schema/descriptions_test.go +++ b/request/graphql/schema/descriptions_test.go @@ -47,17 +47,17 @@ func TestSingleSimpleType(t *testing.T) { }, { Name: "age", - Kind: client.FieldKind_INT, + Kind: client.FieldKind_NILLABLE_INT, Typ: client.LWW_REGISTER, }, { Name: "name", - Kind: client.FieldKind_STRING, + Kind: client.FieldKind_NILLABLE_STRING, Typ: client.LWW_REGISTER, }, { Name: "verified", - Kind: client.FieldKind_BOOL, + Kind: client.FieldKind_NILLABLE_BOOL, Typ: client.LWW_REGISTER, }, }, @@ -96,17 +96,17 @@ func TestSingleSimpleType(t *testing.T) { }, { Name: "age", - Kind: client.FieldKind_INT, + Kind: client.FieldKind_NILLABLE_INT, Typ: client.LWW_REGISTER, }, { Name: "name", - Kind: client.FieldKind_STRING, + Kind: client.FieldKind_NILLABLE_STRING, Typ: client.LWW_REGISTER, }, { Name: "verified", - Kind: client.FieldKind_BOOL, + Kind: client.FieldKind_NILLABLE_BOOL, Typ: client.LWW_REGISTER, }, }, @@ -127,17 +127,17 @@ func TestSingleSimpleType(t *testing.T) { }, { Name: "name", - Kind: client.FieldKind_STRING, + Kind: client.FieldKind_NILLABLE_STRING, Typ: client.LWW_REGISTER, }, { Name: "publisher", - Kind: client.FieldKind_STRING, + Kind: client.FieldKind_NILLABLE_STRING, Typ: client.LWW_REGISTER, }, { Name: "rating", - Kind: client.FieldKind_FLOAT, + Kind: client.FieldKind_NILLABLE_FLOAT, Typ: client.LWW_REGISTER, }, }, @@ -190,12 +190,12 @@ func TestSingleSimpleType(t *testing.T) { }, { Name: "name", - Kind: client.FieldKind_STRING, + Kind: client.FieldKind_NILLABLE_STRING, Typ: client.LWW_REGISTER, }, { Name: "rating", - Kind: client.FieldKind_FLOAT, + Kind: client.FieldKind_NILLABLE_FLOAT, Typ: client.LWW_REGISTER, }, }, @@ -216,12 +216,12 @@ func TestSingleSimpleType(t *testing.T) { }, { Name: "age", - Kind: client.FieldKind_INT, + Kind: client.FieldKind_NILLABLE_INT, Typ: client.LWW_REGISTER, }, { Name: "name", - Kind: client.FieldKind_STRING, + Kind: client.FieldKind_NILLABLE_STRING, Typ: client.LWW_REGISTER, }, { @@ -274,17 +274,17 @@ func TestSingleSimpleType(t *testing.T) { }, { Name: "age", - Kind: client.FieldKind_INT, + Kind: client.FieldKind_NILLABLE_INT, Typ: client.LWW_REGISTER, }, { Name: "name", - Kind: client.FieldKind_STRING, + Kind: client.FieldKind_NILLABLE_STRING, Typ: client.LWW_REGISTER, }, { Name: "verified", - Kind: client.FieldKind_BOOL, + Kind: client.FieldKind_NILLABLE_BOOL, Typ: client.LWW_REGISTER, }, }, @@ -305,17 +305,17 @@ func TestSingleSimpleType(t *testing.T) { }, { Name: "name", - Kind: client.FieldKind_STRING, + Kind: client.FieldKind_NILLABLE_STRING, Typ: client.LWW_REGISTER, }, { Name: "publisher", - Kind: client.FieldKind_STRING, + Kind: client.FieldKind_NILLABLE_STRING, Typ: client.LWW_REGISTER, }, { Name: "rating", - Kind: client.FieldKind_FLOAT, + Kind: client.FieldKind_NILLABLE_FLOAT, Typ: client.LWW_REGISTER, }, }, @@ -368,12 +368,12 @@ func TestSingleSimpleType(t *testing.T) { }, { Name: "name", - Kind: client.FieldKind_STRING, + Kind: client.FieldKind_NILLABLE_STRING, Typ: client.LWW_REGISTER, }, { Name: "rating", - Kind: client.FieldKind_FLOAT, + Kind: client.FieldKind_NILLABLE_FLOAT, Typ: client.LWW_REGISTER, }, }, @@ -394,12 +394,12 @@ func TestSingleSimpleType(t *testing.T) { }, { Name: "age", - Kind: client.FieldKind_INT, + Kind: client.FieldKind_NILLABLE_INT, Typ: client.LWW_REGISTER, }, { Name: "name", - Kind: client.FieldKind_STRING, + Kind: client.FieldKind_NILLABLE_STRING, Typ: client.LWW_REGISTER, }, { @@ -466,12 +466,12 @@ func TestSingleSimpleType(t *testing.T) { }, { Name: "name", - Kind: client.FieldKind_STRING, + Kind: client.FieldKind_NILLABLE_STRING, Typ: client.LWW_REGISTER, }, { Name: "rating", - Kind: client.FieldKind_FLOAT, + Kind: client.FieldKind_NILLABLE_FLOAT, Typ: client.LWW_REGISTER, }, }, @@ -492,12 +492,12 @@ func TestSingleSimpleType(t *testing.T) { }, { Name: "age", - Kind: client.FieldKind_INT, + Kind: client.FieldKind_NILLABLE_INT, Typ: client.LWW_REGISTER, }, { Name: "name", - Kind: client.FieldKind_STRING, + Kind: client.FieldKind_NILLABLE_STRING, Typ: client.LWW_REGISTER, }, { @@ -564,12 +564,12 @@ func TestSingleSimpleType(t *testing.T) { }, { Name: "name", - Kind: client.FieldKind_STRING, + Kind: client.FieldKind_NILLABLE_STRING, Typ: client.LWW_REGISTER, }, { Name: "rating", - Kind: client.FieldKind_FLOAT, + Kind: client.FieldKind_NILLABLE_FLOAT, Typ: client.LWW_REGISTER, }, }, @@ -590,12 +590,12 @@ func TestSingleSimpleType(t *testing.T) { }, { Name: "age", - Kind: client.FieldKind_INT, + Kind: client.FieldKind_NILLABLE_INT, Typ: client.LWW_REGISTER, }, { Name: "name", - Kind: client.FieldKind_STRING, + Kind: client.FieldKind_NILLABLE_STRING, Typ: client.LWW_REGISTER, }, { diff --git a/tests/gen/gen_auto.go b/tests/gen/gen_auto.go index 2dd3f78d8a..2573bcda63 100644 --- a/tests/gen/gen_auto.go +++ b/tests/gen/gen_auto.go @@ -185,22 +185,22 @@ func (g *randomDocGenerator) generateRandomValue( func (g *randomDocGenerator) getValueGenerator(fieldKind client.FieldKind, fieldConfig genConfig) func() any { switch fieldKind { - case client.FieldKind_STRING: + case client.FieldKind_NILLABLE_STRING: strLen := DefaultStrLen if prop, ok := fieldConfig.props["len"]; ok { strLen = prop.(int) } return func() any { return getRandomString(&g.random, strLen) } - case client.FieldKind_INT: + case client.FieldKind_NILLABLE_INT: min, max := getMinMaxOrDefault(fieldConfig, DefaultIntMin, DefaultIntMax) return func() any { return min + g.random.Intn(max-min+1) } - case client.FieldKind_BOOL: + case client.FieldKind_NILLABLE_BOOL: ratio := 0.5 if prop, ok := fieldConfig.props["ratio"]; ok { ratio = prop.(float64) } return func() any { return g.random.Float64() < ratio } - case client.FieldKind_FLOAT: + case client.FieldKind_NILLABLE_FLOAT: min, max := getMinMaxOrDefault(fieldConfig, 0.0, 1.0) return func() any { return min + g.random.Float64()*(max-min) } } diff --git a/tests/gen/gen_auto_config.go b/tests/gen/gen_auto_config.go index 1457e67a12..bca07cead6 100644 --- a/tests/gen/gen_auto_config.go +++ b/tests/gen/gen_auto_config.go @@ -86,7 +86,7 @@ func checkAndValidateMinMax(field *client.FieldDescription, conf *genConfig) err _, hasMin := conf.props["min"] if hasMin { var err error - if field.IsArray() || field.Kind == client.FieldKind_INT { + if field.IsArray() || field.Kind == client.FieldKind_NILLABLE_INT { err = validateMinConfig[int](conf, field.IsArray()) } else { err = validateMinConfig[float64](conf, false) @@ -103,7 +103,7 @@ func checkAndValidateMinMax(field *client.FieldDescription, conf *genConfig) err func checkAndValidateLen(field *client.FieldDescription, conf *genConfig) error { lenConf, hasLen := conf.props["len"] if hasLen { - if field.Kind != client.FieldKind_STRING { + if field.Kind != client.FieldKind_NILLABLE_STRING { return NewErrInvalidConfiguration("len is used on not String") } len, ok := lenConf.(int) @@ -120,7 +120,7 @@ func checkAndValidateLen(field *client.FieldDescription, conf *genConfig) error func checkAndValidateRatio(field *client.FieldDescription, conf *genConfig) error { ratioConf, hasRatio := conf.props["ratio"] if hasRatio { - if field.Kind != client.FieldKind_BOOL { + if field.Kind != client.FieldKind_NILLABLE_BOOL { return NewErrInvalidConfiguration("ratio is used on not Boolean") } len, ok := ratioConf.(float64) diff --git a/tests/gen/gen_auto_test.go b/tests/gen/gen_auto_test.go index 25191bbe41..a43d478c9f 100644 --- a/tests/gen/gen_auto_test.go +++ b/tests/gen/gen_auto_test.go @@ -1209,7 +1209,7 @@ func TestAutoGenerate_IfCollectionDefinitionIsIncomplete_ReturnError(t *testing. Fields: []client.FieldDescription{ { Name: "name", - Kind: client.FieldKind_INT, + Kind: client.FieldKind_NILLABLE_INT, }, { Name: "device", @@ -1230,7 +1230,7 @@ func TestAutoGenerate_IfCollectionDefinitionIsIncomplete_ReturnError(t *testing. Fields: []client.FieldDescription{ { Name: "model", - Kind: client.FieldKind_STRING, + Kind: client.FieldKind_NILLABLE_STRING, }, { Name: "owner", @@ -1327,15 +1327,15 @@ func TestAutoGenerate_IfColDefinitionsAreValid_ShouldGenerate(t *testing.T) { Fields: []client.FieldDescription{ { Name: "name", - Kind: client.FieldKind_STRING, + Kind: client.FieldKind_NILLABLE_STRING, }, { Name: "age", - Kind: client.FieldKind_INT, + Kind: client.FieldKind_NILLABLE_INT, }, { Name: "rating", - Kind: client.FieldKind_FLOAT, + Kind: client.FieldKind_NILLABLE_FLOAT, }, { Name: "devices", @@ -1356,7 +1356,7 @@ func TestAutoGenerate_IfColDefinitionsAreValid_ShouldGenerate(t *testing.T) { Fields: []client.FieldDescription{ { Name: "model", - Kind: client.FieldKind_STRING, + Kind: client.FieldKind_NILLABLE_STRING, }, { Name: "owner_id", diff --git a/tests/integration/mutation/update/field_kinds/blob_test.go b/tests/integration/mutation/update/field_kinds/blob_test.go index 4445c45bba..4434d49ef0 100644 --- a/tests/integration/mutation/update/field_kinds/blob_test.go +++ b/tests/integration/mutation/update/field_kinds/blob_test.go @@ -58,3 +58,44 @@ func TestMutationUpdate_WithBlobField(t *testing.T) { testUtils.ExecuteTestCase(t, test) } + +func TestMutationUpdate_IfBlobFieldSetToNull_ShouldBeNil(t *testing.T) { + test := testUtils.TestCase{ + Description: "If blob field is set to null, should set to nil", + Actions: []any{ + testUtils.SchemaUpdate{ + Schema: ` + type Users { + data: Blob + } + `, + }, + testUtils.CreateDoc{ + Doc: `{ + "data": "00FE" + }`, + }, + testUtils.UpdateDoc{ + Doc: `{ + "data": null + }`, + }, + testUtils.Request{ + Request: ` + query { + Users { + data + } + } + `, + Results: []map[string]any{ + { + "data": nil, + }, + }, + }, + }, + } + + testUtils.ExecuteTestCase(t, test) +} diff --git a/tests/integration/mutation/update/field_kinds/bool_test.go b/tests/integration/mutation/update/field_kinds/bool_test.go new file mode 100644 index 0000000000..36301961ed --- /dev/null +++ b/tests/integration/mutation/update/field_kinds/bool_test.go @@ -0,0 +1,58 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package field_kinds + +import ( + "testing" + + testUtils "github.com/sourcenetwork/defradb/tests/integration" +) + +func TestMutationUpdate_IfBoolFieldSetToNull_ShouldBeNil(t *testing.T) { + test := testUtils.TestCase{ + Description: "If bool field is set to null, should set to nil", + Actions: []any{ + testUtils.SchemaUpdate{ + Schema: ` + type Users { + valid: Boolean + } + `, + }, + testUtils.CreateDoc{ + Doc: `{ + "valid": true + }`, + }, + testUtils.UpdateDoc{ + Doc: `{ + "valid": null + }`, + }, + testUtils.Request{ + Request: ` + query { + Users { + valid + } + } + `, + Results: []map[string]any{ + { + "valid": nil, + }, + }, + }, + }, + } + + testUtils.ExecuteTestCase(t, test) +} diff --git a/tests/integration/mutation/update/field_kinds/date_time_test.go b/tests/integration/mutation/update/field_kinds/date_time_test.go index b7d1546864..c0ac23c166 100644 --- a/tests/integration/mutation/update/field_kinds/date_time_test.go +++ b/tests/integration/mutation/update/field_kinds/date_time_test.go @@ -106,3 +106,44 @@ func TestMutationUpdate_WithDateTimeField_MultipleDocs(t *testing.T) { testUtils.ExecuteTestCase(t, test) } + +func TestMutationUpdate_IfDateTimeFieldSetToNull_ShouldBeNil(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple update of date time field", + Actions: []any{ + testUtils.SchemaUpdate{ + Schema: ` + type Users { + created_at: DateTime + } + `, + }, + testUtils.CreateDoc{ + Doc: `{ + "created_at": "2011-07-23T01:11:11-05:00" + }`, + }, + testUtils.UpdateDoc{ + Doc: `{ + "created_at": null + }`, + }, + testUtils.Request{ + Request: ` + query { + Users { + created_at + } + } + `, + Results: []map[string]any{ + { + "created_at": nil, + }, + }, + }, + }, + } + + testUtils.ExecuteTestCase(t, test) +} diff --git a/tests/integration/mutation/update/field_kinds/float_test.go b/tests/integration/mutation/update/field_kinds/float_test.go new file mode 100644 index 0000000000..bb1a9babce --- /dev/null +++ b/tests/integration/mutation/update/field_kinds/float_test.go @@ -0,0 +1,58 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package field_kinds + +import ( + "testing" + + testUtils "github.com/sourcenetwork/defradb/tests/integration" +) + +func TestMutationUpdate_IfFloatFieldSetToNull_ShouldBeNil(t *testing.T) { + test := testUtils.TestCase{ + Description: "If float field is set to null, should set to nil", + Actions: []any{ + testUtils.SchemaUpdate{ + Schema: ` + type Users { + rate: Float + } + `, + }, + testUtils.CreateDoc{ + Doc: `{ + "rate": 0.55 + }`, + }, + testUtils.UpdateDoc{ + Doc: `{ + "rate": null + }`, + }, + testUtils.Request{ + Request: ` + query { + Users { + rate + } + } + `, + Results: []map[string]any{ + { + "rate": nil, + }, + }, + }, + }, + } + + testUtils.ExecuteTestCase(t, test) +} diff --git a/tests/integration/mutation/update/field_kinds/int_test.go b/tests/integration/mutation/update/field_kinds/int_test.go new file mode 100644 index 0000000000..84a122e080 --- /dev/null +++ b/tests/integration/mutation/update/field_kinds/int_test.go @@ -0,0 +1,58 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package field_kinds + +import ( + "testing" + + testUtils "github.com/sourcenetwork/defradb/tests/integration" +) + +func TestMutationUpdate_IfIntFieldSetToNull_ShouldBeNil(t *testing.T) { + test := testUtils.TestCase{ + Description: "If int field is set to null, should set to nil", + Actions: []any{ + testUtils.SchemaUpdate{ + Schema: ` + type Users { + age: Int + } + `, + }, + testUtils.CreateDoc{ + Doc: `{ + "age": 33 + }`, + }, + testUtils.UpdateDoc{ + Doc: `{ + "age": null + }`, + }, + testUtils.Request{ + Request: ` + query { + Users { + age + } + } + `, + Results: []map[string]any{ + { + "age": nil, + }, + }, + }, + }, + } + + testUtils.ExecuteTestCase(t, test) +} diff --git a/tests/integration/mutation/update/field_kinds/json_test.go b/tests/integration/mutation/update/field_kinds/json_test.go new file mode 100644 index 0000000000..c782f856a3 --- /dev/null +++ b/tests/integration/mutation/update/field_kinds/json_test.go @@ -0,0 +1,58 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package field_kinds + +import ( + "testing" + + testUtils "github.com/sourcenetwork/defradb/tests/integration" +) + +func TestMutationUpdate_IfJSONFieldSetToNull_ShouldBeNil(t *testing.T) { + test := testUtils.TestCase{ + Description: "If json field is set to null, should set to nil", + Actions: []any{ + testUtils.SchemaUpdate{ + Schema: ` + type Users { + custom: JSON + } + `, + }, + testUtils.CreateDoc{ + Doc: `{ + "custom": "{\"foo\": \"bar\"}" + }`, + }, + testUtils.UpdateDoc{ + Doc: `{ + "custom": null + }`, + }, + testUtils.Request{ + Request: ` + query { + Users { + custom + } + } + `, + Results: []map[string]any{ + { + "custom": nil, + }, + }, + }, + }, + } + + testUtils.ExecuteTestCase(t, test) +} diff --git a/tests/integration/mutation/update/field_kinds/string_test.go b/tests/integration/mutation/update/field_kinds/string_test.go new file mode 100644 index 0000000000..7783f9b0fd --- /dev/null +++ b/tests/integration/mutation/update/field_kinds/string_test.go @@ -0,0 +1,58 @@ +// Copyright 2023 Democratized Data Foundation +// +// Use of this software is governed by the Business Source License +// included in the file licenses/BSL.txt. +// +// As of the Change Date specified in that file, in accordance with +// the Business Source License, use of this software will be governed +// by the Apache License, Version 2.0, included in the file +// licenses/APL.txt. + +package field_kinds + +import ( + "testing" + + testUtils "github.com/sourcenetwork/defradb/tests/integration" +) + +func TestMutationUpdate_IfStringFieldSetToNull_ShouldBeNil(t *testing.T) { + test := testUtils.TestCase{ + Description: "If string field is set to null, should set to nil", + Actions: []any{ + testUtils.SchemaUpdate{ + Schema: ` + type Users { + name: String + } + `, + }, + testUtils.CreateDoc{ + Doc: `{ + "name": "John" + }`, + }, + testUtils.UpdateDoc{ + Doc: `{ + "name": null + }`, + }, + testUtils.Request{ + Request: ` + query { + Users { + name + } + } + `, + Results: []map[string]any{ + { + "name": nil, + }, + }, + }, + }, + } + + testUtils.ExecuteTestCase(t, test) +} diff --git a/tests/integration/schema/crdt_type_test.go b/tests/integration/schema/crdt_type_test.go index 0df94edcf6..d09d406161 100644 --- a/tests/integration/schema/crdt_type_test.go +++ b/tests/integration/schema/crdt_type_test.go @@ -46,7 +46,7 @@ func TestSchemaCreate_ContainsPNCounterTypeWithIntKind_NoError(t *testing.T) { { Name: "points", ID: 1, - Kind: client.FieldKind_INT, + Kind: client.FieldKind_NILLABLE_INT, Typ: client.PN_COUNTER, }, }, @@ -86,7 +86,7 @@ func TestSchemaCreate_ContainsPNCounterTypeWithFloatKind_NoError(t *testing.T) { { Name: "points", ID: 1, - Kind: client.FieldKind_FLOAT, + Kind: client.FieldKind_NILLABLE_FLOAT, Typ: client.PN_COUNTER, }, }, diff --git a/tests/integration/schema/get_schema_test.go b/tests/integration/schema/get_schema_test.go index ae63d49812..42f42e4844 100644 --- a/tests/integration/schema/get_schema_test.go +++ b/tests/integration/schema/get_schema_test.go @@ -110,7 +110,7 @@ func TestGetSchema_ReturnsAllSchema(t *testing.T) { { Name: "name", ID: 1, - Kind: client.FieldKind_STRING, + Kind: client.FieldKind_NILLABLE_STRING, Typ: client.LWW_REGISTER, }, }, @@ -185,7 +185,7 @@ func TestGetSchema_ReturnsSchemaForGivenRoot(t *testing.T) { { Name: "name", ID: 1, - Kind: client.FieldKind_STRING, + Kind: client.FieldKind_NILLABLE_STRING, Typ: client.LWW_REGISTER, }, }, @@ -249,7 +249,7 @@ func TestGetSchema_ReturnsSchemaForGivenName(t *testing.T) { { Name: "name", ID: 1, - Kind: client.FieldKind_STRING, + Kind: client.FieldKind_NILLABLE_STRING, Typ: client.LWW_REGISTER, }, }, diff --git a/tests/integration/schema/updates/add/field/simple_test.go b/tests/integration/schema/updates/add/field/simple_test.go index 04bafb2694..63e6f5f9f6 100644 --- a/tests/integration/schema/updates/add/field/simple_test.go +++ b/tests/integration/schema/updates/add/field/simple_test.go @@ -65,13 +65,13 @@ func TestSchemaUpdatesAddFieldSimple(t *testing.T) { { Name: "name", ID: 1, - Kind: client.FieldKind_STRING, + Kind: client.FieldKind_NILLABLE_STRING, Typ: client.LWW_REGISTER, }, { Name: "email", ID: 2, - Kind: client.FieldKind_STRING, + Kind: client.FieldKind_NILLABLE_STRING, Typ: client.LWW_REGISTER, }, }, @@ -156,13 +156,13 @@ func TestSchemaUpdates_AddFieldSimpleDoNotSetDefault_VersionIsQueryable(t *testi { Name: "name", ID: 1, - Kind: client.FieldKind_STRING, + Kind: client.FieldKind_NILLABLE_STRING, Typ: client.LWW_REGISTER, }, { Name: "email", ID: 2, - Kind: client.FieldKind_STRING, + Kind: client.FieldKind_NILLABLE_STRING, Typ: client.LWW_REGISTER, }, }, diff --git a/tests/integration/utils2.go b/tests/integration/utils2.go index 53a0e4e4d0..afbd57bea9 100644 --- a/tests/integration/utils2.go +++ b/tests/integration/utils2.go @@ -80,6 +80,8 @@ const ( lensPoolSize = 2 ) +const testJSONFile = "/test.json" + func init() { // We use environment variables instead of flags `go test ./...` throws for all packages // that don't have the flag defined @@ -1411,7 +1413,7 @@ func backupExport( action BackupExport, ) { if action.Config.Filepath == "" { - action.Config.Filepath = s.t.TempDir() + "/test.json" + action.Config.Filepath = s.t.TempDir() + testJSONFile } var expectedErrorRaised bool @@ -1437,7 +1439,7 @@ func backupImport( action BackupImport, ) { if action.Filepath == "" { - action.Filepath = s.t.TempDir() + "/test.json" + action.Filepath = s.t.TempDir() + testJSONFile } // we can avoid checking the error here as this would mean the filepath is invalid