diff --git a/exposed-core/api/exposed-core.api b/exposed-core/api/exposed-core.api index fdcc8026d7..52a5366766 100644 --- a/exposed-core/api/exposed-core.api +++ b/exposed-core/api/exposed-core.api @@ -3582,6 +3582,7 @@ public abstract class org/jetbrains/exposed/sql/statements/api/ExposedDatabaseMe public abstract fun existingPrimaryKeys ([Lorg/jetbrains/exposed/sql/Table;)Ljava/util/Map; public abstract fun existingSequences ([Lorg/jetbrains/exposed/sql/Table;)Ljava/util/Map; public final fun getDatabase ()Ljava/lang/String; + public abstract fun getDatabaseDialectMode ()Ljava/lang/String; public abstract fun getDatabaseDialectName ()Ljava/lang/String; public abstract fun getDatabaseProductVersion ()Ljava/lang/String; public abstract fun getDefaultIsolationLevel ()I @@ -3620,6 +3621,7 @@ public abstract class org/jetbrains/exposed/sql/statements/api/IdentifierManager protected abstract fun getSupportsMixedIdentifiers ()Z protected abstract fun getSupportsMixedQuotedIdentifiers ()Z public final fun inProperCase (Ljava/lang/String;)Ljava/lang/String; + public final fun isDotPrefixedAndUnquoted (Ljava/lang/String;)Z protected abstract fun isLowerCaseIdentifiers ()Z protected abstract fun isLowerCaseQuotedIdentifiers ()Z protected abstract fun isUpperCaseIdentifiers ()Z @@ -4134,7 +4136,6 @@ public class org/jetbrains/exposed/sql/vendors/H2Dialect : org/jetbrains/exposed public fun listDatabases ()Ljava/lang/String; public fun modifyColumn (Lorg/jetbrains/exposed/sql/Column;Lorg/jetbrains/exposed/sql/ColumnDiff;)Ljava/util/List; public fun resolveRefOptionFromJdbc (I)Lorg/jetbrains/exposed/sql/ReferenceOption; - public fun sequences ()Ljava/util/List; public fun toString ()Ljava/lang/String; } @@ -4230,7 +4231,6 @@ public class org/jetbrains/exposed/sql/vendors/OracleDialect : org/jetbrains/exp public fun listDatabases ()Ljava/lang/String; public fun modifyColumn (Lorg/jetbrains/exposed/sql/Column;Lorg/jetbrains/exposed/sql/ColumnDiff;)Ljava/util/List; public fun resolveRefOptionFromJdbc (I)Lorg/jetbrains/exposed/sql/ReferenceOption; - public fun sequences ()Ljava/util/List; public fun setSchema (Lorg/jetbrains/exposed/sql/Schema;)Ljava/lang/String; } @@ -4302,7 +4302,6 @@ public class org/jetbrains/exposed/sql/vendors/SQLServerDialect : org/jetbrains/ public fun isAllowedAsColumnDefault (Lorg/jetbrains/exposed/sql/Expression;)Z public fun listDatabases ()Ljava/lang/String; public fun modifyColumn (Lorg/jetbrains/exposed/sql/Column;Lorg/jetbrains/exposed/sql/ColumnDiff;)Ljava/util/List; - public fun sequences ()Ljava/util/List; public fun setSchema (Lorg/jetbrains/exposed/sql/Schema;)Ljava/lang/String; } diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/api/ExposedDatabaseMetadata.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/api/ExposedDatabaseMetadata.kt index a7c18022e2..1d73020f56 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/api/ExposedDatabaseMetadata.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/api/ExposedDatabaseMetadata.kt @@ -28,6 +28,9 @@ abstract class ExposedDatabaseMetadata(val database: String) { /** The name of the database based on the name of the underlying JDBC driver. */ abstract val databaseDialectName: String + /** The name of the mode of the database. This is currently applicable only to H2 databases. */ + abstract val databaseDialectMode: String? + /** The version number of the database product as a `String`. */ abstract val databaseProductVersion: String diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/api/IdentifierManagerApi.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/api/IdentifierManagerApi.kt index 472a98e4e6..a2297c9351 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/api/IdentifierManagerApi.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/api/IdentifierManagerApi.kt @@ -126,13 +126,16 @@ abstract class IdentifierManagerApi { /** Returns an SQL token wrapped in quotations, if validated as necessary. */ fun quoteIfNecessary(identity: String): String { - return if (identity.contains('.') && !identity.isAlreadyQuoted()) { + return if (isDotPrefixedAndUnquoted(identity)) { identity.split('.').joinToString(".") { quoteTokenIfNecessary(it) } } else { quoteTokenIfNecessary(identity) } } + /** Returns whether an [identity] is both unquoted and contains dot characters. */ + fun isDotPrefixedAndUnquoted(identity: String): Boolean = identity.contains('.') && !identity.isAlreadyQuoted() + /** Returns an [identity] wrapped in quotations, if validated as necessary. */ fun quoteIdentifierWhenWrongCaseOrNecessary(identity: String): String { val inProperCase = inProperCase(identity) diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/H2.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/H2.kt index c24071e635..e384d37fb9 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/H2.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/H2.kt @@ -1,6 +1,5 @@ package org.jetbrains.exposed.sql.vendors -import org.intellij.lang.annotations.Language import org.jetbrains.exposed.exceptions.throwUnsupportedException import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.statements.StatementType @@ -260,17 +259,7 @@ open class H2Dialect : VendorDialect(dialectName, H2DataTypeProvider, H2Function /** The H2 database compatibility mode retrieved from metadata. */ val h2Mode: H2CompatibilityMode? by lazy { - val (settingNameField, settingValueField) = when (majorVersion) { - H2MajorVersion.One -> "NAME" to "VALUE" - H2MajorVersion.Two -> "SETTING_NAME" to "SETTING_VALUE" - } - - @Language("H2") - val fetchModeQuery = "SELECT $settingValueField FROM INFORMATION_SCHEMA.SETTINGS WHERE $settingNameField = 'MODE'" - val modeValue = TransactionManager.current().exec(fetchModeQuery) { rs -> - rs.next() - rs.getString(settingValueField) - } + val modeValue = TransactionManager.current().db.metadata { databaseDialectMode } when { modeValue == null -> null modeValue.equals("MySQL", ignoreCase = true) -> H2CompatibilityMode.MySQL @@ -355,23 +344,6 @@ open class H2Dialect : VendorDialect(dialectName, H2DataTypeProvider, H2Function } } - override fun sequences(): List { - val sequences = mutableListOf() - TransactionManager.current().exec("SELECT SEQUENCE_NAME FROM INFORMATION_SCHEMA.SEQUENCES") { rs -> - while (rs.next()) { - val result = rs.getString("SEQUENCE_NAME") - val sequenceName = if (h2Mode == H2CompatibilityMode.Oracle) { - val q = if (result.contains('.') && !result.isAlreadyQuoted()) "\"" else "" - "$q$result$q" - } else { - result - } - sequences.add(sequenceName) - } - } - return sequences - } - companion object : DialectNameProvider("H2") } diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/OracleDialect.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/OracleDialect.kt index b6bda586f8..1f14b7188c 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/OracleDialect.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/OracleDialect.kt @@ -486,18 +486,5 @@ open class OracleDialect : VendorDialect(dialectName, OracleDataTypeProvider, Or else -> currentDialect.defaultReferenceOption } - override fun sequences(): List { - val sequences = mutableListOf() - TransactionManager.current().exec("SELECT SEQUENCE_NAME FROM USER_SEQUENCES") { rs -> - while (rs.next()) { - val result = rs.getString("SEQUENCE_NAME") - val q = if (result.contains('.') && !result.isAlreadyQuoted()) "\"" else "" - val sequenceName = "$q$result$q" - sequences.add(sequenceName) - } - } - return sequences - } - companion object : DialectNameProvider("Oracle") } diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLServerDialect.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLServerDialect.kt index 26067ada78..d674387a5d 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLServerDialect.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLServerDialect.kt @@ -467,16 +467,6 @@ open class SQLServerDialect : VendorDialect(dialectName, SQLServerDataTypeProvid // https://docs.microsoft.com/en-us/sql/t-sql/language-elements/like-transact-sql?redirectedfrom=MSDN&view=sql-server-ver15#arguments override val likePatternSpecialChars = sqlServerLikePatternSpecialChars - override fun sequences(): List { - val sequences = mutableListOf() - TransactionManager.current().exec("SELECT name FROM sys.sequences") { rs -> - while (rs.next()) { - sequences.add(rs.getString("name")) - } - } - return sequences - } - companion object : DialectNameProvider("SQLServer") { private val sqlServerLikePatternSpecialChars = mapOf('%' to null, '_' to null, '[' to ']') } diff --git a/exposed-jdbc/api/exposed-jdbc.api b/exposed-jdbc/api/exposed-jdbc.api index f87344a4ad..86c7a4d4a8 100644 --- a/exposed-jdbc/api/exposed-jdbc.api +++ b/exposed-jdbc/api/exposed-jdbc.api @@ -39,6 +39,7 @@ public final class org/jetbrains/exposed/sql/statements/jdbc/JdbcDatabaseMetadat public fun existingIndices ([Lorg/jetbrains/exposed/sql/Table;)Ljava/util/Map; public fun existingPrimaryKeys ([Lorg/jetbrains/exposed/sql/Table;)Ljava/util/Map; public fun existingSequences ([Lorg/jetbrains/exposed/sql/Table;)Ljava/util/Map; + public fun getDatabaseDialectMode ()Ljava/lang/String; public fun getDatabaseDialectName ()Ljava/lang/String; public fun getDatabaseProductVersion ()Ljava/lang/String; public fun getDefaultIsolationLevel ()I diff --git a/exposed-jdbc/src/main/kotlin/org/jetbrains/exposed/sql/statements/jdbc/JdbcDatabaseMetadataImpl.kt b/exposed-jdbc/src/main/kotlin/org/jetbrains/exposed/sql/statements/jdbc/JdbcDatabaseMetadataImpl.kt index d1ae431ec5..14a79d8c5d 100644 --- a/exposed-jdbc/src/main/kotlin/org/jetbrains/exposed/sql/statements/jdbc/JdbcDatabaseMetadataImpl.kt +++ b/exposed-jdbc/src/main/kotlin/org/jetbrains/exposed/sql/statements/jdbc/JdbcDatabaseMetadataImpl.kt @@ -1,5 +1,6 @@ package org.jetbrains.exposed.sql.statements.jdbc +import org.intellij.lang.annotations.Language import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.statements.api.ExposedDatabaseMetadata import org.jetbrains.exposed.sql.statements.api.IdentifierManagerApi @@ -47,6 +48,22 @@ class JdbcDatabaseMetadataImpl(database: String, val metadata: DatabaseMetaData) else -> database } + override val databaseDialectMode: String? by lazy { + val dialect = currentDialect + if (dialect !is H2Dialect) null + + val (settingNameField, settingValueField) = when ((dialect as H2Dialect).majorVersion) { + H2Dialect.H2MajorVersion.One -> "NAME" to "VALUE" + H2Dialect.H2MajorVersion.Two -> "SETTING_NAME" to "SETTING_VALUE" + } + + @Language("H2") + val modeQuery = "SELECT $settingValueField FROM INFORMATION_SCHEMA.SETTINGS WHERE $settingNameField = 'MODE'" + TransactionManager.current().exec(modeQuery) { rs -> + rs.iterate { getString(settingValueField) } + }?.firstOrNull() + } + override val databaseProductVersion by lazyMetadata { databaseProductVersion!! } override val defaultIsolationLevel: Int by lazyMetadata { defaultTransactionIsolation } @@ -388,12 +405,35 @@ class JdbcDatabaseMetadataImpl(database: String, val metadata: DatabaseMetaData) @Suppress("MagicNumber") override fun sequences(): List { - val sequences = mutableListOf() - val rs = metadata.getTables(null, null, null, arrayOf("SEQUENCE")) - while (rs.next()) { - sequences.add(rs.getString(3)) - } - return sequences + val dialect = currentDialect + val transaction = TransactionManager.current() + val fieldName = "SEQUENCE_NAME" + return when (dialect) { + is OracleDialect -> transaction.exec("SELECT $fieldName FROM USER_SEQUENCES") { rs -> + rs.iterate { + val seqName = getString(fieldName) + if (identifierManager.isDotPrefixedAndUnquoted(seqName)) "\"$seqName\"" else seqName + } + } + is H2Dialect -> transaction.exec("SELECT $fieldName FROM INFORMATION_SCHEMA.SEQUENCES") { rs -> + rs.iterate { + val seqName = getString(fieldName) + if (dialect.h2Mode == H2CompatibilityMode.Oracle && identifierManager.isDotPrefixedAndUnquoted(seqName)) { + "\"$seqName\"" + } else { + seqName + } + } + } + is SQLServerDialect -> transaction.exec("SELECT name AS $fieldName FROM sys.sequences") { rs -> + rs.iterate { + getString(fieldName) + } + } + else -> metadata.getTables(null, null, null, arrayOf("SEQUENCE")).iterate { + getString(3) + } + } ?: emptyList() } @Synchronized