forked from cayleygraph/cayley
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathiterator.go
364 lines (311 loc) · 9.39 KB
/
iterator.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
// 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 (
"fmt"
"strings"
"sync"
"github.com/barakmich/glog"
"github.com/google/cayley/quad"
)
type Tagger struct {
tags []string
fixedTags map[string]Value
}
// TODO(barakmich): Linkage is general enough that there are places we take
//the combined arguments `quad.Direction, graph.Value` that it may be worth
//converting these into Linkages. If nothing else, future indexed iterators may
//benefit from the shared representation
// Linkage is a union type representing a set of values established for a given
// quad direction.
type Linkage struct {
Dir quad.Direction
Value Value
}
// TODO(barakmich): Helper functions as needed, eg, ValuesForDirection(quad.Direction) []Value
// Add 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
}
// Tags returns the tags held in the tagger. The returned value must not be mutated.
func (t *Tagger) Tags() []string {
return t.tags
}
// Fixed returns the fixed tags held in the tagger. The returned value must not be mutated.
func (t *Tagger) Fixed() map[string]Value {
return t.fixedTags
}
func (t *Tagger) CopyFrom(src Iterator) {
st := src.Tagger()
t.tags = append(t.tags, st.tags...)
if t.fixedTags == nil {
t.fixedTags = make(map[string]Value, len(st.fixedTags))
}
for k, v := range st.fixedTags {
t.fixedTags[k] = v
}
}
type Iterator interface {
Tagger() *Tagger
// Fills a tag-to-result-value map.
TagResults(map[string]Value)
// Returns the current result.
Result() Value
// 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:
//
// for graph.Next(it) {
// val := it.Result()
// ... do things with val.
// for it.NextPath() {
// ... find other paths to iterate
// }
// }
//
// All of them should set iterator.Last to be the last returned value, to
// make results work.
//
// NextPath() advances iterators that may have more than one valid result,
// from the bottom up.
NextPath() bool
// Contains returns whether the value is within the set held by the iterator.
Contains(Value) bool
// Err returns any error that was encountered by the Iterator.
Err() error
// 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.
Describe() Description
// Close the iterator and do internal cleanup.
Close() error
// UID returns the unique identifier of the iterator.
UID() uint64
}
type Description struct {
UID uint64 `json:",omitempty"`
Name string `json:",omitempty"`
Type Type `json:",omitempty"`
Tags []string `json:",omitempty"`
Size int64 `json:",omitempty"`
Direction quad.Direction `json:",omitempty"`
Iterator *Description `json:",omitempty"`
Iterators []Description `json:",omitempty"`
}
// ApplyMorphism is a curried function that can generates a new iterator based on some prior iterator.
type ApplyMorphism func(QuadStore, Iterator) Iterator
type Nexter interface {
// Next advances the iterator to the next value, which will then be available through
// the Result method. It returns false if no further advancement is possible, or if an
// error was encountered during iteration. Err should be consulted to distinguish
// between the two cases.
Next() 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
// returns false.
func Next(it Iterator) bool {
if n, ok := it.(Nexter); ok {
return n.Next()
}
glog.Errorln("Nexting an un-nextable iterator")
return false
}
// Height is a convienence function to measure the height of an iterator tree.
func Height(it Iterator, until Type) int {
if it.Type() == until {
return 1
}
subs := it.SubIterators()
maxDepth := 0
for _, sub := range subs {
h := Height(sub, until)
if h > maxDepth {
maxDepth = h
}
}
return maxDepth + 1
}
// FixedIterator wraps iterators that are modifiable by addition of fixed value sets.
type FixedIterator interface {
Iterator
Add(Value)
}
type IteratorStats struct {
ContainsCost int64
NextCost int64
Size int64
Next int64
Contains int64
ContainsNext int64
}
// Type enumerates the set of Iterator types.
type Type int
// These are the iterator types, defined as constants
const (
Invalid Type = iota
All
And
Or
HasA
LinksTo
Comparison
Null
Fixed
Not
Optional
Materialize
Unique
)
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",
"materialize",
"unique",
}
)
// 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]
}
func (t *Type) MarshalText() (text []byte, err error) {
if *t < 0 || int(*t) >= len(types) {
return nil, fmt.Errorf("graph: illegal iterator type: %d", *t)
}
return []byte(types[*t]), nil
}
func (t *Type) UnmarshalText(text []byte) error {
s := string(text)
for i, c := range types[1:] {
if c == s {
*t = Type(i + 1)
return nil
}
}
return fmt.Errorf("graph: unknown iterator label: %q", text)
}
type StatsContainer struct {
UID uint64
Type Type
IteratorStats
SubIts []StatsContainer
}
func DumpStats(it Iterator) StatsContainer {
var out StatsContainer
out.IteratorStats = it.Stats()
out.Type = it.Type()
out.UID = it.UID()
for _, sub := range it.SubIterators() {
out.SubIts = append(out.SubIts, DumpStats(sub))
}
return out
}
// Utility logging functions for when an iterator gets called Next upon, or Contains upon, as
// well as what they return. Highly useful for tracing the execution path of a query.
func ContainsLogIn(it Iterator, val Value) {
if glog.V(4) {
glog.V(4).Infof("%s %d CHECK CONTAINS %d", strings.ToUpper(it.Type().String()), it.UID(), val)
}
}
func ContainsLogOut(it Iterator, val Value, good bool) bool {
if glog.V(4) {
if good {
glog.V(4).Infof("%s %d CHECK CONTAINS %d GOOD", strings.ToUpper(it.Type().String()), it.UID(), val)
} else {
glog.V(4).Infof("%s %d CHECK CONTAINS %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) 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 ok
}