flyway icon indicating copy to clipboard operation
flyway copied to clipboard

InsertRowLock requires the database to implement primary keys

Open jaccozilla opened this issue 2 months ago • 0 comments

I'm looking at adding support for locking in clickhouse https://github.com/flyway/flyway-community-db-support/issues/48. InsertRowLock is close to working, but because clickhouse doesn't support traditional primary keys I had to make some modifications to it.

I'm allowing multiple locks to be written, but consistently selecting a winner by ordering on timestamp + version. This needs the insertLockingRow to also query the current owner.

I would like to refactor InsertRowLock a little and pull out an AbstractInsertRowLock allowing for subclasses to implement insertLockingRow.

This is what i'm currently using for clickhouse's `insertRowLock

private boolean insertLockingRow(String insertStatementTemplate, String currentLockOwnerStatementTemplate, String booleanTrue, String checksumValue) throws InterruptedException {

        String currentLockOwner = getCurrentLockOwner(currentLockOwnerStatementTemplate);
        if (tableLockString.equals(currentLockOwner)) {
            return true;
        }
        if (currentLockOwner != null) {
            // owned by another migration
            LOG.info("[{}] Unable to lock, already owned by {}",  tableLockString, currentLockOwner);
            return false;
        }

        String insertStatement = String.format(insertStatementTemplate.replace("?", "%s"),
                                               -100,
                                               "'" + tableLockString + "'",
                                               "'" + FLYWAY_LOCK_STRING + "'",
                                               "''",
                                               "''",
                                               checksumValue,
                                               "''",
                                               0,
                                               booleanTrue
                                              );

        // Insert the locking row - the primary key-ness of 'installed_rank' will prevent us having two
        Results results = jdbcTemplate.executeStatement(insertStatement);

        if (results.getException() != null) {
            return false;
        }

        if (currentLockOwnerStatementTemplate == null) {
            return true;
        }

        currentLockOwner = getCurrentLockOwner(currentLockOwnerStatementTemplate);
        if (tableLockString.equals(currentLockOwner)) {
            // double check we are still the current lock owner, in case we raced with another
            // insert and need version ordering to select the current owner
            currentLockOwner = getCurrentLockOwner(currentLockOwnerStatementTemplate);
            if (tableLockString.equals(currentLockOwner)) {
                // this row lock is the owner
                return true;
            }
        }

        // not the owner
        LOG.info("[{}] Attempted to lock, but already owned by {}",  tableLockString, currentLockOwner);
        return false;
    }

    private String getCurrentLockOwner(String currentLockOwnerStatementTemplate) {
        if (currentLockOwnerStatementTemplate == null) {
            return null;
        }
        String currentLockOwnerStatement = String.format(currentLockOwnerStatementTemplate.replace("?", "%s"),
                "'" + FLYWAY_LOCK_STRING + "'"
        );

        Results results = jdbcTemplate.executeStatement(currentLockOwnerStatement);

        for (Result result : results.getResults()) {
            List<List<String>> data = result.data();
            if (data == null || data.isEmpty()) {
                continue;
            }
            int columnIndex = result.columns().indexOf("version");
            if (columnIndex == -1) {
                throw new IllegalStateException("missing 'version' column in currentLockOwnerStatementTemplate: " + currentLockOwnerStatementTemplate);
            }

            return data.getFirst().get(columnIndex);
        }
        return null;
    }

jaccozilla avatar Oct 19 '25 18:10 jaccozilla