Skip to content

Commit

Permalink
Add query support for one-sided relations
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewSisley committed Sep 17, 2024
1 parent 23b7445 commit c949d8e
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 32 deletions.
9 changes: 7 additions & 2 deletions internal/planner/planner.go
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,11 @@ func findFilteredByRelationFields(
}

func (p *Planner) tryOptimizeJoinDirection(node *invertibleTypeJoin, parentPlan *selectTopNode) error {
if !node.childSide.relFieldDef.HasValue() {
// If the relation is one sided we cannot invert the join, so return early
return nil

Check warning on line 291 in internal/planner/planner.go

View check run for this annotation

Codecov / codecov/patch

internal/planner/planner.go#L291

Added line #L291 was not covered by tests
}

filteredSubFields := findFilteredByRelationFields(
parentPlan.selectNode.filter.Conditions,
node.documentMapping,
Expand All @@ -295,8 +300,8 @@ func (p *Planner) tryOptimizeJoinDirection(node *invertibleTypeJoin, parentPlan
for subFieldName, subFieldInd := range filteredSubFields {
indexes := desc.GetIndexesOnField(subFieldName)
if len(indexes) > 0 && !filter.IsComplex(parentPlan.selectNode.filter) {
subInd := node.documentMapping.FirstIndexOfName(node.parentSide.relFieldDef.Name)
relatedField := mapper.Field{Name: node.parentSide.relFieldDef.Name, Index: subInd}
subInd := node.documentMapping.FirstIndexOfName(node.parentSide.relFieldDef.Value().Name)
relatedField := mapper.Field{Name: node.parentSide.relFieldDef.Value().Name, Index: subInd}
fieldFilter := filter.UnwrapRelation(filter.CopyField(
parentPlan.selectNode.filter,
relatedField,
Expand Down
67 changes: 38 additions & 29 deletions internal/planner/type_join.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,10 @@ func (n *typeIndexJoin) simpleExplain() (map[string]any, error) {

addExplainData := func(j *invertibleTypeJoin) error {
// Add the attribute(s).
simpleExplainMap[joinRootLabel] = immutable.Some(j.childSide.relFieldDef.Name)
simpleExplainMap[joinSubTypeNameLabel] = j.parentSide.relFieldDef.Name
if j.childSide.relFieldDef.HasValue() {
simpleExplainMap[joinRootLabel] = immutable.Some(j.childSide.relFieldDef.Value().Name)
}
simpleExplainMap[joinSubTypeNameLabel] = j.parentSide.relFieldDef.Value().Name

subTypeExplainGraph, err := buildSimpleExplainGraph(j.childSide.plan)
if err != nil {
Expand Down Expand Up @@ -327,23 +329,30 @@ func (p *Planner) newInvertableTypeJoin(
return invertibleTypeJoin{}, err
}

var childsRelFieldDef immutable.Option[client.FieldDefinition]
var childSideRelIDFieldMapIndex immutable.Option[int]
childsRelFieldDesc, ok := subCol.Description().GetFieldByRelation(
parentsRelFieldDef.RelationName,
parent.collection.Name().Value(),
parentsRelFieldDef.Name,
)
if !ok {
return invertibleTypeJoin{}, client.NewErrFieldNotExist(parentsRelFieldDef.Name)
}
if ok {
def, ok := subCol.Definition().GetFieldByName(childsRelFieldDesc.Name)
if !ok {
return invertibleTypeJoin{}, client.NewErrFieldNotExist(subSelect.Name)

Check warning on line 342 in internal/planner/type_join.go

View check run for this annotation

Codecov / codecov/patch

internal/planner/type_join.go#L342

Added line #L342 was not covered by tests
}

childsRelFieldDef, ok := subCol.Definition().GetFieldByName(childsRelFieldDesc.Name)
if !ok {
return invertibleTypeJoin{}, client.NewErrFieldNotExist(subSelect.Name)
ind := subSelectPlan.DocumentMap().IndexesByName[def.Name+request.RelatedObjectID]
if len(ind) > 0 {
childSideRelIDFieldMapIndex = immutable.Some(ind[0])
}

childsRelFieldDef = immutable.Some(def)
}

parentSide := joinSide{
plan: sourcePlan,
relFieldDef: parentsRelFieldDef,
relFieldDef: immutable.Some(parentsRelFieldDef),
relFieldMapIndex: immutable.Some(subSelect.Index),
col: parent.collection,
isFirst: true,
Expand All @@ -356,16 +365,12 @@ func (p *Planner) newInvertableTypeJoin(
}

childSide := joinSide{
plan: subSelectPlan,
relFieldDef: childsRelFieldDef,
col: subCol,
isFirst: false,
isParent: false,
}

ind = subSelectPlan.DocumentMap().IndexesByName[childsRelFieldDef.Name+request.RelatedObjectID]
if len(ind) > 0 {
childSide.relIDFieldMapIndex = immutable.Some(ind[0])
plan: subSelectPlan,
relFieldDef: childsRelFieldDef,
relIDFieldMapIndex: childSideRelIDFieldMapIndex,
col: subCol,
isFirst: false,
isParent: false,
}

return invertibleTypeJoin{
Expand All @@ -377,8 +382,12 @@ func (p *Planner) newInvertableTypeJoin(
}

type joinSide struct {
plan planNode
relFieldDef client.FieldDefinition
plan planNode
// The field definition of the relation-object field on this side of the relation.
//
// This will always have a value on the primary side, but it may not have a value on
// the secondary side, as the secondary half of the relation is optional.
relFieldDef immutable.Option[client.FieldDefinition]
relFieldMapIndex immutable.Option[int]
relIDFieldMapIndex immutable.Option[int]
col client.Collection
Expand All @@ -387,7 +396,7 @@ type joinSide struct {
}

func (s *joinSide) isPrimary() bool {
return s.relFieldDef.IsPrimaryRelation
return s.relFieldDef.HasValue() && s.relFieldDef.Value().IsPrimaryRelation
}

func (join *invertibleTypeJoin) getFirstSide() *joinSide {
Expand Down Expand Up @@ -524,9 +533,9 @@ func newPrimaryObjectsRetriever(

func (j *primaryObjectsRetriever) retrievePrimaryDocsReferencingSecondaryDoc() error {
relIDFieldDef, ok := j.primarySide.col.Definition().GetFieldByName(
j.primarySide.relFieldDef.Name + request.RelatedObjectID)
j.primarySide.relFieldDef.Value().Name + request.RelatedObjectID)
if !ok {
return client.NewErrFieldNotExist(j.primarySide.relFieldDef.Name + request.RelatedObjectID)
return client.NewErrFieldNotExist(j.primarySide.relFieldDef.Value().Name + request.RelatedObjectID)

Check warning on line 538 in internal/planner/type_join.go

View check run for this annotation

Codecov / codecov/patch

internal/planner/type_join.go#L538

Added line #L538 was not covered by tests
}

j.primaryScan = getScanNode(j.primarySide.plan)
Expand Down Expand Up @@ -620,15 +629,15 @@ func joinPrimaryDocs(primaryDocs []core.Doc, secondarySide, primarySide *joinSid
secondaryDoc := secondarySide.plan.Value()

if secondarySide.relFieldMapIndex.HasValue() {
if secondarySide.relFieldDef.Kind.IsArray() {
if !secondarySide.relFieldDef.HasValue() || secondarySide.relFieldDef.Value().Kind.IsArray() {
secondaryDoc.Fields[secondarySide.relFieldMapIndex.Value()] = primaryDocs
} else if len(primaryDocs) > 0 {
secondaryDoc.Fields[secondarySide.relFieldMapIndex.Value()] = primaryDocs[0]
}
}

if secondarySide.relIDFieldMapIndex.HasValue() {
if secondarySide.relFieldDef.Kind.IsArray() {
if !secondarySide.relFieldDef.HasValue() || secondarySide.relFieldDef.Value().Kind.IsArray() {
secondaryDoc.Fields[secondarySide.relIDFieldMapIndex.Value()] = docsToDocIDs(primaryDocs)
} else if len(primaryDocs) > 0 {
secondaryDoc.Fields[secondarySide.relIDFieldMapIndex.Value()] = primaryDocs[0].GetID()
Expand Down Expand Up @@ -703,7 +712,7 @@ func (join *invertibleTypeJoin) nextJoinedSecondaryDoc() (bool, error) {
firstSide := join.getFirstSide()
secondSide := join.getSecondSide()

secondaryDocID := getForeignKey(firstSide.plan, firstSide.relFieldDef.Name)
secondaryDocID := getForeignKey(firstSide.plan, firstSide.relFieldDef.Value().Name)
if secondaryDocID == "" {
if firstSide.isParent {
join.docsToYield = append(join.docsToYield, firstSide.plan.Value())
Expand Down Expand Up @@ -734,7 +743,7 @@ func (join *invertibleTypeJoin) nextJoinedSecondaryDoc() (bool, error) {
return join.Next()
}

if join.parentSide.relFieldDef.Kind.IsArray() {
if join.parentSide.relFieldDef.Value().Kind.IsArray() {
var primaryDocs []core.Doc
var secondaryDoc core.Doc
// if child is not requested as part of the response, we just add the existing one (fetched by the secondary index
Expand Down Expand Up @@ -771,7 +780,7 @@ func (join *invertibleTypeJoin) invertJoinDirectionWithIndex(
) error {
p := join.childSide.plan
s := getScanNode(p)
s.tryAddField(join.childSide.relFieldDef.Name + request.RelatedObjectID)
s.tryAddField(join.childSide.relFieldDef.Value().Name + request.RelatedObjectID)
s.filter = fieldFilter
s.initFetcher(immutable.Option[string]{}, immutable.Some(index))

Expand Down
11 changes: 10 additions & 1 deletion tests/integration/query/one_to_many/one_sided_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,16 @@ func TestQueryOneToMany_OneSided(t *testing.T) {
}
}
}`,
ExpectedError: "The given field does not exist. Name: author",
Results: map[string]any{
"Book": []map[string]any{
{
"name": "Painted House",
"author": map[string]any{
"name": "John Grisham",
},
},
},
},
},
},
}
Expand Down

0 comments on commit c949d8e

Please sign in to comment.