diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Constraints.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Constraints.kt index 6dd02b15cd..ee9d5efd07 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Constraints.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Constraints.kt @@ -281,7 +281,9 @@ data class Index( override fun createStatement(): List = listOf(currentDialect.createIndex(this)) override fun modifyStatement(): List = dropStatement() + createStatement() - override fun dropStatement(): List = listOf(currentDialect.dropIndex(table.nameInDatabaseCase(), indexName, unique, filterCondition != null)) + override fun dropStatement(): List = listOf( + currentDialect.dropIndex(table.nameInDatabaseCase(), indexName, unique, filterCondition != null || functions != null) + ) /** Returns `true` if the [other] index has the same columns and uniqueness as this index, but a different name, `false` otherwise */ fun onlyNameDiffer(other: Index): Boolean = indexName != other.indexName && columns == other.columns && unique == other.unique diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Default.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Default.kt index 53dacd490c..a76742f5cd 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Default.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Default.kt @@ -936,7 +936,7 @@ interface DatabaseDialect { fun createIndex(index: Index): String /** Returns the SQL command that drops the specified [indexName] from the specified [tableName]. */ - fun dropIndex(tableName: String, indexName: String, isUnique: Boolean, isPartial: Boolean): String + fun dropIndex(tableName: String, indexName: String, isUnique: Boolean, isPartialOrFunctional: Boolean): String /** Returns the SQL command that modifies the specified [column]. */ fun modifyColumn(column: Column<*>, columnDiff: ColumnDiff): List @@ -1187,6 +1187,8 @@ abstract class VendorDialect( when (it) { is Column<*> -> t.identity(it) is Function<*> -> indexFunctionToString(it) + // returned by existingIndices() mapping String metadata to stringLiteral() + is LiteralOp<*> -> it.value.toString().trim('"') else -> { exposedLogger.warn("Unexpected defining key field will be passed as String: $it") it.toString() @@ -1222,7 +1224,7 @@ abstract class VendorDialect( return "CREATE INDEX $name ON $table $columns USING $type$filterCondition" } - override fun dropIndex(tableName: String, indexName: String, isUnique: Boolean, isPartial: Boolean): String { + override fun dropIndex(tableName: String, indexName: String, isUnique: Boolean, isPartialOrFunctional: Boolean): String { return "ALTER TABLE ${identifierManager.quoteIfNecessary(tableName)} DROP CONSTRAINT ${identifierManager.quoteIfNecessary(indexName)}" } diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Mysql.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Mysql.kt index b7aad27db1..5a1f6dd6f5 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Mysql.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Mysql.kt @@ -337,7 +337,7 @@ open class MysqlDialect : VendorDialect(dialectName, MysqlDataTypeProvider, Mysq return super.createIndex(index) } - override fun dropIndex(tableName: String, indexName: String, isUnique: Boolean, isPartial: Boolean): String = + override fun dropIndex(tableName: String, indexName: String, isUnique: Boolean, isPartialOrFunctional: Boolean): String = "ALTER TABLE ${identifierManager.quoteIfNecessary(tableName)} DROP INDEX ${identifierManager.quoteIfNecessary(indexName)}" override fun setSchema(schema: Schema): String = "USE ${schema.identifier}" 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 afa5ae4209..5cc5237170 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 @@ -296,7 +296,7 @@ open class OracleDialect : VendorDialect(dialectName, OracleDataTypeProvider, Or override fun isAllowedAsColumnDefault(e: Expression<*>): Boolean = true - override fun dropIndex(tableName: String, indexName: String, isUnique: Boolean, isPartial: Boolean): String { + override fun dropIndex(tableName: String, indexName: String, isUnique: Boolean, isPartialOrFunctional: Boolean): String { return "DROP INDEX ${identifierManager.quoteIfNecessary(indexName)}" } diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/PostgreSQL.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/PostgreSQL.kt index 16e5a2f9b1..5e5b1750f9 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/PostgreSQL.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/PostgreSQL.kt @@ -324,8 +324,8 @@ open class PostgreSQLDialect : VendorDialect(dialectName, PostgreSQLDataTypeProv return "CREATE INDEX $name ON $table USING $type $columns$filterCondition" } - override fun dropIndex(tableName: String, indexName: String, isUnique: Boolean, isPartial: Boolean): String { - return if (isUnique && !isPartial) { + override fun dropIndex(tableName: String, indexName: String, isUnique: Boolean, isPartialOrFunctional: Boolean): String { + return if (isUnique && !isPartialOrFunctional) { "ALTER TABLE IF EXISTS ${identifierManager.quoteIfNecessary(tableName)} DROP CONSTRAINT IF EXISTS ${identifierManager.quoteIfNecessary(indexName)}" } else { "DROP INDEX IF EXISTS ${identifierManager.quoteIfNecessary(indexName)}" 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 e662eaeb40..2c86b3dbdf 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 @@ -276,8 +276,8 @@ open class SQLServerDialect : VendorDialect(dialectName, SQLServerDataTypeProvid return "CREATE $type INDEX $name ON $table $columns$filterCondition" } - override fun dropIndex(tableName: String, indexName: String, isUnique: Boolean, isPartial: Boolean): String { - return if (isUnique && !isPartial) { + override fun dropIndex(tableName: String, indexName: String, isUnique: Boolean, isPartialOrFunctional: Boolean): String { + return if (isUnique && !isPartialOrFunctional) { "ALTER TABLE ${identifierManager.quoteIfNecessary(tableName)} DROP CONSTRAINT IF EXISTS ${identifierManager.quoteIfNecessary(indexName)}" } else { "DROP INDEX IF EXISTS ${identifierManager.quoteIfNecessary(indexName)} ON ${identifierManager.quoteIfNecessary(tableName)}" diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLiteDialect.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLiteDialect.kt index df3dd10999..5193dd0391 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLiteDialect.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/vendors/SQLiteDialect.kt @@ -266,7 +266,7 @@ open class SQLiteDialect : VendorDialect(dialectName, SQLiteDataTypeProvider, SQ } } - override fun dropIndex(tableName: String, indexName: String, isUnique: Boolean, isPartial: Boolean): String { + override fun dropIndex(tableName: String, indexName: String, isUnique: Boolean, isPartialOrFunctional: Boolean): String { return "DROP INDEX IF EXISTS ${identifierManager.quoteIfNecessary(indexName)}" } 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 bda2ed1170..6becca8e4f 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 @@ -204,19 +204,35 @@ class JdbcDatabaseMetadataImpl(database: String, val metadata: DatabaseMetaData) val tmpIndices = hashMapOf, MutableList>() while (rs.next()) { - rs.getString("INDEX_NAME")?.let { - val column = transaction.db.identifierManager.quoteIdentifierWhenWrongCaseOrNecessary(rs.getString("COLUMN_NAME")!!) - val isUnique = !rs.getBoolean("NON_UNIQUE") - val isPartial = if (rs.getString("FILTER_CONDITION").isNullOrEmpty()) null else Op.TRUE - tmpIndices.getOrPut(Triple(it, isUnique, isPartial)) { arrayListOf() }.add(column) + rs.getString("INDEX_NAME")?.let { indexName -> + // if index is function-based, SQLite & MySQL return null column_name metadata + val columnNameMetadata = rs.getString("COLUMN_NAME") ?: when (currentDialect) { + is MysqlDialect, is SQLiteDialect -> "\"\"" + else -> null + } + columnNameMetadata?.let { columnName -> + val column = transaction.db.identifierManager.quoteIdentifierWhenWrongCaseOrNecessary(columnName) + val isUnique = !rs.getBoolean("NON_UNIQUE") + val isPartial = if (rs.getString("FILTER_CONDITION").isNullOrEmpty()) null else Op.TRUE + tmpIndices.getOrPut(Triple(indexName, isUnique, isPartial)) { arrayListOf() }.add(column) + } } } rs.close() val tColumns = table.columns.associateBy { transaction.identity(it) } tmpIndices.filterNot { it.key.first in pkNames } .mapNotNull { (index, columns) -> - columns.distinct().mapNotNull { cn -> tColumns[cn] }.takeIf { c -> c.size == columns.size } - ?.let { c -> Index(c, index.second, index.first, filterCondition = index.third) } + val (functionBased, columnBased) = columns.distinct().partition { cn -> tColumns[cn] == null } + columnBased.map { cn -> tColumns[cn]!! } + .takeIf { c -> c.size + functionBased.size == columns.size } + ?.let { c -> + Index( + c, index.second, index.first, + filterCondition = index.third, + functions = functionBased.map { stringLiteral(it) }.ifEmpty { null }, + functionsTable = if (functionBased.isNotEmpty()) table else null + ) + } } } } diff --git a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/ddl/CreateIndexTests.kt b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/ddl/CreateIndexTests.kt index 3dc0af13b3..5c439c9aac 100644 --- a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/ddl/CreateIndexTests.kt +++ b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/ddl/CreateIndexTests.kt @@ -135,11 +135,6 @@ class CreateIndexTests : DatabaseTestsBase() { kotlin.test.assertEquals(totalIndexCount, 3, "Indexes expected to be created") } - fun getIndices(): List { - db.dialect.resetCaches() - return currentDialect.existingIndices(partialIndexTable)[partialIndexTable].orEmpty() - } - val dropIndex = Index(columns = listOf(partialIndexTable.value, partialIndexTable.name), unique = false).dropStatement().first() kotlin.test.assertTrue(dropIndex.startsWith("DROP INDEX "), "Unique partial index must be created and dropped as index") val dropUniqueConstraint = Index(columns = listOf(partialIndexTable.anotherValue), unique = true).dropStatement().first() @@ -147,7 +142,7 @@ class CreateIndexTests : DatabaseTestsBase() { execInBatch(listOf(dropUniqueConstraint, dropIndex)) - assertEquals(getIndices().size, 1) + assertEquals(getIndices(partialIndexTable).size, 1) SchemaUtils.drop(partialIndexTable) } } @@ -180,19 +175,14 @@ class CreateIndexTests : DatabaseTestsBase() { assertEquals(2, tester.indices.count { it.filterCondition != null }) - fun getIndices(): List { - db.dialect.resetCaches() - return currentDialect.existingIndices(tester)[tester].orEmpty() - } - - var indices = getIndices() + var indices = getIndices(tester) assertEquals(3, indices.size) val uniqueWithPartial = Index(listOf(tester.team), true, "team_only_index", null, Op.TRUE).dropStatement().first() val dropStatements = indices.map { it.dropStatement().first() } expect(Unit) { execInBatch(dropStatements + uniqueWithPartial) } - indices = getIndices() + indices = getIndices(tester) assertEquals(0, indices.size) // test for non-unique partial index with type @@ -255,11 +245,21 @@ class CreateIndexTests : DatabaseTestsBase() { if (!isOldMySql()) { SchemaUtils.createMissingTablesAndColumns() assertTrue(tester.exists()) - assertEquals(3, tester.indices.size) - val dropStatements = tester.indices.map { it.dropStatement().first() } + var indices = getIndices(tester) + assertEquals(3, indices.size) + + val dropStatements = indices.map { it.dropStatement().first() } expect(Unit) { execInBatch(dropStatements) } + + indices = getIndices(tester) + assertEquals(0, indices.size) } } } + + private fun Transaction.getIndices(table: Table): List { + db.dialect.resetCaches() + return currentDialect.existingIndices(table)[table].orEmpty() + } }