Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/deployment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
tags:
- 'v*'
- '[0-9]*'
workflow_dispatch:

jobs:

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
format_check_enabled : true
broken_symlink_check_enabled : true
unacceptable_language_check_enabled : true
shell_check_enabled : true
shell_check_enabled : false
docs_check_enabled : false
api_breakage_check_enabled : false
license_header_check_enabled : false
Expand All @@ -27,7 +27,7 @@ jobs:
uses: BinaryBirds/github-workflows/.github/workflows/extra_soundness.yml@main
with:
local_swift_dependencies_check_enabled : true
headers_check_enabled : true
headers_check_enabled : false
docc_warnings_check_enabled : true

swiftlang_tests:
Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ SHELL=/bin/bash

baseUrl = https://raw.githubusercontent.com/BinaryBirds/github-workflows/refs/heads/main/scripts

check: symlinks language deps lint headers
check: symlinks language deps lint headers docc-warnings package

package:
curl -s $(baseUrl)/check-swift-package.sh | bash

symlinks:
curl -s $(baseUrl)/check-broken-symlinks.sh | bash
Expand Down
6 changes: 3 additions & 3 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ let package = Package(
dependencies: [
.package(url: "https://github.com/apple/swift-log", from: "1.6.0"),
.package(url: "https://github.com/vapor/mysql-nio", from: "1.8.0"),
.package(url: "https://github.com/feather-framework/feather-database", exact: "1.0.0-beta.2"),
.package(url: "https://github.com/feather-framework/feather-database", exact: "1.0.0-beta.3"),
// [docc-plugin-placeholder]
],
targets: [
Expand Down
74 changes: 32 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,30 @@

MySQL/MariaDB driver implementation for the abstract [Feather Database](https://github.com/feather-framework/feather-database) Swift API package.

[
![Release: 1.0.0-beta.2](https://img.shields.io/badge/Release-1%2E0%2E0--beta%2E2-F05138)
](
https://github.com/feather-framework/feather-mysql-database/releases/tag/1.0.0-beta.2
)
[![Release: 1.0.0-beta.2](https://img.shields.io/badge/Release-1%2E0%2E0--beta%2E2-F05138)](https://github.com/feather-framework/feather-mysql-database/releases/tag/1.0.0-beta.2)

## Features

- 🤝 MySQL/MariaDB driver for Feather Database
- 😱 Automatic query parameter escaping via Swift string interpolation.
- 🔄 Async sequence query results with `Decodable` row support.
- 🧵 Designed for modern Swift concurrency
- 📚 DocC-based API Documentation
- Unit tests and code coverage
- MySQL/MariaDB driver for Feather Database
- Automatic query parameter escaping via Swift string interpolation.
- Async sequence query results with `Decodable` row support.
- Designed for modern Swift concurrency
- DocC-based API Documentation
- Unit tests and code coverage

## Requirements

![Swift 6.1+](https://img.shields.io/badge/Swift-6%2E1%2B-F05138)
![Platforms: Linux, macOS, iOS, tvOS, watchOS, visionOS](https://img.shields.io/badge/Platforms-Linux_%7C_macOS_%7C_iOS_%7C_tvOS_%7C_watchOS_%7C_visionOS-F05138)

- Swift 6.1+

- Platforms:
- Linux
- macOS 15+
- iOS 18+
- tvOS 18+
- watchOS 11+
- visionOS 2+
- Swift 6.1+
- Platforms:
- Linux
- macOS 15+
- iOS 18+
- tvOS 18+
- watchOS 11+
- visionOS 2+

## Installation

Expand All @@ -46,19 +41,13 @@ Then add `FeatherMySQLDatabase` to your target dependencies:
.product(name: "FeatherMySQLDatabase", package: "feather-mysql-database"),
```


## Usage

[
![DocC API documentation](https://img.shields.io/badge/DocC-API_documentation-F05138)
](
https://feather-framework.github.io/feather-mysql-database/documentation/feathermysqldatabase/
)
API documentation is available at the link below:

API documentation is available at the following link.
[![DocC API documentation](https://img.shields.io/badge/DocC-API_documentation-F05138)](https://feather-framework.github.io/feather-mysql-database/)

> [!TIP]
> Avoid calling `database.execute` while in a transaction; use the transaction `connection` instead.
Here is a brief example:

```swift
import Logging
Expand Down Expand Up @@ -98,15 +87,17 @@ let database = MySQLDatabaseClient(
)

do {
let result = try await database.execute(
query: #"""
SELECT
VERSION() AS `version`
WHERE
1=\#(1);
"""#
)

let result = try await database.withConnection { connection in
try await connection.run(
query: #"""
SELECT
VERSION() AS `version`
WHERE
1=\#(1);
"""#
)
}

for try await item in result {
let version = try item.decode(column: "version", as: String.self)
print(version)
Expand All @@ -126,7 +117,6 @@ catch {
> [!WARNING]
> This repository is a work in progress, things can break until it reaches v1.0.0.


## Other database drivers

The following database driver implementations are available for use:
Expand All @@ -137,9 +127,9 @@ The following database driver implementations are available for use:
## Development

- Build: `swift build`
- Test:
- local: `swift test`
- using Docker: `make docker-test`
- Test:
- local: `swift test`
- using Docker: `make docker-test`
- Format: `make format`
- Check: `make check`

Expand Down
32 changes: 0 additions & 32 deletions Sources/FeatherMySQLDatabase/MySQLConnection.swift

This file was deleted.

41 changes: 21 additions & 20 deletions Sources/FeatherMySQLDatabase/MySQLDatabaseClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ import MySQLNIO
/// Use this client to execute queries and manage transactions on MySQL.
public struct MySQLDatabaseClient: DatabaseClient {

var connection: MySQLConnection
public typealias Connection = MySQLDatabaseConnection

var connection: MySQLDatabaseConnection
var logger: Logger

/// Create a MySQL database client.
Expand All @@ -27,7 +29,10 @@ public struct MySQLDatabaseClient: DatabaseClient {
connection: MySQLConnection,
logger: Logger
) {
self.connection = connection
self.connection = .init(
connection: connection,
logger: logger
)
self.logger = logger
}

Expand All @@ -36,16 +41,13 @@ public struct MySQLDatabaseClient: DatabaseClient {
/// Execute work using the stored connection.
///
/// The closure is executed with the current connection.
/// - Parameters:
/// - isolation: The actor isolation for the operation.
/// - closure: A closure that receives the MySQL connection.
/// - Parameter closure: A closure that receives the MySQL connection.
/// - Throws: A `DatabaseError` if the connection fails.
/// - Returns: The query result produced by the closure.
@discardableResult
public func connection<T>(
isolation: isolated (any Actor)? = #isolation,
_ closure: (MySQLConnection) async throws -> sending T
) async throws(DatabaseError) -> sending T {
public func withConnection<T>(
_ closure: (Connection) async throws -> T
) async throws(DatabaseError) -> T {
do {
return try await closure(connection)
}
Expand All @@ -60,23 +62,22 @@ public struct MySQLDatabaseClient: DatabaseClient {
/// Execute work inside a MySQL transaction.
///
/// The closure runs between `START TRANSACTION` and `COMMIT` with rollback on failure.
/// - Parameters:
/// - isolation: The actor isolation for the operation.
/// - closure: A closure that receives the MySQL connection.
/// - Parameter closure: A closure that receives the MySQL connection.
/// - Throws: A `DatabaseError` if transaction handling fails.
/// - Returns: The query result produced by the closure.
@discardableResult
public func transaction<T>(
isolation: isolated (any Actor)? = #isolation,
_ closure: (MySQLConnection) async throws -> sending T
) async throws(DatabaseError) -> sending T {
public func withTransaction<T>(
_ closure: (Connection) async throws -> T
) async throws(DatabaseError) -> T {

do {
try await connection.execute(query: "START TRANSACTION;")
try await connection.run(query: "START TRANSACTION;") { _ in }
}
catch {
throw DatabaseError.transaction(
MySQLTransactionError(beginError: error)
MySQLTransactionError(
beginError: error
)
)
}

Expand All @@ -87,7 +88,7 @@ public struct MySQLDatabaseClient: DatabaseClient {
closureHasFinished = true

do {
try await connection.execute(query: "COMMIT;")
try await connection.run(query: "COMMIT;") { _ in }
}
catch {
throw DatabaseError.transaction(
Expand All @@ -104,7 +105,7 @@ public struct MySQLDatabaseClient: DatabaseClient {
txError.closureError = error

do {
try await connection.execute(query: "ROLLBACK;")
try await connection.run(query: "ROLLBACK;") { _ in }
}
catch {
txError.rollbackError = error
Expand Down
55 changes: 55 additions & 0 deletions Sources/FeatherMySQLDatabase/MySQLDatabaseConnection.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//
// MySQLDatabaseConnection.swift
// feather-mysql-database
//
// Created by Tibor Bödecs on 2026. 01. 10.
//

import FeatherDatabase
import MySQLNIO
import NIOCore

public struct MySQLDatabaseConnection: DatabaseConnection, Sendable {

public typealias Query = MySQLQuery
public typealias RowSequence = MySQLRowSequence

let connection: MySQLNIO.MySQLConnection
public var logger: Logging.Logger

/// Execute a MySQL query on this connection.
///
/// This wraps `MySQLNIO` query execution and maps errors.
/// - Parameters:
/// - query: The MySQL query to execute.
/// - handler: A closure that transforms the result into a generic value.
/// - Throws: A `DatabaseError` if the query fails.
/// - Returns: A query result containing the returned rows.
@discardableResult
public func run<T: Sendable>(
query: Query,
_ handler: (RowSequence) async throws -> T = { $0 }
) async throws(DatabaseError) -> T {
do {
let rows =
try await connection.query(
query.sql,
query.bindings
)
.get()

return try await handler(
MySQLRowSequence(
elements: rows.map {
.init(
row: $0
)
}
)
)
}
catch {
throw .query(error)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//
// MySQLQuery.swift
// MySQLDatabaseQuery.swift
// feather-mysql-database
//
// Created by Tibor Bödecs on 2026. 01. 10..
// Created by Tibor Bödecs on 2026. 01. 10.
//

import FeatherDatabase
Expand Down
Loading