diff --git a/Microsoft.Azure.Cosmos/src/RequestOptions/QueryRequestOptions.cs b/Microsoft.Azure.Cosmos/src/RequestOptions/QueryRequestOptions.cs
index 7d4da6f535..1035fac1c9 100644
--- a/Microsoft.Azure.Cosmos/src/RequestOptions/QueryRequestOptions.cs
+++ b/Microsoft.Azure.Cosmos/src/RequestOptions/QueryRequestOptions.cs
@@ -50,7 +50,7 @@ public class QueryRequestOptions : RequestOptions
///
/// Direct (optimistic) execution offers improved performance for several kinds of queries such as a single partition streaming query.
///
- public bool EnableOptimisticDirectExecution { get; set; } = true;
+ public bool EnableOptimisticDirectExecution { get; set; } = ConfigurationManager.IsOptimisticDirectExecutionEnabled(defaultValue: true);
///
/// Gets or sets the maximum number of items that can be buffered client side during
diff --git a/Microsoft.Azure.Cosmos/src/Util/ConfigurationManager.cs b/Microsoft.Azure.Cosmos/src/Util/ConfigurationManager.cs
index 3c48b1f5ab..1be83b2430 100644
--- a/Microsoft.Azure.Cosmos/src/Util/ConfigurationManager.cs
+++ b/Microsoft.Azure.Cosmos/src/Util/ConfigurationManager.cs
@@ -22,6 +22,11 @@ internal static class ConfigurationManager
///
internal static readonly string PartitionLevelFailoverEnabled = "AZURE_COSMOS_PARTITION_LEVEL_FAILOVER_ENABLED";
+ ///
+ /// Environment variable name for overriding optimistic direct execution of queries.
+ ///
+ internal static readonly string OptimisticDirectExecutionEnabled = "AZURE_COSMOS_OPTIMISTIC_DIRECT_EXECUTION_ENABLED";
+
public static T GetEnvironmentVariable(string variable, T defaultValue)
{
string value = Environment.GetEnvironmentVariable(variable);
@@ -72,5 +77,17 @@ public static bool IsPartitionLevelFailoverEnabled(
variable: ConfigurationManager.PartitionLevelFailoverEnabled,
defaultValue: defaultValue);
}
+
+ ///
+ /// Gets the boolean value indicating whether optimistic direct execution is enabled based on the environment variable override.
+ ///
+ public static bool IsOptimisticDirectExecutionEnabled(
+ bool defaultValue)
+ {
+ return ConfigurationManager
+ .GetEnvironmentVariable(
+ variable: OptimisticDirectExecutionEnabled,
+ defaultValue: defaultValue);
+ }
}
}
diff --git a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/OptimisticDirectExecutionQueryTests.cs b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/OptimisticDirectExecutionQueryTests.cs
index cb70aacb41..dbcfafa5ad 100644
--- a/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/OptimisticDirectExecutionQueryTests.cs
+++ b/Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.EmulatorTests/Query/OptimisticDirectExecutionQueryTests.cs
@@ -524,6 +524,136 @@ public async Task TestClientDisableOdeDefaultValue()
bool success = bool.TryParse(properties.QueryEngineConfiguration[ClientDisableOptimisticDirectExecution].ToString(), out bool clientDisablOde);
Assert.IsTrue(success, $"Parsing must succeed. Value supplied '{ClientDisableOptimisticDirectExecution}'");
Assert.IsFalse(clientDisablOde);
+ }
+
+ [TestMethod]
+ public async Task TestOdeEnvironmentVariable()
+ {
+ QueryRequestOptions options = new QueryRequestOptions();
+ Assert.IsTrue(options.EnableOptimisticDirectExecution);
+
+ foreach ((string name, string value, bool expectedValue) in new[]
+ {
+ // Environment variables are case insensitive in windows
+ ("AZURE_COSMOS_OPTIMISTIC_DIRECT_EXECUTION_ENABLED", "true", true),
+ ("AZURE_COSMOS_optimistic_direct_execution_enabled", "True", true),
+ ("azure_cosmos_optimistic_direct_execution_enabled", "TRUE", true),
+ ("Azure_Cosmos_Optimistic_Direct_Execution_Enabled", "truE", true),
+ ("AZURE_COSMOS_OPTIMISTIC_DIRECT_EXECUTION_ENABLED", "false", false),
+ ("AZURE_COSMOS_optimistic_direct_execution_enabled", "False", false),
+ ("azure_cosmos_optimistic_direct_execution_enabled", "FALSE", false),
+ ("Azure_Cosmos_Optimistic_Direct_Execution_Enabled", "false", false),
+ ("Azure_Cosmos_Optimistic_Direct_Execution_Enabled", string.Empty, true),
+ (nameof(QueryRequestOptions.EnableOptimisticDirectExecution), "false", true),
+ (nameof(QueryRequestOptions.EnableOptimisticDirectExecution), null, true),
+ ("enableode", "false", true)
+ })
+ {
+ try
+ {
+ // Test new value
+ Environment.SetEnvironmentVariable(name, value);
+ QueryRequestOptions options2 = new QueryRequestOptions();
+ bool areEqual = expectedValue == options2.EnableOptimisticDirectExecution;
+ Assert.IsTrue(areEqual, $"EnvironmentVariable:'{name}', value:'{value}', expected:'{expectedValue}', actual:'{options2.EnableOptimisticDirectExecution}'");
+ }
+ finally
+ {
+ // Remove side effects.
+ Environment.SetEnvironmentVariable(name, null);
+ }
+ }
+
+ foreach (string value in new[]
+ {
+ "'",
+ "-",
+ "asdf",
+ "'true'",
+ "'false'"
+ })
+ {
+ bool receivedException = false;
+ try
+ {
+ // Test new value
+ Environment.SetEnvironmentVariable("AZURE_COSMOS_OPTIMISTIC_DIRECT_EXECUTION_ENABLED", value);
+ QueryRequestOptions options2 = new QueryRequestOptions();
+ }
+ catch(FormatException fe)
+ {
+ Assert.IsTrue(fe.ToString().Contains($@"String '{value}' was not recognized as a valid Boolean."));
+ receivedException = true;
+ }
+ finally
+ {
+ // Remove side effects.
+ Environment.SetEnvironmentVariable("AZURE_COSMOS_OPTIMISTIC_DIRECT_EXECUTION_ENABLED", null);
+ }
+
+ Assert.IsTrue(receivedException, $"Expected exception was not received for value '{value}'");
+ }
+
+ await this.TestQueryExecutionUsingODEEnvironmentVariable(
+ environmentVariableValue: "false",
+ expectODEPipeline: false);
+
+ await this.TestQueryExecutionUsingODEEnvironmentVariable(
+ environmentVariableValue: "true",
+ expectODEPipeline: true);
+ }
+
+ private async Task TestQueryExecutionUsingODEEnvironmentVariable(string environmentVariableValue, bool expectODEPipeline)
+ {
+ IReadOnlyList empty = new List(0);
+ IReadOnlyList first5Integers = Enumerable.Range(0, 5).ToList();
+ IReadOnlyList first7Integers = Enumerable.Range(0, NumberOfDocuments).ToList();
+ IReadOnlyList first7IntegersReversed = Enumerable.Range(0, NumberOfDocuments).Reverse().ToList();
+
+ try
+ {
+ // Test query execution using environment variable
+ Environment.SetEnvironmentVariable("AZURE_COSMOS_OPTIMISTIC_DIRECT_EXECUTION_ENABLED", environmentVariableValue);
+ PartitionKey partitionKeyValue = new PartitionKey("/value");
+ List singlePartitionContainerTestCases = new List()
+ {
+ CreateInput(
+ query: $"SELECT TOP 5 VALUE r.numberField FROM r ORDER BY r.{PartitionKeyField}",
+ expectedResult: first5Integers,
+ partitionKey: partitionKeyValue,
+ enableOptimisticDirectExecution: null, // Uses environment variable
+ pageSizeOptions: PageSizeOptions.NonGroupByAndNoContinuationTokenPageSizeOptions,
+ expectedPipelineType: expectODEPipeline ? TestInjections.PipelineType.OptimisticDirectExecution : TestInjections.PipelineType.Passthrough),
+ CreateInput(
+ query: $"SELECT TOP 5 VALUE r.numberField FROM r ORDER BY r.{PartitionKeyField}",
+ expectedResult: first5Integers,
+ partitionKey: partitionKeyValue,
+ enableOptimisticDirectExecution: false, // Overrides environment variable
+ pageSizeOptions: PageSizeOptions.NonGroupByAndNoContinuationTokenPageSizeOptions,
+ expectedPipelineType: TestInjections.PipelineType.Passthrough),
+ CreateInput(
+ query: $"SELECT TOP 5 VALUE r.numberField FROM r ORDER BY r.{PartitionKeyField}",
+ expectedResult: first5Integers,
+ partitionKey: partitionKeyValue,
+ enableOptimisticDirectExecution: true, // Overrides environment variable
+ pageSizeOptions: PageSizeOptions.NonGroupByAndNoContinuationTokenPageSizeOptions,
+ expectedPipelineType: TestInjections.PipelineType.OptimisticDirectExecution),
+ };
+
+ IReadOnlyList documents = CreateDocuments(NumberOfDocuments, PartitionKeyField, NumberField, NullField);
+
+ await this.CreateIngestQueryDeleteAsync(
+ ConnectionModes.Direct | ConnectionModes.Gateway,
+ CollectionTypes.SinglePartition,
+ documents,
+ (container, documents) => RunTests(singlePartitionContainerTestCases, container),
+ "/" + PartitionKeyField);
+ }
+ finally
+ {
+ // Attempt to protect other ODE tests from side-effects in case of test failure.
+ Environment.SetEnvironmentVariable("AZURE_COSMOS_OPTIMISTIC_DIRECT_EXECUTION_ENABLED", null);
+ }
}
private static async Task RunTests(IEnumerable testCases, Container container)
@@ -536,9 +666,13 @@ private static async Task RunTests(IEnumerable testCase
{
MaxItemCount = pageSize,
PartitionKey = testCase.PartitionKey,
- EnableOptimisticDirectExecution = testCase.EnableOptimisticDirectExecution,
TestSettings = new TestInjections(simulate429s: false, simulateEmptyPages: false, new TestInjections.ResponseStats())
- };
+ };
+
+ if(testCase.EnableOptimisticDirectExecution.HasValue)
+ {
+ feedOptions.EnableOptimisticDirectExecution = testCase.EnableOptimisticDirectExecution.Value;
+ }
List items = await RunQueryAsync(
container,
@@ -600,7 +734,7 @@ private static DirectExecutionTestCase CreateInput(
string query,
IReadOnlyList expectedResult,
PartitionKey? partitionKey,
- bool enableOptimisticDirectExecution,
+ bool? enableOptimisticDirectExecution,
int[] pageSizeOptions,
TestInjections.PipelineType expectedPipelineType)
{
@@ -612,7 +746,7 @@ private readonly struct DirectExecutionTestCase
public string Query { get; }
public IReadOnlyList ExpectedResult { get; }
public PartitionKey? PartitionKey { get; }
- public bool EnableOptimisticDirectExecution { get; }
+ public bool? EnableOptimisticDirectExecution { get; }
public int[] PageSizeOptions { get; }
public TestInjections.PipelineType ExpectedPipelineType { get; }
@@ -620,7 +754,7 @@ public DirectExecutionTestCase(
string query,
IReadOnlyList expectedResult,
PartitionKey? partitionKey,
- bool enableOptimisticDirectExecution,
+ bool? enableOptimisticDirectExecution,
int[] pageSizeOptions,
TestInjections.PipelineType expectedPipelineType)
{