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