1package shape 2 3import ( 4 "reflect" 5 "regexp" 6 "strings" 7 8 "github.com/cayleygraph/cayley/clog" 9 "github.com/cayleygraph/cayley/graph" 10 "github.com/cayleygraph/cayley/graph/iterator" 11 "github.com/cayleygraph/cayley/quad" 12) 13 14// Shape represent a query tree shape. 15type Shape interface { 16 // BuildIterator constructs an iterator tree from a given shapes and binds it to QuadStore. 17 BuildIterator(qs graph.QuadStore) graph.Iterator 18 // Optimize runs an optimization pass over a query shape. 19 // 20 // It returns a bool that indicates if shape was replaced and should always return a copy of shape in this case. 21 // In case no optimizations were made, it returns the same unmodified shape. 22 // 23 // If Optimizer is specified, it will be used instead of default optimizations. 24 Optimize(r Optimizer) (Shape, bool) 25} 26 27type Optimizer interface { 28 OptimizeShape(s Shape) (Shape, bool) 29} 30 31// Composite shape can be simplified to a tree of more basic shapes. 32type Composite interface { 33 Simplify() Shape 34} 35 36// WalkFunc is used to visit all shapes in the tree. 37// If false is returned, branch will not be traversed further. 38type WalkFunc func(Shape) bool 39 40type resolveValues struct { 41 qs graph.QuadStore 42} 43 44func (r resolveValues) OptimizeShape(s Shape) (Shape, bool) { 45 if l, ok := s.(Lookup); ok { 46 return l.resolve(r.qs), true 47 } 48 return s, false 49} 50 51// Optimize applies generic optimizations for the tree. 52// If quad store is specified it will also resolve Lookups and apply any specific optimizations. 53// Should not be used with Simplify - it will fold query to a compact form again. 54func Optimize(s Shape, qs graph.QuadStore) (Shape, bool) { 55 if s == nil { 56 return nil, false 57 } 58 qs = graph.Unwrap(qs) 59 var opt bool 60 if qs != nil { 61 // resolve all lookups earlier 62 s, opt = s.Optimize(resolveValues{qs: qs}) 63 } 64 if s == nil { 65 return Null{}, true 66 } 67 // generic optimizations 68 var opt1 bool 69 s, opt1 = s.Optimize(nil) 70 if s == nil { 71 return Null{}, true 72 } 73 opt = opt || opt1 74 // apply quadstore-specific optimizations 75 if so, ok := qs.(Optimizer); ok && s != nil { 76 var opt2 bool 77 s, opt2 = s.Optimize(so) 78 opt = opt || opt2 79 } 80 if s == nil { 81 return Null{}, true 82 } 83 return s, opt 84} 85 86var rtShape = reflect.TypeOf((*Shape)(nil)).Elem() 87 88// Walk calls provided function for each shape in the tree. 89func Walk(s Shape, fnc WalkFunc) { 90 if s == nil { 91 return 92 } 93 if !fnc(s) { 94 return 95 } 96 walkReflect(reflect.ValueOf(s), fnc) 97} 98 99func walkReflect(rv reflect.Value, fnc WalkFunc) { 100 rt := rv.Type() 101 switch rv.Kind() { 102 case reflect.Slice: 103 if rt.Elem().ConvertibleTo(rtShape) { 104 // all element are shapes - call function on each of them 105 for i := 0; i < rv.Len(); i++ { 106 Walk(rv.Index(i).Interface().(Shape), fnc) 107 } 108 } else { 109 // elements are not shapes, but might contain them 110 for i := 0; i < rv.Len(); i++ { 111 walkReflect(rv.Index(i), fnc) 112 } 113 } 114 case reflect.Map: 115 keys := rv.MapKeys() 116 if rt.Elem().ConvertibleTo(rtShape) { 117 // all element are shapes - call function on each of them 118 for _, k := range keys { 119 Walk(rv.MapIndex(k).Interface().(Shape), fnc) 120 } 121 } else { 122 // elements are not shapes, but might contain them 123 for _, k := range keys { 124 walkReflect(rv.MapIndex(k), fnc) 125 } 126 } 127 case reflect.Struct: 128 // visit all fields 129 for i := 0; i < rt.NumField(); i++ { 130 f := rt.Field(i) 131 // if field is of shape type - call function on it 132 // we skip anonymous fields because they were already visited as part of the parent 133 if !f.Anonymous && f.Type.ConvertibleTo(rtShape) { 134 Walk(rv.Field(i).Interface().(Shape), fnc) 135 continue 136 } 137 // it might be a struct/map/slice field, so we need to go deeper 138 walkReflect(rv.Field(i), fnc) 139 } 140 } 141} 142 143// InternalQuad is an internal representation of quad index in QuadStore. 144type InternalQuad struct { 145 Subject graph.Value 146 Predicate graph.Value 147 Object graph.Value 148 Label graph.Value 149} 150 151// Get returns a specified direction of the quad. 152func (q InternalQuad) Get(d quad.Direction) graph.Value { 153 switch d { 154 case quad.Subject: 155 return q.Subject 156 case quad.Predicate: 157 return q.Predicate 158 case quad.Object: 159 return q.Object 160 case quad.Label: 161 return q.Label 162 default: 163 return nil 164 } 165} 166 167// Set assigns a specified direction of the quad to a given value. 168func (q InternalQuad) Set(d quad.Direction, v graph.Value) { 169 switch d { 170 case quad.Subject: 171 q.Subject = v 172 case quad.Predicate: 173 q.Predicate = v 174 case quad.Object: 175 q.Object = v 176 case quad.Label: 177 q.Label = v 178 default: 179 panic(d) 180 } 181} 182 183// QuadIndexer is an optional interface for quad stores that keep an index of quad directions. 184// 185// It is used to optimize shapes based on stats from these indexes. 186type QuadIndexer interface { 187 // SizeOfIndex returns a size of a quad index with given constraints. 188 SizeOfIndex(c map[quad.Direction]graph.Value) (int64, bool) 189 // LookupQuadIndex finds a quad that matches a given constraint. 190 // It returns false if quad was not found, or there are multiple quads matching constraint. 191 LookupQuadIndex(c map[quad.Direction]graph.Value) (InternalQuad, bool) 192} 193 194// IsNull safely checks if shape represents an empty set. It accounts for both Null and nil. 195func IsNull(s Shape) bool { 196 _, ok := s.(Null) 197 return s == nil || ok 198} 199 200// BuildIterator optimizes the shape and builds a corresponding iterator tree. 201func BuildIterator(qs graph.QuadStore, s Shape) graph.Iterator { 202 qs = graph.Unwrap(qs) 203 if s != nil { 204 if clog.V(2) { 205 clog.Infof("shape: %#v", s) 206 } 207 s, _ = Optimize(s, qs) 208 if clog.V(2) { 209 clog.Infof("optimized: %#v", s) 210 } 211 } 212 if IsNull(s) { 213 return iterator.NewNull() 214 } 215 return s.BuildIterator(qs) 216} 217 218// Null represent an empty set. Mostly used as a safe alias for nil shape. 219type Null struct{} 220 221func (Null) BuildIterator(qs graph.QuadStore) graph.Iterator { 222 return iterator.NewNull() 223} 224func (s Null) Optimize(r Optimizer) (Shape, bool) { 225 if r != nil { 226 return r.OptimizeShape(s) 227 } 228 return nil, true 229} 230 231// AllNodes represents all nodes in QuadStore. 232type AllNodes struct{} 233 234func (s AllNodes) BuildIterator(qs graph.QuadStore) graph.Iterator { 235 return qs.NodesAllIterator() 236} 237func (s AllNodes) Optimize(r Optimizer) (Shape, bool) { 238 if r != nil { 239 return r.OptimizeShape(s) 240 } 241 return s, false 242} 243 244// Except excludes a set on nodes from a source. If source is nil, AllNodes is assumed. 245type Except struct { 246 Exclude Shape // nodes to exclude 247 From Shape // a set of all nodes to exclude from; nil means AllNodes 248} 249 250func (s Except) BuildIterator(qs graph.QuadStore) graph.Iterator { 251 var all graph.Iterator 252 if s.From != nil { 253 all = s.From.BuildIterator(qs) 254 } else { 255 all = qs.NodesAllIterator() 256 } 257 if IsNull(s.Exclude) { 258 return all 259 } 260 return iterator.NewNot(s.Exclude.BuildIterator(qs), all) 261} 262func (s Except) Optimize(r Optimizer) (Shape, bool) { 263 var opt bool 264 s.Exclude, opt = s.Exclude.Optimize(r) 265 if s.From != nil { 266 var opta bool 267 s.From, opta = s.From.Optimize(r) 268 opt = opt || opta 269 } 270 if r != nil { 271 ns, nopt := r.OptimizeShape(s) 272 return ns, opt || nopt 273 } 274 if IsNull(s.Exclude) { 275 return AllNodes{}, true 276 } else if _, ok := s.Exclude.(AllNodes); ok { 277 return nil, true 278 } 279 return s, opt 280} 281 282// ValueFilter is an interface for iterator wrappers that can filter node values. 283type ValueFilter interface { 284 BuildIterator(qs graph.QuadStore, it graph.Iterator) graph.Iterator 285} 286 287// Filter filters all values from the source using a list of operations. 288type Filter struct { 289 From Shape // source that will be filtered 290 Filters []ValueFilter // filters to apply 291} 292 293func (s Filter) BuildIterator(qs graph.QuadStore) graph.Iterator { 294 if IsNull(s.From) { 295 return iterator.NewNull() 296 } 297 it := s.From.BuildIterator(qs) 298 for _, f := range s.Filters { 299 it = f.BuildIterator(qs, it) 300 } 301 return it 302} 303func (s Filter) Optimize(r Optimizer) (Shape, bool) { 304 if IsNull(s.From) { 305 return nil, true 306 } 307 var opt bool 308 s.From, opt = s.From.Optimize(r) 309 if r != nil { 310 ns, nopt := r.OptimizeShape(s) 311 return ns, opt || nopt 312 } 313 if IsNull(s.From) { 314 return nil, true 315 } else if len(s.Filters) == 0 { 316 return s.From, true 317 } 318 return s, opt 319} 320 321var _ ValueFilter = Comparison{} 322 323// Comparison is a value filter that evaluates binary operation in reference to a fixed value. 324type Comparison struct { 325 Op iterator.Operator 326 Val quad.Value 327} 328 329func (f Comparison) BuildIterator(qs graph.QuadStore, it graph.Iterator) graph.Iterator { 330 return iterator.NewComparison(it, f.Op, f.Val, qs) 331} 332 333var _ ValueFilter = Regexp{} 334 335// Regexp filters values using regular expression. 336// 337// Since regexp patterns can not be optimized in most cases, Wildcard should be used if possible. 338type Regexp struct { 339 Re *regexp.Regexp 340 Refs bool // allow to match IRIs 341} 342 343func (f Regexp) BuildIterator(qs graph.QuadStore, it graph.Iterator) graph.Iterator { 344 rit := iterator.NewRegex(it, f.Re, qs) 345 rit.AllowRefs(f.Refs) 346 return rit 347} 348 349var _ ValueFilter = Wildcard{} 350 351// Wildcard is a filter for string patterns. 352// 353// % - zero or more characters 354// ? - exactly one character 355type Wildcard struct { 356 Pattern string // allowed wildcards are: % and ? 357} 358 359// Regexp returns an analog regexp pattern in format accepted by Go stdlib (RE2). 360func (f Wildcard) Regexp() string { 361 const any = `%` 362 // escape all meta-characters in pattern string 363 pattern := regexp.QuoteMeta(f.Pattern) 364 // if the pattern is anchored, add regexp analog for it 365 if !strings.HasPrefix(pattern, any) { 366 pattern = "^" + pattern 367 } else { 368 pattern = strings.TrimPrefix(pattern, any) 369 } 370 if !strings.HasSuffix(pattern, any) { 371 pattern = pattern + "$" 372 } else { 373 pattern = strings.TrimSuffix(pattern, any) 374 } 375 // replace wildcards 376 pattern = strings.NewReplacer( 377 any, `.*`, 378 `\?`, `.`, 379 ).Replace(pattern) 380 return pattern 381} 382 383func (f Wildcard) BuildIterator(qs graph.QuadStore, it graph.Iterator) graph.Iterator { 384 if f.Pattern == "" { 385 return iterator.NewNull() 386 } else if strings.Trim(f.Pattern, "%") == "" { 387 return it 388 } 389 re, err := regexp.Compile(f.Regexp()) 390 if err != nil { 391 return iterator.NewError(err) 392 } 393 rit := iterator.NewRegex(it, re, qs) 394 rit.AllowRefs(true) 395 return rit 396} 397 398// Count returns a count of objects in source as a single value. It always returns exactly one value. 399type Count struct { 400 Values Shape 401} 402 403func (s Count) BuildIterator(qs graph.QuadStore) graph.Iterator { 404 var it graph.Iterator 405 if IsNull(s.Values) { 406 it = iterator.NewNull() 407 } else { 408 it = s.Values.BuildIterator(qs) 409 } 410 return iterator.NewCount(it, qs) 411} 412func (s Count) Optimize(r Optimizer) (Shape, bool) { 413 if IsNull(s.Values) { 414 return Fixed{graph.PreFetched(quad.Int(0))}, true 415 } 416 var opt bool 417 s.Values, opt = s.Values.Optimize(r) 418 if IsNull(s.Values) { 419 return Fixed{graph.PreFetched(quad.Int(0))}, true 420 } 421 if r != nil { 422 ns, nopt := r.OptimizeShape(s) 423 return ns, opt || nopt 424 } 425 // TODO: ask QS to estimate size - if it exact, then we can use it 426 return s, opt 427} 428 429// QuadFilter is a constraint used to filter quads that have a certain set of values on a given direction. 430// Analog of LinksTo iterator. 431type QuadFilter struct { 432 Dir quad.Direction 433 Values Shape 434} 435 436// buildIterator is not exposed to force to use Quads and group filters together. 437func (s QuadFilter) buildIterator(qs graph.QuadStore) graph.Iterator { 438 if s.Values == nil { 439 return iterator.NewNull() 440 } else if v, ok := One(s.Values); ok { 441 return qs.QuadIterator(s.Dir, v) 442 } 443 if s.Dir == quad.Any { 444 panic("direction is not set") 445 } 446 sub := s.Values.BuildIterator(qs) 447 return iterator.NewLinksTo(qs, sub, s.Dir) 448} 449 450// Quads is a selector of quads with a given set of node constraints. Empty or nil Quads is equivalent to AllQuads. 451// Equivalent to And(AllQuads,LinksTo*) iterator tree. 452type Quads []QuadFilter 453 454func (s *Quads) Intersect(q ...QuadFilter) { 455 *s = append(*s, q...) 456} 457func (s Quads) BuildIterator(qs graph.QuadStore) graph.Iterator { 458 if len(s) == 0 { 459 return qs.QuadsAllIterator() 460 } 461 its := make([]graph.Iterator, 0, len(s)) 462 for _, f := range s { 463 its = append(its, f.buildIterator(qs)) 464 } 465 if len(its) == 1 { 466 return its[0] 467 } 468 return iterator.NewAnd(qs, its...) 469} 470func (s Quads) Optimize(r Optimizer) (Shape, bool) { 471 var opt bool 472 sw := 0 473 realloc := func() { 474 if !opt { 475 opt = true 476 nq := make(Quads, len(s)) 477 copy(nq, s) 478 s = nq 479 } 480 } 481 // TODO: multiple constraints on the same dir -> merge as Intersect on Values of this dir 482 for i := 0; i < len(s); i++ { 483 f := s[i] 484 if f.Values == nil { 485 return nil, true 486 } 487 v, ok := f.Values.Optimize(r) 488 if v == nil { 489 return nil, true 490 } 491 if ok { 492 realloc() 493 s[i].Values = v 494 } 495 switch s[i].Values.(type) { 496 case Fixed: 497 realloc() 498 s[sw], s[i] = s[i], s[sw] 499 sw++ 500 } 501 } 502 if r != nil { 503 ns, nopt := r.OptimizeShape(s) 504 return ns, opt || nopt 505 } 506 return s, opt 507} 508 509// NodesFrom extracts nodes on a given direction from source quads. Similar to HasA iterator. 510type NodesFrom struct { 511 Dir quad.Direction 512 Quads Shape 513} 514 515func (s NodesFrom) BuildIterator(qs graph.QuadStore) graph.Iterator { 516 if IsNull(s.Quads) { 517 return iterator.NewNull() 518 } 519 sub := s.Quads.BuildIterator(qs) 520 if s.Dir == quad.Any { 521 panic("direction is not set") 522 } 523 return iterator.NewHasA(qs, sub, s.Dir) 524} 525func (s NodesFrom) Optimize(r Optimizer) (Shape, bool) { 526 if IsNull(s.Quads) { 527 return nil, true 528 } 529 var opt bool 530 s.Quads, opt = s.Quads.Optimize(r) 531 if r != nil { 532 // ignore default optimizations 533 ns, nopt := r.OptimizeShape(s) 534 return ns, opt || nopt 535 } 536 q, ok := s.Quads.(Quads) 537 if !ok { 538 return s, opt 539 } 540 // HasA(x, LinksTo(x, y)) == y 541 if len(q) == 1 && q[0].Dir == s.Dir { 542 return q[0].Values, true 543 } 544 // collect all fixed tags and push them up the tree 545 var ( 546 tags map[string]graph.Value 547 nquad Quads 548 ) 549 for i, f := range q { 550 if ft, ok := f.Values.(FixedTags); ok { 551 if tags == nil { 552 // allocate map and clone quad filters 553 tags = make(map[string]graph.Value) 554 nquad = make([]QuadFilter, len(q)) 555 copy(nquad, q) 556 q = nquad 557 } 558 q[i].Values = ft.On 559 for k, v := range ft.Tags { 560 tags[k] = v 561 } 562 } 563 } 564 if tags != nil { 565 // re-run optimization without fixed tags 566 ns, _ := NodesFrom{Dir: s.Dir, Quads: q}.Optimize(r) 567 return FixedTags{On: ns, Tags: tags}, true 568 } 569 var ( 570 // if quad filter contains one fixed value, it will be added to the map 571 filt map[quad.Direction]graph.Value 572 // if we see a Save from AllNodes, we will write it here, since it's a Save on quad direction 573 save map[quad.Direction][]string 574 // how many filters are recognized 575 n int 576 ) 577 for _, f := range q { 578 if v, ok := One(f.Values); ok { 579 if filt == nil { 580 filt = make(map[quad.Direction]graph.Value) 581 } 582 if _, ok := filt[f.Dir]; ok { 583 return s, opt // just to be safe 584 } 585 filt[f.Dir] = v 586 n++ 587 } else if sv, ok := f.Values.(Save); ok { 588 if _, ok = sv.From.(AllNodes); ok { 589 if save == nil { 590 save = make(map[quad.Direction][]string) 591 } 592 save[f.Dir] = append(save[f.Dir], sv.Tags...) 593 n++ 594 } 595 } 596 } 597 if n == len(q) { 598 // if all filters were recognized we can merge this tree as a single iterator with multiple 599 // constraints and multiple save commands over the same set of quads 600 ns, _ := QuadsAction{ 601 Result: s.Dir, // this is still a HasA, remember? 602 Filter: filt, 603 Save: save, 604 }.Optimize(r) 605 return ns, true 606 } 607 // TODO 608 return s, opt 609} 610 611var _ Composite = QuadsAction{} 612 613// QuadsAction represents a set of actions that can be done to a set of quads in a single scan pass. 614// It filters quads according to Filter constraints (equivalent of LinksTo), tags directions using tags in Save field 615// and returns a specified quad direction as result of the iterator (equivalent of HasA). 616// Optionally, Size field may be set to indicate an approximate number of quads that will be returned by this query. 617type QuadsAction struct { 618 Size int64 // approximate size; zero means undefined 619 Result quad.Direction 620 Save map[quad.Direction][]string 621 Filter map[quad.Direction]graph.Value 622} 623 624func (s *QuadsAction) SetFilter(d quad.Direction, v graph.Value) { 625 if s.Filter == nil { 626 s.Filter = make(map[quad.Direction]graph.Value) 627 } 628 s.Filter[d] = v 629} 630 631func (s QuadsAction) Clone() QuadsAction { 632 if n := len(s.Save); n != 0 { 633 s2 := make(map[quad.Direction][]string, n) 634 for k, v := range s.Save { 635 s2[k] = v 636 } 637 s.Save = s2 638 } else { 639 s.Save = nil 640 } 641 if n := len(s.Filter); n != 0 { 642 f2 := make(map[quad.Direction]graph.Value, n) 643 for k, v := range s.Filter { 644 f2[k] = v 645 } 646 s.Filter = f2 647 } else { 648 s.Filter = nil 649 } 650 return s 651} 652func (s QuadsAction) simplify() NodesFrom { 653 q := make(Quads, 0, len(s.Save)+len(s.Filter)) 654 for dir, val := range s.Filter { 655 q = append(q, QuadFilter{Dir: dir, Values: Fixed{val}}) 656 } 657 for dir, tags := range s.Save { 658 q = append(q, QuadFilter{Dir: dir, Values: Save{From: AllNodes{}, Tags: tags}}) 659 } 660 return NodesFrom{Dir: s.Result, Quads: q} 661} 662func (s QuadsAction) Simplify() Shape { 663 return s.simplify() 664} 665func (s QuadsAction) BuildIterator(qs graph.QuadStore) graph.Iterator { 666 h := s.simplify() 667 return h.BuildIterator(qs) 668} 669func (s QuadsAction) Optimize(r Optimizer) (Shape, bool) { 670 if r != nil { 671 return r.OptimizeShape(s) 672 } 673 // if optimizer has stats for quad indexes we can use them to do more 674 ind, ok := r.(QuadIndexer) 675 if !ok { 676 return s, false 677 } 678 if s.Size > 0 { // already optimized; specific for QuadIndexer optimization 679 return s, false 680 } 681 sz, exact := ind.SizeOfIndex(s.Filter) 682 if !exact { 683 return s, false 684 } 685 s.Size = sz // computing size is already an optimization 686 if sz == 0 { 687 // nothing here, collapse the tree 688 return nil, true 689 } else if sz == 1 { 690 // only one quad matches this set of filters 691 // try to load it from quad store, do all operations and bake result as a fixed node/tags 692 if q, ok := ind.LookupQuadIndex(s.Filter); ok { 693 fx := Fixed{q.Get(s.Result)} 694 if len(s.Save) == 0 { 695 return fx, true 696 } 697 ft := FixedTags{On: fx, Tags: make(map[string]graph.Value)} 698 for d, tags := range s.Save { 699 for _, t := range tags { 700 ft.Tags[t] = q.Get(d) 701 } 702 } 703 return ft, true 704 } 705 } 706 if sz < int64(MaterializeThreshold) { 707 // if this set is small enough - materialize it 708 return Materialize{Values: s, Size: int(sz)}, true 709 } 710 return s, true 711} 712 713// One checks if Shape represents a single fixed value and returns it. 714func One(s Shape) (graph.Value, bool) { 715 switch s := s.(type) { 716 case Fixed: 717 if len(s) == 1 { 718 return s[0], true 719 } 720 } 721 return nil, false 722} 723 724// Fixed is a static set of nodes. Defined only for a particular QuadStore. 725type Fixed []graph.Value 726 727func (s *Fixed) Add(v ...graph.Value) { 728 *s = append(*s, v...) 729} 730func (s Fixed) BuildIterator(qs graph.QuadStore) graph.Iterator { 731 it := iterator.NewFixed() 732 for _, v := range s { 733 if _, ok := v.(quad.Value); ok { 734 panic("quad value in fixed iterator") 735 } 736 it.Add(v) 737 } 738 return it 739} 740func (s Fixed) Optimize(r Optimizer) (Shape, bool) { 741 if len(s) == 0 { 742 return nil, true 743 } 744 if r != nil { 745 return r.OptimizeShape(s) 746 } 747 return s, false 748} 749 750// FixedTags adds a set of fixed tag values to query results. It does not affect query execution in any other way. 751// 752// Shape implementations should try to push these objects up the tree during optimization process. 753type FixedTags struct { 754 Tags map[string]graph.Value 755 On Shape 756} 757 758func (s FixedTags) BuildIterator(qs graph.QuadStore) graph.Iterator { 759 if IsNull(s.On) { 760 return iterator.NewNull() 761 } 762 it := s.On.BuildIterator(qs) 763 tg := it.Tagger() 764 for k, v := range s.Tags { 765 tg.AddFixed(k, v) 766 } 767 return it 768} 769func (s FixedTags) Optimize(r Optimizer) (Shape, bool) { 770 if IsNull(s.On) { 771 return nil, true 772 } 773 var opt bool 774 s.On, opt = s.On.Optimize(r) 775 if len(s.Tags) == 0 { 776 return s.On, true 777 } else if s2, ok := s.On.(FixedTags); ok { 778 tags := make(map[string]graph.Value, len(s.Tags)+len(s2.Tags)) 779 for k, v := range s.Tags { 780 tags[k] = v 781 } 782 for k, v := range s2.Tags { 783 tags[k] = v 784 } 785 s, opt = FixedTags{On: s2.On, Tags: tags}, true 786 } 787 if r != nil { 788 ns, nopt := r.OptimizeShape(s) 789 return ns, opt || nopt 790 } 791 return s, opt 792} 793 794// Lookup is a static set of values that must be resolved to nodes by QuadStore. 795type Lookup []quad.Value 796 797func (s *Lookup) Add(v ...quad.Value) { 798 *s = append(*s, v...) 799} 800 801var _ valueResolver = graph.QuadStore(nil) 802 803type valueResolver interface { 804 ValueOf(v quad.Value) graph.Value 805} 806 807func (s Lookup) resolve(qs valueResolver) Shape { 808 // TODO: check if QS supports batch lookup 809 vals := make([]graph.Value, 0, len(s)) 810 for _, v := range s { 811 if gv := qs.ValueOf(v); gv != nil { 812 vals = append(vals, gv) 813 } 814 } 815 if len(vals) == 0 { 816 return nil 817 } 818 return Fixed(vals) 819} 820func (s Lookup) BuildIterator(qs graph.QuadStore) graph.Iterator { 821 f := s.resolve(qs) 822 if IsNull(f) { 823 return iterator.NewNull() 824 } 825 return f.BuildIterator(qs) 826} 827func (s Lookup) Optimize(r Optimizer) (Shape, bool) { 828 if r == nil { 829 return s, false 830 } 831 ns, opt := r.OptimizeShape(s) 832 if opt { 833 return ns, true 834 } 835 if qs, ok := r.(valueResolver); ok { 836 ns, opt = s.resolve(qs), true 837 } 838 return ns, opt 839} 840 841var MaterializeThreshold = 100 // TODO: tune 842 843// Materialize loads results of sub-query into memory during execution to speedup iteration. 844type Materialize struct { 845 Size int // approximate size; zero means undefined 846 Values Shape 847} 848 849func (s Materialize) BuildIterator(qs graph.QuadStore) graph.Iterator { 850 if IsNull(s.Values) { 851 return iterator.NewNull() 852 } 853 it := s.Values.BuildIterator(qs) 854 return iterator.NewMaterializeWithSize(it, int64(s.Size)) 855} 856func (s Materialize) Optimize(r Optimizer) (Shape, bool) { 857 if IsNull(s.Values) { 858 return nil, true 859 } 860 var opt bool 861 s.Values, opt = s.Values.Optimize(r) 862 if r != nil { 863 ns, nopt := r.OptimizeShape(s) 864 return ns, opt || nopt 865 } 866 return s, opt 867} 868 869func clearFixedTags(arr []Shape) ([]Shape, map[string]graph.Value) { 870 var tags map[string]graph.Value 871 for i := 0; i < len(arr); i++ { 872 if ft, ok := arr[i].(FixedTags); ok { 873 if tags == nil { 874 tags = make(map[string]graph.Value) 875 na := make([]Shape, len(arr)) 876 copy(na, arr) 877 arr = na 878 } 879 arr[i] = ft.On 880 for k, v := range ft.Tags { 881 tags[k] = v 882 } 883 } 884 } 885 return arr, tags 886} 887 888// Intersect computes an intersection of nodes between multiple queries. Similar to And iterator. 889type Intersect []Shape 890 891func (s Intersect) BuildIterator(qs graph.QuadStore) graph.Iterator { 892 if len(s) == 0 { 893 return iterator.NewNull() 894 } 895 sub := make([]graph.Iterator, 0, len(s)) 896 for _, c := range s { 897 sub = append(sub, c.BuildIterator(qs)) 898 } 899 if len(sub) == 1 { 900 return sub[0] 901 } 902 return iterator.NewAnd(qs, sub...) 903} 904func (s Intersect) Optimize(r Optimizer) (sout Shape, opt bool) { 905 if len(s) == 0 { 906 return nil, true 907 } 908 // function to lazily reallocate a copy of Intersect slice 909 realloc := func() { 910 if !opt { 911 arr := make(Intersect, len(s)) 912 copy(arr, s) 913 s = arr 914 } 915 } 916 // optimize sub-iterators, return empty set if Null is found 917 for i := 0; i < len(s); i++ { 918 c := s[i] 919 if IsNull(c) { 920 return nil, true 921 } 922 v, ok := c.Optimize(r) 923 if !ok { 924 continue 925 } 926 realloc() 927 opt = true 928 if IsNull(v) { 929 return nil, true 930 } 931 s[i] = v 932 } 933 if r != nil { 934 ns, nopt := r.OptimizeShape(s) 935 return ns, opt || nopt 936 } 937 if arr, ft := clearFixedTags([]Shape(s)); ft != nil { 938 ns, _ := FixedTags{On: Intersect(arr), Tags: ft}.Optimize(r) 939 return ns, true 940 } 941 var ( 942 onlyAll = true // contains only AllNodes shapes 943 fixed []Fixed // we will collect all Fixed, and will place it as a first iterator 944 tags []string // if we find a Save inside, we will push it outside of Intersect 945 quads Quads // also, collect all quad filters into a single set 946 ) 947 remove := func(i *int, optimized bool) { 948 realloc() 949 if optimized { 950 opt = true 951 } 952 v := *i 953 s = append(s[:v], s[v+1:]...) 954 v-- 955 *i = v 956 } 957 // second pass - remove AllNodes, merge Quads, collect Fixed, collect Save, merge Intersects 958 for i := 0; i < len(s); i++ { 959 c := s[i] 960 switch c := c.(type) { 961 case AllNodes: // remove AllNodes - it's useless 962 remove(&i, true) 963 // prevent resetting of onlyAll 964 continue 965 case Optional: 966 if IsNull(c.From) { 967 remove(&i, true) 968 // prevent resetting of onlyAll 969 continue 970 } 971 case Quads: // merge all quad filters 972 remove(&i, false) 973 if quads == nil { 974 quads = c[:len(c):len(c)] 975 } else { 976 opt = true 977 quads = append(quads, c...) 978 } 979 case Fixed: // collect all Fixed sets 980 remove(&i, true) 981 fixed = append(fixed, c) 982 case Intersect: // merge with other Intersects 983 remove(&i, true) 984 s = append(s, c...) 985 case Save: // push Save outside of Intersect 986 realloc() 987 opt = true 988 tags = append(tags, c.Tags...) 989 s[i] = c.From 990 i-- 991 } 992 onlyAll = false 993 } 994 if onlyAll { 995 return AllNodes{}, true 996 } 997 if len(tags) != 0 { 998 // don't forget to move Save outside of Intersect at the end 999 defer func() { 1000 if IsNull(sout) { 1001 return 1002 } 1003 sv := Save{From: sout, Tags: tags} 1004 var topt bool 1005 sout, topt = sv.Optimize(r) 1006 opt = opt || topt 1007 }() 1008 } 1009 if quads != nil { 1010 nq, qopt := quads.Optimize(r) 1011 if IsNull(nq) { 1012 return nil, true 1013 } 1014 opt = opt || qopt 1015 s = append(s, nq) 1016 } 1017 // TODO: intersect fixed 1018 if len(fixed) == 1 { 1019 fix := fixed[0] 1020 if len(s) == 1 { 1021 // try to push fixed down the tree 1022 switch sf := s[0].(type) { 1023 case QuadsAction: 1024 // TODO: accept an array of Fixed values 1025 if len(fix) == 1 { 1026 // we have a single value in Fixed that is intersected with HasA tree 1027 // this means we can add a new constraint: LinksTo(HasA.Dir, fixed) 1028 // result direction of HasA will be preserved 1029 fv := fix[0] 1030 if v := sf.Filter[sf.Result]; v != nil { 1031 // we have the same direction set as a fixed constraint - do filtering 1032 if graph.ToKey(v) != graph.ToKey(fv) { 1033 return nil, true 1034 } else { 1035 return sf, true 1036 } 1037 } 1038 sf = sf.Clone() 1039 sf.SetFilter(sf.Result, fv) // LinksTo(HasA.Dir, fixed) 1040 sf.Size = 0 // re-calculate size 1041 ns, _ := sf.Optimize(r) 1042 return ns, true 1043 } 1044 case NodesFrom: 1045 if sq, ok := sf.Quads.(Quads); ok { 1046 // an optimization above is valid for NodesFrom+Quads as well 1047 // we can add the same constraint to Quads and remove Fixed 1048 qi := -1 1049 for i, qf := range sq { 1050 if qf.Dir == sf.Dir { 1051 qi = i 1052 break 1053 } 1054 } 1055 if qi < 0 { 1056 // no filter on this direction - append 1057 sf.Quads = append(Quads{ 1058 {Dir: sf.Dir, Values: fix}, 1059 }, sq...) 1060 } else { 1061 // already have a filter on this direction - push Fixed inside it 1062 sq = append(Quads{}, sq...) 1063 sf.Quads = sq 1064 qf := &sq[qi] 1065 qf.Values = IntersectShapes(fix, qf.Values) 1066 } 1067 return sf, true 1068 } 1069 } 1070 } 1071 // place fixed as a first iterator 1072 s = append(s, nil) 1073 copy(s[1:], s) 1074 s[0] = fix 1075 } else if len(fixed) > 1 { 1076 ns := make(Intersect, len(s)+len(fixed)) 1077 for i, f := range fixed { 1078 ns[i] = f 1079 } 1080 copy(ns[len(fixed):], s) 1081 s = ns 1082 } 1083 if len(s) == 0 { 1084 return nil, true 1085 } else if len(s) == 1 { 1086 return s[0], true 1087 } 1088 // TODO: optimize order 1089 return s, opt 1090} 1091 1092// Union joins results of multiple queries together. It does not make results unique. 1093type Union []Shape 1094 1095func (s Union) BuildIterator(qs graph.QuadStore) graph.Iterator { 1096 if len(s) == 0 { 1097 return iterator.NewNull() 1098 } 1099 sub := make([]graph.Iterator, 0, len(s)) 1100 for _, c := range s { 1101 sub = append(sub, c.BuildIterator(qs)) 1102 } 1103 if len(sub) == 1 { 1104 return sub[0] 1105 } 1106 return iterator.NewOr(sub...) 1107} 1108func (s Union) Optimize(r Optimizer) (Shape, bool) { 1109 var opt bool 1110 realloc := func() { 1111 if !opt { 1112 arr := make(Union, len(s)) 1113 copy(arr, s) 1114 s = arr 1115 } 1116 } 1117 // optimize subiterators 1118 for i := 0; i < len(s); i++ { 1119 c := s[i] 1120 if c == nil { 1121 continue 1122 } 1123 v, ok := c.Optimize(r) 1124 if !ok { 1125 continue 1126 } 1127 realloc() 1128 opt = true 1129 s[i] = v 1130 } 1131 if r != nil { 1132 ns, nopt := r.OptimizeShape(s) 1133 return ns, opt || nopt 1134 } 1135 if arr, ft := clearFixedTags([]Shape(s)); ft != nil { 1136 ns, _ := FixedTags{On: Union(arr), Tags: ft}.Optimize(r) 1137 return ns, true 1138 } 1139 // second pass - remove Null 1140 for i := 0; i < len(s); i++ { 1141 c := s[i] 1142 if IsNull(c) { 1143 realloc() 1144 opt = true 1145 s = append(s[:i], s[i+1:]...) 1146 } 1147 } 1148 if len(s) == 0 { 1149 return nil, true 1150 } else if len(s) == 1 { 1151 return s[0], true 1152 } 1153 // TODO: join Fixed 1154 return s, opt 1155} 1156 1157// Page provides a simple form of pagination. Can be used to skip or limit results. 1158type Page struct { 1159 From Shape 1160 Skip int64 1161 Limit int64 // zero means unlimited 1162} 1163 1164func (s Page) BuildIterator(qs graph.QuadStore) graph.Iterator { 1165 if IsNull(s.From) { 1166 return iterator.NewNull() 1167 } 1168 it := s.From.BuildIterator(qs) 1169 if s.Skip > 0 { 1170 it = iterator.NewSkip(it, s.Skip) 1171 } 1172 if s.Limit > 0 { 1173 it = iterator.NewLimit(it, s.Limit) 1174 } 1175 return it 1176} 1177func (s Page) Optimize(r Optimizer) (Shape, bool) { 1178 if IsNull(s.From) { 1179 return nil, true 1180 } 1181 var opt bool 1182 s.From, opt = s.From.Optimize(r) 1183 if s.Skip <= 0 && s.Limit <= 0 { 1184 return s.From, true 1185 } 1186 if p, ok := s.From.(Page); ok { 1187 p2 := p.ApplyPage(s) 1188 if p2 == nil { 1189 return nil, true 1190 } 1191 s, opt = *p2, true 1192 } 1193 if r != nil { 1194 ns, nopt := r.OptimizeShape(s) 1195 return ns, opt || nopt 1196 } 1197 // TODO: check size 1198 return s, opt 1199} 1200func (s Page) ApplyPage(p Page) *Page { 1201 s.Skip += p.Skip 1202 if s.Limit > 0 { 1203 s.Limit -= p.Skip 1204 if s.Limit <= 0 { 1205 return nil 1206 } 1207 if p.Limit > 0 && s.Limit > p.Limit { 1208 s.Limit = p.Limit 1209 } 1210 } else { 1211 s.Limit = p.Limit 1212 } 1213 return &s 1214} 1215 1216// Unique makes query results unique. 1217type Unique struct { 1218 From Shape 1219} 1220 1221func (s Unique) BuildIterator(qs graph.QuadStore) graph.Iterator { 1222 if IsNull(s.From) { 1223 return iterator.NewNull() 1224 } 1225 it := s.From.BuildIterator(qs) 1226 return iterator.NewUnique(it) 1227} 1228func (s Unique) Optimize(r Optimizer) (Shape, bool) { 1229 if IsNull(s.From) { 1230 return nil, true 1231 } 1232 var opt bool 1233 s.From, opt = s.From.Optimize(r) 1234 if IsNull(s.From) { 1235 return nil, true 1236 } 1237 if r != nil { 1238 ns, nopt := r.OptimizeShape(s) 1239 return ns, opt || nopt 1240 } 1241 return s, opt 1242} 1243 1244// Save tags a results of query with provided tags. 1245type Save struct { 1246 Tags []string 1247 From Shape 1248} 1249 1250func (s Save) BuildIterator(qs graph.QuadStore) graph.Iterator { 1251 if IsNull(s.From) { 1252 return iterator.NewNull() 1253 } 1254 it := s.From.BuildIterator(qs) 1255 tg := it.Tagger() 1256 if len(s.Tags) != 0 { 1257 tg.Add(s.Tags...) 1258 } 1259 return it 1260} 1261func (s Save) Optimize(r Optimizer) (Shape, bool) { 1262 if IsNull(s.From) { 1263 return nil, true 1264 } 1265 var opt bool 1266 s.From, opt = s.From.Optimize(r) 1267 if len(s.Tags) == 0 { 1268 return s.From, true 1269 } 1270 if r != nil { 1271 ns, nopt := r.OptimizeShape(s) 1272 return ns, opt || nopt 1273 } 1274 return s, opt 1275} 1276 1277// Optional makes a query execution optional. The query can only produce tagged results, 1278// since it's value is not used to compute intersection. 1279type Optional struct { 1280 From Shape 1281} 1282 1283func (s Optional) BuildIterator(qs graph.QuadStore) graph.Iterator { 1284 if IsNull(s.From) { 1285 return iterator.NewOptional(iterator.NewNull()) 1286 } 1287 return iterator.NewOptional(s.From.BuildIterator(qs)) 1288} 1289func (s Optional) Optimize(r Optimizer) (Shape, bool) { 1290 if IsNull(s.From) { 1291 return s, false 1292 } 1293 var opt bool 1294 s.From, opt = s.From.Optimize(r) 1295 if IsNull(s.From) { 1296 return s, opt 1297 } 1298 if r != nil { 1299 ns, nopt := r.OptimizeShape(s) 1300 return ns, opt || nopt 1301 } 1302 return s, opt 1303} 1304