From 956ad24d4f0a080c83eb57a9e3a052e7f57efe87 Mon Sep 17 00:00:00 2001 From: Jens Schauder Date: Tue, 24 Mar 2020 10:41:38 +0100 Subject: [PATCH] DATAJDBC-487 - Makes the IdentifierProcessing for MySqlDialect dynamic. This allows to handle the various ways MySQL may behave, depending on the OS it is installed on and on its configuration. Original pull request: #202. --- .../config/JdbcDialectResolver.java | 73 +++++++++++++------ .../relational/core/dialect/MySqlDialect.java | 11 ++- 2 files changed, 60 insertions(+), 24 deletions(-) diff --git a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcDialectResolver.java b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcDialectResolver.java index 46da569d..f545a8c5 100644 --- a/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcDialectResolver.java +++ b/spring-data-jdbc/src/main/java/org/springframework/data/jdbc/repository/config/JdbcDialectResolver.java @@ -15,9 +15,9 @@ */ package org.springframework.data.jdbc.repository.config; -import java.awt.*; import java.sql.Connection; import java.sql.DatabaseMetaData; +import java.sql.SQLException; import java.util.List; import java.util.Optional; @@ -30,9 +30,12 @@ import org.springframework.data.relational.core.dialect.HsqlDbDialect; import org.springframework.data.relational.core.dialect.MySqlDialect; import org.springframework.data.relational.core.dialect.PostgresDialect; import org.springframework.data.relational.core.dialect.SqlServerDialect; +import org.springframework.data.relational.core.sql.IdentifierProcessing; import org.springframework.data.util.Optionals; import org.springframework.jdbc.core.ConnectionCallback; +import org.springframework.jdbc.core.JdbcOperations; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; +import org.springframework.lang.Nullable; /** * Resolves a {@link Dialect} from a {@link DataSource} using {@link JdbcDialectProvider}. Dialect resolution uses @@ -66,8 +69,7 @@ public class JdbcDialectResolver { .flatMap(Optionals::toStream) // .findFirst() // .orElseThrow(() -> new NoDialectException( - String.format("Cannot determine a dialect for %s. Please provide a Dialect.", - template))); + String.format("Cannot determine a dialect for %s. Please provide a Dialect.", template))); } /** @@ -82,7 +84,8 @@ public class JdbcDialectResolver { /** * Returns a {@link Dialect} for a {@link DataSource}. * - * @param template the {@link org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate} to be used with the {@link Dialect}. + * @param template the {@link org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate} to be used with + * the {@link Dialect}. * @return {@link Optional} containing the {@link Dialect} if the {@link JdbcDialectProvider} can provide a dialect * object, otherwise {@link Optional#empty()}. */ @@ -94,28 +97,54 @@ public class JdbcDialectResolver { @Override public Optional getDialect(NamedParameterJdbcOperations template) { - return template.getJdbcOperations().execute((ConnectionCallback>) (connection) ->{ + JdbcOperations operations = template.getJdbcOperations(); + return Optional.ofNullable(operations.execute((ConnectionCallback) DefaultDialectProvider::getDialect)); - DatabaseMetaData metaData = connection.getMetaData(); + } - String name = metaData.getDatabaseProductName().toLowerCase(); + @Nullable + private static Dialect getDialect(Connection connection) throws SQLException { - if (name.contains("hsql")) { - return Optional.of(HsqlDbDialect.INSTANCE); - } - if (name.contains("mysql")) { // catches also mariadb - return Optional.of(MySqlDialect.INSTANCE); - } - if (name.contains("postgresql")) { - return Optional.of(PostgresDialect.INSTANCE); - } - if (name.contains("microsoft")) { - return Optional.of(SqlServerDialect.INSTANCE); - } + DatabaseMetaData metaData = connection.getMetaData(); - return Optional.empty(); - }); + String name = metaData.getDatabaseProductName().toLowerCase(); + if (name.contains("hsql")) { + return HsqlDbDialect.INSTANCE; + } + if (name.contains("mysql")) { // catches also mariadb + return new MySqlDialect(getIdentifierProcessing(metaData)); + } + if (name.contains("postgresql")) { + return PostgresDialect.INSTANCE; + } + if (name.contains("microsoft")) { + return SqlServerDialect.INSTANCE; + } + + return null; + } + + private static IdentifierProcessing getIdentifierProcessing(DatabaseMetaData metaData) throws SQLException { + + // getIdentifierQuoteString() returns a space " " if identifier quoting is not + // supported. + final String quoteString = metaData.getIdentifierQuoteString(); + final IdentifierProcessing.Quoting quoting = " ".equals(quoteString) ? IdentifierProcessing.Quoting.NONE : new IdentifierProcessing.Quoting(quoteString); + + final IdentifierProcessing.LetterCasing letterCasing; + // IdentifierProcessing tries to mimic the behavior of unquoted identifiers for their quoted variants. + if (metaData.supportsMixedCaseIdentifiers()) { + letterCasing = IdentifierProcessing.LetterCasing.AS_IS; + } else if (metaData.storesUpperCaseIdentifiers()) { + letterCasing = IdentifierProcessing.LetterCasing.UPPER_CASE; + } else if (metaData.storesLowerCaseIdentifiers()) { + letterCasing = IdentifierProcessing.LetterCasing.LOWER_CASE; + } else { // this shouldn't happen since one of the previous cases should be true. + // But if it does happen, we go with the ANSI default. + letterCasing = IdentifierProcessing.LetterCasing.UPPER_CASE; + } + return IdentifierProcessing.create(quoting, letterCasing); } } @@ -129,7 +158,7 @@ public class JdbcDialectResolver { * * @param msg the detail message */ - public NoDialectException(String msg) { + NoDialectException(String msg) { super(msg); } } diff --git a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MySqlDialect.java b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MySqlDialect.java index 86efe17b..cc78ebcf 100644 --- a/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MySqlDialect.java +++ b/spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/MySqlDialect.java @@ -32,8 +32,15 @@ public class MySqlDialect extends AbstractDialect { * Singleton instance. */ public static final MySqlDialect INSTANCE = new MySqlDialect(); + private IdentifierProcessing identifierProcessing; - protected MySqlDialect() { } + protected MySqlDialect() { + this(IdentifierProcessing.create(new Quoting("`"), LetterCasing.LOWER_CASE)); + } + + public MySqlDialect(IdentifierProcessing identifierProcessing) { + this.identifierProcessing = identifierProcessing; + } private static final LimitClause LIMIT_CLAUSE = new LimitClause() { @@ -89,6 +96,6 @@ public class MySqlDialect extends AbstractDialect { @Override public IdentifierProcessing getIdentifierProcessing() { - return IdentifierProcessing.create(new Quoting("`"), LetterCasing.LOWER_CASE); + return identifierProcessing; } }