diff --git a/internal/planner/mapper/targetable.go b/internal/planner/mapper/targetable.go index 68f7f993ef..a45e99a516 100644 --- a/internal/planner/mapper/targetable.go +++ b/internal/planner/mapper/targetable.go @@ -110,13 +110,17 @@ func filterObjectToMap(mapping *core.DocumentMapping, obj map[connor.FilterKey]a for k, v := range obj { switch keyType := k.(type) { case *PropertyIndex: - subObj := v.(map[connor.FilterKey]any) outkey, _ := mapping.TryToFindNameFromIndex(keyType.Index) - childMapping, ok := tryGetChildMapping(mapping, keyType.Index) - if ok { - outmap[outkey] = filterObjectToMap(childMapping, subObj) - } else { - outmap[outkey] = filterObjectToMap(mapping, subObj) + switch subObj := v.(type) { + case map[connor.FilterKey]any: + childMapping, ok := tryGetChildMapping(mapping, keyType.Index) + if ok { + outmap[outkey] = filterObjectToMap(childMapping, subObj) + } else { + outmap[outkey] = filterObjectToMap(mapping, subObj) + } + case nil: + outmap[outkey] = nil } case *Operator: diff --git a/internal/request/graphql/parser/commit.go b/internal/request/graphql/parser/commit.go index a8074d031e..b08d80fc69 100644 --- a/internal/request/graphql/parser/commit.go +++ b/internal/request/graphql/parser/commit.go @@ -35,31 +35,61 @@ func parseCommitSelect( arguments := gql.GetArgumentValues(fieldDef.Args, field.Arguments, exe.VariableValues) for _, argument := range field.Arguments { - prop := argument.Name.Value - if prop == request.DocIDArgName { - commit.DocID = immutable.Some(arguments[prop].(string)) - } else if prop == request.Cid { - commit.CID = immutable.Some(arguments[prop].(string)) - } else if prop == request.FieldIDName { - commit.FieldID = immutable.Some(arguments[prop].(string)) - } else if prop == request.OrderClause { - conditions, err := ParseConditionsInOrder(argument.Value.(*ast.ObjectValue), arguments[prop].(map[string]any)) + name := argument.Name.Value + value := arguments[name] + + switch name { + case request.DocIDArgName: + if v, ok := value.(string); ok { + commit.DocID = immutable.Some(v) + } + + case request.Cid: + if v, ok := value.(string); ok { + commit.CID = immutable.Some(v) + } + + case request.FieldIDName: + if v, ok := value.(string); ok { + commit.FieldID = immutable.Some(v) + } + + case request.OrderClause: + v, ok := value.(map[string]any) + if !ok { + continue // value is nil + } + conditions, err := ParseConditionsInOrder(argument.Value.(*ast.ObjectValue), v) if err != nil { return nil, err } commit.OrderBy = immutable.Some(request.OrderBy{ Conditions: conditions, }) - } else if prop == request.LimitClause { - commit.Limit = immutable.Some(uint64(arguments[prop].(int32))) - } else if prop == request.OffsetClause { - commit.Offset = immutable.Some(uint64(arguments[prop].(int32))) - } else if prop == request.DepthClause { - commit.Depth = immutable.Some(uint64(arguments[prop].(int32))) - } else if prop == request.GroupByClause { - fields := []string{} - for _, v := range arguments[prop].([]any) { - fields = append(fields, v.(string)) + + case request.LimitClause: + if v, ok := value.(int32); ok { + commit.Limit = immutable.Some(uint64(v)) + } + + case request.OffsetClause: + if v, ok := value.(int32); ok { + commit.Offset = immutable.Some(uint64(v)) + } + + case request.DepthClause: + if v, ok := value.(int32); ok { + commit.Depth = immutable.Some(uint64(v)) + } + + case request.GroupByClause: + v, ok := value.([]any) + if !ok { + continue // value is nil + } + fields := make([]string, len(v)) + for i, c := range v { + fields[i] = c.(string) } commit.GroupBy = immutable.Some(request.GroupBy{ Fields: fields, @@ -91,6 +121,9 @@ func parseCommitSelect( } commit.Fields, err = parseSelectFields(exe, fieldObject, field.SelectionSet) + if err != nil { + return nil, err + } return commit, err } diff --git a/internal/request/graphql/parser/filter.go b/internal/request/graphql/parser/filter.go index a8ed1ae85f..aa65f77dd2 100644 --- a/internal/request/graphql/parser/filter.go +++ b/internal/request/graphql/parser/filter.go @@ -78,16 +78,9 @@ func ParseConditionsInOrder(stmt *ast.ObjectValue, args map[string]any) ([]reque for _, field := range stmt.Fields { switch v := args[field.Name.Value].(type) { case int: // base direction parsed (hopefully, check NameToOrderDirection) - var dir request.OrderDirection - switch v { - case 0: - dir = request.ASC - - case 1: - dir = request.DESC - - default: - return nil, ErrInvalidOrderDirection + dir, err := parseOrderDirection(v) + if err != nil { + return nil, err } conditions = append(conditions, request.OrderCondition{ Fields: []string{field.Name.Value}, @@ -109,6 +102,9 @@ func ParseConditionsInOrder(stmt *ast.ObjectValue, args map[string]any) ([]reque conditions = append(conditions, cond) } + case nil: + continue // ignore nil filter input + default: return nil, client.NewErrUnhandledType("parseConditionInOrder", v) } @@ -199,3 +195,16 @@ func parseFilterFieldsForDescriptionSlice( } return fields, nil } + +func parseOrderDirection(v int) (request.OrderDirection, error) { + switch v { + case 0: + return request.ASC, nil + + case 1: + return request.DESC, nil + + default: + return request.ASC, ErrInvalidOrderDirection + } +} diff --git a/internal/request/graphql/parser/mutation.go b/internal/request/graphql/parser/mutation.go index 95785b78c9..3fa10195f4 100644 --- a/internal/request/graphql/parser/mutation.go +++ b/internal/request/graphql/parser/mutation.go @@ -95,38 +95,60 @@ func parseMutation(exe *gql.ExecutionContext, parent *gql.Object, field *ast.Fie mut.Collection = strings.Join(mutNameParts[1:], "_") } - // parse arguments for _, argument := range field.Arguments { - prop := argument.Name.Value - // parse each individual arg type seperately - if prop == request.Input { // parse input - mut.Input = arguments[prop].(map[string]any) - } else if prop == request.Inputs { - inputsValue := arguments[prop].([]any) - inputs := make([]map[string]any, len(inputsValue)) - for i, v := range inputsValue { + name := argument.Name.Value + value := arguments[name] + + switch name { + case request.Input: + if v, ok := value.(map[string]any); ok { + mut.Input = v + } + + case request.Inputs: + v, ok := value.([]any) + if !ok { + continue // value is nil + } + inputs := make([]map[string]any, len(v)) + for i, v := range v { inputs[i] = v.(map[string]any) } mut.Inputs = inputs - } else if prop == request.FilterClause { // parse filter - mut.Filter = immutable.Some(request.Filter{ - Conditions: arguments[prop].(map[string]any), - }) - } else if prop == request.DocIDArgName { - mut.DocIDs = immutable.Some([]string{arguments[prop].(string)}) - } else if prop == request.DocIDsArgName { - docIDsValue := arguments[prop].([]any) - docIDs := make([]string, len(docIDsValue)) - for i, v := range docIDsValue { + + case request.FilterClause: + if v, ok := value.(map[string]any); ok { + mut.Filter = immutable.Some(request.Filter{Conditions: v}) + } + + case request.DocIDArgName: + if v, ok := value.(string); ok { + mut.DocIDs = immutable.Some([]string{v}) + } + + case request.DocIDsArgName: + v, ok := value.([]any) + if !ok { + continue // value is nil + } + docIDs := make([]string, len(v)) + for i, v := range v { docIDs[i] = v.(string) } mut.DocIDs = immutable.Some(docIDs) - } else if prop == request.EncryptDocArgName { - mut.Encrypt = arguments[prop].(bool) - } else if prop == request.EncryptFieldsArgName { - fieldsValue := arguments[prop].([]any) - fields := make([]string, len(fieldsValue)) - for i, v := range fieldsValue { + + case request.EncryptDocArgName: + if v, ok := value.(bool); ok { + mut.Encrypt = v + } + + case request.EncryptFieldsArgName: + v, ok := value.([]any) + if !ok { + continue // value is nil + } + fields := make([]string, len(v)) + for i, v := range v { fields[i] = v.(string) } mut.EncryptFields = fields @@ -144,5 +166,9 @@ func parseMutation(exe *gql.ExecutionContext, parent *gql.Object, field *ast.Fie } mut.Fields, err = parseSelectFields(exe, fieldObject, field.SelectionSet) + if err != nil { + return nil, err + } + return mut, err } diff --git a/internal/request/graphql/parser/query.go b/internal/request/graphql/parser/query.go index 1284463ac0..47d6a70493 100644 --- a/internal/request/graphql/parser/query.go +++ b/internal/request/graphql/parser/query.go @@ -101,53 +101,77 @@ func parseSelect( fieldDef := gql.GetFieldDef(exe.Schema, parent, field.Name.Value) arguments := gql.GetArgumentValues(fieldDef.Args, field.Arguments, exe.VariableValues) - // parse arguments for _, argument := range field.Arguments { name := argument.Name.Value value := arguments[name] - // parse filter switch name { case request.FilterClause: - slct.Filter = immutable.Some(request.Filter{ - Conditions: value.(map[string]any), - }) + if v, ok := value.(map[string]any); ok { + slct.Filter = immutable.Some(request.Filter{Conditions: v}) + } + case request.DocIDArgName: // parse single DocID field - slct.DocIDs = immutable.Some([]string{value.(string)}) + if v, ok := value.(string); ok { + slct.DocIDs = immutable.Some([]string{v}) + } + case request.DocIDsArgName: - docIDValues := value.([]any) - docIDs := make([]string, len(docIDValues)) - for i, value := range docIDValues { + v, ok := value.([]any) + if !ok { + continue // value is nil + } + docIDs := make([]string, len(v)) + for i, value := range v { docIDs[i] = value.(string) } slct.DocIDs = immutable.Some(docIDs) + case request.Cid: // parse single CID query field - slct.CID = immutable.Some(value.(string)) + if v, ok := value.(string); ok { + slct.CID = immutable.Some(v) + } + case request.LimitClause: // parse limit/offset - slct.Limit = immutable.Some(uint64(value.(int32))) + if v, ok := value.(int32); ok { + slct.Limit = immutable.Some(uint64(v)) + } + case request.OffsetClause: // parse limit/offset - slct.Offset = immutable.Some(uint64(value.(int32))) + if v, ok := value.(int32); ok { + slct.Offset = immutable.Some(uint64(v)) + } + case request.OrderClause: // parse order by - conditionsAST := argument.Value.(*ast.ObjectValue) - conditionsValue := value.(map[string]any) - conditions, err := ParseConditionsInOrder(conditionsAST, conditionsValue) + v, ok := value.(map[string]any) + if !ok { + continue // value is nil + } + conditions, err := ParseConditionsInOrder(argument.Value.(*ast.ObjectValue), v) if err != nil { return nil, err } slct.OrderBy = immutable.Some(request.OrderBy{ Conditions: conditions, }) + case request.GroupByClause: - fieldsValue := value.([]any) - fields := make([]string, len(fieldsValue)) - for i, v := range fieldsValue { - fields[i] = v.(string) + v, ok := value.([]any) + if !ok { + continue // value is nil + } + fields := make([]string, len(v)) + for i, c := range v { + fields[i] = c.(string) } slct.GroupBy = immutable.Some(request.GroupBy{ Fields: fields, }) + case request.ShowDeleted: - slct.ShowDeleted = value.(bool) + if v, ok := value.(bool); ok { + slct.ShowDeleted = v + } } } @@ -175,96 +199,29 @@ func parseAggregate( parent *gql.Object, field *ast.Field, ) (*request.Aggregate, error) { - targets := make([]*request.AggregateTarget, len(field.Arguments)) - fieldDef := gql.GetFieldDef(exe.Schema, parent, field.Name.Value) arguments := gql.GetArgumentValues(fieldDef.Args, field.Arguments, exe.VariableValues) - for i, argument := range field.Arguments { + var targets []*request.AggregateTarget + for _, argument := range field.Arguments { name := argument.Name.Value - value := arguments[name] - switch v := value.(type) { + switch v := arguments[name].(type) { case string: - targets[i] = &request.AggregateTarget{ + targets = append(targets, &request.AggregateTarget{ HostName: v, - } + }) + case map[string]any: - var childName string - var filter immutable.Option[request.Filter] - var limit immutable.Option[uint64] - var offset immutable.Option[uint64] - var order immutable.Option[request.OrderBy] - - for _, f := range argument.Value.(*ast.ObjectValue).Fields { - switch f.Name.Value { - case request.FieldName: - childName = v[request.FieldName].(string) - - case request.FilterClause: - filter = immutable.Some(request.Filter{ - Conditions: v[request.FilterClause].(map[string]any), - }) - - case request.LimitClause: - limit = immutable.Some(uint64(v[request.LimitClause].(int32))) - - case request.OffsetClause: - offset = immutable.Some(uint64(v[request.OffsetClause].(int32))) - - case request.OrderClause: - switch conditionsAST := f.Value.(type) { - case *ast.EnumValue: - // For inline arrays the order arg will be a simple enum declaring the order direction - var orderDirection request.OrderDirection - switch v[request.OrderClause].(int) { - case 0: - orderDirection = request.ASC - - case 1: - orderDirection = request.DESC - - default: - return nil, ErrInvalidOrderDirection - } - - order = immutable.Some(request.OrderBy{ - Conditions: []request.OrderCondition{{ - Direction: orderDirection, - }}, - }) - - case *ast.ObjectValue: - // For relations the order arg will be the complex order object as used by the host object - // for non-aggregate ordering - conditionsValue := v[request.OrderClause].(map[string]any) - conditions, err := ParseConditionsInOrder(conditionsAST, conditionsValue) - if err != nil { - return nil, err - } - order = immutable.Some(request.OrderBy{ - Conditions: conditions, - }) - } - } + value, ok := argument.Value.(*ast.ObjectValue) + if !ok { + continue // value is nil } - - targets[i] = &request.AggregateTarget{ - HostName: name, - ChildName: immutable.Some(childName), - Filterable: request.Filterable{ - Filter: filter, - }, - Limitable: request.Limitable{ - Limit: limit, - }, - Offsetable: request.Offsetable{ - Offset: offset, - }, - Orderable: request.Orderable{ - OrderBy: order, - }, + target, err := parseAggregateTarget(name, value, v) + if err != nil { + return nil, err } + targets = append(targets, target) } } @@ -276,3 +233,91 @@ func parseAggregate( Targets: targets, }, nil } + +func parseAggregateTarget( + hostName string, + value *ast.ObjectValue, + arguments map[string]any, +) (*request.AggregateTarget, error) { + var childName string + var filter immutable.Option[request.Filter] + var limit immutable.Option[uint64] + var offset immutable.Option[uint64] + var order immutable.Option[request.OrderBy] + + for _, f := range value.Fields { + name := f.Name.Value + value := arguments[name] + + switch name { + case request.FieldName: + if v, ok := value.(string); ok { + childName = v + } + + case request.FilterClause: + if v, ok := value.(map[string]any); ok { + filter = immutable.Some(request.Filter{Conditions: v}) + } + + case request.LimitClause: + if v, ok := value.(int32); ok { + limit = immutable.Some(uint64(v)) + } + + case request.OffsetClause: + if v, ok := value.(int32); ok { + offset = immutable.Some(uint64(v)) + } + + case request.OrderClause: + switch conditionsAST := f.Value.(type) { + case *ast.EnumValue: + // For inline arrays the order arg will be a simple enum declaring the order direction + v, ok := value.(int) + if !ok { + continue // value is nil + } + dir, err := parseOrderDirection(v) + if err != nil { + return nil, err + } + order = immutable.Some(request.OrderBy{ + Conditions: []request.OrderCondition{{Direction: dir}}, + }) + + case *ast.ObjectValue: + // For relations the order arg will be the complex order object as used by the host object + // for non-aggregate ordering + v, ok := value.(map[string]any) + if !ok { + continue // value is nil + } + conditions, err := ParseConditionsInOrder(conditionsAST, v) + if err != nil { + return nil, err + } + order = immutable.Some(request.OrderBy{ + Conditions: conditions, + }) + } + } + } + + return &request.AggregateTarget{ + HostName: hostName, + ChildName: immutable.Some(childName), + Filterable: request.Filterable{ + Filter: filter, + }, + Limitable: request.Limitable{ + Limit: limit, + }, + Offsetable: request.Offsetable{ + Offset: offset, + }, + Orderable: request.Orderable{ + OrderBy: order, + }, + }, nil +} diff --git a/internal/request/graphql/parser/subscription.go b/internal/request/graphql/parser/subscription.go index 4c6f5e3f5f..82aca83302 100644 --- a/internal/request/graphql/parser/subscription.go +++ b/internal/request/graphql/parser/subscription.go @@ -58,10 +58,8 @@ func parseSubscription(exe *gql.ExecutionContext, field *ast.Field) (*request.Ob fieldDef := gql.GetFieldDef(exe.Schema, exe.Schema.QueryType(), field.Name.Value) arguments := gql.GetArgumentValues(fieldDef.Args, field.Arguments, exe.VariableValues) - if v, ok := arguments[request.FilterClause]; ok { - sub.Filter = immutable.Some(request.Filter{ - Conditions: v.(map[string]any), - }) + if v, ok := arguments[request.FilterClause].(map[string]any); ok { + sub.Filter = immutable.Some(request.Filter{Conditions: v}) } // parse field selections @@ -71,5 +69,8 @@ func parseSubscription(exe *gql.ExecutionContext, field *ast.Field) (*request.Ob } sub.Fields, err = parseSelectFields(exe, fieldObject, field.SelectionSet) + if err != nil { + return nil, err + } return sub, err } diff --git a/tests/integration/mutation/create/with_null_input_test.go b/tests/integration/mutation/create/with_null_input_test.go new file mode 100644 index 0000000000..72cec7a7a0 --- /dev/null +++ b/tests/integration/mutation/create/with_null_input_test.go @@ -0,0 +1,141 @@ +// Copyright 2024 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 create + +import ( + "testing" + + testUtils "github.com/sourcenetwork/defradb/tests/integration" +) + +func TestMutationCreate_WithNullEncrypt_Succeeds(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple create mutation, with null encrypt", + Actions: []any{ + testUtils.SchemaUpdate{ + Schema: ` + type Users { + name: String + } + `, + }, + testUtils.Request{ + Request: `mutation { + create_Users(encrypt: null, input: {name: "Bob"}) { + name + } + }`, + Results: map[string]any{ + "create_Users": []map[string]any{ + { + "name": "Bob", + }, + }, + }, + }, + }, + } + + testUtils.ExecuteTestCase(t, test) +} + +func TestMutationCreate_WithNullInput_Succeeds(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple create mutation, with null input", + Actions: []any{ + testUtils.SchemaUpdate{ + Schema: ` + type Users { + name: String + } + `, + }, + testUtils.Request{ + Request: `mutation { + create_Users(input: null, inputs: [{name: "Bob"}]) { + name + } + }`, + Results: map[string]any{ + "create_Users": []map[string]any{ + { + "name": "Bob", + }, + }, + }, + }, + }, + } + + testUtils.ExecuteTestCase(t, test) +} + +func TestMutationCreate_WithNullInputs_Succeeds(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple create mutation, with null inputs", + Actions: []any{ + testUtils.SchemaUpdate{ + Schema: ` + type Users { + name: String + } + `, + }, + testUtils.Request{ + Request: `mutation { + create_Users(inputs: null, input: {name: "Bob"}) { + name + } + }`, + Results: map[string]any{ + "create_Users": []map[string]any{ + { + "name": "Bob", + }, + }, + }, + }, + }, + } + + testUtils.ExecuteTestCase(t, test) +} + +func TestMutationCreate_WithNullEncryptFields_Succeeds(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple create mutation, with null encryptFields", + Actions: []any{ + testUtils.SchemaUpdate{ + Schema: ` + type Users { + name: String + } + `, + }, + testUtils.Request{ + Request: `mutation { + create_Users(encryptFields: null, input: {name: "Bob"}) { + name + } + }`, + Results: map[string]any{ + "create_Users": []map[string]any{ + { + "name": "Bob", + }, + }, + }, + }, + }, + } + + testUtils.ExecuteTestCase(t, test) +} diff --git a/tests/integration/mutation/delete/with_null_input_test.go b/tests/integration/mutation/delete/with_null_input_test.go new file mode 100644 index 0000000000..1619adc64f --- /dev/null +++ b/tests/integration/mutation/delete/with_null_input_test.go @@ -0,0 +1,125 @@ +// Copyright 2024 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 delete + +import ( + "testing" + + testUtils "github.com/sourcenetwork/defradb/tests/integration" +) + +func TestMutationDelete_WithNullFilter_Succeeds(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple delete mutation, with null filter", + Actions: []any{ + testUtils.SchemaUpdate{ + Schema: ` + type Users { + name: String + } + `, + }, + testUtils.CreateDoc{ + DocMap: map[string]any{ + "name": "Bob", + }, + }, + testUtils.Request{ + Request: `mutation { + delete_Users(filter: null) { + name + } + }`, + Results: map[string]any{ + "delete_Users": []map[string]any{ + { + "name": "Bob", + }, + }, + }, + }, + }, + } + + testUtils.ExecuteTestCase(t, test) +} + +func TestMutationDelete_WithNullDocID_Succeeds(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple delete mutation, with null docID", + Actions: []any{ + testUtils.SchemaUpdate{ + Schema: ` + type Users { + name: String + } + `, + }, + testUtils.CreateDoc{ + DocMap: map[string]any{ + "name": "Bob", + }, + }, + testUtils.Request{ + Request: `mutation { + delete_Users(docID: null) { + name + } + }`, + Results: map[string]any{ + "delete_Users": []map[string]any{ + { + "name": "Bob", + }, + }, + }, + }, + }, + } + + testUtils.ExecuteTestCase(t, test) +} + +func TestMutationDelete_WithNullDocIDs_Succeeds(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple delete mutation, with null docIDs", + Actions: []any{ + testUtils.SchemaUpdate{ + Schema: ` + type Users { + name: String + } + `, + }, + testUtils.CreateDoc{ + DocMap: map[string]any{ + "name": "Bob", + }, + }, + testUtils.Request{ + Request: `mutation { + delete_Users(docIDs: null) { + name + } + }`, + Results: map[string]any{ + "delete_Users": []map[string]any{ + { + "name": "Bob", + }, + }, + }, + }, + }, + } + + testUtils.ExecuteTestCase(t, test) +} diff --git a/tests/integration/mutation/update/with_null_input_test.go b/tests/integration/mutation/update/with_null_input_test.go new file mode 100644 index 0000000000..6c26db63a5 --- /dev/null +++ b/tests/integration/mutation/update/with_null_input_test.go @@ -0,0 +1,125 @@ +// Copyright 2024 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 update + +import ( + "testing" + + testUtils "github.com/sourcenetwork/defradb/tests/integration" +) + +func TestMutationUpdate_WithNullFilter_Succeeds(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple update mutation, with null filter", + Actions: []any{ + testUtils.SchemaUpdate{ + Schema: ` + type Users { + name: String + } + `, + }, + testUtils.CreateDoc{ + DocMap: map[string]any{ + "name": "Bob", + }, + }, + testUtils.Request{ + Request: `mutation { + update_Users(filter: null, input: {name: "Alice"}) { + name + } + }`, + Results: map[string]any{ + "update_Users": []map[string]any{ + { + "name": "Alice", + }, + }, + }, + }, + }, + } + + testUtils.ExecuteTestCase(t, test) +} + +func TestMutationUpdate_WithNullDocID_Succeeds(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple update mutation, with null docID", + Actions: []any{ + testUtils.SchemaUpdate{ + Schema: ` + type Users { + name: String + } + `, + }, + testUtils.CreateDoc{ + DocMap: map[string]any{ + "name": "Bob", + }, + }, + testUtils.Request{ + Request: `mutation { + update_Users(docID: null, input: {name: "Alice"}) { + name + } + }`, + Results: map[string]any{ + "update_Users": []map[string]any{ + { + "name": "Alice", + }, + }, + }, + }, + }, + } + + testUtils.ExecuteTestCase(t, test) +} + +func TestMutationUpdate_WithNullDocIDs_Succeeds(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple update mutation, with null docIDs", + Actions: []any{ + testUtils.SchemaUpdate{ + Schema: ` + type Users { + name: String + } + `, + }, + testUtils.CreateDoc{ + DocMap: map[string]any{ + "name": "Bob", + }, + }, + testUtils.Request{ + Request: `mutation { + update_Users(docIDs: null, input: {name: "Alice"}) { + name + } + }`, + Results: map[string]any{ + "update_Users": []map[string]any{ + { + "name": "Alice", + }, + }, + }, + }, + }, + } + + testUtils.ExecuteTestCase(t, test) +} diff --git a/tests/integration/query/commits/with_null_input_test.go b/tests/integration/query/commits/with_null_input_test.go new file mode 100644 index 0000000000..84a257b332 --- /dev/null +++ b/tests/integration/query/commits/with_null_input_test.go @@ -0,0 +1,321 @@ +// Copyright 2024 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 commits + +import ( + "testing" + + testUtils "github.com/sourcenetwork/defradb/tests/integration" +) + +func TestQueryCommitsWithNullDepth(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple all commits query with null depth", + Actions: []any{ + updateUserCollectionSchema(), + testUtils.CreateDoc{ + CollectionID: 0, + Doc: `{ + "name": "John", + "age": 21 + }`, + }, + testUtils.Request{ + Request: `query { + commits(depth: null) { + cid + } + }`, + Results: map[string]any{ + "commits": []map[string]any{ + { + "cid": "bafyreifzyy7bmpx2eywj4lznxzrzrvh6vrz6l7bhthkpexdq3wtho3vz6i", + }, + { + "cid": "bafyreic2sba5sffkfnt32wfeoaw4qsqozjb5acwwtouxuzllb3aymjwute", + }, + { + "cid": "bafyreihv7jqe32wsuff5vwzlp7izoo6pqg6kgqf5edknp3mqm3344gu35q", + }, + }, + }, + }, + }, + } + + testUtils.ExecuteTestCase(t, test) +} + +func TestQueryCommitsWithNullCID(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple all commits query with null cid", + Actions: []any{ + updateUserCollectionSchema(), + testUtils.CreateDoc{ + CollectionID: 0, + Doc: `{ + "name": "John", + "age": 21 + }`, + }, + testUtils.Request{ + Request: `query { + commits(cid: null) { + cid + } + }`, + Results: map[string]any{ + "commits": []map[string]any{ + { + "cid": "bafyreifzyy7bmpx2eywj4lznxzrzrvh6vrz6l7bhthkpexdq3wtho3vz6i", + }, + { + "cid": "bafyreic2sba5sffkfnt32wfeoaw4qsqozjb5acwwtouxuzllb3aymjwute", + }, + { + "cid": "bafyreihv7jqe32wsuff5vwzlp7izoo6pqg6kgqf5edknp3mqm3344gu35q", + }, + }, + }, + }, + }, + } + + testUtils.ExecuteTestCase(t, test) +} + +func TestQueryCommitsWithNullFieldID(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple all commits query with null fieldId", + Actions: []any{ + updateUserCollectionSchema(), + testUtils.CreateDoc{ + CollectionID: 0, + Doc: `{ + "name": "John", + "age": 21 + }`, + }, + testUtils.Request{ + Request: `query { + commits(fieldId: null) { + cid + } + }`, + Results: map[string]any{ + "commits": []map[string]any{ + { + "cid": "bafyreifzyy7bmpx2eywj4lznxzrzrvh6vrz6l7bhthkpexdq3wtho3vz6i", + }, + { + "cid": "bafyreic2sba5sffkfnt32wfeoaw4qsqozjb5acwwtouxuzllb3aymjwute", + }, + { + "cid": "bafyreihv7jqe32wsuff5vwzlp7izoo6pqg6kgqf5edknp3mqm3344gu35q", + }, + }, + }, + }, + }, + } + + testUtils.ExecuteTestCase(t, test) +} + +func TestQueryCommitsWithNullOrder(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple all commits query with null order", + Actions: []any{ + updateUserCollectionSchema(), + testUtils.CreateDoc{ + CollectionID: 0, + Doc: `{ + "name": "John", + "age": 21 + }`, + }, + testUtils.Request{ + Request: `query { + commits(order: null) { + cid + } + }`, + Results: map[string]any{ + "commits": []map[string]any{ + { + "cid": "bafyreifzyy7bmpx2eywj4lznxzrzrvh6vrz6l7bhthkpexdq3wtho3vz6i", + }, + { + "cid": "bafyreic2sba5sffkfnt32wfeoaw4qsqozjb5acwwtouxuzllb3aymjwute", + }, + { + "cid": "bafyreihv7jqe32wsuff5vwzlp7izoo6pqg6kgqf5edknp3mqm3344gu35q", + }, + }, + }, + }, + }, + } + + testUtils.ExecuteTestCase(t, test) +} + +func TestQueryCommitsWithNullOrderField(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple all commits query with null order field", + Actions: []any{ + updateUserCollectionSchema(), + testUtils.CreateDoc{ + CollectionID: 0, + Doc: `{ + "name": "John", + "age": 21 + }`, + }, + testUtils.Request{ + Request: `query { + commits(docID: null) { + cid + } + }`, + Results: map[string]any{ + "commits": []map[string]any{ + { + "cid": "bafyreifzyy7bmpx2eywj4lznxzrzrvh6vrz6l7bhthkpexdq3wtho3vz6i", + }, + { + "cid": "bafyreic2sba5sffkfnt32wfeoaw4qsqozjb5acwwtouxuzllb3aymjwute", + }, + { + "cid": "bafyreihv7jqe32wsuff5vwzlp7izoo6pqg6kgqf5edknp3mqm3344gu35q", + }, + }, + }, + }, + }, + } + + testUtils.ExecuteTestCase(t, test) +} + +func TestQueryCommitsWithNullLimit(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple all commits query with null limit", + Actions: []any{ + updateUserCollectionSchema(), + testUtils.CreateDoc{ + CollectionID: 0, + Doc: `{ + "name": "John", + "age": 21 + }`, + }, + testUtils.Request{ + Request: `query { + commits(limit: null) { + cid + } + }`, + Results: map[string]any{ + "commits": []map[string]any{ + { + "cid": "bafyreifzyy7bmpx2eywj4lznxzrzrvh6vrz6l7bhthkpexdq3wtho3vz6i", + }, + { + "cid": "bafyreic2sba5sffkfnt32wfeoaw4qsqozjb5acwwtouxuzllb3aymjwute", + }, + { + "cid": "bafyreihv7jqe32wsuff5vwzlp7izoo6pqg6kgqf5edknp3mqm3344gu35q", + }, + }, + }, + }, + }, + } + + testUtils.ExecuteTestCase(t, test) +} + +func TestQueryCommitsWithNullOffset(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple all commits query with null offset", + Actions: []any{ + updateUserCollectionSchema(), + testUtils.CreateDoc{ + CollectionID: 0, + Doc: `{ + "name": "John", + "age": 21 + }`, + }, + testUtils.Request{ + Request: `query { + commits(offset: null) { + cid + } + }`, + Results: map[string]any{ + "commits": []map[string]any{ + { + "cid": "bafyreifzyy7bmpx2eywj4lznxzrzrvh6vrz6l7bhthkpexdq3wtho3vz6i", + }, + { + "cid": "bafyreic2sba5sffkfnt32wfeoaw4qsqozjb5acwwtouxuzllb3aymjwute", + }, + { + "cid": "bafyreihv7jqe32wsuff5vwzlp7izoo6pqg6kgqf5edknp3mqm3344gu35q", + }, + }, + }, + }, + }, + } + + testUtils.ExecuteTestCase(t, test) +} + +func TestQueryCommitsWithNullGroupBy(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple all commits query with null groupBy", + Actions: []any{ + updateUserCollectionSchema(), + testUtils.CreateDoc{ + CollectionID: 0, + Doc: `{ + "name": "John", + "age": 21 + }`, + }, + testUtils.Request{ + Request: `query { + commits(groupBy: null) { + cid + } + }`, + Results: map[string]any{ + "commits": []map[string]any{ + { + "cid": "bafyreifzyy7bmpx2eywj4lznxzrzrvh6vrz6l7bhthkpexdq3wtho3vz6i", + }, + { + "cid": "bafyreic2sba5sffkfnt32wfeoaw4qsqozjb5acwwtouxuzllb3aymjwute", + }, + { + "cid": "bafyreihv7jqe32wsuff5vwzlp7izoo6pqg6kgqf5edknp3mqm3344gu35q", + }, + }, + }, + }, + }, + } + + testUtils.ExecuteTestCase(t, test) +} diff --git a/tests/integration/query/simple/with_null_input_test.go b/tests/integration/query/simple/with_null_input_test.go new file mode 100644 index 0000000000..ceba642887 --- /dev/null +++ b/tests/integration/query/simple/with_null_input_test.go @@ -0,0 +1,336 @@ +// Copyright 2024 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 simple + +import ( + "testing" + + testUtils "github.com/sourcenetwork/defradb/tests/integration" +) + +func TestQuerySimple_WithNullFilter_Succeeds(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple query, with null filter", + Actions: []any{ + testUtils.CreateDoc{ + Doc: `{ + "Name": "John" + }`, + }, + testUtils.Request{ + Request: `query { + Users(filter: null) { + Name + } + }`, + Results: map[string]any{ + "Users": []map[string]any{ + { + "Name": "John", + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + +func TestQuerySimple_WithNullFilterFields_Succeeds(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple query, with null filter fields", + Actions: []any{ + testUtils.CreateDoc{ + Doc: `{ + "Name": null + }`, + }, + testUtils.Request{ + Request: `query { + Users(filter: {Name: null}) { + Name + } + }`, + Results: map[string]any{ + "Users": []map[string]any{ + { + "Name": nil, + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + +func TestQuerySimple_WithNullOrder_Succeeds(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple query, with null order", + Actions: []any{ + testUtils.CreateDoc{ + Doc: `{ + "Name": "John" + }`, + }, + testUtils.Request{ + Request: `query { + Users(order: null) { + Name + } + }`, + Results: map[string]any{ + "Users": []map[string]any{ + { + "Name": "John", + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + +func TestQuerySimple_WithNullOrderFields_Succeeds(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple query, with null order fields", + Actions: []any{ + testUtils.CreateDoc{ + Doc: `{ + "Name": "John" + }`, + }, + testUtils.Request{ + Request: `query { + Users(order: {Name: null}) { + Name + } + }`, + Results: map[string]any{ + "Users": []map[string]any{ + { + "Name": "John", + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + +func TestQuerySimple_WithNullLimit_Succeed(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple query, with null limit", + Actions: []any{ + testUtils.CreateDoc{ + Doc: `{ + "Name": "John" + }`, + }, + testUtils.Request{ + Request: `query { + Users(limit: null) { + Name + } + }`, + Results: map[string]any{ + "Users": []map[string]any{ + { + "Name": "John", + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + +func TestQuerySimple_WithNullOffset_Succeeds(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple query, with null offset", + Actions: []any{ + testUtils.CreateDoc{ + Doc: `{ + "Name": "John" + }`, + }, + testUtils.Request{ + Request: `query { + Users(offset: null) { + Name + } + }`, + Results: map[string]any{ + "Users": []map[string]any{ + { + "Name": "John", + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + +func TestQuerySimple_WithNullDocID_Succeeds(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple query, with null docID", + Actions: []any{ + testUtils.CreateDoc{ + Doc: `{ + "Name": "John" + }`, + }, + testUtils.Request{ + Request: `query { + Users(docID: null) { + Name + } + }`, + Results: map[string]any{ + "Users": []map[string]any{ + { + "Name": "John", + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + +func TestQuerySimple_WithNullDocIDs_Succeeds(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple query, with null docIDs", + Actions: []any{ + testUtils.CreateDoc{ + Doc: `{ + "Name": "John" + }`, + }, + testUtils.Request{ + Request: `query { + Users(docIDs: null) { + Name + } + }`, + Results: map[string]any{ + "Users": []map[string]any{ + { + "Name": "John", + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + +func TestQuerySimple_WithNullCID_Succeeds(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple query, with null cid", + Actions: []any{ + testUtils.CreateDoc{ + Doc: `{ + "Name": "John" + }`, + }, + testUtils.Request{ + Request: `query { + Users(cid: null) { + Name + } + }`, + Results: map[string]any{ + "Users": []map[string]any{ + { + "Name": "John", + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + +func TestQuerySimple_WithNullGroupBy_Succeeds(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple query, with null groupBy", + Actions: []any{ + testUtils.CreateDoc{ + Doc: `{ + "Name": "John" + }`, + }, + testUtils.Request{ + Request: `query { + Users(groupBy: null) { + Name + } + }`, + Results: map[string]any{ + "Users": []map[string]any{ + { + "Name": "John", + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + +func TestQuerySimple_WithNullShowDeleted_Succeeds(t *testing.T) { + test := testUtils.TestCase{ + Description: "Simple query, with null showDeleted", + Actions: []any{ + testUtils.CreateDoc{ + Doc: `{ + "Name": "John" + }`, + }, + testUtils.Request{ + Request: `query { + Users(showDeleted: null) { + Name + } + }`, + Results: map[string]any{ + "Users": []map[string]any{ + { + "Name": "John", + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +}