1// Defines the And iterator, one of the base iterators. And requires no
2// knowledge of the constituent QuadStore; its sole purpose is to act as an
3// intersection operator across the subiterators it is given. If one iterator
4// contains [1,3,5] and another [2,3,4] -- then And is an iterator that
5// 'contains' [3]
6//
7// It accomplishes this in one of two ways. If it is a Next()ed iterator (that
8// is, it is a top level iterator, or on the "Next() path", then it will Next()
9// it's primary iterator (helpfully, and.primary_it) and Contains() the resultant
10// value against it's other iterators. If it matches all of them, then it
11// returns that value. Otherwise, it repeats the process.
12//
13// If it's on a Contains() path, it merely Contains()s every iterator, and returns the
14// logical AND of each result.
15
16package iterator
17
18import (
19	"context"
20
21	"github.com/cayleygraph/cayley/graph"
22)
23
24var _ graph.Iterator = &And{}
25
26// The And iterator. Consists of a number of subiterators, the primary of which will
27// be Next()ed if next is called.
28type And struct {
29	uid               uint64
30	tags              graph.Tagger
31	internalIterators []graph.Iterator
32	itCount           int
33	primaryIt         graph.Iterator
34	checkList         []graph.Iterator
35	result            graph.Value
36	runstats          graph.IteratorStats
37	err               error
38	qs                graph.QuadStore
39}
40
41// NewAnd creates an And iterator. `qs` is only required when needing a handle
42// for QuadStore-specific optimizations, otherwise nil is acceptable.
43func NewAnd(qs graph.QuadStore, sub ...graph.Iterator) *And {
44	it := &And{
45		uid:               NextUID(),
46		internalIterators: make([]graph.Iterator, 0, 20),
47		qs:                qs,
48	}
49	for _, s := range sub {
50		it.AddSubIterator(s)
51	}
52	return it
53}
54
55func (it *And) UID() uint64 {
56	return it.uid
57}
58
59// Reset all internal iterators
60func (it *And) Reset() {
61	it.result = nil
62	it.primaryIt.Reset()
63	for _, sub := range it.internalIterators {
64		sub.Reset()
65	}
66	it.checkList = nil
67}
68
69func (it *And) Tagger() *graph.Tagger {
70	return &it.tags
71}
72
73// An extended TagResults, as it needs to add it's own results and
74// recurse down it's subiterators.
75func (it *And) TagResults(dst map[string]graph.Value) {
76	it.tags.TagResult(dst, it.Result())
77
78	if it.primaryIt != nil {
79		it.primaryIt.TagResults(dst)
80	}
81	for _, sub := range it.internalIterators {
82		sub.TagResults(dst)
83	}
84}
85
86func (it *And) Clone() graph.Iterator {
87	and := NewAnd(it.qs)
88	and.AddSubIterator(it.primaryIt.Clone())
89	and.tags.CopyFrom(it)
90	for _, sub := range it.internalIterators {
91		and.AddSubIterator(sub.Clone())
92	}
93	if it.checkList != nil {
94		and.optimizeContains()
95	}
96	return and
97}
98
99// Returns a slice of the subiterators, in order (primary iterator first).
100func (it *And) SubIterators() []graph.Iterator {
101	iters := make([]graph.Iterator, 0, len(it.internalIterators)+1)
102	if it.primaryIt != nil {
103		iters = append(iters, it.primaryIt)
104	}
105	iters = append(iters, it.internalIterators...)
106	return iters
107}
108
109func (it *And) String() string {
110	return "And"
111}
112
113// Add a subiterator to this And iterator.
114//
115// The first iterator that is added becomes the primary iterator. This is
116// important. Calling Optimize() is the way to change the order based on
117// subiterator statistics. Without Optimize(), the order added is the order
118// used.
119func (it *And) AddSubIterator(sub graph.Iterator) {
120	if it.itCount > 0 {
121		it.internalIterators = append(it.internalIterators, sub)
122		it.itCount++
123		return
124	}
125	it.primaryIt = sub
126	it.itCount++
127}
128
129// Returns advances the And iterator. Because the And is the intersection of its
130// subiterators, it must choose one subiterator to produce a candidate, and check
131// this value against the subiterators. A productive choice of primary iterator
132// is therefore very important.
133func (it *And) Next(ctx context.Context) bool {
134	graph.NextLogIn(it)
135	it.runstats.Next += 1
136	for it.primaryIt.Next(ctx) {
137		curr := it.primaryIt.Result()
138		if it.subItsContain(ctx, curr, nil) {
139			it.result = curr
140			return graph.NextLogOut(it, true)
141		}
142	}
143	it.err = it.primaryIt.Err()
144	return graph.NextLogOut(it, false)
145}
146
147func (it *And) Err() error {
148	if err := it.err; err != nil {
149		return err
150	}
151	if err := it.primaryIt.Err(); err != nil {
152		return err
153	}
154	for _, si := range it.internalIterators {
155		if err := si.Err(); err != nil {
156			return err
157		}
158	}
159	return nil
160}
161
162func (it *And) Result() graph.Value {
163	return it.result
164}
165
166// Checks a value against the non-primary iterators, in order.
167func (it *And) subItsContain(ctx context.Context, val graph.Value, lastResult graph.Value) bool {
168	var subIsGood = true
169	for i, sub := range it.internalIterators {
170		subIsGood = sub.Contains(ctx, val)
171		if !subIsGood {
172			if lastResult != nil {
173				for j := 0; j < i; j++ {
174					it.internalIterators[j].Contains(ctx, lastResult)
175				}
176			}
177			break
178		}
179	}
180	return subIsGood
181}
182
183func (it *And) checkContainsList(ctx context.Context, val graph.Value, lastResult graph.Value) bool {
184	ok := true
185	for i, c := range it.checkList {
186		ok = c.Contains(ctx, val)
187		if !ok {
188			it.err = c.Err()
189			if it.err != nil {
190				return false
191			}
192
193			if lastResult != nil {
194				for j := 0; j < i; j++ {
195					// One of the iterators has determined that this value doesn't
196					// match. However, the iterators that came before in the list
197					// may have returned "ok" to Contains().  We need to set all
198					// the tags back to what the previous result was -- effectively
199					// seeking back exactly one -- so we check all the prior iterators
200					// with the (already verified) result and throw away the result,
201					// which will be 'true'
202					it.checkList[j].Contains(ctx, lastResult)
203
204					it.err = it.checkList[j].Err()
205					if it.err != nil {
206						return false
207					}
208				}
209			}
210			break
211		}
212	}
213	if ok {
214		it.result = val
215	}
216	return graph.ContainsLogOut(it, val, ok)
217}
218
219// Check a value against the entire iterator, in order.
220func (it *And) Contains(ctx context.Context, val graph.Value) bool {
221	graph.ContainsLogIn(it, val)
222	it.runstats.Contains += 1
223	lastResult := it.result
224	if it.checkList != nil {
225		return it.checkContainsList(ctx, val, lastResult)
226	}
227	mainGood := it.primaryIt.Contains(ctx, val)
228	if mainGood {
229		othersGood := it.subItsContain(ctx, val, lastResult)
230		if othersGood {
231			it.result = val
232			return graph.ContainsLogOut(it, val, true)
233		}
234	}
235	if lastResult != nil {
236		it.primaryIt.Contains(ctx, lastResult)
237	}
238	return graph.ContainsLogOut(it, val, false)
239}
240
241// Returns the approximate size of the And iterator. Because we're dealing
242// with an intersection, we know that the largest we can be is the size of the
243// smallest iterator. This is the heuristic we shall follow. Better heuristics
244// welcome.
245func (it *And) Size() (int64, bool) {
246	val, b := it.primaryIt.Size()
247	for _, sub := range it.internalIterators {
248		newval, newb := sub.Size()
249		if val > newval {
250			val = newval
251		}
252		b = newb && b
253	}
254	return val, b
255}
256
257// An And has no NextPath of its own -- that is, there are no other values
258// which satisfy our previous result that are not the result itself. Our
259// subiterators might, however, so just pass the call recursively.
260func (it *And) NextPath(ctx context.Context) bool {
261	if it.primaryIt.NextPath(ctx) {
262		return true
263	}
264	it.err = it.primaryIt.Err()
265	if it.err != nil {
266		return false
267	}
268	for _, sub := range it.internalIterators {
269		if sub.NextPath(ctx) {
270			return true
271		}
272
273		it.err = sub.Err()
274		if it.err != nil {
275			return false
276		}
277	}
278	return false
279}
280
281// Perform and-specific cleanup, of which there currently is none.
282func (it *And) cleanUp() {}
283
284// Close this iterator, and, by extension, close the subiterators.
285// Close should be idempotent, and it follows that if it's subiterators
286// follow this contract, the And follows the contract.  It closes all
287// subiterators it can, but returns the first error it encounters.
288func (it *And) Close() error {
289	it.cleanUp()
290
291	err := it.primaryIt.Close()
292	for _, sub := range it.internalIterators {
293		_err := sub.Close()
294		if _err != nil && err == nil {
295			err = _err
296		}
297	}
298
299	return err
300}
301
302// Register this as an "and" iterator.
303func (it *And) Type() graph.Type { return graph.And }
304