In Postgres database, getting aggregate values of sum and average will throw PostgresDecodingError
(MySQL and SQLite databases both works as expected, and the bug is only occurred when using Postgres.)
Create model and migration for a simple model containing an Integer column.
final class User: Model {
static let schema = "users"
@ID(key: .id)
var id: UUID?
@Field(key: "balance")
var balance: Int
init() { }
init(id: UUID? = nil, balance: Int) {
self.id = id
self.balance = balance
}
}
As @gwynne mentioned in Discord:
The problem is caused by two factors:
Postgres is - correctly - concerned that taking the sum of a given series of values of a given integer type could overflow that type (imagine, for example, trying to add any positive number to an integer with the value 2_147_483_647, the maximum possible value for that type). Postgres returns the result as the next-larger integer type, which in the case of bigint is numeric (because there are no larger purely-integer types). In the case of avg(), it's the rather more straightforward issue that the average of any given series of integers of any size is not itself necessarily an integer. Notably, MySQL and SQLite don't do this, or at least not in a way that trips over the second factor:
Fluent does not take into account the fact that many aggregate functions do not return a value of the same type as their inputs. This is what really makes this ugly to fix; the fix would require breaking public API.
Describe the bug
In Postgres database, getting aggregate values of
sum
andaverage
will throwPostgresDecodingError
(MySQL and SQLite databases both works as expected, and the bug is only occurred when using Postgres.)PostgresDecodingError(code: typeMismatch, columnName: "aggregate", columnIndex: 0, targetType: Swift.Optional<Swift.Int>, postgresType: NUMERIC, postgresFormat: binary, postgresData: ByteBuffer { readerIndex: 0, writerIndex: 10, readableBytes: 10, capacity: 10, storageCapacity: 2048, slice: _ByteBufferSlice { 71..<81 }, storage: 0x0000000101827600 (2048 bytes) }
To Reproduce
Steps to reproduce the behavior:
Create model and migration for a simple model containing an
Integer
column.Create migrations for
User
modelAdd a method to get
sum
andaverage
of balance for all usersCall any of
getSum
orgetAverage
methods will cause the app to crash.Expected behavior
It is expected to get the
sum
oraverage
values of the given column.Environment
macOS Ventura 13.4 (22F66) Swift 5.8 Vapor framework: 4.77.0 toolbox: 18.7.1
Additional context
As @gwynne mentioned in Discord: The problem is caused by two factors:
Postgres is - correctly - concerned that taking the sum of a given series of values of a given integer type could overflow that type (imagine, for example, trying to add any positive number to an integer with the value 2_147_483_647, the maximum possible value for that type). Postgres returns the result as the next-larger integer type, which in the case of bigint is numeric (because there are no larger purely-integer types). In the case of avg(), it's the rather more straightforward issue that the average of any given series of integers of any size is not itself necessarily an integer. Notably, MySQL and SQLite don't do this, or at least not in a way that trips over the second factor:
Fluent does not take into account the fact that many aggregate functions do not return a value of the same type as their inputs. This is what really makes this ugly to fix; the fix would require breaking public API.