1// Copyright 2014 The Cayley Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package nosql
16
17import (
18	"context"
19	"fmt"
20
21	"github.com/cayleygraph/cayley/clog"
22	"github.com/cayleygraph/cayley/graph"
23	"github.com/cayleygraph/cayley/graph/iterator"
24	"github.com/cayleygraph/cayley/quad"
25)
26
27var _ graph.Iterator = (*Iterator)(nil)
28
29type Linkage struct {
30	Dir quad.Direction
31	Val NodeHash
32}
33
34type Iterator struct {
35	uid        uint64
36	tags       graph.Tagger
37	qs         *QuadStore
38	collection string
39	limit      int64
40	constraint []FieldFilter
41	links      []Linkage // used in Contains
42
43	iter   DocIterator
44	result graph.Value
45	size   int64
46	err    error
47}
48
49func NewLinksToIterator(qs *QuadStore, collection string, links []Linkage) *Iterator {
50	filters := make([]FieldFilter, 0, len(links))
51	for _, l := range links {
52		filters = append(filters, FieldFilter{
53			Path:   []string{l.Dir.String()},
54			Filter: Equal,
55			Value:  String(l.Val),
56		})
57	}
58	it := NewIterator(qs, collection, filters...)
59	it.links = links
60	return it
61}
62
63func (it *Iterator) makeIterator() DocIterator {
64	q := it.qs.db.Query(it.collection)
65	if len(it.constraint) != 0 {
66		q = q.WithFields(it.constraint...)
67	}
68	if it.limit > 0 {
69		q = q.Limit(int(it.limit))
70	}
71	return q.Iterate()
72}
73
74func NewAllIterator(qs *QuadStore, collection string) *Iterator {
75	return NewIterator(qs, collection)
76}
77
78func NewIterator(qs *QuadStore, collection string, constraints ...FieldFilter) *Iterator {
79	return &Iterator{
80		uid:        iterator.NextUID(),
81		qs:         qs,
82		constraint: constraints,
83		collection: collection,
84		size:       -1,
85	}
86}
87
88func (it *Iterator) UID() uint64 {
89	return it.uid
90}
91
92func (it *Iterator) Reset() {
93	it.Close()
94	it.iter = it.makeIterator()
95}
96
97func (it *Iterator) Close() error {
98	if it.iter != nil {
99		return it.iter.Close()
100	}
101	return nil
102}
103
104func (it *Iterator) Tagger() *graph.Tagger {
105	return &it.tags
106}
107
108func (it *Iterator) TagResults(dst map[string]graph.Value) {
109	it.tags.TagResult(dst, it.Result())
110}
111
112func (it *Iterator) Clone() graph.Iterator {
113	var m *Iterator
114	if len(it.links) == 0 {
115		m = NewIterator(it.qs, it.collection, it.constraint...)
116	} else {
117		m = NewLinksToIterator(it.qs, it.collection, it.links)
118	}
119	m.tags.CopyFrom(it)
120	return m
121}
122
123func (it *Iterator) Next(ctx context.Context) bool {
124	if it.iter == nil {
125		it.iter = it.makeIterator()
126	}
127	var doc Document
128	for {
129		if !it.iter.Next(ctx) {
130			if err := it.iter.Err(); err != nil {
131				it.err = err
132				clog.Errorf("error nexting iterator: %v", err)
133			}
134			return false
135		}
136		doc = it.iter.Doc()
137		if it.collection == colQuads && !checkQuadValid(doc) {
138			continue
139		}
140		break
141	}
142	if it.collection == colQuads {
143		sh, _ := doc[fldSubject].(String)
144		ph, _ := doc[fldPredicate].(String)
145		oh, _ := doc[fldObject].(String)
146		lh, _ := doc[fldLabel].(String)
147		it.result = QuadHash{
148			string(sh), string(ph), string(oh), string(lh),
149		}
150	} else {
151		id, _ := doc[fldHash].(String)
152		it.result = NodeHash(id)
153	}
154	return true
155}
156
157func (it *Iterator) Err() error {
158	return it.err
159}
160
161func (it *Iterator) Result() graph.Value {
162	return it.result
163}
164
165func (it *Iterator) NextPath(ctx context.Context) bool {
166	return false
167}
168
169func (it *Iterator) SubIterators() []graph.Iterator {
170	return nil
171}
172
173func (it *Iterator) Contains(ctx context.Context, v graph.Value) bool {
174	if len(it.links) != 0 {
175		qh := v.(QuadHash)
176		for _, l := range it.links {
177			if l.Val != NodeHash(qh.Get(l.Dir)) {
178				return false
179			}
180		}
181		it.result = v
182		return true
183	}
184	if len(it.constraint) == 0 {
185		it.result = v
186		return true
187	}
188	qv := it.qs.NameOf(v)
189	if qv == nil {
190		return false
191	}
192	d := it.qs.opt.toDocumentValue(qv)
193	for _, f := range it.constraint {
194		if !f.Matches(d) {
195			return false
196		}
197	}
198	it.result = v
199	return true
200}
201
202func (it *Iterator) Size() (int64, bool) {
203	if it.size == -1 {
204		var err error
205		it.size, err = it.qs.getSize(it.collection, it.constraint)
206		if err != nil {
207			it.err = err
208		}
209	}
210	if it.limit > 0 && it.size > it.limit {
211		it.size = it.limit
212	}
213	if it.size < 0 {
214		return it.qs.Size(), false
215	}
216	return it.size, true
217}
218
219func (it *Iterator) Type() graph.Type {
220	if len(it.constraint) == 0 {
221		return graph.All
222	}
223	return "nosql"
224}
225
226func (it *Iterator) Sorted() bool                     { return true }
227func (it *Iterator) Optimize() (graph.Iterator, bool) { return it, false }
228
229func (it *Iterator) String() string {
230	return fmt.Sprintf("NoSQL(%v)", it.collection)
231}
232
233func (it *Iterator) Stats() graph.IteratorStats {
234	size, exact := it.Size()
235	return graph.IteratorStats{
236		ContainsCost: 1,
237		NextCost:     5,
238		Size:         size,
239		ExactSize:    exact,
240	}
241}
242