Skip to content

Commit e2ba9bd

Browse files
[Hotfix] Binary Encoding: Fixes Serialization Gaps on Newtonsoft Reader/Writer for Transactional Batch (#5041)
# Pull Request Template ## Description This PR cherry-picks the following commits: - #5030 ## Type of change Please delete options that are not relevant. - [x] Bug fix (non-breaking change which fixes an issue) ## Closing issues To automatically close an issue: closes #IssueNumber
1 parent c495ad5 commit e2ba9bd

File tree

11 files changed

+3826
-149
lines changed

11 files changed

+3826
-149
lines changed

Directory.Build.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
22
<PropertyGroup>
3-
<ClientOfficialVersion>3.47.1</ClientOfficialVersion>
3+
<ClientOfficialVersion>3.47.2</ClientOfficialVersion>
44
<ClientPreviewVersion>3.48.0</ClientPreviewVersion>
5-
<ClientPreviewSuffixVersion>preview.1</ClientPreviewSuffixVersion>
5+
<ClientPreviewSuffixVersion>preview.2</ClientPreviewSuffixVersion>
66
<DirectVersion>3.37.10</DirectVersion>
77
<FaultInjectionVersion>1.0.0</FaultInjectionVersion>
88
<FaultInjectionSuffixVersion>beta.0</FaultInjectionSuffixVersion>

Microsoft.Azure.Cosmos/contracts/API_3.47.2.txt

Lines changed: 1687 additions & 0 deletions
Large diffs are not rendered by default.

Microsoft.Azure.Cosmos/contracts/API_3.48.0-preview.2.txt

Lines changed: 1849 additions & 0 deletions
Large diffs are not rendered by default.

Microsoft.Azure.Cosmos/src/Batch/TransactionalBatchOperationResult.cs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,14 @@ private static Result ReadOperationResult(ref RowReader reader, out Transactiona
183183
return r;
184184
}
185185

186-
batchOperationResult.ResourceStream = new MemoryStream(
187-
buffer: resourceBody, index: 0, count: resourceBody.Length, writable: false, publiclyVisible: true);
186+
batchOperationResult.ResourceStream = new CloneableStream(
187+
internalStream: new MemoryStream(
188+
buffer: resourceBody,
189+
index: 0,
190+
count: resourceBody.Length,
191+
writable: false,
192+
publiclyVisible: true),
193+
allowUnsafeDataAccess: true);
188194
break;
189195

190196
case "requestCharge":

Microsoft.Azure.Cosmos/src/Json/Interop/CosmosDBToNewtonsoftReader.cs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,19 @@ public override bool Read()
182182
/// <returns>A <see cref="byte"/>[] or <c>null</c> if the next JSON token is null. This method will return <c>null</c> at the end of an array.</returns>
183183
public override byte[] ReadAsBytes()
184184
{
185-
throw new NotImplementedException();
185+
this.Read();
186+
if (this.jsonReader.CurrentTokenType == JsonTokenType.Null)
187+
{
188+
return null;
189+
}
190+
191+
if (this.jsonReader.CurrentTokenType == JsonTokenType.EndArray)
192+
{
193+
return new byte[0];
194+
}
195+
196+
string stringValue = this.jsonReader.GetStringValue();
197+
return Convert.FromBase64String(stringValue);
186198
}
187199

188200
/// <summary>

Microsoft.Azure.Cosmos/src/Json/Interop/CosmosDBToNewtonsoftWriter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ public override void WriteValue(DateTime value)
331331
/// <param name="value">The <see cref="byte"/>[] value to write.</param>
332332
public override void WriteValue(byte[] value)
333333
{
334-
throw new NotSupportedException("Can not write byte arrays");
334+
this.WriteValue(Convert.ToBase64String(value));
335335
}
336336

337337
/// <summary>

Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/CosmosItemIntegrationTests.cs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,5 +145,93 @@ public async Task ReadMany2UnreachablePartitionsTest()
145145
fiClient.Dispose();
146146
}
147147
}
148+
149+
[TestMethod]
150+
[Owner("dkunda")]
151+
[TestCategory("MultiRegion")]
152+
[DataRow(true, DisplayName = "Test scenario when binary encoding is enabled at client level.")]
153+
[DataRow(false, DisplayName = "Test scenario when binary encoding is disabled at client level.")]
154+
public async Task ExecuteTransactionalBatch_WhenBinaryEncodingEnabled_ShouldCompleteSuccessfully(
155+
bool isBinaryEncodingEnabled)
156+
{
157+
Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, isBinaryEncodingEnabled.ToString());
158+
159+
Random random = new();
160+
CosmosIntegrationTestObject testItem = new()
161+
{
162+
Id = $"smTestId{random.Next()}",
163+
Pk = $"smpk{random.Next()}",
164+
};
165+
166+
try
167+
{
168+
CosmosClientOptions cosmosClientOptions = new()
169+
{
170+
ConsistencyLevel = ConsistencyLevel.Session,
171+
RequestTimeout = TimeSpan.FromSeconds(10),
172+
Serializer = new CosmosJsonDotNetSerializer(
173+
cosmosSerializerOptions: new CosmosSerializationOptions()
174+
{
175+
PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase
176+
},
177+
binaryEncodingEnabled: isBinaryEncodingEnabled)
178+
};
179+
180+
using CosmosClient cosmosClient = new(
181+
connectionString: this.connectionString,
182+
clientOptions: cosmosClientOptions);
183+
184+
Database database = cosmosClient.GetDatabase(MultiRegionSetupHelpers.dbName);
185+
Container container = database.GetContainer(MultiRegionSetupHelpers.containerName);
186+
187+
// Create a transactional batch
188+
TransactionalBatch transactionalBatch = container.CreateTransactionalBatch(new PartitionKey(testItem.Pk));
189+
190+
transactionalBatch.CreateItem(
191+
testItem,
192+
new TransactionalBatchItemRequestOptions
193+
{
194+
EnableContentResponseOnWrite = true,
195+
});
196+
197+
transactionalBatch.ReadItem(
198+
testItem.Id,
199+
new TransactionalBatchItemRequestOptions
200+
{
201+
EnableContentResponseOnWrite = true,
202+
});
203+
204+
// Execute the transactional batch
205+
TransactionalBatchResponse transactionResponse = await transactionalBatch.ExecuteAsync(
206+
new TransactionalBatchRequestOptions
207+
{
208+
});
209+
210+
Assert.AreEqual(HttpStatusCode.OK, transactionResponse.StatusCode);
211+
Assert.AreEqual(2, transactionResponse.Count);
212+
213+
TransactionalBatchOperationResult<CosmosIntegrationTestObject> createOperationResult = transactionResponse.GetOperationResultAtIndex<CosmosIntegrationTestObject>(0);
214+
215+
Assert.IsNotNull(createOperationResult);
216+
Assert.IsNotNull(createOperationResult.Resource);
217+
Assert.AreEqual(testItem.Id, createOperationResult.Resource.Id);
218+
Assert.AreEqual(testItem.Pk, createOperationResult.Resource.Pk);
219+
220+
TransactionalBatchOperationResult<CosmosIntegrationTestObject> readOperationResult = transactionResponse.GetOperationResultAtIndex<CosmosIntegrationTestObject>(1);
221+
222+
Assert.IsNotNull(readOperationResult);
223+
Assert.IsNotNull(readOperationResult.Resource);
224+
Assert.AreEqual(testItem.Id, readOperationResult.Resource.Id);
225+
Assert.AreEqual(testItem.Pk, readOperationResult.Resource.Pk);
226+
}
227+
finally
228+
{
229+
Environment.SetEnvironmentVariable(ConfigurationManager.BinaryEncodingEnabled, null);
230+
231+
await this.container.DeleteItemAsync<CosmosIntegrationTestObject>(
232+
testItem.Id,
233+
new PartitionKey(testItem.Pk));
234+
}
235+
}
148236
}
149237
}

Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/Batch/BatchSchemaTests.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,9 @@ public async Task BatchResponseDeserializationAsync()
129129
new TransactionalBatchOperationResult(HttpStatusCode.Conflict),
130130
new TransactionalBatchOperationResult(HttpStatusCode.OK)
131131
{
132-
ResourceStream = new MemoryStream(new byte[] { 0x41, 0x42 }, index: 0, count: 2, writable: false, publiclyVisible: true),
132+
ResourceStream = new CloneableStream(
133+
internalStream: new MemoryStream(new byte[] { 0x41, 0x42 }, index: 0, count: 2, writable: false, publiclyVisible: true),
134+
allowUnsafeDataAccess: true),
133135
RequestCharge = 2.5,
134136
ETag = "1234",
135137
RetryAfter = TimeSpan.FromMilliseconds(360)
@@ -250,7 +252,7 @@ private bool Equals(Stream x, Stream y)
250252
return false;
251253
}
252254

253-
return ((MemoryStream)x).GetBuffer().SequenceEqual(((MemoryStream)y).GetBuffer());
255+
return ((CloneableStream)x).GetBuffer().SequenceEqual(((CloneableStream)y).GetBuffer());
254256
}
255257

256258
return false;

0 commit comments

Comments
 (0)