open-vault/vendor/github.com/couchbase/gocb/v2/collection_crud.go

1041 lines
27 KiB
Go

package gocb
import (
"errors"
"sync"
"time"
gocbcore "github.com/couchbase/gocbcore/v9"
)
type kvProvider interface {
Add(opts gocbcore.AddOptions, cb gocbcore.StoreCallback) (gocbcore.PendingOp, error)
Set(opts gocbcore.SetOptions, cb gocbcore.StoreCallback) (gocbcore.PendingOp, error)
Replace(opts gocbcore.ReplaceOptions, cb gocbcore.StoreCallback) (gocbcore.PendingOp, error)
Get(opts gocbcore.GetOptions, cb gocbcore.GetCallback) (gocbcore.PendingOp, error)
GetOneReplica(opts gocbcore.GetOneReplicaOptions, cb gocbcore.GetReplicaCallback) (gocbcore.PendingOp, error)
Observe(opts gocbcore.ObserveOptions, cb gocbcore.ObserveCallback) (gocbcore.PendingOp, error)
ObserveVb(opts gocbcore.ObserveVbOptions, cb gocbcore.ObserveVbCallback) (gocbcore.PendingOp, error)
GetMeta(opts gocbcore.GetMetaOptions, cb gocbcore.GetMetaCallback) (gocbcore.PendingOp, error)
Delete(opts gocbcore.DeleteOptions, cb gocbcore.DeleteCallback) (gocbcore.PendingOp, error)
LookupIn(opts gocbcore.LookupInOptions, cb gocbcore.LookupInCallback) (gocbcore.PendingOp, error)
MutateIn(opts gocbcore.MutateInOptions, cb gocbcore.MutateInCallback) (gocbcore.PendingOp, error)
GetAndTouch(opts gocbcore.GetAndTouchOptions, cb gocbcore.GetAndTouchCallback) (gocbcore.PendingOp, error)
GetAndLock(opts gocbcore.GetAndLockOptions, cb gocbcore.GetAndLockCallback) (gocbcore.PendingOp, error)
Unlock(opts gocbcore.UnlockOptions, cb gocbcore.UnlockCallback) (gocbcore.PendingOp, error)
Touch(opts gocbcore.TouchOptions, cb gocbcore.TouchCallback) (gocbcore.PendingOp, error)
Increment(opts gocbcore.CounterOptions, cb gocbcore.CounterCallback) (gocbcore.PendingOp, error)
Decrement(opts gocbcore.CounterOptions, cb gocbcore.CounterCallback) (gocbcore.PendingOp, error)
Append(opts gocbcore.AdjoinOptions, cb gocbcore.AdjoinCallback) (gocbcore.PendingOp, error)
Prepend(opts gocbcore.AdjoinOptions, cb gocbcore.AdjoinCallback) (gocbcore.PendingOp, error)
ConfigSnapshot() (*gocbcore.ConfigSnapshot, error)
}
// Cas represents the specific state of a document on the cluster.
type Cas gocbcore.Cas
// InsertOptions are options that can be applied to an Insert operation.
type InsertOptions struct {
Expiry time.Duration
PersistTo uint
ReplicateTo uint
DurabilityLevel DurabilityLevel
Transcoder Transcoder
Timeout time.Duration
RetryStrategy RetryStrategy
}
// Insert creates a new document in the Collection.
func (c *Collection) Insert(id string, val interface{}, opts *InsertOptions) (mutOut *MutationResult, errOut error) {
if opts == nil {
opts = &InsertOptions{}
}
opm := c.newKvOpManager("Insert", nil)
defer opm.Finish()
opm.SetDocumentID(id)
opm.SetTranscoder(opts.Transcoder)
opm.SetValue(val)
opm.SetDuraOptions(opts.PersistTo, opts.ReplicateTo, opts.DurabilityLevel)
opm.SetRetryStrategy(opts.RetryStrategy)
opm.SetTimeout(opts.Timeout)
if err := opm.CheckReadyForOp(); err != nil {
return nil, err
}
agent, err := c.getKvProvider()
if err != nil {
return nil, err
}
err = opm.Wait(agent.Add(gocbcore.AddOptions{
Key: opm.DocumentID(),
Value: opm.ValueBytes(),
Flags: opm.ValueFlags(),
Expiry: durationToExpiry(opts.Expiry),
CollectionName: opm.CollectionName(),
ScopeName: opm.ScopeName(),
DurabilityLevel: opm.DurabilityLevel(),
DurabilityLevelTimeout: opm.DurabilityTimeout(),
RetryStrategy: opm.RetryStrategy(),
TraceContext: opm.TraceSpan(),
Deadline: opm.Deadline(),
}, func(res *gocbcore.StoreResult, err error) {
if err != nil {
errOut = opm.EnhanceErr(err)
opm.Reject()
return
}
mutOut = &MutationResult{}
mutOut.cas = Cas(res.Cas)
mutOut.mt = opm.EnhanceMt(res.MutationToken)
opm.Resolve(mutOut.mt)
}))
if err != nil {
errOut = err
}
return
}
// UpsertOptions are options that can be applied to an Upsert operation.
type UpsertOptions struct {
Expiry time.Duration
PersistTo uint
ReplicateTo uint
DurabilityLevel DurabilityLevel
Transcoder Transcoder
Timeout time.Duration
RetryStrategy RetryStrategy
}
// Upsert creates a new document in the Collection if it does not exist, if it does exist then it updates it.
func (c *Collection) Upsert(id string, val interface{}, opts *UpsertOptions) (mutOut *MutationResult, errOut error) {
if opts == nil {
opts = &UpsertOptions{}
}
opm := c.newKvOpManager("Upsert", nil)
defer opm.Finish()
opm.SetDocumentID(id)
opm.SetTranscoder(opts.Transcoder)
opm.SetValue(val)
opm.SetDuraOptions(opts.PersistTo, opts.ReplicateTo, opts.DurabilityLevel)
opm.SetRetryStrategy(opts.RetryStrategy)
opm.SetTimeout(opts.Timeout)
if err := opm.CheckReadyForOp(); err != nil {
return nil, err
}
agent, err := c.getKvProvider()
if err != nil {
return nil, err
}
err = opm.Wait(agent.Set(gocbcore.SetOptions{
Key: opm.DocumentID(),
Value: opm.ValueBytes(),
Flags: opm.ValueFlags(),
Expiry: durationToExpiry(opts.Expiry),
CollectionName: opm.CollectionName(),
ScopeName: opm.ScopeName(),
DurabilityLevel: opm.DurabilityLevel(),
DurabilityLevelTimeout: opm.DurabilityTimeout(),
RetryStrategy: opm.RetryStrategy(),
TraceContext: opm.TraceSpan(),
Deadline: opm.Deadline(),
}, func(res *gocbcore.StoreResult, err error) {
if err != nil {
errOut = opm.EnhanceErr(err)
opm.Reject()
return
}
mutOut = &MutationResult{}
mutOut.cas = Cas(res.Cas)
mutOut.mt = opm.EnhanceMt(res.MutationToken)
opm.Resolve(mutOut.mt)
}))
if err != nil {
errOut = err
}
return
}
// ReplaceOptions are the options available to a Replace operation.
type ReplaceOptions struct {
Expiry time.Duration
Cas Cas
PersistTo uint
ReplicateTo uint
DurabilityLevel DurabilityLevel
Transcoder Transcoder
Timeout time.Duration
RetryStrategy RetryStrategy
}
// Replace updates a document in the collection.
func (c *Collection) Replace(id string, val interface{}, opts *ReplaceOptions) (mutOut *MutationResult, errOut error) {
if opts == nil {
opts = &ReplaceOptions{}
}
opm := c.newKvOpManager("Replace", nil)
defer opm.Finish()
opm.SetDocumentID(id)
opm.SetTranscoder(opts.Transcoder)
opm.SetValue(val)
opm.SetDuraOptions(opts.PersistTo, opts.ReplicateTo, opts.DurabilityLevel)
opm.SetRetryStrategy(opts.RetryStrategy)
opm.SetTimeout(opts.Timeout)
if err := opm.CheckReadyForOp(); err != nil {
return nil, err
}
agent, err := c.getKvProvider()
if err != nil {
return nil, err
}
err = opm.Wait(agent.Replace(gocbcore.ReplaceOptions{
Key: opm.DocumentID(),
Value: opm.ValueBytes(),
Flags: opm.ValueFlags(),
Expiry: durationToExpiry(opts.Expiry),
Cas: gocbcore.Cas(opts.Cas),
CollectionName: opm.CollectionName(),
ScopeName: opm.ScopeName(),
DurabilityLevel: opm.DurabilityLevel(),
DurabilityLevelTimeout: opm.DurabilityTimeout(),
RetryStrategy: opm.RetryStrategy(),
TraceContext: opm.TraceSpan(),
Deadline: opm.Deadline(),
}, func(res *gocbcore.StoreResult, err error) {
if err != nil {
errOut = opm.EnhanceErr(err)
opm.Reject()
return
}
mutOut = &MutationResult{}
mutOut.cas = Cas(res.Cas)
mutOut.mt = opm.EnhanceMt(res.MutationToken)
opm.Resolve(mutOut.mt)
}))
if err != nil {
errOut = err
}
return
}
// GetOptions are the options available to a Get operation.
type GetOptions struct {
WithExpiry bool
// Project causes the Get operation to only fetch the fields indicated
// by the paths. The result of the operation is then treated as a
// standard GetResult.
Project []string
Transcoder Transcoder
Timeout time.Duration
RetryStrategy RetryStrategy
}
// Get performs a fetch operation against the collection. This can take 3 paths, a standard full document
// fetch, a subdocument full document fetch also fetching document expiry (when WithExpiry is set),
// or a subdocument fetch (when Project is used).
func (c *Collection) Get(id string, opts *GetOptions) (docOut *GetResult, errOut error) {
if opts == nil {
opts = &GetOptions{}
}
if len(opts.Project) == 0 && !opts.WithExpiry {
return c.getDirect(id, opts)
}
return c.getProjected(id, opts)
}
func (c *Collection) getDirect(id string, opts *GetOptions) (docOut *GetResult, errOut error) {
if opts == nil {
opts = &GetOptions{}
}
opm := c.newKvOpManager("Get", nil)
defer opm.Finish()
opm.SetDocumentID(id)
opm.SetTranscoder(opts.Transcoder)
opm.SetRetryStrategy(opts.RetryStrategy)
opm.SetTimeout(opts.Timeout)
if err := opm.CheckReadyForOp(); err != nil {
return nil, err
}
agent, err := c.getKvProvider()
if err != nil {
return nil, err
}
err = opm.Wait(agent.Get(gocbcore.GetOptions{
Key: opm.DocumentID(),
CollectionName: opm.CollectionName(),
ScopeName: opm.ScopeName(),
RetryStrategy: opm.RetryStrategy(),
TraceContext: opm.TraceSpan(),
Deadline: opm.Deadline(),
}, func(res *gocbcore.GetResult, err error) {
if err != nil {
errOut = opm.EnhanceErr(err)
opm.Reject()
return
}
doc := &GetResult{
Result: Result{
cas: Cas(res.Cas),
},
transcoder: opm.Transcoder(),
contents: res.Value,
flags: res.Flags,
}
docOut = doc
opm.Resolve(nil)
}))
if err != nil {
errOut = err
}
return
}
func (c *Collection) getProjected(id string, opts *GetOptions) (docOut *GetResult, errOut error) {
if opts == nil {
opts = &GetOptions{}
}
opm := c.newKvOpManager("Get", nil)
defer opm.Finish()
opm.SetDocumentID(id)
opm.SetTranscoder(opts.Transcoder)
opm.SetRetryStrategy(opts.RetryStrategy)
opm.SetTimeout(opts.Timeout)
if opts.Transcoder != nil {
return nil, errors.New("Cannot specify custom transcoder for projected gets")
}
if err := opm.CheckReadyForOp(); err != nil {
return nil, err
}
numProjects := len(opts.Project)
if opts.WithExpiry {
numProjects = 1 + numProjects
}
projections := opts.Project
if numProjects > 16 {
projections = nil
}
var ops []LookupInSpec
if opts.WithExpiry {
ops = append(ops, GetSpec("$document.exptime", &GetSpecOptions{IsXattr: true}))
}
if len(projections) == 0 {
ops = append(ops, GetSpec("", nil))
} else {
for _, path := range projections {
ops = append(ops, GetSpec(path, nil))
}
}
result, err := c.internalLookupIn(opm, ops, false)
if err != nil {
return nil, err
}
doc := &GetResult{}
if opts.WithExpiry {
// if expiration was requested then extract and remove it from the results
err = result.ContentAt(0, &doc.expiry)
if err != nil {
return nil, err
}
ops = ops[1:]
result.contents = result.contents[1:]
}
doc.transcoder = opm.Transcoder()
doc.cas = result.cas
if projections == nil {
err = doc.fromFullProjection(ops, result, opts.Project)
if err != nil {
return nil, err
}
} else {
err = doc.fromSubDoc(ops, result)
if err != nil {
return nil, err
}
}
return doc, nil
}
// ExistsOptions are the options available to the Exists command.
type ExistsOptions struct {
Timeout time.Duration
RetryStrategy RetryStrategy
}
// Exists checks if a document exists for the given id.
func (c *Collection) Exists(id string, opts *ExistsOptions) (docOut *ExistsResult, errOut error) {
if opts == nil {
opts = &ExistsOptions{}
}
opm := c.newKvOpManager("Exists", nil)
defer opm.Finish()
opm.SetDocumentID(id)
opm.SetRetryStrategy(opts.RetryStrategy)
opm.SetTimeout(opts.Timeout)
if err := opm.CheckReadyForOp(); err != nil {
return nil, err
}
agent, err := c.getKvProvider()
if err != nil {
return nil, err
}
err = opm.Wait(agent.GetMeta(gocbcore.GetMetaOptions{
Key: opm.DocumentID(),
CollectionName: opm.CollectionName(),
ScopeName: opm.ScopeName(),
RetryStrategy: opm.RetryStrategy(),
TraceContext: opm.TraceSpan,
Deadline: opm.Deadline(),
}, func(res *gocbcore.GetMetaResult, err error) {
if errors.Is(err, ErrDocumentNotFound) {
docOut = &ExistsResult{
Result: Result{
cas: Cas(0),
},
docExists: false,
}
opm.Resolve(nil)
return
}
if err != nil {
errOut = opm.EnhanceErr(err)
opm.Reject()
return
}
if res != nil {
docOut = &ExistsResult{
Result: Result{
cas: Cas(res.Cas),
},
docExists: res.Deleted == 0,
}
}
opm.Resolve(nil)
}))
if err != nil {
errOut = err
}
return
}
func (c *Collection) getOneReplica(
span requestSpanContext,
id string,
replicaIdx int,
transcoder Transcoder,
retryStrategy RetryStrategy,
cancelCh chan struct{},
timeout time.Duration,
) (docOut *GetReplicaResult, errOut error) {
opm := c.newKvOpManager("getOneReplica", span)
defer opm.Finish()
opm.SetDocumentID(id)
opm.SetTranscoder(transcoder)
opm.SetRetryStrategy(retryStrategy)
opm.SetTimeout(timeout)
opm.SetCancelCh(cancelCh)
agent, err := c.getKvProvider()
if err != nil {
return nil, err
}
if replicaIdx == 0 {
err = opm.Wait(agent.Get(gocbcore.GetOptions{
Key: opm.DocumentID(),
CollectionName: opm.CollectionName(),
ScopeName: opm.ScopeName(),
RetryStrategy: opm.RetryStrategy(),
TraceContext: opm.TraceSpan(),
Deadline: opm.Deadline(),
}, func(res *gocbcore.GetResult, err error) {
if err != nil {
errOut = opm.EnhanceErr(err)
opm.Reject()
return
}
docOut = &GetReplicaResult{}
docOut.cas = Cas(res.Cas)
docOut.transcoder = opm.Transcoder()
docOut.contents = res.Value
docOut.flags = res.Flags
docOut.isReplica = false
opm.Resolve(nil)
}))
if err != nil {
errOut = err
}
return
}
err = opm.Wait(agent.GetOneReplica(gocbcore.GetOneReplicaOptions{
Key: opm.DocumentID(),
ReplicaIdx: replicaIdx,
CollectionName: opm.CollectionName(),
ScopeName: opm.ScopeName(),
RetryStrategy: opm.RetryStrategy(),
TraceContext: opm.TraceSpan(),
Deadline: opm.Deadline(),
}, func(res *gocbcore.GetReplicaResult, err error) {
if err != nil {
errOut = opm.EnhanceErr(err)
opm.Reject()
return
}
docOut = &GetReplicaResult{}
docOut.cas = Cas(res.Cas)
docOut.transcoder = opm.Transcoder()
docOut.contents = res.Value
docOut.flags = res.Flags
docOut.isReplica = true
opm.Resolve(nil)
}))
if err != nil {
errOut = err
}
return
}
// GetAllReplicaOptions are the options available to the GetAllReplicas command.
type GetAllReplicaOptions struct {
Transcoder Transcoder
Timeout time.Duration
RetryStrategy RetryStrategy
}
// GetAllReplicasResult represents the results of a GetAllReplicas operation.
type GetAllReplicasResult struct {
lock sync.Mutex
totalRequests uint32
totalResults uint32
resCh chan *GetReplicaResult
cancelCh chan struct{}
}
func (r *GetAllReplicasResult) addResult(res *GetReplicaResult) {
// We use a lock here because the alternative means that there is a race
// between the channel writes from multiple results and the channels being
// closed. IE: T1-Incr, T2-Incr, T2-Send, T2-Close, T1-Send[PANIC]
r.lock.Lock()
r.totalResults++
resultCount := r.totalResults
if resultCount <= r.totalRequests {
r.resCh <- res
}
if resultCount == r.totalRequests {
close(r.cancelCh)
close(r.resCh)
}
r.lock.Unlock()
}
// Next fetches the next replica result.
func (r *GetAllReplicasResult) Next() *GetReplicaResult {
return <-r.resCh
}
// Close cancels all remaining get replica requests.
func (r *GetAllReplicasResult) Close() error {
// See addResult discussion on lock usage.
r.lock.Lock()
// Note that this number increment must be high enough to be clear that
// the result set was closed, but low enough that it won't overflow if
// additional result objects are processed after the close.
prevResultCount := r.totalResults
r.totalResults += 100000
// We only have to close everything if the addResult method didn't already
// close them due to already having completed every request
if prevResultCount < r.totalRequests {
close(r.cancelCh)
close(r.resCh)
}
r.lock.Unlock()
return nil
}
// GetAllReplicas returns the value of a particular document from all replica servers. This will return an iterable
// which streams results one at a time.
func (c *Collection) GetAllReplicas(id string, opts *GetAllReplicaOptions) (docOut *GetAllReplicasResult, errOut error) {
if opts == nil {
opts = &GetAllReplicaOptions{}
}
span := c.startKvOpTrace("GetAllReplicas", nil)
defer span.Finish()
// Timeout needs to be adjusted here, since we use it at the bottom of this
// function, but the remaining options are all passed downwards and get handled
// by those functions rather than us.
timeout := opts.Timeout
if timeout == 0 {
timeout = c.timeoutsConfig.KVTimeout
}
deadline := time.Now().Add(timeout)
transcoder := opts.Transcoder
retryStrategy := opts.RetryStrategy
agent, err := c.getKvProvider()
if err != nil {
return nil, err
}
snapshot, err := agent.ConfigSnapshot()
if err != nil {
return nil, err
}
numReplicas, err := snapshot.NumReplicas()
if err != nil {
return nil, err
}
numServers := numReplicas + 1
outCh := make(chan *GetReplicaResult, numServers)
cancelCh := make(chan struct{})
repRes := &GetAllReplicasResult{
totalRequests: uint32(numServers),
resCh: outCh,
cancelCh: cancelCh,
}
// Loop all the servers and populate the result object
for replicaIdx := 0; replicaIdx < numServers; replicaIdx++ {
go func(replicaIdx int) {
// This timeout value will cause the getOneReplica operation to timeout after our deadline has expired,
// as the deadline has already begun. getOneReplica timing out before our deadline would cause inconsistent
// behaviour.
res, err := c.getOneReplica(span, id, replicaIdx, transcoder, retryStrategy, cancelCh, timeout)
if err != nil {
logDebugf("Failed to fetch replica from replica %d: %s", replicaIdx, err)
} else {
repRes.addResult(res)
}
}(replicaIdx)
}
// Start a timer to close it after the deadline
go func() {
select {
case <-time.After(time.Until(deadline)):
// If we timeout, we should close the result
err := repRes.Close()
if err != nil {
logDebugf("failed to close GetAllReplicas response: %s", err)
}
return
case <-cancelCh:
// If the cancel channel closes, we are done
return
}
}()
return repRes, nil
}
// GetAnyReplicaOptions are the options available to the GetAnyReplica command.
type GetAnyReplicaOptions struct {
Transcoder Transcoder
Timeout time.Duration
RetryStrategy RetryStrategy
}
// GetAnyReplica returns the value of a particular document from a replica server.
func (c *Collection) GetAnyReplica(id string, opts *GetAnyReplicaOptions) (docOut *GetReplicaResult, errOut error) {
if opts == nil {
opts = &GetAnyReplicaOptions{}
}
span := c.startKvOpTrace("GetAnyReplica", nil)
defer span.Finish()
repRes, err := c.GetAllReplicas(id, &GetAllReplicaOptions{
Timeout: opts.Timeout,
Transcoder: opts.Transcoder,
RetryStrategy: opts.RetryStrategy,
})
if err != nil {
return nil, err
}
// Try to fetch at least one result
res := repRes.Next()
if res == nil {
return nil, &KeyValueError{
InnerError: ErrDocumentUnretrievable,
BucketName: c.bucketName(),
ScopeName: c.scope,
CollectionName: c.collectionName,
}
}
// Close the results channel since we don't care about any of the
// remaining result objects at this point.
err = repRes.Close()
if err != nil {
logDebugf("failed to close GetAnyReplica response: %s", err)
}
return res, nil
}
// RemoveOptions are the options available to the Remove command.
type RemoveOptions struct {
Cas Cas
PersistTo uint
ReplicateTo uint
DurabilityLevel DurabilityLevel
Timeout time.Duration
RetryStrategy RetryStrategy
}
// Remove removes a document from the collection.
func (c *Collection) Remove(id string, opts *RemoveOptions) (mutOut *MutationResult, errOut error) {
if opts == nil {
opts = &RemoveOptions{}
}
opm := c.newKvOpManager("Remove", nil)
defer opm.Finish()
opm.SetDocumentID(id)
opm.SetDuraOptions(opts.PersistTo, opts.ReplicateTo, opts.DurabilityLevel)
opm.SetRetryStrategy(opts.RetryStrategy)
opm.SetTimeout(opts.Timeout)
if err := opm.CheckReadyForOp(); err != nil {
return nil, err
}
agent, err := c.getKvProvider()
if err != nil {
return nil, err
}
err = opm.Wait(agent.Delete(gocbcore.DeleteOptions{
Key: opm.DocumentID(),
Cas: gocbcore.Cas(opts.Cas),
CollectionName: opm.CollectionName(),
ScopeName: opm.ScopeName(),
DurabilityLevel: opm.DurabilityLevel(),
DurabilityLevelTimeout: opm.DurabilityTimeout(),
RetryStrategy: opm.RetryStrategy(),
TraceContext: opm.TraceSpan(),
Deadline: opm.Deadline(),
}, func(res *gocbcore.DeleteResult, err error) {
if err != nil {
errOut = opm.EnhanceErr(err)
opm.Reject()
return
}
mutOut = &MutationResult{}
mutOut.cas = Cas(res.Cas)
mutOut.mt = opm.EnhanceMt(res.MutationToken)
opm.Resolve(mutOut.mt)
}))
if err != nil {
errOut = err
}
return
}
// GetAndTouchOptions are the options available to the GetAndTouch operation.
type GetAndTouchOptions struct {
Transcoder Transcoder
Timeout time.Duration
RetryStrategy RetryStrategy
}
// GetAndTouch retrieves a document and simultaneously updates its expiry time.
func (c *Collection) GetAndTouch(id string, expiry time.Duration, opts *GetAndTouchOptions) (docOut *GetResult, errOut error) {
if opts == nil {
opts = &GetAndTouchOptions{}
}
opm := c.newKvOpManager("GetAndTouch", nil)
defer opm.Finish()
opm.SetDocumentID(id)
opm.SetTranscoder(opts.Transcoder)
opm.SetRetryStrategy(opts.RetryStrategy)
opm.SetTimeout(opts.Timeout)
if err := opm.CheckReadyForOp(); err != nil {
return nil, err
}
agent, err := c.getKvProvider()
if err != nil {
return nil, err
}
err = opm.Wait(agent.GetAndTouch(gocbcore.GetAndTouchOptions{
Key: opm.DocumentID(),
Expiry: durationToExpiry(expiry),
CollectionName: opm.CollectionName(),
ScopeName: opm.ScopeName(),
RetryStrategy: opm.RetryStrategy(),
TraceContext: opm.TraceSpan(),
Deadline: opm.Deadline(),
}, func(res *gocbcore.GetAndTouchResult, err error) {
if err != nil {
errOut = opm.EnhanceErr(err)
opm.Reject()
return
}
if res != nil {
doc := &GetResult{
Result: Result{
cas: Cas(res.Cas),
},
transcoder: opm.Transcoder(),
contents: res.Value,
flags: res.Flags,
}
docOut = doc
}
opm.Resolve(nil)
}))
if err != nil {
errOut = err
}
return
}
// GetAndLockOptions are the options available to the GetAndLock operation.
type GetAndLockOptions struct {
Transcoder Transcoder
Timeout time.Duration
RetryStrategy RetryStrategy
}
// GetAndLock locks a document for a period of time, providing exclusive RW access to it.
// A lockTime value of over 30 seconds will be treated as 30 seconds. The resolution used to send this value to
// the server is seconds and is calculated using uint32(lockTime/time.Second).
func (c *Collection) GetAndLock(id string, lockTime time.Duration, opts *GetAndLockOptions) (docOut *GetResult, errOut error) {
if opts == nil {
opts = &GetAndLockOptions{}
}
opm := c.newKvOpManager("GetAndLock", nil)
defer opm.Finish()
opm.SetDocumentID(id)
opm.SetTranscoder(opts.Transcoder)
opm.SetRetryStrategy(opts.RetryStrategy)
opm.SetTimeout(opts.Timeout)
if err := opm.CheckReadyForOp(); err != nil {
return nil, err
}
agent, err := c.getKvProvider()
if err != nil {
return nil, err
}
err = opm.Wait(agent.GetAndLock(gocbcore.GetAndLockOptions{
Key: opm.DocumentID(),
LockTime: uint32(lockTime / time.Second),
CollectionName: opm.CollectionName(),
ScopeName: opm.ScopeName(),
RetryStrategy: opm.RetryStrategy(),
TraceContext: opm.TraceSpan(),
Deadline: opm.Deadline(),
}, func(res *gocbcore.GetAndLockResult, err error) {
if err != nil {
errOut = opm.EnhanceErr(err)
opm.Reject()
return
}
if res != nil {
doc := &GetResult{
Result: Result{
cas: Cas(res.Cas),
},
transcoder: opm.Transcoder(),
contents: res.Value,
flags: res.Flags,
}
docOut = doc
}
opm.Resolve(nil)
}))
if err != nil {
errOut = err
}
return
}
// UnlockOptions are the options available to the GetAndLock operation.
type UnlockOptions struct {
Timeout time.Duration
RetryStrategy RetryStrategy
}
// Unlock unlocks a document which was locked with GetAndLock.
func (c *Collection) Unlock(id string, cas Cas, opts *UnlockOptions) (errOut error) {
if opts == nil {
opts = &UnlockOptions{}
}
opm := c.newKvOpManager("Unlock", nil)
defer opm.Finish()
opm.SetDocumentID(id)
opm.SetRetryStrategy(opts.RetryStrategy)
opm.SetTimeout(opts.Timeout)
if err := opm.CheckReadyForOp(); err != nil {
return err
}
agent, err := c.getKvProvider()
if err != nil {
return err
}
err = opm.Wait(agent.Unlock(gocbcore.UnlockOptions{
Key: opm.DocumentID(),
Cas: gocbcore.Cas(cas),
CollectionName: opm.CollectionName(),
ScopeName: opm.ScopeName(),
RetryStrategy: opm.RetryStrategy(),
TraceContext: opm.TraceSpan(),
Deadline: opm.Deadline(),
}, func(res *gocbcore.UnlockResult, err error) {
if err != nil {
errOut = opm.EnhanceErr(err)
opm.Reject()
return
}
mt := opm.EnhanceMt(res.MutationToken)
opm.Resolve(mt)
}))
if err != nil {
errOut = err
}
return
}
// TouchOptions are the options available to the Touch operation.
type TouchOptions struct {
Timeout time.Duration
RetryStrategy RetryStrategy
}
// Touch touches a document, specifying a new expiry time for it.
func (c *Collection) Touch(id string, expiry time.Duration, opts *TouchOptions) (mutOut *MutationResult, errOut error) {
if opts == nil {
opts = &TouchOptions{}
}
opm := c.newKvOpManager("Touch", nil)
defer opm.Finish()
opm.SetDocumentID(id)
opm.SetRetryStrategy(opts.RetryStrategy)
opm.SetTimeout(opts.Timeout)
if err := opm.CheckReadyForOp(); err != nil {
return nil, err
}
agent, err := c.getKvProvider()
if err != nil {
return nil, err
}
err = opm.Wait(agent.Touch(gocbcore.TouchOptions{
Key: opm.DocumentID(),
Expiry: durationToExpiry(expiry),
CollectionName: opm.CollectionName(),
ScopeName: opm.ScopeName(),
RetryStrategy: opm.RetryStrategy(),
TraceContext: opm.TraceSpan(),
Deadline: opm.Deadline(),
}, func(res *gocbcore.TouchResult, err error) {
if err != nil {
errOut = opm.EnhanceErr(err)
opm.Reject()
return
}
mutOut = &MutationResult{}
mutOut.cas = Cas(res.Cas)
mutOut.mt = opm.EnhanceMt(res.MutationToken)
opm.Resolve(mutOut.mt)
}))
if err != nil {
errOut = err
}
return
}
// Binary creates and returns a BinaryCollection object.
func (c *Collection) Binary() *BinaryCollection {
return &BinaryCollection{collection: c}
}