fluent icon indicating copy to clipboard operation
fluent copied to clipboard

Problem with data types when using aggregate functions

Open nerzh opened this issue 6 months ago • 0 comments

Describe the issue

Unable to interpret value of PSQL type NUMERIC

Vapor version

4.102.1

Operating system and version

macos 15

Swift version

swiftlang-6.0.0.6.8

Steps to reproduce

1 - Create a table with columns using other tools Снимок экрана 2024-08-05 в 22 03 36

2 - Fluent code that causes an exception

final class CleanedShares: Model, @unchecked Sendable {
    
    static var schema: String { "cleaned_shares" }
    
    @ID(custom: "id", generatedBy: .database)
    var id: Int64?
    @Timestamp(key: "created_at", on: .create)
    var createdAt: Date?
    @Timestamp(key: "updated_at", on: .update)
    var updatedAt: Date?
    
    @Field(key: "user_id")
    var userId: Int64
    
    @Field(key: "shares")
    var shares: Int64
    
    /// See `Table`
    init() {}
    
    init(
        userId: Int64,
        shares: Int64
    ) {
        self.userId = userId
        self.shares = shares
    }
}

struct Sum: Codable {
    var sum: Int64
}

3 - Calling the following functions causes a postgres error

let object = try await db.query(CleanedShares.self).sum(\.$shares) // CRASH
let object = try await db.query(CleanedShares.self).aggregate(.sum, \.$shares, as: Sum.self) // CRASH

Swift.DecodingError.dataCorrupted(Swift.DecodingError.Context(codingPath: [SomeCodingKey(stringValue: "aggregate (0)", intValue: nil)], debugDescription: "Unable to interpret value of PSQL type NUMERIC: ByteBuffer { readerIndex: 0, writerIndex: 14, readableBytes: 14, capacity: 14, storageCapacity: 2048, slice: _ByteBufferSlice { 63..<77 }, storage: 0x0000000135011400 (2048 bytes) }", underlyingError: nil))


  1. How to "fix"
  • works if you change the type to Decaimal in the mode
@Field(key: "shares")
var shares: Decimal

and

struct Sum: Codable {
    var sum: Decimal
}

but at the same time we remember that in the database it is Int8 byte, that is, it is Int64, and we specified the Decimal floating point type

  • the only acceptable working option is to write raw sql
guard
   let object = try await sqlDataBase.raw(
      """
      SELECT SUM(\(unsafeRaw: try CleanedShares.field(\CleanedShares.$shares))) AS sum
      FROM \(unsafeRaw: CleanedShares.schema);
      """
      ).first(decoding: Sum.self)
  else {
      throw AppError("Aggregate sum - failed")
  }

But codable struct must have Decimal type of the "sum" property anyway

struct Sum: Codable {
    var sum: Decimal
}

Total:

in total, everything works “normally” only if in the model and in the encoded structure this field is in Decimal format

Outcome

No response

Additional notes

No response

nerzh avatar Aug 05 '24 20:08 nerzh