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 (_ 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 { 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 (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