github.com/unigraph-dev/dgraph@v1.1.1-0.20200923154953-8b52b426f765/query/recurse.go (about)

     1  /*
     2   * Copyright 2017-2018 Dgraph Labs, Inc. and Contributors
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package query
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"math"
    23  
    24  	"github.com/dgraph-io/dgraph/algo"
    25  	"github.com/dgraph-io/dgraph/x"
    26  	"github.com/pkg/errors"
    27  )
    28  
    29  func (start *SubGraph) expandRecurse(ctx context.Context, maxDepth uint64) error {
    30  	// Note: Key format is - "attr|fromUID|toUID"
    31  	reachMap := make(map[string]struct{})
    32  	allowLoop := start.Params.RecurseArgs.AllowLoop
    33  	var numEdges uint64
    34  	var exec []*SubGraph
    35  	var err error
    36  
    37  	rrch := make(chan error, len(exec))
    38  	startChildren := make([]*SubGraph, len(start.Children))
    39  	copy(startChildren, start.Children)
    40  	// Empty children before giving to ProcessGraph as we are only concerned with DestUids.
    41  	start.Children = start.Children[:0]
    42  
    43  	// Process the root first.
    44  	go ProcessGraph(ctx, start, nil, rrch)
    45  	select {
    46  	case err = <-rrch:
    47  		if err != nil {
    48  			return err
    49  		}
    50  	case <-ctx.Done():
    51  		return ctx.Err()
    52  	}
    53  
    54  	if start.UnknownAttr {
    55  		return nil
    56  	}
    57  
    58  	// Add children back and expand if necessary
    59  	if exec, err = expandChildren(ctx, start, startChildren); err != nil {
    60  		return err
    61  	}
    62  
    63  	dummy := &SubGraph{}
    64  	var depth uint64
    65  	for {
    66  		if depth >= maxDepth {
    67  			return nil
    68  		}
    69  		depth++
    70  
    71  		// When the maximum depth has been reached, avoid retrieving any facets as
    72  		// the nodes at the other end of the edge will not be a part of this query.
    73  		// Otherwise, the facets will be included in the query without any other
    74  		// information about the node, which is quite counter-intuitive.
    75  		if depth == maxDepth {
    76  			for _, sg := range exec {
    77  				sg.Params.Facet = nil
    78  			}
    79  		}
    80  
    81  		rrch := make(chan error, len(exec))
    82  		for _, sg := range exec {
    83  			go ProcessGraph(ctx, sg, dummy, rrch)
    84  		}
    85  
    86  		var recurseErr error
    87  		for range exec {
    88  			select {
    89  			case err = <-rrch:
    90  				if err != nil {
    91  					if recurseErr == nil {
    92  						recurseErr = err
    93  					}
    94  				}
    95  			case <-ctx.Done():
    96  				if recurseErr == nil {
    97  					recurseErr = ctx.Err()
    98  				}
    99  			}
   100  		}
   101  
   102  		if recurseErr != nil {
   103  			return recurseErr
   104  		}
   105  
   106  		for _, sg := range exec {
   107  			// sg.uidMatrix can be empty. Continue if that is the case.
   108  			if len(sg.uidMatrix) == 0 {
   109  				continue
   110  			}
   111  
   112  			if sg.UnknownAttr {
   113  				continue
   114  			}
   115  
   116  			if len(sg.Filters) > 0 {
   117  				// We need to do this in case we had some filters.
   118  				sg.updateUidMatrix()
   119  			}
   120  
   121  			for mIdx, fromUID := range sg.SrcUIDs.Uids {
   122  				if allowLoop {
   123  					for _, ul := range sg.uidMatrix {
   124  						numEdges += uint64(len(ul.Uids))
   125  					}
   126  				} else {
   127  					algo.ApplyFilter(sg.uidMatrix[mIdx], func(uid uint64, i int) bool {
   128  						key := fmt.Sprintf("%s|%d|%d", sg.Attr, fromUID, uid)
   129  						_, seen := reachMap[key] // Combine fromUID here.
   130  						if seen {
   131  							return false
   132  						}
   133  						// Mark this edge as taken. We'd disallow this edge later.
   134  						reachMap[key] = struct{}{}
   135  						numEdges++
   136  						return true
   137  					})
   138  				}
   139  			}
   140  			if len(sg.Params.Order) > 0 || len(sg.Params.FacetOrder) > 0 {
   141  				// Can't use merge sort if the UIDs are not sorted.
   142  				sg.updateDestUids()
   143  			} else {
   144  				sg.DestUIDs = algo.MergeSorted(sg.uidMatrix)
   145  			}
   146  		}
   147  
   148  		// modify the exec and attach child nodes.
   149  		var out []*SubGraph
   150  		var exp []*SubGraph
   151  		for _, sg := range exec {
   152  			if sg.UnknownAttr {
   153  				continue
   154  			}
   155  			if len(sg.DestUIDs.Uids) == 0 {
   156  				continue
   157  			}
   158  			if exp, err = expandChildren(ctx, sg, startChildren); err != nil {
   159  				return err
   160  			}
   161  			out = append(out, exp...)
   162  		}
   163  
   164  		if numEdges > x.Config.QueryEdgeLimit {
   165  			// If we've seen too many edges, stop the query.
   166  			return errors.Errorf("Exceeded query edge limit = %v. Found %v edges.",
   167  				x.Config.QueryEdgeLimit, numEdges)
   168  		}
   169  
   170  		if len(out) == 0 {
   171  			return nil
   172  		}
   173  		exec = out
   174  	}
   175  }
   176  
   177  // expandChildren adds child nodes to a SubGraph with no children, expanding them if necessary.
   178  func expandChildren(ctx context.Context, sg *SubGraph, children []*SubGraph) ([]*SubGraph, error) {
   179  	if len(sg.Children) > 0 {
   180  		return nil, errors.New("Subgraph should not have any children")
   181  	}
   182  	// Add children and expand if necessary
   183  	sg.Children = append(sg.Children, children...)
   184  	expandedChildren, err := expandSubgraph(ctx, sg)
   185  	if err != nil {
   186  		return nil, err
   187  	}
   188  	out := make([]*SubGraph, 0, len(expandedChildren))
   189  	sg.Children = sg.Children[:0]
   190  	// Link new child nodes back to parent destination UIDs
   191  	for _, child := range expandedChildren {
   192  		newChild := new(SubGraph)
   193  		newChild.copyFiltersRecurse(child)
   194  		newChild.SrcUIDs = sg.DestUIDs
   195  		newChild.Params.Var = child.Params.Var
   196  		sg.Children = append(sg.Children, newChild)
   197  		out = append(out, newChild)
   198  	}
   199  	return out, nil
   200  }
   201  
   202  func recurse(ctx context.Context, sg *SubGraph) error {
   203  	if !sg.Params.Recurse {
   204  		return errors.Errorf("Invalid recurse path query")
   205  	}
   206  
   207  	depth := sg.Params.RecurseArgs.Depth
   208  	if depth == 0 {
   209  		if sg.Params.RecurseArgs.AllowLoop {
   210  			return errors.Errorf("Depth must be > 0 when loop is true for recurse query")
   211  		}
   212  		// If no depth is specified, expand till we reach all leaf nodes
   213  		// or we see reach too many nodes.
   214  		depth = math.MaxUint64
   215  	}
   216  
   217  	for _, child := range sg.Children {
   218  		if len(child.Children) > 0 {
   219  			return errors.Errorf(
   220  				"recurse queries require that all predicates are specified in one level")
   221  		}
   222  	}
   223  
   224  	return sg.expandRecurse(ctx, depth)
   225  }