From 6593b777c27fd33b0cb7f9bc1ddd0f670c6ea066 Mon Sep 17 00:00:00 2001 From: Andrew Sisley Date: Wed, 1 Dec 2021 12:32:30 -0500 Subject: [PATCH 1/2] Use dedicated property for collection name Instead of overwriting the field name --- query/graphql/parser/query.go | 5 +++-- query/graphql/planner/select.go | 6 ++++-- query/graphql/planner/type_join.go | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/query/graphql/parser/query.go b/query/graphql/parser/query.go index d57efd3f10..02f61a5d52 100644 --- a/query/graphql/parser/query.go +++ b/query/graphql/parser/query.go @@ -80,8 +80,9 @@ type Selection interface { // fields, and query arguments like filters, // limits, etc. type Select struct { - Name string - Alias string + Name string + Alias string + CollectionName string // Root is the top level query parsed type Root SelectionType diff --git a/query/graphql/planner/select.go b/query/graphql/planner/select.go index 2593596965..a2ec75b0f1 100644 --- a/query/graphql/planner/select.go +++ b/query/graphql/planner/select.go @@ -161,8 +161,10 @@ func (n *selectNode) Close() error { // the necessary filters. Its designed to work with the // planner.Select construction call. func (n *selectNode) initSource(parsed *parser.Select) error { - collectionName := parsed.Name - sourcePlan, err := n.p.getSource(collectionName) + if parsed.CollectionName == "" { + parsed.CollectionName = parsed.Name + } + sourcePlan, err := n.p.getSource(parsed.CollectionName) if err != nil { return err } diff --git a/query/graphql/planner/type_join.go b/query/graphql/planner/type_join.go index 64f6227c26..c2ecce52f2 100644 --- a/query/graphql/planner/type_join.go +++ b/query/graphql/planner/type_join.go @@ -189,7 +189,7 @@ func (p *Planner) makeTypeJoinOne(parent *selectNode, source planNode, subType * return nil, errors.New("Relation is missing referenced field") } - subType.Name = subTypeFieldDesc.Schema + subType.CollectionName = subTypeFieldDesc.Schema selectPlan, err := p.SubSelect(subType) if err != nil { @@ -351,7 +351,7 @@ func (p *Planner) makeTypeJoinMany(parent *selectNode, source planNode, subType if !ok { return nil, errors.New("couldn't find subtype field description for typeJoin node") } - subType.Name = subTypeFieldDesc.Schema + subType.CollectionName = subTypeFieldDesc.Schema selectPlan, err := p.SubSelect(subType) if err != nil { From fead82b00c075b23d2d6518c84b51772adaa75c9 Mon Sep 17 00:00:00 2001 From: Andrew Sisley Date: Wed, 1 Dec 2021 09:44:10 -0500 Subject: [PATCH 2/2] Render without mutating source doc --- query/graphql/planner/commit.go | 38 --------- query/graphql/planner/render.go | 146 +++++++++++++++++++------------- query/graphql/planner/select.go | 62 +------------- 3 files changed, 91 insertions(+), 155 deletions(-) diff --git a/query/graphql/planner/commit.go b/query/graphql/planner/commit.go index d0de52577b..1581c89855 100644 --- a/query/graphql/planner/commit.go +++ b/query/graphql/planner/commit.go @@ -43,7 +43,6 @@ func (n *commitSelectNode) Next() (bool, error) { } n.doc = n.source.Values() - n.renderDoc() return true, nil } @@ -85,10 +84,6 @@ func (p *Planner) CommitSelect(parsed *parser.CommitSelect) (planNode, error) { if err != nil { return nil, err } - err = commit.initFields(parsed) - if err != nil { - return nil, err - } slct := parsed.ToSelect() plan, err := p.SelectFromSource(slct, commit, false, nil) if err != nil { @@ -165,39 +160,6 @@ func (p *Planner) commitSelectAll(parsed *parser.CommitSelect) (*commitSelectNod return commit, nil } -// renderDoc applies the render meta-data to the -// links/previous sub selections for a commit type -// query. -func (n *commitSelectNode) renderDoc() { - for subfield, info := range n.subRenderInfo { - renderData := map[string]interface{}{ - "numResults": info.numResults, - "fields": info.fields, - "aliases": info.aliases, - } - for _, subcommit := range n.doc[subfield].([]map[string]interface{}) { - subcommit["__render"] = renderData - } - - } -} - -func (n *commitSelectNode) initFields(parsed parser.Selection) error { - for _, selection := range parsed.GetSelections() { - switch node := selection.(type) { - case *parser.Select: - info := renderInfo{} - for _, f := range node.Fields { - info.fields = append(info.fields, f.GetName()) - info.aliases = append(info.aliases, f.GetAlias()) - info.numResults++ - } - n.subRenderInfo[node.Name] = info - } - } - return nil -} - // commitSelectTopNode is a wrapper for the selectTopNode // in the case where the select is actually a CommitSelect type commitSelectTopNode struct { diff --git a/query/graphql/planner/render.go b/query/graphql/planner/render.go index 2cfdcde937..24a09f003f 100644 --- a/query/graphql/planner/render.go +++ b/query/graphql/planner/render.go @@ -11,25 +11,71 @@ package planner import ( "github.com/sourcenetwork/defradb/core" + "github.com/sourcenetwork/defradb/query/graphql/parser" ) -// @todo: Rebuild render system. -// @body: Current render system embeds render meta-data -// into EVERY SINGLE returned object map. This can be drastically -// reduced. Related: Replace Values() result with a typed object -// instead of a raw map[string]interface{} - // the final field select and render -type renderNode struct { // selectNode?? +type renderNode struct { p *Planner plan planNode - // fields []*base.FieldDescription - // aliases []string + renderInfo topLevelRenderInfo +} + +type topLevelRenderInfo struct { + children []renderInfo +} + +type renderInfo struct { + sourceFieldName string + destinationFieldName string + children []renderInfo } -func (p *Planner) render() *renderNode { - return &renderNode{p: p} +func (p *Planner) render(parsed *parser.Select) *renderNode { + return &renderNode{ + p: p, + renderInfo: buildTopLevelRenderInfo(parsed), + } +} + +func buildTopLevelRenderInfo(parsed parser.Selection) topLevelRenderInfo { + childSelections := parsed.GetSelections() + + info := topLevelRenderInfo{ + children: make([]renderInfo, len(childSelections)), + } + + for i, selection := range childSelections { + info.children[i] = buildRenderInfo(selection) + } + + return info +} + +func buildRenderInfo(parsed parser.Selection) renderInfo { + childSelections := parsed.GetSelections() + sourceFieldName := parsed.GetName() + alias := parsed.GetAlias() + + var destinationFieldName string + if alias == "" { + destinationFieldName = sourceFieldName + } else { + destinationFieldName = alias + } + + info := renderInfo{ + sourceFieldName: sourceFieldName, + destinationFieldName: destinationFieldName, + children: make([]renderInfo, len(childSelections)), + } + + for i, selection := range childSelections { + info.children[i] = buildRenderInfo(selection) + } + + return info } func (n *renderNode) Init() error { return n.plan.Init() } @@ -46,58 +92,44 @@ func (r *renderNode) Values() map[string]interface{} { if doc == nil { return doc } - return r.render(doc) + + result := map[string]interface{}{} + for _, renderInfo := range r.renderInfo.children { + renderInfo.render(doc, result) + } + return result } -// render uses the __render map within the return doc via Values(). -// it extracts the associated render meta-data, and returns a newly -// rendered map. -// The render rules are as follows: -// The doc returned by the plan has the following values: -// { -// ... document fields returned by scanPlan -// __render: { -// numRender: ... => the number of fields in the actual selectionset -// fields: ... => array of fields extracted from the raw query (Includes selection set + filter dependencies) -// aliases: ... => array of aliases, index matched to fields array. -// } -// } -func (r *renderNode) render(src map[string]interface{}) map[string]interface{} { - result := make(map[string]interface{}) - if renderMap, ok := src["__render"].(map[string]interface{}); ok { - numRenderFields := renderMap["numResults"].(int) - fields := renderMap["fields"].([]string) - aliases := renderMap["aliases"].([]string) - - for i := 0; i < numRenderFields; i++ { - field := fields[i] - var dst string - name := field - dst = name - alias := aliases[i] - if alias != "" { - dst = alias +// Renders the source document into the destination document using the given renderInfo. +// Function recursively handles any nested children defined in the render info. +func (r *renderInfo) render(src map[string]interface{}, destination map[string]interface{}) { + var resultValue interface{} + if val, ok := src[r.sourceFieldName]; ok { + switch v := val.(type) { + // If the current property is itself a map, we should render any properties of the child + case map[string]interface{}: + inner := map[string]interface{}{} + for _, child := range r.children { + child.render(v, inner) } - - if val, ok := src[name]; ok { - switch v := val.(type) { - case map[string]interface{}: - result[dst] = r.render(v) - case []map[string]interface{}: - subdocs := make([]map[string]interface{}, 0) - for _, subv := range v { - subdocs = append(subdocs, r.render(subv)) - } - result[dst] = subdocs - default: - result[dst] = v + resultValue = inner + // If the current property is an array of maps, we should render each child map + case []map[string]interface{}: + subdocs := make([]map[string]interface{}, 0) + for _, subv := range v { + inner := map[string]interface{}{} + for _, child := range r.children { + child.render(subv, inner) } - } else { - result[dst] = nil + subdocs = append(subdocs, inner) } + resultValue = subdocs + default: + resultValue = v } } else { - return src + resultValue = nil } - return result + + destination[r.destinationFieldName] = resultValue } diff --git a/query/graphql/planner/select.go b/query/graphql/planner/select.go index a2ec75b0f1..460c2077f7 100644 --- a/query/graphql/planner/select.go +++ b/query/graphql/planner/select.go @@ -46,12 +46,6 @@ func (n *selectTopNode) Close() error { return n.plan.Close() } -type renderInfo struct { - numResults int - fields []string - aliases []string -} - type selectNode struct { p *Planner @@ -118,31 +112,12 @@ func (n *selectNode) Next() (bool, error) { } if passes { - n.renderDoc() return true, err - // err := - // return err == nil, err } // didn't pass, keep looping } } -// applies all the necessary rendering to doc -// as defined by the query statement. This includes -// aliases, and any transformations. -// Takes a doc map, and applies the necessary rendering. -// It also holds all the necessary render meta-data -// and ast parser data. -func (n *selectNode) renderDoc() error { - renderData := map[string]interface{}{ - "numResults": n.renderInfo.numResults, - "fields": n.renderInfo.fields, - "aliases": n.renderInfo.aliases, - } - n.doc["__render"] = renderData - return nil -} - func (n *selectNode) Spans(spans core.Spans) { n.source.Spans(spans) } @@ -198,39 +173,6 @@ func (n *selectNode) initSource(parsed *parser.Select) error { } func (n *selectNode) initFields(parsed *parser.Select) error { - n.renderInfo.numResults = 0 - // subTypes := make([]*parser.Select, 0) - - // iterate to build the render info - for _, field := range parsed.Fields { - switch node := field.(type) { - case *parser.Select: - // continue //ignore for now - // future: - // plan := n.p.Select(node) - // n.source := p.SubTypeIndexJoin(origScan, plan) - // f, found := n.sourceInfo.collectionDescription.GetField(node.GetName()) - // if found { - // n.renderInfo.fields = append(n.renderInfo.fields, f.Name) - // } - n.renderInfo.fields = append(n.renderInfo.fields, node.GetName()) - // subTypes = append(subTypes, node) - case *parser.Field, parser.Field: - // f, found := n.sourceInfo.collectionDescription.GetField(node.GetName()) - // if found { - // n.renderInfo.fields = append(n.renderInfo.fields, f.Name) - // } - n.renderInfo.fields = append(n.renderInfo.fields, node.GetName()) - } - n.renderInfo.aliases = append(n.renderInfo.aliases, field.GetAlias()) - n.renderInfo.numResults++ - } - - // iterate to build sub plans - // for _, field := range parsed.Fields { - - // } - // re-organize the fields slice into reverse-alphabetical // this makes sure the reserved database fields that start with // a "_" end up at the end. So if/when we build our MultiNode @@ -353,7 +295,7 @@ func (p *Planner) SelectFromSource(parsed *parser.Select, source planNode, fromC top := &selectTopNode{ source: s, - render: p.render(), + render: p.render(parsed), limit: limitPlan, sort: sortPlan, group: groupPlan, @@ -391,7 +333,7 @@ func (p *Planner) Select(parsed *parser.Select) (planNode, error) { top := &selectTopNode{ source: s, - render: p.render(), + render: p.render(parsed), limit: limitPlan, sort: sortPlan, group: groupPlan,