Skip to content

driver-sql (PG): sum/avg/min/max over a boolean column throw the raw PostgreSQL 42883 with no ADR-0112 envelope (status undefined) #11455

Description

@os-steve

Measured on a live PostgreSQL 16.13 while taking the #11152 readings. Filed unassigned as an out-of-scope finding — #11152 is scoped to packages/spec and may not touch a driver.

What was measured

driver-sql maps a boolean field to table.boolean(name), which is a real boolean column on Postgres (confirmed via columnInfo(): {"flag":{"type":"boolean",...}}). SQL_AGGREGATE_FUNCTIONS lowers the arithmetic aggregates to a bare function name with no cast, so the statement reaches the server as avg("flag").

PostgreSQL has no avg/sum/min/max over boolean, so the call fails — and the failure escapes SqlDriver.aggregate()un-enveloped:

sum(flag) => THREW code=42883 status=undefined
msg=select sum("flag") as "n" from "probe_bool_agg_pg" - function sum(boolean) does not exist
avg(flag) => THREW code=42883 status=undefined
msg=select avg("flag") as "n" from "probe_bool_agg_pg" - function avg(boolean) does not exist
min(flag) => THREW code=42883 status=undefined - function min(boolean) does not exist
max(flag) => THREW code=42883 status=undefined - function max(boolean) does not exist
count(flag) => OK 6 (count is defined over any type)
count_distinct(flag) => OK 2

code is the raw SQLSTATE and status is undefined. The same four calls answer fine on SQLite (sum 3, avg 0.5, min false, max true).

Why this is a defect independent of the contract question

Whether the platform should answer a number here (by casting boolean to int in the lowering) or refuse is a contract question that belongs to #11152 / #11249. This card is the other half, and it holds either way: when it fails, the failure must carry a catalogued code and status.

A raw 42883 is not in @objectstack/rest's isExpectedQueryRejection list, so a caller-shaped mistake (asking for a rate measure over a flag column — an ordinary analytics shape) is logged as an unhandled server fault and answered as a 500.

This is a new instance of a well-worn family, all previously fixed the same way: #5907 (bare Error, code/status undefined on the aggregate door), #8790 (count() threw a raw dialect error with no ADR-0112 envelope), #8926 (MySQL wording matched by neither predicate arm, so no envelope), #8931 (dotted WHERE key escaping as a raw dialect error).

Repro

Declare an object with a boolean field on a live PG connection, seed rows, then driver.aggregate(table, { aggregations: [{ function: 'avg', field: 'flag', alias: 'n' }] }). Measured with PostgreSQL 16.13, driver-sql at 5a916c4d4d.

Out of scope here

Related: #11152, #11249, #11065, #11151.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions