vapor/fluent-mysql-driver

Implementation for deleteReference seems to be missing

foscomputerservices opened this issue · 1 comments

It doesn't appear that MySql is performing drop foreign key when requested to do so.

I have the following code, which is a secondary migration on a table.

struct PromoUpdate: Migration {
    static let migrationName = "promotion"
    
    typealias Database = MySQLDatabase
    
    static func prepare(on connection: MySQLConnection) -> Future<Void> {
        let update = MySQLDatabase.update(PurchaseTransaction.self, on: connection) { updater in
            updater.field(for: \PurchaseTransaction.promotionId)
            updater.reference(from: \PurchaseTransaction.promotionId, to: \Promotion.id, onDelete: .restrict)
        }
        
        return try! PurchaseTransaction.defaultModel(after: update, on: connection)
    }
    
    static func revert(on connection: MySQLConnection) -> Future<Void> {
        return MySQLDatabase.update(PurchaseTransaction.self, on: connection) { updater in
            updater.deleteReference(from: \PurchaseTransaction.promotionId, to: \Promotion.id)
            updater.deleteField(for: \PurchaseTransaction.promotionId)
        }
    }
}

Looking at the SQL generated during revert, I see the following:

[ INFO ] Reverting migration 'promotion' (/.../.build/checkouts/fluent.git-6251908308727715749/Sources/Fluent/Migration/Migrations.swift:151)
[mysql] [2018-08-13 23:40:35 +0000] ALTER TABLE `purchase_transactions`  []
[mysql] [2018-08-13 23:40:35 +0000] DELETE FROM `fluent` WHERE `fluent`.`name` = (?) [string("promotion")]
[mysql] [2018-08-13 23:40:35 +0000] SELECT * FROM `fluent` WHERE `fluent`.`name` = (?) LIMIT 1 OFFSET 0 [string("Promotion")]

When the next table, Promotion, is attempted to be deleted, I get the following and a crash: "Fatal error: 'try!' expression unexpectedly raised an error: ⚠️ MySQL Error: Cannot delete or update a parent row: a foreign key constraint fails"

[ INFO ] Reverting migration 'Promotion' (/.../.build/checkouts/fluent.git-6251908308727715749/Sources/Fluent/Migration/Migrations.swift:151)
[mysql] [2018-08-13 23:40:35 +0000] DROP TABLE `promotions` []

In looking at the database, the foreign key constraint remains.

mysql> SELECT TABLE_NAME,
    ->        COLUMN_NAME,
    ->        CONSTRAINT_NAME,
    ->        REFERENCED_TABLE_NAME,
    ->        REFERENCED_COLUMN_NAME
    -> FROM KEY_COLUMN_USAGE
    -> WHERE TABLE_SCHEMA = "efl_test"
    ->       AND TABLE_NAME = "purchase_transactions"
    ->       AND REFERENCED_COLUMN_NAME IS NOT NULL;
+-----------------------+-------------+---------------------------------------------+-----------------------+------------------------+
| TABLE_NAME            | COLUMN_NAME | CONSTRAINT_NAME                             | REFERENCED_TABLE_NAME | REFERENCED_COLUMN_NAME |
+-----------------------+-------------+---------------------------------------------+-----------------------+------------------------+
| purchase_transactions | promotionId | fk:b335393d3b362ad33aec3378c572f0ed29cb7e3c | promotions            | id                     |
| purchase_transactions | purchaseId  | fk:c2f70206d2df6cb75467a851374e05c995c1cf51 | purchases             | id                     |
+-----------------------+-------------+---------------------------------------------+-----------------------+------------------------+
2 rows in set (0.00 sec)

The only code that I can find that has 'ALTER TABLE' in it is in MySQLAlterTable.swift, and I don't see any 'DROP' code in there at all.

public func serialize(_ binds: inout [Encodable]) -> String {
    var sql: [String] = []
    sql.append("ALTER TABLE")
    sql.append(table.serialize(&binds))
    let actions = columns.map {
        let sql = "ADD COLUMN " + $0.serialize(&binds)
        if let position = columnPositions[$0.column] {
            return sql + " " + position.serialize(&binds)
        } else {
            return sql
        }
    } + constraints.map { "ADD " + $0.serialize(&binds) }
    sql.append(actions.joined(separator: ", "))
    return sql.joined(separator: " ")
}

I came across this issue as I'm attempting to implement unit tests as described in the Server Side Swift with Vapor book, where they recommend calling vapor revert --all -y to reset the database.

This has been fixed, thanks!