1//  Copyright (c) 2014 Couchbase, Inc.
2//  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
3//  except in compliance with the License. You may obtain a copy of the License at
4//    http://www.apache.org/licenses/LICENSE-2.0
5//  Unless required by applicable law or agreed to in writing, software distributed under the
6//  License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
7//  either express or implied. See the License for the specific language governing permissions
8//  and limitations under the License.
9
10/*
11
12Package mock provides a fake, mock 100%-in-memory implementation of
13the datastore package, which can be useful for testing.  Because it is
14memory-oriented, performance testing of higher layers may be easier
15with this mock datastore.
16
17*/
18package mock
19
20import (
21	"encoding/json"
22	"fmt"
23	"net/http"
24	"strconv"
25	"strings"
26
27	"github.com/couchbase/query/auth"
28	"github.com/couchbase/query/datastore"
29	"github.com/couchbase/query/errors"
30	"github.com/couchbase/query/expression"
31	"github.com/couchbase/query/logging"
32	"github.com/couchbase/query/timestamp"
33	"github.com/couchbase/query/value"
34)
35
36const (
37	DEFAULT_NUM_NAMESPACES = 1
38	DEFAULT_NUM_KEYSPACES  = 1
39	DEFAULT_NUM_ITEMS      = 100000
40)
41
42// store is the root for the mock-based Store.
43type store struct {
44	path           string
45	namespaces     map[string]*namespace
46	namespaceNames []string
47	params         map[string]int
48}
49
50func (s *store) Id() string {
51	return s.URL()
52}
53
54func (s *store) URL() string {
55	return "mock:" + s.path
56}
57
58func (s *store) Info() datastore.Info {
59	return nil
60}
61
62func (s *store) NamespaceIds() ([]string, errors.Error) {
63	return s.NamespaceNames()
64}
65
66func (s *store) NamespaceNames() ([]string, errors.Error) {
67	return s.namespaceNames, nil
68}
69
70func (s *store) NamespaceById(id string) (p datastore.Namespace, e errors.Error) {
71	return s.NamespaceByName(id)
72}
73
74func (s *store) NamespaceByName(name string) (p datastore.Namespace, e errors.Error) {
75	p, ok := s.namespaces[name]
76	if !ok {
77		p, e = nil, errors.NewOtherNamespaceNotFoundError(nil, name+" for Mock datastore")
78	}
79
80	return
81}
82
83func (s *store) Authorize(*auth.Privileges, auth.Credentials, *http.Request) (auth.AuthenticatedUsers, errors.Error) {
84	return nil, nil
85}
86
87func (s *store) CredsString(req *http.Request) string {
88	return ""
89}
90
91func (s *store) SetLogLevel(level logging.Level) {
92	// No-op. Uses query engine logger.
93}
94
95func (s *store) Inferencer(name datastore.InferenceType) (datastore.Inferencer, errors.Error) {
96	return nil, errors.NewOtherNotImplementedError(nil, "INFER")
97}
98
99func (s *store) Inferencers() ([]datastore.Inferencer, errors.Error) {
100	return nil, errors.NewOtherNotImplementedError(nil, "INFER")
101}
102
103func (s *store) AuditInfo() (*datastore.AuditInfo, errors.Error) {
104	return nil, errors.NewOtherNotImplementedError(nil, "AuditInfo")
105}
106
107func (s *store) ProcessAuditUpdateStream(callb func(uid string) error) errors.Error {
108	return errors.NewOtherNotImplementedError(nil, "ProcessAuditUpdateStream")
109}
110
111func (s *store) UserInfo() (value.Value, errors.Error) {
112	// Stub implementation with fixed content.
113	content := `[{"name":"Ivan Ivanov","id":"ivanivanov","domain":"local","roles":[{"role":"cluster_admin"},
114                        {"role":"bucket_admin","bucket_name":"default"}]},
115                        {"name":"Petr Petrov","id":"petrpetrov","domain":"local","roles":[{"role":"replication_admin"}]}]`
116	jsonData := make([]interface{}, 3)
117	err := json.Unmarshal([]byte(content), &jsonData)
118	if err != nil {
119		return nil, errors.NewServiceErrorInvalidJSON(err)
120	}
121	v := value.NewValue(jsonData)
122	return v, nil
123}
124
125func (s *store) GetUserInfoAll() ([]datastore.User, errors.Error) {
126	return nil, errors.NewOtherNotImplementedError(nil, "GetUserInfoAll")
127}
128
129func (s *store) PutUserInfo(u *datastore.User) errors.Error {
130	return errors.NewOtherNotImplementedError(nil, "PutUserInfo")
131}
132
133func (s *store) GetRolesAll() ([]datastore.Role, errors.Error) {
134	return nil, errors.NewOtherNotImplementedError(nil, "GetRolesAll")
135}
136
137// namespace represents a mock-based Namespace.
138type namespace struct {
139	store         *store
140	name          string
141	keyspaces     map[string]*keyspace
142	keyspaceNames []string
143}
144
145func (p *namespace) DatastoreId() string {
146	return p.store.Id()
147}
148
149func (p *namespace) Id() string {
150	return p.Name()
151}
152
153func (p *namespace) Name() string {
154	return p.name
155}
156
157func (p *namespace) KeyspaceIds() ([]string, errors.Error) {
158	return p.KeyspaceNames()
159}
160
161func (p *namespace) KeyspaceNames() ([]string, errors.Error) {
162	return p.keyspaceNames, nil
163}
164
165func (p *namespace) KeyspaceById(id string) (b datastore.Keyspace, e errors.Error) {
166	return p.KeyspaceByName(id)
167}
168
169func (p *namespace) KeyspaceByName(name string) (b datastore.Keyspace, e errors.Error) {
170	b, ok := p.keyspaces[name]
171	if !ok {
172		b, e = nil, errors.NewOtherKeyspaceNotFoundError(nil, name+" for Mock datastore")
173	}
174
175	return
176}
177
178func (p *namespace) MetadataVersion() uint64 {
179	return 0
180}
181
182// keyspace is a mock-based keyspace.
183type keyspace struct {
184	namespace *namespace
185	name      string
186	nitems    int
187	mi        datastore.Indexer
188}
189
190func (b *keyspace) NamespaceId() string {
191	return b.namespace.Id()
192}
193
194func (b *keyspace) Namespace() datastore.Namespace {
195	return b.namespace
196}
197
198func (b *keyspace) Id() string {
199	return b.Name()
200}
201
202func (b *keyspace) Name() string {
203	return b.name
204}
205
206func (b *keyspace) Count(context datastore.QueryContext) (int64, errors.Error) {
207	return int64(b.nitems), nil
208}
209
210func (b *keyspace) Indexer(name datastore.IndexType) (datastore.Indexer, errors.Error) {
211	return b.mi, nil
212}
213
214func (b *keyspace) Indexers() ([]datastore.Indexer, errors.Error) {
215	return []datastore.Indexer{b.mi}, nil
216}
217
218func (b *keyspace) Fetch(keys []string, keysMap map[string]value.AnnotatedValue,
219	context datastore.QueryContext, subPaths []string) []errors.Error {
220	var errs []errors.Error
221
222	for _, k := range keys {
223		item, e := b.fetchOne(k)
224		if e != nil {
225			if errs == nil {
226				errs = make([]errors.Error, 0, 1)
227			}
228			errs = append(errs, e)
229			continue
230		}
231
232		if item != nil {
233			item.SetAttachment("meta", map[string]interface{}{
234				"id": k,
235			})
236		}
237
238		keysMap[k] = item
239	}
240	return errs
241}
242
243func (b *keyspace) fetchOne(key string) (value.AnnotatedValue, errors.Error) {
244	i, e := strconv.Atoi(key)
245	if e != nil {
246		return nil, errors.NewOtherKeyNotFoundError(e, fmt.Sprintf("no mock item: %v", key))
247	} else {
248		return genItem(i, b.nitems)
249	}
250}
251
252// generate a mock document - used by fetchOne to mock a document in the keyspace
253func genItem(i int, nitems int) (value.AnnotatedValue, errors.Error) {
254	if i < 0 || i >= nitems {
255		return nil, errors.NewOtherDatastoreError(nil,
256			fmt.Sprintf("item out of mock range: %v [0,%v)", i, nitems))
257	}
258	id := strconv.Itoa(i)
259	doc := value.NewAnnotatedValue(map[string]interface{}{"id": id, "i": float64(i)})
260	doc.SetAttachment("meta", map[string]interface{}{"id": id})
261	return doc, nil
262}
263
264func (b *keyspace) Insert(inserts []value.Pair) ([]value.Pair, errors.Error) {
265	// FIXME
266	return nil, errors.NewOtherNotImplementedError(nil, "for Mock datastore")
267}
268
269func (b *keyspace) Update(updates []value.Pair) ([]value.Pair, errors.Error) {
270	// FIXME
271	return nil, errors.NewOtherNotImplementedError(nil, "for Mock datastore")
272}
273
274func (b *keyspace) Upsert(upserts []value.Pair) ([]value.Pair, errors.Error) {
275	// FIXME
276	return nil, errors.NewOtherNotImplementedError(nil, "for Mock datastore")
277}
278
279func (b *keyspace) Delete(deletes []string, context datastore.QueryContext) ([]string, errors.Error) {
280	// FIXME
281	return nil, errors.NewOtherNotImplementedError(nil, "for Mock datastore")
282}
283
284func (b *keyspace) Release() {
285}
286
287type mockIndexer struct {
288	keyspace *keyspace
289	indexes  map[string]datastore.Index
290	primary  datastore.PrimaryIndex
291}
292
293func newMockIndexer(keyspace *keyspace) datastore.Indexer {
294
295	return &mockIndexer{
296		keyspace: keyspace,
297		indexes:  make(map[string]datastore.Index),
298	}
299}
300
301func (mi *mockIndexer) KeyspaceId() string {
302	return mi.keyspace.Id()
303}
304
305func (mi *mockIndexer) Name() datastore.IndexType {
306	return datastore.DEFAULT
307}
308
309func (mi *mockIndexer) IndexIds() ([]string, errors.Error) {
310	rv := make([]string, 0, len(mi.indexes))
311	for name, _ := range mi.indexes {
312		rv = append(rv, name)
313	}
314	return rv, nil
315}
316
317func (mi *mockIndexer) IndexNames() ([]string, errors.Error) {
318	rv := make([]string, 0, len(mi.indexes))
319	for name, _ := range mi.indexes {
320		rv = append(rv, name)
321	}
322	return rv, nil
323}
324
325func (mi *mockIndexer) IndexById(id string) (datastore.Index, errors.Error) {
326	return mi.IndexByName(id)
327}
328
329func (mi *mockIndexer) IndexByName(name string) (datastore.Index, errors.Error) {
330	index, ok := mi.indexes[name]
331	if !ok {
332		return nil, errors.NewOtherIdxNotFoundError(nil, name+"for Mock datastore")
333	}
334	return index, nil
335}
336
337func (mi *mockIndexer) PrimaryIndexes() ([]datastore.PrimaryIndex, errors.Error) {
338	return []datastore.PrimaryIndex{mi.primary}, nil
339}
340
341func (mi *mockIndexer) Indexes() ([]datastore.Index, errors.Error) {
342	return []datastore.Index{mi.primary}, nil
343}
344
345func (mi *mockIndexer) CreatePrimaryIndex(requestId, name string, with value.Value) (datastore.PrimaryIndex, errors.Error) {
346	if mi.primary == nil {
347		pi := new(primaryIndex)
348		mi.primary = pi
349		pi.keyspace = mi.keyspace
350		pi.name = name
351		pi.indexer = mi
352		mi.indexes[pi.name] = pi
353	}
354
355	return mi.primary, nil
356}
357
358func (mi *mockIndexer) CreateIndex(requestId, name string, seekKey, rangeKey expression.Expressions,
359	where expression.Expression, with value.Value) (datastore.Index, errors.Error) {
360	return nil, errors.NewOtherNotSupportedError(nil, "CREATE INDEX is not supported for mock datastore.")
361}
362
363func (mi *mockIndexer) BuildIndexes(requestId string, names ...string) errors.Error {
364	return errors.NewOtherNotSupportedError(nil, "BUILD INDEXES is not supported for mock datastore.")
365}
366
367func (mi *mockIndexer) Refresh() errors.Error {
368	return nil
369}
370
371func (mi *mockIndexer) MetadataVersion() uint64 {
372	return 0
373}
374
375func (mi *mockIndexer) SetLogLevel(level logging.Level) {
376	// No-op, uses query engine logger
377}
378
379// NewDatastore creates a new mock store for the given "path".  The
380// path has prefix "mock:", with the rest of the path treated as a
381// comma-separated key=value params.  For example:
382// mock:namespaces=2,keyspaces=5,items=50000 The above means 2
383// namespaces.  And, each namespace has 5 keyspaces.  And, each
384// keyspace with 50000 items.  By default, you get...
385// mock:namespaces=1,keyspaces=1,items=100000 Which is what you'd get
386// by specifying a path of just...  mock:
387func NewDatastore(path string) (datastore.Datastore, errors.Error) {
388	if strings.HasPrefix(path, "mock:") {
389		path = path[5:]
390	}
391	params := map[string]int{}
392	for _, kv := range strings.Split(path, ",") {
393		if kv == "" {
394			continue
395		}
396		pair := strings.Split(kv, "=")
397		v, e := strconv.Atoi(pair[1])
398		if e != nil {
399			return nil, errors.NewOtherDatastoreError(e,
400				fmt.Sprintf("could not parse mock param key: %s, val: %s",
401					pair[0], pair[1]))
402		}
403		params[pair[0]] = v
404	}
405	nnamespaces := paramVal(params, "namespaces", DEFAULT_NUM_NAMESPACES)
406	nkeyspaces := paramVal(params, "keyspaces", DEFAULT_NUM_KEYSPACES)
407	nitems := paramVal(params, "items", DEFAULT_NUM_ITEMS)
408	s := &store{path: path, params: params, namespaces: map[string]*namespace{}, namespaceNames: []string{}}
409	for i := 0; i < nnamespaces; i++ {
410		p := &namespace{store: s, name: "p" + strconv.Itoa(i), keyspaces: map[string]*keyspace{}, keyspaceNames: []string{}}
411		for j := 0; j < nkeyspaces; j++ {
412			b := &keyspace{namespace: p, name: "b" + strconv.Itoa(j), nitems: nitems}
413
414			b.mi = newMockIndexer(b)
415			b.mi.CreatePrimaryIndex("", "#primary", nil)
416			p.keyspaces[b.name] = b
417			p.keyspaceNames = append(p.keyspaceNames, b.name)
418		}
419		s.namespaces[p.name] = p
420		s.namespaceNames = append(s.namespaceNames, p.name)
421	}
422	return s, nil
423}
424
425func paramVal(params map[string]int, key string, defaultVal int) int {
426	v, ok := params[key]
427	if ok {
428		return v
429	}
430	return defaultVal
431}
432
433// primaryIndex performs full keyspace scans.
434type primaryIndex struct {
435	name     string
436	keyspace *keyspace
437	indexer  *mockIndexer
438}
439
440func (pi *primaryIndex) KeyspaceId() string {
441	return pi.keyspace.Id()
442}
443
444func (pi *primaryIndex) Id() string {
445	return pi.Name()
446}
447
448func (pi *primaryIndex) Name() string {
449	return pi.name
450}
451
452func (pi *primaryIndex) Type() datastore.IndexType {
453	return datastore.DEFAULT
454}
455
456func (pi *primaryIndex) Indexer() datastore.Indexer {
457	return pi.indexer
458}
459
460func (pi *primaryIndex) SeekKey() expression.Expressions {
461	return nil
462}
463
464func (pi *primaryIndex) RangeKey() expression.Expressions {
465	return nil
466}
467
468func (pi *primaryIndex) Condition() expression.Expression {
469	return nil
470}
471
472func (pi *primaryIndex) IsPrimary() bool {
473	return true
474}
475
476func (pi *primaryIndex) State() (state datastore.IndexState, msg string, err errors.Error) {
477	return datastore.ONLINE, "", nil
478}
479
480func (pi *primaryIndex) Statistics(requestId string, span *datastore.Span) (
481	datastore.Statistics, errors.Error) {
482	return nil, nil
483}
484
485func (pi *primaryIndex) Drop(requestId string) errors.Error {
486	return errors.NewOtherIdxNoDrop(nil, "This primary index cannot be dropped for Mock datastore.")
487}
488
489func (pi *primaryIndex) Scan(requestId string, span *datastore.Span, distinct bool, limit int64,
490	cons datastore.ScanConsistency, vector timestamp.Vector, conn *datastore.IndexConnection) {
491	defer close(conn.EntryChannel())
492
493	// For primary indexes, bounds must always be strings, so we
494	// can just enforce that directly
495	low, high := "", ""
496
497	// Ensure that lower bound is a string, if any
498	if len(span.Range.Low) > 0 {
499		a := span.Range.Low[0].Actual()
500		switch a := a.(type) {
501		case string:
502			low = a
503		default:
504			conn.Error(errors.NewOtherDatastoreError(nil, fmt.Sprintf("Invalid lower bound %v of type %T.", a, a)))
505			return
506		}
507	}
508
509	// Ensure that upper bound is a string, if any
510	if len(span.Range.High) > 0 {
511		a := span.Range.High[0].Actual()
512		switch a := a.(type) {
513		case string:
514			high = a
515		default:
516			conn.Error(errors.NewOtherDatastoreError(nil, fmt.Sprintf("Invalid upper bound %v of type %T.", a, a)))
517			return
518		}
519	}
520
521	if limit == 0 {
522		limit = int64(pi.keyspace.nitems)
523	}
524
525	for i := 0; i < pi.keyspace.nitems && int64(i) < limit; i++ {
526		id := strconv.Itoa(i)
527
528		if low != "" &&
529			(id < low ||
530				(id == low && (span.Range.Inclusion&datastore.LOW == 0))) {
531			continue
532		}
533
534		low = ""
535
536		if high != "" &&
537			(id > high ||
538				(id == high && (span.Range.Inclusion&datastore.HIGH == 0))) {
539			break
540		}
541
542		entry := datastore.IndexEntry{PrimaryKey: id}
543		conn.EntryChannel() <- &entry
544	}
545}
546
547func (pi *primaryIndex) ScanEntries(requestId string, limit int64, cons datastore.ScanConsistency,
548	vector timestamp.Vector, conn *datastore.IndexConnection) {
549	defer close(conn.EntryChannel())
550
551	if limit == 0 {
552		limit = int64(pi.keyspace.nitems)
553	}
554
555	for i := 0; i < pi.keyspace.nitems && int64(i) < limit; i++ {
556		entry := datastore.IndexEntry{PrimaryKey: strconv.Itoa(i)}
557		conn.EntryChannel() <- &entry
558	}
559}
560