Updating H2 database to 2.x
Closes #12607 Co-authored-by: Stian Thorgersen <stian@redhat.com>
This commit is contained in:
parent
f49582cf63
commit
97c4495c4f
8 changed files with 88 additions and 25 deletions
|
@ -34,7 +34,7 @@
|
|||
<keycloak.connectionsJpa.database>keycloak</keycloak.connectionsJpa.database>
|
||||
<keycloak.connectionsJpa.user>sa</keycloak.connectionsJpa.user>
|
||||
<keycloak.connectionsJpa.password></keycloak.connectionsJpa.password>
|
||||
<keycloak.connectionsJpa.url>jdbc:h2:mem:test;MVCC=TRUE;DB_CLOSE_DELAY=-1</keycloak.connectionsJpa.url>
|
||||
<keycloak.connectionsJpa.url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1</keycloak.connectionsJpa.url>
|
||||
<jdbc.mvn.groupId>com.h2database</jdbc.mvn.groupId>
|
||||
<jdbc.mvn.artifactId>h2</jdbc.mvn.artifactId>
|
||||
<jdbc.mvn.version>${h2.version}</jdbc.mvn.version>
|
||||
|
|
|
@ -175,8 +175,13 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
|
|||
properties.put(AvailableSettings.JPA_NON_JTA_DATASOURCE, dataSource);
|
||||
}
|
||||
} else {
|
||||
properties.put(AvailableSettings.JPA_JDBC_URL, config.get("url"));
|
||||
properties.put(AvailableSettings.JPA_JDBC_DRIVER, config.get("driver"));
|
||||
String url = config.get("url");
|
||||
String driver = config.get("driver");
|
||||
if (driver.equals("org.h2.Driver")) {
|
||||
url = addH2NonKeywords(url);
|
||||
}
|
||||
properties.put(AvailableSettings.JPA_JDBC_URL, url);
|
||||
properties.put(AvailableSettings.JPA_JDBC_DRIVER, driver);
|
||||
|
||||
String user = config.get("user");
|
||||
if (user != null) {
|
||||
|
@ -319,6 +324,7 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
|
|||
return sql2012Dialect;
|
||||
}
|
||||
}
|
||||
|
||||
// For Oracle19c, we may need to set dialect explicitly to workaround https://hibernate.atlassian.net/browse/HHH-13184
|
||||
if (dbProductName.equals("Oracle") && connection.getMetaData().getDatabaseMajorVersion() > 12) {
|
||||
logger.debugf("Manually specify dialect for Oracle to org.hibernate.dialect.Oracle12cDialect");
|
||||
|
@ -413,8 +419,13 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
|
|||
DataSource dataSource = (DataSource) new InitialContext().lookup(dataSourceLookup);
|
||||
return dataSource.getConnection();
|
||||
} else {
|
||||
Class.forName(config.get("driver"));
|
||||
return DriverManager.getConnection(StringPropertyReplacer.replaceProperties(config.get("url"), System.getProperties()), config.get("user"), config.get("password"));
|
||||
String url = config.get("url");
|
||||
String driver = config.get("driver");
|
||||
if (driver.equals("org.h2.Driver")) {
|
||||
url = addH2NonKeywords(url);
|
||||
}
|
||||
Class.forName(driver);
|
||||
return DriverManager.getConnection(StringPropertyReplacer.replaceProperties(url, System.getProperties()), config.get("user"), config.get("password"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to connect to database", e);
|
||||
|
@ -448,4 +459,23 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
|
|||
private void migrateModel(KeycloakSession session) {
|
||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), MigrationModelManager::migrate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starting with H2 version 2.x, marking "VALUE" as a non-keyword is necessary as some columns are named "VALUE" in the Keycloak schema.
|
||||
* <p />
|
||||
* Alternatives considered and rejected:
|
||||
* <ul>
|
||||
* <li>customizing H2 Database dialect -> wouldn't work for existing Liquibase scripts.</li>
|
||||
* <li>adding quotes to <code>@Column(name="VALUE")</code> annotations -> would require testing for all DBs, wouldn't work for existing Liquibase scripts.</li>
|
||||
* </ul>
|
||||
* Downsides of this solution: Release notes needed to point out that any H2 JDBC URL parameter with <code>NON_KEYWORDS</code> needs to add the keyword <code>VALUE</code> manually.
|
||||
* @return JDBC URL with <code>NON_KEYWORDS=VALUE</code> appended if the URL doesn't contain <code>NON_KEYWORDS=</code> yet
|
||||
*/
|
||||
private String addH2NonKeywords(String jdbcUrl) {
|
||||
if (!jdbcUrl.contains("NON_KEYWORDS=")) {
|
||||
jdbcUrl = jdbcUrl + ";NON_KEYWORDS=VALUE";
|
||||
}
|
||||
return jdbcUrl;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -276,17 +276,32 @@ public class JpaEventStoreProvider implements EventStoreProvider {
|
|||
CriteriaBuilder cb = em.getCriteriaBuilder();
|
||||
CriteriaQuery<RealmAttributeEntity> cr = cb.createQuery(RealmAttributeEntity.class);
|
||||
Root<RealmAttributeEntity> root = cr.from(RealmAttributeEntity.class);
|
||||
cr.select(root).where(cb.and(cb.equal(root.get("name"),RealmAttributes.ADMIN_EVENTS_EXPIRATION),cb.greaterThan(root.get("value").as(Long.class),Long.valueOf(0))));
|
||||
Map<Long, List<RealmAttributeEntity>> realms = em.createQuery(cr).getResultStream().collect(Collectors.groupingBy(attribute -> Long.valueOf(attribute.getValue())));
|
||||
// unable to cast the CLOB to a BIGINT in the select for H2 2.x, therefore comparing strings only in the DB, and filtering again in the next statement
|
||||
cr.select(root).where(cb.and(cb.equal(root.get("name"),RealmAttributes.ADMIN_EVENTS_EXPIRATION),cb.notEqual(root.get("value"), "0")));
|
||||
Map<Long, List<RealmAttributeEntity>> realms = em.createQuery(cr).getResultStream()
|
||||
// filtering again on the attribute as paring the CLOB to BIGINT didn't work in H2 2.x
|
||||
.filter(attribute -> {
|
||||
try {
|
||||
return Long.parseLong(attribute.getValue()) > 0;
|
||||
} catch (NumberFormatException ex) {
|
||||
logger.warnf("Unable to parse value '%s' for attribute '%s' in realm '%s' (expecting it to be decimal numeric)",
|
||||
attribute.getValue(),
|
||||
RealmAttributes.ADMIN_EVENTS_EXPIRATION,
|
||||
attribute.getRealm().getId(),
|
||||
ex);
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.collect(Collectors.groupingBy(attribute -> Long.valueOf(attribute.getValue())));
|
||||
|
||||
long current = Time.currentTimeMillis();
|
||||
realms.entrySet().forEach(entry -> {
|
||||
List<String> realmIds = entry.getValue().stream().map(RealmAttributeEntity::getRealm).map(RealmEntity::getId).collect(Collectors.toList());
|
||||
realms.forEach((key, value) -> {
|
||||
List<String> realmIds = value.stream().map(RealmAttributeEntity::getRealm).map(RealmEntity::getId).collect(Collectors.toList());
|
||||
int currentNumDeleted = em.createQuery("delete from AdminEventEntity where realmId in :realmIds and time < :eventTime")
|
||||
.setParameter("realmIds", realmIds)
|
||||
.setParameter("eventTime", current - (Long.valueOf(entry.getKey()) * 1000))
|
||||
.setParameter("eventTime", current - (key * 1000))
|
||||
.executeUpdate();
|
||||
logger.tracef("Deleted %d admin events for the expiration %d", currentNumDeleted, entry.getKey());
|
||||
logger.tracef("Deleted %d admin events for the expiration %d", currentNumDeleted, key);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
6
pom.xml
6
pom.xml
|
@ -88,10 +88,10 @@
|
|||
<cxf.jaxrs.version>3.3.10</cxf.jaxrs.version>
|
||||
<cxf.undertow.version>3.3.10</cxf.undertow.version>
|
||||
<dom4j.version>2.1.3</dom4j.version>
|
||||
<h2.version>1.4.197</h2.version>
|
||||
<h2.version>2.1.214</h2.version>
|
||||
<jakarta.persistence.version>2.2.3</jakarta.persistence.version>
|
||||
<hibernate.core.version>5.3.24.Final</hibernate.core.version>
|
||||
<hibernate.c3p0.version>5.3.24.Final</hibernate.c3p0.version>
|
||||
<hibernate.core.version>5.6.10.Final</hibernate.core.version>
|
||||
<hibernate.c3p0.version>5.6.10.Final</hibernate.c3p0.version>
|
||||
<infinispan.version>13.0.10.Final</infinispan.version>
|
||||
<infinispan.protostream.processor.version>4.4.1.Final</infinispan.protostream.processor.version>
|
||||
<javax.annotation-api.version>1.3.2</javax.annotation-api.version>
|
||||
|
|
|
@ -107,11 +107,29 @@ public final class Database {
|
|||
@Override
|
||||
public String apply(String alias) {
|
||||
if ("dev-file".equalsIgnoreCase(alias)) {
|
||||
return "jdbc:h2:file:${kc.home.dir:${kc.db-url-path:" + System.getProperty("user.home") + "}}" + File.separator + "${kc.data.dir:data}"
|
||||
return addH2NonKeywords("jdbc:h2:file:${kc.home.dir:${kc.db-url-path:" + System.getProperty("user.home") + "}}" + File.separator + "${kc.data.dir:data}"
|
||||
+ File.separator + "h2" + File.separator
|
||||
+ "keycloakdb${kc.db-url-properties:;;AUTO_SERVER=TRUE}";
|
||||
+ "keycloakdb${kc.db-url-properties:;;AUTO_SERVER=TRUE}");
|
||||
}
|
||||
return "jdbc:h2:mem:keycloakdb${kc.db-url-properties:}";
|
||||
return addH2NonKeywords("jdbc:h2:mem:keycloakdb${kc.db-url-properties:}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Starting with H2 version 2.x, marking "VALUE" as a non-keyword is necessary as some columns are named "VALUE" in the Keycloak schema.
|
||||
* <p />
|
||||
* Alternatives considered and rejected:
|
||||
* <ul>
|
||||
* <li>customizing H2 Database dialect -> wouldn't work for existing Liquibase scripts.</li>
|
||||
* <li>adding quotes to <code>@Column(name="VALUE")</code> annotations -> would require testing for all DBs, wouldn't work for existing Liquibase scripts.</li>
|
||||
* </ul>
|
||||
* Downsides of this solution: Release notes needed to point out that any H2 JDBC URL parameter with <code>NON_KEYWORDS</code> needs to add the keyword <code>VALUE</code> manually.
|
||||
* @return JDBC URL with <code>NON_KEYWORDS=VALUE</code> appended if the URL doesn't contain <code>NON_KEYWORDS=</code> yet
|
||||
*/
|
||||
private String addH2NonKeywords(String jdbcUrl) {
|
||||
if (!jdbcUrl.contains("NON_KEYWORDS=")) {
|
||||
jdbcUrl = jdbcUrl + ";NON_KEYWORDS=VALUE";
|
||||
}
|
||||
return jdbcUrl;
|
||||
}
|
||||
},
|
||||
asList("liquibase.database.core.H2Database"),
|
||||
|
|
|
@ -252,12 +252,12 @@ public class ConfigurationTest {
|
|||
System.setProperty(CLI_ARGS, "--db=dev-file");
|
||||
SmallRyeConfig config = createConfig();
|
||||
assertEquals(QuarkusH2Dialect.class.getName(), config.getConfigValue("quarkus.hibernate-orm.dialect").getValue());
|
||||
assertEquals("jdbc:h2:file:" + System.getProperty("user.home") + File.separator + "data" + File.separator + "h2" + File.separator + "keycloakdb;;AUTO_SERVER=TRUE", config.getConfigValue("quarkus.datasource.jdbc.url").getValue());
|
||||
assertEquals("jdbc:h2:file:" + System.getProperty("user.home") + File.separator + "data" + File.separator + "h2" + File.separator + "keycloakdb;;AUTO_SERVER=TRUE;NON_KEYWORDS=VALUE", config.getConfigValue("quarkus.datasource.jdbc.url").getValue());
|
||||
|
||||
System.setProperty(CLI_ARGS, "--db=dev-mem");
|
||||
config = createConfig();
|
||||
assertEquals(QuarkusH2Dialect.class.getName(), config.getConfigValue("quarkus.hibernate-orm.dialect").getValue());
|
||||
assertEquals("jdbc:h2:mem:keycloakdb", config.getConfigValue("quarkus.datasource.jdbc.url").getValue());
|
||||
assertEquals("jdbc:h2:mem:keycloakdb;NON_KEYWORDS=VALUE", config.getConfigValue("quarkus.datasource.jdbc.url").getValue());
|
||||
assertEquals("h2", config.getConfigValue("quarkus.datasource.db-kind").getValue());
|
||||
|
||||
System.setProperty(CLI_ARGS, "--db=dev-mem" + ARG_SEPARATOR + "--db-username=other");
|
||||
|
@ -320,13 +320,13 @@ public class ConfigurationTest {
|
|||
System.setProperty(CLI_ARGS, "--db=dev-file");
|
||||
SmallRyeConfig config = createConfig();
|
||||
assertEquals(QuarkusH2Dialect.class.getName(), config.getConfigValue("quarkus.hibernate-orm.dialect").getValue());
|
||||
assertEquals("jdbc:h2:file:test-dir" + File.separator + "data" + File.separator + "h2" + File.separator + "keycloakdb;;test=test;test1=test1", config.getConfigValue("quarkus.datasource.jdbc.url").getValue());
|
||||
assertEquals("jdbc:h2:file:test-dir" + File.separator + "data" + File.separator + "h2" + File.separator + "keycloakdb;;test=test;test1=test1;NON_KEYWORDS=VALUE", config.getConfigValue("quarkus.datasource.jdbc.url").getValue());
|
||||
assertEquals("xa", config.getConfigValue("quarkus.datasource.jdbc.transactions").getValue());
|
||||
|
||||
System.setProperty(CLI_ARGS, "");
|
||||
config = createConfig();
|
||||
assertEquals(QuarkusH2Dialect.class.getName(), config.getConfigValue("quarkus.hibernate-orm.dialect").getValue());
|
||||
assertEquals("jdbc:h2:file:test-dir" + File.separator + "data" + File.separator + "h2" + File.separator + "keycloakdb;;test=test;test1=test1", config.getConfigValue("quarkus.datasource.jdbc.url").getValue());
|
||||
assertEquals("jdbc:h2:file:test-dir" + File.separator + "data" + File.separator + "h2" + File.separator + "keycloakdb;;test=test;test1=test1;NON_KEYWORDS=VALUE", config.getConfigValue("quarkus.datasource.jdbc.url").getValue());
|
||||
|
||||
System.setProperty("kc.db-url-properties", "?test=test&test1=test1");
|
||||
System.setProperty(CLI_ARGS, "--db=mariadb");
|
||||
|
|
|
@ -436,7 +436,7 @@
|
|||
<keycloak.connectionsJpa.database>keycloak</keycloak.connectionsJpa.database>
|
||||
<keycloak.connectionsJpa.user>sa</keycloak.connectionsJpa.user>
|
||||
<keycloak.connectionsJpa.password/>
|
||||
<keycloak.connectionsJpa.url>jdbc:h2:mem:test;MVCC=TRUE;DB_CLOSE_DELAY=-1</keycloak.connectionsJpa.url>
|
||||
<keycloak.connectionsJpa.url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1</keycloak.connectionsJpa.url>
|
||||
</properties>
|
||||
</profile>
|
||||
<profile>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
<keycloak.connectionsJpa.database>keycloak</keycloak.connectionsJpa.database>
|
||||
<keycloak.connectionsJpa.user>sa</keycloak.connectionsJpa.user>
|
||||
<keycloak.connectionsJpa.password></keycloak.connectionsJpa.password>
|
||||
<keycloak.connectionsJpa.url>jdbc:h2:mem:test;MVCC=TRUE;DB_CLOSE_DELAY=-1</keycloak.connectionsJpa.url>
|
||||
<keycloak.connectionsJpa.url>jdbc:h2:mem:test;DB_CLOSE_DELAY=-1</keycloak.connectionsJpa.url>
|
||||
<jdbc.mvn.groupId>com.h2database</jdbc.mvn.groupId>
|
||||
<jdbc.mvn.artifactId>h2</jdbc.mvn.artifactId>
|
||||
<jdbc.mvn.version>${h2.version}</jdbc.mvn.version>
|
||||
|
|
Loading…
Reference in a new issue