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
20 changes: 1 addition & 19 deletions Package.resolved

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

7 changes: 7 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ let package = Package(
name: "FeatherSQLiteDatabaseTests",
dependencies: [
.target(name: "FeatherSQLiteDatabase"),
.product(
name: "ServiceLifecycleTestKit",
package: "swift-service-lifecycle",
condition: .when(
traits: ["ServiceLifecycleSupport"]
)
),
],
swiftSettings: defaultSwiftSettings
),
Expand Down
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

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

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

## Features

Expand Down Expand Up @@ -66,7 +70,11 @@ Available traits:

API documentation is available at the link below:

[![DocC API documentation](https://img.shields.io/badge/DocC-API_documentation-F05138)](https://feather-framework.github.io/feather-sqlite-database/)
[
![DocC API documentation](https://img.shields.io/badge/DocC-API_documentation-F05138)
](
https://feather-framework.github.io/feather-sqlite-database/
)

Here is a brief example:

Expand Down
13 changes: 10 additions & 3 deletions Sources/FeatherSQLiteDatabase/SQLiteDatabaseService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,16 @@ public struct SQLiteDatabaseService: Service {
///
/// - Throws: Rethrows any error produced while starting the SQLite client.
public func run() async throws {
try await client.run()
try? await gracefulShutdown()
await client.shutdown()
do {
try await client.run()
try? await gracefulShutdown()
await client.shutdown()
}
catch {
await client.shutdown()
throw error
}

}

}
Expand Down
23 changes: 8 additions & 15 deletions Sources/SQLiteNIOExtras/SQLiteConnectionPool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -202,17 +202,7 @@ actor SQLiteConnectionPool {
)
}
catch {
do {
try await connection.close()
}
catch {
configuration.logger.warning(
"Failed to close SQLite connection after setup error",
metadata: [
"error": "\(error)"
]
)
}
await closeConnection(connection)
throw error
}
return connection
Expand All @@ -221,10 +211,13 @@ actor SQLiteConnectionPool {
private func closeConnection(
_ connection: SQLiteConnection
) async {
do {
try await connection.close()
}
catch {
let result =
await Task.detached {
try await connection.close()
}
.result

if case .failure(let error) = result {
configuration.logger.warning(
"Failed to close SQLite connection",
metadata: [
Expand Down
180 changes: 179 additions & 1 deletion Tests/FeatherSQLiteDatabaseTests/FeatherSQLiteDatabaseTestSuite.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@
import FeatherDatabase
import Logging
import SQLiteNIO
import SQLiteNIOExtras
import Testing

@testable import FeatherSQLiteDatabase
@testable import SQLiteNIOExtras

#if ServiceLifecycleSupport
import ServiceLifecycleTestKit
#endif

@Suite
struct FeatherSQLiteDatabaseTestSuite {
Expand Down Expand Up @@ -1180,5 +1184,179 @@ extension FeatherSQLiteDatabaseTestSuite {
await serviceGroup.triggerGracefulShutdown()
}
}

@Test
func serviceLifecycleCancellationShutsDownClient() async throws {
var logger = Logger(label: "test")
logger.logLevel = .info

let configuration = SQLiteClient.Configuration(
storage: .memory,
logger: logger
)
let client = SQLiteClient(configuration: configuration)
let service = SQLiteDatabaseService(client)

try await withThrowingTaskGroup(of: Void.self) { group in
group.addTask {
try await service.run()
}

try await Task.sleep(for: .milliseconds(100))
group.cancelAll()

do {
while let _ = try await group.next() {}
}
catch {
// Cancellation is expected; the shutdown is asserted below.
}
}

do {
try await client.withConnection { _ in }
Issue.record("Expected shutdown to reject new connections.")
}
catch {
#expect(error is SQLiteConnectionPoolError)
}
}

@Test
func serviceLifecycleGracefulShutdownShutsDownClient() async throws {
var logger = Logger(label: "test")
logger.logLevel = .info

let configuration = SQLiteClient.Configuration(
storage: .memory,
logger: logger
)
let client = SQLiteClient(configuration: configuration)
let service = SQLiteDatabaseService(client)
let serviceGroup = ServiceGroup(
services: [service],
logger: logger
)

try await withThrowingTaskGroup(of: Void.self) { group in
group.addTask {
try await serviceGroup.run()
}

try await Task.sleep(for: .milliseconds(100))
await serviceGroup.triggerGracefulShutdown()

do {
while let _ = try await group.next() {}
}
catch {
Issue.record(error)
}
}

do {
try await client.withConnection { _ in }
Issue.record("Expected shutdown to reject new connections.")
}
catch {
#expect(error is SQLiteConnectionPoolError)
}
}

@Test
func cancellationErrorTrigger() async throws {
var logger = Logger(label: "test")
logger.logLevel = .info

let configuration = SQLiteClient.Configuration(
storage: .memory,
logger: logger
)
let client = SQLiteClient(configuration: configuration)
let database = SQLiteDatabaseClient(
client: client,
logger: logger
)

enum MigrationError: Error {
case generic
}

struct MigrationService: Service {
let database: any DatabaseClient

func run() async throws {
let result = try await database.withConnection { connection in
try await connection.run(
query: #"""
SELECT sqlite_version() AS "version"
"""#
) { try await $0.collect().first }
}
let version = try result?
.decode(
column: "version",
as: String.self
)
#expect(version?.split(separator: ".").count == 3)

throw MigrationError.generic
}
}

let serviceGroup = ServiceGroup(
configuration: .init(
services: [
.init(
service: SQLiteDatabaseService(client)
),
.init(
service: MigrationService(database: database),
successTerminationBehavior: .gracefullyShutdownGroup,
failureTerminationBehavior: .cancelGroup
),
],
logger: logger
)
)

do {
try await serviceGroup.run()
Issue.record("Service group should fail.")
}
catch let error as MigrationError {
#expect(error == .generic)
}
catch {
Issue.record("Service group should throw a generic Migration error")
}
}

@Test
func serviceGracefulShutdown() async throws {
var logger = Logger(label: "test")
logger.logLevel = .info

let configuration = SQLiteClient.Configuration(
storage: .memory,
logger: logger
)
let client = SQLiteClient(configuration: configuration)
let service = SQLiteDatabaseService(client)

try await testGracefulShutdown { trigger in
try await withThrowingTaskGroup { group in
let serviceGroup = ServiceGroup(
services: [service],
logger: logger
)
group.addTask { try await serviceGroup.run() }

trigger.triggerGracefulShutdown()

try await group.waitForAll()
}
}
}
}
#endif