Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
f629e50
Update ChangeFeedItem to include id and pk metadata
jcocchi Dec 4, 2024
970b689
update emulator tests for avad delete operations
jcocchi Dec 5, 2024
1823f65
add output of UpdateContracts script
jcocchi Dec 11, 2024
1e66f3b
undo encryption contracts changes
jcocchi Dec 12, 2024
60223b4
update changefeed metadata serialization
jcocchi Dec 12, 2024
aacd447
update ttl delete test
jcocchi Dec 12, 2024
b2f0e2a
update change feed metadata contract
jcocchi Jan 14, 2025
4129a95
fix: adding newsoft converter to handle partitionKey
dibahlfi May 9, 2025
c41d59c
fix:updated contract
dibahlfi May 9, 2025
5ddaec1
fix:refactoring
dibahlfi May 10, 2025
31bc8b8
fix:addressing comments
dibahlfi May 27, 2025
d0d775d
Update Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMet…
dibahlfi May 28, 2025
59f53a5
Update Microsoft.Azure.Cosmos/src/Resource/FullFidelity/ChangeFeedMet…
dibahlfi May 28, 2025
2870d20
fix: addressing comments
dibahlfi May 28, 2025
079e9aa
fix: addressing comments
dibahlfi Jun 1, 2025
aa60a72
fix: refactoring
dibahlfi Jun 4, 2025
2658b33
updating contract file
dibahlfi Sep 17, 2025
8cd1360
Merge branch 'master' into users/dibahl/updateChangeFeedMetadata
ananth7592 Oct 17, 2025
8694811
enhanced logging
dibahlfi Oct 29, 2025
c175e7d
Merge branch 'master' into users/dibahl/updateChangeFeedMetadata
yash2710 Oct 29, 2025
b64ffa1
Simplify converter
yash2710 Oct 31, 2025
3810bab
Move to annotation converters
yash2710 Oct 31, 2025
f0d4f72
Merge remote-tracking branch 'origin/master' into users/dibahl/update…
yash2710 Oct 31, 2025
617b4ab
Address comments
yash2710 Oct 31, 2025
9964fd0
Add E2E live account test
yash2710 Nov 4, 2025
3ec8ff3
Merge branch 'master' into users/trivediyash/CFPDeleteMetadata
yash2710 Nov 4, 2025
c0f6f0b
Merge branch 'master' into users/trivediyash/CFPDeleteMetadata
yash2710 Nov 5, 2025
e6fe51c
Update code-doc
yash2710 Nov 6, 2025
cc7eb66
Merge branch 'users/trivediyash/CFPDeleteMetadata' of https://github.…
yash2710 Nov 6, 2025
c5edc44
Merge branch 'master' into users/trivediyash/CFPDeleteMetadata
yash2710 Nov 6, 2025
1a50f38
Merge branch 'master' into users/trivediyash/CFPDeleteMetadata
kirankumarkolli Nov 7, 2025
40cb84a
Fix tests and update contract
yash2710 Nov 7, 2025
4800713
Merge remote-tracking branch 'origin/master' into users/trivediyash/C…
yash2710 Nov 7, 2025
daf2638
Merge branch 'users/trivediyash/CFPDeleteMetadata' of https://github.…
yash2710 Nov 7, 2025
3427559
Update connection string for multi-master mode
yash2710 Nov 10, 2025
9d39e1f
Merge remote-tracking branch 'origin/master' into users/trivediyash/C…
yash2710 Nov 10, 2025
95a0f3e
Refactor test
yash2710 Nov 10, 2025
ce56009
Update cosmosclient createion
yash2710 Nov 10, 2025
3a2a6a3
Fix endpoint for emulator
yash2710 Nov 10, 2025
8f90e97
Fix test for emulator
yash2710 Nov 11, 2025
605c375
Update builder test
yash2710 Nov 11, 2025
9e580cd
Update builder test
yash2710 Nov 13, 2025
16f4752
Update builder test
yash2710 Nov 13, 2025
3a945f6
Update builder test
yash2710 Nov 13, 2025
fe70165
Merge branch 'master' into users/trivediyash/CFPDeleteMetadata
yash2710 Nov 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -57,21 +57,22 @@ namespace Microsoft.Azure.Cosmos
class ChangeFeedItem<T>
{
/// <summary>
/// The full fidelity change feed current item.
/// The current version of the item for all versions and deletes change feed mode.
/// It is always null for delete change feed operations.
/// </summary>
[JsonProperty(PropertyName = "current")]
[JsonPropertyName("current")]
public T Current { get; set; }

/// <summary>
/// The full fidelity change feed metadata.
/// The item metadata for all versions and deletes change feed mode.
/// </summary>
[JsonProperty(PropertyName = "metadata", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("metadata")]
public ChangeFeedMetadata Metadata { get; set; }

/// <summary>
/// For delete operations, previous image is always going to be provided. The previous image on replace operations is not going to be exposed by default and requires account-level or container-level opt-in.
/// The previous version of the item for all versions and deletes change feed mode. The previous version on delete and replace operations is not exposed by default and requires container-level opt-in. Refer to https://aka.ms/cosmosdb-change-feed-deletes for more information.
/// </summary>
[JsonProperty(PropertyName = "previous", NullValueHandling = NullValueHandling.Ignore)]
[JsonPropertyName("previous")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,54 +5,125 @@
namespace Microsoft.Azure.Cosmos
{
using System;
using System.Text.Json;
using System.Collections.Generic;
using Microsoft.Azure.Cosmos.Resource.FullFidelity;
using Microsoft.Azure.Cosmos.Resource.FullFidelity.Converters;
using Microsoft.Azure.Documents;
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

/// <summary>
/// The metadata of a change feed resource with <see cref="ChangeFeedMode"/> is initialized to <see cref="ChangeFeedMode.AllVersionsAndDeletes"/>.
/// </summary>
[System.Text.Json.Serialization.JsonConverter(typeof(ChangeFeedMetadataConverter))]
#if PREVIEW
public
#else
internal
#endif
class ChangeFeedMetadata
class ChangeFeedMetadata
{
private readonly static DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);

/// <summary>
/// The change's conflict resolution timestamp.
/// </summary>
[JsonProperty(PropertyName = ChangeFeedMetadataFields.ConflictResolutionTimestamp, NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(UnixDateTimeConverter))]
public DateTime ConflictResolutionTimestamp { get; internal set; }
[System.Text.Json.Serialization.JsonIgnore]
[Newtonsoft.Json.JsonIgnore]
public DateTime? ConflictResolutionTimestamp => this.ConflictResolutionTimestampInSeconds.HasValue ? UnixEpoch.AddSeconds(this.ConflictResolutionTimestampInSeconds.Value) : null;

[System.Text.Json.Serialization.JsonInclude]
[System.Text.Json.Serialization.JsonPropertyName(ChangeFeedMetadataFields.ConflictResolutionTimestamp)]
[JsonProperty(PropertyName = ChangeFeedMetadataFields.ConflictResolutionTimestamp)]
internal double? ConflictResolutionTimestampInSeconds { get; set; }

/// <summary>
/// The current change's logical sequence number.
/// </summary>
[System.Text.Json.Serialization.JsonInclude]
[System.Text.Json.Serialization.JsonPropertyName(ChangeFeedMetadataFields.Lsn)]
[JsonProperty(PropertyName = ChangeFeedMetadataFields.Lsn, NullValueHandling = NullValueHandling.Ignore)]
public long Lsn { get; internal set; }

/// <summary>
/// The change's feed operation type <see cref="ChangeFeedOperationType"/>.
/// </summary>
[Newtonsoft.Json.JsonConverter(typeof(StringEnumConverter))]
[System.Text.Json.Serialization.JsonInclude]
[System.Text.Json.Serialization.JsonPropertyName(ChangeFeedMetadataFields.OperationType)]
[System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))]
[JsonProperty(PropertyName = ChangeFeedMetadataFields.OperationType, NullValueHandling = NullValueHandling.Ignore)]
[JsonConverter(typeof(StringEnumConverter))]
public ChangeFeedOperationType OperationType { get; internal set; }

/// <summary>
/// The previous change's logical sequence number.
/// </summary>
[System.Text.Json.Serialization.JsonInclude]
[System.Text.Json.Serialization.JsonPropertyName(ChangeFeedMetadataFields.PreviousImageLSN)]
[JsonProperty(PropertyName = ChangeFeedMetadataFields.PreviousImageLSN, NullValueHandling = NullValueHandling.Ignore)]
public long PreviousLsn { get; internal set; }

/// <summary>
/// Used to distinquish explicit deletes (e.g. via DeleteItem) from deletes caused by TTL expiration (a collection may define time-to-live policy for documents).
/// Used to distinguish explicit deletes (e.g. via DeleteItem) from deletes caused by TTL expiration (a collection may define time-to-live policy for documents).
/// </summary>
[System.Text.Json.Serialization.JsonInclude]
[System.Text.Json.Serialization.JsonPropertyName(ChangeFeedMetadataFields.TimeToLiveExpired)]
[JsonProperty(PropertyName = ChangeFeedMetadataFields.TimeToLiveExpired, NullValueHandling = NullValueHandling.Ignore)]
public bool IsTimeToLiveExpired { get; internal set; }

/// <summary>
/// Applicable for delete operations only, otherwise null.
/// The id of the previous item version.
/// </summary>
[System.Text.Json.Serialization.JsonInclude]
[System.Text.Json.Serialization.JsonPropertyName(ChangeFeedMetadataFields.Id)]
[JsonProperty(PropertyName = ChangeFeedMetadataFields.Id, NullValueHandling = NullValueHandling.Ignore)]
public string Id { get; internal set; }

/// <summary>
/// Applicable for delete operations only, otherwise null.
/// The partition key of the previous item version represented as a dictionary where the key is the partition key property name
/// and the value is the partition key property value. All levels of hierarchy will be present if a hierarchical partition key (HPK) is used.
/// </summary>
/// <remarks>
/// <para>
/// For single partition key containers, the dictionary will contain one entry with the partition key path name (without the leading '/')
/// as the key and the partition key value as the value.
/// </para>
/// <para>
/// For hierarchical partition key containers, the dictionary will contain multiple entries, one for each level of the hierarchy,
/// as defined in the container's partition key definition.
/// </para>
/// <para>
/// Example for a single partition key container with partition key path "/tenantId":
/// <code>
/// {
/// "tenantId": "tenant123"
/// }
/// </code>
/// </para>
/// <para>
/// Example for a hierarchical partition key container with partition key paths ["/tenantId", "/userId", "/sessionId"]:
/// <code>
/// {
/// "tenantId": "tenant123",
/// "userId": "user456",
/// "sessionId": "session789"
/// }
/// </code>
/// </para>
/// <para>
/// The partition key values can be of different types (string, number, boolean, null) depending on the document's schema.
/// For example, with partition key paths ["/category", "/priority"]:
/// <code>
/// {
/// "category": "electronics",
/// "priority": 1
/// }
/// </code>
/// </para>
/// </remarks>
[System.Text.Json.Serialization.JsonInclude]
[System.Text.Json.Serialization.JsonPropertyName(ChangeFeedMetadataFields.PartitionKey)]
[JsonProperty(PropertyName = ChangeFeedMetadataFields.PartitionKey, NullValueHandling = NullValueHandling.Ignore)]
public Dictionary<string, object> PartitionKey { get; internal set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ internal class ChangeFeedMetadataFields
public const string OperationType = "operationType";
public const string PreviousImageLSN = "previousImageLSN";
public const string TimeToLiveExpired = "timeToLiveExpired";
public const string Id = "id";
public const string PartitionKey = "partitionKey";
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ public async Task WhenADocumentIsCreatedWithTtlSetThenTheDocumentIsDeletedTestsA
Assert.IsTrue(DateTime.TryParse(s: change.Metadata.ConflictResolutionTimestamp.ToString(), out _), message: "Invalid csrt must be a datetime value.");
Assert.IsTrue(change.Metadata.Lsn > 0, message: "Invalid lsn must be a long value.");
Assert.IsFalse(change.Metadata.IsTimeToLiveExpired);

Assert.IsNull(change.Metadata.Id);
Assert.IsNull(change.Metadata.PartitionKey);
// previous
Assert.IsNull(change.Previous);
}
Expand All @@ -84,10 +85,9 @@ public async Task WhenADocumentIsCreatedWithTtlSetThenTheDocumentIsDeletedTestsA
Assert.IsTrue(change.Metadata.IsTimeToLiveExpired);

// previous
Assert.AreEqual(expected: "1", actual: change.Previous.id.ToString());
Assert.AreEqual(expected: "1", actual: change.Previous.pk.ToString());
Assert.AreEqual(expected: "Testing TTL on CFP.", actual: change.Previous.description.ToString());
Assert.AreEqual(expected: ttlInSeconds, actual: change.Previous.ttl);
Assert.AreEqual(expected: "1", actual: change.Metadata.Id.ToString());
Assert.AreEqual(expected: "1", actual: change.Metadata.PartitionKey.Values.FirstOrDefault());
Assert.IsNull(change.Previous);

// stop after reading delete since it is the last document in feed.
stopwatch.Stop();
Expand Down Expand Up @@ -145,7 +145,7 @@ public async Task WhenADocumentIsCreatedWithTtlSetThenTheDocumentIsDeletedTestsA
[TestMethod]
[Owner("philipthomas-MSFT")]
[Description("Scenario: When a document is created, then updated, and finally deleted, there should be 3 changes that will appear for that " +
"document when using ChangeFeedProcessor with AllVersionsAndDeletes set as the ChangeFeedMode.")]
"document when using ChangeFeedProcessor with AllVersionsAndDeletes set as the ChangeFeedMode and enablePreviousImageForDeleteInFFCF true")]
public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync()
{
ContainerInternal monitoredContainer = await this.CreateMonitoredContainer(ChangeFeedMode.AllVersionsAndDeletes);
Expand All @@ -155,6 +155,8 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync()
ChangeFeedProcessor processor = monitoredContainer
.GetChangeFeedProcessorBuilderWithAllVersionsAndDeletes(processorName: "processor", onChangesDelegate: (ChangeFeedProcessorContext context, IReadOnlyCollection<ChangeFeedItem<dynamic>> docs, CancellationToken token) =>
{
string metadataId = default;
string metadataPk = default;
string id = default;
string pk = default;
string description = default;
Expand All @@ -171,14 +173,13 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync()
}
else
{
id = change.Previous.id.ToString();
pk = change.Previous.pk.ToString();
description = change.Previous.description.ToString();
metadataId = change.Metadata.Id.ToString();
metadataPk = change.Metadata.PartitionKey.Values.FirstOrDefault().ToString();
}

ChangeFeedOperationType operationType = change.Metadata.OperationType;
long previousLsn = change.Metadata.PreviousLsn;
DateTime m = change.Metadata.ConflictResolutionTimestamp;
DateTime? m = change.Metadata.ConflictResolutionTimestamp;
long lsn = change.Metadata.Lsn;
bool isTimeToLiveExpired = change.Metadata.IsTimeToLiveExpired;
}
Expand Down Expand Up @@ -211,8 +212,9 @@ public async Task WhenADocumentIsCreatedThenUpdatedThenDeletedTestsAsync()

ChangeFeedItem<dynamic> deleteChange = docs.ElementAt(2);
Assert.IsNull(deleteChange.Current.id);
Assert.AreEqual(expected: "1", actual: deleteChange.Metadata.Id.ToString());
Assert.AreEqual(expected: "1", actual: deleteChange.Metadata.PartitionKey.Values.FirstOrDefault());
Assert.AreEqual(expected: deleteChange.Metadata.OperationType, actual: ChangeFeedOperationType.Delete);
Assert.AreEqual(expected: replaceChange.Metadata.Lsn, actual: deleteChange.Metadata.PreviousLsn);
Assert.IsNotNull(deleteChange.Previous);
Assert.AreEqual(expected: "1", actual: deleteChange.Previous.id.ToString());
Assert.AreEqual(expected: "1", actual: deleteChange.Previous.pk.ToString());
Expand Down
Loading
Loading