diff --git a/sdk/tables/Azure.Data.Tables/CHANGELOG.md b/sdk/tables/Azure.Data.Tables/CHANGELOG.md index a7f7638829dc..4e0552458bdf 100644 --- a/sdk/tables/Azure.Data.Tables/CHANGELOG.md +++ b/sdk/tables/Azure.Data.Tables/CHANGELOG.md @@ -1,14 +1,17 @@ # Release History -## 12.10.0-beta.1 (Unreleased) - -### Features Added +## 12.10.0 (2025-01-14) ### Breaking Changes -- Calling `TableClient.Query`, `TableClient.QueryAsync`, or `TableClient.CreateQueryFilter` with a filter expression that uses `string.Equals` or `string.Compare` with a `StringComparison` parameter will now throw an exception. This is because the Azure Table service does not support these methods in query filters. Previously the `StringComparison` argument was silently ignored, which can lead to subtle bugs in client code. +- Calling `TableClient.Query`, `TableClient.QueryAsync`, or `TableClient.CreateQueryFilter` with a filter expression that uses `string.Equals` or `string.Compare` with a `StringComparison` parameter will now throw an exception. This is because the Azure Table service does not support these methods in query filters. Previously the `StringComparison` argument was silently ignored, which can lead to subtle bugs in client code. The new behavior can be overridden by either setting an AppContext switch named "Azure.Data.Tables.DisableThrowOnStringComparisonFilter" to `true` or by setting the environment variable "AZURE_DATA_TABLES_DISABLE_THROWONSTRINGCOMPARISONFILTER" to "true". Note: AppContext switches can also be configured via configuration like below: + +```xml + + + + ``` -### Bugs Fixed ### Other Changes - Improved the performance of `TableServiceClient.GetTableClient()` diff --git a/sdk/tables/Azure.Data.Tables/src/Azure.Data.Tables.csproj b/sdk/tables/Azure.Data.Tables/src/Azure.Data.Tables.csproj index 608c3c3cfcb8..d561f9c1a6ad 100644 --- a/sdk/tables/Azure.Data.Tables/src/Azure.Data.Tables.csproj +++ b/sdk/tables/Azure.Data.Tables/src/Azure.Data.Tables.csproj @@ -2,7 +2,7 @@ This client library enables working with the Microsoft Azure Table service Microsoft Azure.Data.Tables client library - 12.10.0-beta.1 + 12.10.0 12.9.1 TableSDK;$(DefineConstants) diff --git a/sdk/tables/Azure.Data.Tables/src/Queryable/ExpressionNormalizer.cs b/sdk/tables/Azure.Data.Tables/src/Queryable/ExpressionNormalizer.cs index 1bfe6237cd08..e9cf61276ae2 100644 --- a/sdk/tables/Azure.Data.Tables/src/Queryable/ExpressionNormalizer.cs +++ b/sdk/tables/Azure.Data.Tables/src/Queryable/ExpressionNormalizer.cs @@ -162,7 +162,7 @@ internal Expression VisitMethodCallNoRewrite(MethodCallExpression call) if (visited.Method.IsStatic && visited.Method.Name == "Equals" && visited.Arguments.Count > 1) { - if (visited.Arguments.Count > 2) + if (visited.Arguments.Count > 2 && !TablesCompatSwitches.DisableThrowOnStringComparisonFilter) { throw new NotSupportedException("string.Equals method with more than two arguments is not supported."); } @@ -171,7 +171,7 @@ internal Expression VisitMethodCallNoRewrite(MethodCallExpression call) if (!visited.Method.IsStatic && visited.Method.Name == "Equals" && visited.Arguments.Count > 0) { - if (visited.Arguments.Count > 1) + if (visited.Arguments.Count > 1 && !TablesCompatSwitches.DisableThrowOnStringComparisonFilter) { throw new NotSupportedException("Equals method with more than two arguments is not supported."); } @@ -190,7 +190,7 @@ internal Expression VisitMethodCallNoRewrite(MethodCallExpression call) if (visited.Method.IsStatic && visited.Method.Name == "Compare" && visited.Arguments.Count > 1 && visited.Method.ReturnType == typeof(int)) { - if (visited.Arguments.Count > 2) + if (visited.Arguments.Count > 2 && !TablesCompatSwitches.DisableThrowOnStringComparisonFilter) { throw new NotSupportedException("string.Compare method with more than two arguments is not supported."); } diff --git a/sdk/tables/Azure.Data.Tables/src/TableConstants.cs b/sdk/tables/Azure.Data.Tables/src/TableConstants.cs index 91f847232b8c..857bfb266efb 100644 --- a/sdk/tables/Azure.Data.Tables/src/TableConstants.cs +++ b/sdk/tables/Azure.Data.Tables/src/TableConstants.cs @@ -17,6 +17,8 @@ internal static class CompatSwitches public const string DisableEscapeSingleQuotesOnGetEntityEnvVar = "AZURE_DATA_TABLES_DISABLE_ESCAPESINGLEQUOTESONGETENTITY"; public const string DisableEscapeSingleQuotesOnDeleteEntitySwitchName = "Azure.Data.Tables.DisableEscapeSingleQuotesOnDeleteEntity"; public const string DisableEscapeSingleQuotesOnDeleteEntityEnvVar = "AZURE_DATA_TABLES_DISABLE_ESCAPESINGLEQUOTESONDELETEENTITY"; + public const string DisableThrowOnStringComparisonFilterSwitchName = "Azure.Data.Tables.DisableThrowOnStringComparisonFilter"; + public const string DisableThrowOnStringComparisonFilterEnvVar = "AZURE_DATA_TABLES_DISABLE_THROWONSTRINGCOMPARISONFILTER"; } internal static class HeaderNames diff --git a/sdk/tables/Azure.Data.Tables/src/TablesCompatSwitches.cs b/sdk/tables/Azure.Data.Tables/src/TablesCompatSwitches.cs index 8994bb8123a3..fb232b7a1ede 100644 --- a/sdk/tables/Azure.Data.Tables/src/TablesCompatSwitches.cs +++ b/sdk/tables/Azure.Data.Tables/src/TablesCompatSwitches.cs @@ -16,5 +16,10 @@ public static bool DisableEscapeSingleQuotesOnDeleteEntity => AppContextSwitchHelper.GetConfigValue( TableConstants.CompatSwitches.DisableEscapeSingleQuotesOnDeleteEntitySwitchName, TableConstants.CompatSwitches.DisableEscapeSingleQuotesOnDeleteEntityEnvVar); + + public static bool DisableThrowOnStringComparisonFilter + => AppContextSwitchHelper.GetConfigValue( + TableConstants.CompatSwitches.DisableThrowOnStringComparisonFilterSwitchName, + TableConstants.CompatSwitches.DisableThrowOnStringComparisonFilterEnvVar); } } diff --git a/sdk/tables/Azure.Data.Tables/tests/TableClientQueryExpressionTests.cs b/sdk/tables/Azure.Data.Tables/tests/TableClientQueryExpressionTests.cs index d3799dc0571e..8eb213edd6b1 100644 --- a/sdk/tables/Azure.Data.Tables/tests/TableClientQueryExpressionTests.cs +++ b/sdk/tables/Azure.Data.Tables/tests/TableClientQueryExpressionTests.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Linq.Expressions; using System.Xml; +using Azure.Core.TestFramework; using Azure.Data.Tables.Models; using NUnit.Framework; using static Azure.Data.Tables.Tests.TableServiceLiveTestsBase; @@ -176,7 +177,7 @@ public class TableClientQueryExpressionTests new object[] { $"PartitionKey eq '{Partition}'", s_tableEntExpEquals }, }; - public static object[] UnSupportedTableItemExpressionTestCases = + public static object[] UnsupportedTableItemExpressionTestCases = { new object[] { s_TEequalsUnsupported }, new object[] { s_TEequalsStaticUnsupported }, @@ -211,11 +212,38 @@ public void TestDictionaryTableEntityFilterExpressions(string expectedFilter, Ex Assert.That(filter, Is.EqualTo(expectedFilter)); } - [TestCaseSource(nameof(UnSupportedTableItemExpressionTestCases))] + [TestCaseSource(nameof(UnsupportedTableItemExpressionTestCases))] [Test] + [NonParallelizable] public void TestTableItemFilterExpressionsUnsupported(Expression> expression) { Assert.Throws(() => TableClient.CreateQueryFilter(expression)); } + + [TestCaseSource(nameof(UnsupportedTableItemExpressionTestCases))] + [Test] + [NonParallelizable] + public void TestTableItemFilterExpressionsUnsupportedDoesNotThrowWithCompatSwitch(Expression> expression) + { + if (expression == s_TEequalsStaticUnsupported) + { + Assert.Ignore("Ignore this expression because it was never supported."); + } + using var ctx = new TestAppContextSwitch(TableConstants.CompatSwitches.DisableThrowOnStringComparisonFilterSwitchName, true.ToString()); + TableClient.CreateQueryFilter(expression); + } + + [TestCaseSource(nameof(UnsupportedTableItemExpressionTestCases))] + [Test] + [NonParallelizable] + public void TestTableItemFilterExpressionsUnsupportedDoesNotThrowWithCompatSwitchEnv(Expression> expression) + { + if (expression == s_TEequalsStaticUnsupported) + { + Assert.Ignore("Ignore this expression because it was never supported."); + } + using var env = new TestEnvVar(TableConstants.CompatSwitches.DisableThrowOnStringComparisonFilterEnvVar, true.ToString()); + TableClient.CreateQueryFilter(expression); + } } }