diff --git a/src/main/kotlin/org/jetbrains/exposed/sql/Constraints.kt b/src/main/kotlin/org/jetbrains/exposed/sql/Constraints.kt index 24c0074cfb..192da21195 100644 --- a/src/main/kotlin/org/jetbrains/exposed/sql/Constraints.kt +++ b/src/main/kotlin/org/jetbrains/exposed/sql/Constraints.kt @@ -61,16 +61,18 @@ data class ForeignKeyConstraint(val fkName: String, val refereeTable: String, va } -data class Index(val indexName: String, val table: Table, val columns: List>, val unique: Boolean) : DdlAware { - companion object { - fun forColumns(vararg columns: Column<*>, unique: Boolean): Index { - assert(columns.isNotEmpty()) - assert(columns.groupBy { it.table }.size == 1) { "Columns from different tables can't persist in one index" } - val indexName = "${columns.first().table.nameInDatabaseCase()}_${columns.joinToString("_"){it.name.inProperCase()}}" + (if (unique) "_unique".inProperCase() else "") - return Index(indexName, columns.first().table, columns.toList(), unique) - } +data class Index(val columns: List>, val unique: Boolean, val customName: String? = null) : DdlAware { + val table: Table + + init { + assert(columns.isNotEmpty()) + assert(columns.groupBy { it.table }.size == 1) { "Columns from different tables can't persist in one index" } + table = columns.first().table } + val indexName + get() = customName?: "${table.nameInDatabaseCase()}_${columns.joinToString("_"){it.name.inProperCase()}}" + (if (unique) "_unique".inProperCase() else "") + override fun createStatement() = listOf(currentDialect.createIndex(this)) override fun dropStatement() = listOf(currentDialect.dropIndex(table.nameInDatabaseCase(), indexName)) diff --git a/src/main/kotlin/org/jetbrains/exposed/sql/Queries.kt b/src/main/kotlin/org/jetbrains/exposed/sql/Queries.kt index 36a38f7adb..d074b7b674 100644 --- a/src/main/kotlin/org/jetbrains/exposed/sql/Queries.kt +++ b/src/main/kotlin/org/jetbrains/exposed/sql/Queries.kt @@ -206,7 +206,7 @@ private fun checkMissingIndices(vararg tables: Table): List { val nameDiffers = HashSet() for (table in tables) { val existingTableIndices = currentDialect.existingIndices(table)[table].orEmpty().filterFKeys() - val mappedIndices = table.indices.map { Index.forColumns(*it.first, unique = it.second)}.filterFKeys() + val mappedIndices = table.indices.filterFKeys() existingTableIndices.forEach { index -> mappedIndices.firstOrNull { it.onlyNameDiffer(index) }?.let { diff --git a/src/main/kotlin/org/jetbrains/exposed/sql/SchemaUtils.kt b/src/main/kotlin/org/jetbrains/exposed/sql/SchemaUtils.kt index 20c0de38c2..fc9383cc99 100644 --- a/src/main/kotlin/org/jetbrains/exposed/sql/SchemaUtils.kt +++ b/src/main/kotlin/org/jetbrains/exposed/sql/SchemaUtils.kt @@ -22,8 +22,8 @@ object SchemaUtils { statements.addAll(table.ddl) // create indices - for ((columns, isUnique) in table.indices) { - statements.addAll(createIndex(columns, isUnique)) + for (index in table.indices) { + statements.addAll(createIndex(index)) } } @@ -34,7 +34,10 @@ object SchemaUtils { fun createFKey(reference: Column<*>) = ForeignKeyConstraint.from(reference).createStatement() - fun createIndex(columns: Array>, isUnique: Boolean) = Index.forColumns(*columns, unique = isUnique).createStatement() + @Deprecated(message = "pass instance of Index", replaceWith = ReplaceWith("SchemaUtils.createIndex(index)")) + fun createIndex(columns: Array>, isUnique: Boolean) = createIndex(Index(columns.toList(), unique = isUnique)) + + fun createIndex(index: Index) = index.createStatement() private fun addMissingColumnsStatements(vararg tables: Table): List { with(TransactionManager.current()) { @@ -54,9 +57,9 @@ object SchemaUtils { if (db.supportsAlterTableWithAddColumn) { // create indexes with new columns - for ((columns, isUnique) in table.indices) { - if (columns.any { missingTableColumns.contains(it) }) { - statements.addAll(createIndex(columns, isUnique)) + for (index in table.indices) { + if (index.columns.any { missingTableColumns.contains(it) }) { + statements.addAll(createIndex(index)) } } @@ -192,4 +195,4 @@ object SchemaUtils { } currentDialect.resetCaches() } -} \ No newline at end of file +} diff --git a/src/main/kotlin/org/jetbrains/exposed/sql/Table.kt b/src/main/kotlin/org/jetbrains/exposed/sql/Table.kt index e0c8924db1..b2c009c09e 100644 --- a/src/main/kotlin/org/jetbrains/exposed/sql/Table.kt +++ b/src/main/kotlin/org/jetbrains/exposed/sql/Table.kt @@ -153,7 +153,7 @@ open class Table(name: String = ""): ColumnSet(), DdlAware { override fun describe(s: Transaction): String = s.identity(this) - val indices = ArrayList>, Boolean>>() + val indices = ArrayList() override val fields: List> get() = columns @@ -397,17 +397,25 @@ open class Table(name: String = ""): ColumnSet(), DdlAware { } fun index (isUnique: Boolean = false, vararg columns: Column<*>) { - indices.add(columns to isUnique) + index(null, isUnique, *columns) } - fun Column.index(isUnique: Boolean = false) : Column = apply { - table.index(isUnique, this) + fun index (customIndexName:String? = null, isUnique: Boolean = false, vararg columns: Column<*>) { + indices.add(Index(columns.toList(), isUnique, customIndexName)) } - fun Column.uniqueIndex() : Column = index(true) + fun Column.index(customIndexName:String? = null, isUnique: Boolean = false) : Column = apply { + table.index(customIndexName, isUnique, this) + } + + fun Column.uniqueIndex(customIndexName:String? = null) : Column = index(customIndexName,true) fun uniqueIndex(vararg columns: Column<*>) { - index(true, *columns) + index(null,true, *columns) + } + + fun uniqueIndex(customIndexName:String? = null, vararg columns: Column<*>) { + index(customIndexName,true, *columns) } val ddl: List @@ -496,4 +504,4 @@ fun ColumnSet.targetTables(): List = when (this) { is Table -> listOf(this) is Join -> this.table.targetTables() + this.joinParts.flatMap { it.joinPart.targetTables() } else -> error("No target provided for update") -} \ No newline at end of file +} diff --git a/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Default.kt b/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Default.kt index 433724aff4..8d04634daa 100644 --- a/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Default.kt +++ b/src/main/kotlin/org/jetbrains/exposed/sql/vendors/Default.kt @@ -243,7 +243,7 @@ internal abstract class VendorDialect(override val name: String, val tColumns = table.columns.associateBy { transaction.identity(it) } tmpIndices.filterNot { it.key.first in pkNames } .mapNotNull { - it.value.mapNotNull { tColumns[it] }.takeIf { c-> c.size == it.value.size }?.let { c-> Index(it.key.first, table, c, it.key.second) } + it.value.mapNotNull { tColumns[it] }.takeIf { c-> c.size == it.value.size }?.let { c-> Index(c, it.key.second) } } }) } @@ -327,4 +327,4 @@ internal val currentDialectIfAvailable : DatabaseDialect? get() = internal fun String.inProperCase(): String = (currentDialectIfAvailable as? VendorDialect)?.run { this@inProperCase.inProperCase -} ?: this \ No newline at end of file +} ?: this diff --git a/src/main/kotlin/org/jetbrains/exposed/sql/vendors/H2.kt b/src/main/kotlin/org/jetbrains/exposed/sql/vendors/H2.kt index 75e8ff2d98..854886650b 100644 --- a/src/main/kotlin/org/jetbrains/exposed/sql/vendors/H2.kt +++ b/src/main/kotlin/org/jetbrains/exposed/sql/vendors/H2.kt @@ -38,7 +38,7 @@ internal class H2Dialect: VendorDialect(dialectName, H2DataTypeProvider) { super.existingIndices(*tables).mapValues { it.value.filterNot { it.indexName.startsWith("PRIMARY_KEY_") } }.filterValues { it.isNotEmpty() } override fun insert(ignore: Boolean, table: Table, columns: List>, expr: String, transaction: Transaction): String { - val uniqueIdxCols = table.indices.filter { it.second }.flatMap { it.first.toList() } + val uniqueIdxCols = table.indices.filter { it.unique }.flatMap { it.columns.toList() } val uniqueCols = columns.filter { it.indexInPK != null || it in uniqueIdxCols} return if (ignore && uniqueCols.isNotEmpty() && isMySQLMode) { val def = super.insert(false, table, columns, expr, transaction) @@ -59,4 +59,4 @@ internal class H2Dialect: VendorDialect(dialectName, H2DataTypeProvider) { companion object { const val dialectName = "h2" } -} \ No newline at end of file +} diff --git a/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/DDLTests.kt b/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/DDLTests.kt index 7188347c8f..dfff631517 100644 --- a/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/DDLTests.kt +++ b/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/DDLTests.kt @@ -242,7 +242,7 @@ class DDLTests : DatabaseTestsBase() { } withTables(t) { - val alter = SchemaUtils.createIndex(t.indices[0].first, t.indices[0].second) + val alter = SchemaUtils.createIndex(t.indices[0]) assertEquals("CREATE INDEX ${"t1_name".inProperCase()} ON ${"t1".inProperCase()} (${"name".inProperCase()})", alter) } } @@ -260,10 +260,10 @@ class DDLTests : DatabaseTestsBase() { } withTables(t) { - val a1 = SchemaUtils.createIndex(t.indices[0].first, t.indices[0].second) + val a1 = SchemaUtils.createIndex(t.indices[0]) assertEquals("CREATE INDEX ${"t2_name".inProperCase()} ON ${"t2".inProperCase()} (${"name".inProperCase()})", a1) - val a2 = SchemaUtils.createIndex(t.indices[1].first, t.indices[1].second) + val a2 = SchemaUtils.createIndex(t.indices[1]) assertEquals("CREATE INDEX ${"t2_lvalue_rvalue".inProperCase()} ON ${"t2".inProperCase()} " + "(${"lvalue".inProperCase()}, ${"rvalue".inProperCase()})", a2) } @@ -276,15 +276,31 @@ class DDLTests : DatabaseTestsBase() { } withTables(t) { - val alter = SchemaUtils.createIndex(t.indices[0].first, t.indices[0].second) + val alter = SchemaUtils.createIndex(t.indices[0]) if (currentDialect is SQLiteDialect) - assertEquals("CREATE UNIQUE INDEX ${"t1_name_unique".inProperCase()} ON ${"t1".inProperCase()} (${"name".inProperCase()})", alter) + assertEquals("CREATE UNIQUE INDEX ${"t1_name".inProperCase()} ON ${"t1".inProperCase()} (${"name".inProperCase()})", alter) else assertEquals("ALTER TABLE ${"t1".inProperCase()} ADD CONSTRAINT ${"t1_name_unique".inProperCase()} UNIQUE (${"name".inProperCase()})", alter) } } + @Test fun testUniqueIndicesCustomName() { + val t = object : Table("t1") { + val id = integer("id").primaryKey() + val name = varchar("name", 255).uniqueIndex("U_T1_NAME") + } + + withTables(t) { + val alter = SchemaUtils.createIndex(t.indices[0]) + if (currentDialect is SQLiteDialect) + assertEquals("CREATE UNIQUE INDEX ${"U_T1_NAME"} ON ${"t1".inProperCase()} (${"name".inProperCase()})", alter) + else + assertEquals("ALTER TABLE ${"t1".inProperCase()} ADD CONSTRAINT ${"U_T1_NAME"} UNIQUE (${"name".inProperCase()})", alter) + + } + } + @Test fun testMultiColumnIndex() { val t = object : Table("t1") { val type = varchar("type", 255) @@ -296,16 +312,37 @@ class DDLTests : DatabaseTestsBase() { } withTables(t) { - val indexAlter = SchemaUtils.createIndex(t.indices[0].first, t.indices[0].second) - val uniqueAlter = SchemaUtils.createIndex(t.indices[1].first, t.indices[1].second) + val indexAlter = SchemaUtils.createIndex(t.indices[0]) + val uniqueAlter = SchemaUtils.createIndex(t.indices[1]) assertEquals("CREATE INDEX ${"t1_name_type".inProperCase()} ON ${"t1".inProperCase()} (${"name".inProperCase()}, ${"type".inProperCase()})", indexAlter) if (currentDialect is SQLiteDialect) - assertEquals("CREATE UNIQUE INDEX ${"t1_type_name_unique".inProperCase()} ON ${"t1".inProperCase()} (${"type".inProperCase()}, ${"name".inProperCase()})", uniqueAlter) + assertEquals("CREATE UNIQUE INDEX ${"t1_type_name".inProperCase()} ON ${"t1".inProperCase()} (${"type".inProperCase()}, ${"name".inProperCase()})", uniqueAlter) else assertEquals("ALTER TABLE ${"t1".inProperCase()} ADD CONSTRAINT ${"t1_type_name_unique".inProperCase()} UNIQUE (${"type".inProperCase()}, ${"name".inProperCase()})", uniqueAlter) } } + @Test fun testMultiColumnIndexCustomName() { + val t = object : Table("t1") { + val type = varchar("type", 255) + val name = varchar("name", 255) + init { + index("I_T1_NAME_TYPE", false, name, type) + uniqueIndex("U_T1_TYPE_NAME", type, name) + } + } + + withTables(t) { + val indexAlter = SchemaUtils.createIndex(t.indices[0]) + val uniqueAlter = SchemaUtils.createIndex(t.indices[1]) + assertEquals("CREATE INDEX ${"I_T1_NAME_TYPE"} ON ${"t1".inProperCase()} (${"name".inProperCase()}, ${"type".inProperCase()})", indexAlter) + if (currentDialect is SQLiteDialect) + assertEquals("CREATE UNIQUE INDEX ${"U_T1_TYPE_NAME"} ON ${"t1".inProperCase()} (${"type".inProperCase()}, ${"name".inProperCase()})", uniqueAlter) + else + assertEquals("ALTER TABLE ${"t1".inProperCase()} ADD CONSTRAINT ${"U_T1_TYPE_NAME"} UNIQUE (${"type".inProperCase()}, ${"name".inProperCase()})", uniqueAlter) + } + } + @Test fun testBlob() { val t = object: Table("t1") { val id = integer("id").autoIncrement("t1_seq").primaryKey() @@ -473,4 +510,4 @@ private fun String.inProperCase(): String = TransactionManager.currentOrNull()?. (currentDialect as? VendorDialect)?.run { this@inProperCase.inProperCase } -} ?: this \ No newline at end of file +} ?: this