From 3a4f2354a5c1e2a46986938ece4a570430a70747 Mon Sep 17 00:00:00 2001 From: Jocelyne Date: Tue, 22 Aug 2023 23:15:48 +0200 Subject: [PATCH] feat: Support DB-generated values for columns --- exposed-core/api/exposed-core.api | 1 + .../org/jetbrains/exposed/sql/Column.kt | 2 + .../kotlin/org/jetbrains/exposed/sql/Table.kt | 5 ++ .../statements/BaseBatchInsertStatement.kt | 2 +- .../sql/tests/shared/entities/EntityTests.kt | 60 +++++++++++++++++++ 5 files changed, 69 insertions(+), 1 deletion(-) diff --git a/exposed-core/api/exposed-core.api b/exposed-core/api/exposed-core.api index ded68426f0..2b00410ce4 100644 --- a/exposed-core/api/exposed-core.api +++ b/exposed-core/api/exposed-core.api @@ -2215,6 +2215,7 @@ public class org/jetbrains/exposed/sql/Table : org/jetbrains/exposed/sql/ColumnS public fun crossJoin (Lorg/jetbrains/exposed/sql/ColumnSet;)Lorg/jetbrains/exposed/sql/Join; public final fun customEnumeration (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/exposed/sql/Column; public static synthetic fun customEnumeration$default (Lorg/jetbrains/exposed/sql/Table;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lorg/jetbrains/exposed/sql/Column; + public final fun dbGenerated (Lorg/jetbrains/exposed/sql/Column;)Lorg/jetbrains/exposed/sql/Column; public final fun decimal (Ljava/lang/String;II)Lorg/jetbrains/exposed/sql/Column; public final fun default (Lorg/jetbrains/exposed/sql/Column;Ljava/lang/Object;)Lorg/jetbrains/exposed/sql/Column; public final fun default (Lorg/jetbrains/exposed/sql/CompositeColumn;Ljava/lang/Object;)Lorg/jetbrains/exposed/sql/CompositeColumn; diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Column.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Column.kt index 810e5de52d..6b2d18463e 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Column.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Column.kt @@ -39,6 +39,8 @@ class Column( fun defaultValueInDb() = dbDefaultValue + internal var isGeneratedInDb: Boolean = false + /** Appends the SQL representation of this column to the specified [queryBuilder]. */ override fun toQueryBuilder(queryBuilder: QueryBuilder): Unit = TransactionManager.current().fullIdentity(this@Column, queryBuilder) diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Table.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Table.kt index 7fd2c33a99..d45e7ef977 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Table.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Table.kt @@ -725,6 +725,11 @@ open class Table(name: String = "") : ColumnSet(), DdlAware { defaultValueFun = defaultValue } + // Potential names: readOnly, generatable, dbGeneratable, dbGenerated, generated, generatedDefault, generatedInDb + fun Column.dbGenerated(): Column = apply { + isGeneratedInDb = true + } + /** UUID column will auto generate its value on a client side just before an insert. */ fun Column.autoGenerate(): Column = clientDefault { UUID.randomUUID() } diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/BaseBatchInsertStatement.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/BaseBatchInsertStatement.kt index e0f34017b8..27ccaa80ff 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/BaseBatchInsertStatement.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/BaseBatchInsertStatement.kt @@ -18,7 +18,7 @@ abstract class BaseBatchInsertStatement( internal val data = ArrayList, Any?>>() - private fun Column<*>.isDefaultable() = columnType.nullable || defaultValueFun != null + private fun Column<*>.isDefaultable() = columnType.nullable || defaultValueFun != null || isGeneratedInDb override operator fun set(column: Column, value: S) { if (data.size > 1 && column !in data[data.size - 2] && !column.isDefaultable()) { diff --git a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/entities/EntityTests.kt b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/entities/EntityTests.kt index 6851b4ea72..3c67ead3da 100644 --- a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/entities/EntityTests.kt +++ b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/entities/EntityTests.kt @@ -1400,4 +1400,64 @@ class EntityTests : DatabaseTestsBase() { assertEquals(1, count) } } + + object CreditCards : IntIdTable("CreditCards") { + val number = varchar("number", 16) + val spendingLimit = ulong("spendingLimit").dbGenerated() + } + + class CreditCard(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(CreditCards) + + var number by CreditCards.number + var spendingLimit by CreditCards.spendingLimit + } + + @Test + fun testDbGeneratedDefault() { + withTables(excludeSettings = listOf(TestDB.SQLITE), CreditCards) { testDb -> + addLogger(StdOutSqlLogger) + when (testDb) { + TestDB.POSTGRESQL, TestDB.POSTGRESQLNG -> { + // The value can also be set using a SQL trigger + exec( + """ + CREATE OR REPLACE FUNCTION set_spending_limit() + RETURNS TRIGGER + LANGUAGE PLPGSQL + AS + $$ + BEGIN + NEW."spendingLimit" := 10000; + RETURN NEW; + END; + $$; + + CREATE TRIGGER set_spending_limit + BEFORE INSERT + ON CreditCards + FOR EACH ROW + EXECUTE PROCEDURE set_spending_limit() + """.trimIndent() + ) + } + else -> { + // This table is only used to get the statement that adds the DEFAULT value, and use it with exec + val CreditCards2 = object : IntIdTable("CreditCards") { + val spendingLimit = ulong("spendingLimit").default(10000uL) + } + val missingStatements = SchemaUtils.addMissingColumnsStatements(CreditCards2) + missingStatements.forEach { + exec(it) + } + } + } + + val creditCard = CreditCard.new { + number = "0000 1111 2222 3333" + } + + assertEquals(10000uL, creditCard.spendingLimit) + } + } }