github.com/cayleygraph/cayley@v0.7.7/graph/path/morphism_apply_functions.go (about)

     1  // Copyright 2014 The Cayley Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package path
    16  
    17  import (
    18  	"fmt"
    19  
    20  	"github.com/cayleygraph/cayley/graph"
    21  	"github.com/cayleygraph/cayley/graph/iterator"
    22  	"github.com/cayleygraph/cayley/graph/shape"
    23  	"github.com/cayleygraph/quad"
    24  )
    25  
    26  // join puts two iterators together by intersecting their result sets with an AND
    27  // Since we're using an and iterator, it's a good idea to put the smallest result
    28  // set first so that Next() produces fewer values to check Contains().
    29  func join(its ...shape.Shape) shape.Shape {
    30  	if len(its) == 0 {
    31  		return shape.Null{}
    32  	} else if _, ok := its[0].(shape.AllNodes); ok {
    33  		return join(its[1:]...)
    34  	}
    35  	return shape.Intersect(its)
    36  }
    37  
    38  // isMorphism represents all nodes passed in-- if there are none, this function
    39  // acts as a passthrough for the previous iterator.
    40  func isMorphism(nodes ...quad.Value) morphism {
    41  	return morphism{
    42  		Reversal: func(ctx *pathContext) (morphism, *pathContext) { return isMorphism(nodes...), ctx },
    43  		Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
    44  			if len(nodes) == 0 {
    45  				// Acting as a passthrough here is equivalent to
    46  				// building a NodesAllIterator to Next() or Contains()
    47  				// from here as in previous versions.
    48  				return in, ctx
    49  			}
    50  			s := shape.Lookup(nodes)
    51  			if _, ok := in.(shape.AllNodes); ok {
    52  				return s, ctx
    53  			}
    54  			// Anything with fixedIterators will usually have a much
    55  			// smaller result set, so join isNodes first here.
    56  			return join(s, in), ctx
    57  		},
    58  	}
    59  }
    60  
    61  // isNodeMorphism represents all nodes passed in-- if there are none, this function
    62  // acts as a passthrough for the previous iterator.
    63  func isNodeMorphism(nodes ...graph.Ref) morphism {
    64  	return morphism{
    65  		Reversal: func(ctx *pathContext) (morphism, *pathContext) { return isNodeMorphism(nodes...), ctx },
    66  		Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
    67  			if len(nodes) == 0 {
    68  				// Acting as a passthrough here is equivalent to
    69  				// building a NodesAllIterator to Next() or Contains()
    70  				// from here as in previous versions.
    71  				return in, ctx
    72  			}
    73  			// Anything with fixedIterators will usually have a much
    74  			// smaller result set, so join isNodes first here.
    75  			return join(shape.Fixed(nodes), in), ctx
    76  		},
    77  	}
    78  }
    79  
    80  // filterMorphism is the set of nodes that passes filters.
    81  func filterMorphism(filt []shape.ValueFilter) morphism {
    82  	return morphism{
    83  		Reversal: func(ctx *pathContext) (morphism, *pathContext) { return filterMorphism(filt), ctx },
    84  		Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
    85  			return shape.AddFilters(in, filt...), ctx
    86  		},
    87  	}
    88  }
    89  
    90  // hasMorphism is the set of nodes that is reachable via either a *Path, a
    91  // single node.(string) or a list of nodes.([]string).
    92  func hasMorphism(via interface{}, rev bool, nodes ...quad.Value) morphism {
    93  	var node shape.Shape
    94  	if len(nodes) == 0 {
    95  		node = shape.AllNodes{}
    96  	} else {
    97  		node = shape.Lookup(nodes)
    98  	}
    99  	return hasShapeMorphism(via, rev, node)
   100  }
   101  
   102  // hasShapeMorphism is the set of nodes that is reachable via either a *Path, a
   103  // single node.(string) or a list of nodes.([]string).
   104  func hasShapeMorphism(via interface{}, rev bool, nodes shape.Shape) morphism {
   105  	return morphism{
   106  		Reversal: func(ctx *pathContext) (morphism, *pathContext) { return hasShapeMorphism(via, rev, nodes), ctx },
   107  		Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
   108  			return shape.HasLabels(in, buildVia(via), nodes, ctx.labelSet, rev), ctx
   109  		},
   110  	}
   111  }
   112  
   113  // hasFilterMorphism is the set of nodes that is reachable via either a *Path, a
   114  // single node.(string) or a list of nodes.([]string) and that passes provided filters.
   115  func hasFilterMorphism(via interface{}, rev bool, filt []shape.ValueFilter) morphism {
   116  	return hasShapeMorphism(via, rev, shape.Filter{
   117  		From:    shape.AllNodes{},
   118  		Filters: filt,
   119  	})
   120  }
   121  
   122  func tagMorphism(tags ...string) morphism {
   123  	return morphism{
   124  		IsTag:    true,
   125  		Reversal: func(ctx *pathContext) (morphism, *pathContext) { return tagMorphism(tags...), ctx },
   126  		Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
   127  			return shape.Save{From: in, Tags: tags}, ctx
   128  		},
   129  		tags: tags,
   130  	}
   131  }
   132  
   133  // outMorphism iterates forward one RDF triple or via an entire path.
   134  func outMorphism(tags []string, via ...interface{}) morphism {
   135  	return morphism{
   136  		Reversal: func(ctx *pathContext) (morphism, *pathContext) { return inMorphism(tags, via...), ctx },
   137  		Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
   138  			return shape.Out(in, buildVia(via...), ctx.labelSet, tags...), ctx
   139  		},
   140  		tags: tags,
   141  	}
   142  }
   143  
   144  // inMorphism iterates backwards one RDF triple or via an entire path.
   145  func inMorphism(tags []string, via ...interface{}) morphism {
   146  	return morphism{
   147  		Reversal: func(ctx *pathContext) (morphism, *pathContext) { return outMorphism(tags, via...), ctx },
   148  		Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
   149  			return shape.In(in, buildVia(via...), ctx.labelSet, tags...), ctx
   150  		},
   151  		tags: tags,
   152  	}
   153  }
   154  
   155  func bothMorphism(tags []string, via ...interface{}) morphism {
   156  	return morphism{
   157  		Reversal: func(ctx *pathContext) (morphism, *pathContext) { return bothMorphism(tags, via...), ctx },
   158  		Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
   159  			via := buildVia(via...)
   160  			return shape.Union{
   161  				shape.In(in, via, ctx.labelSet, tags...),
   162  				shape.Out(in, via, ctx.labelSet, tags...),
   163  			}, ctx
   164  		},
   165  		tags: tags,
   166  	}
   167  }
   168  
   169  func labelContextMorphism(tags []string, via ...interface{}) morphism {
   170  	var path shape.Shape
   171  	if len(via) == 0 {
   172  		path = nil
   173  	} else {
   174  		path = shape.Save{From: buildVia(via...), Tags: tags}
   175  	}
   176  	return morphism{
   177  		Reversal: func(ctx *pathContext) (morphism, *pathContext) {
   178  			out := ctx.copy()
   179  			ctx.labelSet = path
   180  			return labelContextMorphism(tags, via...), &out
   181  		},
   182  		Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
   183  			out := ctx.copy()
   184  			out.labelSet = path
   185  			return in, &out
   186  		},
   187  		tags: tags,
   188  	}
   189  }
   190  
   191  // labelsMorphism iterates to the uniqified set of labels from
   192  // the given set of nodes in the path.
   193  func labelsMorphism() morphism {
   194  	return morphism{
   195  		Reversal: func(ctx *pathContext) (morphism, *pathContext) {
   196  			panic("not implemented")
   197  		},
   198  		Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
   199  			return shape.Labels(in), ctx
   200  		},
   201  	}
   202  }
   203  
   204  // predicatesMorphism iterates to the uniqified set of predicates from
   205  // the given set of nodes in the path.
   206  func predicatesMorphism(isIn bool) morphism {
   207  	return morphism{
   208  		Reversal: func(ctx *pathContext) (morphism, *pathContext) {
   209  			panic("not implemented: need a function from predicates to their associated edges")
   210  		},
   211  		Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
   212  			return shape.Predicates(in, isIn), ctx
   213  		},
   214  	}
   215  }
   216  
   217  // savePredicatesMorphism tags either forward or reverse predicates from current node
   218  // without affecting path.
   219  func savePredicatesMorphism(isIn bool, tag string) morphism {
   220  	return morphism{
   221  		Reversal: func(ctx *pathContext) (morphism, *pathContext) {
   222  			return savePredicatesMorphism(isIn, tag), ctx
   223  		},
   224  		Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
   225  			return shape.SavePredicates(in, isIn, tag), ctx
   226  		},
   227  	}
   228  }
   229  
   230  type iteratorShape struct {
   231  	it   graph.Iterator
   232  	sent bool
   233  }
   234  
   235  func (s *iteratorShape) BuildIterator(qs graph.QuadStore) graph.Iterator {
   236  	if s.sent {
   237  		return iterator.NewError(fmt.Errorf("iterator already used in query"))
   238  	}
   239  	it := s.it
   240  	s.it, s.sent = nil, true
   241  	return it
   242  }
   243  func (s *iteratorShape) Optimize(r shape.Optimizer) (shape.Shape, bool) {
   244  	return s, false
   245  }
   246  
   247  // iteratorMorphism simply tacks the input iterator onto the chain.
   248  func iteratorMorphism(it graph.Iterator) morphism {
   249  	return morphism{
   250  		Reversal: func(ctx *pathContext) (morphism, *pathContext) { return iteratorMorphism(it), ctx },
   251  		Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
   252  			return join(&iteratorShape{it: it}, in), ctx
   253  		},
   254  	}
   255  }
   256  
   257  // andMorphism sticks a path onto the current iterator chain.
   258  func andMorphism(p *Path) morphism {
   259  	return morphism{
   260  		Reversal: func(ctx *pathContext) (morphism, *pathContext) { return andMorphism(p), ctx },
   261  		Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
   262  			return join(in, p.Shape()), ctx
   263  		},
   264  	}
   265  }
   266  
   267  // orMorphism is the union, vice intersection, of a path and the current iterator.
   268  func orMorphism(p *Path) morphism {
   269  	return morphism{
   270  		Reversal: func(ctx *pathContext) (morphism, *pathContext) { return orMorphism(p), ctx },
   271  		Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
   272  			return shape.Union{in, p.Shape()}, ctx
   273  		},
   274  	}
   275  }
   276  
   277  func followMorphism(p *Path) morphism {
   278  	return morphism{
   279  		Reversal: func(ctx *pathContext) (morphism, *pathContext) { return followMorphism(p.Reverse()), ctx },
   280  		Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
   281  			return p.ShapeFrom(in), ctx
   282  		},
   283  	}
   284  }
   285  
   286  type iteratorBuilder func(qs graph.QuadStore) graph.Iterator
   287  
   288  func (s iteratorBuilder) BuildIterator(qs graph.QuadStore) graph.Iterator {
   289  	return s(qs)
   290  }
   291  func (s iteratorBuilder) Optimize(r shape.Optimizer) (shape.Shape, bool) {
   292  	return s, false
   293  }
   294  
   295  func followRecursiveMorphism(p *Path, maxDepth int, depthTags []string) morphism {
   296  	return morphism{
   297  		Reversal: func(ctx *pathContext) (morphism, *pathContext) {
   298  			return followRecursiveMorphism(p.Reverse(), maxDepth, depthTags), ctx
   299  		},
   300  		Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
   301  			return iteratorBuilder(func(qs graph.QuadStore) graph.Iterator {
   302  				in := in.BuildIterator(qs)
   303  				it := iterator.NewRecursive(in, p.MorphismFor(qs), maxDepth)
   304  				for _, s := range depthTags {
   305  					it.AddDepthTag(s)
   306  				}
   307  				return it
   308  			}), ctx
   309  		},
   310  	}
   311  }
   312  
   313  // exceptMorphism removes all results on p.(*Path) from the current iterators.
   314  func exceptMorphism(p *Path) morphism {
   315  	return morphism{
   316  		Reversal: func(ctx *pathContext) (morphism, *pathContext) { return exceptMorphism(p), ctx },
   317  		Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
   318  			return join(in, shape.Except{From: shape.AllNodes{}, Exclude: p.Shape()}), ctx
   319  		},
   320  	}
   321  }
   322  
   323  // uniqueMorphism removes duplicate values from current path.
   324  func uniqueMorphism() morphism {
   325  	return morphism{
   326  		Reversal: func(ctx *pathContext) (morphism, *pathContext) { return uniqueMorphism(), ctx },
   327  		Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
   328  			return shape.Unique{in}, ctx
   329  		},
   330  	}
   331  }
   332  
   333  func saveMorphism(via interface{}, tag string) morphism {
   334  	return morphism{
   335  		Reversal: func(ctx *pathContext) (morphism, *pathContext) { return saveMorphism(via, tag), ctx },
   336  		Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
   337  			return shape.SaveViaLabels(in, buildVia(via), ctx.labelSet, tag, false, false), ctx
   338  		},
   339  		tags: []string{tag},
   340  	}
   341  }
   342  
   343  func saveReverseMorphism(via interface{}, tag string) morphism {
   344  	return morphism{
   345  		Reversal: func(ctx *pathContext) (morphism, *pathContext) { return saveReverseMorphism(via, tag), ctx },
   346  		Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
   347  			return shape.SaveViaLabels(in, buildVia(via), ctx.labelSet, tag, true, false), ctx
   348  		},
   349  		tags: []string{tag},
   350  	}
   351  }
   352  
   353  func saveOptionalMorphism(via interface{}, tag string) morphism {
   354  	return morphism{
   355  		Reversal: func(ctx *pathContext) (morphism, *pathContext) { return saveOptionalMorphism(via, tag), ctx },
   356  		Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
   357  			return shape.SaveViaLabels(in, buildVia(via), ctx.labelSet, tag, false, true), ctx
   358  		},
   359  		tags: []string{tag},
   360  	}
   361  }
   362  
   363  func saveOptionalReverseMorphism(via interface{}, tag string) morphism {
   364  	return morphism{
   365  		Reversal: func(ctx *pathContext) (morphism, *pathContext) { return saveOptionalReverseMorphism(via, tag), ctx },
   366  		Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
   367  			return shape.SaveViaLabels(in, buildVia(via), ctx.labelSet, tag, true, true), ctx
   368  		},
   369  		tags: []string{tag},
   370  	}
   371  }
   372  
   373  func buildVia(via ...interface{}) shape.Shape {
   374  	if len(via) == 0 {
   375  		return shape.AllNodes{}
   376  	} else if len(via) == 1 {
   377  		v := via[0]
   378  		switch p := v.(type) {
   379  		case nil:
   380  			return shape.AllNodes{}
   381  		case *Path:
   382  			return p.Shape()
   383  		case quad.Value:
   384  			return shape.Lookup{p}
   385  		case []quad.Value:
   386  			return shape.Lookup(p)
   387  		}
   388  	}
   389  	nodes := make([]quad.Value, 0, len(via))
   390  	for _, v := range via {
   391  		qv, ok := quad.AsValue(v)
   392  		if !ok {
   393  			panic(fmt.Errorf("Invalid type passed to buildViaPath: %v (%T)", v, v))
   394  		}
   395  		nodes = append(nodes, qv)
   396  	}
   397  	return shape.Lookup(nodes)
   398  }
   399  
   400  // skipMorphism will skip a number of values-- if there are none, this function
   401  // acts as a passthrough for the previous iterator.
   402  func skipMorphism(v int64) morphism {
   403  	return morphism{
   404  		Reversal: func(ctx *pathContext) (morphism, *pathContext) { return skipMorphism(v), ctx },
   405  		Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
   406  			if v == 0 {
   407  				// Acting as a passthrough
   408  				return in, ctx
   409  			}
   410  			return shape.Page{From: in, Skip: v}, ctx
   411  		},
   412  	}
   413  }
   414  
   415  func orderMorphism() morphism {
   416  	return morphism{
   417  		Reversal: func(ctx *pathContext) (morphism, *pathContext) { return orderMorphism(), ctx },
   418  		Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
   419  			return shape.Sort{From: in}, ctx
   420  		},
   421  	}
   422  }
   423  
   424  // limitMorphism will limit a number of values-- if number is negative or zero, this function
   425  // acts as a passthrough for the previous iterator.
   426  func limitMorphism(v int64) morphism {
   427  	return morphism{
   428  		Reversal: func(ctx *pathContext) (morphism, *pathContext) { return limitMorphism(v), ctx },
   429  		Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
   430  			if v <= 0 {
   431  				// Acting as a passthrough
   432  				return in, ctx
   433  			}
   434  			return shape.Page{From: in, Limit: v}, ctx
   435  		},
   436  	}
   437  }
   438  
   439  // countMorphism will return count of values.
   440  func countMorphism() morphism {
   441  	return morphism{
   442  		Reversal: func(ctx *pathContext) (morphism, *pathContext) { return countMorphism(), ctx },
   443  		Apply: func(in shape.Shape, ctx *pathContext) (shape.Shape, *pathContext) {
   444  			return shape.Count{Values: in}, ctx
   445  		},
   446  	}
   447  }