This is done unsubtlely at the moment and there is plenty of room for optimisation of assertion location to prevent repeated reasserting as is done now.
266 lines
6.8 KiB
Go
266 lines
6.8 KiB
Go
// 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 graph
|
|
|
|
// Define the general iterator interface.
|
|
|
|
import (
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/barakmich/glog"
|
|
)
|
|
|
|
type Tagger struct {
|
|
tags []string
|
|
fixedTags map[string]Value
|
|
}
|
|
|
|
// Adds a tag to the iterator.
|
|
func (t *Tagger) Add(tag string) {
|
|
t.tags = append(t.tags, tag)
|
|
}
|
|
|
|
func (t *Tagger) AddFixed(tag string, value Value) {
|
|
if t.fixedTags == nil {
|
|
t.fixedTags = make(map[string]Value)
|
|
}
|
|
t.fixedTags[tag] = value
|
|
}
|
|
|
|
// Returns the tags. The returned value must not be mutated.
|
|
func (t *Tagger) Tags() []string {
|
|
return t.tags
|
|
}
|
|
|
|
// Returns the fixed tags. The returned value must not be mutated.
|
|
func (t *Tagger) Fixed() map[string]Value {
|
|
return t.fixedTags
|
|
}
|
|
|
|
func (t *Tagger) CopyFrom(src Iterator) {
|
|
for _, tag := range src.Tagger().Tags() {
|
|
t.Add(tag)
|
|
}
|
|
|
|
for k, v := range src.Tagger().Fixed() {
|
|
t.AddFixed(k, v)
|
|
}
|
|
|
|
}
|
|
|
|
type Iterator interface {
|
|
Tagger() *Tagger
|
|
|
|
// Fills a tag-to-result-value map.
|
|
TagResults(map[string]Value)
|
|
|
|
// Returns the current result.
|
|
Result() Value
|
|
|
|
// DEPRECATED -- Fills a ResultTree struct with Result().
|
|
ResultTree() *ResultTree
|
|
|
|
// These methods are the heart and soul of the iterator, as they constitute
|
|
// the iteration interface.
|
|
//
|
|
// To get the full results of iteration, do the following:
|
|
// while (!Next()):
|
|
// emit result
|
|
// while (!NextResult()):
|
|
// emit result
|
|
//
|
|
// All of them should set iterator.Last to be the last returned value, to
|
|
// make results work.
|
|
//
|
|
// NextResult() advances iterators that may have more than one valid result,
|
|
// from the bottom up.
|
|
NextResult() bool
|
|
|
|
// Check(), given a value, returns whether or not that value is within the set
|
|
// held by this iterator.
|
|
Check(Value) bool
|
|
|
|
// Start iteration from the beginning
|
|
Reset()
|
|
|
|
// Create a new iterator just like this one
|
|
Clone() Iterator
|
|
|
|
// These methods relate to choosing the right iterator, or optimizing an
|
|
// iterator tree
|
|
//
|
|
// Stats() returns the relative costs of calling the iteration methods for
|
|
// this iterator, as well as the size. Roughly, it will take NextCost * Size
|
|
// "cost units" to get everything out of the iterator. This is a wibbly-wobbly
|
|
// thing, and not exact, but a useful heuristic.
|
|
Stats() IteratorStats
|
|
|
|
// Helpful accessor for the number of things in the iterator. The first return
|
|
// value is the size, and the second return value is whether that number is exact,
|
|
// or a conservative estimate.
|
|
Size() (int64, bool)
|
|
|
|
// Returns a string relating to what the function of the iterator is. By
|
|
// knowing the names of the iterators, we can devise optimization strategies.
|
|
Type() Type
|
|
|
|
// Optimizes an iterator. Can replace the iterator, or merely move things
|
|
// around internally. if it chooses to replace it with a better iterator,
|
|
// returns (the new iterator, true), if not, it returns (self, false).
|
|
Optimize() (Iterator, bool)
|
|
|
|
// Return a slice of the subiterators for this iterator.
|
|
SubIterators() []Iterator
|
|
|
|
// Return a string representation of the iterator, indented by the given amount.
|
|
DebugString(int) string
|
|
|
|
// Close the iterator and do internal cleanup.
|
|
Close()
|
|
|
|
// UID returns the unique identifier of the iterator.
|
|
UID() uint64
|
|
}
|
|
|
|
type Nexter interface {
|
|
// Next() advances the iterator and returns the next valid result. Returns
|
|
// (<value>, true) or (nil, false)
|
|
Next() (Value, bool)
|
|
|
|
Iterator
|
|
}
|
|
|
|
// Next is a convenience function that conditionally calls the Next method
|
|
// of an Iterator if it is a Nexter. If the Iterator is not a Nexter, Next
|
|
// return a nil Value and false.
|
|
func Next(it Iterator) (Value, bool) {
|
|
if n, ok := it.(Nexter); ok {
|
|
return n.Next()
|
|
}
|
|
glog.Errorln("Nexting an un-nextable iterator")
|
|
return nil, false
|
|
}
|
|
|
|
// FixedIterator wraps iterators that are modifiable by addition of fixed value sets.
|
|
type FixedIterator interface {
|
|
Iterator
|
|
Add(Value)
|
|
}
|
|
|
|
type IteratorStats struct {
|
|
CheckCost int64
|
|
NextCost int64
|
|
Size int64
|
|
}
|
|
|
|
// Type enumerates the set of Iterator types.
|
|
type Type int
|
|
|
|
const (
|
|
Invalid Type = iota
|
|
All
|
|
And
|
|
Or
|
|
HasA
|
|
LinksTo
|
|
Comparison
|
|
Null
|
|
Fixed
|
|
Not
|
|
Optional
|
|
)
|
|
|
|
var (
|
|
// We use a sync.Mutex rather than an RWMutex since the client packages keep
|
|
// the Type that was returned, so the only possibility for contention is at
|
|
// initialization.
|
|
lock sync.Mutex
|
|
// These strings must be kept in order consistent with the Type const block above.
|
|
types = []string{
|
|
"invalid",
|
|
"all",
|
|
"and",
|
|
"or",
|
|
"hasa",
|
|
"linksto",
|
|
"comparison",
|
|
"null",
|
|
"fixed",
|
|
"not",
|
|
"optional",
|
|
}
|
|
)
|
|
|
|
// RegisterIterator adds a new iterator type to the set of acceptable types, returning
|
|
// the registered Type.
|
|
// Calls to Register are idempotent and must be made prior to use of the iterator.
|
|
// The conventional approach for use is to include a call to Register in a package
|
|
// init() function, saving the Type to a private package var.
|
|
func RegisterIterator(name string) Type {
|
|
lock.Lock()
|
|
defer lock.Unlock()
|
|
for i, t := range types {
|
|
if t == name {
|
|
return Type(i)
|
|
}
|
|
}
|
|
types = append(types, name)
|
|
return Type(len(types) - 1)
|
|
}
|
|
|
|
// String returns a string representation of the Type.
|
|
func (t Type) String() string {
|
|
if t < 0 || int(t) >= len(types) {
|
|
return "illegal-type"
|
|
}
|
|
return types[t]
|
|
}
|
|
|
|
// Utility logging functions for when an iterator gets called Next upon, or Check upon, as
|
|
// well as what they return. Highly useful for tracing the execution path of a query.
|
|
func CheckLogIn(it Iterator, val Value) {
|
|
if glog.V(4) {
|
|
glog.V(4).Infof("%s %d CHECK %d", strings.ToUpper(it.Type().String()), it.UID(), val)
|
|
}
|
|
}
|
|
|
|
func CheckLogOut(it Iterator, val Value, good bool) bool {
|
|
if glog.V(4) {
|
|
if good {
|
|
glog.V(4).Infof("%s %d CHECK %d GOOD", strings.ToUpper(it.Type().String()), it.UID(), val)
|
|
} else {
|
|
glog.V(4).Infof("%s %d CHECK %d BAD", strings.ToUpper(it.Type().String()), it.UID(), val)
|
|
}
|
|
}
|
|
return good
|
|
}
|
|
|
|
func NextLogIn(it Iterator) {
|
|
if glog.V(4) {
|
|
glog.V(4).Infof("%s %d NEXT", strings.ToUpper(it.Type().String()), it.UID())
|
|
}
|
|
}
|
|
|
|
func NextLogOut(it Iterator, val Value, ok bool) (Value, bool) {
|
|
if glog.V(4) {
|
|
if ok {
|
|
glog.V(4).Infof("%s %d NEXT IS %d", strings.ToUpper(it.Type().String()), it.UID(), val)
|
|
} else {
|
|
glog.V(4).Infof("%s %d NEXT DONE", strings.ToUpper(it.Type().String()), it.UID())
|
|
}
|
|
}
|
|
return val, ok
|
|
}
|