[KEYCLOAK-19310] - MSSQL Support

This commit is contained in:
Pedro Igor 2021-11-09 13:52:40 -03:00
parent 1f000fe218
commit 3a7f5171ab
21 changed files with 292 additions and 93 deletions

View file

@ -58,6 +58,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-mysql-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-mssql-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-bootstrap-core</artifactId>

View file

@ -9,6 +9,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import io.quarkus.agroal.spi.JdbcDataSourceBuildItem;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import org.jboss.jandex.AnnotationInstance;
import org.jboss.jandex.ClassInfo;
@ -35,11 +36,12 @@ class LiquibaseProcessor {
@Record(ExecutionTime.STATIC_INIT)
@BuildStep
void configure(KeycloakRecorder recorder, CombinedIndexBuildItem indexBuildItem) {
void configure(KeycloakRecorder recorder, List<JdbcDataSourceBuildItem> jdbcDataSources, CombinedIndexBuildItem indexBuildItem) {
DotName liquibaseServiceName = DotName.createSimple(LiquibaseService.class.getName());
Map<String, List<String>> services = new HashMap<>();
IndexView index = indexBuildItem.getIndex();
JdbcDataSourceBuildItem dataSourceBuildItem = jdbcDataSources.get(0);
String dbKind = dataSourceBuildItem.getDbKind();
for (Class<?> c : Arrays.asList(liquibase.diff.compare.DatabaseObjectComparator.class,
liquibase.parser.NamespaceDetails.class,
@ -61,7 +63,7 @@ class LiquibaseProcessor {
} else {
classes.addAll(index.getAllKnownSubclasses(DotName.createSimple(c.getName())));
}
filterImplementations(c, classes);
filterImplementations(c, dbKind, classes);
for (ClassInfo found : classes) {
if (Modifier.isAbstract(found.flags()) ||
Modifier.isInterface(found.flags()) ||
@ -85,10 +87,10 @@ class LiquibaseProcessor {
recorder.configureLiquibase(services);
}
private void filterImplementations(Class<?> types, Set<ClassInfo> classes) {
private void filterImplementations(Class<?> types, String dbKind, Set<ClassInfo> classes) {
if (Database.class.equals(types)) {
// removes unsupported databases
classes.removeIf(classInfo -> !org.keycloak.quarkus.runtime.storage.database.Database.isSupported(classInfo.name().toString()));
classes.removeIf(classInfo -> !org.keycloak.quarkus.runtime.storage.database.Database.isLiquibaseDatabaseSupported(classInfo.name().toString(), dbKind));
}
}
}

View file

@ -59,6 +59,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-mysql</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-mssql</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core</artifactId>

View file

@ -42,8 +42,6 @@ import liquibase.servicelocator.ServiceLocator;
@Recorder
public class KeycloakRecorder {
private static final Logger LOGGER = Logger.getLogger(KeycloakRecorder.class);
public void configureLiquibase(Map<String, List<String>> services) {
LogFactory.setInstance(new LogFactory() {
final KeycloakLogger logger = new KeycloakLogger();

View file

@ -52,7 +52,7 @@ public class OptionRenderer implements CommandLine.Help.IOptionRenderer {
throw new CommandLine.PicocliException("Option[" + option + "] description should have a single line.");
}
Text description = scheme.text(descriptions[0]);
Text description = formatDescription(descriptions, option, scheme);
if (EMPTY_TEXT.equals(shortName)) {
result[0] = new Text[] { longName, description };
@ -63,6 +63,17 @@ public class OptionRenderer implements CommandLine.Help.IOptionRenderer {
return result;
}
private Text formatDescription(String[] descriptions, OptionSpec option, ColorScheme scheme) {
String description = descriptions[0];
String defaultValue = option.defaultValue();
if (defaultValue != null) {
description = description + " Default: " + defaultValue + ".";
}
return scheme.text(description);
}
private Text createLongName(OptionSpec option, ColorScheme scheme) {
Text name = scheme.optionText(option.longestName());
String paramLabel = formatParamLabel(option);

View file

@ -67,6 +67,7 @@ public final class Picocli {
private static final String ARG_SEPARATOR = ";;";
public static final String ARG_PREFIX = "--";
public static final String ARG_SHORT_PREFIX = "-";
public static final String ARG_PART_SEPARATOR = "-";
public static final char ARG_KEY_VALUE_SEPARATOR = '=';
public static final Pattern ARG_SPLIT = Pattern.compile(";;");
@ -342,6 +343,7 @@ public final class Picocli {
.description("Enables a group of features. Possible values are: " + String.join(",", featuresExpectedValues))
.paramLabel("feature")
.completionCandidates(featuresExpectedValues)
.parameterConsumer(PropertyMapperParameterConsumer.INSTANCE)
.type(String.class)
.build());
@ -352,6 +354,7 @@ public final class Picocli {
.description("Enables the " + feature.name() + " feature.")
.paramLabel(String.join("|", expectedValues))
.type(String.class)
.parameterConsumer(PropertyMapperParameterConsumer.INSTANCE)
.completionCandidates(expectedValues)
.build());
}
@ -385,12 +388,14 @@ public final class Picocli {
}
String defaultValue = mapper.getDefaultValue();
Iterable<String> expectedValues = mapper.getExpectedValues();
argGroupBuilder.addArg(OptionSpec.builder(name)
.defaultValue(defaultValue)
.description(description + (defaultValue == null ? "" : " Default: ${DEFAULT-VALUE}."))
.description(description)
.paramLabel(mapper.getParamLabel())
.completionCandidates(mapper.getExpectedValues())
.completionCandidates(expectedValues)
.parameterConsumer(PropertyMapperParameterConsumer.INSTANCE)
.type(String.class)
.build());
}

View file

@ -0,0 +1,99 @@
/*
* 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.quarkus.runtime.cli;
import static org.keycloak.quarkus.runtime.cli.Picocli.ARG_PREFIX;
import java.util.Iterator;
import java.util.Stack;
import picocli.CommandLine;
import picocli.CommandLine.Model.ArgSpec;
import picocli.CommandLine.Model.CommandSpec;
import picocli.CommandLine.Model.OptionSpec;
import picocli.CommandLine.ParameterException;
public final class PropertyMapperParameterConsumer implements CommandLine.IParameterConsumer {
static final CommandLine.IParameterConsumer INSTANCE = new PropertyMapperParameterConsumer();
private PropertyMapperParameterConsumer() {
// singleton
}
@Override
public void consumeParameters(Stack<String> args, ArgSpec argSpec,
CommandSpec commandSpec) {
if (argSpec instanceof OptionSpec) {
validateOption(args, argSpec, commandSpec);
}
}
private void validateOption(Stack<String> args, ArgSpec argSpec, CommandSpec commandSpec) {
OptionSpec option = (OptionSpec) argSpec;
String name = String.join(", ", option.names());
CommandLine commandLine = commandSpec.commandLine();
if (args.isEmpty() || !isOptionValue(args.peek())) {
throw new ParameterException(
commandLine, "Missing required value for option '" + name + "' (" + argSpec.paramLabel() + ")." + getExpectedValuesMessage(argSpec, option));
}
// consumes the value
String value = args.pop();
if (!args.isEmpty() && isOptionValue(args.peek())) {
throw new ParameterException(
commandLine, "Option '" + name + "' expects a single value (" + argSpec.paramLabel() + ")" + getExpectedValuesMessage(argSpec, option));
}
if (isExpectedValue(option, value)) {
return;
}
throw new ParameterException(
commandLine, "Invalid value for option '" + name + "': " + value + "." + getExpectedValuesMessage(argSpec, option));
}
private boolean isOptionValue(String arg) {
return !(arg.startsWith(ARG_PREFIX) || arg.startsWith(Picocli.ARG_SHORT_PREFIX));
}
private String getExpectedValuesMessage(ArgSpec argSpec, OptionSpec option) {
return option.completionCandidates().iterator().hasNext() ? " Expected values are: " + String.join(", ", argSpec.completionCandidates()) : "";
}
private boolean isExpectedValue(OptionSpec option, String value) {
Iterator<String> expectedValues = option.completionCandidates().iterator();
if (!expectedValues.hasNext()) {
// accept any
return true;
}
while (expectedValues.hasNext()) {
String expectedValue = expectedValues.next();
if (expectedValue.equals(value)) {
return true;
}
}
return false;
}
}

View file

@ -19,8 +19,6 @@ package org.keycloak.quarkus.runtime.cli.command;
import static org.keycloak.quarkus.runtime.cli.Picocli.NO_PARAM_LABEL;
import org.keycloak.quarkus.runtime.cli.Picocli;
import picocli.CommandLine;
import picocli.CommandLine.Command;

View file

@ -26,7 +26,6 @@ import static org.keycloak.quarkus.runtime.configuration.MicroProfileConfigProvi
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Pattern;
import org.jboss.logging.Logger;

View file

@ -19,7 +19,6 @@ final class ClusteringPropertyMappers {
"Value 'default' enables clustering for infinispan caches.")
.paramLabel("mode")
.isBuildTimeProperty(true)
.expectedValues(Arrays.asList("local", "default"))
.build(),
builder().from("cluster-stack")
.to("kc.spi.connections-infinispan.quarkus.stack")

View file

@ -3,16 +3,15 @@ package org.keycloak.quarkus.runtime.configuration.mappers;
import io.smallrye.config.ConfigSourceInterceptorContext;
import org.keycloak.quarkus.runtime.storage.database.Database;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.BiFunction;
import static java.util.Arrays.asList;
import static org.keycloak.quarkus.runtime.configuration.mappers.Messages.invalidDatabaseVendor;
import static org.keycloak.quarkus.runtime.integration.QuarkusPlatform.addInitializationException;
final class DatabasePropertyMappers {
private static final String[] supportedDatabaseVendors = {"h2-file", "h2-mem", "mariadb", "mysql", "postgres", "postgres-95", "postgres-10"};
private DatabasePropertyMappers(){}
public static PropertyMapper[] getDatabasePropertyMappers() {
@ -29,10 +28,10 @@ final class DatabasePropertyMappers {
builder().from("db").
to("quarkus.datasource.db-kind")
.isBuildTimeProperty(true)
.transformer(getSupportedDbValue())
.description("The database vendor. Possible values are: " + String.join(",", supportedDatabaseVendors))
.transformer(toDatabaseKind())
.description("The database vendor. Possible values are: " + String.join(", ", Database.getAliases()))
.paramLabel("vendor")
.expectedValues(Arrays.asList(supportedDatabaseVendors))
.expectedValues(asList(Database.getAliases()))
.build(),
builder().from("db")
.to("quarkus.datasource.jdbc.transactions")
@ -83,13 +82,17 @@ final class DatabasePropertyMappers {
};
}
private static BiFunction<String, ConfigSourceInterceptorContext, String> getSupportedDbValue() {
private static BiFunction<String, ConfigSourceInterceptorContext, String> toDatabaseKind() {
return (db, context) -> {
if (Database.isSupported(db)) {
return Database.getDatabaseKind(db).orElse(db);
Optional<String> databaseKind = Database.getDatabaseKind(db);
if (databaseKind.isPresent()) {
return databaseKind.get();
}
addInitializationException(invalidDatabaseVendor(db, "h2-file", "h2-mem", "mariadb", "mysql", "postgres", "postgres-95", "postgres-10"));
return "h2";
addInitializationException(invalidDatabaseVendor(db, Database.getAliases()));
return null;
};
}

View file

@ -32,8 +32,6 @@ import org.keycloak.services.resources.admin.permissions.AdminPermissions;
public final class QuarkusKeycloakSessionFactory extends DefaultKeycloakSessionFactory {
private static final Logger logger = Logger.getLogger(QuarkusKeycloakSessionFactory.class);
public static QuarkusKeycloakSessionFactory getInstance() {
if (INSTANCE == null) {
INSTANCE = new QuarkusKeycloakSessionFactory();

View file

@ -46,7 +46,7 @@ public class QuarkusRequestFilter extends AbstractRequestFilter implements Handl
context.vertx().executeBlocking(promise -> {
ClientConnection connection = createClientConnection(context.request());
filter(connection, (session) -> {
filter(connection, session -> {
try {
configureContextualData(context, connection, session);

View file

@ -17,34 +17,35 @@
package org.keycloak.quarkus.runtime.storage.database;
import static java.util.Arrays.asList;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import liquibase.database.core.H2Database;
import liquibase.database.core.PostgresDatabase;
import org.keycloak.connections.jpa.updater.liquibase.PostgresPlusDatabase;
import org.keycloak.connections.jpa.updater.liquibase.UpdatedMariaDBDatabase;
import org.keycloak.connections.jpa.updater.liquibase.UpdatedMySqlDatabase;
public final class Database {
private static Map<String, Vendor> DATABASES = new HashMap<>();
private static final Map<String, Vendor> DATABASES = new HashMap<>();
static {
for (Vendor vendor : Vendor.values()) {
DATABASES.put(vendor.name().toLowerCase(), vendor);
for (String alias : vendor.aliases) {
DATABASES.put(alias, vendor);
}
}
}
public static boolean isSupported(String alias) {
return DATABASES.containsKey(alias);
public static boolean isLiquibaseDatabaseSupported(String databaseType, String dbKind) {
for (Vendor vendor : DATABASES.values()) {
if (vendor.liquibaseTypes.contains(databaseType) && vendor.isOfKind(dbKind)) {
return true;
}
}
return false;
}
public static Optional<String> getDatabaseKind(String alias) {
@ -87,28 +88,44 @@ public final class Database {
return Optional.of(vendor.dialect.apply(alias));
}
public static String[] getAliases() {
return DATABASES.keySet().stream().sorted().toArray(String[]::new);
}
private enum Vendor {
H2("h2", "org.h2.jdbcx.JdbcDataSource", "io.quarkus.hibernate.orm.runtime.dialect.QuarkusH2Dialect",
H2("h2",
"org.h2.jdbcx.JdbcDataSource",
"io.quarkus.hibernate.orm.runtime.dialect.QuarkusH2Dialect",
new Function<String, String>() {
@Override
public String apply(String alias) {
if ("h2-file".equalsIgnoreCase(alias)) {
return "jdbc:h2:file:${kc.home.dir:${kc.db.url.path:~}}" + File.separator + "${kc.data.dir:data}"
+ File.separator + "h2" + File.separator + "keycloakdb${kc.db.url.properties:;;AUTO_SERVER=TRUE}";
+ File.separator + "h2" + File.separator
+ "keycloakdb${kc.db.url.properties:;;AUTO_SERVER=TRUE}";
}
return "jdbc:h2:mem:keycloakdb${kc.db.url.properties:}";
}
}, "h2-mem", "h2-file", H2Database.class
.getName()),
MYSQL("mysql", "com.mysql.cj.jdbc.MysqlXADataSource", "org.hibernate.dialect.MySQL8Dialect",
},
asList("liquibase.database.core.H2Database"),
"h2-mem", "h2-file"
),
MYSQL("mysql",
"com.mysql.cj.jdbc.MysqlXADataSource",
"org.hibernate.dialect.MySQL8Dialect",
"jdbc:mysql://${kc.db.url.host:localhost}/${kc.db.url.database:keycloak}${kc.db.url.properties:}",
UpdatedMySqlDatabase.class
.getName()),
MARIADB("mariadb", "org.mariadb.jdbc.MySQLDataSource", "org.hibernate.dialect.MariaDBDialect",
asList("org.keycloak.connections.jpa.updater.liquibase.UpdatedMySqlDatabase")
),
MARIADB("mariadb",
"org.mariadb.jdbc.MySQLDataSource",
"org.hibernate.dialect.MariaDBDialect",
"jdbc:mariadb://${kc.db.url.host:localhost}/${kc.db.url.database:keycloak}${kc.db.url.properties:}",
UpdatedMariaDBDatabase.class
.getName()),
POSTGRES("postgresql", "org.postgresql.xa.PGXADataSource", new Function<String, String>() {
asList("org.keycloak.connections.jpa.updater.liquibase.UpdatedMariaDBDatabase")
),
POSTGRES("postgresql",
"org.postgresql.xa.PGXADataSource",
new Function<String, String>() {
@Override
public String apply(String alias) {
if ("postgres-95".equalsIgnoreCase(alias)) {
@ -116,34 +133,64 @@ public final class Database {
}
return "io.quarkus.hibernate.orm.runtime.dialect.QuarkusPostgreSQL10Dialect";
}
}, "jdbc:postgresql://${kc.db.url.host:localhost}/${kc.db.url.database:keycloak}${kc.db.url.properties:}",
"postgres-95", "postgres-10", PostgresDatabase.class.getName(), PostgresPlusDatabase.class.getName());
},
"jdbc:postgresql://${kc.db.url.host:localhost}/${kc.db.url.database:keycloak}${kc.db.url.properties:}",
asList("liquibase.database.core.PostgresDatabase",
"org.keycloak.connections.jpa.updater.liquibase.PostgresPlusDatabase"),
"postgres", "postgres-95"
),
MSSQL("mssql",
"com.microsoft.sqlserver.jdbc.SQLServerXADataSource",
new Function<String, String>() {
@Override
public String apply(String alias) {
if ("mssql-12".equals(alias)) {
return "org.hibernate.dialect.SQLServer2012Dialect";
}
// quarkus latest/default
return "org.hibernate.dialect.SQLServer2016Dialect";
}
},
"jdbc:sqlserver://${kc.db.url.host:localhost}:1433;databaseName=${kc.db.url.database:keycloak}${kc.db.url.properties:}",
asList("org.keycloak.quarkus.runtime.storage.database.liquibase.database.CustomMSSQLDatabase"),
"mssql", "mssql-2012"
);
final String databaseKind;
final String driver;
final Function<String, String> dialect;
final Function<String, String> defaultUrl;
final List<String> liquibaseTypes;
final String[] aliases;
Vendor(String databaseKind, String driver, String dialect, String defaultUrl, String... aliases) {
this(databaseKind, driver, (alias) -> dialect, (alias) -> defaultUrl, aliases);
Vendor(String databaseKind, String driver, String dialect, String defaultUrl, List<String> liquibaseTypes,
String... aliases) {
this(databaseKind, driver, alias -> dialect, alias -> defaultUrl, liquibaseTypes, aliases);
}
Vendor(String databaseKind, String driver, String dialect, Function<String, String> defaultUrl, String... aliases) {
this(databaseKind, driver, (alias) -> dialect, defaultUrl, aliases);
Vendor(String databaseKind, String driver, String dialect, Function<String, String> defaultUrl,
List<String> liquibaseTypes, String... aliases) {
this(databaseKind, driver, alias -> dialect, defaultUrl, liquibaseTypes, aliases);
}
Vendor(String databaseKind, String driver, Function<String, String> dialect, String defaultUrl, String... aliases) {
this(databaseKind, driver, dialect, (alias) -> defaultUrl, aliases);
Vendor(String databaseKind, String driver, Function<String, String> dialect, String defaultUrl,
List<String> liquibaseTypes, String... aliases) {
this(databaseKind, driver, dialect, alias -> defaultUrl, liquibaseTypes, aliases);
}
Vendor(String databaseKind, String driver, Function<String, String> dialect, Function<String, String> defaultUrl,
List<String> liquibaseTypes,
String... aliases) {
this.databaseKind = databaseKind;
this.driver = driver;
this.dialect = dialect;
this.defaultUrl = defaultUrl;
this.aliases = aliases;
this.liquibaseTypes = liquibaseTypes;
this.aliases = aliases.length == 0 ? new String[] { databaseKind } : aliases;
}
public boolean isOfKind(String dbKind) {
return databaseKind.equals(dbKind);
}
}
}

View file

@ -45,7 +45,6 @@ import javax.transaction.Transaction;
import com.fasterxml.jackson.core.type.TypeReference;
import io.quarkus.runtime.Quarkus;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.SessionFactoryImpl;
import org.jboss.logging.Logger;
import org.keycloak.Config;
@ -117,7 +116,6 @@ public final class QuarkusJpaConnectionProviderFactory implements JpaConnectionP
}
private void addSpecificNamedQueries(KeycloakSession session, Connection connection) {
SessionFactoryImplementor sfi = emf.unwrap(SessionFactoryImplementor.class);
EntityManager em = createEntityManager(session);
try {

View file

@ -79,6 +79,15 @@ public class FastServiceLocator extends ServiceLocator {
getPackages().remove("liquibase.parser.core.json");
getPackages().remove("liquibase.serializer.core.json");
// register only the implementations related to the chosen db
for (String databaseImpl : services.get(Database.class.getName())) {
try {
register((Database) getClass().getClassLoader().loadClass(databaseImpl).getDeclaredConstructor().newInstance());
} catch (Exception cause) {
throw new RuntimeException("Failed to load database implementation", cause);
}
}
this.services = services;
}

View file

@ -24,7 +24,7 @@ import liquibase.logging.Logger;
public class KeycloakLogger implements Logger {
private static final org.jboss.logging.Logger logger = org.jboss.logging.Logger.getLogger(QuarkusLiquibaseConnectionProvider.class);
private static final org.jboss.logging.Logger logger = org.jboss.logging.Logger.getLogger(KeycloakLogger.class);
@Override
public void setName(String name) {

View file

@ -29,9 +29,6 @@ import org.keycloak.Config;
import org.keycloak.connections.jpa.JpaConnectionProvider;
import org.keycloak.connections.jpa.JpaConnectionProviderFactory;
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;
@ -79,11 +76,6 @@ public class QuarkusLiquibaseConnectionProvider implements LiquibaseConnectionPr
JpaConnectionProviderFactory jpaConnectionProvider = (JpaConnectionProviderFactory) session
.getKeycloakSessionFactory().getProviderFactory(JpaConnectionProvider.class);
// register our custom databases
locator.register(new PostgresPlusDatabase());
locator.register(new UpdatedMySqlDatabase());
locator.register(new UpdatedMariaDBDatabase());
// registers only the database we are using
try (Connection connection = jpaConnectionProvider.getConnection()) {
Database database = DatabaseFactory.getInstance()

View file

@ -0,0 +1,35 @@
/*
* 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.quarkus.runtime.storage.database.liquibase.database;
import liquibase.database.core.MSSQLDatabase;
public class CustomMSSQLDatabase extends MSSQLDatabase {
private static String ENGINE_EDITION;
@Override
public String getEngineEdition() {
// no need to query engine edition every time
// it should be safe to update without any synchronization code as liquibase runs from a single thread
if (ENGINE_EDITION == null) {
return ENGINE_EDITION = super.getEngineEdition();
}
return ENGINE_EDITION;
}
}

View file

@ -26,8 +26,6 @@ import org.keycloak.Config;
public class CacheInitializer {
private static final Logger log = Logger.getLogger(CacheInitializer.class);
private final String config;
public CacheInitializer(String config) {

View file

@ -235,7 +235,7 @@ public class ConfigurationTest {
@Test
public void testDatabaseKindProperties() {
System.setProperty(CLI_ARGS, "--db=postgres-10" + ARG_SEPARATOR + "--db-url=jdbc:postgresql://localhost/keycloak");
System.setProperty(CLI_ARGS, "--db=postgres" + ARG_SEPARATOR + "--db-url=jdbc:postgresql://localhost/keycloak");
SmallRyeConfig config = createConfig();
assertEquals("io.quarkus.hibernate.orm.runtime.dialect.QuarkusPostgreSQL10Dialect",
config.getConfigValue("quarkus.hibernate-orm.dialect").getValue());