/// <summary>
 /// Reads the VBucketUUID and Sequence Number from  the extras if the instance has a <see cref="VBucket"/> -
 /// only persistent Couchbase buckets that use VBucket Hashing support mutation tokens.
 /// </summary>
 /// <param name="buffer">The memcached response buffer.</param>
 public override void ReadExtras(byte[] buffer)
 {
     if (buffer.Length >= 40 && VBucket != null)
     {
         var uuid = Converter.ToInt64(buffer, 24);
         var seqno = Converter.ToInt64(buffer, 32);
         MutationToken = new MutationToken(VBucket.BucketName, (short)VBucket.Index, uuid, seqno);
     }
 }
        public void AddToMutationState_FirstRealTokenThenNull_DoesNothing()
        {
            // Arrange

            var bucket = new Mock<IBucket>();
            bucket.SetupGet(m => m.Name).Returns("default");

            var db = new BucketContext(bucket.Object);

            var token = new MutationToken("default", 1, 2, 3);
            db.AddToMutationState(token);

            // Act

            db.AddToMutationState(null);

            // Assert

            Assert.IsNotNull(db.MutationState);

            var tokens = MutationStateToList(db.MutationState);
            Assert.AreEqual(1, tokens.Count);
            Assert.Contains(token, tokens);
        }
        public void SubmitChanges_WithSave_AddsToMutationState()
        {
            // Arrange

            var token = new MutationToken("default", 1, 2, 3);

            var bucket = new Mock<IBucket>();
            bucket.SetupGet(m => m.Name).Returns("default");
            bucket
                .Setup(m => m.Upsert(It.IsAny<string>(), It.IsAny<object>()))
                .Returns(() =>
                {
                    var result = new Mock<IOperationResult<Beer>>();
                    result.SetupGet(p => p.Success).Returns(true);
                    result.SetupGet(p => p.Token).Returns(token);

                    return result.Object;
                });

            var db = new Mock<BucketContext>(bucket.Object)
            {
                CallBase = true
            };
            db.Setup(m => m.GetDocumentId(It.IsAny<Beer>())).Returns("id");

            db.Object.BeginChangeTracking();
            db.Object.Save(new Beer());

            // Act

            db.Object.SubmitChanges();

            // Assert

            db.Verify(m => m.AddToMutationState(token), Times.Once);
        }
        public void SubmitChanges_WithRemove_AddsToMutationState()
        {
            // Arrange

            var token = new MutationToken("default", 1, 2, 3);

            var bucket = new Mock<IBucket>();
            bucket.SetupGet(m => m.Name).Returns("default");
            bucket
                .Setup(m => m.Remove(It.IsAny<string>()))
                .Returns(() =>
                {
                    var result = new Mock<IOperationResult<Beer>>();
                    result.SetupGet(p => p.Success).Returns(true);
                    result.SetupGet(p => p.Token).Returns(token);

                    return result.Object;
                });

            var db = new Mock<BucketContext>(bucket.Object)
            {
                CallBase = true
            };
            db.Setup(m => m.GetDocumentId(It.IsAny<object>())).Returns("id");

            var document = new Mock<ITrackedDocumentNode>();
            document.SetupGet(m => m.Metadata).Returns(new DocumentMetadata()
            {
                Id = "id"
            });
            document.SetupAllProperties();

            db.Object.BeginChangeTracking();
            ((IChangeTrackableContext) db.Object).Track(document.Object);
            db.Object.Remove(document.Object);

            // Act

            db.Object.SubmitChanges();

            // Assert

            db.Verify(m => m.AddToMutationState(token), Times.Once);
        }
        public void AddToMutationState_TwoRealTokens_CombinesTokens()
        {
            // Arrange

            var bucket = new Mock<IBucket>();
            bucket.SetupGet(m => m.Name).Returns("default");

            var db = new BucketContext(bucket.Object);

            var token1 = new MutationToken("default", 1, 2, 3);
            var token2 = new MutationToken("default", 4, 5, 6);

            // Act

            db.AddToMutationState(token1);
            db.AddToMutationState(token2);

            // Assert

            Assert.IsNotNull(db.MutationState);

            var tokens = MutationStateToList(db.MutationState);
            Assert.AreEqual(2, tokens.Count);
            Assert.Contains(token1, tokens);
            Assert.Contains(token2, tokens);
        }
        /// <summary>
        /// Observes the specified key using the Seqno.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="token">The token.</param>
        /// <param name="replicateTo">The replicate to.</param>
        /// <param name="persistTo">The persist to.</param>
        /// <returns>True if durability constraints were matched.</returns>
        /// <exception cref="DocumentMutationLostException">Thrown if the observed document was lost during
        /// a hard failover because the document did not reach the replica in time.</exception>
        /// <exception cref="ReplicaNotConfiguredException">Thrown if the number of replicas requested
        /// in the ReplicateTo parameter does not match the # of replicas configured on the server.</exception>
        public bool Observe(MutationToken token, ReplicateTo replicateTo, PersistTo persistTo)
        {
            var keyMapper = (VBucketKeyMapper)_configInfo.GetKeyMapper();

            var p = new ObserveParams
            {
                ReplicateTo = replicateTo,
                PersistTo = persistTo,
                Token = token,
                VBucket = keyMapper[token.VBucketId]
            };
            p.CheckConfiguredReplicas();

            var op = new ObserveSeqno(p.Token, _transcoder, _timeout);
            do
            {
                var master = p.VBucket.LocatePrimary();
                var result = master.Send(op);
                var osr = result.Value;

                p.CheckMutationLost(osr);
                p.CheckPersisted(osr);

                if (p.IsDurabilityMet())
                {
                    return true;
                }

                if (CheckReplicas(p, op))
                {
                    return true;
                }

                //prepare for another attempt
                op = (ObserveSeqno)op.Clone();
                p.Reset();
            } while (!op.TimedOut());

            return false;
        }
        /// <summary>
        ///  Performs an observe event on the durability requirements specified on a key asynchronously
        /// </summary>
        /// <param name="token">The <see cref="MutationToken"/> to compare against.</param>
        /// <param name="replicateTo">The number of replicas that the key must be replicated to to satisfy the durability constraint.</param>
        /// <param name="persistTo">The number of replicas that the key must be persisted to to satisfy the durability constraint.</param>
        /// <returns> A <see cref="Task{bool}"/> representing the aynchronous operation.</returns>
        /// <exception cref="ReplicaNotConfiguredException">Thrown if the number of replicas requested
        /// in the ReplicateTo parameter does not match the # of replicas configured on the server.</exception>
        public async Task<bool> ObserveAsync(MutationToken token, ReplicateTo replicateTo, PersistTo persistTo)
        {
            var keyMapper = (VBucketKeyMapper)_configInfo.GetKeyMapper();
            var obParams = new ObserveParams
            {
                ReplicateTo = replicateTo,
                PersistTo = persistTo,
                Token = token,
                VBucket = keyMapper[token.VBucketId]
            };
            obParams.CheckConfiguredReplicas();
            var op = new ObserveSeqno(obParams.Token, _transcoder, _timeout);

            using (var cts = new CancellationTokenSource((int)_timeout))
            {
                //perform the observe operation at the set interval and terminate if not successful by the timeout
                var task = await ObserveEvery(async p =>
                {
                    IServer master;
                    var attempts = 0;
                    while ((master = p.VBucket.LocatePrimary()) == null)
                    {
                        if (attempts++ > 10) { throw new TimeoutException("Could not acquire a server."); }
                        await Task.Delay((int)Math.Pow(2, attempts)).ContinueOnAnyContext();
                    }

                    var result = master.Send(op);
                    var osr = result.Value;

                    p.CheckMutationLost(osr);
                    p.CheckPersisted(osr);

                    if (p.IsDurabilityMet())
                    {
                        return true;
                    }
                    return await CheckReplicasAsync(p, op).ContinueOnAnyContext();
                }, obParams, _interval, op, cts.Token).ContinueOnAnyContext();
                return task;
            }
        }
        /// <summary>
        ///  Performs an observe event on the durability requirements specified on a key asynchronously
        /// </summary>
        /// <param name="token">The <see cref="MutationToken"/> to compare against.</param>
        /// <param name="replicateTo">The number of replicas that the key must be replicated to to satisfy the durability constraint.</param>
        /// <param name="persistTo">The number of replicas that the key must be persisted to to satisfy the durability constraint.</param>
        /// <param name="cts"></param>
        /// <returns> A <see cref="Task{boolean}"/> representing the aynchronous operation.</returns>
        /// <exception cref="ReplicaNotConfiguredException">Thrown if the number of replicas requested
        /// in the ReplicateTo parameter does not match the # of replicas configured on the server.</exception>
        public async Task<bool> ObserveAsync(MutationToken token, ReplicateTo replicateTo, PersistTo persistTo, CancellationTokenSource cts)
        {
            var keyMapper = (VBucketKeyMapper) _configInfo.GetKeyMapper();
            var obParams = new ObserveParams
            {
                ReplicateTo = replicateTo,
                PersistTo = persistTo,
                Token = token,
                VBucket = keyMapper[token.VBucketId]
            };
            obParams.CheckConfiguredReplicas();

            var persisted = await CheckPersistToAsync(obParams).ContinueOnAnyContext();
            var replicated = await CheckReplicasAsync(obParams).ContinueOnAnyContext();
            if (persisted && replicated)
            {
                Log.DebugFormat("Persisted and replicated on first try: {0}", _key);
                return true;
            }
            return await ObserveEveryAsync(async p =>
            {
                Log.DebugFormat("trying again: {0}", _key);
                persisted = await CheckPersistToAsync(obParams).ContinueOnAnyContext();
                replicated = await CheckReplicasAsync(obParams).ContinueOnAnyContext();
                return persisted & replicated;
            }, obParams, _interval, cts.Token);
        }