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
80 changes: 80 additions & 0 deletions docs/postgresql-command-timeout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# PostgreSQL Command Timeout Configuration
Copy link

Copilot AI Jan 21, 2026

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".

Copilot uses AI. Check for mistakes.

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
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

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

The documentation states "This feature allows you to override the default command timeout for all PostgreSQL queries" but the feature is supposed to work for MSSQL, MySQL, and other database types as well based on the schema. The overview should clarify that this feature supports multiple database types, not just PostgreSQL.

Copilot uses AI. Check for mistakes.

## Configuration

Add the `command-timeout` option to your PostgreSQL data source configuration:

```json
{
"data-source": {
"database-type": "postgresql",
"connection-string": "Host=localhost;Database=mydb;Username=user;Password=pass;",
"options": {
"command-timeout": 60
}
}
}
```

### Parameters

- **command-timeout**: Integer value representing the timeout in seconds
- **Type**: `integer`
- **Minimum**: `0`
- **Default**: `30` (if not specified)
- **Description**: Sets the wait time (in seconds) before terminating the attempt to execute a command and generating an error

### Behavior

1. **Override**: The `command-timeout` value from the configuration will override any `CommandTimeout` parameter present in the connection string
2. **Precedence**: Configuration file setting takes priority over connection string setting
3. **Scope**: Applies to all PostgreSQL queries executed through Data API Builder
Comment on lines +33 to +37
Copy link

Copilot AI Jan 21, 2026

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 uses AI. Check for mistakes.

### Example

```json
{
"$schema": "schemas/dab.draft.schema.json",
"data-source": {
"database-type": "postgresql",
"connection-string": "Host=localhost;Database=bookstore;Username=postgres;Password=password;",
"options": {
"command-timeout": 120
}
},
"runtime": {
"rest": { "enabled": true, "path": "/api" },
"graphql": { "enabled": true, "path": "/graphql" }
},
"entities": {
"Book": {
"source": { "object": "books", "type": "table" },
"permissions": [
{ "role": "anonymous", "actions": [{ "action": "*" }] }
]
}
}
}
```

In this example, all PostgreSQL queries will have a 120-second timeout, regardless of any `CommandTimeout` value in the connection string.

## Implementation Details

The feature is implemented by:

1. **Schema Validation**: The JSON schema validates the `command-timeout` parameter
2. **Options Parsing**: The `PostgreSqlOptions` class parses the timeout value from various data types (integer, string, JsonElement)
3. **Connection String Processing**: The timeout is applied to the Npgsql connection string builder during connection string normalization
4. **Override Logic**: Configuration values take precedence over existing connection string parameters

## Related

- See `samples/postgresql-command-timeout-example.json` for a complete working example
- For other database types, command timeout can be configured directly in the connection string
Copy link

Copilot AI Jan 21, 2026

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.

Suggested change
- 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.

Copilot uses AI. Check for mistakes.
56 changes: 56 additions & 0 deletions samples/connection-string-timeout-example.json
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": "*"
}
]
}
]
}
}
}
59 changes: 59 additions & 0 deletions samples/postgresql-command-timeout-example.json
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": "*"
}
]
}
]
}
}
}
15 changes: 14 additions & 1 deletion schemas/dab.draft.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

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

The configuration property is named "command-timeout" but the documentation and code don't clearly indicate the unit. While it's stated in comments that it's in seconds, the original issue #2766 suggested naming it "command-timeout-seconds" to make the unit explicit. Consider either renaming to "command-timeout-seconds" or ensuring the documentation and schema description clearly specify the unit is seconds to avoid confusion.

Copilot uses AI. Check for mistakes.
Comment on lines +119 to +123
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

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

The schema defines command-timeout with a "default": 30, but this doesn't actually set a default value in JSON schema - it's just documentation. The actual default is implemented in code (DataSource.GetCommandTimeout returns 30). Consider removing the "default" property from the schema or clarifying that it's documentation-only to avoid confusion about how defaults work in JSON schema.

Copilot uses AI. Check for mistakes.
}
}
}
Expand All @@ -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
Copy link

Copilot AI Oct 19, 2025

Choose a reason for hiding this comment

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

The PostgreSQL schema section adds command-timeout support, but MySQL and other database types are not updated. Consider adding command-timeout support to all database types or documenting why it's only available for specific databases.

Copilot uses AI. Check for mistakes.
"required": []
}
}
Expand Down
58 changes: 58 additions & 0 deletions src/Config/ObjectModel/DataSource.cs
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;
Expand Down Expand Up @@ -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");
}

Expand All @@ -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
Copy link

Copilot AI Oct 19, 2025

Choose a reason for hiding this comment

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

The ReadIntOption method duplicates parsing logic found in GetCommandTimeout. Consider consolidating this logic to avoid code duplication and ensure consistent parsing behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +102 to +121
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

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

ReadIntOption doesn't handle the 'long' type case that GetCommandTimeout handles (lines 135-138). When JSON numbers are deserialized, they might come through as long values. For consistency and to prevent potential issues, ReadIntOption should also handle the long type conversion case similar to GetCommandTimeout.

Copilot uses AI. Check for mistakes.

/// <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
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

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

The GetCommandTimeout method duplicates parsing logic that already exists in ReadIntOption. Consider refactoring to use ReadIntOption internally and provide the default value if it returns null: return ReadIntOption("command-timeout") ?? 30;. This reduces code duplication and ensures consistent parsing behavior across the codebase.

Suggested change
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 uses AI. Check for mistakes.
}
Comment on lines +127 to +146
Copy link

Copilot AI Oct 19, 2025

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 uses AI. Check for mistakes.

[JsonIgnore]
public string DatabaseTypeNotSupportedMessage => $"The provided database-type value: {DatabaseType} is currently not supported. Please check the configuration file.";
}
Expand All @@ -111,3 +164,8 @@ public record CosmosDbNoSQLDataSourceOptions(string? Database, string? Container
/// Options for MsSql database.
/// </summary>
public record MsSqlOptions(bool SetSessionContext = true) : IDataSourceOptions;
Copy link

Copilot AI Jan 21, 2026

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;

Suggested change
public record MsSqlOptions(bool SetSessionContext = true) : IDataSourceOptions;
public record MsSqlOptions(bool SetSessionContext = true, int? CommandTimeout = null) : IDataSourceOptions;

Copilot uses AI. Check for mistakes.

/// <summary>
/// Options for PostgreSQL database.
/// </summary>
public record PostgreSqlOptions(int? CommandTimeout = null) : IDataSourceOptions;
17 changes: 14 additions & 3 deletions src/Config/RuntimeConfigLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

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

The command timeout from data source options is not being applied to MSSQL connection strings. The MSSQL connection string processing in GetConnectionStringWithApplicationName (line 246) should also accept the DataSource parameter and apply the command timeout option, similar to how it's done for PostgreSQL in GetPgSqlConnectionStringWithApplicationName. Without this, the command-timeout option will not work for MSSQL databases even though it's allowed in the schema.

Copilot uses AI. Check for mistakes.
}
Comment on lines 247 to 251
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

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

MySQL is missing from the connection string processing logic. The schema allows command-timeout for MySQL (line 144-150), but there's no corresponding connection string processing similar to MSSQL and PostgreSQL. MySQL connection strings also need to be processed to apply the command timeout configuration. This requires either adding a GetMySqlConnectionStringWithApplicationName method or applying the timeout directly to DbCommand objects.

Copilot uses AI. Check for mistakes.

ds = ds with { ConnectionString = updatedConnection };
Expand Down Expand Up @@ -399,8 +399,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 399 to +404
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

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

The GetConnectionStringWithApplicationName method signature should be updated to accept a DataSource parameter (similar to GetPgSqlConnectionStringWithApplicationName) to enable applying command timeout from MsSqlOptions to the connection string. Without this parameter, MSSQL command timeout configuration cannot be properly applied.

Copilot uses AI. Check for mistakes.
{
// If the connection string is null, empty, or whitespace, return it as is.
if (string.IsNullOrWhiteSpace(connectionString))
Expand Down Expand Up @@ -437,6 +438,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;
}
Expand Down
40 changes: 40 additions & 0 deletions src/Service.Tests/Configuration/ConfigurationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5795,6 +5795,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 +5802 to +5836
Copy link

Copilot AI Jan 21, 2026

Choose a reason for hiding this comment

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

The test only verifies that GetCommandTimeout() returns the correct value from configuration, but doesn't test that the timeout is actually applied to database commands or connection strings. Consider adding integration tests that verify the command timeout is properly set on DbCommand objects or in the processed connection strings for each database type (MSSQL, PostgreSQL, MySQL).

Copilot uses AI. Check for mistakes.

/// <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.
Expand Down
Loading