-
Couldn't load subscription status.
- Fork 284
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
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. | ||
|
|
||
| ## 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 | ||
|
|
||
| ### 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 |
| 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 |
|---|---|---|
| @@ -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
+127
to
+146
|
||
|
|
||
| [JsonIgnore] | ||
| public string DatabaseTypeNotSupportedMessage => $"The provided database-type value: {DatabaseType} is currently not supported. Please check the configuration file."; | ||
| } | ||
|
|
@@ -111,3 +164,8 @@ public record CosmosDbNoSQLDataSourceOptions(string? Database, string? Container | |
| /// Options for MsSql database. | ||
| /// </summary> | ||
| public record MsSqlOptions(bool SetSessionContext = true) : IDataSourceOptions; | ||
|
|
||
| /// <summary> | ||
| /// Options for PostgreSQL database. | ||
| /// </summary> | ||
| public record PostgreSqlOptions(int? CommandTimeout = null) : IDataSourceOptions; | ||
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 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.