Azure / azure-cosmos-dotnet-v3

.NET SDK for Azure Cosmos DB for the core SQL API
MIT License
739 stars 494 forks source link

Patching a document changes read behaviour of floating point numbers #4346

Open red-cb opened 7 months ago

red-cb commented 7 months ago

We are continuously addressing and improving the SDK, if possible, make sure the problem persist in the latest SDK version.

Describe the bug Usually after a document that includes a decimal value with trailing zeros is created it can be read back again and the trailing zeros will be retained. After the document is patched the trailing zeros are lost when the document is read again. This is true even when the decimal value is not updated as part of the patch operation.

To Reproduce The following test demonstrates this behaviour

public class CosmosTests
{
    [Fact]
    public async Task Patch_Should_Retain_Number_Precision()
    {
        var cosmosClient = new CosmosClient("AccountEndpoint=https://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==");
        var container = cosmosClient.GetContainer("test", "test");

        var id = Guid.NewGuid().ToString();
        var document = new Document { Id = id, Value = 12.00m };
        await container.CreateItemAsync(document);

        var response = await container.ReadItemAsync<Document>(id, new PartitionKey(id), null);
        var decimals = (byte)((decimal.GetBits(response.Resource.Value)[3] >> 16) & 0x7F);
        Assert.Equal(2, decimals); // success

        await container.PatchItemAsync<Document>(
            id,
            new PartitionKey(id),
            new List<PatchOperation>()
            {
            PatchOperation.Set($"/PatchMe", "new-value")
            }
        );

        response = await container.ReadItemAsync<Document>(id, new PartitionKey(id), null);
        decimals = (byte)((decimal.GetBits(response.Resource.Value)[3] >> 16) & 0x7F);
        Assert.Equal(2, decimals); // failure
    }

    public class Document
    {
        public string PartitionKey => Id;

        [JsonProperty(PropertyName = "id")]
        public string Id { get; set; } = string.Empty;

        public decimal Value { get; set; }

        public string PatchMe { get; set; } = string.Empty;
    }
}

Expected behavior A patch operation should not affect any values other than those being patched.

Actual behavior A patch operation can lose number precision for floating point numbers with trailing zeros.

Environment summary SDK Version: 3.38.1 OS Version Windows

Additional context Tested both in Azure Cosmos and in Azure Cosmos Emulator

ealsur commented 7 months ago

@red-cb I'm sorry this behavior causes confusion, but what you are describing is unrelated to this repository and the .NET SDK, there is nothing that can be changed in this SDK to change that behavior.

But let me try to explain what you are seeing: This is a Json document store, which follows the definition of Json Integer/Number. For Json, 12 and 12.00 are the same, they represent the same Integer. Patch rewrites the document, it applies your partial modifications and updates the document, because 12.00 and 12 are the same for Json, then the rewrite could be removing trailing zeroes, they are not adding anything in terms of Json.

For Queries it would be the same, a Query filtering by such a property (for example c.value = 12) would find documents with 12 and with 12.00.

If you require exact decimal or numeric representation (including numbers outside of the IEEE754 precision), you might want to consider using a string.

red-cb commented 7 months ago

Thanks for the explanation @ealsur, I figured something like that would be happening, however, what is confusing me is that this behaviour is exclusive to the patch operation. If a document is written and then updated with UpsertItemAsync for example, then the document can be read and the trailing zeros are retained. So although cosmos stores json documents, it must be retaining that precision somehow but not when a patch operation is used.