github.com/cayleygraph/cayley@v0.7.7/graph/path/path.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  	"context"
    19  	"regexp"
    20  
    21  	"github.com/cayleygraph/cayley/graph"
    22  	"github.com/cayleygraph/cayley/graph/iterator"
    23  	"github.com/cayleygraph/cayley/graph/shape"
    24  	"github.com/cayleygraph/quad"
    25  )
    26  
    27  type applyMorphism func(shape.Shape, *pathContext) (shape.Shape, *pathContext)
    28  
    29  type morphism struct {
    30  	IsTag    bool
    31  	Reversal func(*pathContext) (morphism, *pathContext)
    32  	Apply    applyMorphism
    33  	tags     []string
    34  }
    35  
    36  // pathContext allows a high-level change to the way paths are constructed. Some
    37  // functions may change the context, causing following chained calls to act
    38  // differently.
    39  //
    40  // In a sense, this is a global state which can be changed as the path
    41  // continues. And as with dealing with any global state, care should be taken:
    42  //
    43  // When modifying the context in Apply(), please copy the passed struct,
    44  // modifying the relevant fields if need be (or pass the given context onward).
    45  //
    46  // Under Reversal(), any functions that wish to change the context should
    47  // appropriately change the passed context (that is, the context that came after
    48  // them will now be what the application of the function would have been) and
    49  // then yield a pointer to their own member context as the return value.
    50  //
    51  // For more examples, look at the morphisms which claim the individual fields.
    52  type pathContext struct {
    53  	// TODO(dennwc): replace with net/context?
    54  
    55  	// Represents the path to the limiting set of labels that should be considered under traversal.
    56  	// inMorphism, outMorphism, et al should constrain edges by this set.
    57  	// A nil in this field represents all labels.
    58  	//
    59  	// Claimed by the withLabel morphism
    60  	labelSet shape.Shape
    61  }
    62  
    63  func (c pathContext) copy() pathContext {
    64  	return pathContext{
    65  		labelSet: c.labelSet,
    66  	}
    67  }
    68  
    69  // Path represents either a morphism (a pre-defined path stored for later use),
    70  // or a concrete path, consisting of a morphism and an underlying QuadStore.
    71  type Path struct {
    72  	stack       []morphism
    73  	qs          graph.QuadStore // Optionally. A nil qs is equivalent to a morphism.
    74  	baseContext pathContext
    75  }
    76  
    77  // IsMorphism returns whether this Path is a morphism.
    78  func (p *Path) IsMorphism() bool { return p.qs == nil }
    79  
    80  // StartMorphism creates a new Path with no underlying QuadStore.
    81  func StartMorphism(nodes ...quad.Value) *Path {
    82  	return StartPath(nil, nodes...)
    83  }
    84  
    85  func newPath(qs graph.QuadStore, m ...morphism) *Path {
    86  	qs = graph.Unwrap(qs)
    87  	return &Path{
    88  		stack: m,
    89  		qs:    qs,
    90  	}
    91  }
    92  
    93  // StartPath creates a new Path from a set of nodes and an underlying QuadStore.
    94  func StartPath(qs graph.QuadStore, nodes ...quad.Value) *Path {
    95  	return newPath(qs, isMorphism(nodes...))
    96  }
    97  
    98  // StartPathNodes creates a new Path from a set of nodes and an underlying QuadStore.
    99  func StartPathNodes(qs graph.QuadStore, nodes ...graph.Ref) *Path {
   100  	return newPath(qs, isNodeMorphism(nodes...))
   101  }
   102  
   103  // PathFromIterator creates a new Path from a set of nodes contained in iterator.
   104  func PathFromIterator(qs graph.QuadStore, it graph.Iterator) *Path {
   105  	return newPath(qs, iteratorMorphism(it))
   106  }
   107  
   108  // NewPath creates a new, empty Path.
   109  func NewPath(qs graph.QuadStore) *Path {
   110  	return newPath(qs)
   111  }
   112  
   113  // Clone returns a clone of the current path.
   114  func (p *Path) Clone() *Path {
   115  	stack := p.stack
   116  	return &Path{
   117  		stack:       stack[:len(stack):len(stack)],
   118  		qs:          p.qs,
   119  		baseContext: p.baseContext,
   120  	}
   121  }
   122  
   123  // Unexported clone method returns a *Path with a copy of the original stack,
   124  // with assumption that the new stack will be appended to.
   125  func (p *Path) clone() *Path {
   126  	stack := p.stack
   127  	p.stack = stack[:len(stack):len(stack)]
   128  	return &Path{
   129  		stack:       stack,
   130  		qs:          p.qs,
   131  		baseContext: p.baseContext,
   132  	}
   133  }
   134  
   135  // Reverse returns a new Path that is the reverse of the current one.
   136  func (p *Path) Reverse() *Path {
   137  	newPath := NewPath(p.qs)
   138  	ctx := &newPath.baseContext
   139  	for i := len(p.stack) - 1; i >= 0; i-- {
   140  		var revMorphism morphism
   141  		revMorphism, ctx = p.stack[i].Reversal(ctx)
   142  		newPath.stack = append(newPath.stack, revMorphism)
   143  	}
   144  	return newPath
   145  }
   146  
   147  // Is declares that the current nodes in this path are only the nodes
   148  // passed as arguments.
   149  func (p *Path) Is(nodes ...quad.Value) *Path {
   150  	np := p.clone()
   151  	np.stack = append(np.stack, isMorphism(nodes...))
   152  	return np
   153  }
   154  
   155  // Regex represents the nodes that are matching provided regexp pattern.
   156  // It will only include Raw and String values.
   157  func (p *Path) Regex(pattern *regexp.Regexp) *Path {
   158  	return p.Filters(shape.Regexp{Re: pattern, Refs: false})
   159  }
   160  
   161  // RegexWithRefs is the same as Regex, but also matches IRIs and BNodes.
   162  //
   163  // Consider using it carefully. In most cases it's better to reconsider
   164  // your graph structure instead of relying on slow unoptimizable regexp.
   165  //
   166  // An example of incorrect usage is to match IRIs:
   167  // 	<http://example.org/page>
   168  // 	<http://example.org/page/foo>
   169  // Via regexp like:
   170  //	http://example.org/page.*
   171  //
   172  // The right way is to explicitly link graph nodes and query them by this relation:
   173  // 	<http://example.org/page/foo> <type> <http://example.org/page>
   174  func (p *Path) RegexWithRefs(pattern *regexp.Regexp) *Path {
   175  	return p.Filters(shape.Regexp{Re: pattern, Refs: true})
   176  }
   177  
   178  // Filter represents the nodes that are passing comparison with provided value.
   179  func (p *Path) Filter(op iterator.Operator, node quad.Value) *Path {
   180  	return p.Filters(shape.Comparison{Op: op, Val: node})
   181  }
   182  
   183  // Filters represents the nodes that are passing provided filters.
   184  func (p *Path) Filters(filters ...shape.ValueFilter) *Path {
   185  	np := p.clone()
   186  	np.stack = append(np.stack, filterMorphism(filters))
   187  	return np
   188  }
   189  
   190  // Tag adds tag strings to the nodes at this point in the path for each result
   191  // path in the set.
   192  func (p *Path) Tag(tags ...string) *Path {
   193  	np := p.clone()
   194  	np.stack = append(np.stack, tagMorphism(tags...))
   195  	return np
   196  }
   197  
   198  // Out updates this Path to represent the nodes that are adjacent to the
   199  // current nodes, via the given outbound predicate.
   200  //
   201  // For example:
   202  //  // Returns the list of nodes that "B" follows.
   203  //  //
   204  //  // Will return []string{"F"} if there is a predicate (edge) from "B"
   205  //  // to "F" labelled "follows".
   206  //  StartPath(qs, "A").Out("follows")
   207  func (p *Path) Out(via ...interface{}) *Path {
   208  	np := p.clone()
   209  	np.stack = append(np.stack, outMorphism(nil, via...))
   210  	return np
   211  }
   212  
   213  // In updates this Path to represent the nodes that are adjacent to the
   214  // current nodes, via the given inbound predicate.
   215  //
   216  // For example:
   217  //  // Return the list of nodes that follow "B".
   218  //  //
   219  //  // Will return []string{"A", "C", "D"} if there are the appropriate
   220  //  // edges from those nodes to "B" labelled "follows".
   221  //  StartPath(qs, "B").In("follows")
   222  func (p *Path) In(via ...interface{}) *Path {
   223  	np := p.clone()
   224  	np.stack = append(np.stack, inMorphism(nil, via...))
   225  	return np
   226  }
   227  
   228  // InWithTags is exactly like In, except it tags the value of the predicate
   229  // traversed with the tags provided.
   230  func (p *Path) InWithTags(tags []string, via ...interface{}) *Path {
   231  	np := p.clone()
   232  	np.stack = append(np.stack, inMorphism(tags, via...))
   233  	return np
   234  }
   235  
   236  // OutWithTags is exactly like In, except it tags the value of the predicate
   237  // traversed with the tags provided.
   238  func (p *Path) OutWithTags(tags []string, via ...interface{}) *Path {
   239  	np := p.clone()
   240  	np.stack = append(np.stack, outMorphism(tags, via...))
   241  	return np
   242  }
   243  
   244  // Both updates this path following both inbound and outbound predicates.
   245  //
   246  // For example:
   247  //  // Return the list of nodes that follow or are followed by "B".
   248  //  //
   249  //  // Will return []string{"A", "C", "D", "F} if there are the appropriate
   250  //  // edges from those nodes to "B" labelled "follows", in either direction.
   251  //  StartPath(qs, "B").Both("follows")
   252  func (p *Path) Both(via ...interface{}) *Path {
   253  	np := p.clone()
   254  	np.stack = append(np.stack, bothMorphism(nil, via...))
   255  	return np
   256  }
   257  
   258  // BothWithTags is exactly like Both, except it tags the value of the predicate
   259  // traversed with the tags provided.
   260  func (p *Path) BothWithTags(tags []string, via ...interface{}) *Path {
   261  	np := p.clone()
   262  	np.stack = append(np.stack, bothMorphism(tags, via...))
   263  	return np
   264  }
   265  
   266  // Labels updates this path to represent the nodes of the labels
   267  // of inbound and outbound quads.
   268  func (p *Path) Labels() *Path {
   269  	np := p.clone()
   270  	np.stack = append(np.stack, labelsMorphism())
   271  	return np
   272  }
   273  
   274  // InPredicates updates this path to represent the nodes of the valid inbound
   275  // predicates from the current nodes.
   276  //
   277  // For example:
   278  //  // Returns a list of predicates valid from "bob"
   279  //  //
   280  //  // Will return []string{"follows"} if there are any things that "follow" Bob
   281  //  StartPath(qs, "bob").InPredicates()
   282  func (p *Path) InPredicates() *Path {
   283  	np := p.clone()
   284  	np.stack = append(np.stack, predicatesMorphism(true))
   285  	return np
   286  }
   287  
   288  // OutPredicates updates this path to represent the nodes of the valid inbound
   289  // predicates from the current nodes.
   290  //
   291  // For example:
   292  //  // Returns a list of predicates valid from "bob"
   293  //  //
   294  //  // Will return []string{"follows", "status"} if there are edges from "bob"
   295  //  // labelled "follows", and edges from "bob" that describe his "status".
   296  //  StartPath(qs, "bob").OutPredicates()
   297  func (p *Path) OutPredicates() *Path {
   298  	np := p.clone()
   299  	np.stack = append(np.stack, predicatesMorphism(false))
   300  	return np
   301  }
   302  
   303  // SavePredicates saves either forward or reverse predicates of current node
   304  // without changing path location.
   305  func (p *Path) SavePredicates(rev bool, tag string) *Path {
   306  	np := p.clone()
   307  	np.stack = append(np.stack, savePredicatesMorphism(rev, tag))
   308  	return np
   309  }
   310  
   311  // And updates the current Path to represent the nodes that match both the
   312  // current Path so far, and the given Path.
   313  func (p *Path) And(path *Path) *Path {
   314  	np := p.clone()
   315  	np.stack = append(np.stack, andMorphism(path))
   316  	return np
   317  }
   318  
   319  // Or updates the current Path to represent the nodes that match either the
   320  // current Path so far, or the given Path.
   321  func (p *Path) Or(path *Path) *Path {
   322  	np := p.clone()
   323  	np.stack = append(np.stack, orMorphism(path))
   324  	return np
   325  }
   326  
   327  // Except updates the current Path to represent the all of the current nodes
   328  // except those in the supplied Path.
   329  //
   330  // For example:
   331  //  // Will return []string{"B"}
   332  //  StartPath(qs, "A", "B").Except(StartPath(qs, "A"))
   333  func (p *Path) Except(path *Path) *Path {
   334  	np := p.clone()
   335  	np.stack = append(np.stack, exceptMorphism(path))
   336  	return np
   337  }
   338  
   339  // Unique updates the current Path to contain only unique nodes.
   340  func (p *Path) Unique() *Path {
   341  	np := p.clone()
   342  	np.stack = append(np.stack, uniqueMorphism())
   343  	return np
   344  }
   345  
   346  // Follow allows you to stitch two paths together. The resulting path will start
   347  // from where the first path left off and continue iterating down the path given.
   348  func (p *Path) Follow(path *Path) *Path {
   349  	np := p.clone()
   350  	np.stack = append(np.stack, followMorphism(path))
   351  	return np
   352  }
   353  
   354  // FollowReverse is the same as follow, except it will iterate backwards up the
   355  // path given as argument.
   356  func (p *Path) FollowReverse(path *Path) *Path {
   357  	np := p.clone()
   358  	np.stack = append(np.stack, followMorphism(path.Reverse()))
   359  	return np
   360  }
   361  
   362  // FollowRecursive will repeatedly follow the given string predicate or Path
   363  // object starting from the given node(s), through the morphism or pattern
   364  // provided, ignoring loops. For example, this turns "parent" into "all
   365  // ancestors", by repeatedly following the "parent" connection on the result of
   366  // the parent nodes.
   367  //
   368  // The second argument, "maxDepth" is the maximum number of recursive steps before
   369  // stopping and returning.
   370  // If -1 is passed, it will have no limit.
   371  // If 0 is passed, it will use the default value of 50 steps before returning.
   372  // If 1 is passed, it will stop after 1 step before returning, and so on.
   373  //
   374  // The third argument, "depthTags" is a set of tags that will return strings of
   375  // numeric values relating to how many applications of the path were applied the
   376  // first time the result node was seen.
   377  //
   378  // This is a very expensive operation in practice. Be sure to use it wisely.
   379  func (p *Path) FollowRecursive(via interface{}, maxDepth int, depthTags []string) *Path {
   380  	var path *Path
   381  	switch v := via.(type) {
   382  	case string:
   383  		path = StartMorphism().Out(v)
   384  	case quad.Value:
   385  		path = StartMorphism().Out(v)
   386  	case *Path:
   387  		path = v
   388  	default:
   389  		panic("did not pass a string predicate or a Path to FollowRecursive")
   390  	}
   391  	np := p.clone()
   392  	np.stack = append(p.stack, followRecursiveMorphism(path, maxDepth, depthTags))
   393  	return np
   394  }
   395  
   396  // Save will, from the current nodes in the path, retrieve the node
   397  // one linkage away (given by either a path or a predicate), add the given
   398  // tag, and propagate that to the result set.
   399  //
   400  // For example:
   401  //  // Will return []map[string]string{{"social_status: "cool"}}
   402  //  StartPath(qs, "B").Save("status", "social_status"
   403  func (p *Path) Save(via interface{}, tag string) *Path {
   404  	np := p.clone()
   405  	np.stack = append(np.stack, saveMorphism(via, tag))
   406  	return np
   407  }
   408  
   409  // SaveReverse is the same as Save, only in the reverse direction
   410  // (the subject of the linkage should be tagged, instead of the object).
   411  func (p *Path) SaveReverse(via interface{}, tag string) *Path {
   412  	np := p.clone()
   413  	np.stack = append(np.stack, saveReverseMorphism(via, tag))
   414  	return np
   415  }
   416  
   417  // SaveOptional is the same as Save, but does not require linkage to exist.
   418  func (p *Path) SaveOptional(via interface{}, tag string) *Path {
   419  	np := p.clone()
   420  	np.stack = append(np.stack, saveOptionalMorphism(via, tag))
   421  	return np
   422  }
   423  
   424  // SaveOptionalReverse is the same as SaveReverse, but does not require linkage to exist.
   425  func (p *Path) SaveOptionalReverse(via interface{}, tag string) *Path {
   426  	np := p.clone()
   427  	np.stack = append(np.stack, saveOptionalReverseMorphism(via, tag))
   428  	return np
   429  }
   430  
   431  // Has limits the paths to be ones where the current nodes have some linkage
   432  // to some known node.
   433  func (p *Path) Has(via interface{}, nodes ...quad.Value) *Path {
   434  	np := p.clone()
   435  	np.stack = append(np.stack, hasMorphism(via, false, nodes...))
   436  	return np
   437  }
   438  
   439  // HasReverse limits the paths to be ones where some known node have some linkage
   440  // to the current nodes.
   441  func (p *Path) HasReverse(via interface{}, nodes ...quad.Value) *Path {
   442  	np := p.clone()
   443  	np.stack = append(np.stack, hasMorphism(via, true, nodes...))
   444  	return np
   445  }
   446  
   447  // HasFilter limits the paths to be ones where the current nodes have some linkage
   448  // to some nodes that pass provided filters.
   449  func (p *Path) HasFilter(via interface{}, rev bool, filt ...shape.ValueFilter) *Path {
   450  	np := p.clone()
   451  	np.stack = append(np.stack, hasFilterMorphism(via, rev, filt))
   452  	return np
   453  }
   454  
   455  // LabelContext restricts the following operations (such as In, Out) to only
   456  // traverse edges that match the given set of labels.
   457  func (p *Path) LabelContext(via ...interface{}) *Path {
   458  	np := p.clone()
   459  	np.stack = append(np.stack, labelContextMorphism(nil, via...))
   460  	return np
   461  }
   462  
   463  // LabelContextWithTags is exactly like LabelContext, except it tags the value
   464  // of the label used in the traversal with the tags provided.
   465  func (p *Path) LabelContextWithTags(tags []string, via ...interface{}) *Path {
   466  	np := p.clone()
   467  	np.stack = append(np.stack, labelContextMorphism(tags, via...))
   468  	return np
   469  }
   470  
   471  // Back returns to a previously tagged place in the path. Any constraints applied after the Tag will remain in effect, but traversal continues from the tagged point instead, not from the end of the chain.
   472  //
   473  // For example:
   474  //  // Will return "bob" iff "bob" is cool
   475  //  StartPath(qs, "bob").Tag("person_tag").Out("status").Is("cool").Back("person_tag")
   476  func (p *Path) Back(tag string) *Path {
   477  	newPath := NewPath(p.qs)
   478  	i := len(p.stack) - 1
   479  	ctx := &newPath.baseContext
   480  	for {
   481  		if i < 0 {
   482  			return p.Reverse()
   483  		}
   484  		if p.stack[i].IsTag {
   485  			for _, x := range p.stack[i].tags {
   486  				if x == tag {
   487  					// Found what we're looking for.
   488  					p.stack = p.stack[:i+1]
   489  					return p.And(newPath)
   490  				}
   491  			}
   492  		}
   493  		var revMorphism morphism
   494  		revMorphism, ctx = p.stack[i].Reversal(ctx)
   495  		newPath.stack = append(newPath.stack, revMorphism)
   496  		i--
   497  	}
   498  }
   499  
   500  // BuildIterator returns an iterator from this given Path.  Note that you must
   501  // call this with a full path (not a morphism), since a morphism does not have
   502  // the ability to fetch the underlying quads.  This function will panic if
   503  // called with a morphism (i.e. if p.IsMorphism() is true).
   504  func (p *Path) BuildIterator() graph.Iterator {
   505  	if p.IsMorphism() {
   506  		panic("Building an iterator from a morphism. Bind a QuadStore with BuildIteratorOn(qs)")
   507  	}
   508  	return p.BuildIteratorOn(p.qs)
   509  }
   510  
   511  // BuildIteratorOn will return an iterator for this path on the given QuadStore.
   512  func (p *Path) BuildIteratorOn(qs graph.QuadStore) graph.Iterator {
   513  	return shape.BuildIterator(qs, p.Shape())
   514  }
   515  
   516  // Morphism returns the morphism of this path.  The returned value is a
   517  // function that, when given a QuadStore and an existing Iterator, will
   518  // return a new Iterator that yields the subset of values from the existing
   519  // iterator matched by the current Path.
   520  func (p *Path) Morphism() graph.ApplyMorphism {
   521  	return func(qs graph.QuadStore, it graph.Iterator) graph.Iterator {
   522  		return p.ShapeFrom(&iteratorShape{it: it}).BuildIterator(qs)
   523  	}
   524  }
   525  
   526  // MorphismFor is the same as Morphism but binds returned function to a specific QuadStore.
   527  func (p *Path) MorphismFor(qs graph.QuadStore) iterator.Morphism {
   528  	return func(it graph.Iterator) graph.Iterator {
   529  		return p.ShapeFrom(&iteratorShape{it: it}).BuildIterator(qs)
   530  	}
   531  }
   532  
   533  // Skip will omit a number of values from result set.
   534  func (p *Path) Skip(v int64) *Path {
   535  	p.stack = append(p.stack, skipMorphism(v))
   536  	return p
   537  }
   538  
   539  func (p *Path) Order() *Path {
   540  	p.stack = append(p.stack, orderMorphism())
   541  	return p
   542  }
   543  
   544  // Limit will limit a number of values in result set.
   545  func (p *Path) Limit(v int64) *Path {
   546  	p.stack = append(p.stack, limitMorphism(v))
   547  	return p
   548  }
   549  
   550  // Count will count a number of results as it's own result set.
   551  func (p *Path) Count() *Path {
   552  	p.stack = append(p.stack, countMorphism())
   553  	return p
   554  }
   555  
   556  // Iterate is an shortcut for graph.Iterate.
   557  func (p *Path) Iterate(ctx context.Context) *graph.IterateChain {
   558  	return shape.Iterate(ctx, p.qs, p.Shape())
   559  }
   560  func (p *Path) Shape() shape.Shape {
   561  	return p.ShapeFrom(shape.AllNodes{})
   562  }
   563  func (p *Path) ShapeFrom(from shape.Shape) shape.Shape {
   564  	s := from
   565  	ctx := &p.baseContext
   566  	for _, m := range p.stack {
   567  		s, ctx = m.Apply(s, ctx)
   568  	}
   569  	return s
   570  }