KEYCLOAK-16935 Fix liquibase to work with MySQL 8.0.23+

This commit is contained in:
Hynek Mlnarik 2021-04-16 19:48:57 +02:00 committed by Hynek Mlnařík
parent 068a1811f2
commit ff4c0e4412
6 changed files with 134 additions and 3 deletions

View file

@ -44,6 +44,7 @@ import org.jboss.logging.Logger;
import org.keycloak.common.util.reflections.Reflections;
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.LiquibaseConnectionProvider;
import org.keycloak.connections.jpa.util.JpaUtils;
import org.keycloak.models.KeycloakSession;
@ -274,6 +275,8 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider {
private void resetLiquibaseServices(Liquibase liquibase) {
Method resetServices = Reflections.findDeclaredMethod(Liquibase.class, "resetServices");
Reflections.invokeMethod(true, resetServices, liquibase);
ChangeLogHistoryServiceFactory.getInstance().register(new CustomChangeLogHistoryService());
}
@SuppressWarnings("unchecked")

View file

@ -0,0 +1,120 @@
/*
* Copyright 2021 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.connections.jpa.updater.liquibase.conn;
import org.keycloak.common.util.reflections.Reflections;
import java.lang.reflect.Field;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import liquibase.ContextExpression;
import liquibase.Labels;
import liquibase.change.CheckSum;
import liquibase.changelog.ChangeSet;
import liquibase.changelog.RanChangeSet;
import liquibase.changelog.StandardChangeLogHistoryService;
import liquibase.database.Database;
import liquibase.database.core.MySQLDatabase;
import liquibase.exception.DatabaseException;
import liquibase.logging.LogFactory;
/**
*
* @author hmlnarik
*/
public class CustomChangeLogHistoryService extends StandardChangeLogHistoryService {
private List<RanChangeSet> ranChangeSetList;
@Override
public List<RanChangeSet> getRanChangeSets() throws DatabaseException {
Database database = getDatabase();
if (! (database instanceof MySQLDatabase) || database.getDatabaseMajorVersion() < 8) {
return super.getRanChangeSets();
}
if (this.ranChangeSetList == null) {
String databaseChangeLogTableName = getDatabase().escapeTableName(getLiquibaseCatalogName(), getLiquibaseSchemaName(), getDatabaseChangeLogTableName());
List<RanChangeSet> ranChangeSetList = new ArrayList<>();
if (hasDatabaseChangeLogTable()) {
LogFactory.getLogger().info("Reading from " + databaseChangeLogTableName);
List<Map<String, ?>> results = queryDatabaseChangeLogTable(database);
for (Map rs : results) {
String fileName = rs.get("FILENAME").toString();
String author = rs.get("AUTHOR").toString();
String id = rs.get("ID").toString();
String md5sum = rs.get("MD5SUM") == null || getDatabaseChecksumsCompatible() ? null : rs.get("MD5SUM").toString();
String description = rs.get("DESCRIPTION") == null ? null : rs.get("DESCRIPTION").toString();
String comments = rs.get("COMMENTS") == null ? null : rs.get("COMMENTS").toString();
Object tmpDateExecuted = rs.get("DATEEXECUTED");
Date dateExecuted = null;
if (tmpDateExecuted instanceof Date) {
dateExecuted = (Date) tmpDateExecuted;
} else if (tmpDateExecuted instanceof LocalDateTime) {
dateExecuted = Date.from(((LocalDateTime) tmpDateExecuted).atZone(ZoneId.systemDefault()).toInstant());
} else {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
try {
dateExecuted = df.parse((String) tmpDateExecuted);
} catch (ParseException e) {
}
}
String tmpOrderExecuted = rs.get("ORDEREXECUTED").toString();
Integer orderExecuted = (tmpOrderExecuted == null ? null : Integer.valueOf(tmpOrderExecuted));
String tag = rs.get("TAG") == null ? null : rs.get("TAG").toString();
String execType = rs.get("EXECTYPE") == null ? null : rs.get("EXECTYPE").toString();
ContextExpression contexts = new ContextExpression((String) rs.get("CONTEXTS"));
Labels labels = new Labels((String) rs.get("LABELS"));
String deploymentId = (String) rs.get("DEPLOYMENT_ID");
try {
RanChangeSet ranChangeSet = new RanChangeSet(fileName, id, author, CheckSum.parse(md5sum), dateExecuted, tag, ChangeSet.ExecType.valueOf(execType), description, comments, contexts, labels, deploymentId);
ranChangeSet.setOrderExecuted(orderExecuted);
ranChangeSetList.add(ranChangeSet);
} catch (IllegalArgumentException e) {
LogFactory.getLogger().severe("Unknown EXECTYPE from database: " + execType);
throw e;
}
}
}
this.ranChangeSetList = ranChangeSetList;
}
return Collections.unmodifiableList(ranChangeSetList);
}
private boolean getDatabaseChecksumsCompatible() {
Field f = Reflections.findDeclaredField(StandardChangeLogHistoryService.class, "databaseChecksumsCompatible");
if (f != null) {
f.setAccessible(true);
Boolean databaseChecksumsCompatible = Reflections.getFieldValue(f, this, Boolean.class);
return databaseChecksumsCompatible == null ? true : databaseChecksumsCompatible;
}
return true;
}
@Override
public int getPriority() {
return super.getPriority() + 1; // Ensure bigger priority than StandardChangeLogHistoryService
}
}

View file

@ -48,6 +48,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import java.sql.Connection;
import liquibase.changelog.ChangeLogHistoryServiceFactory;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
@ -117,6 +118,8 @@ public class DefaultLiquibaseConnectionProvider implements LiquibaseConnectionPr
// Use "SELECT FOR UPDATE" for locking database
SqlGeneratorFactory.getInstance().register(new CustomLockDatabaseChangeLogGenerator());
ChangeLogHistoryServiceFactory.getInstance().register(new CustomChangeLogHistoryService());
// Adding CustomCreateIndexChange for handling conditional indices creation
ChangeFactory.getInstance().register(CustomCreateIndexChange.class);
}

View file

@ -128,7 +128,7 @@
<jetty9.version>${jetty92.version}</jetty9.version>
<liquibase.version>3.5.5</liquibase.version>
<mysql.version>8.0.18</mysql.version>
<mysql.version>8.0.23</mysql.version>
<osgi.version>4.2.0</osgi.version>
<pax.web.version>7.1.0</pax.web.version>
<postgresql.version>42.2.8</postgresql.version>

View file

@ -35,6 +35,7 @@ import org.keycloak.common.util.reflections.Reflections;
import org.keycloak.connections.jpa.entityprovider.JpaEntityProvider;
import org.keycloak.connections.jpa.updater.JpaUpdaterProvider;
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.LiquibaseConnectionProvider;
import org.keycloak.connections.jpa.util.JpaUtils;
import org.keycloak.models.KeycloakSession;
@ -283,6 +284,8 @@ public class QuarkusJpaUpdaterProvider implements JpaUpdaterProvider {
private void resetLiquibaseServices(Liquibase liquibase) {
Method resetServices = Reflections.findDeclaredMethod(Liquibase.class, "resetServices");
Reflections.invokeMethod(true, resetServices, liquibase);
ChangeLogHistoryServiceFactory.getInstance().register(new CustomChangeLogHistoryService());
}
@SuppressWarnings("unchecked")

View file

@ -24,7 +24,6 @@ import javax.xml.parsers.SAXParserFactory;
import liquibase.database.core.MariaDBDatabase;
import liquibase.database.core.MySQLDatabase;
import liquibase.database.core.PostgresDatabase;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.connections.jpa.JpaConnectionProvider;
@ -33,18 +32,19 @@ import org.keycloak.connections.jpa.updater.liquibase.MySQL8VarcharType;
import org.keycloak.connections.jpa.updater.liquibase.PostgresPlusDatabase;
import org.keycloak.connections.jpa.updater.liquibase.UpdatedMariaDBDatabase;
import org.keycloak.connections.jpa.updater.liquibase.UpdatedMySqlDatabase;
import org.keycloak.connections.jpa.updater.liquibase.conn.CustomChangeLogHistoryService;
import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProvider;
import org.keycloak.connections.jpa.updater.liquibase.conn.LiquibaseConnectionProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import liquibase.Liquibase;
import liquibase.changelog.ChangeLogHistoryServiceFactory;
import liquibase.database.Database;
import liquibase.database.DatabaseFactory;
import liquibase.database.jvm.JdbcConnection;
import liquibase.datatype.DataTypeFactory;
import liquibase.exception.LiquibaseException;
import liquibase.logging.LogFactory;
import liquibase.parser.ChangeLogParser;
import liquibase.parser.ChangeLogParserFactory;
import liquibase.parser.core.xml.XMLChangeLogSAXParser;
@ -91,6 +91,8 @@ public class QuarkusLiquibaseConnectionProvider implements LiquibaseConnectionPr
if (database.getDatabaseProductName().equals(MySQLDatabase.PRODUCT_NAME)) {
// Adding CustomVarcharType for MySQL 8 and newer
DataTypeFactory.getInstance().register(MySQL8VarcharType.class);
ChangeLogHistoryServiceFactory.getInstance().register(new CustomChangeLogHistoryService());
} else if (database.getDatabaseProductName().equals(MariaDBDatabase.PRODUCT_NAME)) {
// Adding CustomVarcharType for MySQL 8 and newer
DataTypeFactory.getInstance().register(MySQL8VarcharType.class);