...

Source file src/github.com/go-kivik/kivik/v4/x/memorydb/store.go

Documentation: github.com/go-kivik/kivik/v4/x/memorydb

     1  // Licensed under the Apache License, Version 2.0 (the "License"); you may not
     2  // use this file except in compliance with the License. You may obtain a copy of
     3  // the License at
     4  //
     5  //  http://www.apache.org/licenses/LICENSE-2.0
     6  //
     7  // Unless required by applicable law or agreed to in writing, software
     8  // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
     9  // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    10  // License for the specific language governing permissions and limitations under
    11  // the License.
    12  
    13  package memorydb
    14  
    15  import (
    16  	"encoding/json"
    17  	"errors"
    18  	"fmt"
    19  	"math/rand"
    20  	"net/http"
    21  	"strings"
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/go-kivik/kivik/v4/driver"
    26  )
    27  
    28  type file struct {
    29  	ContentType string
    30  	Data        []byte
    31  }
    32  
    33  type document struct {
    34  	revs []*revision
    35  }
    36  
    37  type revision struct {
    38  	data        []byte
    39  	ID          int64
    40  	Rev         string
    41  	Deleted     bool
    42  	Attachments map[string]file
    43  }
    44  
    45  type database struct {
    46  	mu       sync.RWMutex
    47  	docs     map[string]*document
    48  	deleted  bool
    49  	security *driver.Security
    50  }
    51  
    52  var (
    53  	rnd   *rand.Rand
    54  	rndMU = &sync.Mutex{}
    55  )
    56  
    57  func init() {
    58  	rnd = rand.New(rand.NewSource(time.Now().UnixNano()))
    59  }
    60  
    61  func (d *database) getRevision(docID, rev string) (*revision, bool) {
    62  	d.mu.RLock()
    63  	defer d.mu.RUnlock()
    64  	doc, ok := d.docs[docID]
    65  	if !ok {
    66  		return nil, false
    67  	}
    68  	for _, r := range doc.revs {
    69  		if rev == fmt.Sprintf("%d-%s", r.ID, r.Rev) {
    70  			return r, true
    71  		}
    72  	}
    73  	return nil, false
    74  }
    75  
    76  func (d *database) docExists(docID string) bool {
    77  	d.mu.RLock()
    78  	defer d.mu.RUnlock()
    79  	_, ok := d.docs[docID]
    80  	return ok
    81  }
    82  
    83  func (d *database) latestRevision(docID string) (*revision, bool) {
    84  	d.mu.RLock()
    85  	defer d.mu.RUnlock()
    86  	doc, ok := d.docs[docID]
    87  	if ok {
    88  		last := doc.revs[len(doc.revs)-1]
    89  		return last, true
    90  	}
    91  	return nil, false
    92  }
    93  
    94  type couchDoc map[string]interface{}
    95  
    96  func (d couchDoc) ID() string {
    97  	id, _ := d["_id"].(string)
    98  	return id
    99  }
   100  
   101  func (d couchDoc) Rev() string {
   102  	rev, _ := d["_rev"].(string)
   103  	return rev
   104  }
   105  
   106  func toCouchDoc(i interface{}) (couchDoc, error) {
   107  	if d, ok := i.(couchDoc); ok {
   108  		return d, nil
   109  	}
   110  	asJSON, err := json.Marshal(i)
   111  	if err != nil {
   112  		return nil, statusError{status: http.StatusBadRequest, error: err}
   113  	}
   114  	var m couchDoc
   115  	if e := json.Unmarshal(asJSON, &m); e != nil {
   116  		return nil, statusError{status: http.StatusInternalServerError, error: errors.New("THIS IS A BUG: failed to decode encoded document")}
   117  	}
   118  	return m, nil
   119  }
   120  
   121  func (d *database) addRevision(doc couchDoc) string {
   122  	d.mu.Lock()
   123  	defer d.mu.Unlock()
   124  	id, ok := doc["_id"].(string)
   125  	if !ok {
   126  		panic("_id missing or not a string")
   127  	}
   128  	isLocal := strings.HasPrefix(id, "_local/")
   129  	if d.docs[id] == nil {
   130  		d.docs[id] = &document{
   131  			revs: make([]*revision, 0, 1),
   132  		}
   133  	}
   134  	var revID int64
   135  	var revStr string
   136  	if isLocal {
   137  		revID = 1
   138  		revStr = "0"
   139  	} else {
   140  		l := len(d.docs[id].revs)
   141  		if l == 0 {
   142  			revID = 1
   143  		} else {
   144  			revID = d.docs[id].revs[l-1].ID + 1
   145  		}
   146  		revStr = randStr()
   147  	}
   148  	rev := fmt.Sprintf("%d-%s", revID, revStr)
   149  	doc["_rev"] = rev
   150  	data, err := json.Marshal(doc)
   151  	if err != nil {
   152  		panic(err)
   153  	}
   154  	deleted, _ := doc["_deleted"].(bool)
   155  	newRev := &revision{
   156  		data:    data,
   157  		ID:      revID,
   158  		Rev:     revStr,
   159  		Deleted: deleted,
   160  	}
   161  	if isLocal {
   162  		d.docs[id].revs = []*revision{newRev}
   163  	} else {
   164  		d.docs[id].revs = append(d.docs[id].revs, newRev)
   165  	}
   166  	return rev
   167  }
   168  

View as plain text