Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions sdk/tables/azure-data-tables/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@

### Bugs Fixed

- Fixed multipart transaction framing, the `InsertReplace` request method and body, and `UpdateMerge` ETag conditions.
- Fixed `UpdateEntity` and `MergeEntity` to send `If-Match: *` when the entity's ETag is empty, rather than performing an upsert.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Behavior change worth calling out: previously an empty ETag sent no If-Match, so the service performed Insert-or-Replace/Merge. With If-Match: *, a missing entity now returns 404. This matches the REST contract and the other SDKs, but callers relying on the old behavior will break. Suggest moving the changelog entry under "Breaking Changes" (fine for a beta) and checking the "Any breaking changes?" box.

- Preserved native JSON Boolean, Int32, and Double property types when responses omit OData type annotations.
- Preserved query options and key selectors across pages and supported partition-only continuation tokens.
- Escaped single quotes in OData entity keys before URL encoding for point, query, and transaction requests.

### Other Changes

## 1.0.0-beta.7 (2026-11-04)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,9 @@ namespace Azure { namespace Data { namespace Tables {
std::string Value;
/**
* Property type.
* When deserializing, an explicit OData type annotation takes precedence; otherwise, native
* JSON booleans, integers, and floating-point numbers infer Edm.Boolean, Edm.Int32, and
* Edm.Double respectively. Unannotated strings leave the type unset.
*/
Azure::Nullable<TableEntityDataType> Type;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ namespace Azure { namespace Data { namespace Tables {
/**
* @brief Update entity in a table.
*
* @note An empty ETag updates the existing entity unconditionally. Use UpdateOrInsertEntity
* to insert the entity if it does not exist.
*
* @param tableEntity The TableEntity to set.
* @param context for canceling long running operations.
* @return Update entity result.
Expand All @@ -118,6 +121,9 @@ namespace Azure { namespace Data { namespace Tables {
/**
* @brief Merge entity in a table.
*
* @note An empty ETag merges the existing entity unconditionally. Use MergeOrInsertEntity
* to insert the entity if it does not exist.
*
* @param tableEntity The TableEntity to merge.
* @param context for canceling long running operations.
* @return Merge entity result.
Expand Down Expand Up @@ -221,6 +227,10 @@ namespace Azure { namespace Data { namespace Tables {
std::string PrepAddEntity(std::string const& changesetId, Models::TableEntity entity) const;
std::string PrepDeleteEntity(std::string const& changesetId, Models::TableEntity entity) const;
std::string PrepMergeEntity(std::string const& changesetId, Models::TableEntity entity) const;
std::string PrepMergeEntity(
std::string const& changesetId,
Models::TableEntity entity,
bool isUpsert) const;
std::string PrepUpdateEntity(std::string const& changesetId, Models::TableEntity entity) const;
std::string PrepInsertEntity(std::string const& changesetId, Models::TableEntity entity) const;
std::shared_ptr<Core::Http::_internal::HttpPipeline> m_pipeline;
Expand Down
12 changes: 12 additions & 0 deletions sdk/tables/azure-data-tables/src/serializers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,18 @@ namespace Azure { namespace Data { namespace Tables { namespace _detail {
value, static_cast<Azure::Data::Tables::Models::TableEntityDataType>(type));
erasable.push_back(typeFieldName);
}
else if (json.at(name).is_boolean())
{
tableEntity.Properties[name] = TableEntityProperty(value, TableEntityDataType::EdmBoolean);
}
else if (json.at(name).is_number_integer())
{
tableEntity.Properties[name] = TableEntityProperty(value, TableEntityDataType::EdmInt32);
Comment on lines +562 to +564

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit (optional): is_number_integer() also matches values outside the Int32 range. The service always sends Int64 as an annotated string, so this shouldn't happen in practice, but leaving out-of-range integers untyped would be a cheap safeguard.

}
else if (json.at(name).is_number_float())
{
tableEntity.Properties[name] = TableEntityProperty(value, TableEntityDataType::EdmDouble);
}
Comment on lines +558 to +569

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inferring Edm.Boolean/Edm.Int32/Edm.Double on read changes what a Get → Update round trip sends. CreateEntity always writes Value as a JSON string and adds @odata.type whenever Type is set, so an entity read back from the service is now written as:

"Enabled": "true", "Enabled@odata.type": "Edm.Boolean"

The payload-format docs require Boolean to be a JSON literal and Int32/Double to be JSON numbers; only NaN/Infinity are sent as annotated strings. Before this change the same round trip sent an unannotated string, which silently stored the property as Edm.String (wrong, but it succeeded). After it, the request may be rejected with a 400, and nothing in CI exercises this path. Explicitly typed properties already go through it, but now every round-tripped bool/int/double does.

DeserializeEntityInfersNativePrimitiveTypes only verifies that the SDK can re-read its own output, so it would not catch this.

Could CreateEntity emit native JSON values for these three types, with a unit test asserting the serialized body? Alternatively, a live round-trip test showing the service accepts annotated strings would settle it.

else
{
tableEntity.Properties[name] = TableEntityProperty(value);
Expand Down
Loading
Loading