Support for EDB 17 (#42341)

Closes #42742
Closes #42293

Signed-off-by: Václav Muzikář <vmuzikar@redhat.com>
This commit is contained in:
Václav Muzikář
2025-09-26 16:04:47 +02:00
committed by GitHub
parent 746a8211ff
commit b65a60e40d
32 changed files with 515 additions and 180 deletions

View File

@@ -1,73 +0,0 @@
/*
* Copyright 2016 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;
import liquibase.Scope;
import liquibase.database.DatabaseConnection;
import liquibase.database.core.PostgresDatabase;
import liquibase.exception.DatabaseException;
import liquibase.executor.ExecutorService;
import liquibase.statement.core.RawSqlStatement;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class PostgresPlusDatabase extends PostgresDatabase {
public static final String POSTGRESPLUS_PRODUCT_NAME = "EnterpriseDB";
@Override
public String getShortName() {
return "postgresplus";
}
@Override
protected String getDefaultDatabaseProductName() {
return POSTGRESPLUS_PRODUCT_NAME;
}
@Override
public boolean isCorrectDatabaseImplementation(DatabaseConnection conn) throws DatabaseException {
return POSTGRESPLUS_PRODUCT_NAME.equalsIgnoreCase(conn.getDatabaseProductName());
}
@Override
public String getDefaultDriver(String url) {
String defaultDriver = super.getDefaultDriver(url);
if (defaultDriver == null) {
if (url.startsWith("jdbc:edb:")) {
defaultDriver = "com.edb.Driver";
}
}
return defaultDriver;
}
@Override
protected String getConnectionSchemaName() {
try {
return Scope.getCurrentScope().getSingleton(ExecutorService.class).getExecutor(LiquibaseConstants.JDBC_EXECUTOR, this)
.queryForObject(new RawSqlStatement("select current_schema"), String.class);
} catch (Exception e) {
throw new RuntimeException("Failed to get current schema", e);
}
}
}

View File

@@ -18,11 +18,10 @@
package org.keycloak.connections.jpa.updater.liquibase.conn;
import liquibase.Scope;
import liquibase.ScopeManager;
import liquibase.ThreadLocalScopeManager;
import liquibase.database.AbstractJdbcDatabase;
import liquibase.database.Database;
import liquibase.database.DatabaseFactory;
import liquibase.database.DatabaseConnection;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.LiquibaseException;
import liquibase.resource.ClassLoaderResourceAccessor;
@@ -30,6 +29,7 @@ import liquibase.resource.ResourceAccessor;
import liquibase.ui.LoggerUIService;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.config.DatabaseOptions;
import org.keycloak.connections.jpa.updater.liquibase.LiquibaseJpaUpdaterProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
@@ -49,6 +49,7 @@ public class DefaultLiquibaseConnectionProvider implements LiquibaseConnectionPr
public static final String INDEX_CREATION_THRESHOLD_PARAM = "keycloak.indexCreationThreshold";
private int indexCreationThreshold;
private Class<? extends Database> liquibaseDatabaseClazz;
private static final AtomicBoolean INITIALIZATION = new AtomicBoolean(false);
@@ -92,10 +93,26 @@ public class DefaultLiquibaseConnectionProvider implements LiquibaseConnectionPr
}
}
@SuppressWarnings("unchecked")
@Override
public void init(Config.Scope config) {
indexCreationThreshold = config.getInt("indexCreationThreshold", 300000);
logger.debugf("indexCreationThreshold is %d", indexCreationThreshold);
// We need to explicitly handle the default here as Config might not be MicroProfile and hence no actually server config exists
String dbAlias = config.root().get(DatabaseOptions.DB.getKey(), "dev-file");
logger.debugf("dbAlias is %s", dbAlias);
// We're not using the Liquibase logic to get the DB. That is because we already know which DB class we want to use
// for which DB vendor. We don't want to rely on auto-detection in Liquibase as it might make wrong assumptions (e.g. EDB).
String liquibaseType = org.keycloak.config.database.Database.getVendor(dbAlias).orElseThrow().getLiquibaseType();
logger.debugf("liquibaseType is %s", liquibaseType);
try {
liquibaseDatabaseClazz = (Class<? extends Database>) Class.forName(liquibaseType);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Failed to load Liquibase Database class: " + liquibaseType, e);
}
}
@Override
@@ -114,7 +131,7 @@ public class DefaultLiquibaseConnectionProvider implements LiquibaseConnectionPr
@Override
public KeycloakLiquibase getLiquibase(Connection connection, String defaultSchema) throws LiquibaseException {
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection));
Database database = getLiquibaseDatabase(connection);
if (defaultSchema != null) {
database.setDefaultSchemaName(defaultSchema);
}
@@ -130,7 +147,7 @@ public class DefaultLiquibaseConnectionProvider implements LiquibaseConnectionPr
@Override
public KeycloakLiquibase getLiquibaseForCustomUpdate(Connection connection, String defaultSchema, String changelogLocation, ClassLoader classloader, String changelogTableName) throws LiquibaseException {
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(new JdbcConnection(connection));
Database database = getLiquibaseDatabase(connection);
if (defaultSchema != null) {
database.setDefaultSchemaName(defaultSchema);
}
@@ -143,4 +160,25 @@ public class DefaultLiquibaseConnectionProvider implements LiquibaseConnectionPr
return new KeycloakLiquibase(changelogLocation, resourceAccessor, database);
}
// Similarly to Hibernate, we want to enforce Liquibase to use the same DB as configured in Keycloak
private Database getLiquibaseDatabase(Connection connection) {
Database liquibaseDatabase;
// Mimic what DatabaseFactory#findCorrectDatabaseImplementation does: create DB instance using reflections
try {
liquibaseDatabase = liquibaseDatabaseClazz.getConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException("Failed to create instance of " + liquibaseDatabaseClazz.getName());
}
DatabaseConnection liquibaseConnection = new JdbcConnection(connection);
try {
logger.debugf("DB Product Name: %s", liquibaseConnection.getDatabaseProductName());
} catch (LiquibaseException e) {
logger.debug("Failed to detect DB Product Name", e);
}
liquibaseDatabase.setConnection(liquibaseConnection);
return liquibaseDatabase;
}
}

View File

@@ -64,6 +64,10 @@
<validCheckSum>7:fad08e83c77d0171ec166bc9bc5d390a</validCheckSum>
<validCheckSum>7:72553fac2d2281052acbbbb14aa22ccf</validCheckSum>
<validCheckSum>7:b558ad47ea0e4d3c3514225a49cc0d65</validCheckSum>
<!-- EDB previously did not run this change set, now it does -->
<validCheckSum>8:f43dfba07ba249d5d932dc489fd2b886</validCheckSum>
<validCheckSum>9:bd2bd0fc7768cf0845ac96a8786fa735</validCheckSum>
<preConditions onSqlOutput="TEST" onFail="MARK_RAN">
<or>
<dbms type="mysql"/>

View File

@@ -18,6 +18,7 @@
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<changeSet author="keycloak" id="20.0.0-12964-supported-dbs">
<validCheckSum>9:e5f243877199fd96bcc842f27a1656ac</validCheckSum>
<preConditions onSqlOutput="TEST" onFail="MARK_RAN">
<or>
<dbms type="mysql"/>
@@ -38,6 +39,19 @@
</modifySql>
</changeSet>
<changeSet author="keycloak" id="20.0.0-12964-supported-dbs-edb-migration">
<preConditions onSqlOutput="TEST" onFail="MARK_RAN">
<dbms type="postgresql"/>
<changeSetExecuted id="20.0.0-12964-supported-dbs" author="keycloak" changeLogFile="META-INF/jpa-changelog-20.0.0.xml"/>
<indexExists tableName="GROUP_ATTRIBUTE" indexName="IDX_GROUP_ATT_BY_NAME_VALUE" />
</preConditions>
<dropIndex tableName="GROUP_ATTRIBUTE" indexName="IDX_GROUP_ATT_BY_NAME_VALUE" />
<createIndex tableName="GROUP_ATTRIBUTE" indexName="IDX_GROUP_ATT_BY_NAME_VALUE">
<column name="NAME" type="VARCHAR(255)"/>
<column name="(value::varchar(250))" valueComputed="(value::varchar(250))" />
</createIndex>
</changeSet>
<changeSet author="keycloak" id="20.0.0-12964-unsupported-dbs">
<preConditions onSqlOutput="TEST" onFail="MARK_RAN">
<not>

View File

@@ -83,4 +83,18 @@
</modifySql>
</changeSet>
<changeSet author="keycloak" id="24.0.0-26618-edb-migration">
<preConditions onSqlOutput="TEST" onFail="MARK_RAN">
<dbms type="postgresql"/>
<indexExists tableName="CLIENT_ATTRIBUTES" indexName="IDX_CLIENT_ATT_BY_NAME_VALUE" />
<changeSetExecuted id="24.0.0-26618-drop-index-if-present" author="keycloak" changeLogFile="META-INF/jpa-changelog-24.0.0.xml" />
<changeSetExecuted id="24.0.0-26618-reindex" author="keycloak" changeLogFile="META-INF/jpa-changelog-24.0.0.xml" />
</preConditions>
<dropIndex tableName="CLIENT_ATTRIBUTES" indexName="IDX_CLIENT_ATT_BY_NAME_VALUE"/>
<createIndex tableName="CLIENT_ATTRIBUTES" indexName="IDX_CLIENT_ATT_BY_NAME_VALUE">
<column name="NAME" type="VARCHAR(255)"/>
<column name="substr(VALUE,1,255)" valueComputed="substr(VALUE,1,255)" />
</createIndex>
</changeSet>
</databaseChangeLog>

View File

@@ -15,7 +15,7 @@
~ * See the License for the specific language governing permissions and
~ * limitations under the License.
-->
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<changeSet author="keycloak" id="25.0.0-28265-tables">
<addColumn tableName="OFFLINE_USER_SESSION">
@@ -144,6 +144,18 @@
<addUniqueConstraint columnNames="CLIENT_STORAGE_PROVIDER, EXTERNAL_CLIENT_ID, USER_ID" constraintName="UK_EXTERNAL_CONSENT" tableName="USER_CONSENT"/>
</changeSet>
<changeSet author="keycloak" id="unique-consentuser-edb-migration">
<preConditions onSqlOutput="TEST" onFail="MARK_RAN">
<dbms type="postgresql"/>
<changeSetExecuted id="unique-consentuser" author="keycloak" changeLogFile="META-INF/jpa-changelog-25.0.0.xml" />
<uniqueConstraintExists tableName="USER_CONSENT" constraintName="UK_JKUWUVD56ONTGSUHOGM8UEWRT"/>
</preConditions>
<customChange class="org.keycloak.connections.jpa.updater.liquibase.custom.JpaUpdate25_0_0_ConsentConstraints"/>
<dropUniqueConstraint tableName="USER_CONSENT" constraintName="UK_JKUWUVD56ONTGSUHOGM8UEWRT"/>
<addUniqueConstraint columnNames="CLIENT_ID, USER_ID" constraintName="UK_LOCAL_CONSENT" tableName="USER_CONSENT"/>
<addUniqueConstraint columnNames="CLIENT_STORAGE_PROVIDER, EXTERNAL_CLIENT_ID, USER_ID" constraintName="UK_EXTERNAL_CONSENT" tableName="USER_CONSENT"/>
</changeSet>
<changeSet author="keycloak" id="unique-consentuser-mysql">
<preConditions onSqlOutput="TEST" onFail="MARK_RAN">
<or>

View File

@@ -15,6 +15,5 @@
# limitations under the License.
#
org.keycloak.connections.jpa.updater.liquibase.PostgresPlusDatabase
org.keycloak.connections.jpa.updater.liquibase.UpdatedMariaDBDatabase
org.keycloak.connections.jpa.updater.liquibase.UpdatedMySqlDatabase