diff --git a/client/document.go b/client/document.go index 8fb5e28868..93e06df27e 100644 --- a/client/document.go +++ b/client/document.go @@ -251,6 +251,8 @@ func getFloat64(v any) (float64, error) { return val.Float64() case int: return float64(val), nil + case int32: + return float64(val), nil case int64: return float64(val), nil case float64: @@ -266,6 +268,8 @@ func getInt64(v any) (int64, error) { return val.Int64() case int: return int64(val), nil + case int32: + return int64(val), nil case int64: return val, nil case float64: diff --git a/client/request/consts.go b/client/request/consts.go index 85b7d63d84..1a1d653a25 100644 --- a/client/request/consts.go +++ b/client/request/consts.go @@ -20,7 +20,7 @@ const ( RelatedObjectID = "_id" Cid = "cid" - Data = "data" + Input = "input" FieldName = "field" FieldIDName = "fieldId" ShowDeleted = "showDeleted" diff --git a/client/request/mutation.go b/client/request/mutation.go index 3d19210458..6bff180dd9 100644 --- a/client/request/mutation.go +++ b/client/request/mutation.go @@ -33,7 +33,7 @@ type ObjectMutation struct { IDs immutable.Option[[]string] Filter immutable.Option[Filter] - Data string + Input map[string]any Fields []Selection } diff --git a/planner/create.go b/planner/create.go index e272c80722..a03c429da9 100644 --- a/planner/create.go +++ b/planner/create.go @@ -11,8 +11,6 @@ package planner import ( - "encoding/json" - "github.com/sourcenetwork/defradb/client" "github.com/sourcenetwork/defradb/client/request" "github.com/sourcenetwork/defradb/core" @@ -37,9 +35,9 @@ type createNode struct { // collection name, meta-data, etc. collection client.Collection - // newDoc is the JSON string of the new document, unparsed - newDocStr string - doc *client.Document + // input map of fields and values + input map[string]any + doc *client.Document err error @@ -59,7 +57,7 @@ func (n *createNode) Kind() string { return "createNode" } func (n *createNode) Init() error { return nil } func (n *createNode) Start() error { - doc, err := client.NewDocFromJSON([]byte(n.newDocStr), n.collection.Schema()) + doc, err := client.NewDocFromMap(n.input, n.collection.Schema()) if err != nil { n.err = err return err @@ -135,24 +133,14 @@ func (n *createNode) Close() error { func (n *createNode) Source() planNode { return n.results } -func (n *createNode) simpleExplain() (map[string]any, error) { - data := map[string]any{} - err := json.Unmarshal([]byte(n.newDocStr), &data) - if err != nil { - return nil, err - } - - return map[string]any{ - dataLabel: data, - }, nil -} - // Explain method returns a map containing all attributes of this node that // are to be explained, subscribes / opts-in this node to be an explainablePlanNode. func (n *createNode) Explain(explainType request.ExplainType) (map[string]any, error) { switch explainType { case request.SimpleExplain: - return n.simpleExplain() + return map[string]any{ + inputLabel: n.input, + }, nil case request.ExecuteExplain: return map[string]any{ @@ -173,7 +161,7 @@ func (p *Planner) CreateDoc(parsed *mapper.Mutation) (planNode, error) { // create a mutation createNode. create := &createNode{ p: p, - newDocStr: parsed.Data, + input: parsed.Input, results: results, docMapper: docMapper{parsed.DocumentMapping}, } diff --git a/planner/explain.go b/planner/explain.go index 76e562dc94..5ab2f292f8 100644 --- a/planner/explain.go +++ b/planner/explain.go @@ -53,7 +53,7 @@ const ( childFieldNameLabel = "childFieldName" collectionIDLabel = "collectionID" collectionNameLabel = "collectionName" - dataLabel = "data" + inputLabel = "input" fieldNameLabel = "fieldName" filterLabel = "filter" joinRootLabel = "root" diff --git a/planner/mapper/mapper.go b/planner/mapper/mapper.go index ff7e19ff21..06772be487 100644 --- a/planner/mapper/mapper.go +++ b/planner/mapper/mapper.go @@ -1089,7 +1089,7 @@ func ToMutation(ctx context.Context, store client.Store, mutationRequest *reques return &Mutation{ Select: *underlyingSelect, Type: MutationType(mutationRequest.Type), - Data: mutationRequest.Data, + Input: mutationRequest.Input, }, nil } diff --git a/planner/mapper/mutation.go b/planner/mapper/mutation.go index c3c5829294..a38444e01c 100644 --- a/planner/mapper/mutation.go +++ b/planner/mapper/mutation.go @@ -27,9 +27,8 @@ type Mutation struct { // The type of mutation. For example a create request. Type MutationType - // The data to be used for the mutation. For example, during a create this - // will be the json representation of the object to be inserted. - Data string + // Input is the map of fields and values used for the mutation. + Input map[string]any } func (m *Mutation) CloneTo(index int) Requestable { @@ -40,6 +39,6 @@ func (m *Mutation) cloneTo(index int) *Mutation { return &Mutation{ Select: *m.Select.cloneTo(index), Type: m.Type, - Data: m.Data, + Input: m.Input, } } diff --git a/planner/update.go b/planner/update.go index 78619bd55f..077ceb39e4 100644 --- a/planner/update.go +++ b/planner/update.go @@ -31,7 +31,8 @@ type updateNode struct { docIDs []string - patch string + // input map of fields and values + input map[string]any isUpdating bool @@ -67,7 +68,11 @@ func (n *updateNode) Next() (bool, error) { if err != nil { return false, err } - _, err = n.collection.UpdateWithDocID(n.p.ctx, docID, n.patch) + patch, err := json.Marshal(n.input) + if err != nil { + return false, err + } + _, err = n.collection.UpdateWithDocID(n.p.ctx, docID, string(patch)) if err != nil { return false, err } @@ -126,12 +131,7 @@ func (n *updateNode) simpleExplain() (map[string]any, error) { } // Add the attribute that represents the patch to update with. - data := map[string]any{} - err := json.Unmarshal([]byte(n.patch), &data) - if err != nil { - return nil, err - } - simpleExplainMap[dataLabel] = data + simpleExplainMap[inputLabel] = n.input return simpleExplainMap, nil } @@ -160,7 +160,7 @@ func (p *Planner) UpdateDocs(parsed *mapper.Mutation) (planNode, error) { filter: parsed.Filter, docIDs: parsed.DocIDs.Value(), isUpdating: true, - patch: parsed.Data, + input: parsed.Input, docMapper: docMapper{parsed.DocumentMapping}, } diff --git a/request/graphql/parser/mutation.go b/request/graphql/parser/mutation.go index 0802c745d6..27becabb71 100644 --- a/request/graphql/parser/mutation.go +++ b/request/graphql/parser/mutation.go @@ -99,12 +99,9 @@ func parseMutation(schema gql.Schema, parent *gql.Object, field *ast.Field) (*re for _, argument := range field.Arguments { prop := argument.Name.Value // parse each individual arg type seperately - if prop == request.Data { // parse data - raw := argument.Value.(*ast.StringValue) - if raw.Value == "" { - return nil, ErrEmptyDataPayload - } - mut.Data = raw.Value + if prop == request.Input { // parse input + raw := argument.Value.(*ast.ObjectValue) + mut.Input = parseMutationInputObject(raw) } else if prop == request.FilterClause { // parse filter obj := argument.Value.(*ast.ObjectValue) filterType, ok := getArgumentType(fieldDef, request.FilterClause) @@ -147,3 +144,44 @@ func parseMutation(schema gql.Schema, parent *gql.Object, field *ast.Field) (*re mut.Fields, err = parseSelectFields(schema, request.ObjectSelection, fieldObject, field.SelectionSet) return mut, err } + +// parseMutationInput parses the correct underlying +// value type of the given ast.Value +func parseMutationInput(val ast.Value) any { + switch t := val.(type) { + case *ast.IntValue: + return gql.Int.ParseLiteral(val) + case *ast.FloatValue: + return gql.Float.ParseLiteral(val) + case *ast.BooleanValue: + return t.Value + case *ast.StringValue: + return t.Value + case *ast.ObjectValue: + return parseMutationInputObject(t) + case *ast.ListValue: + return parseMutationInputList(t) + default: + return val.GetValue() + } +} + +// parseMutationInputList parses the correct underlying +// value type for all of the values in the ast.ListValue +func parseMutationInputList(val *ast.ListValue) []any { + list := make([]any, 0) + for _, val := range val.Values { + list = append(list, parseMutationInput(val)) + } + return list +} + +// parseMutationInputObject parses the correct underlying +// value type for all of the fields in the ast.ObjectValue +func parseMutationInputObject(val *ast.ObjectValue) map[string]any { + obj := make(map[string]any) + for _, field := range val.Fields { + obj[field.Name.Value] = parseMutationInput(field.Value) + } + return obj +} diff --git a/request/graphql/schema/descriptions.go b/request/graphql/schema/descriptions.go index 5ab76ff726..147c494c74 100644 --- a/request/graphql/schema/descriptions.go +++ b/request/graphql/schema/descriptions.go @@ -125,9 +125,6 @@ An optional value that specifies as to whether deleted documents may be ` createDocumentDescription string = ` Creates a single document of this type using the data provided. -` - createDataArgDescription string = ` -The json representation of the document you wish to create. Required. ` updateDocumentsDescription string = ` Updates documents in this collection using the data provided. Only documents @@ -148,10 +145,6 @@ An optional set of docID values that will limit the update to documents An optional filter for this update that will limit the update to the documents matching the given criteria. If no matching documents are found, the operation will succeed, but no documents will be updated. -` - updateDataArgDescription string = ` -The json representation of the fields to update and their new values. Required. - Fields not explicitly mentioned here will not be updated. ` deleteDocumentsDescription string = ` Deletes documents in this collection matching any provided criteria. If no diff --git a/request/graphql/schema/errors.go b/request/graphql/schema/errors.go index cf28c7d710..39bbbd803a 100644 --- a/request/graphql/schema/errors.go +++ b/request/graphql/schema/errors.go @@ -13,35 +13,37 @@ package schema import "github.com/sourcenetwork/defradb/errors" const ( - errDuplicateField string = "duplicate field" - errFieldMissingRelation string = "field missing associated relation" - errRelationMissingField string = "relation missing field" - errAggregateTargetNotFound string = "aggregate target not found" - errSchemaTypeAlreadyExist string = "schema type already exists" - errObjectNotFoundDuringThunk string = "object not found whilst executing fields thunk" - errTypeNotFound string = "no type found for given name" - errRelationNotFound string = "no relation found" - errNonNullForTypeNotSupported string = "NonNull variants for type are not supported" - errIndexMissingFields string = "index missing fields" - errIndexUnknownArgument string = "index with unknown argument" - errIndexInvalidArgument string = "index with invalid argument" - errIndexInvalidName string = "index with invalid name" + errDuplicateField string = "duplicate field" + errFieldMissingRelation string = "field missing associated relation" + errRelationMissingField string = "relation missing field" + errAggregateTargetNotFound string = "aggregate target not found" + errSchemaTypeAlreadyExist string = "schema type already exists" + errMutationInputTypeAlreadyExist string = "mutation input type already exists" + errObjectNotFoundDuringThunk string = "object not found whilst executing fields thunk" + errTypeNotFound string = "no type found for given name" + errRelationNotFound string = "no relation found" + errNonNullForTypeNotSupported string = "NonNull variants for type are not supported" + errIndexMissingFields string = "index missing fields" + errIndexUnknownArgument string = "index with unknown argument" + errIndexInvalidArgument string = "index with invalid argument" + errIndexInvalidName string = "index with invalid name" ) var ( - ErrDuplicateField = errors.New(errDuplicateField) - ErrFieldMissingRelation = errors.New(errFieldMissingRelation) - ErrRelationMissingField = errors.New(errRelationMissingField) - ErrAggregateTargetNotFound = errors.New(errAggregateTargetNotFound) - ErrSchemaTypeAlreadyExist = errors.New(errSchemaTypeAlreadyExist) - ErrObjectNotFoundDuringThunk = errors.New(errObjectNotFoundDuringThunk) - ErrTypeNotFound = errors.New(errTypeNotFound) - ErrRelationNotFound = errors.New(errRelationNotFound) - ErrNonNullForTypeNotSupported = errors.New(errNonNullForTypeNotSupported) - ErrRelationMutlipleTypes = errors.New("relation type can only be either One or Many, not both") - ErrRelationMissingTypes = errors.New("relation is missing its defined types and fields") - ErrRelationInvalidType = errors.New("relation has an invalid type to be finalize") - ErrMultipleRelationPrimaries = errors.New("relation can only have a single field set as primary") + ErrDuplicateField = errors.New(errDuplicateField) + ErrFieldMissingRelation = errors.New(errFieldMissingRelation) + ErrRelationMissingField = errors.New(errRelationMissingField) + ErrAggregateTargetNotFound = errors.New(errAggregateTargetNotFound) + ErrSchemaTypeAlreadyExist = errors.New(errSchemaTypeAlreadyExist) + ErrMutationInputTypeAlreadyExist = errors.New(errMutationInputTypeAlreadyExist) + ErrObjectNotFoundDuringThunk = errors.New(errObjectNotFoundDuringThunk) + ErrTypeNotFound = errors.New(errTypeNotFound) + ErrRelationNotFound = errors.New(errRelationNotFound) + ErrNonNullForTypeNotSupported = errors.New(errNonNullForTypeNotSupported) + ErrRelationMutlipleTypes = errors.New("relation type can only be either One or Many, not both") + ErrRelationMissingTypes = errors.New("relation is missing its defined types and fields") + ErrRelationInvalidType = errors.New("relation has an invalid type to be finalize") + ErrMultipleRelationPrimaries = errors.New("relation can only have a single field set as primary") // NonNull is the literal name of the GQL type, so we have to disable the linter //nolint:revive ErrNonNullNotSupported = errors.New("NonNull fields are not currently supported") @@ -94,6 +96,13 @@ func NewErrSchemaTypeAlreadyExist(name string) error { ) } +func NewErrMutationInputTypeAlreadyExist(name string) error { + return errors.New( + errMutationInputTypeAlreadyExist, + errors.NewKV("Name", name), + ) +} + func NewErrObjectNotFoundDuringThunk(object string) error { return errors.New( errObjectNotFoundDuringThunk, diff --git a/request/graphql/schema/generate.go b/request/graphql/schema/generate.go index 556700cd7f..9e1d67a9c8 100644 --- a/request/graphql/schema/generate.go +++ b/request/graphql/schema/generate.go @@ -13,6 +13,7 @@ package schema import ( "context" "fmt" + "strings" gql "github.com/sourcenetwork/graphql-go" @@ -85,6 +86,11 @@ func (g *Generator) generate(ctx context.Context, collections []client.Collectio if err != nil { return nil, err } + // build mutation input types + err = g.buildMutationInputTypes(collections) + if err != nil { + return nil, err + } // resolve types if err := g.manager.ResolveTypes(); err != nil { return nil, err @@ -407,6 +413,7 @@ func (g *Generator) buildTypes( for _, c := range collections { // Copy the loop variable before usage within the loop or it // will be reassigned before the thunk is run + // TODO remove when Go 1.22 collection := c fieldDescriptions := collection.Schema.Fields isEmbeddedObject := collection.Description.Name == "" @@ -517,6 +524,67 @@ func (g *Generator) buildTypes( return objs, nil } +// buildMutationInputTypes creates the input object types +// for collection create and update mutation operations. +func (g *Generator) buildMutationInputTypes(collections []client.CollectionDefinition) error { + for _, c := range collections { + // Copy the loop variable before usage within the loop or it + // will be reassigned before the thunk is run + // TODO remove when Go 1.22 + collection := c + fieldDescriptions := collection.Schema.Fields + mutationInputName := collection.Description.Name + "MutationInputArg" + + // check if mutation input type exists + if _, ok := g.manager.schema.TypeMap()[mutationInputName]; ok { + return NewErrMutationInputTypeAlreadyExist(mutationInputName) + } + + mutationObjConf := gql.InputObjectConfig{ + Name: mutationInputName, + } + + // Wrap mutation input object definition in a thunk so we can + // handle any embedded object which is defined + // at a future point in time. + mutationObjConf.Fields = (gql.InputObjectConfigFieldMapThunk)(func() (gql.InputObjectConfigFieldMap, error) { + fields := make(gql.InputObjectConfigFieldMap) + + for _, field := range fieldDescriptions { + if strings.HasPrefix(field.Name, "_") { + // ignore system defined args as the + // user cannot override their values + continue + } + + var ttype gql.Type + if field.Kind == client.FieldKind_FOREIGN_OBJECT { + ttype = gql.ID + } else if field.Kind == client.FieldKind_FOREIGN_OBJECT_ARRAY { + ttype = gql.NewList(gql.ID) + } else { + var ok bool + ttype, ok = fieldKindToGQLType[field.Kind] + if !ok { + return nil, NewErrTypeNotFound(fmt.Sprint(field.Kind)) + } + } + + fields[field.Name] = &gql.InputObjectFieldConfig{ + Type: ttype, + } + } + + return fields, nil + }) + + mutationObj := gql.NewInputObject(mutationObjConf) + g.manager.schema.TypeMap()[mutationObj.Name()] = mutationObj + } + + return nil +} + func (g *Generator) genAggregateFields(ctx context.Context) error { topLevelCountInputs := map[string]*gql.InputObject{} topLevelNumericAggInputs := map[string]*gql.InputObject{} @@ -950,79 +1018,51 @@ func (g *Generator) GenerateMutationInputForGQLType(obj *gql.Object) ([]*gql.Fie return nil, obj.Error() } - typeName := obj.Name() - filter, ok := g.manager.schema.TypeMap()[typeName+"FilterArg"].(*gql.InputObject) - if !ok { - return nil, NewErrTypeNotFound(typeName + "FilterArg") - } - - return g.genTypeMutationFields(obj, filter) -} + filterInputName := genTypeName(obj, "FilterArg") + mutationInputName := genTypeName(obj, "MutationInputArg") -func (g *Generator) genTypeMutationFields( - obj *gql.Object, - filterInput *gql.InputObject, -) ([]*gql.Field, error) { - create, err := g.genTypeMutationCreateField(obj) - if err != nil { - return nil, err - } - update, err := g.genTypeMutationUpdateField(obj, filterInput) - if err != nil { - return nil, err + filterInput, ok := g.manager.schema.TypeMap()[filterInputName].(*gql.InputObject) + if !ok { + return nil, NewErrTypeNotFound(filterInputName) } - delete, err := g.genTypeMutationDeleteField(obj, filterInput) - if err != nil { - return nil, err + mutationInput, ok := g.manager.schema.TypeMap()[mutationInputName] + if !ok { + return nil, NewErrTypeNotFound(mutationInputName) } - return []*gql.Field{create, update, delete}, nil -} -func (g *Generator) genTypeMutationCreateField(obj *gql.Object) (*gql.Field, error) { - field := &gql.Field{ + create := &gql.Field{ Name: "create_" + obj.Name(), Description: createDocumentDescription, Type: obj, Args: gql.FieldConfigArgument{ - "data": schemaTypes.NewArgConfig(gql.String, createDataArgDescription), + "input": schemaTypes.NewArgConfig(mutationInput, "Create field values"), }, } - return field, nil -} -func (g *Generator) genTypeMutationUpdateField( - obj *gql.Object, - filter *gql.InputObject, -) (*gql.Field, error) { - field := &gql.Field{ + update := &gql.Field{ Name: "update_" + obj.Name(), Description: updateDocumentsDescription, Type: gql.NewList(obj), Args: gql.FieldConfigArgument{ request.DocIDArgName: schemaTypes.NewArgConfig(gql.ID, updateIDArgDescription), request.DocIDsArgName: schemaTypes.NewArgConfig(gql.NewList(gql.ID), updateIDsArgDescription), - "filter": schemaTypes.NewArgConfig(filter, updateFilterArgDescription), - "data": schemaTypes.NewArgConfig(gql.String, updateDataArgDescription), + "filter": schemaTypes.NewArgConfig(filterInput, updateFilterArgDescription), + "input": schemaTypes.NewArgConfig(mutationInput, "Update field values"), }, } - return field, nil -} -func (g *Generator) genTypeMutationDeleteField( - obj *gql.Object, - filter *gql.InputObject, -) (*gql.Field, error) { - field := &gql.Field{ + delete := &gql.Field{ Name: "delete_" + obj.Name(), Description: deleteDocumentsDescription, Type: gql.NewList(obj), Args: gql.FieldConfigArgument{ request.DocIDArgName: schemaTypes.NewArgConfig(gql.ID, deleteIDArgDescription), request.DocIDsArgName: schemaTypes.NewArgConfig(gql.NewList(gql.ID), deleteIDsArgDescription), - "filter": schemaTypes.NewArgConfig(filter, deleteFilterArgDescription), + "filter": schemaTypes.NewArgConfig(filterInput, deleteFilterArgDescription), }, } - return field, nil + + return []*gql.Field{create, update, delete}, nil } func (g *Generator) genTypeFieldsEnum(obj *gql.Object) *gql.Enum { diff --git a/tests/integration/events/simple/with_create_txn_test.go b/tests/integration/events/simple/with_create_txn_test.go index 962a16e39a..c890792157 100644 --- a/tests/integration/events/simple/with_create_txn_test.go +++ b/tests/integration/events/simple/with_create_txn_test.go @@ -28,7 +28,7 @@ func TestEventsSimpleWithCreateWithTxnDiscarded(t *testing.T) { r := d.ExecRequest( ctx, `mutation { - create_Users(data: "{\"name\": \"John\"}") { + create_Users(input: {name: "John"}) { _docID } }`, @@ -43,7 +43,7 @@ func TestEventsSimpleWithCreateWithTxnDiscarded(t *testing.T) { r := d.WithTxn(txn).ExecRequest( ctx, `mutation { - create_Users(data: "{\"name\": \"Shahzad\"}") { + create_Users(input: {name: "Shahzad"}) { _docID } }`, diff --git a/tests/integration/explain/debug/create_test.go b/tests/integration/explain/debug/create_test.go index 029c0eaefe..21d334bb84 100644 --- a/tests/integration/explain/debug/create_test.go +++ b/tests/integration/explain/debug/create_test.go @@ -39,7 +39,7 @@ func TestDebugExplainMutationRequestWithCreate(t *testing.T) { testUtils.ExplainRequest{ Request: `mutation @explain(type: debug) { - create_Author(data: "{\"name\": \"Shahzad Lone\",\"age\": 27,\"verified\": true}") { + create_Author(input: {name: "Shahzad Lone", age: 27, verified: true}) { name age } @@ -63,7 +63,7 @@ func TestDebugExplainMutationRequestDoesNotCreateDocGivenDuplicate(t *testing.T) testUtils.ExplainRequest{ Request: `mutation @explain(type: debug) { - create_Author(data: "{\"name\": \"Shahzad Lone\",\"age\": 27}") { + create_Author(input: {name: "Shahzad Lone", age: 27}) { name age } diff --git a/tests/integration/explain/debug/update_test.go b/tests/integration/explain/debug/update_test.go index 8c8ed82f0b..d9c190ca0c 100644 --- a/tests/integration/explain/debug/update_test.go +++ b/tests/integration/explain/debug/update_test.go @@ -46,7 +46,7 @@ func TestDebugExplainMutationRequestWithUpdateUsingBooleanFilter(t *testing.T) { _eq: true } }, - data: "{\"age\": 59}" + input: {age: 59} ) { _docID name @@ -78,7 +78,7 @@ func TestDebugExplainMutationRequestWithUpdateUsingIds(t *testing.T) { "bae-bfbfc89c-0d63-5ea4-81a3-3ebd295be67f", "bae-079d0bd8-4b1b-5f5f-bd95-4d915c277f9d" ], - data: "{\"age\": 59}" + input: {age: 59} ) { _docID name @@ -107,7 +107,7 @@ func TestDebugExplainMutationRequestWithUpdateUsingId(t *testing.T) { Request: `mutation @explain(type: debug) { update_Author( docID: "bae-bfbfc89c-0d63-5ea4-81a3-3ebd295be67f", - data: "{\"age\": 59}" + input: {age: 59} ) { _docID name @@ -144,7 +144,7 @@ func TestDebugExplainMutationRequestWithUpdateUsingIdsAndFilter(t *testing.T) { "bae-bfbfc89c-0d63-5ea4-81a3-3ebd295be67f", "bae-079d0bd8-4b1b-5f5f-bd95-4d915c277f9d" ], - data: "{\"age\": 59}" + input: {age: 59} ) { _docID name diff --git a/tests/integration/explain/default/create_test.go b/tests/integration/explain/default/create_test.go index eaecd7879f..dc57671bdd 100644 --- a/tests/integration/explain/default/create_test.go +++ b/tests/integration/explain/default/create_test.go @@ -39,7 +39,7 @@ func TestDefaultExplainMutationRequestWithCreate(t *testing.T) { testUtils.ExplainRequest{ Request: `mutation @explain { - create_Author(data: "{\"name\": \"Shahzad Lone\",\"age\": 27,\"verified\": true}") { + create_Author(input: {name: "Shahzad Lone", age: 27, verified: true}) { name age } @@ -52,8 +52,8 @@ func TestDefaultExplainMutationRequestWithCreate(t *testing.T) { TargetNodeName: "createNode", IncludeChildNodes: false, ExpectedAttributes: dataMap{ - "data": dataMap{ - "age": float64(27), + "input": dataMap{ + "age": int32(27), "name": "Shahzad Lone", "verified": true, }, @@ -77,7 +77,7 @@ func TestDefaultExplainMutationRequestDoesNotCreateDocGivenDuplicate(t *testing. testUtils.ExplainRequest{ Request: `mutation @explain { - create_Author(data: "{\"name\": \"Shahzad Lone\",\"age\": 27}") { + create_Author(input: {name: "Shahzad Lone", age: 27}) { name age } @@ -90,8 +90,8 @@ func TestDefaultExplainMutationRequestDoesNotCreateDocGivenDuplicate(t *testing. TargetNodeName: "createNode", IncludeChildNodes: false, ExpectedAttributes: dataMap{ - "data": dataMap{ - "age": float64(27), + "input": dataMap{ + "age": int32(27), "name": "Shahzad Lone", }, }, diff --git a/tests/integration/explain/default/update_test.go b/tests/integration/explain/default/update_test.go index cd2af141c3..e2d52e8b73 100644 --- a/tests/integration/explain/default/update_test.go +++ b/tests/integration/explain/default/update_test.go @@ -46,7 +46,7 @@ func TestDefaultExplainMutationRequestWithUpdateUsingBooleanFilter(t *testing.T) _eq: true } }, - data: "{\"age\": 59}" + input: {age: 59} ) { _docID name @@ -61,8 +61,8 @@ func TestDefaultExplainMutationRequestWithUpdateUsingBooleanFilter(t *testing.T) TargetNodeName: "updateNode", IncludeChildNodes: false, ExpectedAttributes: dataMap{ - "data": dataMap{ - "age": float64(59), + "input": dataMap{ + "age": int32(59), }, "filter": dataMap{ "verified": dataMap{ @@ -115,7 +115,7 @@ func TestDefaultExplainMutationRequestWithUpdateUsingIds(t *testing.T) { "bae-bfbfc89c-0d63-5ea4-81a3-3ebd295be67f", "bae-079d0bd8-4b1b-5f5f-bd95-4d915c277f9d" ], - data: "{\"age\": 59}" + input: {age: 59} ) { _docID name @@ -130,8 +130,8 @@ func TestDefaultExplainMutationRequestWithUpdateUsingIds(t *testing.T) { TargetNodeName: "updateNode", IncludeChildNodes: false, ExpectedAttributes: dataMap{ - "data": dataMap{ - "age": float64(59), + "input": dataMap{ + "age": int32(59), }, "filter": nil, "docIDs": []string{ @@ -180,7 +180,7 @@ func TestDefaultExplainMutationRequestWithUpdateUsingId(t *testing.T) { Request: `mutation @explain { update_Author( docID: "bae-bfbfc89c-0d63-5ea4-81a3-3ebd295be67f", - data: "{\"age\": 59}" + input: {age: 59} ) { _docID name @@ -195,8 +195,8 @@ func TestDefaultExplainMutationRequestWithUpdateUsingId(t *testing.T) { TargetNodeName: "updateNode", IncludeChildNodes: false, ExpectedAttributes: dataMap{ - "data": dataMap{ - "age": float64(59), + "input": dataMap{ + "age": int32(59), }, "filter": nil, "docIDs": []string{ @@ -248,7 +248,7 @@ func TestDefaultExplainMutationRequestWithUpdateUsingIdsAndFilter(t *testing.T) "bae-bfbfc89c-0d63-5ea4-81a3-3ebd295be67f", "bae-079d0bd8-4b1b-5f5f-bd95-4d915c277f9d" ], - data: "{\"age\": 59}" + input: {age: 59} ) { _docID name @@ -263,8 +263,8 @@ func TestDefaultExplainMutationRequestWithUpdateUsingIdsAndFilter(t *testing.T) TargetNodeName: "updateNode", IncludeChildNodes: false, ExpectedAttributes: dataMap{ - "data": dataMap{ - "age": float64(59), + "input": dataMap{ + "age": int32(59), }, "filter": dataMap{ "verified": dataMap{ diff --git a/tests/integration/explain/execute/create_test.go b/tests/integration/explain/execute/create_test.go index bd99ab39a4..58736edb90 100644 --- a/tests/integration/explain/execute/create_test.go +++ b/tests/integration/explain/execute/create_test.go @@ -27,7 +27,7 @@ func TestExecuteExplainMutationRequestWithCreate(t *testing.T) { testUtils.ExplainRequest{ Request: `mutation @explain(type: execute) { - create_Author(data: "{\"name\": \"Shahzad Lone\",\"age\": 27,\"verified\": true}") { + create_Author(input: {name: "Shahzad Lone", age: 27, verified: true}) { name } }`, diff --git a/tests/integration/explain/execute/update_test.go b/tests/integration/explain/execute/update_test.go index fa54f7f331..4f7a262136 100644 --- a/tests/integration/explain/execute/update_test.go +++ b/tests/integration/explain/execute/update_test.go @@ -35,7 +35,7 @@ func TestExecuteExplainMutationRequestWithUpdateUsingIDs(t *testing.T) { "bae-c8448e47-6cd1-571f-90bd-364acb80da7b", "bae-f01bf83f-1507-5fb5-a6a3-09ecffa3c692" ], - data: "{\"country\": \"USA\"}" + input: {country: "USA"} ) { country city @@ -93,7 +93,7 @@ func TestExecuteExplainMutationRequestWithUpdateUsingFilter(t *testing.T) { _eq: "Waterloo" } }, - data: "{\"country\": \"USA\"}" + input: {country: "USA"} ) { country city diff --git a/tests/integration/gql.go b/tests/integration/gql.go new file mode 100644 index 0000000000..22a368adf7 --- /dev/null +++ b/tests/integration/gql.go @@ -0,0 +1,68 @@ +// 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 tests + +import ( + "encoding/json" + "fmt" + "strings" +) + +// jsonToGql transforms a json doc string to a gql string. +func jsonToGQL(val string) (string, error) { + var doc map[string]any + if err := json.Unmarshal([]byte(val), &doc); err != nil { + return "", err + } + return mapToGQL(doc) +} + +// valueToGQL transforms a value to a gql string. +func valueToGQL(val any) (string, error) { + switch t := val.(type) { + case map[string]any: + return mapToGQL(t) + + case []any: + return sliceToGQL(t) + } + out, err := json.Marshal(val) + if err != nil { + return "", err + } + return string(out), nil +} + +// mapToGql transforms a map to a gql string. +func mapToGQL(val map[string]any) (string, error) { + var entries []string + for k, v := range val { + out, err := valueToGQL(v) + if err != nil { + return "", err + } + entries = append(entries, fmt.Sprintf("%s: %s", k, out)) + } + return fmt.Sprintf("{%s}", strings.Join(entries, ",")), nil +} + +// sliceToGQL transforms a slice to a gql string. +func sliceToGQL(val []any) (string, error) { + var entries []string + for _, v := range val { + out, err := valueToGQL(v) + if err != nil { + return "", err + } + entries = append(entries, out) + } + return fmt.Sprintf("[%s]", strings.Join(entries, ",")), nil +} diff --git a/tests/integration/mutation/create/field_kinds/one_to_many/utils.go b/tests/integration/mutation/create/field_kinds/one_to_many/utils.go index 21b9524567..c4ef949e53 100644 --- a/tests/integration/mutation/create/field_kinds/one_to_many/utils.go +++ b/tests/integration/mutation/create/field_kinds/one_to_many/utils.go @@ -20,7 +20,8 @@ func executeTestCase(t *testing.T, test testUtils.TestCase) { testUtils.ExecuteTestCase( t, testUtils.TestCase{ - Description: test.Description, + Description: test.Description, + SupportedMutationTypes: test.SupportedMutationTypes, Actions: append( []any{ testUtils.SchemaUpdate{ diff --git a/tests/integration/mutation/create/field_kinds/one_to_many/with_alias_test.go b/tests/integration/mutation/create/field_kinds/one_to_many/with_alias_test.go index 3b37756b6c..27ddcf0e68 100644 --- a/tests/integration/mutation/create/field_kinds/one_to_many/with_alias_test.go +++ b/tests/integration/mutation/create/field_kinds/one_to_many/with_alias_test.go @@ -14,12 +14,20 @@ import ( "fmt" "testing" + "github.com/sourcenetwork/immutable" + testUtils "github.com/sourcenetwork/defradb/tests/integration" ) func TestMutationCreateOneToMany_AliasedRelationNameWithInvalidField_Error(t *testing.T) { test := testUtils.TestCase{ Description: "One to many create mutation, with an invalid field, with alias.", + SupportedMutationTypes: immutable.Some([]testUtils.MutationType{ + // GQL mutation will return a different error + // when field types do not match + testUtils.CollectionNamedMutationType, + testUtils.CollectionSaveMutationType, + }), Actions: []any{ testUtils.CreateDoc{ Doc: `{ @@ -36,6 +44,12 @@ func TestMutationCreateOneToMany_AliasedRelationNameWithInvalidField_Error(t *te func TestMutationCreateOneToMany_AliasedRelationNameNonExistingRelationSingleSide_NoIDFieldError(t *testing.T) { test := testUtils.TestCase{ Description: "One to many create mutation, non-existing id, from the single side, no id relation field, with alias.", + SupportedMutationTypes: immutable.Some([]testUtils.MutationType{ + // GQL mutation will return a different error + // when field types do not match + testUtils.CollectionNamedMutationType, + testUtils.CollectionSaveMutationType, + }), Actions: []any{ testUtils.CreateDoc{ CollectionID: 0, diff --git a/tests/integration/mutation/create/field_kinds/one_to_many/with_simple_test.go b/tests/integration/mutation/create/field_kinds/one_to_many/with_simple_test.go index e72d7d218e..2a8b64d1b1 100644 --- a/tests/integration/mutation/create/field_kinds/one_to_many/with_simple_test.go +++ b/tests/integration/mutation/create/field_kinds/one_to_many/with_simple_test.go @@ -15,11 +15,19 @@ import ( "testing" testUtils "github.com/sourcenetwork/defradb/tests/integration" + + "github.com/sourcenetwork/immutable" ) func TestMutationCreateOneToMany_WithInvalidField_Error(t *testing.T) { test := testUtils.TestCase{ Description: "One to many create mutation, with an invalid field.", + SupportedMutationTypes: immutable.Some([]testUtils.MutationType{ + // GQL mutation will return a different error + // when field types do not match + testUtils.CollectionNamedMutationType, + testUtils.CollectionSaveMutationType, + }), Actions: []any{ testUtils.CreateDoc{ Doc: `{ @@ -36,6 +44,12 @@ func TestMutationCreateOneToMany_WithInvalidField_Error(t *testing.T) { func TestMutationCreateOneToMany_NonExistingRelationSingleSide_NoIDFieldError(t *testing.T) { test := testUtils.TestCase{ Description: "One to many create mutation, non-existing id, from the single side, no id relation field.", + SupportedMutationTypes: immutable.Some([]testUtils.MutationType{ + // GQL mutation will return a different error + // when field types do not match + testUtils.CollectionNamedMutationType, + testUtils.CollectionSaveMutationType, + }), Actions: []any{ testUtils.CreateDoc{ CollectionID: 0, diff --git a/tests/integration/mutation/create/field_kinds/one_to_one/utils.go b/tests/integration/mutation/create/field_kinds/one_to_one/utils.go index 4b5d33f618..8cd920a063 100644 --- a/tests/integration/mutation/create/field_kinds/one_to_one/utils.go +++ b/tests/integration/mutation/create/field_kinds/one_to_one/utils.go @@ -20,7 +20,8 @@ func executeTestCase(t *testing.T, test testUtils.TestCase) { testUtils.ExecuteTestCase( t, testUtils.TestCase{ - Description: test.Description, + Description: test.Description, + SupportedMutationTypes: test.SupportedMutationTypes, Actions: append( []any{ testUtils.SchemaUpdate{ diff --git a/tests/integration/mutation/create/field_kinds/one_to_one/with_alias_test.go b/tests/integration/mutation/create/field_kinds/one_to_one/with_alias_test.go index da8bd1b7b0..18d4a2e13c 100644 --- a/tests/integration/mutation/create/field_kinds/one_to_one/with_alias_test.go +++ b/tests/integration/mutation/create/field_kinds/one_to_one/with_alias_test.go @@ -14,12 +14,20 @@ import ( "fmt" "testing" + "github.com/sourcenetwork/immutable" + testUtils "github.com/sourcenetwork/defradb/tests/integration" ) func TestMutationCreateOneToOne_UseAliasWithInvalidField_Error(t *testing.T) { test := testUtils.TestCase{ Description: "One to one create mutation, alias relation, with an invalid field.", + SupportedMutationTypes: immutable.Some([]testUtils.MutationType{ + // GQL mutation will return a different error + // when field types do not match + testUtils.CollectionNamedMutationType, + testUtils.CollectionSaveMutationType, + }), Actions: []any{ testUtils.CreateDoc{ CollectionID: 1, diff --git a/tests/integration/mutation/create/field_kinds/one_to_one/with_simple_test.go b/tests/integration/mutation/create/field_kinds/one_to_one/with_simple_test.go index cf985bfa18..30545d6e7c 100644 --- a/tests/integration/mutation/create/field_kinds/one_to_one/with_simple_test.go +++ b/tests/integration/mutation/create/field_kinds/one_to_one/with_simple_test.go @@ -15,11 +15,19 @@ import ( "testing" testUtils "github.com/sourcenetwork/defradb/tests/integration" + + "github.com/sourcenetwork/immutable" ) func TestMutationCreateOneToOne_WithInvalidField_Error(t *testing.T) { test := testUtils.TestCase{ Description: "One to one create mutation, with an invalid field.", + SupportedMutationTypes: immutable.Some([]testUtils.MutationType{ + // GQL mutation will return a different error + // when field types do not match + testUtils.CollectionNamedMutationType, + testUtils.CollectionSaveMutationType, + }), Actions: []any{ testUtils.CreateDoc{ CollectionID: 1, diff --git a/tests/integration/mutation/create/field_kinds/one_to_one_to_one/with_txn_test.go b/tests/integration/mutation/create/field_kinds/one_to_one_to_one/with_txn_test.go index accf929402..64272779d3 100644 --- a/tests/integration/mutation/create/field_kinds/one_to_one_to_one/with_txn_test.go +++ b/tests/integration/mutation/create/field_kinds/one_to_one_to_one/with_txn_test.go @@ -42,7 +42,7 @@ func TestTransactionalCreationAndLinkingOfRelationalDocumentsForward(t *testing. testUtils.Request{ TransactionID: immutable.Some(0), Request: `mutation { - create_Book(data: "{\"name\": \"Book By Website\",\"rating\": 4.0, \"publisher_id\": \"bae-0e7c3bb5-4917-5d98-9fcf-b9db369ea6e4\"}") { + create_Book(input: {name: "Book By Website", rating: 4.0, publisher_id: "bae-0e7c3bb5-4917-5d98-9fcf-b9db369ea6e4"}) { _docID } }`, @@ -55,7 +55,7 @@ func TestTransactionalCreationAndLinkingOfRelationalDocumentsForward(t *testing. testUtils.Request{ TransactionID: immutable.Some(1), Request: `mutation { - create_Book(data: "{\"name\": \"Book By Online\",\"rating\": 4.0, \"publisher_id\": \"bae-8a381044-9206-51e7-8bc8-dc683d5f2523\"}") { + create_Book(input: {name: "Book By Online", rating: 4.0, publisher_id: "bae-8a381044-9206-51e7-8bc8-dc683d5f2523"}) { _docID } }`, @@ -194,7 +194,7 @@ func TestTransactionalCreationAndLinkingOfRelationalDocumentsBackward(t *testing testUtils.Request{ TransactionID: immutable.Some(0), Request: `mutation { - create_Book(data: "{\"name\": \"Book By Website\",\"rating\": 4.0, \"publisher_id\": \"bae-0e7c3bb5-4917-5d98-9fcf-b9db369ea6e4\"}") { + create_Book(input: {name: "Book By Website", rating: 4.0, publisher_id: "bae-0e7c3bb5-4917-5d98-9fcf-b9db369ea6e4"}) { _docID } }`, @@ -207,7 +207,7 @@ func TestTransactionalCreationAndLinkingOfRelationalDocumentsBackward(t *testing testUtils.Request{ TransactionID: immutable.Some(1), Request: `mutation { - create_Book(data: "{\"name\": \"Book By Online\",\"rating\": 4.0, \"publisher_id\": \"bae-8a381044-9206-51e7-8bc8-dc683d5f2523\"}") { + create_Book(input: {name: "Book By Online", rating: 4.0, publisher_id: "bae-8a381044-9206-51e7-8bc8-dc683d5f2523"}) { _docID } }`, diff --git a/tests/integration/mutation/create/simple_test.go b/tests/integration/mutation/create/simple_test.go index cedac8c58e..d095fdfc6d 100644 --- a/tests/integration/mutation/create/simple_test.go +++ b/tests/integration/mutation/create/simple_test.go @@ -21,6 +21,12 @@ import ( func TestMutationCreate_GivenNonExistantField_Errors(t *testing.T) { test := testUtils.TestCase{ Description: "Simple create mutation with non existant field", + SupportedMutationTypes: immutable.Some([]testUtils.MutationType{ + // GQL mutation will return a different error + // when field types do not match + testUtils.CollectionNamedMutationType, + testUtils.CollectionSaveMutationType, + }), Actions: []any{ testUtils.SchemaUpdate{ Schema: ` @@ -132,9 +138,9 @@ func TestMutationCreate_GivenDuplicate_Errors(t *testing.T) { testUtils.ExecuteTestCase(t, test) } -func TestMutationCreate_GivenEmptyData_Errors(t *testing.T) { +func TestMutationCreate_GivenEmptyInput(t *testing.T) { test := testUtils.TestCase{ - Description: "Simple create mutation with empty data param.", + Description: "Simple create mutation with empty input param.", Actions: []any{ testUtils.SchemaUpdate{ Schema: ` @@ -145,11 +151,15 @@ func TestMutationCreate_GivenEmptyData_Errors(t *testing.T) { }, testUtils.Request{ Request: `mutation { - create_Users(data: "") { + create_Users(input: {}) { _docID } }`, - ExpectedError: "given data payload is empty", + Results: []map[string]any{ + { + "_docID": "bae-524bfa06-849c-5daf-b6df-05c2da80844d", + }, + }, }, }, } diff --git a/tests/integration/mutation/create/with_version_test.go b/tests/integration/mutation/create/with_version_test.go index 9749119c60..b4578786c9 100644 --- a/tests/integration/mutation/create/with_version_test.go +++ b/tests/integration/mutation/create/with_version_test.go @@ -29,7 +29,7 @@ func TestMutationCreate_ReturnsVersionCID(t *testing.T) { }, testUtils.Request{ Request: `mutation { - create_Users(data: "{\"name\": \"John\"}") { + create_Users(input: {name: "John"}) { _version { cid } diff --git a/tests/integration/mutation/mix/with_txn_test.go b/tests/integration/mutation/mix/with_txn_test.go index 50cbee7809..de45e22fd4 100644 --- a/tests/integration/mutation/mix/with_txn_test.go +++ b/tests/integration/mutation/mix/with_txn_test.go @@ -33,7 +33,7 @@ func TestMutationWithTxnDeletesUserGivenSameTransaction(t *testing.T) { testUtils.Request{ TransactionID: immutable.Some(0), Request: `mutation { - create_User(data: "{\"name\": \"John\",\"age\": 27}") { + create_User(input: {name: "John", age: 27}) { _docID } }`, @@ -77,7 +77,7 @@ func TestMutationWithTxnDoesNotDeletesUserGivenDifferentTransactions(t *testing. testUtils.Request{ TransactionID: immutable.Some(0), Request: `mutation { - create_User(data: "{\"name\": \"John\",\"age\": 27}") { + create_User(input: {name: "John", age: 27}) { _docID } }`, @@ -151,7 +151,7 @@ func TestMutationWithTxnDoesUpdateUserGivenSameTransactions(t *testing.T) { testUtils.Request{ TransactionID: immutable.Some(0), Request: `mutation { - update_User(data: "{\"age\": 28}") { + update_User(input: {age: 28}) { _docID } }`, @@ -205,7 +205,7 @@ func TestMutationWithTxnDoesNotUpdateUserGivenDifferentTransactions(t *testing.T testUtils.Request{ TransactionID: immutable.Some(0), Request: `mutation { - update_User(data: "{\"age\": 28}") { + update_User(input: {age: 28}) { _docID name age @@ -264,7 +264,7 @@ func TestMutationWithTxnDoesNotAllowUpdateInSecondTransactionUser(t *testing.T) testUtils.Request{ TransactionID: immutable.Some(0), Request: `mutation { - update_User(data: "{\"age\": 28}") { + update_User(input: {age: 28}) { _docID name age @@ -281,7 +281,7 @@ func TestMutationWithTxnDoesNotAllowUpdateInSecondTransactionUser(t *testing.T) testUtils.Request{ TransactionID: immutable.Some(1), Request: `mutation { - update_User(data: "{\"age\": 29}") { + update_User(input: {age: 29}) { _docID name age diff --git a/tests/integration/mutation/special/invalid_operation_test.go b/tests/integration/mutation/special/invalid_operation_test.go index 1694a37c67..1862d11c7c 100644 --- a/tests/integration/mutation/special/invalid_operation_test.go +++ b/tests/integration/mutation/special/invalid_operation_test.go @@ -29,7 +29,7 @@ func TestMutationInvalidMutation(t *testing.T) { }, testUtils.Request{ Request: `mutation { - dostuff_User(data: "") { + dostuff_User(input: {}) { _docID } }`, diff --git a/tests/integration/mutation/update/crdt/pncounter_test.go b/tests/integration/mutation/update/crdt/pncounter_test.go index fb5f30613e..f8ede1cffc 100644 --- a/tests/integration/mutation/update/crdt/pncounter_test.go +++ b/tests/integration/mutation/update/crdt/pncounter_test.go @@ -15,6 +15,8 @@ import ( "math" "testing" + "github.com/sourcenetwork/immutable" + testUtils "github.com/sourcenetwork/defradb/tests/integration" ) @@ -72,6 +74,12 @@ func TestPNCounterUpdate_IntKindWithPositiveIncrement_ShouldIncrement(t *testing func TestPNCounterUpdate_IntKindWithPositiveIncrementOverflow_RollsOverToMinInt64(t *testing.T) { test := testUtils.TestCase{ Description: "Positive increments of a PN Counter with Int type causing overflow behaviour", + SupportedMutationTypes: immutable.Some([]testUtils.MutationType{ + // GQL mutation will return an error + // when integer type overflows + testUtils.CollectionNamedMutationType, + testUtils.CollectionSaveMutationType, + }), Actions: []any{ testUtils.SchemaUpdate{ Schema: ` 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 46dddaffa0..b7d1546864 100644 --- a/tests/integration/mutation/update/field_kinds/date_time_test.go +++ b/tests/integration/mutation/update/field_kinds/date_time_test.go @@ -85,7 +85,7 @@ func TestMutationUpdate_WithDateTimeField_MultipleDocs(t *testing.T) { }, testUtils.Request{ Request: `mutation { - update_Users(data: "{\"created_at\": \"2031-07-23T03:23:23Z\"}") { + update_Users(input: {created_at: "2031-07-23T03:23:23Z"}) { name created_at } diff --git a/tests/integration/mutation/update/field_kinds/one_to_many/simple_test.go b/tests/integration/mutation/update/field_kinds/one_to_many/simple_test.go index 882fddd891..dda55ffcfa 100644 --- a/tests/integration/mutation/update/field_kinds/one_to_many/simple_test.go +++ b/tests/integration/mutation/update/field_kinds/one_to_many/simple_test.go @@ -15,6 +15,8 @@ import ( "testing" testUtils "github.com/sourcenetwork/defradb/tests/integration" + + "github.com/sourcenetwork/immutable" ) func TestMutationUpdateOneToMany_RelationIDToLinkFromSingleSide_Error(t *testing.T) { @@ -23,6 +25,12 @@ func TestMutationUpdateOneToMany_RelationIDToLinkFromSingleSide_Error(t *testing test := testUtils.TestCase{ Description: "One to many update mutation using relation id from single side (wrong)", + SupportedMutationTypes: immutable.Some([]testUtils.MutationType{ + // GQL mutation will return a different error + // when field types do not match + testUtils.CollectionNamedMutationType, + testUtils.CollectionSaveMutationType, + }), Actions: []any{ testUtils.CreateDoc{ CollectionID: 1, @@ -143,6 +151,12 @@ func TestMutationUpdateOneToMany_RelationIDToLinkFromManySideWithWrongField_Erro test := testUtils.TestCase{ Description: "One to many update mutation using relation id from many side, with a wrong field.", + SupportedMutationTypes: immutable.Some([]testUtils.MutationType{ + // GQL mutation will return a different error + // when field types do not match + testUtils.CollectionNamedMutationType, + testUtils.CollectionSaveMutationType, + }), Actions: []any{ testUtils.CreateDoc{ CollectionID: 1, diff --git a/tests/integration/mutation/update/field_kinds/one_to_many/with_alias_test.go b/tests/integration/mutation/update/field_kinds/one_to_many/with_alias_test.go index 6f4373976f..751ca67b78 100644 --- a/tests/integration/mutation/update/field_kinds/one_to_many/with_alias_test.go +++ b/tests/integration/mutation/update/field_kinds/one_to_many/with_alias_test.go @@ -262,6 +262,12 @@ func TestMutationUpdateOneToMany_AliasRelationNameToLinkFromManySideWithWrongFie test := testUtils.TestCase{ Description: "One to many update mutation using relation alias name from many side, with a wrong field.", + SupportedMutationTypes: immutable.Some([]testUtils.MutationType{ + // GQL mutation will return a different error + // when field types do not match + testUtils.CollectionNamedMutationType, + testUtils.CollectionSaveMutationType, + }), Actions: []any{ testUtils.CreateDoc{ CollectionID: 1, diff --git a/tests/integration/mutation/update/field_kinds/one_to_one/with_alias_test.go b/tests/integration/mutation/update/field_kinds/one_to_one/with_alias_test.go index 67d5f0b38c..fdb8928964 100644 --- a/tests/integration/mutation/update/field_kinds/one_to_one/with_alias_test.go +++ b/tests/integration/mutation/update/field_kinds/one_to_one/with_alias_test.go @@ -236,7 +236,7 @@ func TestMutationUpdateOneToOne_AliasRelationNameToLinkFromSecondarySideWithWron }`, author2ID, ), - ExpectedError: "The given field does not exist. Name: notName", + ExpectedError: "Unknown field.", }, }, } diff --git a/tests/integration/mutation/update/field_kinds/one_to_one/with_simple_test.go b/tests/integration/mutation/update/field_kinds/one_to_one/with_simple_test.go index 5b0980baab..6d38a9914d 100644 --- a/tests/integration/mutation/update/field_kinds/one_to_one/with_simple_test.go +++ b/tests/integration/mutation/update/field_kinds/one_to_one/with_simple_test.go @@ -14,9 +14,9 @@ import ( "fmt" "testing" - "github.com/sourcenetwork/immutable" - testUtils "github.com/sourcenetwork/defradb/tests/integration" + + "github.com/sourcenetwork/immutable" ) // Note: This test should probably not pass, as it contains a @@ -420,7 +420,7 @@ func TestMutationUpdateOneToOne_RelationIDToLinkFromSecondarySideWithWrongField_ }`, author2ID, ), - ExpectedError: "The given field does not exist. Name: notName", + ExpectedError: "In field \"notName\": Unknown field.", }, }, } diff --git a/tests/integration/mutation/update/with_filter_test.go b/tests/integration/mutation/update/with_filter_test.go index d7b3ae9dde..1819a8ba0c 100644 --- a/tests/integration/mutation/update/with_filter_test.go +++ b/tests/integration/mutation/update/with_filter_test.go @@ -37,7 +37,7 @@ func TestMutationUpdate_WithBooleanFilter_ResultFilteredOut(t *testing.T) { testUtils.Request{ // The update will result in a record that no longer matches the filter Request: `mutation { - update_Users(filter: {verified: {_eq: true}}, data: "{\"verified\":false}") { + update_Users(filter: {verified: {_eq: true}}, input: {verified: false}) { _docID name verified @@ -88,7 +88,7 @@ func TestMutationUpdate_WithBooleanFilter(t *testing.T) { }, testUtils.Request{ Request: `mutation { - update_Users(filter: {verified: {_eq: true}}, data: "{\"points\": 59}") { + update_Users(filter: {verified: {_eq: true}}, input: {points: 59}) { name points } diff --git a/tests/integration/mutation/update/with_id_test.go b/tests/integration/mutation/update/with_id_test.go index ddc0fe7128..899711a1ab 100644 --- a/tests/integration/mutation/update/with_id_test.go +++ b/tests/integration/mutation/update/with_id_test.go @@ -43,7 +43,7 @@ func TestMutationUpdate_WithId(t *testing.T) { }, testUtils.Request{ Request: `mutation { - update_Users(docID: "bae-cc36febf-4029-52b3-a876-c99c6293f588", data: "{\"points\": 59}") { + update_Users(docID: "bae-cc36febf-4029-52b3-a876-c99c6293f588", input: {points: 59}) { name points } @@ -82,7 +82,7 @@ func TestMutationUpdate_WithNonExistantId(t *testing.T) { }, testUtils.Request{ Request: `mutation { - update_Users(docID: "bae-does-not-exist", data: "{\"points\": 59}") { + update_Users(docID: "bae-does-not-exist", input: {points: 59}) { _docID name points diff --git a/tests/integration/mutation/update/with_ids_test.go b/tests/integration/mutation/update/with_ids_test.go index d1d7645829..59f4e7ac73 100644 --- a/tests/integration/mutation/update/with_ids_test.go +++ b/tests/integration/mutation/update/with_ids_test.go @@ -52,7 +52,7 @@ func TestMutationUpdate_WithIds(t *testing.T) { Request: `mutation { update_Users( docIDs: ["bae-cc36febf-4029-52b3-a876-c99c6293f588", "bae-3ac659d1-521a-5eba-a833-5c58b151ca72"], - data: "{\"points\": 59}" + input: {points: 59} ) { name points diff --git a/tests/integration/query/one_to_many/with_id_field_test.go b/tests/integration/query/one_to_many/with_id_field_test.go index 0a26cc17ff..8a16f1c49a 100644 --- a/tests/integration/query/one_to_many/with_id_field_test.go +++ b/tests/integration/query/one_to_many/with_id_field_test.go @@ -14,12 +14,20 @@ import ( "testing" testUtils "github.com/sourcenetwork/defradb/tests/integration" + + "github.com/sourcenetwork/immutable" ) // This documents unwanted behaviour, see https://github.com/sourcenetwork/defradb/issues/1520 func TestQueryOneToManyWithIdFieldOnPrimary(t *testing.T) { test := testUtils.TestCase{ Description: "One-to-many relation primary direction, id field with name clash on primary side", + SupportedMutationTypes: immutable.Some([]testUtils.MutationType{ + // GQL mutation will return a different error + // when field types do not match + testUtils.CollectionNamedMutationType, + testUtils.CollectionSaveMutationType, + }), Actions: []any{ testUtils.SchemaUpdate{ Schema: ` diff --git a/tests/integration/schema/migrations/query/with_update_test.go b/tests/integration/schema/migrations/query/with_update_test.go index 9fbf2b914a..1c5c8e87a9 100644 --- a/tests/integration/schema/migrations/query/with_update_test.go +++ b/tests/integration/schema/migrations/query/with_update_test.go @@ -62,7 +62,7 @@ func TestSchemaMigrationQueryWithUpdateRequest(t *testing.T) { }, testUtils.Request{ Request: `mutation { - update_Users(data: "{\"name\":\"Johnnnn\"}") { + update_Users(input: {name: "Johnnnn"}) { name verified } diff --git a/tests/integration/schema/updates/add/field/kind/foreign_object_array_test.go b/tests/integration/schema/updates/add/field/kind/foreign_object_array_test.go index fb14d6ef30..b0ee08bb80 100644 --- a/tests/integration/schema/updates/add/field/kind/foreign_object_array_test.go +++ b/tests/integration/schema/updates/add/field/kind/foreign_object_array_test.go @@ -469,7 +469,7 @@ func TestSchemaUpdatesAddFieldKindForeignObjectArray_Succeeds(t *testing.T) { }, testUtils.Request{ Request: `mutation { - create_Users(data: "{\"name\": \"John\"}") { + create_Users(input: {name: "John"}) { _docID } }`, @@ -481,7 +481,7 @@ func TestSchemaUpdatesAddFieldKindForeignObjectArray_Succeeds(t *testing.T) { }, testUtils.Request{ Request: fmt.Sprintf(`mutation { - create_Users(data: "{\"name\": \"Keenan\", \"foo\": \"%s\"}") { + create_Users(input: {name: "Keenan", foo: "%s"}) { name foo { name @@ -652,7 +652,7 @@ func TestSchemaUpdatesAddFieldKindForeignObjectArray_SingleSecondaryObjectKindSu CollectionID: 0, Doc: fmt.Sprintf(`{ "name": "Keenan", - "foo": "%s" + "foo_id": "%s" }`, key1, ), diff --git a/tests/integration/schema/updates/add/field/kind/foreign_object_test.go b/tests/integration/schema/updates/add/field/kind/foreign_object_test.go index abaa1d4564..dc724d5af7 100644 --- a/tests/integration/schema/updates/add/field/kind/foreign_object_test.go +++ b/tests/integration/schema/updates/add/field/kind/foreign_object_test.go @@ -469,7 +469,7 @@ func TestSchemaUpdatesAddFieldKindForeignObject_Succeeds(t *testing.T) { }, testUtils.Request{ Request: `mutation { - create_Users(data: "{\"name\": \"John\"}") { + create_Users(input: {name: "John"}) { _docID } }`, @@ -481,7 +481,7 @@ func TestSchemaUpdatesAddFieldKindForeignObject_Succeeds(t *testing.T) { }, testUtils.Request{ Request: fmt.Sprintf(`mutation { - create_Users(data: "{\"name\": \"Keenan\", \"foo\": \"%s\"}") { + create_Users(input: {name: "Keenan", foo: "%s"}) { name foo { name diff --git a/tests/integration/subscription/subscription_test.go b/tests/integration/subscription/subscription_test.go index 947330fbd0..02ac058c90 100644 --- a/tests/integration/subscription/subscription_test.go +++ b/tests/integration/subscription/subscription_test.go @@ -43,7 +43,7 @@ func TestSubscriptionWithCreateMutations(t *testing.T) { }, testUtils.Request{ Request: `mutation { - create_User(data: "{\"name\": \"John\",\"age\": 27,\"points\": 42.1,\"verified\": true}") { + create_User(input: {name: "John", age: 27, points: 42.1, verified: true}) { name } }`, @@ -55,7 +55,7 @@ func TestSubscriptionWithCreateMutations(t *testing.T) { }, testUtils.Request{ Request: `mutation { - create_User(data: "{\"name\": \"Addo\",\"age\": 31,\"points\": 42.1,\"verified\": true}") { + create_User(input: {name: "Addo", age: 31, points: 42.1, verified: true}) { name } }`, @@ -93,7 +93,7 @@ func TestSubscriptionWithFilterAndOneCreateMutation(t *testing.T) { }, testUtils.Request{ Request: `mutation { - create_User(data: "{\"name\": \"John\",\"age\": 27,\"points\": 42.1,\"verified\": true}") { + create_User(input: {name: "John", age: 27, points: 42.1, verified: true}) { name } }`, @@ -125,7 +125,7 @@ func TestSubscriptionWithFilterAndOneCreateMutationOutsideFilter(t *testing.T) { }, testUtils.Request{ Request: `mutation { - create_User(data: "{\"name\": \"John\",\"age\": 27,\"points\": 42.1,\"verified\": true}") { + create_User(input: {name: "John", age: 27, points: 42.1, verified: true}) { name } }`, @@ -163,7 +163,7 @@ func TestSubscriptionWithFilterAndCreateMutations(t *testing.T) { }, testUtils.Request{ Request: `mutation { - create_User(data: "{\"name\": \"John\",\"age\": 27,\"points\": 42.1,\"verified\": true}") { + create_User(input: {name: "John", age: 27, points: 42.1, verified: true}) { name } }`, @@ -175,7 +175,7 @@ func TestSubscriptionWithFilterAndCreateMutations(t *testing.T) { }, testUtils.Request{ Request: `mutation { - create_User(data: "{\"name\": \"Addo\",\"age\": 31,\"points\": 42.1,\"verified\": true}") { + create_User(input: {name: "Addo", age: 31, points: 42.1, verified: true}) { name } }`, @@ -233,7 +233,7 @@ func TestSubscriptionWithUpdateMutations(t *testing.T) { }, testUtils.Request{ Request: `mutation { - update_User(filter: {name: {_eq: "John"}}, data: "{\"points\": 45}") { + update_User(filter: {name: {_eq: "John"}}, input: {points: 45}) { name } }`, @@ -297,7 +297,7 @@ func TestSubscriptionWithUpdateAllMutations(t *testing.T) { }, testUtils.Request{ Request: `mutation { - update_User(data: "{\"points\": 55}") { + update_User(input: {points: 55}) { name } }`, diff --git a/tests/integration/utils2.go b/tests/integration/utils2.go index 9589cc1a9e..dc344d49f7 100644 --- a/tests/integration/utils2.go +++ b/tests/integration/utils2.go @@ -12,7 +12,6 @@ package tests import ( "context" - "encoding/json" "fmt" "os" "reflect" @@ -1147,17 +1146,17 @@ func createDocViaGQL( ) (*client.Document, error) { collection := collections[action.CollectionID] - escapedJson, err := json.Marshal(action.Doc) + input, err := jsonToGQL(action.Doc) require.NoError(s.t, err) request := fmt.Sprintf( `mutation { - create_%s(data: %s) { + create_%s(input: %s) { _docID } }`, collection.Name(), - escapedJson, + input, ) db := getStore(s, node, immutable.None[int](), action.ExpectedError) @@ -1294,18 +1293,18 @@ func updateDocViaGQL( doc := s.documents[action.CollectionID][action.DocID] collection := collections[action.CollectionID] - escapedJson, err := json.Marshal(action.Doc) + input, err := jsonToGQL(action.Doc) require.NoError(s.t, err) request := fmt.Sprintf( `mutation { - update_%s(docID: "%s", data: %s) { + update_%s(docID: "%s", input: %s) { _docID } }`, collection.Name(), doc.ID().String(), - escapedJson, + input, ) db := getStore(s, node, immutable.None[int](), action.ExpectedError)