Skip to content

Commit

Permalink
Using a sequence for auto-incremented IDs only works for certain dial…
Browse files Browse the repository at this point in the history
…ects JetBrains#492

Support sequences in SQL Server JetBrains#1164
Sequence is not used when specifying via autoIncrement JetBrains#1209
  • Loading branch information
Tapac authored and SchweinchenFuntik committed Oct 23, 2021
1 parent f1b88a7 commit ee2b3e0
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -92,17 +92,27 @@ abstract class ColumnType(override var nullable: Boolean = false) : IColumnType
class AutoIncColumnType(
/** Returns the base column type of this auto-increment column. */
val delegate: ColumnType,
_autoincSeq: String
private val _autoincSeq: String?,
private val fallbackSeqName: String
) : IColumnType by delegate {

private val nextValValue = run {
val sequence = Sequence(_autoincSeq ?: fallbackSeqName)
if (delegate is IntegerColumnType) sequence.nextIntVal() else sequence.nextLongVal()
}

/** Returns the name of the sequence used to generate new values for this auto-increment column. */
val autoincSeq: String? = _autoincSeq
get() = if (currentDialect.needsSequenceToAutoInc) field else null
val autoincSeq: String?
get() = _autoincSeq ?: fallbackSeqName.takeIf { currentDialect.needsSequenceToAutoInc }

val nextValExpression: NextVal<*>? get() = nextValValue.takeIf { autoincSeq != null }

private fun resolveAutoIncType(columnType: IColumnType): String = when (columnType) {
is EntityIDColumnType<*> -> resolveAutoIncType(columnType.idColumn.columnType)
is IntegerColumnType -> currentDialect.dataTypeProvider.integerAutoincType()
is LongColumnType -> currentDialect.dataTypeProvider.longAutoincType()
private fun resolveAutoIncType(columnType: IColumnType): String = when {
columnType is EntityIDColumnType<*> -> resolveAutoIncType(columnType.idColumn.columnType)
columnType is IntegerColumnType && autoincSeq != null -> currentDialect.dataTypeProvider.integerType()
columnType is IntegerColumnType -> currentDialect.dataTypeProvider.integerAutoincType()
columnType is LongColumnType && autoincSeq != null -> currentDialect.dataTypeProvider.longType()
columnType is LongColumnType -> currentDialect.dataTypeProvider.longAutoincType()
else -> guessAutoIncTypeBy(columnType.sqlType())
} ?: error("Unsupported type $delegate for auto-increment")

Expand All @@ -115,22 +125,34 @@ class AutoIncColumnType(
override fun sqlType(): String = resolveAutoIncType(delegate)

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as AutoIncColumnType

if (delegate != other.delegate) return false
return when {
other == null -> false
this === other -> true
this::class != other::class -> false
other !is AutoIncColumnType -> false
delegate != other.delegate -> false
_autoincSeq != other._autoincSeq -> false
fallbackSeqName != other.fallbackSeqName -> false
else -> true
}
}

return true
override fun hashCode(): Int {
var result = delegate.hashCode()
result = 31 * result + (_autoincSeq?.hashCode() ?: 0)
result = 31 * result + fallbackSeqName.hashCode()
return result
}
}

/** Returns `true` if this is an auto-increment column, `false` otherwise. */
val IColumnType.isAutoInc: Boolean get() = this is AutoIncColumnType || (this is EntityIDColumnType<*> && idColumn.columnType.isAutoInc)
/** Returns the name of the auto-increment sequence of this column. */
val Column<*>.autoIncColumnType: AutoIncColumnType?
get() = (columnType as? AutoIncColumnType) ?: (columnType as? EntityIDColumnType<*>)?.idColumn?.columnType as? AutoIncColumnType
@Deprecated("Will be removed in upcoming releases. Please use [autoIncColumnType.autoincSeq] instead", ReplaceWith("this.autoIncColumnType.autoincSeq"), DeprecationLevel.ERROR)
val Column<*>.autoIncSeqName: String?
get() = (columnType as? AutoIncColumnType)?.autoincSeq ?: (columnType as? EntityIDColumnType<*>)?.idColumn?.autoIncSeqName
get() = autoIncColumnType?.autoincSeq

class EntityIDColumnType<T : Comparable<T>>(val idColumn: Column<T>) : ColumnType() {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1023,8 +1023,7 @@ open class Table(name: String = "") : ColumnSet(), DdlAware {
private fun <T> Column<T>.cloneWithAutoInc(idSeqName: String?): Column<T> = when (columnType) {
is AutoIncColumnType -> this
is ColumnType -> {
val autoIncSequence = idSeqName ?: "${tableName}_${name}_seq"
this@cloneWithAutoInc.clone<Column<T>>(mapOf(Column<T>::columnType to AutoIncColumnType(columnType as ColumnType, autoIncSequence)))
this@cloneWithAutoInc.clone(mapOf(Column<T>::columnType to AutoIncColumnType(columnType, idSeqName, "${tableName}_${name}_seq")))
}
else -> error("Unsupported column type for auto-increment $columnType")
}
Expand All @@ -1043,7 +1042,7 @@ open class Table(name: String = "") : ColumnSet(), DdlAware {
}

override fun createStatement(): List<String> {
val createSequence = autoIncColumn?.autoIncSeqName?.let { Sequence(it).createStatement() }.orEmpty()
val createSequence = autoIncColumn?.autoIncColumnType?.autoincSeq?.let { Sequence(it).createStatement() }.orEmpty()

val addForeignKeysInAlterPart = SchemaUtils.checkCycle(this) && currentDialect !is SQLiteDialect

Expand Down Expand Up @@ -1102,7 +1101,7 @@ open class Table(name: String = "") : ColumnSet(), DdlAware {
}
}

val dropSequence = autoIncColumn?.autoIncSeqName?.let { Sequence(it).dropStatement() }.orEmpty()
val dropSequence = autoIncColumn?.autoIncColumnType?.autoincSeq?.let { Sequence(it).dropStatement() }.orEmpty()

return listOf(dropTable) + dropSequence
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -358,9 +358,17 @@ abstract class FunctionProvider {
transaction.throwUnsupportedException("There's no generic SQL for INSERT IGNORE. There must be vendor specific implementation.")
}

val (columnsExpr, valuesExpr) = if (columns.isNotEmpty()) {
columns.joinToString(prefix = "(", postfix = ")") { transaction.identity(it) } to expr
} else "" to DEFAULT_VALUE_EXPRESSION
val autoIncColumn = table.autoIncColumn

val nextValExpression = autoIncColumn?.autoIncColumnType?.nextValExpression?.takeIf { autoIncColumn !in columns }

val (columnsToInsert, valuesExpr) = when {
nextValExpression != null && columns.isNotEmpty() -> (columns + autoIncColumn) to expr.dropLast(1) + ", $nextValExpression)"
nextValExpression != null -> listOf(autoIncColumn) to "VALUES ($nextValExpression)"
columns.isNotEmpty() -> columns to expr
else -> emptyList<Column<*>>() to DEFAULT_VALUE_EXPRESSION
}
val columnsExpr = columnsToInsert.takeIf { it.isNotEmpty() }?.joinToString(prefix = "(", postfix = ")") { transaction.identity(it) } ?: ""

return "INSERT INTO ${transaction.identity(table)} $columnsExpr $valuesExpr"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,24 +123,6 @@ internal object OracleFunctionProvider : FunctionProvider() {
append(")")
}

override fun insert(
ignore: Boolean,
table: Table,
columns: List<Column<*>>,
expr: String,
transaction: Transaction
): String {
return table.autoIncColumn?.takeIf { it !in columns }?.let {
val newExpr = if (expr.isBlank()) {
"VALUES (${it.autoIncSeqName!!}.NEXTVAL)"
} else {
expr.replace("VALUES (", "VALUES (${it.autoIncSeqName!!}.NEXTVAL, ")
}

super.insert(ignore, table, listOf(it) + columns, newExpr, transaction)
} ?: super.insert(ignore, table, columns, expr, transaction)
}

override fun update(
target: Table,
columnsAndValues: List<Pair<Column<*>, Any?>>,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package org.jetbrains.exposed.sql.tests.shared.ddl

import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.dao.id.IdTable
import org.jetbrains.exposed.dao.id.LongIdTable
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.tests.DatabaseTestsBase
import org.jetbrains.exposed.sql.tests.currentDialectTest
import org.jetbrains.exposed.sql.tests.shared.assertEquals
import org.junit.Test
import kotlin.test.assertNotNull

class SequencesTests : DatabaseTestsBase() {
@Test
Expand Down Expand Up @@ -79,6 +82,30 @@ class SequencesTests : DatabaseTestsBase() {
}
}

@Test
fun `test insert LongIdTable with auth-increment with sequence`() {
withDb {
if (currentDialectTest.supportsSequenceAsGeneratedKeys) {
addLogger(StdOutSqlLogger)
try {
SchemaUtils.create(DeveloperWithAutoIncrementBySequence)
val developerId = DeveloperWithAutoIncrementBySequence.insertAndGetId {
it[name] = "Hichem"
}

assertNotNull(developerId)

val developerId2 = DeveloperWithAutoIncrementBySequence.insertAndGetId {
it[name] = "Andrey"
}
assertEquals(developerId.value + 1, developerId2.value)
} finally {
SchemaUtils.drop(DeveloperWithAutoIncrementBySequence)
}
}
}
}

@Test
fun `test select with nextVal`() {
withTables(Developer) {
Expand Down Expand Up @@ -117,6 +144,11 @@ class SequencesTests : DatabaseTestsBase() {
var name = varchar("name", 25)
}

private object DeveloperWithAutoIncrementBySequence : IdTable<Long>() {
override val id: Column<EntityID<Long>> = long("id").autoIncrement("id_seq").entityId()
var name = varchar("name", 25)
}

private val myseq = Sequence(
name = "my_sequence",
startWith = 4,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ class CreateTableTests : DatabaseTestsBase() {
withTables(parent, child) {
val t = TransactionManager.current()
val expected = listOfNotNull(
child.autoIncColumn?.autoIncSeqName?.let { Sequence(it).createStatement().single() },
child.autoIncColumn?.autoIncColumnType?.autoincSeq?.let { Sequence(it).createStatement().single() },
"CREATE TABLE " + addIfNotExistsIfSupported() + "${t.identity(child)} (" +
"${child.columns.joinToString { it.descriptionDdl() }}," +
" CONSTRAINT ${t.db.identifierManager.cutIfNecessaryAndQuote(fkName).inProperCase()}" +
Expand Down Expand Up @@ -222,7 +222,7 @@ class CreateTableTests : DatabaseTestsBase() {
withTables(parent, child) {
val t = TransactionManager.current()
val expected = listOfNotNull(
child.autoIncColumn?.autoIncSeqName?.let { Sequence(it).createStatement().single() },
child.autoIncColumn?.autoIncColumnType?.autoincSeq?.let { Sequence(it).createStatement().single() },
"CREATE TABLE " + addIfNotExistsIfSupported() + "${t.identity(child)} (" +
"${child.columns.joinToString { it.descriptionDdl() }}," +
" CONSTRAINT ${t.db.identifierManager.cutIfNecessaryAndQuote(fkName).inProperCase()}" +
Expand Down Expand Up @@ -250,7 +250,7 @@ class CreateTableTests : DatabaseTestsBase() {
withTables(parent, child) {
val t = TransactionManager.current()
val expected = listOfNotNull(
child.autoIncColumn?.autoIncSeqName?.let { Sequence(it).createStatement().single() },
child.autoIncColumn?.autoIncColumnType?.autoincSeq?.let { Sequence(it).createStatement().single() },
"CREATE TABLE " + addIfNotExistsIfSupported() + "${t.identity(child)} (" +
"${child.columns.joinToString { it.descriptionDdl() }}," +
" CONSTRAINT ${t.db.identifierManager.cutIfNecessaryAndQuote(fkName).inProperCase()}" +
Expand Down Expand Up @@ -280,7 +280,7 @@ class CreateTableTests : DatabaseTestsBase() {
withTables(parent, child) {
val t = TransactionManager.current()
val expected = listOfNotNull(
child.autoIncColumn?.autoIncSeqName?.let { Sequence(it).createStatement().single() },
child.autoIncColumn?.autoIncColumnType?.autoincSeq?.let { Sequence(it).createStatement().single() },
"CREATE TABLE " + addIfNotExistsIfSupported() + "${t.identity(child)} (" +
"${child.columns.joinToString { it.descriptionDdl() }}," +
" CONSTRAINT ${t.db.identifierManager.cutIfNecessaryAndQuote(fkName).inProperCase()}" +
Expand Down

0 comments on commit ee2b3e0

Please sign in to comment.