Add PK creation for databasechangelog in MySQL to keycloak-database-update.sql when manual migration is used.

Closes #44349

Signed-off-by: vramik <vramik@redhat.com>
This commit is contained in:
vramik
2025-11-25 08:15:06 +01:00
committed by Pedro Igor
parent 33b6065c2a
commit 7167262909
6 changed files with 49 additions and 10 deletions

View File

@@ -29,9 +29,9 @@ import java.util.Set;
import org.keycloak.connections.jpa.entityprovider.JpaEntityProvider;
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
import org.keycloak.connections.jpa.updater.liquibase.conn.CustomChangeLogHistoryService;
import org.keycloak.connections.jpa.updater.liquibase.conn.KeycloakLiquibase;
import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
import org.keycloak.connections.jpa.updater.liquibase.conn.MySQLCustomChangeLogHistoryService;
import org.keycloak.connections.jpa.util.JpaUtils;
import org.keycloak.models.KeycloakSession;
@@ -215,6 +215,13 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
// in org.keycloak.connections.jpa.updater.liquibase.lock.CustomLockService.init() called indirectly from
// KeycloakApplication constructor (search for waitForLock() call). Hence it is not included in the creation script.
// For MySQL, add primary key to DATABASECHANGELOG table (handled by MySQLCustomChangeLogHistoryService at runtime)
ChangeLogHistoryService changeLogHistoryService = ChangeLogHistoryServiceFactory.getInstance().getChangeLogService(database);
if (changeLogHistoryService instanceof MySQLCustomChangeLogHistoryService customChangeLogHistoryService) {
loggingExecutor.comment("Add primary key to DATABASECHANGELOG table for MySQL");
loggingExecutor.execute(customChangeLogHistoryService.getAddDatabaseChangeLogPKStatement());
}
executorService.setExecutor(LiquibaseConstants.JDBC_EXECUTOR, database, oldTemplate);
}
@@ -286,7 +293,7 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
private void resetLiquibaseServices(KeycloakLiquibase liquibase) {
liquibase.resetServices();
ChangeLogHistoryServiceFactory.getInstance().register(new CustomChangeLogHistoryService());
ChangeLogHistoryServiceFactory.getInstance().register(new MySQLCustomChangeLogHistoryService());
}
private List<ChangeSet> getLiquibaseUnrunChangeSets(Liquibase liquibase) throws LiquibaseException {

View File

@@ -16,11 +16,16 @@
*/
package org.keycloak.connections.jpa.updater.liquibase.conn;
import org.keycloak.connections.jpa.updater.liquibase.LiquibaseConstants;
import liquibase.Scope;
import liquibase.change.ColumnConfig;
import liquibase.changelog.StandardChangeLogHistoryService;
import liquibase.database.Database;
import liquibase.database.core.MySQLDatabase;
import liquibase.exception.DatabaseException;
import liquibase.executor.ExecutorService;
import liquibase.executor.LoggingExecutor;
import liquibase.executor.jvm.ChangelogJdbcMdcListener;
import liquibase.snapshot.InvalidExampleException;
import liquibase.snapshot.SnapshotGeneratorFactory;
@@ -33,7 +38,7 @@ import liquibase.structure.core.Table;
*
* @author hmlnarik
*/
public class CustomChangeLogHistoryService extends StandardChangeLogHistoryService {
public class MySQLCustomChangeLogHistoryService extends StandardChangeLogHistoryService {
private boolean serviceInitialized;
@@ -50,6 +55,13 @@ public class CustomChangeLogHistoryService extends StandardChangeLogHistoryServi
serviceInitialized = true;
// Skip execution in manual migration mode - the PK statement is added to the export by LiquibaseJpaUpdaterProvider
ExecutorService executorService = Scope.getCurrentScope().getSingleton(ExecutorService.class);
if (executorService.getExecutor(LiquibaseConstants.JDBC_EXECUTOR, getDatabase()) instanceof LoggingExecutor) {
return;
}
if (!existsDatabaseChangelogPK()) {
createDatabaseChangelogPK();
}
@@ -74,8 +86,7 @@ public class CustomChangeLogHistoryService extends StandardChangeLogHistoryServi
}
private void createDatabaseChangelogPK() throws DatabaseException {
AddPrimaryKeyStatement pkStatement = new AddPrimaryKeyStatement(getLiquibaseCatalogName(), getLiquibaseSchemaName(), getDatabaseChangeLogTableName(),
ColumnConfig.arrayFromNames("ID, AUTHOR, FILENAME"), "PK_DATABASECHANGELOG");
AddPrimaryKeyStatement pkStatement = getAddDatabaseChangeLogPKStatement();
try {
ChangelogJdbcMdcListener.execute(getDatabase(), ex -> ex.execute(pkStatement));
getDatabase().commit();
@@ -86,4 +97,9 @@ public class CustomChangeLogHistoryService extends StandardChangeLogHistoryServi
}
}
}
public AddPrimaryKeyStatement getAddDatabaseChangeLogPKStatement() {
return new AddPrimaryKeyStatement(getLiquibaseCatalogName(), getLiquibaseSchemaName(), getDatabaseChangeLogTableName(),
ColumnConfig.arrayFromNames("ID, AUTHOR, FILENAME"), "PK_DATABASECHANGELOG");
}
}

View File

@@ -15,4 +15,4 @@
# limitations under the License.
#
org.keycloak.connections.jpa.updater.liquibase.conn.CustomChangeLogHistoryService
org.keycloak.connections.jpa.updater.liquibase.conn.MySQLCustomChangeLogHistoryService

View File

@@ -32,9 +32,9 @@ import org.keycloak.connections.jpa.entityprovider.JpaEntityProvider;
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
import org.keycloak.connections.jpa.updater.liquibase.LiquibaseConstants;
import org.keycloak.connections.jpa.updater.liquibase.ThreadLocalSessionContext;
import org.keycloak.connections.jpa.updater.liquibase.conn.CustomChangeLogHistoryService;
import org.keycloak.connections.jpa.updater.liquibase.conn.KeycloakLiquibase;
import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
import org.keycloak.connections.jpa.updater.liquibase.conn.MySQLCustomChangeLogHistoryService;
import org.keycloak.connections.jpa.util.JpaUtils;
import org.keycloak.models.KeycloakSession;
@@ -217,6 +217,13 @@ public class QuarkusJpaUpdaterProvider implements JpaUpdaterProvider {
// in org.keycloak.connections.jpa.updater.liquibase.lock.CustomLockService.init() called indirectly from
// KeycloakApplication constructor (search for waitForLock() call). Hence it is not included in the creation script.
// For MySQL, add primary key to DATABASECHANGELOG table (handled by MySQLCustomChangeLogHistoryService at runtime)
ChangeLogHistoryService changeLogHistoryService = ChangeLogHistoryServiceFactory.getInstance().getChangeLogService(database);
if (changeLogHistoryService instanceof MySQLCustomChangeLogHistoryService customChangeLogHistoryService) {
loggingExecutor.comment("Add primary key to DATABASECHANGELOG table for MySQL");
loggingExecutor.execute(customChangeLogHistoryService.getAddDatabaseChangeLogPKStatement());
}
executorService.setExecutor(LiquibaseConstants.JDBC_EXECUTOR, database, oldTemplate);
}
@@ -283,7 +290,7 @@ public class QuarkusJpaUpdaterProvider implements JpaUpdaterProvider {
private void resetLiquibaseServices(KeycloakLiquibase liquibase) {
liquibase.resetServices();
getChangeLogHistoryService().register(new CustomChangeLogHistoryService());
getChangeLogHistoryService().register(new MySQLCustomChangeLogHistoryService());
}
private ChangeLogHistoryServiceFactory getChangeLogHistoryService() {

View File

@@ -85,7 +85,7 @@ public abstract class BasicDatabaseTest {
cliResult.assertMessage("Import finished successfully");
}
public void assertManualDbInitialization(CLIResult cliResult, RawDistRootPath rawDistRootPath) {
public String assertManualDbInitialization(CLIResult cliResult, RawDistRootPath rawDistRootPath) {
cliResult.assertMessage("Database not initialized, please initialize database with");
var output = readKeycloakDbUpdateScript(rawDistRootPath);
@@ -94,6 +94,8 @@ public abstract class BasicDatabaseTest {
assertThat(output, containsString("Keycloak database creation script - apply this script to empty DB"));
assertThat(output, containsString("Change Log: META-INF/jpa-changelog-master.xml"));
assertThat(output, containsString("Changeset META-INF/jpa-changelog-26.2.6.xml"));
return output;
}
protected static String readKeycloakDbUpdateScript(RawDistRootPath path) {

View File

@@ -11,6 +11,9 @@ import io.quarkus.test.junit.main.Launch;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
@DistributionTest(removeBuildOptionsAfterBuild = true)
@WithDatabase(alias = "mysql")
public class MySQLDistTest extends MySQLTest {
@@ -27,7 +30,11 @@ public class MySQLDistTest extends MySQLTest {
@Test
@Launch({"start", AbstractAutoBuildCommand.OPTIMIZED_BUILD_OPTION_LONG, "--spi-connections-jpa-quarkus-migration-strategy=manual", "--spi-connections-jpa-quarkus-initialize-empty=false", "--http-enabled=true", "--hostname-strict=false",})
public void testKeycloakDbUpdateScript(CLIResult cliResult, RawDistRootPath rawDistRootPath) {
assertManualDbInitialization(cliResult, rawDistRootPath);
String output = assertManualDbInitialization(cliResult, rawDistRootPath);
// Verify MySQL primary key is included
assertThat(output, containsString("Add primary key to DATABASECHANGELOG table for MySQL"));
assertThat(output, containsString("ALTER TABLE keycloak.DATABASECHANGELOG ADD PRIMARY KEY (ID, AUTHOR, FILENAME);"));
}
@Tag(DistributionTest.STORAGE)