Skip to content
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

Unable to Decode sum(int(11)) Values in MySQL #18

Closed
thecheatah opened this issue Mar 15, 2020 · 3 comments · Fixed by #19
Closed

Unable to Decode sum(int(11)) Values in MySQL #18

thecheatah opened this issue Mar 15, 2020 · 3 comments · Fixed by #19
Labels
bug Something isn't working

Comments

@thecheatah
Copy link
Contributor

thecheatah commented Mar 15, 2020

I am not able to decode an integer for a query like

SELECT sum(unread_messages_count) `count` FROM `chat_participant` WHERE `user_id` = \(bind: userId)

where unread_messages_count is an int(11) in mysql. Going through the debugger, it seems that we hit the default case on line 313 of MySQLData.swift.

The value of MySQLData.type that the switch statement is switching off of has a raw value of 246. Type 246 is newdecimal from MySQLProtocol+DataType.swift line 68. Should we handle converting newdecimal to Int? The default .sum() aggregation function tries to decode to Int as well and I was having trouble with the following code:

    return ChatParticipant.query(on: db)
      .filter(\.$user.$id, .equal, userId)
      .sum(\.$numberOfUnreadMessages)
      .unwrap(or: Abort(.internalServerError, reason: "Unable to unwrap sum(numberOfUnreadMessages) for user \(userId)"))

I was getting the same decoding error as the raw query.

I can see why they might use decimal as the summed up columns might not be integers. Looking at the MySQLNIO decoding logic, it doesn't seem like we handle decoding of newdecimal anywhere. newdecimal seems to be unimplemented.

Also, does decoding sum as int by default make sense? (I can be convinced either way) This is dynamic based on the type of the field. The "issue" is that for an int column, mysql is returning a newdecimal that cannot be parsed as an int.

I am using the latest docker image for MySQL 5.7.

My work around for this bug was to cast the sum aggregate as SIGNED

SELECT CAST(sum(unread_messages_count) as SIGNED) `count` FROM `chat_participant` WHERE `user_id` = \(bind: userId)

Added test case that fails:

    func testDecodingSumOfInts() throws {
        let conn = try MySQLConnection.test(on: self.eventLoop).wait()
        defer { try! conn.close().wait() }
        let dropResults = try conn.simpleQuery("DROP TABLE IF EXISTS foos").wait()
        XCTAssertEqual(dropResults.count, 0)
        let createResults = try conn.simpleQuery("CREATE TABLE foos (`item_count` int(11))").wait()
        XCTAssertEqual(createResults.count, 0)
        let rows = try conn.simpleQuery("SELECT sum(`item_count`) as sum from foos").wait()
        guard rows.count == 1 else {
            XCTFail("invalid row count")
            return
        }
        XCTAssertNotNil(rows[0].column("sum"))
        XCTAssertEqual(rows[0].column("sum")?.string, "0")
        XCTAssertEqual(rows[0].column("sum")?.double, 0)
        XCTAssertEqual(rows[0].column("sum")?.int, 0)
    }

Debugger returns

(lldb) po rows[0].column("sum")
▿ Optional<MySQLData>
  ▿ some : nil
    ▿ type : MYSQL_TYPE_NEWDECIMAL
      - rawValue : 246
    - format : MySQLNIO.MySQLData.Format.text
    - buffer : nil
    - isUnsigned : false
@thecheatah thecheatah changed the title Issue decoding sum(int_column) Unable to Decode sum(column) Values Mar 15, 2020
@thecheatah thecheatah changed the title Unable to Decode sum(column) Values Unable to Decode sum(column) Values in MySQL Mar 16, 2020
@thecheatah
Copy link
Contributor Author

thecheatah commented Mar 17, 2020

The native D mysql driver seems to have run into a similar issue: mysql-d/mysql-native#39

@thecheatah thecheatah changed the title Unable to Decode sum(column) Values in MySQL Unable to Decode sum(int(11)) Values in MySQL Mar 17, 2020
@tanner0101 tanner0101 added the bug Something isn't working label Mar 17, 2020
@thecheatah
Copy link
Contributor Author

thecheatah commented Mar 17, 2020

newdecimal appears to be a length encoded string as documented here: https://mariadb.com/kb/en/resultset-row/#decimal-binary-encoding

From observation a 0 is represented as a nil buffer. 1 is represented as the string 1. From the documentation above, numbers can also be 123.456.

@thecheatah
Copy link
Contributor Author

thecheatah commented Mar 19, 2020

Example encoding of 1 as a new decimal

(lldb) po self
▿ <MYSQL_TYPE_NEWDECIMAL>
  ▿ type : MYSQL_TYPE_NEWDECIMAL
    - rawValue : 246
  - format : MySQLNIO.MySQLData.Format.binary
  ▿ buffer : Optional<ByteBuffer>
    ▿ some : ByteBuffer { readerIndex: 0, writerIndex: 1, readableBytes: 1, capacity: 1, slice: _ByteBufferSlice { 7..<8 }, storage: 0x00000001018d8600 (32768 bytes) }
      ▿ _storage : <_Storage: 0x100cad320>
      - _readerIndex : 0
      - _writerIndex : 1
      ▿ _slice : _ByteBufferSlice { 7..<8 }
        - upperBound : 8
        ▿ _begin : 7
          - b12 : 7
          - b3 : 0
  - isUnsigned : false

(lldb) po buffer.readString(length: buffer.readableBytes)
▿ Optional<String>
  - some : "1"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants