From b8e882e480253556f4ca382541c6647aaa6baded Mon Sep 17 00:00:00 2001 From: OleH Date: Mon, 29 Jan 2024 16:08:48 +0100 Subject: [PATCH] Add async/await #19 by @nerzh --- Package.swift | 2 +- Sources/Bridges/Bridges.swift | 4 + Sources/Bridges/BridgesRow.swift | 8 + .../Bridges/Builders/UpdateEnumBuilder.swift | 6 + Sources/Bridges/Connection.swift | 5 + Sources/Bridges/DatabaseMigrations.swift | 99 ++- .../Bridges/Extensions/Array+Extensions.swift | 21 + .../Extensions/Bridgeable+Transaction.swift | 21 +- .../EventLoopFuture+SyncFlatten.swift | 12 +- .../Extensions/SwifQLable+Execute.swift | 30 + .../Extensions/Table+Conveniences.swift | 66 ++ Sources/Bridges/Helpers/TableDelete.swift | 42 ++ Sources/Bridges/Helpers/TableInsert.swift | 106 ++++ Sources/Bridges/Helpers/TableUpdate.swift | 299 +++++++++ Sources/Bridges/Helpers/TableUpsert.swift | 574 ++++++++++++++++++ Sources/Bridges/Protocols/AnyBridge.swift | 2 +- .../Protocols/AnyDatabaseIdentifiable.swift | 4 + Sources/Bridges/Protocols/AnyMigration.swift | 10 + Sources/Bridges/Protocols/Bridgeable.swift | 18 +- Sources/Bridges/Protocols/Migration.swift | 11 + 20 files changed, 1326 insertions(+), 14 deletions(-) create mode 100644 Sources/Bridges/Extensions/Array+Extensions.swift diff --git a/Package.swift b/Package.swift index f24f66d..cf3497e 100644 --- a/Package.swift +++ b/Package.swift @@ -45,7 +45,7 @@ deps.append("https://github.com/apple/swift-log.git", from: "1.0.0", targets: .p if localDev { deps.appendLocal("SwifQL", targets: "SwifQL") } else { - deps.append("https://github.com/SwifQL/SwifQL.git", from: "2.0.0-beta", targets: .product(name: "SwifQL", package: "SwifQL")) + deps.append("https://github.com/nerzh/SwifQL.git", .branchItem("master"), targets: .product(name: "SwifQL", package: "SwifQL")) } // MARK: - Package diff --git a/Sources/Bridges/Bridges.swift b/Sources/Bridges/Bridges.swift index 186831e..8ae9912 100644 --- a/Sources/Bridges/Bridges.swift +++ b/Sources/Bridges/Bridges.swift @@ -42,6 +42,10 @@ extension SwifQLable { public func execute(on conn: BridgeConnection) -> EventLoopFuture { conn.query(raw: prepare(conn.dialect).plain).transform(to: ()) } + + public func execute(on conn: BridgeConnection) async throws { + try await conn.query(raw: prepare(conn.dialect).plain) + } } public protocol SQLRow { diff --git a/Sources/Bridges/BridgesRow.swift b/Sources/Bridges/BridgesRow.swift index eb540e6..a131e22 100644 --- a/Sources/Bridges/BridgesRow.swift +++ b/Sources/Bridges/BridgesRow.swift @@ -40,6 +40,14 @@ extension EventLoopFuture where Value: BridgesRows { public func all(decoding type: R.Type) -> EventLoopFuture<[R]> where R: Decodable { flatMapThrowing { try $0.all(as: type) } } + + public func first(decoding type: R.Type) async throws -> R? where R: Decodable { + try await get().first(as: type) + } + + public func all(decoding type: R.Type) async throws -> [R] where R: Decodable { + try await get().all(as: type) + } } extension Array: BridgesRows where Element: BridgesRow { diff --git a/Sources/Bridges/Builders/UpdateEnumBuilder.swift b/Sources/Bridges/Builders/UpdateEnumBuilder.swift index 5b55431..ac3a185 100644 --- a/Sources/Bridges/Builders/UpdateEnumBuilder.swift +++ b/Sources/Bridges/Builders/UpdateEnumBuilder.swift @@ -32,4 +32,10 @@ public class UpdateEnumBuilder where Enum.RawValue == String { action.execute(on: conn) } }.flatten(on: conn.eventLoop) } + + public func execute(on conn: BridgeConnection) async throws { + try await actions.map { action in + { try await action.execute(on: conn) } + }.flatten() + } } diff --git a/Sources/Bridges/Connection.swift b/Sources/Bridges/Connection.swift index c039e74..5a28a9a 100644 --- a/Sources/Bridges/Connection.swift +++ b/Sources/Bridges/Connection.swift @@ -19,4 +19,9 @@ public protocol BridgeConnection { func query(sql: SwifQLable) -> EventLoopFuture func query(raw: String, decoding type: V.Type) -> EventLoopFuture<[V]> func query(sql: SwifQLable, decoding type: V.Type) -> EventLoopFuture<[V]> + + func query(raw: String) async throws + func query(sql: SwifQLable) async throws + func query(raw: String, decoding type: V.Type) async throws -> [V] + func query(sql: SwifQLable, decoding type: V.Type) async throws -> [V] } diff --git a/Sources/Bridges/DatabaseMigrations.swift b/Sources/Bridges/DatabaseMigrations.swift index 47efe49..b830737 100644 --- a/Sources/Bridges/DatabaseMigrations.swift +++ b/Sources/Bridges/DatabaseMigrations.swift @@ -17,6 +17,10 @@ public protocol Migrator { func migrate() -> EventLoopFuture func revertLast() -> EventLoopFuture func revertAll() -> EventLoopFuture + + func migrate() async throws + func revertLast() async throws + func revertAll() async throws } public class BridgeDatabaseMigrations: Migrator { @@ -53,9 +57,17 @@ public class BridgeDatabaseMigrations: Migrator { createBuilder.checkIfNotExists().execute(on: conn) } + static func prepare(on conn: BridgeConnection) async throws { + try await createBuilder.checkIfNotExists().execute(on: conn) + } + static func revert(on conn: BridgeConnection) -> EventLoopFuture { dropBuilder.execute(on: conn) } + + static func revert(on conn: BridgeConnection) async throws { + try await dropBuilder.execute(on: conn) + } } } @@ -83,9 +95,22 @@ public class BridgeDatabaseMigrations: Migrator { .execute(on: conn) } + static func prepare(on conn: BridgeConnection) async throws { + try await createBuilder + .checkIfNotExists() + .column(\.$id, .bigserial, .primaryKey) + .column(\.$name, .text, .unique) + .column(\.$batch, .int) + .execute(on: conn) + } + static func revert(on conn: BridgeConnection) -> EventLoopFuture { dropBuilder.checkIfExists().execute(on: conn) } + + static func revert(on conn: BridgeConnection) async throws { + try await dropBuilder.checkIfExists().execute(on: conn) + } } } @@ -96,8 +121,8 @@ public class BridgeDatabaseMigrations: Migrator { return bridge.transaction(to: db, on: bridge.eventLoopGroup.next()) { conn in conn.eventLoop.future().flatMap { self.dedicatedSchema - ? BridgesSchema.Create.prepare(on: conn) - : conn.eventLoop.future() + ? BridgesSchema.Create.prepare(on: conn) + : conn.eventLoop.future() }.flatMap { CreateTableBuilder(schema: self.schemaName) .checkIfNotExists() @@ -126,7 +151,7 @@ public class BridgeDatabaseMigrations: Migrator { } } } - + public func revertLast() -> EventLoopFuture { bridge.transaction(to: db, on: bridge.eventLoopGroup.next()) { self._revertLast(on: $0).transform(to: ()) @@ -137,7 +162,7 @@ public class BridgeDatabaseMigrations: Migrator { let query = SwifQL.select(self.m.table.*).from(self.m.table).prepare(conn.dialect).plain return conn.query(raw: query, decoding: Migrations.self).flatMap { completedMigrations in guard let lastBatch = completedMigrations.map({ $0.batch }).max() - else { return conn.eventLoop.future(false) } + else { return conn.eventLoop.future(false) } let migrationsToRevert = completedMigrations.filter { $0.batch == lastBatch } var migrations = self.migrations migrations.removeAll { m in migrationsToRevert.contains { $0.name != m.migrationName } } @@ -175,6 +200,72 @@ public class BridgeDatabaseMigrations: Migrator { return promise.futureResult } } + + ///ASYNC + public func migrate() async throws { + return try await bridge.transaction(to: db, on: bridge.eventLoopGroup.next()) { conn in + + if self.dedicatedSchema { + try await BridgesSchema.Create.prepare(on: conn) + } + try await CreateTableBuilder(schema: self.schemaName) + .checkIfNotExists() + .column(\.$id, .bigserial, .primaryKey) + .column(\.$name, .text, .unique) + .column(\.$batch, .int) + .execute(on: conn) + + let query = SwifQL.select(self.m.table.*).from(self.m.table).prepare(conn.dialect).plain + let completedMigrations = try await conn.query(raw: query, decoding: Migrations.self) + let batch = completedMigrations.map { $0.batch }.max() ?? 0 + var migrations = self.migrations + migrations.removeAll { m in completedMigrations.contains { $0.name == m.migrationName } } + for migration in migrations { + try await migration.prepare(on: conn) + try await SwifQL + .insertInto(self.m.table, fields: self.m.$name, self.m.$batch) + .values + .values(migration.migrationName, batch + 1) + .execute(on: conn) + } + } + } + + public func revertLast() async throws { + _ = try await bridge.transaction(to: db, on: bridge.eventLoopGroup.next()) { + try await self._revertLast(on: $0) + } + } + + private func _revertLast(on conn: BridgeConnection) async throws -> Bool { + let query = SwifQL.select(self.m.table.*).from(self.m.table).prepare(conn.dialect).plain + let completedMigrations = try await conn.query(raw: query, decoding: Migrations.self) + + guard let lastBatch = completedMigrations.map({ $0.batch }).max() else { return false } + let migrationsToRevert = completedMigrations.filter { $0.batch == lastBatch } + var migrations = self.migrations + migrations.removeAll { m in migrationsToRevert.contains { $0.name != m.migrationName } } + for migration in migrations { + try await migration.revert(on: conn) + try await SwifQL + .delete(from: self.m.table) + .where(self.m.$name == migration.migrationName) + .execute(on: conn) + } + return true + } + + public func revertAll() async throws { + try await bridge.transaction(to: db, on: bridge.eventLoopGroup.next()) { conn in + func revert() async throws { + let reverted = try await self._revertLast(on: conn) + if reverted { + try await revert() + } + } + try await revert() + } + } } // TODO: implement migration lock diff --git a/Sources/Bridges/Extensions/Array+Extensions.swift b/Sources/Bridges/Extensions/Array+Extensions.swift new file mode 100644 index 0000000..4d6d6f7 --- /dev/null +++ b/Sources/Bridges/Extensions/Array+Extensions.swift @@ -0,0 +1,21 @@ +// +// File.swift +// +// +// Created by Oleh Hudeichuk on 02.04.2023. +// + +import Foundation + +public extension Array { + + func map(_ handler: @Sendable @escaping (Element) async throws -> T) async throws -> [T] where Element == BridgesRow { + try await Task { + var result: [T] = .init() + for item in self { + result.append(try await handler(item)) + } + return result + }.value + } +} diff --git a/Sources/Bridges/Extensions/Bridgeable+Transaction.swift b/Sources/Bridges/Extensions/Bridgeable+Transaction.swift index d76efc8..fa9518c 100644 --- a/Sources/Bridges/Extensions/Bridgeable+Transaction.swift +++ b/Sources/Bridges/Extensions/Bridgeable+Transaction.swift @@ -12,8 +12,8 @@ import SwifQL extension Bridgeable { public func transaction(to db: DatabaseIdentifier, - on eventLoop: EventLoop, - _ closure: @escaping (Connection) -> EventLoopFuture) -> EventLoopFuture { + on eventLoop: EventLoop, + _ closure: @escaping (Connection) -> EventLoopFuture) -> EventLoopFuture { connection(to: db, on: eventLoop) { conn in conn.query(raw: SwifQL.begin.semicolon.prepare().plain).transform(to: conn).flatMap { conn in closure(conn).flatMapError { error in @@ -27,6 +27,23 @@ extension Bridgeable { } } + public func transaction(to db: DatabaseIdentifier, + on eventLoop: EventLoop, + _ closure: @escaping (Connection) async throws -> T + ) async throws -> T { + try await connection(to: db, on: eventLoop) { conn in + try await conn.query(raw: SwifQL.begin.semicolon.prepare().plain) + do { + let result = try await closure(conn) + try await conn.query(raw: SwifQL.commit.semicolon.prepare().plain) + return result + } catch { + try await conn.query(raw: SwifQL.rollback.semicolon.prepare().plain) + throw error + } + } + } + public func shutdown() { pools.values.forEach { $0.shutdown() } } diff --git a/Sources/Bridges/Extensions/EventLoopFuture+SyncFlatten.swift b/Sources/Bridges/Extensions/EventLoopFuture+SyncFlatten.swift index f98dd76..b92d927 100644 --- a/Sources/Bridges/Extensions/EventLoopFuture+SyncFlatten.swift +++ b/Sources/Bridges/Extensions/EventLoopFuture+SyncFlatten.swift @@ -7,7 +7,7 @@ import Foundation -extension Array where Element == (() -> EventLoopFuture) { +public extension Array where Element == (() -> EventLoopFuture) { func flatten(on eventLoop: EventLoop) -> EventLoopFuture { let promise = eventLoop.makePromise(of: Void.self) @@ -36,3 +36,13 @@ extension Array where Element == (() -> EventLoopFuture) { return promise.futureResult } } + +public extension Array where Element == (() async throws -> Void) { + + func flatten() async throws { + var iterator = self.makeIterator() + while let item = iterator.next() { + try await item() + } + } +} diff --git a/Sources/Bridges/Extensions/SwifQLable+Execute.swift b/Sources/Bridges/Extensions/SwifQLable+Execute.swift index 3b87e61..5bd9823 100644 --- a/Sources/Bridges/Extensions/SwifQLable+Execute.swift +++ b/Sources/Bridges/Extensions/SwifQLable+Execute.swift @@ -47,6 +47,27 @@ extension EventLoopFuture where Value == BridgesExecutedResult { public func count() -> EventLoopFuture { map { .init($0.rows.count) } } + + public func all() async throws -> [T] where T: Decodable { + try await all(decoding: T.self) + } + + + public func all(decoding: T.Type) async throws -> [T] where T: Decodable { + try await get().rows.map { try $0.decode(model: T.self) } + } + + public func first() async throws -> T? where T: Table { + try await first(decoding: T.self) + } + + public func first(decoding: T.Type) async throws -> T? where T: Decodable { + try await get().rows.first?.decode(model: T.self) + } + + public func count() async throws -> Int64 { + try await Int64(get().rows.count) + } } extension SwifQLable { @@ -57,6 +78,15 @@ extension SwifQLable { } return db.query(self, on: container).map { .init(db: db, container: container, rows: $0) } } + + public func execute(on db: DatabaseIdentifier, on container: AnyBridgesObject) async throws -> BridgesExecutedResult { + guard let db = db as? AnyDatabaseIdentifiable else { + error(container.logger) + throw BridgesError.nonGenericDatabaseIdentifier + } + let result = try await db.query(self, on: container) + return .init(db: db, container: container, rows: result) + } } fileprivate func error(_ logger: Logger) { diff --git a/Sources/Bridges/Extensions/Table+Conveniences.swift b/Sources/Bridges/Extensions/Table+Conveniences.swift index 8cffaea..4bf4a3e 100644 --- a/Sources/Bridges/Extensions/Table+Conveniences.swift +++ b/Sources/Bridges/Extensions/Table+Conveniences.swift @@ -30,6 +30,22 @@ extension Table { return db.first(Self.self, on: on) } + public static func all(on db: DatabaseIdentifier, on: AnyBridgesObject) async throws -> [Self] { + guard let db = db as? AnyDatabaseIdentifiable else { + error(on.logger) + throw BridgesError.nonGenericDatabaseIdentifier + } + return try await db.all(Self.self, on: on) + } + + public static func first(on db: DatabaseIdentifier, on: AnyBridgesObject) async throws -> Self? { + guard let db = db as? AnyDatabaseIdentifiable else { + error(on.logger) + throw BridgesError.nonGenericDatabaseIdentifier + } + return try await db.first(Self.self, on: on) + } + public static func query(on db: DatabaseIdentifier, on container: AnyBridgesObject) -> TableQuerySingle { .init(db: db, container: container) } @@ -106,6 +122,36 @@ public class TableQuerySingle: QueryBuilderable { let query = SwifQL.delete(from: T.table) return db.query(queryParts.appended(to: query), on: container).transform(to: ()) } + + public func all() async throws -> [T] { + let query = SwifQL.select(T.table.*).from(T.table) + return try await db.query(queryParts.appended(to: query), on: container).map { try $0.decode(model: T.self) } + } + + public func all(decoding: CT.Type) async throws -> [CT] where CT: Decodable { + let query = SwifQL.select(T.table.*).from(T.table) + return try await db.query(queryParts.appended(to: query), on: container).map { try $0.decode(model: CT.self) } + } + + public func count() async throws -> Int64 { + let query = SwifQL.select(Fn.count(T.table.*) => \CountResult.$count).from(T.table) + return try await db.query(queryParts.appended(to: query), on: container).first?.decode(model: CountResult.self).count ?? 0 + } + + public func first() async throws -> T? { + let query = SwifQL.select(T.table.*).from(T.table) + return try await db.query(queryParts.appended(to: query), on: container).first?.decode(model: T.self) + } + + public func first(decoding: CT.Type) async throws -> CT? where CT: Decodable { + let query = SwifQL.select(T.table.*).from(T.table) + return try await db.query(queryParts.appended(to: query), on: container).first?.decode(model: CT.self) + } + + public func delete() async throws { + let query = SwifQL.delete(from: T.table) + _ = try await db.query(queryParts.appended(to: query), on: container) + } } public class TableQueryOnConn: QueryBuilderable { @@ -144,4 +190,24 @@ public class TableQueryOnConn: QueryBuilderable { let query = SwifQL.delete(from: T.table) return conn.query(sql: queryParts.appended(to: query), decoding: T.self).transform(to: ()) } + + public func all() async throws -> [T] { + let query = SwifQL.select(T.table.*).from(T.table) + return try await conn.query(sql: queryParts.appended(to: query), decoding: T.self) + } + + public func count() async throws -> Int64 { + let query = SwifQL.select(Fn.count(T.table.*) => \CountResult.$count).from(T.table) + return try await conn.query(sql: queryParts.appended(to: query), decoding: CountResult.self).get().first?.count ?? 0 + } + + public func first() async throws -> T? { + let query = SwifQL.select(T.table.*).from(T.table) + return try await conn.query(sql: queryParts.appended(to: query), decoding: T.self).get().first + } + + public func delete() async throws { + let query = SwifQL.delete(from: T.table) + _ = try await conn.query(sql: queryParts.appended(to: query), decoding: T.self) + } } diff --git a/Sources/Bridges/Helpers/TableDelete.swift b/Sources/Bridges/Helpers/TableDelete.swift index 0b3fa56..db694f1 100644 --- a/Sources/Bridges/Helpers/TableDelete.swift +++ b/Sources/Bridges/Helpers/TableDelete.swift @@ -61,4 +61,46 @@ extension Table { let query = buildDeleteQuery(items: items.0, where: items.1 == items.2, returning: false) return conn.query(sql: query) } + + public func deleteNonReturning( + on keyColumn: KeyPath, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws { + guard let items = allColumns(excluding: keyColumn, logger: container.logger) else { + throw BridgesError.valueIsNilInKeyColumnUpdateIsImpossible + } + _ = try await buildDeleteQuery(items: items.0, where: items.1 == items.2, returning: false) + .execute(on: db, on: container) + } + + public func delete( + on keyColumn: KeyPath, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws -> Self { + guard let items = allColumns(excluding: keyColumn, logger: container.logger) else { + throw BridgesError.valueIsNilInKeyColumnUpdateIsImpossible + } + guard let first = try await buildDeleteQuery(items: items.0, where: items.1 == items.2, returning: true) + .execute(on: db, on: container) + .all(decoding: Self.self).first + else { + throw BridgesError.failedToDecodeWithReturning + } + return first + } + + // MARK: On connection + + public func delete( + on keyColumn: KeyPath, + on conn: BridgeConnection + ) async throws { + guard let items = allColumns(excluding: keyColumn, logger: conn.logger) else { + throw BridgesError.valueIsNilInKeyColumnUpdateIsImpossible + } + let query = buildDeleteQuery(items: items.0, where: items.1 == items.2, returning: false) + return try await conn.query(sql: query) + } } diff --git a/Sources/Bridges/Helpers/TableInsert.swift b/Sources/Bridges/Helpers/TableInsert.swift index 82c031f..0cccc2d 100644 --- a/Sources/Bridges/Helpers/TableInsert.swift +++ b/Sources/Bridges/Helpers/TableInsert.swift @@ -117,6 +117,100 @@ extension Table { return row } } + + public func insertNonReturning( + inSchema schema: Schemable.Type? = nil, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws { + try await _insertNonReturning(schema: schema?.schemaName ?? (Self.self as? Schemable.Type)?.schemaName, on: db, on: container) + } + + public func insert( + inSchema schema: Schemable.Type? = nil, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws -> EventLoopFuture { + _insert(schema: schema?.schemaName ?? (Self.self as? Schemable.Type)?.schemaName, on: db, on: container) + } + + /// + + public func insertNonReturning( + inSchema schema: String, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws -> EventLoopFuture { + _insertNonReturning(schema: schema, on: db, on: container) + } + + public func insert( + inSchema schema: String, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws -> EventLoopFuture { + _insert(schema: schema, on: db, on: container) + } + + + private func _insertNonReturning( + schema: String?, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws { + _ = try await buildInsertQuery(schema: schema, items: allColumns(logger: container.logger), returning: false) + .execute(on: db, on: container) + } + + private func _insert( + schema: String?, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws -> Self { + guard let first = try await buildInsertQuery(schema: schema, items: allColumns(logger: container.logger), returning: true) + .execute(on: db, on: container) + .all(decoding: Self.self).first + else { + throw BridgesError.failedToDecodeWithReturning + } + return first + } + + // MARK: On connection + + public func insertNonReturning(inSchema schema: Schemable.Type? = nil, on conn: BridgeConnection) async throws { + try await _insertNonReturning(schema: schema?.schemaName ?? (Self.self as? Schemable.Type)?.schemaName, on: conn) + } + + public func insert(inSchema schema: Schemable.Type? = nil, on conn: BridgeConnection) async throws -> Self { + try await _insert(schema: schema?.schemaName ?? (Self.self as? Schemable.Type)?.schemaName, on: conn) + } + + /// + + public func insertNonReturning(inSchema schema: String, on conn: BridgeConnection) async throws { + try await _insertNonReturning(schema: schema, on: conn) + } + + public func insert(inSchema schema: String, on conn: BridgeConnection) async throws -> Self { + try await _insert(schema: schema, on: conn) + } + + /// + + private func _insertNonReturning(schema: String?, on conn: BridgeConnection) async throws { + let query = buildInsertQuery(schema: schema, items: allColumns(logger: conn.logger), returning: false) + try await conn.query(sql: query) + } + + private func _insert(schema: String?, on conn: BridgeConnection) async throws -> Self { + let query = buildInsertQuery(schema: schema, items: allColumns(logger: conn.logger), returning: true) + guard let first = try await conn.query(sql: query, decoding: Self.self).first + else { + throw BridgesError.failedToDecodeWithReturning + } + return first + } } // MARK: Batch Insert @@ -132,6 +226,18 @@ extension Array where Element: Table { return conn.query(sql: batchInsertQuery(schema: schema)) } + public func batchInsert(inSchema schema: Schemable.Type? = nil, on conn: BridgeConnection) async throws { + if count > 0 { + try await conn.query(sql: batchInsertQuery(schema: schema?.schemaName ?? (Element.self as? Schemable.Type)?.schemaName)) + } + } + + public func batchInsert(schema: String, on conn: BridgeConnection) async throws { + if count > 0 { + try await conn.query(sql: batchInsertQuery(schema: schema)) + } + } + private func batchInsertQuery(schema: String?) -> SwifQLable { var data: [String: [SwifQLable]] = [:] self.forEach { table in diff --git a/Sources/Bridges/Helpers/TableUpdate.swift b/Sources/Bridges/Helpers/TableUpdate.swift index 07d9aaf..969be10 100644 --- a/Sources/Bridges/Helpers/TableUpdate.swift +++ b/Sources/Bridges/Helpers/TableUpdate.swift @@ -335,6 +335,305 @@ extension Table { public func update(on conn: BridgeConnection, where predicates: SwifQLable) -> EventLoopFuture { conn.query(sql: buildUpdateQuery(items: allColumns(logger: conn.logger), where: predicates, returning: false)) } + + /// ASYNC + public func updateNonReturning( + on keyColumn: KeyPath, + on db: DatabaseIdentifier, + on container: AnyBridgesObject, + preActions: @escaping @Sendable () async throws -> Void + ) async throws { + try await preActions() + try await updateNonReturning(on: keyColumn, on: db, on: container) + } + + public func update( + on keyColumn: KeyPath, + on db: DatabaseIdentifier, + on container: AnyBridgesObject, + preActions: @escaping @Sendable () async throws -> Void + ) async throws -> Self { + try await preActions() + return try await update(on: keyColumn, on: db, on: container) + } + + /// + + public func updateNonReturning( + on keyColumn: KeyPath, + on db: DatabaseIdentifier, + on container: AnyBridgesObject, + preActions: @escaping @Sendable (Self) async throws -> Void + ) async throws { + try await preActions(self) + try await updateNonReturning(on: keyColumn, on: db, on: container) + } + + public func update( + on keyColumn: KeyPath, + on db: DatabaseIdentifier, + on container: AnyBridgesObject, + preActions: @escaping @Sendable (Self) async throws -> Void + ) async throws -> Self { + try await preActions(self) + return try await update(on: keyColumn, on: db, on: container) + } + + /// + + public func updateNonReturning( + on keyColumn: KeyPath, + on db: DatabaseIdentifier, + on container: AnyBridgesObject, + preActions: @Sendable () async throws -> T + ) async throws { + _ = try await preActions() + try await updateNonReturning(on: keyColumn, on: db, on: container) + } + + public func update( + on keyColumn: KeyPath, + on db: DatabaseIdentifier, + on container: AnyBridgesObject, + preActions: @Sendable () async throws -> T + ) async throws -> Self { + _ = try await preActions() + return try await update(on: keyColumn, on: db, on: container) + } + + /// + + public func updateNonReturning( + on keyColumn: KeyPath, + on db: DatabaseIdentifier, + on container: AnyBridgesObject, + preActions: @Sendable (Self) async throws -> T + ) async throws { + _ = try await preActions(self) + try await updateNonReturning(on: keyColumn, on: db, on: container) + } + + public func update( + on keyColumn: KeyPath, + on db: DatabaseIdentifier, + on container: AnyBridgesObject, + preActions: @Sendable (Self) async throws -> T + ) async throws -> Self { + _ = try await preActions(self) + return try await update(on: keyColumn, on: db, on: container) + } + + /// + + public func updateNonReturning( + on keyColumn: KeyPath, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws { + guard let items = allColumns(excluding: keyColumn, logger: container.logger) else { + throw BridgesError.valueIsNilInKeyColumnUpdateIsImpossible + } + guard items.0.count > 0 else { + container.logger.debug("\(Self.tableName) update has been skipped cause nothing to update") + return + } + _ = try await buildUpdateQuery(items: items.0, where: items.1 == items.2, returning: false) + .execute(on: db, on: container) + } + + public func update( + on keyColumn: KeyPath, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws -> Self { + guard let items = allColumns(excluding: keyColumn, logger: container.logger) else { + throw BridgesError.valueIsNilInKeyColumnUpdateIsImpossible + } + guard items.0.count > 0 else { + container.logger.debug("\(Self.tableName) update has been skipped cause nothing to update") + return self + } + guard let first = try await buildUpdateQuery(items: items.0, where: items.1 == items.2, returning: true) + .execute(on: db, on: container) + .all(decoding: Self.self).first + else { + throw BridgesError.failedToDecodeWithReturning + } + return first + } + + /// + + public func updateNonReturning( + on db: DatabaseIdentifier, + on container: AnyBridgesObject, + where predicates: SwifQLable + ) async throws { + let items = allColumns(logger: container.logger) + guard items.count > 0 else { + container.logger.debug("\(Self.tableName) update has been skipped cause nothing to update") + return + } + _ = try await buildUpdateQuery(items: items, where: predicates, returning: false) + .execute(on: db, on: container) + } + + public func update( + on db: DatabaseIdentifier, + on container: AnyBridgesObject, + where predicates: SwifQLable + ) async throws -> Self { + let items = allColumns(logger: container.logger) + guard items.count > 0 else { + container.logger.debug("\(Self.tableName) update has been skipped cause nothing to update") + return self + } + guard let first = try await buildUpdateQuery(items: items, where: predicates, returning: true) + .execute(on: db, on: container) + .all(decoding: Self.self).first + else { + throw BridgesError.failedToDecodeWithReturning + } + return first + } + + // MARK: On connection + + public func updateNonReturning( + on keyColumn: KeyPath, + on conn: BridgeConnection, + preActions: @escaping () throws -> Void + ) async throws -> EventLoopFuture { + conn.eventLoop.future().flatMapThrowing { + try preActions() + }.flatMap { + self.updateNonReturning(on: keyColumn, on: conn) + } + } + + public func update( + on keyColumn: KeyPath, + on conn: BridgeConnection, + preActions: @escaping () throws -> Void + ) async throws -> EventLoopFuture { + conn.eventLoop.future().flatMapThrowing { + try preActions() + }.flatMap { + self.update(on: keyColumn, on: conn) + } + } + + /// + + public func updateNonReturning( + on keyColumn: KeyPath, + on conn: BridgeConnection, + preActions: @escaping (Self) throws -> Void + ) async throws -> EventLoopFuture { + conn.eventLoop.future().flatMapThrowing { + try preActions(self) + }.flatMap { + self.updateNonReturning(on: keyColumn, on: conn) + } + } + + public func update( + on keyColumn: KeyPath, + on conn: BridgeConnection, + preActions: @escaping (Self) throws -> Void + ) async throws -> EventLoopFuture { + conn.eventLoop.future().flatMapThrowing { + try preActions(self) + }.flatMap { + self.update(on: keyColumn, on: conn) + } + } + + /// + + public func updateNonReturning( + on keyColumn: KeyPath, + on conn: BridgeConnection, + preActions: () -> EventLoopFuture + ) async throws -> EventLoopFuture { + preActions().flatMap { _ in + self.updateNonReturning(on: keyColumn, on: conn) + } + } + + public func update( + on keyColumn: KeyPath, + on conn: BridgeConnection, + preActions: () -> EventLoopFuture + ) async throws -> EventLoopFuture { + preActions().flatMap { _ in + self.update(on: keyColumn, on: conn) + } + } + + /// + + public func updateNonReturning( + on keyColumn: KeyPath, + on conn: BridgeConnection, + preActions: (Self) -> EventLoopFuture + ) async throws -> EventLoopFuture { + preActions(self).flatMap { _ in + self.updateNonReturning(on: keyColumn, on: conn) + } + } + + public func update( + on keyColumn: KeyPath, + on conn: BridgeConnection, + preActions: (Self) -> EventLoopFuture + ) async throws -> EventLoopFuture { + preActions(self).flatMap { _ in + self.update(on: keyColumn, on: conn) + } + } + + /// + + public func updateNonReturning( + on keyColumn: KeyPath, + on conn: BridgeConnection + ) async throws -> EventLoopFuture { + guard let items = allColumns(excluding: keyColumn, logger: conn.logger) else { + return conn.eventLoop.makeFailedFuture(BridgesError.valueIsNilInKeyColumnUpdateIsImpossible) + } + guard items.0.count > 0 else { + conn.logger.debug("\(Self.tableName) update has been skipped cause nothing to update") + return conn.eventLoop.makeSucceededVoidFuture() + } + let query = buildUpdateQuery(items: items.0, where: items.1 == items.2, returning: false) + return conn.query(sql: query) + } + + public func update( + on keyColumn: KeyPath, + on conn: BridgeConnection + ) async throws -> EventLoopFuture { + guard let items = allColumns(excluding: keyColumn, logger: conn.logger) else { + return conn.eventLoop.makeFailedFuture(BridgesError.valueIsNilInKeyColumnUpdateIsImpossible) + } + guard items.0.count > 0 else { + conn.logger.debug("\(Self.tableName) update has been skipped cause nothing to update") + return conn.eventLoop.makeSucceededFuture(self) + } + let query = buildUpdateQuery(items: items.0, where: items.1 == items.2, returning: true) + return conn.query(sql: query, decoding: Self.self).flatMapThrowing { rows in + guard let row = rows.first else { throw BridgesError.failedToDecodeWithReturning } + return row + } + } + + /// + + public func update(on conn: BridgeConnection, where predicates: SwifQLable) async throws -> EventLoopFuture { + conn.query(sql: buildUpdateQuery(items: allColumns(logger: conn.logger), where: predicates, returning: false)) + } + } fileprivate func error(_ logger: Logger) { diff --git a/Sources/Bridges/Helpers/TableUpsert.swift b/Sources/Bridges/Helpers/TableUpsert.swift index cf5c0fb..c5eaea0 100644 --- a/Sources/Bridges/Helpers/TableUpsert.swift +++ b/Sources/Bridges/Helpers/TableUpsert.swift @@ -642,4 +642,578 @@ extension Table { return row } } + + ///ASYNC + + + public func upsertNonReturning( + conflictColumn: KeyPath, + excluding: KeyPathLastPath..., + inSchema schema: Schemable.Type? = nil, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws { + try await _upsertNonReturning( + conflictColumn: conflictColumn, + excluding: excluding, + schema: schema?.schemaName ?? (Self.self as? Schemable.Type)?.schemaName, + on: db, + on: container) + } + + public func upsert( + conflictColumn: KeyPath, + excluding: KeyPathLastPath..., + inSchema schema: Schemable.Type? = nil, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws -> Self { + try await _upsert( + conflictColumn: conflictColumn, + excluding: excluding, + schema: schema?.schemaName ?? (Self.self as? Schemable.Type)?.schemaName, + on: db, + on: container) + } + + /// + + public func upsertNonReturning( + conflictColumn: KeyPath, + excluding: [KeyPathLastPath], + inSchema schema: Schemable.Type? = nil, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws { + try await _upsertNonReturning( + conflictColumn: conflictColumn, + excluding: excluding, + schema: schema?.schemaName ?? (Self.self as? Schemable.Type)?.schemaName, + on: db, + on: container) + } + + public func upsert( + conflictColumn: KeyPath, + excluding: [KeyPathLastPath], + inSchema schema: Schemable.Type? = nil, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws -> Self { + try await _upsert( + conflictColumn: conflictColumn, + excluding: excluding, + schema: schema?.schemaName ?? (Self.self as? Schemable.Type)?.schemaName, + on: db, + on: container) + } + + /// + + public func upsertNonReturning( + conflictColumn: KeyPath, + excluding: KeyPathLastPath..., + inSchema schema: String, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws { + try await _upsertNonReturning(conflictColumn: conflictColumn, excluding: excluding, schema: schema, on: db, on: container) + } + + public func upsert( + conflictColumn: KeyPath, + excluding: KeyPathLastPath..., + inSchema schema: String, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws -> Self { + try await _upsert(conflictColumn: conflictColumn, excluding: excluding, schema: schema, on: db, on: container) + } + + /// + + public func upsertNonReturning( + conflictColumn: KeyPath, + excluding: [KeyPathLastPath], + inSchema schema: String, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws { + try await _upsertNonReturning(conflictColumn: conflictColumn, excluding: excluding, schema: schema, on: db, on: container) + } + + public func upsert( + conflictColumn: KeyPath, + excluding: [KeyPathLastPath], + inSchema schema: String, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws -> Self { + try await _upsert(conflictColumn: conflictColumn, excluding: excluding, schema: schema, on: db, on: container) + } + + /// + + private func _upsertNonReturning( + conflictColumn: KeyPath, + excluding: [KeyPathLastPath], + schema: String?, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws { + guard let updateItems = allColumns(excluding: conflictColumn, excluding: excluding, logger: container.logger) else { + throw BridgesError.valueIsNilInKeyColumnUpdateIsImpossible + } + _ = try await buildUpsertQuery( + schema: schema, + insertionItems: allColumns(logger: container.logger), + updateItems: updateItems.0, + conflictColumn: updateItems.1, + returning: false + ) + .execute(on: db, on: container) + } + + private func _upsert( + conflictColumn: KeyPath, + excluding: [KeyPathLastPath], + schema: String?, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws -> Self { + guard let updateItems = allColumns(excluding: conflictColumn, excluding: excluding, logger: container.logger) else { + throw BridgesError.valueIsNilInKeyColumnUpdateIsImpossible + } + return try await buildUpsertQuery( + schema: schema, + insertionItems: allColumns(logger: container.logger), + updateItems: updateItems.0, + conflictColumn: updateItems.1, + returning: true + ) + .execute(on: db, on: container) + .all(decoding: Self.self).first ?? self + } + + // MARK: Standalone, conflict constraint + + public func upsertNonReturning( + conflictConstraint: KeyPathLastPath, + excluding: KeyPathLastPath..., + inSchema schema: Schemable.Type? = nil, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws { + try await _upsertNonReturning( + conflictConstraint: conflictConstraint, + excluding: excluding, + schema: schema?.schemaName ?? (Self.self as? Schemable.Type)?.schemaName, + on: db, + on: container) + } + + public func upsert( + conflictConstraint: KeyPathLastPath, + excluding: KeyPathLastPath..., + inSchema schema: Schemable.Type? = nil, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws -> Self { + try await _upsert( + conflictConstraint: conflictConstraint, + excluding: excluding, + schema: schema?.schemaName ?? (Self.self as? Schemable.Type)?.schemaName, + on: db, + on: container) + } + + /// + + public func upsertNonReturning( + conflictConstraint: KeyPathLastPath, + excluding: [KeyPathLastPath], + inSchema schema: Schemable.Type? = nil, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws { + try await _upsertNonReturning( + conflictConstraint: conflictConstraint, + excluding: excluding, + schema: schema?.schemaName ?? (Self.self as? Schemable.Type)?.schemaName, + on: db, + on: container) + } + + public func upsert( + conflictConstraint: KeyPathLastPath, + excluding: [KeyPathLastPath], + inSchema schema: Schemable.Type? = nil, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws -> Self { + try await _upsert( + conflictConstraint: conflictConstraint, + excluding: excluding, + schema: schema?.schemaName ?? (Self.self as? Schemable.Type)?.schemaName, + on: db, + on: container) + } + + /// + + public func upsertNonReturning( + conflictConstraint: KeyPathLastPath, + excluding: KeyPathLastPath..., + inSchema schema: String, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws { + try await _upsertNonReturning(conflictConstraint: conflictConstraint, excluding: excluding, schema: schema, on: db, on: container) + } + + public func upsert( + conflictConstraint: KeyPathLastPath, + excluding: KeyPathLastPath..., + inSchema schema: String, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws -> Self { + try await _upsert(conflictConstraint: conflictConstraint, excluding: excluding, schema: schema, on: db, on: container) + } + + /// + + public func upsertNonReturning( + conflictConstraint: KeyPathLastPath, + excluding: [KeyPathLastPath], + inSchema schema: String, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws { + try await _upsertNonReturning(conflictConstraint: conflictConstraint, excluding: excluding, schema: schema, on: db, on: container) + } + + public func upsert( + conflictConstraint: KeyPathLastPath, + excluding: [KeyPathLastPath], + inSchema schema: String, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws -> Self { + try await _upsert(conflictConstraint: conflictConstraint, excluding: excluding, schema: schema, on: db, on: container) + } + + /// + + private func _upsertNonReturning( + conflictConstraint: KeyPathLastPath, + excluding: [KeyPathLastPath], + schema: String?, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws { + _ = try await buildUpsertQuery( + schema: schema, + insertionItems: allColumns(logger: container.logger), + updateItems: allColumns(excluding: excluding, logger: container.logger), + conflictConstraint: conflictConstraint, + returning: false + ).execute(on: db, on: container) + } + + private func _upsert( + conflictConstraint: KeyPathLastPath, + excluding: [KeyPathLastPath], + schema: String?, + on db: DatabaseIdentifier, + on container: AnyBridgesObject + ) async throws -> Self { + try await buildUpsertQuery( + schema: schema, + insertionItems: allColumns(logger: container.logger), + updateItems: allColumns(excluding: excluding, logger: container.logger), + conflictConstraint: conflictConstraint, + returning: true + ) + .execute(on: db, on: container) + .all(decoding: Self.self).first ?? self + } + + // MARK: On connection, conflict column + + public func upsertNonReturning( + conflictColumn: KeyPath, + excluding: KeyPathLastPath..., + inSchema schema: Schemable.Type? = nil, + on conn: BridgeConnection + ) async throws { + try await _upsertNonReturning( + conflictColumn: conflictColumn, + excluding: excluding, + schema: schema?.schemaName ?? (Self.self as? Schemable.Type)?.schemaName, + on: conn + ) + } + + public func upsert( + conflictColumn: KeyPath, + excluding: KeyPathLastPath..., + inSchema schema: Schemable.Type? = nil, + on conn: BridgeConnection + ) async throws -> Self { + try await _upsert( + conflictColumn: conflictColumn, + excluding: excluding, + schema: schema?.schemaName ?? (Self.self as? Schemable.Type)?.schemaName, + on: conn + ) + } + + /// + + public func upsertNonReturning( + conflictColumn: KeyPath, + excluding: [KeyPathLastPath], + inSchema schema: Schemable.Type? = nil, + on conn: BridgeConnection + ) async throws { + try await _upsertNonReturning( + conflictColumn: conflictColumn, + excluding: excluding, + schema: schema?.schemaName ?? (Self.self as? Schemable.Type)?.schemaName, + on: conn + ) + } + + public func upsert( + conflictColumn: KeyPath, + excluding: [KeyPathLastPath], + inSchema schema: Schemable.Type? = nil, + on conn: BridgeConnection + ) async throws -> Self { + try await _upsert( + conflictColumn: conflictColumn, + excluding: excluding, + schema: schema?.schemaName ?? (Self.self as? Schemable.Type)?.schemaName, + on: conn + ) + } + + /// + + public func upsertNonReturning( + conflictColumn: KeyPath, + excluding: KeyPathLastPath..., + inSchema schema: String, + on conn: BridgeConnection + ) async throws { + try await _upsertNonReturning(conflictColumn: conflictColumn, excluding: excluding, schema: schema, on: conn) + } + + public func upsert( + conflictColumn: KeyPath, + excluding: KeyPathLastPath..., + inSchema schema: String, + on conn: BridgeConnection + ) async throws -> Self { + try await _upsert(conflictColumn: conflictColumn, excluding: excluding, schema: schema, on: conn) + } + + /// + + public func upsertNonReturning( + conflictColumn: KeyPath, + excluding: [KeyPathLastPath], + inSchema schema: String, + on conn: BridgeConnection + ) async throws { + try await _upsertNonReturning(conflictColumn: conflictColumn, excluding: excluding, schema: schema, on: conn) + } + + public func upsert( + conflictColumn: KeyPath, + excluding: [KeyPathLastPath], + inSchema schema: String, + on conn: BridgeConnection + ) async throws -> Self { + try await _upsert(conflictColumn: conflictColumn, excluding: excluding, schema: schema, on: conn) + } + + /// + + private func _upsertNonReturning( + conflictColumn: KeyPath, + excluding: [KeyPathLastPath], + schema: String?, + on conn: BridgeConnection + ) async throws { + guard let updateItems = allColumns(excluding: conflictColumn, excluding: excluding, logger: conn.logger) else { + throw BridgesError.valueIsNilInKeyColumnUpdateIsImpossible + } + let query = buildUpsertQuery( + schema: schema, + insertionItems: allColumns(logger: conn.logger), + updateItems: updateItems.0, + conflictColumn: updateItems.1, + returning: false + ) + return try await conn.query(sql: query) + } + + private func _upsert( + conflictColumn: KeyPath, + excluding: [KeyPathLastPath], + schema: String?, + on conn: BridgeConnection + ) async throws -> Self { + guard let updateItems = allColumns(excluding: conflictColumn, excluding: excluding, logger: conn.logger) else { + throw BridgesError.valueIsNilInKeyColumnUpdateIsImpossible + } + let query = buildUpsertQuery( + schema: schema, + insertionItems: allColumns(logger: conn.logger), + updateItems: updateItems.0, + conflictColumn: updateItems.1, + returning: true + ) + return try await conn.query(sql: query, decoding: Self.self).first ?? self + } + + // MARK: On connection, conflict constraint + + public func upsertNonReturning( + conflictConstraint: KeyPathLastPath, + excluding: KeyPathLastPath..., + inSchema schema: Schemable.Type? = nil, + on conn: BridgeConnection + ) async throws { + try await _upsertNonReturning( + conflictConstraint: conflictConstraint, + excluding: excluding, + schema: schema?.schemaName ?? (Self.self as? Schemable.Type)?.schemaName, + on: conn + ) + } + + public func upsert( + conflictConstraint: KeyPathLastPath, + excluding: KeyPathLastPath..., + inSchema schema: Schemable.Type? = nil, + on conn: BridgeConnection + ) async throws -> Self { + try await _upsert( + conflictConstraint: conflictConstraint, + excluding: excluding, + schema: schema?.schemaName ?? (Self.self as? Schemable.Type)?.schemaName, + on: conn + ) + } + + /// + + public func upsertNonReturning( + conflictConstraint: KeyPathLastPath, + excluding: [KeyPathLastPath], + inSchema schema: Schemable.Type? = nil, + on conn: BridgeConnection + ) async throws { + try await _upsertNonReturning( + conflictConstraint: conflictConstraint, + excluding: excluding, + schema: schema?.schemaName ?? (Self.self as? Schemable.Type)?.schemaName, + on: conn + ) + } + + public func upsert( + conflictConstraint: KeyPathLastPath, + excluding: [KeyPathLastPath], + inSchema schema: Schemable.Type? = nil, + on conn: BridgeConnection + ) async throws -> Self { + try await _upsert( + conflictConstraint: conflictConstraint, + excluding: excluding, + schema: schema?.schemaName ?? (Self.self as? Schemable.Type)?.schemaName, + on: conn + ) + } + + /// + + public func upsertNonReturning( + conflictConstraint: KeyPathLastPath, + excluding: KeyPathLastPath..., + inSchema schema: String, + on conn: BridgeConnection + ) async throws { + try await _upsertNonReturning(conflictConstraint: conflictConstraint, excluding: excluding, schema: schema, on: conn) + } + + public func upsert( + conflictConstraint: KeyPathLastPath, + excluding: KeyPathLastPath..., + inSchema schema: String, + on conn: BridgeConnection + ) async throws -> Self { + try await _upsert(conflictConstraint: conflictConstraint, excluding: excluding, schema: schema, on: conn) + } + + /// + + public func upsertNonReturning( + conflictConstraint: KeyPathLastPath, + excluding: [KeyPathLastPath], + inSchema schema: String, + on conn: BridgeConnection + ) async throws { + try await _upsertNonReturning(conflictConstraint: conflictConstraint, excluding: excluding, schema: schema, on: conn) + } + + public func upsert( + conflictConstraint: KeyPathLastPath, + excluding: [KeyPathLastPath], + inSchema schema: String, + on conn: BridgeConnection + ) async throws -> Self { + try await _upsert(conflictConstraint: conflictConstraint, excluding: excluding, schema: schema, on: conn) + } + + /// + + private func _upsertNonReturning( + conflictConstraint: KeyPathLastPath, + excluding: [KeyPathLastPath], + schema: String?, + on conn: BridgeConnection + ) async throws { + let query = buildUpsertQuery( + schema: schema, + insertionItems: allColumns(logger: conn.logger), + updateItems: allColumns(excluding: excluding, logger: conn.logger), + conflictConstraint: conflictConstraint, + returning: false + ) + try await conn.query(sql: query) + } + + private func _upsert( + conflictConstraint: KeyPathLastPath, + excluding: [KeyPathLastPath], + schema: String?, + on conn: BridgeConnection + ) async throws -> Self { + let query = buildUpsertQuery( + schema: schema, + insertionItems: allColumns(logger: conn.logger), + updateItems: allColumns(excluding: excluding, logger: conn.logger), + conflictConstraint: conflictConstraint, + returning: true + ) + guard let first = try await conn.query(sql: query, decoding: Self.self).first else { + return self + } + return first + } + } diff --git a/Sources/Bridges/Protocols/AnyBridge.swift b/Sources/Bridges/Protocols/AnyBridge.swift index c63bd6c..36ae353 100644 --- a/Sources/Bridges/Protocols/AnyBridge.swift +++ b/Sources/Bridges/Protocols/AnyBridge.swift @@ -8,7 +8,7 @@ import NIO import Logging -public protocol AnyBridge: class { +public protocol AnyBridge: AnyObject { static var name: String { get } static func create(eventLoopGroup: EventLoopGroup, logger: Logger) -> AnyBridge diff --git a/Sources/Bridges/Protocols/AnyDatabaseIdentifiable.swift b/Sources/Bridges/Protocols/AnyDatabaseIdentifiable.swift index cde16c7..23f9ec6 100644 --- a/Sources/Bridges/Protocols/AnyDatabaseIdentifiable.swift +++ b/Sources/Bridges/Protocols/AnyDatabaseIdentifiable.swift @@ -12,6 +12,10 @@ 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]> + + func all(_ table: T.Type, on bridges: AnyBridgesObject) async throws -> [T] where T: Table + func first(_ table: T.Type, on bridges: AnyBridgesObject) async throws -> T? where T: Table + func query(_ query: SwifQLable, on bridges: AnyBridgesObject) async throws -> [BridgesRow] } public protocol AnyMySQLDatabaseIdentifiable: AnyDatabaseIdentifiable {} public protocol AnyPostgresDatabaseIdentifiable: AnyDatabaseIdentifiable {} diff --git a/Sources/Bridges/Protocols/AnyMigration.swift b/Sources/Bridges/Protocols/AnyMigration.swift index 43b1b9a..137653b 100644 --- a/Sources/Bridges/Protocols/AnyMigration.swift +++ b/Sources/Bridges/Protocols/AnyMigration.swift @@ -13,9 +13,19 @@ public protocol AnyMigration { static func prepare(on conn: BridgeConnection) -> EventLoopFuture static func revert(on conn: BridgeConnection) -> EventLoopFuture + + static func prepare(on conn: BridgeConnection) async throws + static func revert(on conn: BridgeConnection) async throws } extension AnyMigration { public static var name: String { String(describing: Self.self) } public static var migrationName: String { name } + + public static func prepare(on conn: BridgeConnection) -> EventLoopFuture { + conn.eventLoop.future() + } + public static func revert(on conn: BridgeConnection) -> EventLoopFuture { + conn.eventLoop.future() + } } diff --git a/Sources/Bridges/Protocols/Bridgeable.swift b/Sources/Bridges/Protocols/Bridgeable.swift index 6a4e5c8..c5e64db 100644 --- a/Sources/Bridges/Protocols/Bridgeable.swift +++ b/Sources/Bridges/Protocols/Bridgeable.swift @@ -33,14 +33,22 @@ public protocol Bridgeable: AnyBridge { func pool(_ db: DatabaseIdentifier, for eventLoop: EventLoop) -> Pool func db(_ db: DatabaseIdentifier, on eventLoop: EventLoop) -> Database - + + func connection(to db: DatabaseIdentifier, + on eventLoop: EventLoop, + _ closure: @escaping (Connection) -> EventLoopFuture) -> EventLoopFuture + func connection(to db: DatabaseIdentifier, - on eventLoop: EventLoop, - _ closure: @escaping (Connection) -> EventLoopFuture) -> EventLoopFuture + on eventLoop: EventLoop, + _ closure: @escaping (Connection) async throws -> T) async throws -> T + + func transaction(to db: DatabaseIdentifier, + on eventLoop: EventLoop, + _ closure: @escaping (Connection) -> EventLoopFuture) -> EventLoopFuture func transaction(to db: DatabaseIdentifier, - on eventLoop: EventLoop, - _ closure: @escaping (Connection) -> EventLoopFuture) -> EventLoopFuture + on eventLoop: EventLoop, + _ closure: @escaping (Connection) async throws -> T) async throws -> T func shutdown() diff --git a/Sources/Bridges/Protocols/Migration.swift b/Sources/Bridges/Protocols/Migration.swift index 4e6581f..670c408 100644 --- a/Sources/Bridges/Protocols/Migration.swift +++ b/Sources/Bridges/Protocols/Migration.swift @@ -12,6 +12,9 @@ public protocol Migration: AnyMigration { static func prepare(on conn: Connection) -> EventLoopFuture static func revert(on conn: Connection) -> EventLoopFuture + + static func prepare(on conn: Connection) async throws + static func revert(on conn: Connection) async throws } extension Migration { @@ -22,4 +25,12 @@ extension Migration { public static func revert(on conn: BridgeConnection) -> EventLoopFuture { revert(on: conn as! Connection) } + + public static func prepare(on conn: BridgeConnection) async throws { + try await prepare(on: conn as! Connection) + } + + public static func revert(on conn: BridgeConnection) async throws { + try await revert(on: conn as! Connection) + } }