vapor / fluent-mysql-driver

πŸ–‹πŸ¬ Swift ORM (queries, models, relations, etc) built on MySQL.
MIT License
78 stars 52 forks source link

How can I store a master table with its items in only one transaction. #160

Closed jmarkstar closed 4 years ago

jmarkstar commented 4 years ago

I tried this but I'm getting this error: Fatal error: Attempting to callsend(...)while handler is still: callback(NIO.EventLoopPromise<()>(futureResult: NIO.EventLoopFuture<()>), (Function)).

Any help please.

return req.transaction(on: .mysql) { conn in

    guard let items = newInput.items
        else { throw Abort(.badRequest) }

    guard var inputModel = newInput.toModel()
        else { throw Abort(.badRequest) }

        return inputModel.create(on: conn).flatMap(to: HTTPStatus.self) { productInput in                
            guard let newInputId = productInput.id
                else { throw Abort(.internalServerError) }

            for index in items.indices {

                var publicInputItem = items[index]
                //....
                guard let itemModel = publicInputItem.toModel()
                    else { throw Abort(.badRequest) }

                itemModel.save(on: conn)            
            }

            return req.future(HTTPStatus.created)
        }
    }
0xTim commented 4 years ago

You can't use a for loop like that with a future operation. What's happening is you're kicking off a load of saves then trying to return and release the connection whilst it's still being used by the saves. What you need to do is to capture the result of each save in an array and then flatten that array of futures before returning the status code

vzsg commented 4 years ago

While that's true and would cause the transaction to be committed before the actual insertions succeeded, the crash is caused by something else.

Vapor 3's database drivers – with the exception of Redis which was rewritten from the ground up – expect you, the developer, to ensure that only one command is sent on a database connection at a time (instead of queueing them for example).

Therefore, using a for loop (which starts all the requests "at once") will crash, and so would the flatMap(map(...).flatten(on: conn)) approach that usually works in other scenarios.


You have three options:

Unfortunately, I don't have time to show examples for either solution at this moment.