Azure / Azure/data-api-builder

[Bug]: smalldatetime column cannot be filtered via OData $filter - three literal forms, three different failurs

Open
#3,760 1 comment 0 reactions 1 assignee Claimed by @aaronburtle View on GitHub
bug
Dominant language
C#
Stars
1.5k
Forks
370
Avg merge
3d 17h
Merged PRs (30d)
8

Description

### What happened?

Filtering a `smalldatetime` column through the MCP DML tools fails for every literal form tried. Three forms were tested and each failed differently: one is rejected by the parser, one fails at parameter binding, and one fails with no type mapping. No form succeeded.

In the parameter-binding case the query itself is correct — DAB resolves the entity and fields and emits well-formed SQL with correct parameter placeholders. The failure occurs when binding the parameter value.

This was against on-premise SQL Server 2022.

### Version

2.0.9

### What database are you using?

Azure SQL

### What hosting model are you using?

Custom Docker host

### Which API approach are you accessing DAB through?

MCP

### Relevant log output

```Text
### 1. `aggregate_records`, ISO 8601 with `Z` — `UnexpectedError`

Arguments:

{
"entity": "ARAS_Charges_By_Day",
"function": "count",
"field": "*",
"filter": "ChargeNumber eq '390100' and DateOfWork ge 2026-07-01T00:00:00Z and DateOfWork lt 2026-08-01T00:00:00Z",
"groupby": ["Username"]
}

SQL generated (correct):

SELECT TOP 1001 [dbo_ARAS_LABOR_CHARGES_BY_DAY_NO_DATE_RESTRICT].[Username] AS [Username],
count([dbo_ARAS_LABOR_CHARGES_BY_DAY_NO_DATE_RESTRICT].[DateOfWork]) AS [count]
FROM [dbo].[ARAS_LABOR_CHARGES_BY_DAY_NO_DATE_RESTRICT] AS [dbo_ARAS_LABOR_CHARGES_BY_DAY_NO_DATE_RESTRICT]
WHERE ((([ChargeNumber] = @param0) AND ([DateOfWork] >= @param1)) AND ([DateOfWork] < @param2))
GROUP BY [dbo_ARAS_LABOR_CHARGES_BY_DAY_NO_DATE_RESTRICT].[Username]
ORDER BY COUNT([dbo_ARAS_LABOR_CHARGES_BY_DAY_NO_DATE_RESTRICT].[DateOfWork]) DESC
FOR JSON PATH, INCLUDE_NULL_VALUES

Server log:

fail: Azure.DataApiBuilder.Mcp.BuiltInTools.AggregateRecordsTool[0]
Unexpected error in AggregateRecordsTool.
System.InvalidCastException: Failed to convert parameter value from a DateTimeOffset to a DateTime.
---> System.InvalidCastException: Object must implement IConvertible.
at System.Convert.ChangeType(Object value, Type conversionType, IFormatProvider provider)
at Microsoft.Data.SqlClient.SqlParameter.CoerceValue(Object value, MetaType destinationType, Boolean& coercedToDataFeed, Boolean& typeChanged, Boolean allowStreaming)
--- End of inner exception stack trace ---
at Azure.DataApiBuilder.Core.Resolvers.QueryExecutor`1.ExecuteQueryAgainstDbAsync[TResult](...) in /_/src/Core/Resolvers/QueryExecutor.cs:line 296
at Azure.DataApiBuilder.Core.Resolvers.QueryExecutor`1.ExecuteQueryAsync[TResult](...) in /_/src/Core/Resolvers/QueryExecutor.cs:line 196
at Azure.DataApiBuilder.Mcp.BuiltInTools.AggregateRecordsTool.ExecuteAsync(JsonDocument arguments, IServiceProvider serviceProvider, CancellationToken cancellationToken) in src\Azure.DataApiBuilder.Mcp\BuiltInTools\AggregateRecordsTool.cs:line 269
warn: Azure.DataApiBuilder.Mcp.BuiltInTools.AggregateRecordsTool[0]
MCP Tool error UnexpectedError: Unexpected error occurred in AggregateRecordsTool.

Returned to the client:

{ "toolName": "aggregate_records", "status": "error",
"error": { "type": "UnexpectedError", "message": "Unexpected error occurred in AggregateRecordsTool." } }

### 2. `read_records`, ISO 8601 without offset — `BadRequest`

Arguments:

{
"entity": "ARAS_Charges_By_Day",
"select": "Username,DateOfWork",
"filter": "ChargeNumber eq '390100' and DateOfWork ge 2026-07-01T00:00:00 and DateOfWork lt 2026-08-01T00:00:00"
}

Returned to the client:

{ "toolName": "read_records", "status": "error",
"error": { "type": "BadRequest",
"message": "The DateTimeOffset text '2026-07-01T00:00:00' should be in format 'yyyy-MM-ddThh:mm:ss('.'s+)?(zzzzzz)?' and each field value is within valid range." } }

So the parser requires the offset that case 1 supplies — and that offset is what case 1 then fails to bind.

### 3. Date-only literal — no type mapping

Using an unquoted date with no time component (`DateOfWork ge 2026-07-01`), the error returned was:

No mapping exists from object type Microsoft.OData.Edm.Date to a known managed provider native type.

## Summary

| Literal form | Result |
|---|---|
| `2026-07-01T00:00:00Z` | `UnexpectedError` — `InvalidCastException`, `DateTimeOffset` → `DateTime` at `SqlParameter.CoerceValue` |
| `2026-07-01T00:00:00` | `BadRequest` — parser requires an offset |
| `2026-07-01` | No mapping from `Microsoft.OData.Edm.Date` to a provider native type |

## Expected behaviour

A `smalldatetime` column should be filterable. The parsed OData value should be converted to the CLR type matching the target column before binding.

Additionally, the case 1 failure reaches the MCP client as a bare `UnexpectedError` with no detail. If this is a value-conversion problem it is user-correctable, and a `BadRequest` naming the field and expected format would let a calling agent self-correct rather than retry blindly.

## Possibly related

- #2783 — `datetime2` columns are emitted in the generated OpenAPI as `string` with no format, and the reporter hit trouble applying date filters.
- #2268 — `SqlClient` coercion failure on date parameters (GraphQL/MSSQL). Different endpoint, same coercion layer.

## Workaround in use

An `int` day key on the view, filtered instead of the date column:

CONVERT(int, CONVERT(char(8), DateOfWork, 112)) AS DateKey
-- DateKey ge 20260701 and DateKey lt 20260801

### Code of Conduct

- [x] I agree to follow this project's Code of Conduct

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.