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.database>keycloak</keycloak.connectionsJpa.database>
|
||||||
<keycloak.connectionsJpa.user>sa</keycloak.connectionsJpa.user>
|
<keycloak.connectionsJpa.user>sa</keycloak.connectionsJpa.user>
|
||||||
<keycloak.connectionsJpa.password></keycloak.connectionsJpa.password>
|
<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.groupId>com.h2database</jdbc.mvn.groupId>
|
||||||
<jdbc.mvn.artifactId>h2</jdbc.mvn.artifactId>
|
<jdbc.mvn.artifactId>h2</jdbc.mvn.artifactId>
|
||||||
<jdbc.mvn.version>${h2.version}</jdbc.mvn.version>
|
<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);
|
properties.put(AvailableSettings.JPA_NON_JTA_DATASOURCE, dataSource);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
properties.put(AvailableSettings.JPA_JDBC_URL, config.get("url"));
|
String url = config.get("url");
|
||||||
properties.put(AvailableSettings.JPA_JDBC_DRIVER, config.get("driver"));
|
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");
|
String user = config.get("user");
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
|
@ -319,6 +324,7 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
|
||||||
return sql2012Dialect;
|
return sql2012Dialect;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For Oracle19c, we may need to set dialect explicitly to workaround https://hibernate.atlassian.net/browse/HHH-13184
|
// 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) {
|
if (dbProductName.equals("Oracle") && connection.getMetaData().getDatabaseMajorVersion() > 12) {
|
||||||
logger.debugf("Manually specify dialect for Oracle to org.hibernate.dialect.Oracle12cDialect");
|
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);
|
DataSource dataSource = (DataSource) new InitialContext().lookup(dataSourceLookup);
|
||||||
return dataSource.getConnection();
|
return dataSource.getConnection();
|
||||||
} else {
|
} else {
|
||||||
Class.forName(config.get("driver"));
|
String url = config.get("url");
|
||||||
return DriverManager.getConnection(StringPropertyReplacer.replaceProperties(config.get("url"), System.getProperties()), config.get("user"), config.get("password"));
|
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) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Failed to connect to database", e);
|
throw new RuntimeException("Failed to connect to database", e);
|
||||||
|
@ -448,4 +459,23 @@ public class DefaultJpaConnectionProviderFactory implements JpaConnectionProvide
|
||||||
private void migrateModel(KeycloakSession session) {
|
private void migrateModel(KeycloakSession session) {
|
||||||
KeycloakModelUtils.runJobInTransaction(session.getKeycloakSessionFactory(), MigrationModelManager::migrate);
|
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();
|
CriteriaBuilder cb = em.getCriteriaBuilder();
|
||||||
CriteriaQuery<RealmAttributeEntity> cr = cb.createQuery(RealmAttributeEntity.class);
|
CriteriaQuery<RealmAttributeEntity> cr = cb.createQuery(RealmAttributeEntity.class);
|
||||||
Root<RealmAttributeEntity> root = cr.from(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))));
|
// 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
|
||||||
Map<Long, List<RealmAttributeEntity>> realms = em.createQuery(cr).getResultStream().collect(Collectors.groupingBy(attribute -> Long.valueOf(attribute.getValue())));
|
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();
|
long current = Time.currentTimeMillis();
|
||||||
realms.entrySet().forEach(entry -> {
|
realms.forEach((key, value) -> {
|
||||||
List<String> realmIds = entry.getValue().stream().map(RealmAttributeEntity::getRealm).map(RealmEntity::getId).collect(Collectors.toList());
|
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")
|
int currentNumDeleted = em.createQuery("delete from AdminEventEntity where realmId in :realmIds and time < :eventTime")
|
||||||
.setParameter("realmIds", realmIds)
|
.setParameter("realmIds", realmIds)
|
||||||
.setParameter("eventTime", current - (Long.valueOf(entry.getKey()) * 1000))
|
.setParameter("eventTime", current - (key * 1000))
|
||||||
.executeUpdate();
|
.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.jaxrs.version>3.3.10</cxf.jaxrs.version>
|
||||||
<cxf.undertow.version>3.3.10</cxf.undertow.version>
|
<cxf.undertow.version>3.3.10</cxf.undertow.version>
|
||||||
<dom4j.version>2.1.3</dom4j.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>
|
<jakarta.persistence.version>2.2.3</jakarta.persistence.version>
|
||||||
<hibernate.core.version>5.3.24.Final</hibernate.core.version>
|
<hibernate.core.version>5.6.10.Final</hibernate.core.version>
|
||||||
<hibernate.c3p0.version>5.3.24.Final</hibernate.c3p0.version>
|
<hibernate.c3p0.version>5.6.10.Final</hibernate.c3p0.version>
|
||||||
<infinispan.version>13.0.10.Final</infinispan.version>
|
<infinispan.version>13.0.10.Final</infinispan.version>
|
||||||
<infinispan.protostream.processor.version>4.4.1.Final</infinispan.protostream.processor.version>
|
<infinispan.protostream.processor.version>4.4.1.Final</infinispan.protostream.processor.version>
|
||||||
<javax.annotation-api.version>1.3.2</javax.annotation-api.version>
|
<javax.annotation-api.version>1.3.2</javax.annotation-api.version>
|
||||||
|
|
|
@ -107,11 +107,29 @@ public final class Database {
|
||||||
@Override
|
@Override
|
||||||
public String apply(String alias) {
|
public String apply(String alias) {
|
||||||
if ("dev-file".equalsIgnoreCase(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
|
+ 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"),
|
asList("liquibase.database.core.H2Database"),
|
||||||
|
|
|
@ -252,12 +252,12 @@ public class ConfigurationTest {
|
||||||
System.setProperty(CLI_ARGS, "--db=dev-file");
|
System.setProperty(CLI_ARGS, "--db=dev-file");
|
||||||
SmallRyeConfig config = createConfig();
|
SmallRyeConfig config = createConfig();
|
||||||
assertEquals(QuarkusH2Dialect.class.getName(), config.getConfigValue("quarkus.hibernate-orm.dialect").getValue());
|
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");
|
System.setProperty(CLI_ARGS, "--db=dev-mem");
|
||||||
config = createConfig();
|
config = createConfig();
|
||||||
assertEquals(QuarkusH2Dialect.class.getName(), config.getConfigValue("quarkus.hibernate-orm.dialect").getValue());
|
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());
|
assertEquals("h2", config.getConfigValue("quarkus.datasource.db-kind").getValue());
|
||||||
|
|
||||||
System.setProperty(CLI_ARGS, "--db=dev-mem" + ARG_SEPARATOR + "--db-username=other");
|
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");
|
System.setProperty(CLI_ARGS, "--db=dev-file");
|
||||||
SmallRyeConfig config = createConfig();
|
SmallRyeConfig config = createConfig();
|
||||||
assertEquals(QuarkusH2Dialect.class.getName(), config.getConfigValue("quarkus.hibernate-orm.dialect").getValue());
|
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());
|
assertEquals("xa", config.getConfigValue("quarkus.datasource.jdbc.transactions").getValue());
|
||||||
|
|
||||||
System.setProperty(CLI_ARGS, "");
|
System.setProperty(CLI_ARGS, "");
|
||||||
config = createConfig();
|
config = createConfig();
|
||||||
assertEquals(QuarkusH2Dialect.class.getName(), config.getConfigValue("quarkus.hibernate-orm.dialect").getValue());
|
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("kc.db-url-properties", "?test=test&test1=test1");
|
||||||
System.setProperty(CLI_ARGS, "--db=mariadb");
|
System.setProperty(CLI_ARGS, "--db=mariadb");
|
||||||
|
|
|
@ -436,7 +436,7 @@
|
||||||
<keycloak.connectionsJpa.database>keycloak</keycloak.connectionsJpa.database>
|
<keycloak.connectionsJpa.database>keycloak</keycloak.connectionsJpa.database>
|
||||||
<keycloak.connectionsJpa.user>sa</keycloak.connectionsJpa.user>
|
<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>
|
||||||
</properties>
|
</properties>
|
||||||
</profile>
|
</profile>
|
||||||
<profile>
|
<profile>
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<keycloak.connectionsJpa.database>keycloak</keycloak.connectionsJpa.database>
|
<keycloak.connectionsJpa.database>keycloak</keycloak.connectionsJpa.database>
|
||||||
<keycloak.connectionsJpa.user>sa</keycloak.connectionsJpa.user>
|
<keycloak.connectionsJpa.user>sa</keycloak.connectionsJpa.user>
|
||||||
<keycloak.connectionsJpa.password></keycloak.connectionsJpa.password>
|
<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.groupId>com.h2database</jdbc.mvn.groupId>
|
||||||
<jdbc.mvn.artifactId>h2</jdbc.mvn.artifactId>
|
<jdbc.mvn.artifactId>h2</jdbc.mvn.artifactId>
|
||||||
<jdbc.mvn.version>${h2.version}</jdbc.mvn.version>
|
<jdbc.mvn.version>${h2.version}</jdbc.mvn.version>
|
||||||
|
|
Loading…
Reference in a new issue