Skip to content

Commit

Permalink
Handle secondary side of relational ids
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewSisley committed Aug 10, 2023
1 parent 8646ed8 commit d3874c1
Show file tree
Hide file tree
Showing 6 changed files with 600 additions and 23 deletions.
90 changes: 90 additions & 0 deletions planner/mapper/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ func toSelect(
return nil, err
}

fields, err = resolveSecondaryRelationIDs(descriptionsRepo, desc, mapping, fields)
if err != nil {
return nil, err
}

// Resolve groupBy mappings i.e. alias remapping and handle missed inner group.
if selectRequest.GroupBy.HasValue() {
groupByFields := selectRequest.GroupBy.Value().Fields
Expand Down Expand Up @@ -941,6 +946,91 @@ func constructDummyJoin(
}, nil
}

// resolveSecondaryRelationIDs contructs the required stuff needed to resolve secondary relation ids.
//
// They are handled by joining (if not already done so) the related object and copying its key into the
// secondary relation id field.
//
// They copying itself is handled within [typeJoinOne].
func resolveSecondaryRelationIDs(
descriptionsRepo *DescriptionsRepo,
desc *client.CollectionDescription,
mapping *core.DocumentMapping,
requestables []Requestable,
) ([]Requestable, error) {
fields := requestables

for _, requestable := range requestables {
existingField, isField := requestable.(*Field)
if !isField {
continue
}

fieldDesc, descFound := desc.Schema.GetField(existingField.Name)
if !descFound {
continue
}

if !fieldDesc.RelationType.IsSet(client.Relation_Type_INTERNAL_ID) {
continue
}

objectFieldDesc, descFound := desc.Schema.GetField(
strings.TrimSuffix(existingField.Name, request.RelatedObjectID),
)
if !descFound {
continue
}

if objectFieldDesc.RelationName == "" {
continue
}

var siblingFound bool
for _, siblingRequestable := range requestables {
siblingSelect, isSelect := siblingRequestable.(*Select)
if !isSelect {
continue
}

siblingFieldDesc, descFound := desc.Schema.GetField(siblingSelect.Field.Name)
if !descFound {
continue
}

if siblingFieldDesc.RelationName != objectFieldDesc.RelationName {
continue
}

if siblingFieldDesc.Kind != client.FieldKind_FOREIGN_OBJECT {
continue
}

siblingFound = true
break
}

if !siblingFound {
objectFieldName := strings.TrimSuffix(existingField.Name, request.RelatedObjectID)

// We only require the dockey of the related object, so an empty join is all we need.
dummyJoin, err := constructDummyJoin(
descriptionsRepo,
desc.Name,
mapping,
objectFieldName,
)
if err != nil {
return nil, err
}

fields = append(fields, dummyJoin)
}
}

return fields, nil
}

// ToCommitSelect converts the given [request.CommitSelect] into a [CommitSelect].
//
// In the process of doing so it will construct the document map required to access the data
Expand Down
35 changes: 26 additions & 9 deletions planner/type_join.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
package planner

import (
"github.com/sourcenetwork/immutable"

"github.com/sourcenetwork/defradb/client"
"github.com/sourcenetwork/defradb/client/request"
"github.com/sourcenetwork/defradb/connor"
Expand Down Expand Up @@ -255,7 +257,8 @@ type typeJoinOne struct {
subTypeName string
subTypeFieldName string

primary bool
primary bool
secondaryFieldIndex immutable.Option[int]

spans core.Spans
subSelect *mapper.Select
Expand Down Expand Up @@ -305,15 +308,24 @@ func (p *Planner) makeTypeJoinOne(
return nil, client.NewErrFieldNotExist(subTypeFieldDesc.RelationName)
}

var secondaryFieldIndex immutable.Option[int]
if !isPrimary {
idFieldName := subTypeFieldDesc.Name + request.RelatedObjectID
secondaryFieldIndex = immutable.Some(
parent.documentMapping.FirstIndexOfName(idFieldName),
)
}

return &typeJoinOne{
p: p,
root: source,
subSelect: subType,
subTypeName: subType.Name,
subTypeFieldName: subTypeField.Name,
subType: selectPlan,
primary: isPrimary,
docMapper: docMapper{parent.documentMapping},
p: p,
root: source,
subSelect: subType,
subTypeName: subType.Name,
subTypeFieldName: subTypeField.Name,
subType: selectPlan,
primary: isPrimary,
secondaryFieldIndex: secondaryFieldIndex,
docMapper: docMapper{parent.documentMapping},
}, nil
}

Expand Down Expand Up @@ -387,6 +399,11 @@ func (n *typeJoinOne) valuesSecondary(doc core.Doc) (core.Doc, error) {

subdoc := n.subType.Value()
doc.Fields[n.subSelect.Index] = subdoc

if n.secondaryFieldIndex.HasValue() {
doc.Fields[n.secondaryFieldIndex.Value()] = subdoc.GetKey()
}

return doc, nil
}

Expand Down
5 changes: 2 additions & 3 deletions tests/integration/query/one_to_one/simple_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,9 +374,8 @@ func TestQueryOneToOne_WithRelationIDFromSecondarySide(t *testing.T) {
}`,
Results: []map[string]any{
{
"name": "Painted House",
// This is undesirable and needs to change within this PR
"author_id": nil,
"name": "Painted House",
"author_id": "bae-6b624301-3d0a-5336-bd2c-ca00bca3de85",
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
testUtils "github.com/sourcenetwork/defradb/tests/integration"
)

// This documents unwanted behaviour, see https://github.com/sourcenetwork/defradb/issues/1520
func TestQueryOneToOneWithClashingIdFieldOnSecondary(t *testing.T) {
test := testUtils.TestCase{
Description: "One-to-one relation secondary direction, id field with name clash on secondary side",
Expand Down Expand Up @@ -62,7 +63,7 @@ func TestQueryOneToOneWithClashingIdFieldOnSecondary(t *testing.T) {
Results: []map[string]any{
{
"name": "Painted House",
"author_id": uint64(123456),
"author_id": "bae-9d67a886-64e3-520b-8cd5-1ca7b098fabe",
"author": map[string]any{
"name": "John Grisham",
},
Expand Down
Loading

0 comments on commit d3874c1

Please sign in to comment.