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 }