// Copyright 2014 The Cayley Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package iterator // Defines the or and short-circuiting or iterator. Or is the union operator for it's subiterators. // Short-circuiting-or is a little different. It will return values from the first graph.iterator that returns // values at all, and then stops. // // Never reorders the iterators from the order they arrive. It is either the union or the first one. // May return the same value twice -- once for each branch. import ( "fmt" "strings" "github.com/google/cayley/graph" ) type Or struct { uid uint64 tags graph.Tagger isShortCircuiting bool internalIterators []graph.Iterator itCount int currentIterator int result graph.Value } func NewOr() *Or { return &Or{ uid: NextUID(), internalIterators: make([]graph.Iterator, 0, 20), currentIterator: -1, } } func NewShortCircuitOr() *Or { return &Or{ uid: NextUID(), internalIterators: make([]graph.Iterator, 0, 20), isShortCircuiting: true, currentIterator: -1, } } func (it *Or) UID() uint64 { return it.uid } // Reset all internal iterators func (it *Or) Reset() { for _, sub := range it.internalIterators { sub.Reset() } it.currentIterator = -1 } func (it *Or) Tagger() *graph.Tagger { return &it.tags } func (it *Or) Clone() graph.Iterator { var or *Or if it.isShortCircuiting { or = NewShortCircuitOr() } else { or = NewOr() } for _, sub := range it.internalIterators { or.AddSubIterator(sub.Clone()) } or.tags.CopyFrom(it) return or } // Returns a list.List of the subiterators, in order. The returned slice must not be modified. func (it *Or) SubIterators() []graph.Iterator { return it.internalIterators } // Overrides BaseIterator TagResults, as it needs to add it's own results and // recurse down it's subiterators. func (it *Or) TagResults(dst map[string]graph.Value) { for _, tag := range it.tags.Tags() { dst[tag] = it.Result() } for tag, value := range it.tags.Fixed() { dst[tag] = value } it.internalIterators[it.currentIterator].TagResults(dst) } // DEPRECATED Returns the ResultTree for this graph.iterator, recurses to it's subiterators. func (it *Or) ResultTree() *graph.ResultTree { tree := graph.NewResultTree(it.Result()) for _, sub := range it.internalIterators { tree.AddSubtree(sub.ResultTree()) } return tree } // Prints information about this graph.iterator. func (it *Or) DebugString(indent int) string { var total string for i, sub := range it.internalIterators { total += strings.Repeat(" ", indent+2) total += fmt.Sprintf("%d:\n%s\n", i, sub.DebugString(indent+4)) } var tags string for _, k := range it.tags.Tags() { tags += fmt.Sprintf("%s;", k) } spaces := strings.Repeat(" ", indent+2) return fmt.Sprintf("%s(%s\n%stags:%s\n%sits:\n%s)", strings.Repeat(" ", indent), it.Type(), spaces, tags, spaces, total) } // Add a subiterator to this Or graph.iterator. Order matters. func (it *Or) AddSubIterator(sub graph.Iterator) { it.internalIterators = append(it.internalIterators, sub) it.itCount++ } // Next advances the Or graph.iterator. Because the Or is the union of its // subiterators, it must produce from all subiterators -- unless it it // shortcircuiting, in which case, it is the first one that returns anything. func (it *Or) Next() bool { graph.NextLogIn(it) var first bool for { if it.currentIterator == -1 { it.currentIterator = 0 first = true } curIt := it.internalIterators[it.currentIterator] if graph.Next(curIt) { it.result = curIt.Result() return graph.NextLogOut(it, it.result, true) } if it.isShortCircuiting && !first { break } it.currentIterator++ if it.currentIterator == it.itCount { break } } return graph.NextLogOut(it, nil, false) } func (it *Or) Result() graph.Value { return it.result } // Checks a value against the iterators, in order. func (it *Or) subItsContain(val graph.Value) bool { var subIsGood = false for i, sub := range it.internalIterators { subIsGood = sub.Contains(val) if subIsGood { it.currentIterator = i break } } return subIsGood } // Check a value against the entire graph.iterator, in order. func (it *Or) Contains(val graph.Value) bool { graph.ContainsLogIn(it, val) anyGood := it.subItsContain(val) if !anyGood { return graph.ContainsLogOut(it, val, false) } it.result = val return graph.ContainsLogOut(it, val, true) } // Returns the approximate size of the Or graph.iterator. Because we're dealing // with a union, we know that the largest we can be is the sum of all the iterators, // or in the case of short-circuiting, the longest. func (it *Or) Size() (int64, bool) { var val int64 var b bool if it.isShortCircuiting { val = 0 b = true for _, sub := range it.internalIterators { newval, newb := sub.Size() if val < newval { val = newval } b = newb && b } } else { val = 0 b = true for _, sub := range it.internalIterators { newval, newb := sub.Size() val += newval b = newb && b } } return val, b } // An Or has no NextPath of its own -- that is, there are no other values // which satisfy our previous result that are not the result itself. Our // subiterators might, however, so just pass the call recursively. In the case of // shortcircuiting, only allow new results from the currently checked graph.iterator func (it *Or) NextPath() bool { if it.currentIterator != -1 { return it.internalIterators[it.currentIterator].NextPath() } return false } // Perform or-specific cleanup, of which there currently is none. func (it *Or) cleanUp() {} // Close this graph.iterator, and, by extension, close the subiterators. // Close should be idempotent, and it follows that if it's subiterators // follow this contract, the And follows the contract. func (it *Or) Close() { it.cleanUp() for _, sub := range it.internalIterators { sub.Close() } } func (it *Or) Optimize() (graph.Iterator, bool) { old := it.SubIterators() optIts := optimizeSubIterators(old) // Close the replaced iterators (they ought to close themselves, but Close() // is idempotent, so this just protects against any machinations). closeIteratorList(old, nil) newOr := NewOr() newOr.isShortCircuiting = it.isShortCircuiting // Add the subiterators in order. for _, o := range optIts { newOr.AddSubIterator(o) } // Move the tags hanging on us (like any good replacement). newOr.tags.CopyFrom(it) // And close ourselves but not our subiterators -- some may still be alive in // the new And (they were unchanged upon calling Optimize() on them, at the // start). it.cleanUp() return newOr, true } func (it *Or) Stats() graph.IteratorStats { ContainsCost := int64(0) NextCost := int64(0) Size := int64(0) for _, sub := range it.internalIterators { stats := sub.Stats() NextCost += stats.NextCost ContainsCost += stats.ContainsCost if it.isShortCircuiting { if Size < stats.Size { Size = stats.Size } } else { Size += stats.Size } } return graph.IteratorStats{ ContainsCost: ContainsCost, NextCost: NextCost, Size: Size, } } // Register this as an "or" graph.iterator. func (it *Or) Type() graph.Type { return graph.Or }