Example #1
0
        Task <IList <TableResult> > ExecuteExportedMirrorBatchAsync(TableBatchOperation batch, IList <TableResult> originalResponse)
        {
            var exportedBatch            = new TableBatchOperation();
            var exportedOriginalResponse = new List <TableResult>();

            for (int i = 0; i < batch.Count; i++)
            {
                TableOperation op           = batch[i];
                var            mtableEntity = (MTableEntity)op.GetEntity();
                if (MigratingTable.RowKeyIsInternal(mtableEntity.RowKey))
                {
                    continue;
                }
                exportedOriginalResponse.Add(originalResponse[i]);
                Debug.Assert(op.GetOperationType() == TableOperationType.InsertOrReplace);
                DynamicTableEntity exported = mtableEntity.Export <DynamicTableEntity>();
                if (mtableEntity.deleted)
                {
                    exported.ETag = ChainTable2Constants.ETAG_DELETE_IF_EXISTS;
                    exportedBatch.Delete(exported);
                }
                else
                {
                    exported.ETag = null;
                    exportedBatch.InsertOrReplace(exported);
                }
            }
            return(referenceTable.ExecuteMirrorBatchAsync(exportedBatch, exportedOriginalResponse));
        }
        // XXX: Preserve the entity type without the caller having to specify it?
        public static TableOperation CopyOperation <TEntity>(TableOperation op)
            where TEntity : ITableEntity, new()
        {
            ITableEntity newEntity = CopyEntity <TEntity>(op.GetEntity());

            switch (op.GetOperationType())
            {
            case TableOperationType.Insert:
                return(TableOperation.Insert(newEntity));

            case TableOperationType.Replace:
                return(TableOperation.Replace(newEntity));

            case TableOperationType.Merge:
                return(TableOperation.Merge(newEntity));

            case TableOperationType.Delete:
                return(TableOperation.Delete(newEntity));

            case TableOperationType.InsertOrReplace:
                return(TableOperation.InsertOrReplace(newEntity));

            case TableOperationType.InsertOrMerge:
                return(TableOperation.InsertOrMerge(newEntity));

            default:
                throw new NotImplementedException();
            }
        }
        async Task <IList <TableResult> > ExecuteBatchOnNewTableAsync(MTableConfiguration config,
                                                                      TableBatchOperation batch, TableRequestOptions requestOptions, OperationContext operationContext)
        {
            string partitionKey = ChainTableUtils.GetBatchPartitionKey(batch);

            await EnsurePartitionSwitchedAsync(partitionKey, requestOptions, operationContext);

            if (config.state <= TableClientState.PREFER_NEW)
            {
                await EnsureAffectedRowsMigratedAsync(batch, requestOptions, operationContext);
            }

            Attempt:
            // Batch on new table.
            var query = GenerateQueryForAffectedRows(batch);
            IList <MTableEntity> newRows = await newTable.ExecuteQueryAtomicAsync(query, requestOptions, operationContext);

            Dictionary <string, MTableEntity> newDict = newRows.ToDictionary(ent => ent.RowKey);
            // NOTE!  At this point, the read has not yet been annotated.  It is annotated below.

            var newBatch = new TableBatchOperation();
            var inputToNewTableIndexMapping = new List <int?>();

            for (int i = 0; i < batch.Count; i++)
            {
                TableOperation op             = batch[i];
                ITableEntity   passedEntity   = op.GetEntity();
                MTableEntity   existingEntity = newDict.GetValueOrDefault(passedEntity.RowKey);
                TableOperation newOp          = null;
                HttpStatusCode?errorCode      = null;

                TranslateOperationForNewTable(
                    op, existingEntity, config.state <= TableClientState.USE_NEW_WITH_TOMBSTONES,
                    ref newOp, ref errorCode);

                if (errorCode != null)
                {
                    Debug.Assert(newOp == null);
                    await monitor.AnnotateLastBackendCallAsync(wasLinearizationPoint : true);

                    throw ChainTableUtils.GenerateBatchException(errorCode.Value, i);
                }
                if (newOp != null)
                {
                    inputToNewTableIndexMapping.Add(newBatch.Count);
                    newBatch.Add(newOp);
                }
                else
                {
                    inputToNewTableIndexMapping.Add(null);
                }
            }
            await monitor.AnnotateLastBackendCallAsync();

            IList <TableResult> newResults;

            try
            {
                newResults = await newTable.ExecuteBatchAsync(newBatch, requestOptions, operationContext);
            }
            catch (ChainTableBatchException)
            {
                // XXX: Try to distinguish expected concurrency exceptions from unexpected exceptions?
                await monitor.AnnotateLastBackendCallAsync();

                goto Attempt;
            }

            // We made it!
            var results = new List <TableResult>();

            for (int i = 0; i < batch.Count; i++)
            {
                ITableEntity passedEntity  = batch[i].GetEntity();
                int?         newTableIndex = inputToNewTableIndexMapping[i];
                string       newETag       =
                    (IsBugEnabled(MTableOptionalBug.TombstoneOutputETag)
                    ? newTableIndex != null
                    : batch[i].GetOperationType() == TableOperationType.Delete)
                    ? null : newResults[newTableIndex.Value].Etag;
                if (newETag != null)
                {
                    passedEntity.ETag = newETag;
                }
                results.Add(new TableResult
                {
                    HttpStatusCode = (int)(
                        (batch[i].GetOperationType() == TableOperationType.Insert) ? HttpStatusCode.Created : HttpStatusCode.NoContent),
                    Etag   = newETag,
                    Result = passedEntity,
                });
            }
            await monitor.AnnotateLastBackendCallAsync(wasLinearizationPoint : true, successfulBatchResult : results);

            return(results);
        }
Example #4
0
        public override Task <IList <TableResult> > ExecuteMirrorBatchAsync(
            TableBatchOperation originalBatch, IList <TableResult> originalResponse,
            TableRequestOptions requestOptions = null, OperationContext operationContext = null)
        {
            ChainTableUtils.GetBatchPartitionKey(originalBatch);  // For validation; result ignored

            // Copy the table.  Entities are aliased to the original table, so don't mutate them.
            var tmpTable    = new SortedDictionary <PrimaryKey, DynamicTableEntity>(table);
            int tmpNextEtag = nextEtag;
            var results     = new List <TableResult>();

            for (int i = 0; i < originalBatch.Count; i++)
            {
                TableOperation     op           = originalBatch[i];
                TableOperationType opType       = op.GetOperationType();
                ITableEntity       passedEntity = op.GetEntity();
                PrimaryKey         key          = passedEntity.GetPrimaryKey();
                DynamicTableEntity oldEntity    = tmpTable.GetValueOrDefault(key);

                DynamicTableEntity newEntity  = null;
                HttpStatusCode     statusCode = HttpStatusCode.NoContent;

                if (opType == TableOperationType.Insert)
                {
                    if (oldEntity != null)
                    {
                        throw ChainTableUtils.GenerateBatchException(HttpStatusCode.Conflict, i);
                    }
                    else
                    {
                        newEntity  = ChainTableUtils.CopyEntity <DynamicTableEntity>(passedEntity);
                        statusCode = HttpStatusCode.Created;
                    }
                }
                else if (opType == TableOperationType.InsertOrReplace)
                {
                    newEntity = ChainTableUtils.CopyEntity <DynamicTableEntity>(passedEntity);
                }
                else if (opType == TableOperationType.InsertOrMerge)
                {
                    if (oldEntity == null)
                    {
                        newEntity = ChainTableUtils.CopyEntity <DynamicTableEntity>(passedEntity);
                    }
                    else
                    {
                        newEntity = ChainTableUtils.CopyEntity <DynamicTableEntity>(oldEntity);
                        Merge(newEntity, passedEntity);
                    }
                }
                else if (opType == TableOperationType.Delete &&
                         passedEntity.ETag == ChainTable2Constants.ETAG_DELETE_IF_EXISTS)
                {
                    tmpTable.Remove(key);
                }
                else if (oldEntity == null)
                {
                    throw ChainTableUtils.GenerateBatchException(HttpStatusCode.NotFound, i);
                }
                else if (string.IsNullOrEmpty(passedEntity.ETag))
                {
                    // Enforce this because real Azure table will.
                    // XXX Ideally do this up front.
                    throw new ArgumentException(string.Format("Operation {0} requires an explicit ETag.", i));
                }
                else if (passedEntity.ETag != ChainTable2Constants.ETAG_ANY &&
                         oldEntity.ETag != passedEntity.ETag)
                {
                    throw ChainTableUtils.GenerateBatchException(HttpStatusCode.PreconditionFailed, i);
                }
                else if (opType == TableOperationType.Delete)
                {
                    tmpTable.Remove(key);
                }
                else if (opType == TableOperationType.Replace)
                {
                    newEntity = ChainTableUtils.CopyEntity <DynamicTableEntity>(passedEntity);
                }
                else if (opType == TableOperationType.Merge)
                {
                    newEntity = ChainTableUtils.CopyEntity <DynamicTableEntity>(oldEntity);
                    Merge(newEntity, passedEntity);
                }
                else
                {
                    // IChainTable2 does not allow Retrieve in a batch.
                    throw new NotImplementedException();
                }

                if (newEntity != null)
                {
                    newEntity.ETag      = (originalResponse != null) ? originalResponse[i].Etag : (tmpNextEtag++).ToString();
                    newEntity.Timestamp = DateTimeOffset.MinValue;  // Arbitrary, deterministic
                    tmpTable[key]       = newEntity;
                }
                results.Add(new TableResult {
                    Result         = passedEntity,
                    HttpStatusCode = (int)statusCode,
                    Etag           = (newEntity != null) ? newEntity.ETag : null,
                });
            }

            // If we got here, commit.
            table    = tmpTable;
            nextEtag = tmpNextEtag;
            for (int i = 0; i < originalBatch.Count; i++)
            {
                if (results[i].Etag != null)  // not delete
                {
                    originalBatch[i].GetEntity().ETag = results[i].Etag;
                }
            }
            return(Task.FromResult((IList <TableResult>)results));
        }
        void TranslateOperationForNewTable(
            TableOperation op, MTableEntity existingEntity, bool leaveTombstones,
            ref TableOperation newOp, ref HttpStatusCode?errorCode)
        {
            ITableEntity       passedEntity = op.GetEntity();
            TableOperationType opType       = op.GetOperationType();

            switch (opType)
            {
            case TableOperationType.Insert:
                if (existingEntity == null)
                {
                    newOp = TableOperation.Insert(ChainTableUtils.CopyEntity <MTableEntity>(passedEntity));
                }
                else if (existingEntity.deleted)
                {
                    newOp = TableOperation.Replace(ImportWithIfMatch(passedEntity, existingEntity.ETag));
                }
                else
                {
                    errorCode = HttpStatusCode.Conflict;
                }
                break;

            case TableOperationType.Replace:
                if ((errorCode = CheckExistingEntity(passedEntity, existingEntity)) == null)
                {
                    newOp = TableOperation.Replace(ImportWithIfMatch(passedEntity, existingEntity.ETag));
                }
                break;

            case TableOperationType.Merge:
                if ((errorCode = CheckExistingEntity(passedEntity, existingEntity)) == null)
                {
                    newOp = TableOperation.Merge(ImportWithIfMatch(passedEntity, existingEntity.ETag));
                }
                break;

            case TableOperationType.Delete:
                string buggablePartitionKey, buggableRowKey;
                if (IsBugEnabled(MTableOptionalBug.DeletePrimaryKey))
                {
                    buggablePartitionKey = buggableRowKey = null;
                }
                else
                {
                    buggablePartitionKey = passedEntity.PartitionKey;
                    buggableRowKey       = passedEntity.RowKey;
                }
                if (leaveTombstones)
                {
                    if (passedEntity.ETag == ChainTable2Constants.ETAG_DELETE_IF_EXISTS)
                    {
                        newOp = TableOperation.InsertOrReplace(new MTableEntity {
                            PartitionKey = buggablePartitionKey, RowKey = buggableRowKey, deleted = true
                        });
                    }
                    else if ((errorCode = CheckExistingEntity(passedEntity, existingEntity)) == null)
                    {
                        newOp = TableOperation.Replace(new MTableEntity {
                            PartitionKey = buggablePartitionKey, RowKey = buggableRowKey,
                            deleted      = true, ETag = existingEntity.ETag
                        });
                    }
                }
                else
                {
                    if (passedEntity.ETag == ChainTable2Constants.ETAG_DELETE_IF_EXISTS)
                    {
                        if (existingEntity != null)
                        {
                            newOp = TableOperation.Delete(new MTableEntity {
                                PartitionKey = buggablePartitionKey, RowKey = buggableRowKey,
                                // It's OK to delete the entity and return success whether or not
                                // the entity is a tombstone by the time it is actually deleted.
                                ETag = IsBugEnabled(MTableOptionalBug.DeleteNoLeaveTombstonesETag) ? null : ChainTable2Constants.ETAG_ANY
                            });
                        }
                        // Otherwise generate nothing.
                        // FIXME: This is not linearizable!  It can also generate empty batches.
                    }
                    else if ((errorCode = CheckExistingEntity(passedEntity, existingEntity)) == null)
                    {
                        // Another client in USE_NEW_WITH_TOMBSTONES could concurrently replace the
                        // entity with a tombstone, in which case we need to return 404 to the caller,
                        // hence this needs to be conditioned on the existing ETag.
                        newOp = TableOperation.Delete(new MTableEntity {
                            PartitionKey = buggablePartitionKey, RowKey = buggableRowKey,
                            ETag         = IsBugEnabled(MTableOptionalBug.DeleteNoLeaveTombstonesETag) ? null : existingEntity.ETag
                        });
                    }
                }
                break;

            case TableOperationType.InsertOrReplace:
                newOp = TableOperation.InsertOrReplace(ChainTableUtils.CopyEntity <MTableEntity>(passedEntity));
                break;

            case TableOperationType.InsertOrMerge:
                newOp = TableOperation.InsertOrMerge(ChainTableUtils.CopyEntity <MTableEntity>(passedEntity));
                break;

            default:
                throw new NotImplementedException();
            }
        }