-
Notifications
You must be signed in to change notification settings - Fork 304
Fix command timeout issues in DAB #2912
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
76b88d7
faea032
410d458
9a23ff6
2fb1b78
857fced
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,80 @@ | ||||||
| # PostgreSQL Command Timeout Configuration | ||||||
|
|
||||||
| This document describes how to configure command timeout for PostgreSQL data sources in Data API Builder. | ||||||
|
|
||||||
| ## Overview | ||||||
|
|
||||||
| Data API Builder now supports configuring PostgreSQL command timeout through the `command-timeout` option in the data source configuration. This feature allows you to override the default command timeout for all PostgreSQL queries executed by Data API Builder. | ||||||
|
Comment on lines
+5
to
+7
|
||||||
| Data API Builder now supports configuring PostgreSQL command timeout through the `command-timeout` option in the data source configuration. This feature allows you to override the default command timeout for all PostgreSQL queries executed by Data API Builder. | |
| Data API builder now supports configuring PostgreSQL command timeout through the `command-timeout` option in the data source configuration. This feature allows you to override the default command timeout for all PostgreSQL queries executed by Data API builder. |
Copilot
AI
Jan 21, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation incorrectly claims the feature is PostgreSQL-specific throughout lines 35-37, 65-66, and 72-75. However, the JSON schema allows this option for MSSQL, MySQL, PostgreSQL, and cosmosdb_postgresql. The documentation should reflect support for all these database types and clarify any database-specific implementation differences.
Copilot
AI
Jan 21, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The statement "For other database types, command timeout can be configured directly in the connection string" is misleading. According to the schema (dab.draft.schema.json lines 119-150), MSSQL, MySQL, PostgreSQL, and cosmosdb_postgresql all support the command-timeout option. The documentation should clarify which databases support the option in the data-source.options configuration versus connection string only.
| - For other database types, command timeout can be configured directly in the connection string | |
| - For other supported database types (such as MSSQL, MySQL, and cosmosdb_postgresql), `command-timeout` can also be configured via the `data-source.options.command-timeout` setting, or via the connection string parameters supported by each provider. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| { | ||
| "$schema": "../schemas/dab.draft.schema.json", | ||
| "data-source": { | ||
| "database-type": "postgresql", | ||
| "connection-string": "Host=localhost;Port=5432;Username=postgres;Password=password;Database=bookshelf;CommandTimeout=120" | ||
| }, | ||
| "runtime": { | ||
| "rest": { | ||
| "enabled": true, | ||
| "path": "/api" | ||
| }, | ||
| "graphql": { | ||
| "enabled": true, | ||
| "path": "/graphql", | ||
| "allow-introspection": true | ||
| }, | ||
| "host": { | ||
| "cors": { | ||
| "origins": ["*"], | ||
| "allow-credentials": false | ||
| }, | ||
| "authentication": { | ||
| "provider": "StaticWebApps" | ||
| }, | ||
| "mode": "development" | ||
| } | ||
| }, | ||
| "entities": { | ||
| "Book": { | ||
| "source": { | ||
| "object": "books", | ||
| "type": "table" | ||
| }, | ||
| "graphql": { | ||
| "enabled": true, | ||
| "type": { | ||
| "singular": "Book", | ||
| "plural": "Books" | ||
| } | ||
| }, | ||
| "rest": { | ||
| "enabled": true | ||
| }, | ||
| "permissions": [ | ||
| { | ||
| "role": "anonymous", | ||
| "actions": [ | ||
| { | ||
| "action": "*" | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| { | ||
| "$schema": "../schemas/dab.draft.schema.json", | ||
| "data-source": { | ||
| "database-type": "postgresql", | ||
| "connection-string": "Host=localhost;Database=bookstore;Username=postgres;Password=password;", | ||
| "options": { | ||
| "command-timeout": 60 | ||
| } | ||
| }, | ||
| "runtime": { | ||
| "rest": { | ||
| "enabled": true, | ||
| "path": "/api" | ||
| }, | ||
| "graphql": { | ||
| "enabled": true, | ||
| "path": "/graphql", | ||
| "allow-introspection": true | ||
| }, | ||
| "host": { | ||
| "cors": { | ||
| "origins": ["*"], | ||
| "allow-credentials": false | ||
| }, | ||
| "authentication": { | ||
| "provider": "StaticWebApps" | ||
| }, | ||
| "mode": "development" | ||
| } | ||
| }, | ||
| "entities": { | ||
| "Book": { | ||
| "source": { | ||
| "object": "books", | ||
| "type": "table" | ||
| }, | ||
| "graphql": { | ||
| "enabled": true, | ||
| "type": { | ||
| "singular": "Book", | ||
| "plural": "Books" | ||
| } | ||
| }, | ||
| "rest": { | ||
| "enabled": true | ||
| }, | ||
| "permissions": [ | ||
| { | ||
| "role": "anonymous", | ||
| "actions": [ | ||
| { | ||
| "action": "*" | ||
| } | ||
| ] | ||
| } | ||
| ] | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -115,6 +115,12 @@ | |
| "set-session-context": { | ||
| "type": "boolean", | ||
| "description": "Enable sending data to MsSql using session context" | ||
| }, | ||
| "command-timeout": { | ||
| "type": "integer", | ||
| "description": "Command timeout in seconds for database operations", | ||
| "minimum": 0, | ||
| "default": 30 | ||
|
Comment on lines
+119
to
+123
|
||
| } | ||
| } | ||
| } | ||
|
|
@@ -135,7 +141,14 @@ | |
| "options": { | ||
| "type": "object", | ||
| "additionalProperties": false, | ||
| "properties": {}, | ||
| "properties": { | ||
| "command-timeout": { | ||
| "type": "integer", | ||
| "description": "Command timeout in seconds for database operations", | ||
| "minimum": 0, | ||
| "default": 30 | ||
| } | ||
| }, | ||
|
Comment on lines
+144
to
+151
|
||
| "required": [] | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,7 @@ | ||||||||||||||||||||||||||||||||||||||
| // Copyright (c) Microsoft Corporation. | ||||||||||||||||||||||||||||||||||||||
| // Licensed under the MIT License. | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| using System.Text.Json; | ||||||||||||||||||||||||||||||||||||||
| using System.Text.Json.Serialization; | ||||||||||||||||||||||||||||||||||||||
| using Azure.DataApiBuilder.Config.HealthCheck; | ||||||||||||||||||||||||||||||||||||||
| using Azure.DataApiBuilder.Config.NamingPolicies; | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -69,6 +70,12 @@ public int DatasourceThresholdMs | |||||||||||||||||||||||||||||||||||||
| SetSessionContext: ReadBoolOption(namingPolicy.ConvertName(nameof(MsSqlOptions.SetSessionContext)))); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if (typeof(TOptionType).IsAssignableFrom(typeof(PostgreSqlOptions))) | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| return (TOptionType)(object)new PostgreSqlOptions( | ||||||||||||||||||||||||||||||||||||||
| CommandTimeout: ReadIntOption(namingPolicy.ConvertName("command-timeout"))); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| throw new NotSupportedException($"The type {typeof(TOptionType).FullName} is not a supported strongly typed options object"); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
@@ -92,6 +99,52 @@ private bool ReadBoolOption(string option) | |||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| private int? ReadIntOption(string option) | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| if (Options is not null && Options.TryGetValue(option, out object? value)) | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| if (value is int intValue) | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| return intValue; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| else if (value is string stringValue && int.TryParse(stringValue, out int parsedValue)) | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| return parsedValue; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| else if (value is JsonElement jsonElement && jsonElement.ValueKind == JsonValueKind.Number && jsonElement.TryGetInt32(out int jsonValue)) | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| return jsonValue; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+102
to
+121
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||
| /// Gets the command timeout value from the options. | ||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||
| /// <returns>The command timeout in seconds, or 30 (default) if not specified.</returns> | ||||||||||||||||||||||||||||||||||||||
| public int GetCommandTimeout() | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| if (Options is not null && Options.TryGetValue("command-timeout", out object? value)) | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| if (value is int intValue) | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| return intValue; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| else if (value is long longValue && longValue <= int.MaxValue && longValue >= int.MinValue) | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| return (int)longValue; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| else if (value is string stringValue && int.TryParse(stringValue, out int parsedValue)) | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| return parsedValue; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| return 30; // default command timeout | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+129
to
+145
|
||||||||||||||||||||||||||||||||||||||
| if (Options is not null && Options.TryGetValue("command-timeout", out object? value)) | |
| { | |
| if (value is int intValue) | |
| { | |
| return intValue; | |
| } | |
| else if (value is long longValue && longValue <= int.MaxValue && longValue >= int.MinValue) | |
| { | |
| return (int)longValue; | |
| } | |
| else if (value is string stringValue && int.TryParse(stringValue, out int parsedValue)) | |
| { | |
| return parsedValue; | |
| } | |
| } | |
| return 30; // default command timeout | |
| return ReadIntOption("command-timeout") ?? 30; |
Copilot
AI
Oct 19, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The magic number 30 for default command timeout should be extracted as a named constant to improve maintainability and avoid duplication with the schema default value.
Copilot
AI
Jan 21, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The MsSqlOptions record should include a CommandTimeout property similar to PostgreSqlOptions. Currently, the schema allows command-timeout for MSSQL (line 119-124 in dab.draft.schema.json), but there's no corresponding property in MsSqlOptions to parse and store it. This needs to be added: public record MsSqlOptions(bool SetSessionContext = true, int? CommandTimeout = null) : IDataSourceOptions;
| public record MsSqlOptions(bool SetSessionContext = true) : IDataSourceOptions; | |
| public record MsSqlOptions(bool SetSessionContext = true, int? CommandTimeout = null) : IDataSourceOptions; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -247,7 +247,7 @@ public static bool TryParseConfig(string json, | |
| } | ||
| else if (ds.DatabaseType is DatabaseType.PostgreSQL && replacementSettings?.DoReplaceEnvVar == true) | ||
| { | ||
| updatedConnection = GetPgSqlConnectionStringWithApplicationName(connectionValue); | ||
| updatedConnection = GetPgSqlConnectionStringWithApplicationName(connectionValue, ds); | ||
|
Comment on lines
247
to
+250
|
||
| } | ||
|
Comment on lines
247
to
251
|
||
|
|
||
| ds = ds with { ConnectionString = updatedConnection }; | ||
|
|
@@ -400,8 +400,9 @@ internal static string GetConnectionStringWithApplicationName(string connectionS | |
| /// else add the Application Name property with DataApiBuilder Application Name based on hosted/oss platform. | ||
| /// </summary> | ||
| /// <param name="connectionString">Connection string for connecting to database.</param> | ||
| /// <returns>Updated connection string with `Application Name` property.</returns> | ||
| internal static string GetPgSqlConnectionStringWithApplicationName(string connectionString) | ||
| /// <param name="dataSource">The data source configuration, used to get command timeout override.</param> | ||
| /// <returns>Updated connection string with `Application Name` property and command timeout.</returns> | ||
| internal static string GetPgSqlConnectionStringWithApplicationName(string connectionString, DataSource? dataSource = null) | ||
|
Comment on lines
400
to
+405
|
||
| { | ||
| // If the connection string is null, empty, or whitespace, return it as is. | ||
| if (string.IsNullOrWhiteSpace(connectionString)) | ||
|
|
@@ -438,6 +439,16 @@ internal static string GetPgSqlConnectionStringWithApplicationName(string connec | |
| connectionStringBuilder.ApplicationName += $",{applicationName}"; | ||
| } | ||
|
|
||
| // Apply command timeout from data source configuration if specified (overrides connection string value) | ||
| if (dataSource?.Options is not null) | ||
| { | ||
| PostgreSqlOptions? pgOptions = dataSource.GetTypedOptions<PostgreSqlOptions>(); | ||
| if (pgOptions?.CommandTimeout is not null) | ||
| { | ||
| connectionStringBuilder.CommandTimeout = pgOptions.CommandTimeout.Value; | ||
| } | ||
| } | ||
|
|
||
| // Return the updated connection string. | ||
| return connectionStringBuilder.ConnectionString; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5871,6 +5871,46 @@ public static EntityPermission GetMinimalPermissionConfig(string roleName) | |
| ); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Test that command timeout is properly read from data source options and applied to connection string. | ||
| /// </summary> | ||
| [TestMethod] | ||
| public void TestCommandTimeoutFromDataSourceOptions() | ||
| { | ||
| // Test SQL Server | ||
| Dictionary<string, object> mssqlOptions = new() | ||
| { | ||
| { "command-timeout", 60 }, | ||
| { "set-session-context", true } | ||
| }; | ||
| DataSource mssqlDataSource = new(DatabaseType.MSSQL, "Server=localhost;Database=test;", mssqlOptions); | ||
| Assert.AreEqual(60, mssqlDataSource.GetCommandTimeout()); | ||
|
|
||
| // Test PostgreSQL | ||
| Dictionary<string, object> pgOptions = new() | ||
| { | ||
| { "command-timeout", 120 } | ||
| }; | ||
| DataSource pgDataSource = new(DatabaseType.PostgreSQL, "Host=localhost;Database=test;", pgOptions); | ||
| Assert.AreEqual(120, pgDataSource.GetCommandTimeout()); | ||
|
|
||
| // Test MySQL | ||
| Dictionary<string, object> mysqlOptions = new() | ||
| { | ||
| { "command-timeout", 45 } | ||
| }; | ||
| DataSource mysqlDataSource = new(DatabaseType.MySQL, "Server=localhost;Database=test;", mysqlOptions); | ||
| Assert.AreEqual(45, mysqlDataSource.GetCommandTimeout()); | ||
|
|
||
| // Test default value when not specified | ||
| DataSource defaultDataSource = new(DatabaseType.MSSQL, "Server=localhost;Database=test;", new()); | ||
| Assert.AreEqual(30, defaultDataSource.GetCommandTimeout()); | ||
|
|
||
| // Test null options | ||
| DataSource nullOptionsDataSource = new(DatabaseType.MSSQL, "Server=localhost;Database=test;", null); | ||
| Assert.AreEqual(30, nullOptionsDataSource.GetCommandTimeout()); | ||
| } | ||
|
Comment on lines
+5878
to
+5912
|
||
|
|
||
| /// <summary> | ||
| /// Reads configuration file for defined environment to acquire the connection string. | ||
| /// CI/CD Pipelines and local environments may not have connection string set as environment variable. | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The documentation title and content are misleading. This feature is not PostgreSQL-specific - the schema allows command-timeout for MSSQL, PostgreSQL, MySQL, and cosmosdb_postgresql (lines 119-150 in dab.draft.schema.json). The documentation should be generalized to cover all supported database types, not just PostgreSQL. Consider renaming this file to "command-timeout.md" or "database-command-timeout.md".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@copilot address this feedback