116 lines
3.5 KiB
Swift
116 lines
3.5 KiB
Swift
import SQLKit
|
|
import Logging
|
|
import NIOCore
|
|
import Vapor
|
|
|
|
#if DEBUG
|
|
public class MockDatabase: @unchecked Sendable {
|
|
public let logger: Logger = Logger (label: "MockDatabase")
|
|
public let eventLoop: any EventLoop
|
|
public var queries = [any SQLExpression]()
|
|
public var results = [[any SQLRow]]()
|
|
|
|
public init (eventLoop: any EventLoop) {
|
|
self.eventLoop = eventLoop
|
|
}
|
|
|
|
func withConnection<T> (_ body: (any SQLDatabase) async throws -> T) async rethrows -> T {
|
|
return try await body (self)
|
|
}
|
|
|
|
public let dialect: any SQLDialect = Dialect()
|
|
|
|
struct Dialect: SQLDialect {
|
|
let name = "MockDatabase"
|
|
let identifierQuote: any SQLExpression = SQLRaw(#"""#)
|
|
let supportsAutoIncrement = false
|
|
let autoIncrementClause: any SQLExpression = SQLRaw("GENERATED BY DEFAULT AS IDENTITY")
|
|
|
|
func bindPlaceholder(at position: Int) -> any SQLExpression {
|
|
SQLRaw("$\(position)")
|
|
}
|
|
|
|
func literalBoolean(_ value: Bool) -> any SQLKit.SQLExpression {
|
|
SQLRaw("\(value)")
|
|
}
|
|
}
|
|
}
|
|
|
|
enum MockDatabaseErrors: Error {
|
|
case empty
|
|
}
|
|
|
|
extension MockDatabase: SQLDatabase {
|
|
|
|
public func execute (sql query: any SQLExpression, _ onRow: @escaping @Sendable (any SQLRow) -> ()) -> EventLoopFuture<Void> {
|
|
self.queries.append (query)
|
|
|
|
guard !results.isEmpty else {
|
|
return eventLoop.makeFailedFuture (MockDatabaseErrors.empty)
|
|
}
|
|
|
|
for try row in results.removeFirst() {
|
|
onRow (row)
|
|
}
|
|
|
|
return eventLoop.makeSucceededFuture(())
|
|
}
|
|
}
|
|
|
|
extension MockDatabase {
|
|
public struct Row: SQLRow {
|
|
public let values: [String: any Sendable]
|
|
|
|
public init(values: [String : any Sendable]) {
|
|
self.values = values
|
|
}
|
|
|
|
public var allColumns: [String] { values.keys.sorted() }
|
|
public func contains (column: String) -> Bool {
|
|
values.keys.contains (column)
|
|
}
|
|
public func decodeNil (column: String) throws -> Bool { !values.keys.contains (column) }
|
|
|
|
public func decode<D> (column: String, as: D.Type) throws -> D where D : Decodable {
|
|
guard let value = values[column] as? D else {
|
|
throw DecodingError.dataCorrupted (DecodingError.Context (codingPath: [], debugDescription: "Cannot decode \(D.self) from \(String (describing: self))"))
|
|
}
|
|
|
|
return value
|
|
}
|
|
}
|
|
}
|
|
|
|
extension Application {
|
|
|
|
protocol MockDatabaseConnectionKey: StorageKey where Value == MockDatabase {}
|
|
|
|
public struct MockDatabaseKey: MockDatabaseConnectionKey {
|
|
public typealias Value = MockDatabase
|
|
}
|
|
}
|
|
|
|
/// A wrapper around the errors that can occur during a transaction.
|
|
public struct TransactionError: Error {
|
|
|
|
/// The file in which the transaction was started
|
|
public var file: String
|
|
/// The line in which the transaction was started
|
|
public var line: Int
|
|
|
|
/// The error thrown when running the `BEGIN` query
|
|
public var beginError: (any Error)?
|
|
/// The error thrown in the transaction closure
|
|
public var closureError: (any Error)?
|
|
|
|
/// The error thrown while rolling the transaction back. If the ``closureError`` is set,
|
|
/// but the ``rollbackError`` is empty, the rollback was successful. If the ``rollbackError``
|
|
/// is set, the rollback failed.
|
|
public var rollbackError: (any Error)?
|
|
|
|
/// The error thrown while commiting the transaction.
|
|
public var commitError: (any Error)?
|
|
}
|
|
|
|
#endif
|