Skip to content

Commit

Permalink
feat: EXPOSED-89 Support functions in Create Index (JetBrains#1788)
Browse files Browse the repository at this point in the history
Add support for functional indices.

Note:
- Add dropIndex() override for Oracle as default was incorrect.
- Adjust all Table index functions to not use = operator with Unit return type.
  • Loading branch information
bog-walk authored and saral committed Oct 3, 2023
1 parent 1a07180 commit 86823ae
Show file tree
Hide file tree
Showing 11 changed files with 202 additions and 28 deletions.
24 changes: 16 additions & 8 deletions exposed-core/api/exposed-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -1106,21 +1106,25 @@ public final class org/jetbrains/exposed/sql/InSubQueryOp : org/jetbrains/expose
}

public final class org/jetbrains/exposed/sql/Index : org/jetbrains/exposed/sql/DdlAware {
public fun <init> (Ljava/util/List;ZLjava/lang/String;Ljava/lang/String;Lorg/jetbrains/exposed/sql/Op;)V
public synthetic fun <init> (Ljava/util/List;ZLjava/lang/String;Ljava/lang/String;Lorg/jetbrains/exposed/sql/Op;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Ljava/util/List;ZLjava/lang/String;Ljava/lang/String;Lorg/jetbrains/exposed/sql/Op;Ljava/util/List;Lorg/jetbrains/exposed/sql/Table;)V
public synthetic fun <init> (Ljava/util/List;ZLjava/lang/String;Ljava/lang/String;Lorg/jetbrains/exposed/sql/Op;Ljava/util/List;Lorg/jetbrains/exposed/sql/Table;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/util/List;
public final fun component2 ()Z
public final fun component3 ()Ljava/lang/String;
public final fun component4 ()Ljava/lang/String;
public final fun component5 ()Lorg/jetbrains/exposed/sql/Op;
public final fun copy (Ljava/util/List;ZLjava/lang/String;Ljava/lang/String;Lorg/jetbrains/exposed/sql/Op;)Lorg/jetbrains/exposed/sql/Index;
public static synthetic fun copy$default (Lorg/jetbrains/exposed/sql/Index;Ljava/util/List;ZLjava/lang/String;Ljava/lang/String;Lorg/jetbrains/exposed/sql/Op;ILjava/lang/Object;)Lorg/jetbrains/exposed/sql/Index;
public final fun component6 ()Ljava/util/List;
public final fun component7 ()Lorg/jetbrains/exposed/sql/Table;
public final fun copy (Ljava/util/List;ZLjava/lang/String;Ljava/lang/String;Lorg/jetbrains/exposed/sql/Op;Ljava/util/List;Lorg/jetbrains/exposed/sql/Table;)Lorg/jetbrains/exposed/sql/Index;
public static synthetic fun copy$default (Lorg/jetbrains/exposed/sql/Index;Ljava/util/List;ZLjava/lang/String;Ljava/lang/String;Lorg/jetbrains/exposed/sql/Op;Ljava/util/List;Lorg/jetbrains/exposed/sql/Table;ILjava/lang/Object;)Lorg/jetbrains/exposed/sql/Index;
public fun createStatement ()Ljava/util/List;
public fun dropStatement ()Ljava/util/List;
public fun equals (Ljava/lang/Object;)Z
public final fun getColumns ()Ljava/util/List;
public final fun getCustomName ()Ljava/lang/String;
public final fun getFilterCondition ()Lorg/jetbrains/exposed/sql/Op;
public final fun getFunctions ()Ljava/util/List;
public final fun getFunctionsTable ()Lorg/jetbrains/exposed/sql/Table;
public final fun getIndexName ()Ljava/lang/String;
public final fun getIndexType ()Ljava/lang/String;
public final fun getTable ()Lorg/jetbrains/exposed/sql/Table;
Expand Down Expand Up @@ -2291,10 +2295,10 @@ public class org/jetbrains/exposed/sql/Table : org/jetbrains/exposed/sql/ColumnS
public fun getPrimaryKey ()Lorg/jetbrains/exposed/sql/Table$PrimaryKey;
public fun getTableName ()Ljava/lang/String;
public fun hashCode ()I
public final fun index (Ljava/lang/String;Z[Lorg/jetbrains/exposed/sql/Column;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
public final fun index (Ljava/lang/String;Z[Lorg/jetbrains/exposed/sql/Column;Ljava/util/List;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
public final fun index (Lorg/jetbrains/exposed/sql/Column;Ljava/lang/String;Z)Lorg/jetbrains/exposed/sql/Column;
public final fun index (Z[Lorg/jetbrains/exposed/sql/Column;)V
public static synthetic fun index$default (Lorg/jetbrains/exposed/sql/Table;Ljava/lang/String;Z[Lorg/jetbrains/exposed/sql/Column;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
public static synthetic fun index$default (Lorg/jetbrains/exposed/sql/Table;Ljava/lang/String;Z[Lorg/jetbrains/exposed/sql/Column;Ljava/util/List;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
public static synthetic fun index$default (Lorg/jetbrains/exposed/sql/Table;Lorg/jetbrains/exposed/sql/Column;Ljava/lang/String;ZILjava/lang/Object;)Lorg/jetbrains/exposed/sql/Column;
public static synthetic fun index$default (Lorg/jetbrains/exposed/sql/Table;Z[Lorg/jetbrains/exposed/sql/Column;ILjava/lang/Object;)V
public fun innerJoin (Lorg/jetbrains/exposed/sql/ColumnSet;)Lorg/jetbrains/exposed/sql/Join;
Expand Down Expand Up @@ -2339,10 +2343,10 @@ public class org/jetbrains/exposed/sql/Table : org/jetbrains/exposed/sql/ColumnS
public final fun ubyte (Ljava/lang/String;)Lorg/jetbrains/exposed/sql/Column;
public final fun uinteger (Ljava/lang/String;)Lorg/jetbrains/exposed/sql/Column;
public final fun ulong (Ljava/lang/String;)Lorg/jetbrains/exposed/sql/Column;
public final fun uniqueIndex (Ljava/lang/String;[Lorg/jetbrains/exposed/sql/Column;Lkotlin/jvm/functions/Function1;)V
public final fun uniqueIndex (Ljava/lang/String;[Lorg/jetbrains/exposed/sql/Column;Ljava/util/List;Lkotlin/jvm/functions/Function1;)V
public final fun uniqueIndex (Lorg/jetbrains/exposed/sql/Column;Ljava/lang/String;)Lorg/jetbrains/exposed/sql/Column;
public final fun uniqueIndex ([Lorg/jetbrains/exposed/sql/Column;Lkotlin/jvm/functions/Function1;)V
public static synthetic fun uniqueIndex$default (Lorg/jetbrains/exposed/sql/Table;Ljava/lang/String;[Lorg/jetbrains/exposed/sql/Column;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
public static synthetic fun uniqueIndex$default (Lorg/jetbrains/exposed/sql/Table;Ljava/lang/String;[Lorg/jetbrains/exposed/sql/Column;Ljava/util/List;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
public static synthetic fun uniqueIndex$default (Lorg/jetbrains/exposed/sql/Table;Lorg/jetbrains/exposed/sql/Column;Ljava/lang/String;ILjava/lang/Object;)Lorg/jetbrains/exposed/sql/Column;
public static synthetic fun uniqueIndex$default (Lorg/jetbrains/exposed/sql/Table;[Lorg/jetbrains/exposed/sql/Column;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
public final fun ushort (Ljava/lang/String;)Lorg/jetbrains/exposed/sql/Column;
Expand Down Expand Up @@ -3524,6 +3528,7 @@ public final class org/jetbrains/exposed/sql/vendors/KeywordsKt {
public final class org/jetbrains/exposed/sql/vendors/MariaDBDialect : org/jetbrains/exposed/sql/vendors/MysqlDialect {
public static final field Companion Lorg/jetbrains/exposed/sql/vendors/MariaDBDialect$Companion;
public fun <init> ()V
public fun createIndex (Lorg/jetbrains/exposed/sql/Index;)Ljava/lang/String;
public fun getFunctionProvider ()Lorg/jetbrains/exposed/sql/vendors/FunctionProvider;
public fun getName ()Ljava/lang/String;
public fun getSupportsOnlyIdentifiersInGeneratedKeys ()Z
Expand All @@ -3535,6 +3540,7 @@ public final class org/jetbrains/exposed/sql/vendors/MariaDBDialect$Companion :
public class org/jetbrains/exposed/sql/vendors/MysqlDialect : org/jetbrains/exposed/sql/vendors/VendorDialect {
public static final field Companion Lorg/jetbrains/exposed/sql/vendors/MysqlDialect$Companion;
public fun <init> ()V
public fun createIndex (Lorg/jetbrains/exposed/sql/Index;)Ljava/lang/String;
public fun createSchema (Lorg/jetbrains/exposed/sql/Schema;)Ljava/lang/String;
public fun dropIndex (Ljava/lang/String;Ljava/lang/String;ZZ)Ljava/lang/String;
public fun dropSchema (Lorg/jetbrains/exposed/sql/Schema;Z)Ljava/lang/String;
Expand All @@ -3557,6 +3563,7 @@ public class org/jetbrains/exposed/sql/vendors/OracleDialect : org/jetbrains/exp
public fun createDatabase (Ljava/lang/String;)Ljava/lang/String;
public fun createSchema (Lorg/jetbrains/exposed/sql/Schema;)Ljava/lang/String;
public fun dropDatabase (Ljava/lang/String;)Ljava/lang/String;
public fun dropIndex (Ljava/lang/String;Ljava/lang/String;ZZ)Ljava/lang/String;
public fun dropSchema (Lorg/jetbrains/exposed/sql/Schema;Z)Ljava/lang/String;
public fun getDefaultReferenceOption ()Lorg/jetbrains/exposed/sql/ReferenceOption;
public fun getNeedsQuotesWhenSymbolsInNames ()Z
Expand Down Expand Up @@ -3605,6 +3612,7 @@ public class org/jetbrains/exposed/sql/vendors/SQLServerDialect : org/jetbrains/
public static final field Companion Lorg/jetbrains/exposed/sql/vendors/SQLServerDialect$Companion;
public fun <init> ()V
public fun createDatabase (Ljava/lang/String;)Ljava/lang/String;
public fun createIndex (Lorg/jetbrains/exposed/sql/Index;)Ljava/lang/String;
protected fun createIndexWithType (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
public fun createSchema (Lorg/jetbrains/exposed/sql/Schema;)Ljava/lang/String;
public fun dropDatabase (Ljava/lang/String;)Ljava/lang/String;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,11 @@ data class Index(
/** Optional custom index type (e.g, BTREE or HASH) */
val indexType: String? = null,
/** Partial index filter condition */
val filterCondition: Op<Boolean>? = null
val filterCondition: Op<Boolean>? = null,
/** Functions that are part of the index. */
val functions: List<ExpressionWithColumnType<*>>? = null,
/** Table where the functional index should be defined. */
val functionsTable: Table? = null
) : DdlAware {
/** Table where the index is defined. */
val table: Table
Expand All @@ -253,16 +257,26 @@ data class Index(
append(table.nameInDatabaseCase())
append('_')
append(columns.joinToString("_") { it.name }.inProperCase())
functions?.let { f ->
if (columns.isNotEmpty()) append('_')
append(f.joinToString("_") { it.toString().substringBefore("(").lowercase() }.inProperCase())
}
if (unique) {
append("_unique".inProperCase())
}
}

init {
require(columns.isNotEmpty()) { "At least one column is required to create an index" }
val table = columns.distinctBy { it.table }.singleOrNull()?.table
requireNotNull(table) { "Columns from different tables can't persist in one index" }
this.table = table
require(columns.isNotEmpty() || functions?.isNotEmpty() == true) { "At least one column or function is required to create an index" }
val columnsTable = if (columns.isNotEmpty()) {
val table = columns.distinctBy { it.table }.singleOrNull()?.table
requireNotNull(table) { "Columns from different tables can't persist in one index" }
table
} else null
if (functions?.isNotEmpty() == true) {
requireNotNull(functionsTable) { "functionsTable argument must also be provided if functions are defined to create an index" }
}
this.table = columnsTable ?: functionsTable!!
}

override fun createStatement(): List<String> = listOf(currentDialect.createIndex(this))
Expand Down
30 changes: 23 additions & 7 deletions exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Table.kt
Original file line number Diff line number Diff line change
Expand Up @@ -986,28 +986,36 @@ open class Table(name: String = "") : ColumnSet(), DdlAware {
/**
* Creates an index.
*
* @param columns Columns that compose the index.
* @param isUnique Whether the index is unique or not.
* @param columns Columns that compose the index.
*/
fun index(isUnique: Boolean = false, vararg columns: Column<*>): Unit = index(null, isUnique, *columns)
fun index(isUnique: Boolean = false, vararg columns: Column<*>) { index(null, isUnique, *columns) }

/**
* Creates an index.
*
* @param customIndexName Name of the index.
* @param columns Columns that compose the index.
* @param isUnique Whether the index is unique or not.
* @param columns Columns that compose the index.
* @param functions Functions that compose the index.
* @param indexType A custom index type (e.g., "BTREE" or "HASH").
* @param filterCondition Index filtering conditions (also known as "partial index") declaration.
*/
fun index(
customIndexName: String? = null,
isUnique: Boolean = false,
vararg columns: Column<*>,
functions: List<ExpressionWithColumnType<*>>? = null,
indexType: String? = null,
filterCondition: FilterCondition = null
) {
_indices.add(Index(columns.toList(), isUnique, customIndexName, indexType = indexType, filterCondition?.invoke(SqlExpressionBuilder)))
_indices.add(
Index(
columns.toList(), isUnique, customIndexName, indexType,
filterCondition?.invoke(SqlExpressionBuilder),
functions, functions?.let { this }
)
)
}

/**
Expand All @@ -1033,18 +1041,26 @@ open class Table(name: String = "") : ColumnSet(), DdlAware {
* @param columns Columns that compose the index.
* @param filterCondition Index filtering conditions (also known as "partial index") declaration.
*/
fun uniqueIndex(vararg columns: Column<*>, filterCondition: FilterCondition = null): Unit =
fun uniqueIndex(vararg columns: Column<*>, filterCondition: FilterCondition = null) {
index(null, true, *columns, filterCondition = filterCondition)
}

/**
* Creates a unique index.
*
* @param customIndexName Name of the index.
* @param columns Columns that compose the index.
* @param functions Functions that compose the index.
* @param filterCondition Index filtering conditions (also known as "partial index") declaration.
*/
fun uniqueIndex(customIndexName: String? = null, vararg columns: Column<*>, filterCondition: FilterCondition = null): Unit =
index(customIndexName, true, *columns, filterCondition = filterCondition)
fun uniqueIndex(
customIndexName: String? = null,
vararg columns: Column<*>,
functions: List<ExpressionWithColumnType<*>>? = null,
filterCondition: FilterCondition = null
) {
index(customIndexName, true, *columns, functions = functions, filterCondition = filterCondition)
}

/**
* Creates a composite foreign key.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1161,6 +1161,16 @@ abstract class VendorDialect(
} ?: ""
}

private fun indexFunctionToString(function: Function<*>): String {
val baseString = function.toString()
return when (currentDialect) {
// SQLite & Oracle do not support "." operator (with table prefix) in index expressions
is SQLiteDialect, is OracleDialect -> baseString.replace(Regex("""^*[^( ]*\."""), "")
is MysqlDialect -> if (baseString.first() != '(') "($baseString)" else baseString
else -> baseString
}
}

/**
* Uniqueness might be required for foreign key constraints.
*
Expand All @@ -1172,29 +1182,38 @@ abstract class VendorDialect(
val t = TransactionManager.current()
val quotedTableName = t.identity(index.table)
val quotedIndexName = t.db.identifierManager.cutIfNecessaryAndQuote(index.indexName)
val columnsList = index.columns.joinToString(prefix = "(", postfix = ")") { t.identity(it) }

val keyFields = index.columns.plus(index.functions ?: emptyList())
val fieldsList = keyFields.joinToString(prefix = "(", postfix = ")") {
when (it) {
is Column<*> -> t.identity(it)
is Function<*> -> indexFunctionToString(it)
else -> {
exposedLogger.warn("Unexpected defining key field will be passed as String: $it")
it.toString()
}
}
}
val includesOnlyColumns = index.functions?.isEmpty() != false
val maybeFilterCondition = filterCondition(index) ?: return ""

return when {
// unique and no filter -> constraint, the type is not supported
index.unique && maybeFilterCondition.isEmpty() -> {
"ALTER TABLE $quotedTableName ADD CONSTRAINT $quotedIndexName UNIQUE $columnsList"
index.unique && maybeFilterCondition.isEmpty() && includesOnlyColumns -> {
"ALTER TABLE $quotedTableName ADD CONSTRAINT $quotedIndexName UNIQUE $fieldsList"
}
// unique and filter -> index only, the type is not supported
index.unique -> {
"CREATE UNIQUE INDEX $quotedIndexName ON $quotedTableName $columnsList$maybeFilterCondition"
"CREATE UNIQUE INDEX $quotedIndexName ON $quotedTableName $fieldsList$maybeFilterCondition"
}
// type -> can't be unique or constraint
index.indexType != null -> {
createIndexWithType(
name = quotedIndexName, table = quotedTableName,
columns = columnsList, type = index.indexType, filterCondition = maybeFilterCondition
columns = fieldsList, type = index.indexType, filterCondition = maybeFilterCondition
)
}
// any other indexes. May be can be merged with `createIndexWithType`
else -> {
"CREATE INDEX $quotedIndexName ON $quotedTableName $columnsList$maybeFilterCondition"
"CREATE INDEX $quotedIndexName ON $quotedTableName $fieldsList$maybeFilterCondition"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,12 @@ open class H2Dialect : VendorDialect(dialectName, H2DataTypeProvider, H2Function
)
return ""
}
if (index.functions != null) {
exposedLogger.warn(
"Functional index on ${index.table.tableName} using ${index.functions.joinToString { it.toString() }} can't be created in H2"
)
return ""
}
return super.createIndex(index)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package org.jetbrains.exposed.sql.vendors

import org.jetbrains.exposed.sql.Expression
import org.jetbrains.exposed.sql.Index
import org.jetbrains.exposed.sql.QueryBuilder
import org.jetbrains.exposed.sql.Sequence
import org.jetbrains.exposed.sql.append
import org.jetbrains.exposed.sql.exposedLogger

internal object MariaDBFunctionProvider : MysqlFunctionProvider() {
override fun nextVal(seq: Sequence, builder: QueryBuilder) = builder {
Expand Down Expand Up @@ -36,5 +38,15 @@ class MariaDBDialect : MysqlDialect() {
override val functionProvider: FunctionProvider = MariaDBFunctionProvider
override val supportsOnlyIdentifiersInGeneratedKeys: Boolean = true

override fun createIndex(index: Index): String {
if (index.functions != null) {
exposedLogger.warn(
"Functional index on ${index.table.tableName} using ${index.functions.joinToString { it.toString() }} can't be created in MariaDB"
)
return ""
}
return super.createIndex(index)
}

companion object : DialectNameProvider("mariadb")
}
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,16 @@ open class MysqlDialect : VendorDialect(dialectName, MysqlDataTypeProvider, Mysq
}
}

override fun createIndex(index: Index): String {
if (index.functions != null && !isMysql8) {
exposedLogger.warn(
"Functional index on ${index.table.tableName} using ${index.functions.joinToString { it.toString() }} can't be created in MySQL prior to 8.0"
)
return ""
}
return super.createIndex(index)
}

override fun dropIndex(tableName: String, indexName: String, isUnique: Boolean, isPartial: Boolean): String =
"ALTER TABLE ${identifierManager.quoteIfNecessary(tableName)} DROP INDEX ${identifierManager.quoteIfNecessary(indexName)}"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,10 @@ 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 {
return "DROP INDEX ${identifierManager.quoteIfNecessary(indexName)}"
}

override fun modifyColumn(column: Column<*>, columnDiff: ColumnDiff): List<String> {
val result = super.modifyColumn(column, columnDiff).map {
it.replace("MODIFY COLUMN", "MODIFY")
Expand Down
Loading

0 comments on commit 86823ae

Please sign in to comment.