diff --git a/client/request/consts.go b/client/request/consts.go index 04ade35bc5..7287a49ac3 100644 --- a/client/request/consts.go +++ b/client/request/consts.go @@ -15,6 +15,10 @@ const ( // https://spec.graphql.org/October2021/#sec-Type-Name-Introspection TypeNameFieldName = "__typename" + // This is appended to the related object name to give us the field name + // that corresponds to the related object's join relation id, i.e. `Author_id`. + RelatedObjectID = "_id" + Cid = "cid" Data = "data" DocKey = "dockey" diff --git a/client/request/select.go b/client/request/select.go index 0d09cad8dc..f8fd7f6258 100644 --- a/client/request/select.go +++ b/client/request/select.go @@ -89,13 +89,17 @@ func (s *Select) validateGroupBy() []error { } var fieldExistsInGroupBy bool + var isAliasFieldInGroupBy bool for _, groupByField := range s.GroupBy.Value().Fields { if typedChildSelection.Name == groupByField { fieldExistsInGroupBy = true break + } else if typedChildSelection.Name == groupByField+RelatedObjectID { + isAliasFieldInGroupBy = true + break } } - if !fieldExistsInGroupBy { + if !fieldExistsInGroupBy && !isAliasFieldInGroupBy { result = append(result, client.NewErrSelectOfNonGroupField(typedChildSelection.Name)) } default: diff --git a/db/collection_update.go b/db/collection_update.go index ea2d0b3980..cb1a4d70f8 100644 --- a/db/collection_update.go +++ b/db/collection_update.go @@ -398,7 +398,9 @@ func (c *collection) isSecondaryIDField(fieldDesc client.FieldDescription) (clie return client.FieldDescription{}, false } - relationFieldDescription, valid := c.Description().GetField(strings.TrimSuffix(fieldDesc.Name, "_id")) + relationFieldDescription, valid := c.Description().GetField( + strings.TrimSuffix(fieldDesc.Name, request.RelatedObjectID), + ) return relationFieldDescription, valid && !relationFieldDescription.IsPrimaryRelation() } @@ -431,7 +433,7 @@ func (c *collection) patchPrimaryDoc( _, err = primaryCol.UpdateWithKey( ctx, primaryDockey, - fmt.Sprintf(`{"%s": "%s"}`, primaryField.Name+"_id", docKey), + fmt.Sprintf(`{"%s": "%s"}`, primaryField.Name+request.RelatedObjectID, docKey), ) if err != nil { return err diff --git a/planner/mapper/errors.go b/planner/mapper/errors.go index 83a5c11b3a..9d6db3dab0 100644 --- a/planner/mapper/errors.go +++ b/planner/mapper/errors.go @@ -12,8 +12,16 @@ package mapper import "github.com/sourcenetwork/defradb/errors" +const ( + errInvalidFieldToGroupBy string = "invalid field value to groupBy" +) + var ( ErrUnableToIdAggregateChild = errors.New("unable to identify aggregate child") ErrAggregateTargetMissing = errors.New("aggregate must be provided with a property to aggregate") ErrFailedToFindHostField = errors.New("failed to find host field") ) + +func NewErrInvalidFieldToGroupBy(field string) error { + return errors.New(errInvalidFieldToGroupBy, errors.NewKV("Field", field)) +} diff --git a/planner/mapper/mapper.go b/planner/mapper/mapper.go index e1cafef386..799d5c5c9b 100644 --- a/planner/mapper/mapper.go +++ b/planner/mapper/mapper.go @@ -87,8 +87,31 @@ func toSelect( return nil, err } - // If there is a groupBy, and no inner group has been requested, we need to map the property here + // Resolve groupBy mappings i.e. alias remapping and handle missed inner group. if selectRequest.GroupBy.HasValue() { + groupByFields := selectRequest.GroupBy.Value().Fields + // Remap all alias field names to use their internal field name mappings. + for index, groupByField := range groupByFields { + if _, fieldHasMapping := mapping.IndexesByName[groupByField]; fieldHasMapping { + // Not an alias as is already mapped. + continue + } else if _, isAlias := mapping.IndexesByName[groupByField+request.RelatedObjectID]; isAlias { + // Remap the alias to it's actual internal name. + groupByFields[index] = groupByField + request.RelatedObjectID + } else { + // Field is not mapped nor is an alias, then is invalid field to group on. This can be + // incase of when an alias might have been used on groupBy relation from the single side. + return nil, NewErrInvalidFieldToGroupBy(groupByField) + } + } + + selectRequest.GroupBy = immutable.Some( + request.GroupBy{ + Fields: groupByFields, + }, + ) + + // If there is a groupBy, and no inner group has been requested, we need to map the property here if _, isGroupFieldMapped := mapping.IndexesByName[request.GroupFieldName]; !isGroupFieldMapped { index := mapping.GetNextIndex() mapping.Add(index, request.GroupFieldName) diff --git a/planner/type_join.go b/planner/type_join.go index 9d3d982c0e..e50a165aa6 100644 --- a/planner/type_join.go +++ b/planner/type_join.go @@ -356,7 +356,7 @@ func (n *typeJoinOne) Next() (bool, error) { func (n *typeJoinOne) valuesSecondary(doc core.Doc) core.Doc { fkIndex := &mapper.PropertyIndex{ - Index: n.subType.DocumentMap().FirstIndexOfName(n.subTypeFieldName + "_id"), + Index: n.subType.DocumentMap().FirstIndexOfName(n.subTypeFieldName + request.RelatedObjectID), } filter := map[connor.FilterKey]any{ fkIndex: doc.GetKey(), @@ -386,7 +386,7 @@ func (n *typeJoinOne) valuesSecondary(doc core.Doc) core.Doc { func (n *typeJoinOne) valuesPrimary(doc core.Doc) core.Doc { // get the subtype doc key - subDocKey := n.docMapper.documentMapping.FirstOfName(doc, n.subTypeName+"_id") + subDocKey := n.docMapper.documentMapping.FirstOfName(doc, n.subTypeName+request.RelatedObjectID) subDocKeyStr, ok := subDocKey.(string) if !ok { @@ -546,7 +546,7 @@ func (n *typeJoinMany) Next() (bool, error) { // @todo: handle index for one-to-many setup } else { fkIndex := &mapper.PropertyIndex{ - Index: n.subSelect.FirstIndexOfName(n.rootName + "_id"), + Index: n.subSelect.FirstIndexOfName(n.rootName + request.RelatedObjectID), } filter := map[connor.FilterKey]any{ fkIndex: n.currentValue.GetKey(), // user_id: "bae-ALICE" | user_id: "bae-CHARLIE" diff --git a/tests/integration/query/one_to_many/with_group_related_id_alias_test.go b/tests/integration/query/one_to_many/with_group_related_id_alias_test.go new file mode 100644 index 0000000000..4f4d1c9a37 --- /dev/null +++ b/tests/integration/query/one_to_many/with_group_related_id_alias_test.go @@ -0,0 +1,777 @@ +// 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 one_to_many + +import ( + "testing" + + testUtils "github.com/sourcenetwork/defradb/tests/integration" +) + +// TODO: Don't return grouped field if not selected. [https://github.com/sourcenetwork/defradb/issues/1582]. +func TestQueryOneToManyWithParentGroupByOnRelatedTypeFromManySideUsingAlias(t *testing.T) { + test := testUtils.RequestTestCase{ + + Description: "One-to-many query with groupBy on related field alias (from many side).", + + Request: `query { + Book(groupBy: [author]) { + _group { + name + rating + author { + name + age + } + } + } + }`, + + Docs: map[int][]string{ + //books + 0: { // bae-fd541c25-229e-5280-b44b-e5c2af3e374d + `{ + "name": "Painted House", + "rating": 4.9, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "A Time for Mercy", + "rating": 4.5, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "The Client", + "rating": 4.5, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "Candide", + "rating": 4.95, + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3" + }`, + `{ + "name": "Zadig", + "rating": 4.91, + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3" + }`, + `{ + "name": "Histoiare des Celtes et particulierement des Gaulois et des Germains depuis les temps fabuleux jusqua la prise de Roze par les Gaulois", + "rating": 2, + "author_id": "bae-09d33399-197a-5b98-b135-4398f2b6de4c" + }`, + }, + //authors + 1: { + // bae-41598f0c-19bc-5da6-813b-e80f14a10df3 + `{ + "name": "John Grisham", + "age": 65, + "verified": true + }`, + // bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3 + `{ + "name": "Voltaire", + "age": 327, + "verified": true + }`, + // bae-09d33399-197a-5b98-b135-4398f2b6de4c + `{ + "name": "Simon Pelloutier", + "age": 327, + "verified": true + }`, + }, + }, + Results: []map[string]any{ + { + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3", + "_group": []map[string]any{ + { + "name": "Candide", + "rating": 4.95, + "author": map[string]any{ + "age": uint64(327), + "name": "Voltaire", + }, + }, + { + "name": "Zadig", + "rating": 4.91, + "author": map[string]any{ + "age": uint64(327), + "name": "Voltaire", + }, + }, + }, + }, + { + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3", + "_group": []map[string]any{ + { + "name": "The Client", + "rating": 4.5, + "author": map[string]any{ + "age": uint64(65), + "name": "John Grisham", + }, + }, + { + "name": "Painted House", + "rating": 4.9, + "author": map[string]any{ + "age": uint64(65), + "name": "John Grisham", + }, + }, + { + "name": "A Time for Mercy", + "rating": 4.5, + "author": map[string]any{ + "age": uint64(65), + "name": "John Grisham", + }, + }, + }, + }, + { + "author_id": "bae-09d33399-197a-5b98-b135-4398f2b6de4c", + "_group": []map[string]any{ + { + "name": "Histoiare des Celtes et particulierement des Gaulois et des Germains depuis les temps fabuleux jusqua la prise de Roze par les Gaulois", + "rating": 2.0, + "author": map[string]any{ + "age": uint64(327), + "name": "Simon Pelloutier", + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + +// TODO: Don't return grouped field if not selected. [https://github.com/sourcenetwork/defradb/issues/1582]. +func TestQueryOneToManyWithParentGroupByOnRelatedTypeFromManySideUsingAliasAndRelatedSelection(t *testing.T) { + test := testUtils.RequestTestCase{ + + Description: "One-to-many query with groupBy on related field alias (from many side).", + + Request: `query { + Book(groupBy: [author]) { + author { + _key + name + } + _group { + name + rating + author { + name + age + } + } + } + }`, + + Docs: map[int][]string{ + //books + 0: { // bae-fd541c25-229e-5280-b44b-e5c2af3e374d + `{ + "name": "Painted House", + "rating": 4.9, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "A Time for Mercy", + "rating": 4.5, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "The Client", + "rating": 4.5, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "Candide", + "rating": 4.95, + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3" + }`, + `{ + "name": "Zadig", + "rating": 4.91, + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3" + }`, + `{ + "name": "Histoiare des Celtes et particulierement des Gaulois et des Germains depuis les temps fabuleux jusqua la prise de Roze par les Gaulois", + "rating": 2, + "author_id": "bae-09d33399-197a-5b98-b135-4398f2b6de4c" + }`, + }, + //authors + 1: { + // bae-41598f0c-19bc-5da6-813b-e80f14a10df3 + `{ + "name": "John Grisham", + "age": 65, + "verified": true + }`, + // bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3 + `{ + "name": "Voltaire", + "age": 327, + "verified": true + }`, + // bae-09d33399-197a-5b98-b135-4398f2b6de4c + `{ + "name": "Simon Pelloutier", + "age": 327, + "verified": true + }`, + }, + }, + Results: []map[string]any{ + { + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3", + "author": map[string]any{ + "name": "Voltaire", + "_key": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3", + }, + "_group": []map[string]any{ + { + "name": "Candide", + "rating": 4.95, + "author": map[string]any{ + "age": uint64(327), + "name": "Voltaire", + }, + }, + { + "name": "Zadig", + "rating": 4.91, + "author": map[string]any{ + "age": uint64(327), + "name": "Voltaire", + }, + }, + }, + }, + { + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3", + "author": map[string]any{ + "name": "John Grisham", + "_key": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3", + }, + "_group": []map[string]any{ + { + "name": "The Client", + "rating": 4.5, + "author": map[string]any{ + "age": uint64(65), + "name": "John Grisham", + }, + }, + { + "name": "Painted House", + "rating": 4.9, + "author": map[string]any{ + "age": uint64(65), + "name": "John Grisham", + }, + }, + { + "name": "A Time for Mercy", + "rating": 4.5, + "author": map[string]any{ + "age": uint64(65), + "name": "John Grisham", + }, + }, + }, + }, + { + "author_id": "bae-09d33399-197a-5b98-b135-4398f2b6de4c", + "author": map[string]any{ + "name": "Simon Pelloutier", + "_key": "bae-09d33399-197a-5b98-b135-4398f2b6de4c", + }, + "_group": []map[string]any{ + { + "name": "Histoiare des Celtes et particulierement des Gaulois et des Germains depuis les temps fabuleux jusqua la prise de Roze par les Gaulois", + "rating": 2.0, + "author": map[string]any{ + "age": uint64(327), + "name": "Simon Pelloutier", + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + +func TestQueryOneToManyWithParentGroupByOnRelatedTypeWithIDSelectionFromManySideUsingAlias(t *testing.T) { + test := testUtils.RequestTestCase{ + + Description: "One-to-many query with groupBy on related field alias, with id selection & related selection (from many side).", + + Request: `query { + Book(groupBy: [author]) { + author_id + _group { + name + rating + author { + name + age + } + } + } + }`, + + Docs: map[int][]string{ + //books + 0: { // bae-fd541c25-229e-5280-b44b-e5c2af3e374d + `{ + "name": "Painted House", + "rating": 4.9, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "A Time for Mercy", + "rating": 4.5, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "The Client", + "rating": 4.5, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "Candide", + "rating": 4.95, + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3" + }`, + `{ + "name": "Zadig", + "rating": 4.91, + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3" + }`, + `{ + "name": "Histoiare des Celtes et particulierement des Gaulois et des Germains depuis les temps fabuleux jusqua la prise de Roze par les Gaulois", + "rating": 2, + "author_id": "bae-09d33399-197a-5b98-b135-4398f2b6de4c" + }`, + }, + //authors + 1: { + // bae-41598f0c-19bc-5da6-813b-e80f14a10df3 + `{ + "name": "John Grisham", + "age": 65, + "verified": true + }`, + // bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3 + `{ + "name": "Voltaire", + "age": 327, + "verified": true + }`, + // bae-09d33399-197a-5b98-b135-4398f2b6de4c + `{ + "name": "Simon Pelloutier", + "age": 327, + "verified": true + }`, + }, + }, + Results: []map[string]any{ + { + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3", + "_group": []map[string]any{ + { + "name": "Candide", + "rating": 4.95, + "author": map[string]any{ + "age": uint64(327), + "name": "Voltaire", + }, + }, + { + "name": "Zadig", + "rating": 4.91, + "author": map[string]any{ + "age": uint64(327), + "name": "Voltaire", + }, + }, + }, + }, + { + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3", + "_group": []map[string]any{ + { + "name": "The Client", + "rating": 4.5, + "author": map[string]any{ + "age": uint64(65), + "name": "John Grisham", + }, + }, + { + "name": "Painted House", + "rating": 4.9, + "author": map[string]any{ + "age": uint64(65), + "name": "John Grisham", + }, + }, + { + "name": "A Time for Mercy", + "rating": 4.5, + "author": map[string]any{ + "age": uint64(65), + "name": "John Grisham", + }, + }, + }, + }, + { + "author_id": "bae-09d33399-197a-5b98-b135-4398f2b6de4c", + "_group": []map[string]any{ + { + "name": "Histoiare des Celtes et particulierement des Gaulois et des Germains depuis les temps fabuleux jusqua la prise de Roze par les Gaulois", + "rating": 2.0, + "author": map[string]any{ + "age": uint64(327), + "name": "Simon Pelloutier", + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + +func TestQueryOneToManyWithParentGroupByOnRelatedTypeWithIDSelectionFromManySideUsingAliasAndRelatedSelection(t *testing.T) { + test := testUtils.RequestTestCase{ + + Description: "One-to-many query with groupBy on related field alias, with id selection & related selection (from many side).", + + Request: `query { + Book(groupBy: [author]) { + author_id + author { + _key + name + } + _group { + name + rating + author { + name + age + } + } + } + }`, + + Docs: map[int][]string{ + //books + 0: { // bae-fd541c25-229e-5280-b44b-e5c2af3e374d + `{ + "name": "Painted House", + "rating": 4.9, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "A Time for Mercy", + "rating": 4.5, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "The Client", + "rating": 4.5, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "Candide", + "rating": 4.95, + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3" + }`, + `{ + "name": "Zadig", + "rating": 4.91, + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3" + }`, + `{ + "name": "Histoiare des Celtes et particulierement des Gaulois et des Germains depuis les temps fabuleux jusqua la prise de Roze par les Gaulois", + "rating": 2, + "author_id": "bae-09d33399-197a-5b98-b135-4398f2b6de4c" + }`, + }, + //authors + 1: { + // bae-41598f0c-19bc-5da6-813b-e80f14a10df3 + `{ + "name": "John Grisham", + "age": 65, + "verified": true + }`, + // bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3 + `{ + "name": "Voltaire", + "age": 327, + "verified": true + }`, + // bae-09d33399-197a-5b98-b135-4398f2b6de4c + `{ + "name": "Simon Pelloutier", + "age": 327, + "verified": true + }`, + }, + }, + Results: []map[string]any{ + { + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3", + "author": map[string]any{ + "name": "Voltaire", + "_key": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3", + }, + "_group": []map[string]any{ + { + "name": "Candide", + "rating": 4.95, + "author": map[string]any{ + "age": uint64(327), + "name": "Voltaire", + }, + }, + { + "name": "Zadig", + "rating": 4.91, + "author": map[string]any{ + "age": uint64(327), + "name": "Voltaire", + }, + }, + }, + }, + { + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3", + "author": map[string]any{ + "name": "John Grisham", + "_key": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3", + }, + "_group": []map[string]any{ + { + "name": "The Client", + "rating": 4.5, + "author": map[string]any{ + "age": uint64(65), + "name": "John Grisham", + }, + }, + { + "name": "Painted House", + "rating": 4.9, + "author": map[string]any{ + "age": uint64(65), + "name": "John Grisham", + }, + }, + { + "name": "A Time for Mercy", + "rating": 4.5, + "author": map[string]any{ + "age": uint64(65), + "name": "John Grisham", + }, + }, + }, + }, + { + "author_id": "bae-09d33399-197a-5b98-b135-4398f2b6de4c", + "author": map[string]any{ + "name": "Simon Pelloutier", + "_key": "bae-09d33399-197a-5b98-b135-4398f2b6de4c", + }, + "_group": []map[string]any{ + { + "name": "Histoiare des Celtes et particulierement des Gaulois et des Germains depuis les temps fabuleux jusqua la prise de Roze par les Gaulois", + "rating": 2.0, + "author": map[string]any{ + "age": uint64(327), + "name": "Simon Pelloutier", + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + +// TODO: Don't return grouped field if not selected. [https://github.com/sourcenetwork/defradb/issues/1582]. +func TestQueryOneToManyWithParentGroupByOnRelatedTypeFromSingleSideUsingAlias(t *testing.T) { + test := testUtils.RequestTestCase{ + Description: "One-to-many query with groupBy on related id field alias (from single side).", + Request: `query { + Author(groupBy: [published]) { + _group { + name + } + } + }`, + Docs: map[int][]string{ + //books + 0: { // bae-fd541c25-229e-5280-b44b-e5c2af3e374d + `{ + "name": "Painted House", + "rating": 4.9, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "A Time for Mercy", + "rating": 4.5, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "The Client", + "rating": 4.5, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "Candide", + "rating": 4.95, + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3" + }`, + `{ + "name": "Zadig", + "rating": 4.91, + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3" + }`, + `{ + "name": "Histoiare des Celtes et particulierement des Gaulois et des Germains depuis les temps fabuleux jusqua la prise de Roze par les Gaulois", + "rating": 2, + "author_id": "bae-09d33399-197a-5b98-b135-4398f2b6de4c" + }`, + }, + //authors + 1: { + // bae-41598f0c-19bc-5da6-813b-e80f14a10df3 + `{ + "name": "John Grisham", + "age": 65, + "verified": true + }`, + // bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3 + `{ + "name": "Voltaire", + "age": 327, + "verified": true + }`, + // bae-09d33399-197a-5b98-b135-4398f2b6de4c + `{ + "name": "Simon Pelloutier", + "age": 327, + "verified": true + }`, + }, + }, + + ExpectedError: "invalid field value to groupBy. Field: published", + } + + executeTestCase(t, test) +} + +func TestQueryOneToManyWithParentGroupByOnRelatedTypeWithIDSelectionFromSingleSideUsingAlias(t *testing.T) { + test := testUtils.RequestTestCase{ + Description: "One-to-many query with groupBy on related id field alias, with id selection (from single side).", + Request: `query { + Author(groupBy: [published]) { + published_id + _group { + name + } + } + }`, + Docs: map[int][]string{ + //books + 0: { // bae-fd541c25-229e-5280-b44b-e5c2af3e374d + `{ + "name": "Painted House", + "rating": 4.9, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "A Time for Mercy", + "rating": 4.5, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "The Client", + "rating": 4.5, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "Candide", + "rating": 4.95, + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3" + }`, + `{ + "name": "Zadig", + "rating": 4.91, + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3" + }`, + `{ + "name": "Histoiare des Celtes et particulierement des Gaulois et des Germains depuis les temps fabuleux jusqua la prise de Roze par les Gaulois", + "rating": 2, + "author_id": "bae-09d33399-197a-5b98-b135-4398f2b6de4c" + }`, + }, + //authors + 1: { + // bae-41598f0c-19bc-5da6-813b-e80f14a10df3 + `{ + "name": "John Grisham", + "age": 65, + "verified": true + }`, + // bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3 + `{ + "name": "Voltaire", + "age": 327, + "verified": true + }`, + // bae-09d33399-197a-5b98-b135-4398f2b6de4c + `{ + "name": "Simon Pelloutier", + "age": 327, + "verified": true + }`, + }, + }, + + ExpectedError: "Cannot query field \"published_id\" on type \"Author\". ", + } + + executeTestCase(t, test) +} diff --git a/tests/integration/query/one_to_many/with_group_related_id_test.go b/tests/integration/query/one_to_many/with_group_related_id_test.go new file mode 100644 index 0000000000..535e8665cd --- /dev/null +++ b/tests/integration/query/one_to_many/with_group_related_id_test.go @@ -0,0 +1,457 @@ +// 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 one_to_many + +import ( + "testing" + + testUtils "github.com/sourcenetwork/defradb/tests/integration" +) + +// TODO: Don't return grouped field if not selected. [https://github.com/sourcenetwork/defradb/issues/1582]. +func TestQueryOneToManyWithParentGroupByOnRelatedTypeIDFromManySide(t *testing.T) { + test := testUtils.RequestTestCase{ + Description: "One-to-many query with groupBy on related id (from many side).", + Request: `query { + Book(groupBy: [author_id]) { + _group { + name + rating + author { + name + age + } + } + } + }`, + Docs: map[int][]string{ + //books + 0: { // bae-fd541c25-229e-5280-b44b-e5c2af3e374d + `{ + "name": "Painted House", + "rating": 4.9, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "A Time for Mercy", + "rating": 4.5, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "The Client", + "rating": 4.5, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "Candide", + "rating": 4.95, + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3" + }`, + `{ + "name": "Zadig", + "rating": 4.91, + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3" + }`, + `{ + "name": "Histoiare des Celtes et particulierement des Gaulois et des Germains depuis les temps fabuleux jusqua la prise de Roze par les Gaulois", + "rating": 2, + "author_id": "bae-09d33399-197a-5b98-b135-4398f2b6de4c" + }`, + }, + //authors + 1: { + // bae-41598f0c-19bc-5da6-813b-e80f14a10df3 + `{ + "name": "John Grisham", + "age": 65, + "verified": true + }`, + // bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3 + `{ + "name": "Voltaire", + "age": 327, + "verified": true + }`, + // bae-09d33399-197a-5b98-b135-4398f2b6de4c + `{ + "name": "Simon Pelloutier", + "age": 327, + "verified": true + }`, + }, + }, + Results: []map[string]any{ + { + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3", + "_group": []map[string]any{ + { + "name": "Candide", + "rating": 4.95, + "author": map[string]any{ + "age": uint64(327), + "name": "Voltaire", + }, + }, + { + "name": "Zadig", + "rating": 4.91, + "author": map[string]any{ + "age": uint64(327), + "name": "Voltaire", + }, + }, + }, + }, + { + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3", + "_group": []map[string]any{ + { + "name": "The Client", + "rating": 4.5, + "author": map[string]any{ + "age": uint64(65), + "name": "John Grisham", + }, + }, + { + "name": "Painted House", + "rating": 4.9, + "author": map[string]any{ + "age": uint64(65), + "name": "John Grisham", + }, + }, + { + "name": "A Time for Mercy", + "rating": 4.5, + "author": map[string]any{ + "age": uint64(65), + "name": "John Grisham", + }, + }, + }, + }, + { + "author_id": "bae-09d33399-197a-5b98-b135-4398f2b6de4c", + "_group": []map[string]any{ + { + "name": "Histoiare des Celtes et particulierement des Gaulois et des Germains depuis les temps fabuleux jusqua la prise de Roze par les Gaulois", + "rating": 2.0, + "author": map[string]any{ + "age": uint64(327), + "name": "Simon Pelloutier", + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + +func TestQueryOneToManyWithParentGroupByOnRelatedTypeIDWithIDSelectionFromManySide(t *testing.T) { + test := testUtils.RequestTestCase{ + Description: "One-to-many query with groupBy on related id, with id selection (from many side).", + Request: `query { + Book(groupBy: [author_id]) { + author_id + _group { + name + rating + author { + name + age + } + } + } + }`, + Docs: map[int][]string{ + //books + 0: { // bae-fd541c25-229e-5280-b44b-e5c2af3e374d + `{ + "name": "Painted House", + "rating": 4.9, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "A Time for Mercy", + "rating": 4.5, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "The Client", + "rating": 4.5, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "Candide", + "rating": 4.95, + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3" + }`, + `{ + "name": "Zadig", + "rating": 4.91, + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3" + }`, + `{ + "name": "Histoiare des Celtes et particulierement des Gaulois et des Germains depuis les temps fabuleux jusqua la prise de Roze par les Gaulois", + "rating": 2, + "author_id": "bae-09d33399-197a-5b98-b135-4398f2b6de4c" + }`, + }, + //authors + 1: { + // bae-41598f0c-19bc-5da6-813b-e80f14a10df3 + `{ + "name": "John Grisham", + "age": 65, + "verified": true + }`, + // bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3 + `{ + "name": "Voltaire", + "age": 327, + "verified": true + }`, + // bae-09d33399-197a-5b98-b135-4398f2b6de4c + `{ + "name": "Simon Pelloutier", + "age": 327, + "verified": true + }`, + }, + }, + Results: []map[string]any{ + { + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3", + "_group": []map[string]any{ + { + "name": "Candide", + "rating": 4.95, + "author": map[string]any{ + "age": uint64(327), + "name": "Voltaire", + }, + }, + { + "name": "Zadig", + "rating": 4.91, + "author": map[string]any{ + "age": uint64(327), + "name": "Voltaire", + }, + }, + }, + }, + { + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3", + "_group": []map[string]any{ + { + "name": "The Client", + "rating": 4.5, + "author": map[string]any{ + "age": uint64(65), + "name": "John Grisham", + }, + }, + { + "name": "Painted House", + "rating": 4.9, + "author": map[string]any{ + "age": uint64(65), + "name": "John Grisham", + }, + }, + { + "name": "A Time for Mercy", + "rating": 4.5, + "author": map[string]any{ + "age": uint64(65), + "name": "John Grisham", + }, + }, + }, + }, + { + "author_id": "bae-09d33399-197a-5b98-b135-4398f2b6de4c", + "_group": []map[string]any{ + { + "name": "Histoiare des Celtes et particulierement des Gaulois et des Germains depuis les temps fabuleux jusqua la prise de Roze par les Gaulois", + "rating": 2.0, + "author": map[string]any{ + "age": uint64(327), + "name": "Simon Pelloutier", + }, + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + +// TODO: Don't return grouped field if not selected. [https://github.com/sourcenetwork/defradb/issues/1582]. +func TestQueryOneToManyWithParentGroupByOnRelatedTypeFromSingleSide(t *testing.T) { + test := testUtils.RequestTestCase{ + Description: "One-to-many query with groupBy on related id (from single side).", + Request: `query { + Author(groupBy: [published_id]) { + _group { + name + published { + name + rating + } + } + } + }`, + Docs: map[int][]string{ + //books + 0: { // bae-fd541c25-229e-5280-b44b-e5c2af3e374d + `{ + "name": "Painted House", + "rating": 4.9, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "A Time for Mercy", + "rating": 4.5, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "The Client", + "rating": 4.5, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "Candide", + "rating": 4.95, + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3" + }`, + `{ + "name": "Zadig", + "rating": 4.91, + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3" + }`, + `{ + "name": "Histoiare des Celtes et particulierement des Gaulois et des Germains depuis les temps fabuleux jusqua la prise de Roze par les Gaulois", + "rating": 2, + "author_id": "bae-09d33399-197a-5b98-b135-4398f2b6de4c" + }`, + }, + //authors + 1: { + // bae-41598f0c-19bc-5da6-813b-e80f14a10df3 + `{ + "name": "John Grisham", + "age": 65, + "verified": true + }`, + // bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3 + `{ + "name": "Voltaire", + "age": 327, + "verified": true + }`, + // bae-09d33399-197a-5b98-b135-4398f2b6de4c + `{ + "name": "Simon Pelloutier", + "age": 327, + "verified": true + }`, + }, + }, + + ExpectedError: "Argument \"groupBy\" has invalid value [published_id].\nIn element #1: Expected type \"AuthorFields\", found published_id.", + } + + executeTestCase(t, test) +} + +func TestQueryOneToManyWithParentGroupByOnRelatedTypeWithIDSelectionFromSingleSide(t *testing.T) { + test := testUtils.RequestTestCase{ + Description: "One-to-many query with groupBy on related id, with id selection (from single side).", + Request: `query { + Author(groupBy: [published_id]) { + published_id + _group { + name + published { + name + rating + } + } + } + }`, + Docs: map[int][]string{ + //books + 0: { // bae-fd541c25-229e-5280-b44b-e5c2af3e374d + `{ + "name": "Painted House", + "rating": 4.9, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "A Time for Mercy", + "rating": 4.5, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "The Client", + "rating": 4.5, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "Candide", + "rating": 4.95, + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3" + }`, + `{ + "name": "Zadig", + "rating": 4.91, + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3" + }`, + `{ + "name": "Histoiare des Celtes et particulierement des Gaulois et des Germains depuis les temps fabuleux jusqua la prise de Roze par les Gaulois", + "rating": 2, + "author_id": "bae-09d33399-197a-5b98-b135-4398f2b6de4c" + }`, + }, + //authors + 1: { + // bae-41598f0c-19bc-5da6-813b-e80f14a10df3 + `{ + "name": "John Grisham", + "age": 65, + "verified": true + }`, + // bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3 + `{ + "name": "Voltaire", + "age": 327, + "verified": true + }`, + // bae-09d33399-197a-5b98-b135-4398f2b6de4c + `{ + "name": "Simon Pelloutier", + "age": 327, + "verified": true + }`, + }, + }, + + ExpectedError: "Argument \"groupBy\" has invalid value [published_id].\nIn element #1: Expected type \"AuthorFields\", found published_id.", + } + + executeTestCase(t, test) +} diff --git a/tests/integration/query/one_to_many/with_related_id_test.go b/tests/integration/query/one_to_many/with_related_id_test.go new file mode 100644 index 0000000000..bcfef26cfe --- /dev/null +++ b/tests/integration/query/one_to_many/with_related_id_test.go @@ -0,0 +1,194 @@ +// 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 one_to_many + +import ( + "testing" + + testUtils "github.com/sourcenetwork/defradb/tests/integration" +) + +func TestQueryOneToManyWithRelatedTypeIDFromManySide(t *testing.T) { + test := testUtils.RequestTestCase{ + + Description: "One-to-many query with related id (from many side).", + + Request: `query { + Book { + name + author_id + } + }`, + + Docs: map[int][]string{ + //books + 0: { // bae-fd541c25-229e-5280-b44b-e5c2af3e374d + `{ + "name": "Painted House", + "rating": 4.9, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "A Time for Mercy", + "rating": 4.5, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "The Client", + "rating": 4.5, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "Candide", + "rating": 4.95, + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3" + }`, + `{ + "name": "Zadig", + "rating": 4.91, + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3" + }`, + `{ + "name": "Histoiare des Celtes et particulierement des Gaulois et des Germains depuis les temps fabuleux jusqua la prise de Roze par les Gaulois", + "rating": 2, + "author_id": "bae-09d33399-197a-5b98-b135-4398f2b6de4c" + }`, + }, + + //authors + 1: { + // bae-41598f0c-19bc-5da6-813b-e80f14a10df3 + `{ + "name": "John Grisham", + "age": 65, + "verified": true + }`, + // bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3 + `{ + "name": "Voltaire", + "age": 327, + "verified": true + }`, + // bae-09d33399-197a-5b98-b135-4398f2b6de4c + `{ + "name": "Simon Pelloutier", + "age": 327, + "verified": true + }`, + }, + }, + + Results: []map[string]any{ + { + "name": "Candide", + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3", + }, + { + "name": "Zadig", + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3", + }, + { + "name": "The Client", + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3", + }, + { + "name": "Histoiare des Celtes et particulierement des Gaulois et des Germains depuis les temps fabuleux jusqua la prise de Roze par les Gaulois", + "author_id": "bae-09d33399-197a-5b98-b135-4398f2b6de4c", + }, + { + "name": "Painted House", + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3", + }, + { + "name": "A Time for Mercy", + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3", + }, + }, + } + + executeTestCase(t, test) +} + +func TestQueryOneToManyWithRelatedTypeIDFromSingleSide(t *testing.T) { + test := testUtils.RequestTestCase{ + + Description: "One-to-many query with related id (from single side).", + + Request: `query { + Author { + name + author_id + } + }`, + + Docs: map[int][]string{ + //books + 0: { // bae-fd541c25-229e-5280-b44b-e5c2af3e374d + `{ + "name": "Painted House", + "rating": 4.9, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "A Time for Mercy", + "rating": 4.5, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "The Client", + "rating": 4.5, + "author_id": "bae-41598f0c-19bc-5da6-813b-e80f14a10df3" + }`, + `{ + "name": "Candide", + "rating": 4.95, + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3" + }`, + `{ + "name": "Zadig", + "rating": 4.91, + "author_id": "bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3" + }`, + `{ + "name": "Histoiare des Celtes et particulierement des Gaulois et des Germains depuis les temps fabuleux jusqua la prise de Roze par les Gaulois", + "rating": 2, + "author_id": "bae-09d33399-197a-5b98-b135-4398f2b6de4c" + }`, + }, + + //authors + 1: { + // bae-41598f0c-19bc-5da6-813b-e80f14a10df3 + `{ + "name": "John Grisham", + "age": 65, + "verified": true + }`, + // bae-7accaba8-ea9d-54b1-92f4-4a7ac5de88b3 + `{ + "name": "Voltaire", + "age": 327, + "verified": true + }`, + // bae-09d33399-197a-5b98-b135-4398f2b6de4c + `{ + "name": "Simon Pelloutier", + "age": 327, + "verified": true + }`, + }, + }, + + ExpectedError: "Cannot query field \"author_id\" on type \"Author\".", + } + + executeTestCase(t, test) +} diff --git a/tests/integration/query/simple/with_group_test.go b/tests/integration/query/simple/with_group_test.go index 5f19880081..6489e52fb8 100644 --- a/tests/integration/query/simple/with_group_test.go +++ b/tests/integration/query/simple/with_group_test.go @@ -208,6 +208,70 @@ func TestQuerySimpleWithGroupByNumberWithGroupString(t *testing.T) { executeTestCase(t, test) } +func TestQuerySimpleWithGroupByWithoutGroupedFieldSelectedWithInnerGroup(t *testing.T) { + test := testUtils.RequestTestCase{ + Description: "Simple query with groupBy without selecting field grouped by, with inner _group.", + Request: `query { + Users(groupBy: [Name]) { + _group { + Age + } + } + }`, + Docs: map[int][]string{ + 0: { + `{ + "Name": "John", + "Age": 25 + }`, + `{ + "Name": "John", + "Age": 32 + }`, + `{ + "Name": "Carlo", + "Age": 55 + }`, + `{ + "Name": "Alice", + "Age": 19 + }`, + }, + }, + Results: []map[string]any{ + { + "Name": "Alice", + "_group": []map[string]any{ + { + "Age": uint64(19), + }, + }, + }, + { + "Name": "John", + "_group": []map[string]any{ + { + "Age": uint64(32), + }, + { + "Age": uint64(25), + }, + }, + }, + { + "Name": "Carlo", + "_group": []map[string]any{ + { + "Age": uint64(55), + }, + }, + }, + }, + } + + executeTestCase(t, test) +} + func TestQuerySimpleWithGroupByString(t *testing.T) { test := testUtils.RequestTestCase{ Description: "Simple query with group by string", diff --git a/tests/integration/schema/group_test.go b/tests/integration/schema/group_test.go new file mode 100644 index 0000000000..e7e2fe41c9 --- /dev/null +++ b/tests/integration/schema/group_test.go @@ -0,0 +1,142 @@ +// 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 schema + +import ( + "testing" + + testUtils "github.com/sourcenetwork/defradb/tests/integration" +) + +func TestGroupByFieldForTheManySideInSchema(t *testing.T) { + test := testUtils.TestCase{ + + Description: "Test the fields for the many side groupBy are generated.", + + Actions: []any{ + testUtils.SchemaUpdate{ + Schema: ` + type Book { + name: String + rating: Float + author: Author + } + + type Author { + name: String + age: Int + verified: Boolean + published: [Book] + } + `, + }, + testUtils.IntrospectionRequest{ + Request: ` + { + __type(name: "BookFields") { + name + kind + enumValues { + name + } + } + } + `, + ContainsData: map[string]any{ + "__type": map[string]any{ + "kind": "ENUM", + "name": "BookFields", + "enumValues": []any{ + // Internal related object fields. + map[string]any{"name": "author"}, + map[string]any{"name": "author_id"}, + + // Internal fields. + map[string]any{"name": "_deleted"}, + map[string]any{"name": "_group"}, + map[string]any{"name": "_key"}, + map[string]any{"name": "_version"}, + + // User defined schema fields> + map[string]any{"name": "name"}, + map[string]any{"name": "rating"}, + }, + }, + }, + }, + }, + } + + testUtils.ExecuteTestCase(t, []string{"Book, Author"}, test) +} + +func TestGroupByFieldForTheSingleSideInSchema(t *testing.T) { + test := testUtils.TestCase{ + + Description: "Test the fields for the single side groupBy are generated.", + + Actions: []any{ + testUtils.SchemaUpdate{ + Schema: ` + type Book { + name: String + rating: Float + author: Author + } + + type Author { + name: String + age: Int + verified: Boolean + published: [Book] + } + `, + }, + testUtils.IntrospectionRequest{ + Request: ` + { + __type(name: "AuthorFields") { + name + kind + enumValues { + name + } + } + } + `, + ContainsData: map[string]any{ + "__type": map[string]any{ + "kind": "ENUM", + "name": "AuthorFields", + "enumValues": []any{ + // Internal related object fields. + map[string]any{"name": "published"}, + // Note: No `published_id` of this side. + + // Internal fields. + map[string]any{"name": "_deleted"}, + map[string]any{"name": "_group"}, + map[string]any{"name": "_key"}, + map[string]any{"name": "_version"}, + + // User defined schema fields> + map[string]any{"name": "name"}, + map[string]any{"name": "age"}, + map[string]any{"name": "verified"}, + }, + }, + }, + }, + }, + } + + testUtils.ExecuteTestCase(t, []string{"Book, Author"}, test) +}