From d36e5fd9a281c2d6e856b0bceca8c2a1c8ffafd6 Mon Sep 17 00:00:00 2001 From: Mihael Isaev Date: Tue, 19 May 2020 15:24:47 +0400 Subject: [PATCH] Implement `Table.query(on:)` --- Sources/Bridges/BridgesRow.swift | 47 ++++++++ .../Extensions/Table+Conveniences.swift | 105 +++++++++++++++++- .../Protocols/AnyDatabaseIdentifiable.swift | 1 + 3 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 Sources/Bridges/BridgesRow.swift diff --git a/Sources/Bridges/BridgesRow.swift b/Sources/Bridges/BridgesRow.swift new file mode 100644 index 0000000..eb540e6 --- /dev/null +++ b/Sources/Bridges/BridgesRow.swift @@ -0,0 +1,47 @@ +// +// BridgesRow.swift +// Bridges +// +// Created by Mihael Isaev on 18.05.2020. +// + +import Foundation + +public protocol BridgesRow { + func decode(model type: D.Type) throws -> D where D: Decodable + func decode(model type: D.Type, prefix: String?) throws -> D where D: Decodable +} + +extension BridgesRow { + public func decode(model type: D.Type) throws -> D where D : Decodable { + try decode(model: type, prefix: nil) + } +} + +public protocol BridgesRows { + var rows: [BridgesRow] { get } +} + +extension BridgesRows { + public func first(as type: R.Type) throws -> R? where R: Decodable { + try rows.first?.decode(model: type) + } + + public func all(as type: R.Type) throws -> [R] where R: Decodable { + try rows.map { try $0.decode(model: type) } + } +} + +extension EventLoopFuture where Value: BridgesRows { + public func first(decoding type: R.Type) -> EventLoopFuture where R: Decodable { + flatMapThrowing { try $0.first(as: type) } + } + + public func all(decoding type: R.Type) -> EventLoopFuture<[R]> where R: Decodable { + flatMapThrowing { try $0.all(as: type) } + } +} + +extension Array: BridgesRows where Element: BridgesRow { + public var rows: [BridgesRow] { self } +} diff --git a/Sources/Bridges/Extensions/Table+Conveniences.swift b/Sources/Bridges/Extensions/Table+Conveniences.swift index 54697e6..4071049 100644 --- a/Sources/Bridges/Extensions/Table+Conveniences.swift +++ b/Sources/Bridges/Extensions/Table+Conveniences.swift @@ -10,7 +10,7 @@ import NIO import SwifQL extension Table { - private static func error(_ logger: Logger) { + fileprivate static func error(_ logger: Logger) { logger.error(.init(stringLiteral: "Query doesn't work with non-generic database identifier. Please initialize AnyDatabaseIdentifier as MySQL or Postgres explicitly.")) } @@ -29,4 +29,107 @@ extension Table { } return db.first(Self.self, on: on) } + + public static func query(_ db: DatabaseIdentifier, on container: AnyBridgesObject) -> TableQuerySingle { + .init(db: db, container: container) + } + + public static func query(on conn: BridgeConnection) -> TableQueryOnConn { + .init(conn) + } +} + +struct CountResult: Aliasable { + @Alias("count") + var count: Int64 +} + +public class TableQuerySingle: QueryBuilderable { + let db: AnyDatabaseIdentifiable + let container: AnyBridgesObject + + public var queryParts = QueryParts() + + private init (db: AnyDatabaseIdentifiable, container: AnyBridgesObject) { + self.db = db + self.container = container + } + + init (db: DatabaseIdentifier, container: AnyBridgesObject) { + guard let db = db as? AnyDatabaseIdentifiable else { + T.error(container.logger) + fatalError() + } + self.db = db + self.container = container + } + + public func copy() -> TableQuerySingle { + let copy = TableQuerySingle(db: db, container: container) + + copy.queryParts = queryParts.copy() + + return copy + } + + public func all() -> EventLoopFuture<[T]> { + let query = SwifQL.select(T.table.*).from(T.table) + return db.query(queryParts.appended(to: query), on: container) + .flatMapThrowing { try $0.map { try $0.decode(model: T.self) } } + } + + public func count() -> EventLoopFuture { + let query = SwifQL.select(Fn.count(T.table.*) => \CountResult.$count).from(T.table) + return db.query(queryParts.appended(to: query), on: container) + .flatMapThrowing { try $0.first?.decode(model: CountResult.self).count ?? 0 } + } + + public func first() -> EventLoopFuture { + let query = SwifQL.select(T.table.*).from(T.table) + return db.query(queryParts.appended(to: query), on: container) + .flatMapThrowing { try $0.first?.decode(model: T.self) } + } + + public func delete() -> EventLoopFuture { + let query = SwifQL.delete(from: T.table) + return db.query(queryParts.appended(to: query), on: container).transform(to: ()) + } +} + +public class TableQueryOnConn: QueryBuilderable { + let conn: BridgeConnection + + public var queryParts = QueryParts() + + init (_ conn: BridgeConnection) { + self.conn = conn + } + + public func copy() -> TableQueryOnConn { + let copy = TableQueryOnConn(conn) + + copy.queryParts = queryParts.copy() + + return copy + } + + public func all() -> EventLoopFuture<[T]> { + let query = SwifQL.select(T.table.*).from(T.table) + return conn.query(sql: queryParts.appended(to: query), decoding: T.self) + } + + public func count() -> EventLoopFuture { + let query = SwifQL.select(Fn.count(T.table.*) => \CountResult.$count).from(T.table) + return conn.query(sql: queryParts.appended(to: query), decoding: CountResult.self).map { $0.first?.count ?? 0 } + } + + public func first() -> EventLoopFuture { + let query = SwifQL.select(T.table.*).from(T.table) + return conn.query(sql: queryParts.appended(to: query), decoding: T.self).map { $0.first } + } + + public func delete() -> EventLoopFuture { + let query = SwifQL.delete(from: T.table) + return conn.query(sql: queryParts.appended(to: query), decoding: T.self).transform(to: ()) + } } diff --git a/Sources/Bridges/Protocols/AnyDatabaseIdentifiable.swift b/Sources/Bridges/Protocols/AnyDatabaseIdentifiable.swift index c79d879..cde16c7 100644 --- a/Sources/Bridges/Protocols/AnyDatabaseIdentifiable.swift +++ b/Sources/Bridges/Protocols/AnyDatabaseIdentifiable.swift @@ -11,6 +11,7 @@ import NIO public protocol AnyDatabaseIdentifiable { func all(_ table: T.Type, on bridges: AnyBridgesObject) -> EventLoopFuture<[T]> where T: Table func first(_ table: T.Type, on bridges: AnyBridgesObject) -> EventLoopFuture where T: Table + func query(_ query: SwifQLable, on bridges: AnyBridgesObject) -> EventLoopFuture<[BridgesRow]> } public protocol AnyMySQLDatabaseIdentifiable: AnyDatabaseIdentifiable {} public protocol AnyPostgresDatabaseIdentifiable: AnyDatabaseIdentifiable {}