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