Skip to content

Commit

Permalink
Pedantically avoid API breakage with SQLiteDatabase.sql() and make it…
Browse files Browse the repository at this point in the history
… @inlinable, fix a Sendability warning, add a missing doc comment.
  • Loading branch information
gwynne committed May 12, 2024
1 parent 631b856 commit 17b88c3
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 4 deletions.
75 changes: 71 additions & 4 deletions Sources/SQLiteKit/SQLiteConnection+SQLKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,64 @@ import SQLKit
import SQLiteNIO
import Logging

// Hint: Yes, I know what default arguments are. This ridiculous spelling out of each alternative avoids public API
// breakage from adding the defaults.

extension SQLiteDatabase {
/// Shorthand for ``sql(encoder:decoder:queryLogLevel:)``.
@inlinable
public func sql() -> any SQLDatabase {
self.sql(encoder: .init(), decoder: .init(), queryLogLevel: .debug)
}

/// Shorthand for ``sql(encoder:decoder:queryLogLevel:)``.
@inlinable
public func sql(encoder: SQLiteDataEncoder) -> any SQLDatabase {
self.sql(encoder: encoder, decoder: .init(), queryLogLevel: .debug)
}

/// Shorthand for ``sql(encoder:decoder:queryLogLevel:)``.
@inlinable
public func sql(decoder: SQLiteDataDecoder) -> any SQLDatabase {
self.sql(encoder: .init(), decoder: decoder, queryLogLevel: .debug)
}

/// Shorthand for ``sql(encoder:decoder:queryLogLevel:)``.
@inlinable
public func sql(encoder: SQLiteDataEncoder, decoder: SQLiteDataDecoder) -> any SQLDatabase {
self.sql(encoder: encoder, decoder: decoder, queryLogLevel: .debug)
}

/// Shorthand for ``sql(encoder:decoder:queryLogLevel:)``.
@inlinable
public func sql(queryLogLevel: Logger.Level?) -> any SQLDatabase {
self.sql(encoder: .init(), decoder: .init(), queryLogLevel: queryLogLevel)
}

/// Shorthand for ``sql(encoder:decoder:queryLogLevel:)``.
@inlinable
public func sql(encoder: SQLiteDataEncoder, queryLogLevel: Logger.Level?) -> any SQLDatabase {
self.sql(encoder: encoder, decoder: .init(), queryLogLevel: queryLogLevel)
}

/// Shorthand for ``sql(encoder:decoder:queryLogLevel:)``.
@inlinable
public func sql(decoder: SQLiteDataDecoder, queryLogLevel: Logger.Level?) -> any SQLDatabase {
self.sql(encoder: .init(), decoder: decoder, queryLogLevel: queryLogLevel)
}

/// Return an object allowing access to this database via the `SQLDatabase` interface.
///
/// - Parameters:
/// - encoder: An ``SQLiteDataEncoder`` used to translate bound query parameters into `SQLiteData` values.
/// - decoder: An ``SQLiteDataDecoder`` used to translate `SQLiteData` values into output values in `SQLRow`s.
/// - queryLogLevel: The level at which SQL queries issued through the SQLKit interface will be logged.
/// - Returns: An instance of `SQLDatabase` which accesses the same database as `self`.
@inlinable
public func sql(
encoder: SQLiteDataEncoder = .init(),
decoder: SQLiteDataDecoder = .init(),
queryLogLevel: Logger.Level? = .debug
encoder: SQLiteDataEncoder,
decoder: SQLiteDataDecoder,
queryLogLevel: Logger.Level?
) -> any SQLDatabase {
SQLiteSQLDatabase(database: self, encoder: encoder, decoder: decoder, queryLogLevel: queryLogLevel)
}
Expand Down Expand Up @@ -112,40 +159,58 @@ struct SQLiteDatabaseVersion: SQLDatabaseReportedVersion {
}

/// Wraps a `SQLiteDatabase` with the `SQLDatabase` protocol.
private struct SQLiteSQLDatabase<D: SQLiteDatabase>: SQLDatabase {
@usableFromInline
/*private*/ struct SQLiteSQLDatabase<D: SQLiteDatabase>: SQLDatabase {
/// The underlying database.
@usableFromInline
let database: D

/// An ``SQLiteDataEncoder`` used to translate bindings into `SQLiteData` values.
@usableFromInline
let encoder: SQLiteDataEncoder

/// An ``SQLiteDataDecoder`` used to translate `SQLiteData` values into output values in `SQLRow`s.
@usableFromInline
let decoder: SQLiteDataDecoder

// See `SQLDatabase.eventLoop`.
@usableFromInline
var eventLoop: any EventLoop {
self.database.eventLoop
}

// See `SQLDatabase.version`.
@usableFromInline
var version: (any SQLDatabaseReportedVersion)? {
SQLiteDatabaseVersion.runtimeVersion
}

// See `SQLDatabase.logger`.
@usableFromInline
var logger: Logger {
self.database.logger
}

// See `SQLDatabase.dialect`.
@usableFromInline
var dialect: any SQLDialect {
SQLiteDialect()
}

// See `SQLDatabase.queryLogLevel`.
@usableFromInline
let queryLogLevel: Logger.Level?

@inlinable
init(database: D, encoder: SQLiteDataEncoder, decoder: SQLiteDataDecoder, queryLogLevel: Logger.Level?) {
self.database = database
self.encoder = encoder
self.decoder = decoder
self.queryLogLevel = queryLogLevel
}

// See `SQLDatabase.execute(sql:_:)`.
@usableFromInline
func execute(
sql query: any SQLExpression,
_ onRow: @escaping @Sendable (any SQLRow) -> ()
Expand All @@ -171,6 +236,7 @@ private struct SQLiteSQLDatabase<D: SQLiteDatabase>: SQLDatabase {
}

// See `SQLDatabase.execute(sql:_:)`.
@usableFromInline
func execute(
sql query: any SQLExpression,
_ onRow: @escaping @Sendable (any SQLRow) -> ()
Expand All @@ -189,6 +255,7 @@ private struct SQLiteSQLDatabase<D: SQLiteDatabase>: SQLDatabase {
}

// See `SQLDatabase.withSession(_:)`.
@usableFromInline
func withSession<R>(_ closure: @escaping @Sendable (any SQLDatabase) async throws -> R) async throws -> R {
try await self.database.withConnection {
try await closure($0.sql(encoder: self.encoder, decoder: self.decoder, queryLogLevel: self.queryLogLevel))
Expand Down
4 changes: 4 additions & 0 deletions Sources/SQLiteKit/SQLiteConnectionSource.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#if canImport(Darwin)
import Foundation
#else
@preconcurrency import Foundation
#endif
import Logging
import AsyncKit
import NIOPosix
Expand Down
4 changes: 4 additions & 0 deletions Sources/SQLiteKit/SQLiteDataEncoder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ public struct SQLiteDataEncoder: Sendable {
/// The `JSONEncoder` used for encoding values that can't be directly converted.
let json: FakeSendable<JSONEncoder>

/// Initialize a ``SQLiteDataEncoder`` with a JSON encoder.
///
/// - Parameter json: A `JSONEncoder` to use for encoding types that can't be directly converted. Defaults
/// to an unconfigured encoder.
public init(json: JSONEncoder = .init()) {
self.json = .init(value: json)
}
Expand Down

0 comments on commit 17b88c3

Please sign in to comment.