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 }