Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ let package = Package(
.package(url: "https://github.com/apple/swift-nio.git", from: "2.81.0"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.6.3"),
.package(url: "https://github.com/vapor/sql-kit.git", from: "3.32.0"),
.package(url: "https://github.com/apple/swift-distributed-tracing.git", from: "1.3.1"),
.package(url: "https://github.com/apple/swift-service-context.git", from: "1.0.0"),
],
targets: [
.target(
Expand All @@ -30,6 +32,8 @@ let package = Package(
.product(name: "NIOPosix", package: "swift-nio"),
.product(name: "Logging", package: "swift-log"),
.product(name: "SQLKit", package: "sql-kit"),
.product(name: "Tracing", package: "swift-distributed-tracing"),
.product(name: "ServiceContextModule", package: "swift-service-context"),
],
swiftSettings: swiftSettings
),
Expand Down Expand Up @@ -65,6 +69,7 @@ let package = Package(
.target(name: "FluentBenchmark"),
.target(name: "FluentSQL"),
.target(name: "XCTFluent"),
.product(name: "InMemoryTracing", package: "swift-distributed-tracing")
],
swiftSettings: swiftSettings
),
Expand Down
50 changes: 31 additions & 19 deletions Sources/FluentKit/Concurrency/AsyncModelMiddleware.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,33 +11,45 @@ public protocol AsyncModelMiddleware: AnyModelMiddleware {
}

extension AsyncModelMiddleware {
public func handle(
func handle(
_ event: ModelEvent,
_ model: any AnyModel,
on db: any Database,
chainingTo next: any AnyModelResponder
) -> EventLoopFuture<Void> {
chainingTo next: any AnyAsyncModelResponder
) async throws {
guard let modelType = (model as? Model) else {
return next.handle(event, model, on: db)
return try await next.handle(event, model, on: db)
}

return db.eventLoop.makeFutureWithTask {
let responder = AsyncBasicModelResponder { responderEvent, responderModel, responderDB in
try await next.handle(responderEvent, responderModel, on: responderDB).get()
}
switch event {
case .create:
try await self.create(model: modelType, on: db, next: next)
case .update:
try await self.update(model: modelType, on: db, next: next)
case .delete(let force):
try await self.delete(model: modelType, force: force, on: db, next: next)
case .softDelete:
try await self.softDelete(model: modelType, on: db, next: next)
case .restore:
try await self.restore(model: modelType, on: db, next: next)
}
}

switch event {
case .create:
try await self.create(model: modelType, on: db, next: responder)
case .update:
try await self.update(model: modelType, on: db, next: responder)
case .delete(let force):
try await self.delete(model: modelType, force: force, on: db, next: responder)
case .softDelete:
try await self.softDelete(model: modelType, on: db, next: responder)
case .restore:
try await self.restore(model: modelType, on: db, next: responder)
public func handle(
_ event: ModelEvent,
_ model: any AnyModel,
on db: any Database,
chainingTo next: any AnyModelResponder
) -> EventLoopFuture<Void> {
db.eventLoop.makeFutureWithTask {
let responder = AnyAsyncBasicModelResponder { responderModel, responderEvent, responderDB in
if let next = (next as? any AnyAsyncModelResponder) {
try await next.handle(responderModel, responderEvent, on: responderDB)
} else {
try await next.handle(responderModel, responderEvent, on: responderDB).get()
}
}
try await self.handle(event, model, on: db, chainingTo: responder)
}
}

Expand Down
26 changes: 23 additions & 3 deletions Sources/FluentKit/Concurrency/Children+Concurrency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,35 @@ import NIOCore

public extension ChildrenProperty {
func load(on database: any Database) async throws {
try await self.load(on: database).get()
self.value = try await self.query(on: database).all()
}

func create(_ to: To, on database: any Database) async throws {
try await self.create(to, on: database).get()
guard let id = self.idValue else {
fatalError("Cannot save child in relation \(self.name) to unsaved model.")
}
switch self.parentKey {
case .required(let keyPath):
to[keyPath: keyPath].id = id
case .optional(let keyPath):
to[keyPath: keyPath].id = id
}
return try await to.create(on: database)
}

func create(_ to: [To], on database: any Database) async throws {
try await self.create(to, on: database).get()
guard let id = self.idValue else {
fatalError("Cannot save child in relation \(self.name) to unsaved model.")
}
to.forEach {
switch self.parentKey {
case .required(let keyPath):
$0[keyPath: keyPath].id = id
case .optional(let keyPath):
$0[keyPath: keyPath].id = id
}
}
return try await to.create(on: database)
}
}

Expand Down
5 changes: 4 additions & 1 deletion Sources/FluentKit/Concurrency/Database+Concurrency.swift
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import NIOCore
import Tracing

public extension Database {
func execute(
query: DatabaseQuery,
onOutput: @escaping @Sendable (any DatabaseOutput) -> ()
) async throws {
try await self.execute(query: query, onOutput: onOutput).get()
try await query.withTracing {
try await self.execute(query: query, onOutput: onOutput).get()
}
}

func execute(
Expand Down
54 changes: 50 additions & 4 deletions Sources/FluentKit/Concurrency/EnumBuilder+Concurrency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,64 @@ import NIOCore

public extension EnumBuilder {
func create() async throws -> DatabaseSchema.DataType {
try await self.create().get()
self.enum.action = .create
try await self.database.execute(enum: self.enum)
return try await self.generateDatatype()
}

func read() async throws -> DatabaseSchema.DataType {
try await self.read().get()
try await self.generateDatatype()
}

func update() async throws -> DatabaseSchema.DataType {
try await self.update().get()
self.enum.action = .update
try await self.database.execute(enum: self.enum)
return try await self.generateDatatype()
}

func delete() async throws {
try await self.delete().get()
self.enum.action = .delete
try await self.database.execute(enum: self.enum)
try await self.deleteMetadata()
}

// MARK: Private

func generateDatatype() async throws -> DatabaseSchema.DataType {
try await EnumMetadata.migration.prepare(on: self.database)
try await self.updateMetadata()

let cases = try await EnumMetadata.query(on: self.database).filter(\.$name == self.enum.name).all()

return .enum(.init(
name: self.enum.name,
cases: cases.map { $0.case }
))
}

private func updateMetadata() async throws {
// Create all new enum cases.
try await self.enum.createCases.map {
EnumMetadata(name: self.enum.name, case: $0)
}.create(on: self.database)

// Delete all old enum cases.
try await EnumMetadata.query(on: self.database)
.filter(\.$name == self.enum.name)
.filter(\.$case ~~ self.enum.deleteCases)
.delete()
}

private func deleteMetadata() async throws {
// Delete all cases for this enum.
try await EnumMetadata.query(on: self.database)
.filter(\.$name == self.enum.name)
.delete()
let count = try await EnumMetadata.query(on: self.database).count()

// If no enums are left, remove table.
if count == 0 {
try await EnumMetadata.migration.revert(on: self.database)
}
}
}
Loading