Code Monkey home page Code Monkey logo

mysql-nio's Introduction

MySQLNIO

Documentation Team Chat MIT License Continuous Integration Swift 5.7+


🐬 Non-blocking, event-driven Swift client for MySQL built on SwiftNIO.

Using MySQLNIO

Use standard SwiftPM syntax to include MySQLNIO as a dependency in your Package.swift file.

.package(url: "https://github.com/vapor/mysql-nio.git", from: "1.0.0")

Supported Platforms

MySQLNIO supports the following platforms:

  • Ubuntu 20.04+
  • macOS 10.15+
  • iOS 13+
  • tvOS 13+ and watchOS 7+ (experimental)

Overview

MySQLNIO is a client package for connecting to, authorizing, and querying a MySQL server. At the heart of this module are NIO channel handlers for parsing and serializing messages in MySQL's proprietary wire protocol. These channel handlers are combined in a request / response style connection type that provides a convenient, client-like interface for performing queries.

Support for both simple (text) and parameterized (prepared statement) querying is provided out of the box alongside a MySQLData type that handles conversion between MySQL's wire format and native Swift types.

Motivation

Most Swift implementations of MySQL clients are based on the libmysqlclient C library which handles transport internally. Building a library directly on top of MySQL's wire protocol using SwiftNIO should yield a more reliable, maintainable, and performant interface for MySQL databases.

Goals

This package is meant to be a low-level, unopinionated MySQL wire-protocol implementation for Swift. The hope is that higher level packages can share MySQLNIO as a foundation for interacting with MySQL servers without needing to duplicate complex logic.

Because of this, MySQLNIO excludes some important concepts for the sake of simplicity, such as:

  • Connection pooling
  • Swift Codable integration
  • Query building

If you are looking for a MySQL client package to use in your project, take a look at these higher-level packages built on top of MySQLNIO:

Dependencies

This package has four dependencies:

This package has no additional system dependencies.

API Docs

Check out the MySQLNIO API docs for a detailed look at all of the classes, structs, protocols, and more.

Getting Started

This section will provide a quick look at using MySQLNIO.

Creating a Connection

The first step to making a query is creating a new MySQLConnection. The minimum requirements to create one are a SocketAddress, EventLoop, and credentials.

import MySQLNIO

let eventLoop: any EventLoop = ...
let conn = try await MySQLConnection(
    to: .makeAddressResolvingHost("my.mysql.server", port: 3306),
    username: "test_username",
    database: "test_database",
    password: "test_password",
    on: eventLoop
).get()

Note: These examples will make use of wait() for simplicity. This is appropriate if you are using MySQLNIO on the main thread, like for a CLI tool or in tests. However, you should never use wait() on an event loop.

There are a few ways to create a SocketAddress:

  • init(ipAddress: String, port: Int)
  • init(unixDomainSocketPath: String)
  • makeAddressResolvingHost(_ host: String, port: Int)

There are also some additional arguments you can supply to connect.

  • tlsConfiguration An optional TLSConfiguration struct. This will be used if the MySQL server supports TLS. Pass nil to opt-out of TLS.
  • serverHostname An optional String to use in conjunction with tlsConfiguration to specify the server's hostname.

connect will return a future MySQLConnection, or an error if it could not connect.

Database Protocol

Interaction with a server revolves around the MySQLDatabase protocol. This protocol includes methods like query(_:) for executing SQL queries and reading the resulting rows.

MySQLConnection is the default implementation of MySQLDatabase provided by this package. Assume the client here is the connection from the previous example.

import MySQLNIO

let db: any MySQLDatabase = ...
// now we can use client to do queries

Simple Query

Simple (text) queries allow you to execute a SQL string on the connected MySQL server. These queries do not support binding parameters, so any values sent must be escaped manually.

These queries are most useful for schema or transactional queries, or simple selects. Note that values returned by simple queries will be transferred in the less efficient text format.

simpleQuery has two overloads, one that returns an array of rows, and one that accepts a closure for handling each row as it is returned.

let rows = try await db.simpleQuery("SELECT @@version").get()
print(rows) // [["@@version": "8.x.x"]]

try await db.simpleQuery("SELECT @@version") { row in
    print(row) // ["@@version": "8.x.x"]
}.get()

Parameterized Query

Parameterized (prepared statement) queries allow you to execute a SQL string on the connected MySQL server. These queries support passing bound parameters as a separate argument. Each parameter is represented in the SQL string using placeholders (?).

These queries are most useful for selecting, inserting, and updating data. Data for these queries is transferred using the highly efficient binary format.

Just like simpleQuery, query also offers two overloads. One that returns an array of rows, and one that accepts a closure for handling each row as it is returned.

let rows = try await db.query("SELECT * FROM planets WHERE name = ?", ["Earth"]).get()
print(rows) // [["id": 42, "name": "Earth"]]

try await db.query("SELECT * FROM planets WHERE name = ?", ["Earth"]) { row in
    print(row) // ["id": 42, "name": "Earth"]
}.get()

Rows and Data

Both simpleQuery and query return the same MySQLRow type. Columns can be fetched from the row using the column(_:table:) method.

let row: any MySQLRow = ...
let version = row.column("name")
print(version) // MySQLData?

MySQLRow columns are stored as MySQLData. This struct contains the raw bytes returned by MySQL as well as some information for parsing them, such as:

  • MySQL data type
  • Wire format: binary or text
  • Value as array of bytes

MySQLData has a variety of convenience methods for converting column data to usable Swift types.

let data: MySQLData = ...

print(data.string) // String?

print(data.int) // Int?
print(data.int8) // Int8?
print(data.int16) // Int16?
print(data.int32) // Int32?
print(data.int64) // Int64?

print(data.uint) // UInt?
print(data.uint8) // UInt8?
print(data.uint16) // UInt16?
print(data.uint32) // UInt32?
print(data.uint64) // UInt64?

print(data.bool) // Bool?

try print(data.json(as: Foo.self)) // Foo?

print(data.float) // Float?
print(data.double) // Double?

print(data.date) // Date?
print(data.uuid) // UUID?
print(data.decimal) // Decimal?

print(data.time) // MySQLTime?

MySQLData is also used for sending data to the server via parameterized values. To create MySQLData from a Swift type, use the available intializer methods.

mysql-nio's People

Contributors

0xtim avatar bennydebock avatar elgeekalpha avatar fabianfett avatar florianreinhart avatar gwynne avatar jaapwijnen avatar jetforme avatar lluuaapp avatar nobodynada avatar nostradani avatar samalone avatar tanner0101 avatar thecheatah avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mysql-nio's Issues

Support connection reset

Problem
Today, there is no way how to reliably reset connection. So when connection is returned to the connection pool (or reused from the pool), there can be transaction pending, locks hold, temporary tables being present, session variables set, ...

Describe the solution you'd like
The cheap way how to reset connection is to use COM_RESET_CONNECTION utility command.

Describe alternatives you've considered
Alternatively, connection can be re-opened every time. This is prohibitively expensive. Or ROLLBACK can be issued, but that doesn't solve all the issues (e.g. session variables being set).

Context

ProxySQL support (COM_STMT_CLOSE issue)

  • fluent-mysql-driver - .exact("4.0.0-beta.2.2")),
  • Connecting through ProxySQL 2.0.8 to MariaDB 10.4.10 (Galera cluster).

ProxySQL terminates connections due to "[ERROR] Unexpected packet from client".

Wireshark suggests a COM_STMT_CLOSE is sent before the "Response OK".
image

Debugging suggests an EOF packet is received but this is not shown in the packet capture, commenting out the COM_STMT_CLOSE resolves the issue.

checkouts/mysql-nio/Sources/MySQLNIO/MySQLQueryCommand.swift:141

                return .noResponse
/*                var packet = MySQLPacket()
                MySQLProtocol.COM_STMT_CLOSE(statementID: self.ok!.statementID).encode(into: &packet)
                if let error = self.lastUserError {
                    throw error
                } else {
                    return .init(response: [packet], done: true, resetSequence: true)
                }
*/

SimpleQuery vs Query with Date and UUID columns ?

Just wondering - if there is a difference in the way Date and UUID columns are handled between SimpleQuery and Query ?

If I run exactly the same SQL code in both, the SimpleQuery fails when trying to read a returned Date column; whereas Query reads the column fine.

Likewise, SimpleQuery correctly returns a UUID column, but Query fails to read the UUID and it has to be read as a .string.

Any ideas?

Mariadb 10.1 fatalError

I am using v10.1.44-MariaDB-0ubuntu0.18.04.1 Ubuntu 18.04 on a Raspberry Pi. Simply running the project works and the initial view is displayed. However if I try to do a Migration to create a Model/table, project crashes:

Migrate Command: Prepare
[ INFO ] query read _fluent_migrations
Fatal error: file /Users/npr/rv4/.build/checkouts/mysql-nio/Sources/MySQLNIO/MySQLQueryCommand.swift, line 128

Simply running 'Vapor-beta run migrate' causes the error. Debug shows that the error happens before any attempt execute a migration.

[Not sure if it is relevant but an initial problem was that the default collation of the database was utf8mb4_general_ci. This worked fine with Vapor 3, but caused the attempt to create a unique index on the 'name' field in '_fluent_migrations' to fail because the key length was too great. Changing collation to utf8_general_ci fixed this issue.]

Protocol error with older database

I have an older/stable [1] MySQL database.

let rows : [MySQLRow] = try conn.simpleQuery("SELECT @@version").wait()

...returns empty result.

There's also an output on the screen saying:
Fatal error: unhandled packet: ByteBuffer { readerIndex: 0, writerIndex: 16, readableBytes: 16, capacity: 16, slice: _ByteBufferSlice { 53..<69 }, storage: 0x00007fce8002c260 (512 bytes) } readable bytes (max 1k): [ 0f 35 2e 36 2e 32 35 2d 37 33 2e 31 2d 6c 6f 67 ]

Decoding [ 0f 35 2e 36 2e 32 35 2d 37 33 2e 31 2d 6c 6f 67 ] as ASCII
gives �5.6.25-73.1-log
so apparently database is producing the result, but Vapor is failing to deserialize it.

I assume there's probably a protocol mismatch.

[1] MySQL 5.6 is indeed a bit older, but it it still officially supported. Latest release was about a month ago

Is it Possible to Call Stored Procedures via SimpleQuery ?

I am trying out MySQL-NIO and I want to be able to call a Stored Procedure from my MySQL database and return the result set to my Swift application, however the SimpleQuery is failing with...

MySQL error: Server error: PROCEDURE Accounts.display_Transactions can't return a result set in the given context

The Stored Procedure only has one select, so it is only returning one result set. If I replace the call with the actual SQL from the Stored Procedure it works.

Is it possible to call a Stored Procedure in this way - or is there another method I should be using ?

thanks

Phil

Remove hard crash from `MySQLData` with malformed data

Describe the bug

This section: https://github.com/vapor/mysql-nio/blob/main/Sources/MySQLNIO/MySQLData.swift#L448-L469 causes a hard crash (i.e. bang operator) when there is data that is malformed at the MySQL level. For example, a datetime that is non-nullable defaults to 0000-00-00 00:00:00.000000 which is 1. obviously invalid as a date and 2. causes the description property to crash.

To Reproduce

Do a SELECT from a table with a column with a date value set to 0000-00-00 00:00:00.000000

Expected behavior

It should not crash and ideally log out some kind of an error

Environment

MySQLNIO v 1.3.2

MysqlNIO is missing MySQLData conformance for Data

Describe the issue

MysqlNIO is missing MySQLData conformance for Data

Vapor version

4.92.4

Operating system and version

macOS 14.3.x

Swift version

5.10

Steps to reproduce

Create an Entity with .data type, and try to write Swift Data to it. It'll end up encoded base64.

Adding the missing conformance fixes it:

import MySQLNIO
import NIOFoundationCompat

extension Foundation.Data: MySQLDataConvertible {
    init?(mysqlData: MySQLData) {
        guard [.blob, .mediumBlob, .longBlob, .tinyBlob].contains(mysqlData.type),
              mysqlData.format == .binary,
              let buffer = mysqlData.buffer else {
            return nil
        }
        self = .init(buffer: buffer)
    }
    var mysqlData: MySQLData? {
        .init(type: .blob, buffer: ByteBufferAllocator().buffer(data: self))
    }
}

Outcome

Can't store Data type.

Additional notes

No response

MySQLConnectionHandler.swift Fatal error: unhandled packet: ByteBuffer

Describe the bug

Server periodically crashes with the following output:

MySQLNIO/MySQLConnectionHandler.swift:89: Fatal error: unhandled packet: ByteBuffer { readerIndex: 0, writerIndex: 145, readableBytes: 145, capacity: 145, storageCapacity: 32768, slice: _
ByteBufferSlice { 4..<149 }, storage: 0x00007f78d8037030 (32768 bytes) }

ByteBufferSlice { 4..<149 }, storage: 0x00007f78d8037030 (32768 bytes) }
readable bytes (max 1k): [ ff bf 0f 23 48 59 30 30 30 54 68 65 20 63 6c 69 65 6e 74 20 77 61 73 20 64 69 73 63 6f 6e 6e 65 63 74 65 64 20 62 79 20 74 68 65 20 73 65 72 76 65 72 20 62 65 6
3 61 75 73 65 20 6f 66 20 69 6e 61 63 74 69 76 69 74 79 2e 20 53 65 65 20 77 61 69 74 5f 74 69 6d 65 6f 75 74 20 61 6e 64 20 69 6e 74 65 72 61 63 74 69 76 65 5f 74 69 6d 65 6f 75 74 20 66
6f 72 20 63 6f 6e 66 69 67 75 72 69 6e 67 20 74 68 69 73 20 62 65 68 61 76 69 6f 72 2e ]

Received signal 4. Backtrace:
0x560ba09f1eaf, Backtrace.(printBacktrace in _B82A8C0ED7C904841114FDF244F9E58E)(signal: Swift.Int32) -> () at /home/yong-yan/projects/MerlinDaemonAPIServer/.build/checkouts/swift-
backtrace/Sources/Backtrace/Backtrace.swift:66
0x560ba09f21d9, closure #1 (Swift.Int32) -> () in static Backtrace.Backtrace.install(signals: Swift.Array<Swift.Int32>) -> () at /home/yong-yan/projects/MerlinDaemonAPIServer/.bui
ld/checkouts/swift-backtrace/Sources/Backtrace/Backtrace.swift:80
0x560ba09f21f8, @objc closure #1 (Swift.Int32) -> () in static Backtrace.Backtrace.install(signals: Swift.Array<Swift.Int32>) -> () at /home/yong-yan/projects/MerlinDaemonAPIServe
r/:0
0x7f78fb67851f
0x7f78fbc663cf
0x7f78fbc666b0
0x7f78fbf0f3e5
0x560ba0fc0abc, MySQLNIO.MySQLConnectionHandler.channelRead(context: NIOCore.ChannelHandlerContext, data: NIOCore.NIOAny) -> () at /home/yong-yan/projects/MerlinDaemonAPIServer/.b
uild/checkouts/mysql-nio/Sources/MySQLNIO/MySQLConnectionHandler.swift:89
0x560ba0fce438, protocol witness for NIOCore._ChannelInboundHandler.channelRead(context: NIOCore.ChannelHandlerContext, data: NIOCore.NIOAny) -> () in conformance MySQLNIO.MySQLConnection
Handler : NIOCore._ChannelInboundHandler in MySQLNIO at /home/yong-yan/projects/MerlinDaemonAPIServer/:0
0x560ba1128e38, NIOCore.ChannelHandlerContext.(invokeChannelRead in _F5AC316541457BD146E3694279514AA3)(NIOCore.NIOAny) -> () at /home/yong-yan/projects/MerlinDaemonAPIServer/.buil
d/checkouts/swift-nio/Sources/NIOCore/ChannelPipeline.swift:1702
0x560ba1128ea6, NIOCore.ChannelHandlerContext.(invokeChannelRead in _F5AC316541457BD146E3694279514AA3)(NIOCore.NIOAny) -> () at /home/yong-yan/projects/MerlinDaemonAPIServer/.buil
d/checkouts/swift-nio/Sources/NIOCore/ChannelPipeline.swift:1704
0x560ba112d82b, NIOCore.ChannelHandlerContext.fireChannelRead(NIOCore.NIOAny) -> () at /home/yong-yan/projects/MerlinDaemonAPIServer/.build/checkouts/swift-nio/Sources/NIOCore/Cha
nnelPipeline.swift:1515
0x560ba0ffc927, MySQLNIO.MySQLPacketDecoder.decode(context: NIOCore.ChannelHandlerContext, buffer: inout NIOCore.ByteBuffer) throws -> NIOCore.DecodingState at /home/yong-yan/proj
ects/MerlinDaemonAPIServer/.build/checkouts/mysql-nio/Sources/MySQLNIO/Packet/MySQLPacketDecoder.swift:32
0x560ba0ffcf4c, protocol witness for NIOCore.ByteToMessageDecoder.decode(context: NIOCore.ChannelHandlerContext, buffer: inout NIOCore.ByteBuffer) throws -> NIOCore.DecodingState in confo
rmance MySQLNIO.MySQLPacketDecoder : NIOCore.ByteToMessageDecoder in MySQLNIO at /home/yong-yan/projects/MerlinDaemonAPIServer/:0
0x560ba114833c, closure #1 (inout A, inout NIOCore.ByteBuffer) throws -> NIOCore.DecodingState in NIOCore.ByteToMessageHandler.(decodeLoop in _3B15959193A57E0C1E789632BC533F5E)(context: N
IOCore.ChannelHandlerContext, decodeMode: NIOCore.ByteToMessageHandler.(DecodeMode in _3B15959193A57E0C1E789632BC533F5E)) throws -> NIOCore.(B2MDBuffer in _3B15959193A57E0C1E789632BC53
3F5E).BufferProcessingResult at /home/yong-yan/projects/MerlinDaemonAPIServer/.build/checkouts/swift-nio/Sources/NIOCore/Codec.swift:582
0x560ba114f20c, partial apply forwarder for closure #1 (inout A, inout NIOCore.ByteBuffer) throws -> NIOCore.DecodingState in NIOCore.ByteToMessageHandler.(decodeLoop in _3B15959193A57E0C
1E789632BC533F5E)(context: NIOCore.ChannelHandlerContext, decodeMode: NIOCore.ByteToMessageHandler.(DecodeMode in _3B15959193A57E0C1E789632BC533F5E)) throws -> NIOCore.(B2MDBuffer in _
3B15959193A57E0C1E789632BC533F5E).BufferProcessingResult at /home/yong-yan/projects/MerlinDaemonAPIServer/:0
0x560ba1146fb0, NIOCore.ByteToMessageHandler.(withNextBuffer in _3B15959193A57E0C1E789632BC533F5E)(allowEmptyBuffer: Swift.Bool, _: (inout A, inout NIOCore.ByteBuffer) throws -> NIOCore.D
ecodingState) throws -> NIOCore.(B2MDBuffer in _3B15959193A57E0C1E789632BC533F5E).BufferProcessingResult at /home/yong-yan/projects/MerlinDaemonAPIServer/.build/checkouts/swift-ni
o/Sources/NIOCore/Codec.swift:539
0x560ba1147b8b, NIOCore.ByteToMessageHandler.(decodeLoop in _3B15959193A57E0C1E789632BC533F5E)(context: NIOCore.ChannelHandlerContext, decodeMode: NIOCore.ByteToMessageHandler.(DecodeM
ode in _3B15959193A57E0C1E789632BC533F5E)) throws -> NIOCore.(B2MDBuffer in _3B15959193A57E0C1E789632BC533F5E).BufferProcessingResult at /home/yong-yan/projects/MerlinDaemonAPISer
ver/.build/checkouts/swift-nio/Sources/NIOCore/Codec.swift:578
0x560ba114952f, NIOCore.ByteToMessageHandler.channelRead(context: NIOCore.ChannelHandlerContext, data: NIOCore.NIOAny) -> () at /home/yong-yan/projects/MerlinDaemonAPIServer/.buil
d/checkouts/swift-nio/Sources/NIOCore/Codec.swift:651
0x560ba1149dc8, protocol witness for NIOCore._ChannelInboundHandler.channelRead(context: NIOCore.ChannelHandlerContext, data: NIOCore.NIOAny) -> () in conformance NIOCore.ByteToMessageHandler : NIOCore._ChannelInboundHandler in NIOCore at /home/yong-yan/projects/MerlinDaemonAPIServer/:0
0x560ba1128e38, NIOCore.ChannelHandlerContext.(invokeChannelRead in _F5AC316541457BD146E3694279514AA3)(NIOCore.NIOAny) -> () at /home/yong-yan/projects/MerlinDaemonAPIServer/.build/checkouts/swift-nio/Sources/NIOCore/ChannelPipeline.swift:1702
0x560ba112d82b, NIOCore.ChannelHandlerContext.fireChannelRead(NIOCore.NIOAny) -> () at /home/yong-yan/projects/MerlinDaemonAPIServer/.build/checkouts/swift-nio/Sources/NIOCore/ChannelPipeline.swift:1515
0x560ba14fea27, NIOSSL.NIOSSLHandler.(doFlushReadData in _4C55B9A85907C0CB3F4E7FBD2C1C5493)(context: NIOCore.ChannelHandlerContext, receiveBuffer: NIOCore.ByteBuffer, readOnEmptyBuffer: Swift.Bool) -> () at /home/yong-yan/projects/MerlinDaemonAPIServer/.build/checkouts/swift-nio-ssl/Sources/NIOSSL/NIOSSLHandler.swift:517
0x560ba14f9cfa, NIOSSL.NIOSSLHandler.channelReadComplete(context: NIOCore.ChannelHandlerContext) -> () at /home/yong-yan/projects/MerlinDaemonAPIServer/.build/checkouts/swift-nio-ssl/Sources/NIOSSL/NIOSSLHandler.swift:190
0x560ba1500540, protocol witness for NIOCore._ChannelInboundHandler.channelReadComplete(context: NIOCore.ChannelHandlerContext) -> () in conformance NIOSSL.NIOSSLHandler : NIOCore._ChannelInboundHandler in NIOSSL at /home/yong-yan/projects/MerlinDaemonAPIServer/:0
0x560ba1128f9f, NIOCore.ChannelHandlerContext.(invokeChannelReadComplete in _F5AC316541457BD146E3694279514AA3)() -> () at /home/yong-yan/projects/MerlinDaemonAPIServer/.build/checkouts/swift-nio/Sources/NIOCore/ChannelPipeline.swift:1712
0x560ba11257e9, NIOCore.ChannelPipeline.(fireChannelReadComplete0 in _F5AC316541457BD146E3694279514AA3)() -> () at /home/yong-yan/projects/MerlinDaemonAPIServer/.build/checkouts/swift-nio/Sources/NIOCore/ChannelPipeline.swift:903
0x560ba112b47c, NIOCore.ChannelPipeline.SynchronousOperations.fireChannelReadComplete() -> () at /home/yong-yan/projects/MerlinDaemonAPIServer/.build/checkouts/swift-nio/Sources/NIOCore/ChannelPipeline.swift:1170
0x560ba140cf37, NIOPosix.BaseSocketChannel.(readable0 in _7F4F544BB68CD2CFABA0C7990D6EB2C6)() -> NIOPosix.BaseSocketChannel.(ReadStreamState in 7F4F544BB68CD2CFABA0C7990D6EB2C6) at /home/yong-yan/projects/MerlinDaemonAPIServer/.build/checkouts/swift-nio/Sources/NIOPosix/BaseSocketChannel.swift:1144
0x560ba140df50, NIOPosix.BaseSocketChannel.readable() -> () at /home/yong-yan/projects/MerlinDaemonAPIServer/.build/checkouts/swift-nio/Sources/NIOPosix/BaseSocketChannel.swift:1071
0x560ba140f158, protocol witness for NIOPosix.SelectableChannel.readable() -> () in conformance NIOPosix.BaseSocketChannel
: NIOPosix.SelectableChannel in NIOPosix at /home/yong-yan/projects/MerlinDaemonAPIServer/:0
0x560ba148f37e, NIOPosix.SelectableEventLoop.handleEvent(
: NIOPosix.SelectorEventSet, channel: A) -> () at /home/yong-yan/projects/MerlinDaemonAPIServer/.build/checkouts/swift-nio/Sources/NIOPosix/SelectableEventLoop.swift:394
0x560ba1490bea, closure #2 (NIOPosix.SelectorEvent<NIOPosix.NIORegistration>) -> () in closure #2 () throws -> () in NIOPosix.SelectableEventLoop.run() throws -> () at /home/yong-yan/projects/MerlinDaemonAPIServer/.build/checkouts/swift-nio/Sources/NIOPosix/SelectableEventLoop.swift:469
0x560ba149542e, partial apply forwarder for closure #2 (NIOPosix.SelectorEvent<NIOPosix.NIORegistration>) -> () in closure #2 () throws -> () in NIOPosix.SelectableEventLoop.run() throws -> () at /home/yong-yan/projects/MerlinDaemonAPIServer/:0
0x560ba1498fc9, NIOPosix.Selector.whenReady0(strategy: NIOPosix.SelectorStrategy, onLoopBegin: () -> (), _: (NIOPosix.SelectorEvent) throws -> ()) throws -> () at /home/yong-yan/projects/MerlinDaemonAPIServer/.build/checkouts/swift-nio/Sources/NIOPosix/SelectorEpoll.swift:252
0x560ba149fcf3, NIOPosix.Selector.whenReady(strategy: NIOPosix.SelectorStrategy, onLoopBegin: () -> (), _: (NIOPosix.SelectorEvent) throws -> ()) throws -> () at /home/yong-yan/projects/MerlinDaemonAPIServer/.build/checkouts/swift-nio/Sources/NIOPosix/SelectorGeneric.swift:288
0x560ba149087d, closure #2 () throws -> () in NIOPosix.SelectableEventLoop.run() throws -> () at /home/yong-yan/projects/MerlinDaemonAPIServer/.build/checkouts/swift-nio/Sources/NIOPosix/SelectableEventLoop.swift:461
0x560ba14941f3, partial apply forwarder for closure #2 () throws -> () in NIOPosix.SelectableEventLoop.run() throws -> () at /home/yong-yan/projects/MerlinDaemonAPIServer/:0
0x560ba148a7e1, NIOPosix.withAutoReleasePool(() throws -> A) throws -> A at /home/yong-yan/projects/MerlinDaemonAPIServer/.build/checkouts/swift-nio/Sources/NIOPosix/SelectableEventLoop.swift:29
0x560ba148f78f, NIOPosix.SelectableEventLoop.run() throws -> () at /home/yong-yan/projects/MerlinDaemonAPIServer/.build/checkouts/swift-nio/Sources/NIOPosix/SelectableEventLoop.swift:460
0x560ba1447526, static NIOPosix.MultiThreadedEventLoopGroup.(runTheLoop in _C2B1528F4FBA68A3DBFA89DBAEBE9D4D)(thread: NIOPosix.NIOThread, parentGroup: Swift.Optional<NIOPosix.MultiThreadedEventLoopGroup>, canEventLoopBeShutdownIndividually: Swift.Bool, selectorFactory: () throws -> NIOPosix.Selector<NIOPosix.NIORegistration>, initializer: (NIOPosix.NIOThread) -> (), _: (NIOPosix.SelectableEventLoop) -> ()) -> () at /home/yong-yan/projects/MerlinDaemonAPIServer/.build/checkouts/swift-nio/Sources/NIOPosix/MultiThreadedEventLoopGroup.swift:95
0x560ba1447c0c, closure #1 (NIOPosix.NIOThread) -> () in static NIOPosix.MultiThreadedEventLoopGroup.(setupThreadAndEventLoop in _C2B1528F4FBA68A3DBFA89DBAEBE9D4D)(name: Swift.String, parentGroup: NIOPosix.MultiThreadedEventLoopGroup, selectorFactory: () throws -> NIOPosix.Selector<NIOPosix.NIORegistration>, initializer: (NIOPosix.NIOThread) -> ()) -> NIOPosix.SelectableEventLoop at /home/yong-yan/projects/MerlinDaemonAPIServer/.build/checkouts/swift-nio/Sources/NIOPosix/MultiThreadedEventLoopGroup.swift:116
0x560ba144d3eb, partial apply forwarder for closure #1 (NIOPosix.NIOThread) -> () in static NIOPosix.MultiThreadedEventLoopGroup.(setupThreadAndEventLoop in _C2B1528F4FBA68A3DBFA89DBAEBE9D4D)(name: Swift.String, parentGroup: NIOPosix.MultiThreadedEventLoopGroup, selectorFactory: () throws -> NIOPosix.Selector<NIOPosix.NIORegistration>, initializer: (NIOPosix.NIOThread) -> ()) -> NIOPosix.SelectableEventLoop at /home/yong-yan/projects/MerlinDaemonAPIServer/:0
0x560ba14c8b1e, reabstraction thunk helper from @escaping @callee_guaranteed (@guaranteed NIOPosix.NIOThread) -> () to @escaping @callee_guaranteed (@in_guaranteed NIOPosix.NIOThread) -> (@out ()) at /home/yong-yan/projects/MerlinDaemonAPIServer/:0
0x560ba14cb651, closure #1 (Swift.Optional<Swift.UnsafeMutableRawPointer>) -> Swift.Optional<Swift.UnsafeMutableRawPointer> in static NIOPosix.ThreadOpsPosix.run(handle: inout Swift.Optional<Swift.UInt>, args: NIOPosix.Box<(body: (NIOPosix.NIOThread) -> (), name: Swift.Optional<Swift.String>)>, detachThread: Swift.Bool) -> () at /home/yong-yan/projects/MerlinDaemonAPIServer/.build/checkouts/swift-nio/Sources/NIOPosix/ThreadPosix.swift:105
0x560ba14cb758, @objc closure #1 (Swift.Optional<Swift.UnsafeMutableRawPointer>) -> Swift.Optional<Swift.UnsafeMutableRawPointer> in static NIOPosix.ThreadOpsPosix.run(handle: inout Swift.Optional<Swift.UInt>, args: NIOPosix.Box<(body: (NIOPosix.NIOThread) -> (), name: Swift.Optional<Swift.String>)>, detachThread: Swift.Bool) -> () at /home/yong-yan/projects/MerlinDaemonAPIServer/:0
0x7f78fb6cab42
0x7f78fb75c9ff
0xffffffffffffffff
Server 'myserver' crashed with exit code 132. Respawning..

To Reproduce

Currently uncertain but appears to occur randomly during the execution of a select statement.

Expected behavior

No crash should occur.

Environment

Versions:
dependencies: [
// 💧 A server-side Swift web framework.
.package(url: "https://github.com/vapor/fluent.git", from: "4.7.1"),
.package(url: "https://github.com/vapor/fluent-mysql-driver.git", from: "4.2.0"),
.package(url: "https://github.com/vapor/vapor.git", from: "4.75.0")
],

Swift version 5.7.1 (swift-5.7.1-RELEASE)

Distributor ID: Ubuntu
Description: Ubuntu 22.04.1 LTS
Release: 22.04
Codename: jammy

Additional context

Similar or identical to #8

Exception when reading MySQLTime formatted as text

Describe the bug

The getter for MySQLData.time assumes the data is in .binary format. However, MariaDB 10.5 returns data from timestamp fields in .text format "2008-12-29 12:59:59", which leads to a fatal error unwrapping nil.

To Reproduce

  1. Run a Docker container with mariadb:10.5 and these command-line arguments:
    • --max-allowed-packet=512M
    • --sql-mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION
    • --default-time-zone=America/New_York
  2. Query the MariaDB server with:
let rows = try db.simpleQuery("SELECT CURRENT_TIMESTAMP();").wait()
print(rows)

Expected behavior

The current timestamp should be returned. Instead, there is a fatal error at MySQLData.swift:458.

Environment

framework: 4.55.0
toolbox: 18.3.3
OS version: macOS 12.2.1

Additional context

I think the fix is straightforward, and I hope to have a pull request to fix the problem soon.

Parsing of Decimal numbers failed

When using a model with a Decimal property, queries for those rows fail since the decimal value cannot be parsed.

Steps to reproduce

  • Create a model with a decimal property, e.g.

final class Product: Model {
    static let schema = "Products"

    @ID(custom: .id)
    var id: Int?

    @Field(key: "price")
    var price: Decimal

    init() { } 
 } 
  • Create a MySQL table "Products" with a field "price" as Decimal(12,2)
  • Create a row in Products with a price like 19.99
  • Implement a typical get REST method in Vapor that returns a Product by ID.
  • When testing the GET method, the query will fail

Expected behavior

The GET method should return the Product with its price

Actual behavior

The query fails because the decimal value cannot be parsed from the MySQLData object.

Environment

  • Fluent-Kit version: 1.0.2
  • OS version: macOS 10.15.5
  • MySQL Server: 5.7.29

Proposed solution

The MySQLData returned in our scenario is of type MYSQL_TYPE_NEWDECIMAL. The current implementation for decimal parsing however uses the "string" property of MySQLData, which cannot deal with it.

I implemented a decimal property method for MySQLData and updated the tests for it:
#35

MySql 8 MySQLConnectionHandler Fatal error: unhandled packet:

MySql 8 Error:
MySQLNIO/MySQLConnectionHandler.swift:89: Fatal error: unhandled packet: ByteBuffer ......

Xcode running the Vapor server and after about 12 hours, overnight, without connection activity, crashes with the above error message. See the attached error message snippet.

To Reproduce

The vapor server connected to LAN MySql 8 server on Mac OSX 12.6,
OSX Vapor server:
image
MySql 8 server
image
image

Xcode running the Vapor server and after about 12 hours, overnight, without connection activity, crashes with the above error message. See the attached error message snippet.

see attached snippets

Steps to reproduce the behavior:

  1. Add package with configuration '...'
  2. Send request with options '...'
  3. See error

Expected behavior

A clear and concise description of what you expected to happen.
The MySql 8 server is running continuously. The vapor app connects as necessary to the MySql 8 server, residing in another osx server on the same network (subnet). After maybe 12 hours, overnight, the Vapor server crashes with the above error.

  1. My suspicion is that the vapor connection should be released after a timeout (maybe 30min).
  2. Is the Vapor Server receiving a query from the MySql 8 server?
  3. Is there an issue if MySql 8 server is inactive for 12 hours?

Environment

There is nothing special about the environment, standard, only setting is the working directory.

image

  • Vapor Framework version: 4.69.2
  • Vapor Toolbox version:
  • OS version: Ventura 13.3
    Apple MacStudio
    M1 Max

Additional context

Add any other context about the problem here.
MySql 8 ConnectionHandler Error

Server-side Handler

We have an outstanding client here.
Encoding, decoding is here.

Applications:

  • Mysql to other db proxy
  • Fake/mock database. prevent alterations.
  • Personal data filtering proxy, etc
  • Access other resources using Mysql protocol, which is very well adopted.

Solution
Provide server-side handler

Describe alternatives you've considered
Implementations from other languages
https://github.com/jonhoo/msql-srv Rust
https://github.com/kelsin/mysql-mimic Python
https://github.com/ClickHouse/ClickHouse/blob/fbff52847cf591ff0617721cd3483ccadc313634/src/Server/MySQLHandler.h c++

Similar implementations
https://github.com/jonhoo/msql-srv/blob/master/examples/psql_as_mysql.rs

`db.raw(_:).run()` times out on valid queries

It looks like the db.raw(_:).run() method sometimes times out on valid queries. Sometimes, in this context, would be “always on certain queries, while other queries are always fine”.

For example, I wrote this raw query a few days ago, and it works without a problem:

db.raw("UPDATE `YouTubeVideo` SET `valid` = false").run().flatMap { _ in
    
}

However, when attempting to do a different UPDATE query today, I’m met with nothing but Thread 1: Fatal error: Error raised at top level: ⚠️ MySQL Error: Lock wait timeout exceeded; try restarting transaction - id: MySQLError.server (1205).

Example of queries I was never able to execute from Vapor (but run fine from any MySQL client):

  • UPDATE `Phrase` SET `views` = CASE WHEN `id` = 1 THEN `views` + 6 WHEN `id` = 2 THEN `views` + 2 END WHERE `id` IN (1,2);
  • UPDATE `Phrase` SET `views`=`views` + IF(`id`=1,6,2) WHERE `id` IN (1,2);
  • UPDATE Phrase p SET p.views=p.views + (SELECT COUNT(*) FROM PhraseBrowsingEvent ev WHERE ev.phraseId=p.id AND ev.when BETWEEN DATE_SUB(CURRENT_DATE(), INTERVAL 1 DAY) AND CURRENT_DATE())
  • UPDATE `Phrase` `p` INNER JOIN (SELECT `phraseId`, COUNT(`phraseId`) AS `count` FROM `PhraseBrowsingEvent` WHERE `createdAt`>=? GROUP BY `phraseId`) `ec` ON `ec`.`phraseId`=`p`.`id` SET `p`.`views`=`p`.`views` + `ec`.`count`;

All of these queries were attempted to execute from within a Command, like so:

func run(using context: CommandContext) throws -> Future<Void> {
    return context.container.withPooledConnection(to: .mysql) { db in
        return db.raw("query here").run()
    }
}

When inspecting MySQL’s process list with SHOW PROCESSLIST after query is sent, but before the time out error happens, the state of the query is stuck in the “Sending data” state.

MySQL server version 5.7.22, vapor/fluent-mysql 3.0.1, vapor/mysql 3.0.2. Relevant Discord discussion starts here.

MySQLConnection closes unexpectedly in Swift Command Line Tool

Describe the bug

MySQLConnection closes unexpectedly in a Swift Command Line Tool that I am writing.

To Reproduce

import Foundation
import MySQLNIO

@main
struct InsertValuesIntoMySQLDB {
    
    static func main() {
        do {
            try insertRecords()
        } catch {
            print("There was an error!")
            print(error.localizedDescription)
        }
    }
    
    static func insertRecords() throws {
        let addr = try SocketAddress.makeAddressResolvingHost(
            "database.domain.com",
            port: 3306
        )
        let eventGroup = MultiThreadedEventLoopGroup(numberOfThreads: 4)
        let eventLoop = eventGroup.next()
        let conn = try MySQLConnection.connect(to: addr, username: "admin", database: "quora", password: "xyzzy", tlsConfiguration: nil, on: eventLoop).wait()
        defer {
            do {
                try conn.close().wait()
            } catch {
                print("Error: \(error)")
            }
        }
        let rows = try conn.simpleQuery("SELECT COUNT(*) from questions").wait()
        print("rows: \(rows.count)")
//      try eventGroup.syncShutdownGracefully()
    }
}

Steps to reproduce the behavior:
When I run the code I get:
rows: 0 (should be > 0)
Error: alreadyClosed

The query clearly reaches the database server since a misspelled table name generates an error (MySQL error: Server error: Table 'quora.question' doesn't exist), but when the SQL is valid, I get no response.

If I uncomment the last line (try eventGroup.syncShutdownGracefully), then I get the following error:

ERROR: Cannot schedule tasks on an EventLoop that has already shut down. This will be upgraded to a forced crash in future SwiftNIO versions.

Environment

  • OS version: macOS Sonoma 14.3

This is my first time trying to use mysql-nio as well as swift-no, therefore I could be totally wrong on how to use these packages, but I have run out of ideas. Thanks in advance for any kind of help you could provide.

Error Selecting From MySQL database on Linux

Hello,
I am new to Vapor and running into some odd issues when I try to select from a MySQL database. It works locally (on OSX), but it does not work when I deploy to a prod environment.

In production, my docker container is running on a host in AWS EC2, connecting to an RDS instance. My other containers (which are not vapor 4.0) on the host can connect. Also, this is not a connection issue, I have command ("moments") which can successfully insert into the database. However, it seems as soon as I select there is a problem reading bytes.

It works locally, and so I cannot replicate locally, but fails every time I try to select using code like this:

LogMoment.query(on: req.db).limit(1).all()

MySQL Version: 5.6.37

The app was created using vapor-beta, because I wanted the app dockerized.

Here is the error:

Fatal error: file /build/.build/checkouts/mysql-nio/Sources/MySQLNIO/MySQLQueryCommand.swift, line 134
0x7fadc3c6088f
0x7fadc3fbd4e5
0x562aecb567d5, MySQLNIO.(MySQLQueryCommand in _2ACF6639C25D14CC4B926B0BFBB183FB).handle(packet: inout MySQLNIO.MySQLPacket, capabilities: MySQLNIO.MySQLProtocol.CapabilityFlags) throws -> MySQLNIO.MySQLCommandState at /build/.build/checkouts/mysql-nio/Sources/MySQLNIO/MySQLQueryCommand.swift:0
0x562aecb56b8f, protocol witness for MySQLNIO.MySQLCommand.handle(packet: inout MySQLNIO.MySQLPacket, capabilities: MySQLNIO.MySQLProtocol.CapabilityFlags) throws -> MySQLNIO.MySQLCommandState in conformance MySQLNIO.(MySQLQueryCommand in _2ACF6639C25D14CC4B926B0BFBB183FB) : MySQLNIO.MySQLCommand in MySQLNIO at /build/<compiler-generated>:0
0x562aecb417fe, MySQLNIO.MySQLConnectionHandler.channelRead(context: NIO.ChannelHandlerContext, data: NIO.NIOAny) -> () at /build/.build/checkouts/mysql-nio/Sources/MySQLNIO/MySQLConnectionHandler.swift:72
0x562aecbb1ee2, NIO.ChannelHandlerContext.(invokeChannelRead in _EEC863903996E9F191EBAFEB0FB0DFDD)(NIO.NIOAny) -> () at /build/.build/checkouts/swift-nio/Sources/NIO/ChannelPipeline.swift:1339
0x562aecbb1f14, NIO.ChannelHandlerContext.(invokeChannelRead in _EEC863903996E9F191EBAFEB0FB0DFDD)(NIO.NIOAny) -> () at /build/.build/checkouts/swift-nio/Sources/NIO/ChannelPipeline.swift:1341
0x562aecbae102, NIO.ChannelHandlerContext.fireChannelRead(NIO.NIOAny) -> () at /build/.build/checkouts/swift-nio/Sources/NIO/ChannelPipeline.swift:1152
0x562aecb5db00, MySQLNIO.MySQLPacketDecoder.decode(context: NIO.ChannelHandlerContext, buffer: inout NIO.ByteBuffer) throws -> NIO.DecodingState at /build/.build/checkouts/mysql-nio/Sources/MySQLNIO/Packet/MySQLPacketDecoder.swift:32
0x562aecb5db78, protocol witness for NIO.ByteToMessageDecoder.decode(context: NIO.ChannelHandlerContext, buffer: inout NIO.ByteBuffer) throws -> NIO.DecodingState in conformance MySQLNIO.MySQLPacketDecoder : NIO.ByteToMessageDecoder in MySQLNIO at /build/<compiler-generated>:0
0x562aecbc96f6, closure vapor/fluent-mysql-driver#1 (inout A, inout NIO.ByteBuffer) throws -> NIO.DecodingState in NIO.ByteToMessageHandler.(decodeLoop in _F2A740607FEBA425AF6F8C49A24C0AD7)(context: NIO.ChannelHandlerContext, decodeMode: NIO.ByteToMessageHandler<A>.(DecodeMode in _F2A740607FEBA425AF6F8C49A24C0AD7)) throws -> NIO.(B2MDBuffer in _F2A740607FEBA425AF6F8C49A24C0AD7).BufferProcessingResult at /build/.build/checkouts/swift-nio/Sources/NIO/Codec.swift:567
0x562aecbc947e, NIO.ByteToMessageHandler.(withNextBuffer in _F2A740607FEBA425AF6F8C49A24C0AD7)(allowEmptyBuffer: Swift.Bool, _: (inout A, inout NIO.ByteBuffer) throws -> NIO.DecodingState) throws -> NIO.(B2MDBuffer in _F2A740607FEBA425AF6F8C49A24C0AD7).BufferProcessingResult at /build/.build/checkouts/swift-nio/Sources/NIO/Codec.swift:529
0x562aecbc947e, NIO.ByteToMessageHandler.(decodeLoop in _F2A740607FEBA425AF6F8C49A24C0AD7)(context: NIO.ChannelHandlerContext, decodeMode: NIO.ByteToMessageHandler<A>.(DecodeMode in _F2A740607FEBA425AF6F8C49A24C0AD7)) throws -> NIO.(B2MDBuffer in _F2A740607FEBA425AF6F8C49A24C0AD7).BufferProcessingResult at /build/.build/checkouts/swift-nio/Sources/NIO/Codec.swift:563
0x562aecbc9cf6, NIO.ByteToMessageHandler.channelRead(context: NIO.ChannelHandlerContext, data: NIO.NIOAny) -> () at /build/.build/checkouts/swift-nio/Sources/NIO/Codec.swift:636
0x562aecbb1ee2, NIO.ChannelHandlerContext.(invokeChannelRead in _EEC863903996E9F191EBAFEB0FB0DFDD)(NIO.NIOAny) -> () at /build/.build/checkouts/swift-nio/Sources/NIO/ChannelPipeline.swift:1339
0x562aecb914b6, NIO.ChannelPipeline.fireChannelRead0(NIO.NIOAny) -> () at .build/checkouts/swift-nio/Sources/NIO/ChannelPipeline.swift:829
0x562aecb914b6, NIO.BaseStreamSocketChannel.readFromSocket() throws -> NIO.BaseSocketChannel<A>.ReadResult at /build/.build/checkouts/swift-nio/Sources/NIO/BaseStreamSocketChannel.swift:107
0x562aecb8bf13
0x562aecc2658a, generic specialization <NIO.Socket> of NIO.BaseSocketChannel.readable() -> () at .build/checkouts/swift-nio/Sources/NIO/BaseSocketChannel.swift:1048
0x562aecc2658a, inlined generic function <NIO.Socket> of protocol witness for NIO.SelectableChannel.readable() -> () in conformance NIO.BaseSocketChannel<A> : NIO.SelectableChannel in NIO at /build/<compiler-generated>:1045
0x562aecc2658a, function signature specialization <Arg[2] = Dead> of generic specialization <NIO.SocketChannel> of NIO.SelectableEventLoop.handleEvent<A where A: NIO.SelectableChannel>(_: NIO.SelectorEventSet, channel: A) -> () at /build/.build/checkouts/swift-nio/Sources/NIO/SelectableEventLoop.swift:323
0x562aecc23f4f, generic specialization <NIO.SocketChannel> of NIO.SelectableEventLoop.handleEvent<A where A: NIO.SelectableChannel>(_: NIO.SelectorEventSet, channel: A) -> () at /build/<compiler-generated>:0
0x562aecc23f4f, closure vapor/fluent-mysql-driver#1 (NIO.SelectorEvent<NIO.NIORegistration>) -> () in closure vapor/fluent-mysql-driver#2 () throws -> () in NIO.SelectableEventLoop.run() throws -> () at /build/.build/checkouts/swift-nio/Sources/NIO/SelectableEventLoop.swift:393
0x562aecc256c3, reabstraction thunk helper from @callee_guaranteed (@guaranteed NIO.SelectorEvent<NIO.NIORegistration>) -> (@error @owned Swift.Error) to @escaping @callee_guaranteed (@in_guaranteed NIO.SelectorEvent<NIO.NIORegistration>) -> (@error @owned Swift.Error) at /build/<compiler-generated>:0
0x562aecc256c3, partial apply forwarder for reabstraction thunk helper from @callee_guaranteed (@guaranteed NIO.SelectorEvent<NIO.NIORegistration>) -> (@error @owned Swift.Error) to @escaping @callee_guaranteed (@in_guaranteed NIO.SelectorEvent<NIO.NIORegistration>) -> (@error @owned Swift.Error) at /build/<compiler-generated>:0
0x562aecc2912f, NIO.Selector.whenReady(strategy: NIO.SelectorStrategy, _: (NIO.SelectorEvent<A>) throws -> ()) throws -> () at /build/.build/checkouts/swift-nio/Sources/NIO/Selector.swift:620
0x562aecc228b7, closure vapor/fluent-mysql-driver#2 () throws -> () in NIO.SelectableEventLoop.run() throws -> () at /build/.build/checkouts/swift-nio/Sources/NIO/SelectableEventLoop.swift:388
0x562aecc228b7, reabstraction thunk helper from @callee_guaranteed () -> (@error @owned Swift.Error) to @escaping @callee_guaranteed () -> (@out (), @error @owned Swift.Error) at /build/<compiler-generated>:0
0x562aecc228b7, generic specialization <()> of NIO.withAutoReleasePool<A>(() throws -> A) throws -> A at /build/.build/checkouts/swift-nio/Sources/NIO/SelectableEventLoop.swift:26
0x562aecc228b7, NIO.SelectableEventLoop.run() throws -> () at /build/.build/checkouts/swift-nio/Sources/NIO/SelectableEventLoop.swift:387
0x562aecbd9865, static NIO.MultiThreadedEventLoopGroup.(runTheLoop in _D5D78C61B22284700B9BD1ACFBC25157)(thread: NIO.NIOThread, canEventLoopBeShutdownIndividually: Swift.Bool, selectorFactory: () throws -> NIO.Selector<NIO.NIORegistration>, initializer: (NIO.NIOThread) -> (), _: (NIO.SelectableEventLoop) -> ()) -> () at /build/.build/checkouts/swift-nio/Sources/NIO/EventLoop.swift:822
0x562aecbd9865, closure vapor/fluent-mysql-driver#1 (NIO.NIOThread) -> () in static NIO.MultiThreadedEventLoopGroup.(setupThreadAndEventLoop in _D5D78C61B22284700B9BD1ACFBC25157)(name: Swift.String, selectorFactory: () throws -> NIO.Selector<NIO.NIORegistration>, initializer: (NIO.NIOThread) -> ()) -> NIO.SelectableEventLoop at /build/.build/checkouts/swift-nio/Sources/NIO/EventLoop.swift:842
0x562aecbde919, partial apply forwarder for closure vapor/fluent-mysql-driver#1 (NIO.NIOThread) -> () in static NIO.MultiThreadedEventLoopGroup.(setupThreadAndEventLoop in _D5D78C61B22284700B9BD1ACFBC25157)(name: Swift.String, selectorFactory: () throws -> NIO.Selector<NIO.NIORegistration>, initializer: (NIO.NIOThread) -> ()) -> NIO.SelectableEventLoop at /build/<compiler-generated>:0
0x562aecc3b9de, reabstraction thunk helper from @escaping @callee_guaranteed (@guaranteed NIO.NIOThread) -> () to @escaping @callee_guaranteed (@in_guaranteed NIO.NIOThread) -> (@out ()) at /build/<compiler-generated>:0
0x562aecbde930, partial apply forwarder for reabstraction thunk helper from @escaping @callee_guaranteed (@guaranteed NIO.NIOThread) -> () to @escaping @callee_guaranteed (@in_guaranteed NIO.NIOThread) -> (@out ()) at /build/<compiler-generated>:0
0x562aecc3cbdd, closure vapor/fluent-mysql-driver#1 (Swift.Optional<Swift.UnsafeMutableRawPointer>) -> Swift.Optional<Swift.UnsafeMutableRawPointer> in static NIO.ThreadOpsPosix.run(handle: inout Swift.Optional<Swift.UInt>, args: NIO.Box<(body: (NIO.NIOThread) -> (), name: Swift.Optional<Swift.String>)>, detachThread: Swift.Bool) -> () at /build/.build/checkouts/swift-nio/Sources/NIO/ThreadPosix.swift:105
0x7fadc3c556da
0x7fadc1d9088e
0xffffffffffffffff

Package.swift:

import PackageDescription

let package = Package(
    name: "tickertracker",
    platforms: [
       .macOS(.v10_15)
    ],
    dependencies: [
        // 💧 A server-side Swift web framework.
        .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0-rc"),
        .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0-rc"),
        .package(url: "https://github.com/vapor/fluent-mysql-driver.git", from: "4.0.0-rc")
    ],
    targets: [
        .target(
            name: "App",
            dependencies: [
                .product(name: "Fluent", package: "fluent"),
                .product(name: "FluentMySQLDriver", package: "fluent-mysql-driver"),
                .product(name: "Vapor", package: "vapor")
            ],
            swiftSettings: [
                .unsafeFlags(["-cross-module-optimization"], .when(configuration: .release))
            ]
        ),
        .target(name: "Run", dependencies: [.target(name: "App")]),
        .testTarget(name: "AppTests", dependencies: [
            .target(name: "App"),
            .product(name: "XCTVapor", package: "vapor"),
        ])
    ]
)

Package.resolved:

  "object": {
    "pins": [
      {
        "package": "async-http-client",
        "repositoryURL": "https://github.com/swift-server/async-http-client.git",
        "state": {
          "branch": null,
          "revision": "037b70291941fe43de668066eb6fb802c5e181d2",
          "version": "1.1.1"
        }
      },
      {
        "package": "async-kit",
        "repositoryURL": "https://github.com/vapor/async-kit.git",
        "state": {
          "branch": null,
          "revision": "48a719dec79ea3cbbc71ca9b29f69df7cd5794cd",
          "version": "1.0.1"
        }
      },
      {
        "package": "console-kit",
        "repositoryURL": "https://github.com/vapor/console-kit.git",
        "state": {
          "branch": null,
          "revision": "7a97a5ea7fefe61cf2c943242113125b0f396a98",
          "version": "4.1.0"
        }
      },
      {
        "package": "fluent",
        "repositoryURL": "https://github.com/vapor/fluent.git",
        "state": {
          "branch": null,
          "revision": "dba22d5a6115092482669efe5492d800ace4da38",
          "version": "4.0.0-rc.2.2"
        }
      },
      {
        "package": "fluent-kit",
        "repositoryURL": "https://github.com/vapor/fluent-kit.git",
        "state": {
          "branch": null,
          "revision": "c73570c85d6661c04b8ea29a9f8eede67f6d4aae",
          "version": "1.0.0-rc.1.26"
        }
      },
      {
        "package": "fluent-mysql-driver",
        "repositoryURL": "https://github.com/vapor/fluent-mysql-driver.git",
        "state": {
          "branch": null,
          "revision": "74d95e80b998e3d143a0361cd0ecd8b43f2d9fd1",
          "version": "4.0.0-rc.1.3"
        }
      },
      {
        "package": "mysql-kit",
        "repositoryURL": "https://github.com/vapor/mysql-kit.git",
        "state": {
          "branch": null,
          "revision": "b59a08d77c3830b21f35fe744813640ef56a2ebb",
          "version": "4.0.0-rc.1.6"
        }
      },
      {
        "package": "mysql-nio",
        "repositoryURL": "https://github.com/vapor/mysql-nio.git",
        "state": {
          "branch": null,
          "revision": "6a9235582076122badc5428c8d5b6c41f9e40e1a",
          "version": "1.0.0-rc.2.2"
        }
      },
      {
        "package": "routing-kit",
        "repositoryURL": "https://github.com/vapor/routing-kit.git",
        "state": {
          "branch": null,
          "revision": "e7f2d5bd36dc65a9edb303541cb678515a7fece3",
          "version": "4.1.0"
        }
      },
      {
        "package": "sql-kit",
        "repositoryURL": "https://github.com/vapor/sql-kit.git",
        "state": {
          "branch": null,
          "revision": "1e0239616134dd45fa4894bfe83750d196e3a83d",
          "version": "3.0.0"
        }
      },
      {
        "package": "swift-backtrace",
        "repositoryURL": "https://github.com/swift-server/swift-backtrace.git",
        "state": {
          "branch": null,
          "revision": "f2fd8c4845a123419c348e0bc4b3839c414077d5",
          "version": "1.2.0"
        }
      },
      {
        "package": "swift-crypto",
        "repositoryURL": "https://github.com/apple/swift-crypto.git",
        "state": {
          "branch": null,
          "revision": "d67ac68d09a95443303e9d6e37b34e7ba101d5f1",
          "version": "1.0.1"
        }
      },
      {
        "package": "swift-log",
        "repositoryURL": "https://github.com/apple/swift-log.git",
        "state": {
          "branch": null,
          "revision": "74d7b91ceebc85daf387ebb206003f78813f71aa",
          "version": "1.2.0"
        }
      },
      {
        "package": "swift-metrics",
        "repositoryURL": "https://github.com/apple/swift-metrics.git",
        "state": {
          "branch": null,
          "revision": "708b960b4605abb20bc55d65abf6bad607252200",
          "version": "2.0.0"
        }
      },
      {
        "package": "swift-nio",
        "repositoryURL": "https://github.com/apple/swift-nio.git",
        "state": {
          "branch": null,
          "revision": "c5fa0b456524cd73dc3ddbb263d4f46c20b86ca3",
          "version": "2.17.0"
        }
      },
      {
        "package": "swift-nio-extras",
        "repositoryURL": "https://github.com/apple/swift-nio-extras.git",
        "state": {
          "branch": null,
          "revision": "f21a87da1353b97ab914d4e36599a09bd06e936b",
          "version": "1.5.0"
        }
      },
      {
        "package": "swift-nio-http2",
        "repositoryURL": "https://github.com/apple/swift-nio-http2.git",
        "state": {
          "branch": null,
          "revision": "b66a08e4bc53ab7c39fb03ab3678132e6ba5d12d",
          "version": "1.12.0"
        }
      },
      {
        "package": "swift-nio-ssl",
        "repositoryURL": "https://github.com/apple/swift-nio-ssl.git",
        "state": {
          "branch": null,
          "revision": "10e0e17dd47b594c3d864a063f343d716e33e5c1",
          "version": "2.7.3"
        }
      },
      {
        "package": "vapor",
        "repositoryURL": "https://github.com/vapor/vapor.git",
        "state": {
          "branch": null,
          "revision": "74bbf36fe0378fafe5413762affe8db6b871a380",
          "version": "4.5.1"
        }
      },
      {
        "package": "websocket-kit",
        "repositoryURL": "https://github.com/vapor/websocket-kit.git",
        "state": {
          "branch": null,
          "revision": "021edd1ca55451ad15b3e84da6b4064e4b877b34",
          "version": "2.1.0"
        }
      }
    ]
  },
  "version": 1
}

LogMoment (the model I'm just testing a select from):

import Vapor

final class LogMoment: Model, Content {
    init() {}

    static let schema = "logMoments"

    @ID(custom: .id) var id: Int?
    @Field(key: "startedAt") var startedAt: Date?    
    @Field(key: "completedAt") var completedAt: Date?
    @Field(key: "allTickersCount") var allTickersCount: Int?
    @Field(key: "tickersProcessed") var tickersProcessed: Int?
    @Field(key: "tickersNotProcessed") var tickersNotProcessed: Int?
    @Field(key: "durationSeconds") var durationSeconds: Int?


    init(id: Int? = nil,
        startedAt: Date? = nil,
        completedAt: Date? = nil,
        allTickersCount: Int? = nil,
        tickersProcessed: Int? = nil,
        tickersNotProcessed: Int? = nil,
        durationSeconds: Int? = nil
) {
        self.id = id
        self.startedAt = startedAt
        self.completedAt = completedAt
        self.allTickersCount = allTickersCount
        self.tickersProcessed = tickersProcessed
        self.tickersNotProcessed = tickersNotProcessed
        self.durationSeconds = durationSeconds
    }
}

Controller I'm using (I recognize the class is TickerController, not Moment controller. I had the same issue with the TickerModel, so I wanted to test a different model):

struct TickerController {
    func index(req: Request) throws -> EventLoopFuture<[LogMoment]> {
        return LogMoment.query(on: req.db).limit(1).all()
    }
}

configure.swift:

import FluentMySQLDriver
import Vapor

// configures your application
public func configure(_ app: Application) throws {
    // uncomment to serve files from /Public folder
    // app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory))

    app.databases.use(.mysql(
        hostname: Environment.get("DATABASE_HOST") ?? "****",
        username: Environment.get("DATABASE_USERNAME") ?? "****",
        password: Environment.get("DATABASE_PASSWORD") ?? "****",
        database: Environment.get("DATABASE_NAME") ?? "****",
        tlsConfiguration: .none
    ), as: .mysql)
    

//    app.migrations.add(CreateTodo())

    // register routes
    try routes(app)
    app.commands.use(MomentsCommand(), as: "moments")
}

I have tlsConfiguration: .none in there, because my of a SSL handshake error, when using .forClient()

Dockerfile:

# Build image
# ================================
FROM swift:5.2-bionic as build
WORKDIR /build

# First just resolve dependencies.
# This creates a cached layer that can be reused
# as long as your Package.swift/Package.resolved
# files do not change.
COPY ./Package.* ./
RUN swift package resolve

# Copy entire repo into container
COPY . .

# Compile with optimizations
RUN swift build --enable-test-discovery -c release

# ================================
# Run image
# ================================
FROM swift:5.2-bionic-slim

# Create a vapor user and group with /app as its home directory
RUN useradd --user-group --create-home --home-dir /app vapor

WORKDIR /app

# Copy build artifacts
COPY --from=build --chown=vapor:vapor /build/.build/release /app
# Uncomment the next line if you need to load resources from the `Public` directory
#COPY --from=build --chown=vapor:vapor /build/Public /app/Public

# Ensure all further commands run as the vapor user
USER vapor

# Start the Vapor service when the image is run, default to listening on 8080 in production environment 
ENTRYPOINT ["./Run"]
CMD ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"]

docker-compose.yml:


services:
   tickertracker:
     image: ****
     expose:
       - 8001
     ports:
       - "8001:8001"
     restart: always
     command: ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8001"]
     environment:
       VIRTUAL_HOST: ****
       DATABASE_HOST: ****
       DATABASE_USERNAME: ****
       DATABASE_PASSWORD: ****
       DATABASE_NAME: ****
     container_name: tickertracker_prod
networks:
  default:
    external:
      name: nginx-proxy

Note, I have the port set to 8001, because I have multiple others containers on the host, so I am using docker-compose.yml to configure the port manually (which is also the reason for the nginx-proxy

I have tried to dig into why the fatalError() is being called, but I realized quickly this is way over my head.

Nothing seems to be out of date (in terms of my Package.resolved).

Any ideas?

Any other information that I can provide?

Extended Integer Support

Is your feature request related to a problem? Please describe.
I've noticed that inserting an UInt64 with a value bigger than Int.max into a field using FluentMySQL does not work.
This is because FluentMySQL only makes use of the MySQLData init(int:) initializer.
(FluentMySQL also translates UInt64 to INT in sql but this is another issue).

Describe the solution you'd like
As PostgresNIO also includes separate initializers and conversion methods for the different integer types i would suggest to also implement these in mysql-nio.

Custom initializers for MySQLData:

struct MySQLData {
//...

    //Private generic initializers
    private init<T>(type: MySQLProtocol.DataType, value: T, isUnsigned: Bool) where T: FixedWidthInteger {
        self.format = .binary
        self.type = type
        self.buffer = .init(integer: value, endianness: .little)
        self.isUnsigned = isUnsigned
    }
    private init<T>(type: MySQLProtocol.DataType, value: T) where T: FixedWidthInteger, T: SignedInteger {
        self.init(type: type, value: value, isUnsigned: false)
    }
    private init<T>(type: MySQLProtocol.DataType, value: T) where T: FixedWidthInteger, T: UnsignedInteger {
        self.init(type: type, value: value, isUnsigned: true)
    }

    //public initializers with specified MySQL DataType
    public init(int8 int: Int8) {
        self.init(type: .tiny, value: int)
    }

    public init(uint64 int: UInt64) {
        self.init(type: .longlong, value: int)
    }
    //...
}

Separate MySQLDataConvertible-Conformance
-> Instead of one extension for FixedWidthInteger. NumericCast is no longer necessary.

extension UInt64: MySQLDataConvertible {
    public init?(mysqlData: MySQLData) {
        guard let int = mysqlData.uint64 else {
            return nil
        }
        self = int
    }

    public var mysqlData: MySQLData? {
        return .init(uint64: self)
    }
}

Could not importet using iPadOS 17.4 and Swift Playgrounds 4.4.1

Describe the issue

Importing this package causes the error `target type, "ClangTarget", is not supported´

Vapor version

don‘t know

Operating system and version

iPadOS 17.4

Swift version

Swift Playgrounds 4.4.1

Steps to reproduce

Importing package using Swift Playgrounds using the menu.

Outcome

error message >target type, "ClangModule", is not supported<

Additional notes

No response

SERVER_MORE_RESULTS_EXISTS support

Recently I made PR to make MySQLCapabilities available (#170).

Now we can use CLIENT_MULTI_STATEMENTS. However I found MySQLPacketDecoder doesn't support results of such queries.

The result packet of multi statements query contains the status flag SERVER_MORE_RESULTS_EXISTS, but the decoder doesn't read more.
So if we execute next query, it'll recieves invalid bytes previous query left.

Reproduction code.

let config = MySQLDatabaseConfig(hostname: "localhost",
                                 port: 3306,
                                 username: "root",
                                 password: "PASSWORD",
                                 database: "DATABASE",
                                 capabilities: MySQLCapabilities.default.union([.CLIENT_MULTI_STATEMENTS]))
let mysql = MySQLDatabase(config: config)
let eventLoop = MultiThreadedEventLoopGroup(numberOfThreads: 1)
let conn = try! mysql.newConnection(on: eventLoop).wait()

let query = """
set time_zone = "+00:00";
select CURRENT_TIMESTAMP;
"""
let res1 = try! conn.simpleQuery(query).wait()
print("res1: ", res1)

// Second query doesn't work well.
let res2 = try! conn.simpleQuery("select * from fluent where id = 0").wait()
print("res2: ", res2)

simpleQuery returns only one result, so we need multiQuery if the decoder supports SERVER_MORE_RESULTS_EXISTS.

I don't know if there's someone who really needs multiple statements (We can split query with ; and execute one by one). So simply forbid using CLIENT_MULTI_STATEMENTS is an option.

Connect through an SSH tunnel

Thank you to the developer. This is indeed an excellent library for MySQL development. However, I noticed that there's no information about connecting through an SSH tunnel. Does it support SSH tunneling, or am I overlooking something?

WX20240510-110615@2x

Could not convert MySQL data to String: <MYSQL_TYPE_JSON>

Hi,

I have a table with a column in json format:

CREATE TABLE "document" (
  ...
  "payload_json" json DEFAULT NULL,
  ...
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_sv_0900_ai_ci;

My model is defined as

public struct Document: Codable, Hashable {
    //...
    public let payloadJson: String?
    //...
    public init() {
        //...
        payloadJson = ""
        //...
    }
}

when I try to decode the row with

try row.sql().decode(model: Document.self, keyDecodingStrategy: .convertFromSnakeCase)

I get the following error:

typeMismatch(Swift.String, Swift.DecodingError.Context(codingPath: [], debugDescription: "Could not convert MySQL data to String: <MYSQL_TYPE_JSON>", underlyingError: nil))

Using MySQLWorkbench, I looked up the contents of the row and its value viewer shows me:

{"id": "952DA533-E953-4A3D-B84C-D375616E4107", "dela": {}, "target": "9777288B-CF99-441D-9341-A276CB3E17A0", "operation": {"delete": {}}}

Stepping through the MySQLNIO code I find it strange that this part of the code does not make use of this case.

Am I missing something here? Every other aspect of the library works really well for me.

Cheers

Unhandled packet assertion. Can this throw instead?

Describe the bug

I've been working on a Vapor REST API project. I'm running locally on my Mac at home, and hitting a MySQL DB hosted at DigitalOcean. I came back to work on it after leaving it the night before to find the Vapor app stopped in Xcode at this assertion:

MySQLNIO/MySQLConnectionHandler.swift:89: Fatal error: unhandled packet: ByteBuffer { readerIndex: 0, writerIndex: 145, readableBytes: 145, capacity: 145, storageCapacity: 32768, slice: _ByteBufferSlice { 4..<149 }, storage: 0x000000010683f000 (32768 bytes) }
readable bytes (max 1k): [ ff bf 0f 23 48 59 30 30 30 54 68 65 20 63 6c 69 65 6e 74 20 77 61 73 20 64 69 73 63 6f 6e 6e 65 63 74 65 64 20 62 79 20 74 68 65 20 73 65 72 76 65 72 20 62 65 63 61 75 73 65 20 6f 66 20 69 6e 61 63 74 69 76 69 74 79 2e 20 53 65 65 20 77 61 69 74 5f 74 69 6d 65 6f 75 74 20 61 6e 64 20 69 6e 74 65 72 61 63 74 69 76 65 5f 74 69 6d 65 6f 75 74 20 66 6f 72 20 63 6f 6e 66 69 67 75 72 69 6e 67 20 74 68 69 73 20 62 65 68 61 76 69 6f 72 2e ]
2023-01-05 11:00:42.500355-0800 Run[10403:20023895] MySQLNIO/MySQLConnectionHandler.swift:89: Fatal error: unhandled packet: ByteBuffer { readerIndex: 0, writerIndex: 145, readableBytes: 145, capacity: 145, storageCapacity: 32768, slice: _ByteBufferSlice { 4..<149 }, storage: 0x000000010683f000 (32768 bytes) }
readable bytes (max 1k): [ ff bf 0f 23 48 59 30 30 30 54 68 65 20 63 6c 69 65 6e 74 20 77 61 73 20 64 69 73 63 6f 6e 6e 65 63 74 65 64 20 62 79 20 74 68 65 20 73 65 72 76 65 72 20 62 65 63 61 75 73 65 20 6f 66 20 69 6e 61 63 74 69 76 69 74 79 2e 20 53 65 65 20 77 61 69 74 5f 74 69 6d 65 6f 75 74 20 61 6e 64 20 69 6e 74 65 72 61 63 74 69 76 65 5f 74 69 6d 65 6f 75 74 20 66 6f 72 20 63 6f 6e 66 69 67 75 72 69 6e 67 20 74 68 69 73 20 62 65 68 61 76 69 6f 72 2e ]

I don't think there was an explicit query in progress, as it was just idling locally. The stack:

#4	0x00000001b86c5530 in assertionFailure(_:file:line:) ()
#5	0x0000000100f02058 in MySQLConnectionHandler.channelRead(context:data:) at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/mysql-nio/Sources/MySQLNIO/MySQLConnectionHandler.swift:89
#6	0x0000000100f0c25c in protocol witness for _ChannelInboundHandler.channelRead(context:data:) in conformance MySQLConnectionHandler ()
#7	0x0000000100191db8 in ChannelHandlerContext.invokeChannelRead(_:) at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/swift-nio/Sources/NIOCore/ChannelPipeline.swift:1702
#8	0x0000000100191e14 in ChannelHandlerContext.invokeChannelRead(_:) at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/swift-nio/Sources/NIOCore/ChannelPipeline.swift:1704
#9	0x000000010019679c in ChannelHandlerContext.fireChannelRead(_:) at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/swift-nio/Sources/NIOCore/ChannelPipeline.swift:1515
#10	0x0000000100f3607c in MySQLPacketDecoder.decode(context:buffer:) at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/mysql-nio/Sources/MySQLNIO/Packet/MySQLPacketDecoder.swift:32
#11	0x0000000100f362c8 in protocol witness for ByteToMessageDecoder.decode(context:buffer:) in conformance MySQLPacketDecoder ()
#12	0x00000001001afa80 in closure #1 in ByteToMessageHandler.decodeLoop(context:decodeMode:) at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/swift-nio/Sources/NIOCore/Codec.swift:582
#13	0x00000001001b6914 in partial apply for closure #1 in ByteToMessageHandler.decodeLoop(context:decodeMode:) ()
#14	0x00000001001ae8e4 in ByteToMessageHandler.withNextBuffer(allowEmptyBuffer:_:) at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/swift-nio/Sources/NIOCore/Codec.swift:539
#15	0x00000001001af3a4 in ByteToMessageHandler.decodeLoop(context:decodeMode:) at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/swift-nio/Sources/NIOCore/Codec.swift:578
#16	0x00000001001b0984 in ByteToMessageHandler.channelRead(context:data:) at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/swift-nio/Sources/NIOCore/Codec.swift:651
#17	0x00000001001b108c in protocol witness for _ChannelInboundHandler.channelRead(context:data:) in conformance ByteToMessageHandler<τ_0_0> ()
#18	0x0000000100191db8 in ChannelHandlerContext.invokeChannelRead(_:) at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/swift-nio/Sources/NIOCore/ChannelPipeline.swift:1702
#19	0x000000010019679c in ChannelHandlerContext.fireChannelRead(_:) at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/swift-nio/Sources/NIOCore/ChannelPipeline.swift:1515
#20	0x00000001009d6064 in NIOSSLHandler.doFlushReadData(context:receiveBuffer:readOnEmptyBuffer:) at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/swift-nio-ssl/Sources/NIOSSL/NIOSSLHandler.swift:517
#21	0x00000001009d1b08 in NIOSSLHandler.channelReadComplete(context:) at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/swift-nio-ssl/Sources/NIOSSL/NIOSSLHandler.swift:190
#22	0x00000001009d76ec in protocol witness for _ChannelInboundHandler.channelReadComplete(context:) in conformance NIOSSLHandler ()
#23	0x0000000100191f2c in ChannelHandlerContext.invokeChannelReadComplete() at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/swift-nio/Sources/NIOCore/ChannelPipeline.swift:1712
#24	0x000000010018e96c in ChannelPipeline.fireChannelReadComplete0() at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/swift-nio/Sources/NIOCore/ChannelPipeline.swift:903
#25	0x0000000100194358 in ChannelPipeline.SynchronousOperations.fireChannelReadComplete() at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/swift-nio/Sources/NIOCore/ChannelPipeline.swift:1170
#26	0x0000000100489b1c in BaseSocketChannel.readable0() at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/swift-nio/Sources/NIOPosix/BaseSocketChannel.swift:1143
#27	0x000000010048ab48 in BaseSocketChannel.readable() at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/swift-nio/Sources/NIOPosix/BaseSocketChannel.swift:1070
#28	0x000000010048ba94 in protocol witness for SelectableChannel.readable() in conformance BaseSocketChannel<τ_0_0> ()
#29	0x00000001004f64b0 in SelectableEventLoop.handleEvent<τ_0_0>(_:channel:) at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/swift-nio/Sources/NIOPosix/SelectableEventLoop.swift:413
#30	0x00000001004f7948 in closure #2 in closure #2 in SelectableEventLoop.run() at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/swift-nio/Sources/NIOPosix/SelectableEventLoop.swift:488
#31	0x00000001004fbe38 in partial apply for closure #2 in closure #2 in SelectableEventLoop.run() ()
#32	0x0000000100509ba0 in Selector.whenReady0(strategy:onLoopBegin:_:) at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/swift-nio/Sources/NIOPosix/SelectorKqueue.swift:258
#33	0x00000001005028c8 in Selector.whenReady(strategy:onLoopBegin:_:) at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/swift-nio/Sources/NIOPosix/SelectorGeneric.swift:288
#34	0x00000001004f7640 in closure #2 in SelectableEventLoop.run() at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/swift-nio/Sources/NIOPosix/SelectableEventLoop.swift:480
#35	0x00000001004fac20 in partial apply for closure #2 in SelectableEventLoop.run() ()
#36	0x00000001004f18bc in closure #1 in withAutoReleasePool<τ_0_0>(_:) at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/swift-nio/Sources/NIOPosix/SelectableEventLoop.swift:26
#37	0x00000001004f1920 in partial apply for closure #1 in withAutoReleasePool<τ_0_0>(_:) ()
#38	0x00000001bffa7474 in autoreleasepool<τ_0_0>(invoking:) ()
#39	0x00000001004f1850 in withAutoReleasePool<τ_0_0>(_:) at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/swift-nio/Sources/NIOPosix/SelectableEventLoop.swift:25
#40	0x00000001004f6864 in SelectableEventLoop.run() at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/swift-nio/Sources/NIOPosix/SelectableEventLoop.swift:479
#41	0x00000001004bd41c in static MultiThreadedEventLoopGroup.runTheLoop(thread:parentGroup:canEventLoopBeShutdownIndividually:selectorFactory:initializer:_:) at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/swift-nio/Sources/NIOPosix/MultiThreadedEventLoopGroup.swift:95
#42	0x00000001004bd99c in closure #1 in static MultiThreadedEventLoopGroup.setupThreadAndEventLoop(name:parentGroup:selectorFactory:initializer:) at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/swift-nio/Sources/NIOPosix/MultiThreadedEventLoopGroup.swift:116
#43	0x00000001004c3364 in partial apply for closure #1 in static MultiThreadedEventLoopGroup.setupThreadAndEventLoop(name:parentGroup:selectorFactory:initializer:) ()
#44	0x0000000100529dc4 in thunk for @escaping @callee_guaranteed (@guaranteed NIOThread) -> () ()
#45	0x000000010052de48 in closure #1 in static ThreadOpsPosix.run(handle:args:detachThread:) at /Users/rmann/Library/Developer/Xcode/DerivedData/camsync-bylosnkysgfpvnbwbikdmasqdlgw/SourcePackages/checkouts/swift-nio/Sources/NIOPosix/ThreadPosix.swift:105
#46	0x000000010052df04 in @objc closure #1 in static ThreadOpsPosix.run(handle:args:detachThread:) ()
#47	0x000000010396d5d4 in _pthread_start ()

The queue has 16 nil entries:

(lldb) po self.queue
▿ [ _ _ _ _ _ _ _ _ _ _ _ _ _ _ <_ _ ] (bufferCapacity: 16, ringLength: 0)
  ▿ _buffer : 16 elements
    - 0 : nil
    - 1 : nil
    - 2 : nil
    - 3 : nil
    - 4 : nil
    - 5 : nil
    - 6 : nil
    - 7 : nil
    - 8 : nil
    - 9 : nil
    - 10 : nil
    - 11 : nil
    - 12 : nil
    - 13 : nil
    - 14 : nil
    - 15 : nil
  - headBackingIndex : 14
  - tailBackingIndex : 14
(lldb) po data
▿ NIOAny { [255, 191, 15, 35, 72, 89, 48, 48, 48, 84, 104, 101, 32, 99, 108, 105]... (err: true, ok: false, eof: false) }
  ▿ _storage : _NIOAny
    ▿ other : [255, 191, 15, 35, 72, 89, 48, 48, 48, 84, 104, 101, 32, 99, 108, 105]... (err: true, ok: false, eof: false)
      ▿ payload : ByteBuffer { readerIndex: 0, writerIndex: 145, readableBytes: 145, capacity: 145, storageCapacity: 32768, slice: _ByteBufferSlice { 4..<149 }, storage: 0x000000010683f000 (32768 bytes) }
        ▿ _storage : <_Storage: 0x600001718180>
        - _readerIndex : 0
        - _writerIndex : 145
        ▿ _slice : _ByteBufferSlice { 4..<149 }
          - upperBound : 149
          ▿ _begin : 4
            ▿ _backing : 2 elements
              - .0 : 0
              - .1 : 4

To Reproduce

I didn't try to reproduce it.

Expected behavior

Ideally an error like this wouldn't crash the process. Maybe roll back the current transaction and throw an error?

Environment

$ vapor --version
framework: 4.67.5
toolbox: 18.6.0

Ventura 13.1 (22C65)
mysql-nio v1.4.0
mysql-kit v4.6.1
MySQL server v8.0.28, protocol version 10

Additional context

Add any other context about the problem here.

Decode MySQLRow with specified table name

There seems to be no way to decode models from MySQLRow with specified table name in Vapor 4.
(In Vapor 3 I often used this method. https://github.com/vapor/mysql-kit/blob/3.3.0/Sources/MySQL/Connection/MySQLConnection.swift#L66-L69)

I have raw query like below:

SELECT * FROM `table1`, `table2`;

It returns columns like table1.id, table2.id... There are columns with same name but they can be distinguished with table name.
MySQLRow conveys this information, but all its decode methods come from SQLRow protocol and they don't consider table names.
Thus they can't distinguish table1.id and table2.id, and we get wrong result.

MySQLRow should have its own decoding method decode(model: Decodable.Type, table: String).
I think it can be done by creating MySQLRowDecoder that imitates SQLRowDecoder,
but first I want your opinion if this design is good or not.
I use only MySQL so I'm not sure if this can be generalized to other RDBMSs.

Support re-using prepared statements

MySQL's wire protocol supports this but currently the driver just creates a new prepared statement for each query, even if only the bind values are changing.

Prepared statements might not be properly closed

After running my server for ~48 hours with steady traffic all database queries started throwing Can't create more than max_prepared_stmt_count statements. I fixed this issue by restarting the server and the error went away (although I assume it will return in the near future).

I'm betting that somewhere the prepared statements aren't getting closed properly and are retained, starving other requests. Unfortunately I don't know enough about this codebase to try and find the exact place that would be happening.

invalidProtocolVersion(255) when trying to connect to MySQL on Ubuntu

Describe the bug

When trying to connect to MySQL from Vapor running on Ubuntu, I am receiving the following error:
invalidProtocolVersion(255)

Meanwhile, I have no issues with connecting when running Vapor on my Mac locally

Environment

I am running Vapor in a Docker Container on an Ubuntu 22 server. I have also tried running vapor outside of docker using the cli.
I am running MySQL 8.0.32-0ubuntu0.22.04.2

  • Vapor Framework version:
    • Vapor: 4.69.1
    • Fluent 4.6.0
    • Fluent MySQL Driver 4.2.0
  • OS version: Ubuntu 22.04.2

High level transaction support?

My government organisation is switching to Swift backend micro services. We rather have as few dependencies as possible and I am really missing a way to execute multiple statements in a transaction. I guess it's kind of possible to hack that in but with many in-house developers it should rather be obvious (and documented) to do so. I am either missing that it's there or really would like to have this implemented in an intuitive to use feature.

Describe the solution you'd like
Ultimately a way to execute statements inside a closure that is provided by a transaction api is what I am wishing for.

Add rollback on throwing and it becomes pretty simply to use. Of course this is just a high level wish and probably requires more - for example a way to turn off automatic transactions in a nice way without using raw sql.

/pardeike

SocketAddress error (under Ventura)

Just trying to start a project with mysql-nio.

The following calls;
let socketAddress = try SocketAddress(ipAddress: "192.168.1.75", port: 3306)
let connection = MySQLConnection.connect(to: try .makeAddressResolvingHost("192.168.1.75", port: 3306),...

both return an error dialog; The operation couldn’t be completed. (MySQLNIO.MySQLProtocol.HandshakeV10.Error error 0.)
and simultaneously crashing the debugger (Xcode 14).

top of the log;
cmsMedical2 0x0000000109e5435f $s8NIOPosix27MultiThreadedEventLoopGroupC014setupThreadAnddE033_C2B1528F4FBA68A3DBFA89DBAEBE9D4DLL4name06parentF015selectorFactory11initializerAA010SelectabledE0CSS_AcA8SelectorCyAA15NIORegistrationVGyKcyAA9NIOThreadCctFZ + 575
4 cmsMedical2 0x0000000109e5512e $s8NIOPosix27MultiThreadedEventLoopGroupC18threadInitializers15selectorFactoryACSayyAA9NIOThreadCcG_AA8SelectorCyAA15NIORegistrationVGyKctcfcAA010SelectabledE0CyAGcXEfU_ + 734
5 cmsMedical2 0x0000000109e580b0 $s8NIOPosix27MultiThreadedEventLoopGroupC18threadInitializers15selectorFactoryACSayyAA9NIOThreadCcG_AA8SelectorCyAA15NIORegistrationVGyKctcfcAA010SelectabledE0CyAGcXEfU_TA + 32
6 libswiftCore.dylib 0x00007ff8199efe2e $sSlsE3mapySayqd__Gqd__7ElementQzKXEKlF + 462
7 cmsMedical2 0x0000000109e54d7a $s8NIOPosix27MultiThreadedEventLoopGroupC18threadInitializers15selectorFactoryACSayyAA9NIOThreadCcG_AA8SelectorCyAA15NIORegistrationVGyKctcfc + 666
8 cmsMedical2 0x0000000109e54ad4 $s8NIOPosix27MultiThreadedEventLoopGroupC18threadInitializers15selectorFactoryACSayyAA9NIOThreadCcG_AA8SelectorCyAA15NIORegistrationVGyKctcfC + 68
9 cmsMedical2 0x0000000109e5495d $s8NIOPosix27MultiThreadedEventLoopGroupC15numberOfThreads15selectorFactoryACSi_AA8SelectorCyAA15NIORegistrationVGyKctcfC + 269
10 cmsMedical2 0x0000000109e547bc $s8NIOPosix27MultiThreadedEventLoopGroupC15numberOfThreadsACSi_tcfC + 44
11 cmsMedical2 0x0000000109b60e70 $s11cmsMedical28databaseCACycfc + 48
12 cmsMedical2 0x0000000109b60e31 $s11cmsMedical28databaseCACycfC + 33
13 cmsMedical2 0x0000000109b5ebf8 $s11cmsMedical24myDB_WZ + 24
14

Errors related to Float80 when trying to compile on Raspberry Pi 3

I am trying to compile a vapor project that uses the mysql https://github.com/vapor/mysql repository on a Raspberry Pi 3 (w/ Ubuntu 16.04) but it fails with the errors below.
I might be wrong but from what I was able to find, Float80 is not available for ARM-based platforms.

Steps to reproduce

Trying to compile a Vapor project with the mysql-driver/fluent-mysql repository using this line in the dependencies section in Package.swift:
.package(url: "https://github.com/vapor/mysql-driver.git", .upToNextMajor(from: "3.0.0-rc")),

Expected behavior

Compile the project (works fine on MacOS)

Actual behavior

Not compiling and getting the errors:

/home/xxx/yyy-master/.build/checkouts/mysql.git-1505813759/Sources/MySQL/Connection/MySQLData.swift:219:24: error: use of unresolved identifier 'Float80'
.flatMap { Float80($0) }
^~~~~~~
Swift.Float:1:15: note: did you mean 'Float'?`
public struct Float {
^
Swift.Float32:1:18: note: did you mean 'Float32'?
public typealias Float32 = Float
^
Swift.Float64:1:18: note: did you mean 'Float64'?
public typealias Float64 = Double
^
SwiftGlibc.float_t:1:18: note: did you mean 'float_t'?
public typealias float_t = Float
^
/home/xxx/yyy-master/.build/checkouts/mysql.git-1505813759/Sources/MySQL/Connection/MySQLData.swift:237:36: error: use of unresolved identifier 'Float80'
.flatMap { Float80($0) }
^~~~~~~
Swift.Float:1:15: note: did you mean 'Float'?
public struct Float {
^
Swift.Float32:1:18: note: did you mean 'Float32'?
public typealias Float32 = Float
^
Swift.Float64:1:18: note: did you mean 'Float64'?
public typealias Float64 = Double
^
SwiftGlibc.float_t:1:18: note: did you mean 'float_t'?
public typealias float_t = Float

Environment

Raspberry Pi 3
Ubuntu 16.04
Vapor 3
Swift 4.1 (tried 4.2 as well)

Anything can be done to work around this error?

Timezone support

Currently it seems Date is encoded/decoded as GMT+00:00.
encode/decode
Now I want to store date as local time zone. So I want to make the TimeZones above configurable.

There are two way to accomplish this.

  1. Set TimeZone via MySQLDatabaseConfig
  2. Get database's time zone while MySQLConnection.authenticate and use it

Eitherway we must provide time zone to convertFromMySQLTime and convertToMySQLTime.
Any ideas?

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

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

8 byte MYSQL_TIME

I have saved a TIME for my MySQL, but when I'm trying to select the same table I'm receiving the following throw:
throw MySQLError(identifier: "timeLength", reason: "Invalid MYSQL_TIME length.")
The timeLenght is 8! For the following value, sounds correct to me: 08:00:00.
Can you help me please?
I have already removed this field from my Swift object, but even though the decode is still trying to decode this field.

Link to MSQLNIO API in Readme.md gives error 403

Hi
When clicking on the link to MYSQLNIO API, under the heading API Docs, readme.md - I get en error 403 Forbidden.

Can you help with the correct link to the documentation?

The full error code is:
403 Forbidden

Code: AccessDenied
Message: Access Denied
RequestId: R7FF9D4VCD0GAXPT
HostId: UxuQc4A9eph0bkmDZ1kx99JaRIB/7doa6yyPODRk7+eJQjV0vyX7VijRwv7fg8dm3fVZliNI8K8=
An Error Occurred While Attempting to Retrieve a Custom Error Document

Code: AccessDenied
Message: Access Denied

Protocol mismatch not detected

Describe the bug

I accidentally tried to connect to the port 33060 of the MySQL server, which is used for protocol X of the MySQL. This port returns version 11 of the protocol and so the handshake should fail with reasonable error message (as implemented here).

The problem is, that this check somehow passed and the application crashed later with cryptic message:

Fatal error: Unexpectedly found nil while unwrapping an Optional value: file MySQLNIO/MySQLConnectionHandler.swift, line 416

with increased verbosity:

[ codes.vapor.application ] [ DEBUG ] Opening new connection to [IPv6]localhost/::1:33060 [database-id: mysql] (MySQLNIO/MySQLConnection.swift:17)
[ codes.vapor.application ] [ TRACE ] MySQLPacketDecoder.decode: [11, 8, 5, 26, 0]... (err: false, ok: false, eof: false) [database-id: mysql] (MySQLNIO/Packet/MySQLPacketDecoder.swift:30)
[ codes.vapor.application ] [ TRACE ] Handle handshake packet [database-id: mysql] (MySQLNIO/MySQLConnectionHandler.swift:62)
Fatal error: Unexpectedly found nil while unwrapping an Optional value: file MySQLNIO/MySQLConnectionHandler.swift, line 416

To Reproduce

Try to connect to the port 33060 instead of the default 3306

Expected behaviour

Meaningful error message should be printed.

Environment

  • Vapor Framework version: 4.39.2
  • Vapor Toolbox version: 18.3.2
  • OS version: macOS 11
  • MySQL version: 8.0.23

Allow MySQLConnection creation without specifying an event loop

[The equivalent of] cherry-picked from vapor/postgres-nio#388:

SwiftNIO has landed EventLoopGroup singletons in 2.58.0. See apple/swift-nio#2471.

We should add an additional MySQLConnection connect method, that internally uses the existing one, but uses the NIO singleton:

extension MySQLConnection {
    static func connect(
        to socketAddress: SocketAddress,
        username: String,
        database: String,
        password: String? = nil,
        tlsConfiguration: TLSConfiguration? = .makeClientConfiguration(),
        serverHostname: String? = nil,
        logger: Logger = .init(label: "codes.vapor.mysql")
    ) -> EventLoopFuture<MySQLConnection> {
        Self.connect(
            to: socketAddress,
            username: username,
            password: password,
            tlsConfiguration: tlsConfiguration,
            serverHostname: serverHostname,
            on: MultiThreadedEventLoopGroup.singleton.any()
        )
    }
}
  • Add API
  • Remove all EventLoopGroup usages in the README

MySQL cannot save long strings (over 252 chars long)

Found another bug related to MySQL

Posting this directly here because it's related to MySQL (where my previous unrelated issue has been migrated to).

MySQL supports the TEXT data type for storing extra long strings. I can access it in Fluent by using .custom("text") during migration. Fluent is able to read from and write to it fine, with a caveat: the content cannot be over 252 characters long.

This does not happen with FluentSQLiteDriver, it can save string of any length to the column with text datatype. (I did not test with Postgre because I don't have it installed on my system)

Steps to reproduce

Create a new project from template (vapor new --branch=4), then add a new content field, when migrating, let it have the text data type:

struct CreateTodo: Migration {
    func prepare(on database: Database) -> EventLoopFuture<Void> {
        return database.schema("todos")
            .id()
            .field("title", .string, .required)
            .field("content", .custom("text"), .required)
            .create()
    }

    func revert(on database: Database) -> EventLoopFuture<Void> {
        return database.schema("todos").delete()
    }
}
final class Todo: Model, Content {
    static let schema = "todos"
    
    @ID(key: .id)
    var id: UUID?

    @Field(key: "title")
    var title: String
    
    @Field(key: "content")
    var content: String

    init() { }

    init(id: UUID? = nil, title: String) {
        self.id = id
        self.title = title
    }
}

Expected behavior

The server should be able to read and write strings of any length.

Actual behavior

If the string is over 252 characters long, FluentMySQLDriver will output an error:

[ ERROR ] MySQL error: Server error: Malformed communication packet.

If the string is over 255 characters long, the server will just crash with the following error:

Fatal error: Not enough bits to represent the passed value: file /AppleInternal/BuildRoot/Library/Caches/com.apple.xbs/Sources/swiftlang/swiftlang-1103.8.25.8/swift/stdlib/public/core/Integers.swift, line 3447

Environment

  • Vapor Framework version: 4.0.0-rc.3
  • Fluent-kit Framework version: 1.0.0-rc.1.5
  • Fluent-MySQL-driver Framework version: 4.0.0-rc.1.1
  • Vapor Toolbox version: 3.1.10
  • OS version: macOS 10.15.4
  • Xcode version: 11.4

Multiple batched request

it would be useful to be able to submit multiple requests in one batch, and to retrieve the multiple results.

The idea is to avoid several way and back from the swift app to the server when we do not need to wait for the server answer to decide the next request (ie. Request several select on various table without join, and retrieve the different select result in one answer)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.