From 38b91f45b32d0d894f6db77d123b554ab9952699 Mon Sep 17 00:00:00 2001 From: Thiago Cordeiro Date: Tue, 31 Dec 2024 09:09:45 +0100 Subject: [PATCH] Added bulk insert --- src/main/kotlin/io/tcds/orm/Table.kt | 23 ++++++-- .../{TimestampColumn.kt => DatetimeColumn.kt} | 4 +- ...ampColumn.kt => NullableDatetimeColumn.kt} | 2 +- .../kotlin/io/tcds/orm/extension/column.kt | 8 +-- .../kotlin/io/tcds/orm/statement/Statement.kt | 4 +- .../kotlin/io/tcds/orm/TableBulkInsertTest.kt | 52 +++++++++++++++++++ src/test/kotlin/io/tcds/orm/TableDdlTest.kt | 2 +- .../kotlin/io/tcds/orm/TableInsertTest.kt | 2 +- ...ampColumnTest.kt => DatetimeColumnTest.kt} | 6 +-- ...nTest.kt => NullableDatetimeColumnTest.kt} | 4 +- 10 files changed, 87 insertions(+), 20 deletions(-) rename src/main/kotlin/io/tcds/orm/column/{TimestampColumn.kt => DatetimeColumn.kt} (85%) rename src/main/kotlin/io/tcds/orm/column/nullable/{NullableTimestampColumn.kt => NullableDatetimeColumn.kt} (80%) create mode 100644 src/test/kotlin/io/tcds/orm/TableBulkInsertTest.kt rename src/test/kotlin/io/tcds/orm/column/{TimestampColumnTest.kt => DatetimeColumnTest.kt} (80%) rename src/test/kotlin/io/tcds/orm/column/nullable/{NullableTimestampColumnTest.kt => NullableDatetimeColumnTest.kt} (89%) diff --git a/src/main/kotlin/io/tcds/orm/Table.kt b/src/main/kotlin/io/tcds/orm/Table.kt index c186cf6..d5fadc4 100644 --- a/src/main/kotlin/io/tcds/orm/Table.kt +++ b/src/main/kotlin/io/tcds/orm/Table.kt @@ -13,11 +13,26 @@ abstract class Table( ) : ResultSetEntry { val columns = mutableListOf>() - fun insert(vararg entries: E) = entries.map { - val params = params(it) - val sql = "INSERT INTO $table (${params.columns()}) VALUES (${params.marks()})" + fun insert(vararg entries: E): List { + val cols = columns.joinToString(",") { it.name } + val marks = columns.joinToString(",") { "?" } + val sql = "INSERT INTO $table ($cols) VALUES ($marks)" - connection.write(sql, params) + return entries.map { connection.write(sql, params(it)) } + } + + fun bulkInsert(entries: List): JdbcStatement { + val cols = columns.joinToString(",") { it.name } + val marks = columns.joinToString(",") { "?" } + val params = mutableListOf>() + + val sql = StringBuilder().apply { + appendLine("INSERT INTO $table ($cols)") + appendLine(" VALUES") + appendLine(entries.joinToString(",\n") { params.addAll(params(it)).let { " ($marks)" } }) + }.toString().trim() + + return connection.write(sql, params) } fun loadBy(where: Statement, order: OrderStatement = emptyList()): E? = findBy( diff --git a/src/main/kotlin/io/tcds/orm/column/TimestampColumn.kt b/src/main/kotlin/io/tcds/orm/column/DatetimeColumn.kt similarity index 85% rename from src/main/kotlin/io/tcds/orm/column/TimestampColumn.kt rename to src/main/kotlin/io/tcds/orm/column/DatetimeColumn.kt index d50510d..058ff53 100644 --- a/src/main/kotlin/io/tcds/orm/column/TimestampColumn.kt +++ b/src/main/kotlin/io/tcds/orm/column/DatetimeColumn.kt @@ -5,11 +5,11 @@ import io.tcds.orm.Param import io.tcds.orm.param.InstantParam import java.time.Instant -class TimestampColumn( +class DatetimeColumn( name: String, value: (Entity) -> Instant, ) : Column(name, value) { - override fun columnType(): String = "TIMESTAMP" + override fun columnType(): String = "DATETIME" override fun valueParam(value: Instant): Param = InstantParam(this.name, value) override fun entryParam(entry: Entity): Param = InstantParam(this.name, valueOf(entry)) override fun ddl(): String = "`$name` DATETIME(6) NOT NULL" diff --git a/src/main/kotlin/io/tcds/orm/column/nullable/NullableTimestampColumn.kt b/src/main/kotlin/io/tcds/orm/column/nullable/NullableDatetimeColumn.kt similarity index 80% rename from src/main/kotlin/io/tcds/orm/column/nullable/NullableTimestampColumn.kt rename to src/main/kotlin/io/tcds/orm/column/nullable/NullableDatetimeColumn.kt index 2b782ff..456ce72 100644 --- a/src/main/kotlin/io/tcds/orm/column/nullable/NullableTimestampColumn.kt +++ b/src/main/kotlin/io/tcds/orm/column/nullable/NullableDatetimeColumn.kt @@ -5,7 +5,7 @@ import io.tcds.orm.Param import io.tcds.orm.param.nullable.NullableInstantParam import java.time.Instant -class NullableTimestampColumn(name: String, value: (Entity) -> Instant?) : Column(name, value) { +class NullableDatetimeColumn(name: String, value: (Entity) -> Instant?) : Column(name, value) { override fun columnType(): String = "DATETIME NULL" override fun valueParam(value: Instant?): Param = NullableInstantParam(this.name, value) override fun entryParam(entry: Entity): Param = NullableInstantParam(this.name, valueOf(entry)) diff --git a/src/main/kotlin/io/tcds/orm/extension/column.kt b/src/main/kotlin/io/tcds/orm/extension/column.kt index 1559a4b..cacabc2 100644 --- a/src/main/kotlin/io/tcds/orm/extension/column.kt +++ b/src/main/kotlin/io/tcds/orm/extension/column.kt @@ -39,10 +39,10 @@ fun Table.doubleNullable(name: String, value: (E) -> Double?) = column(Nu fun Table.bool(name: String, value: (E) -> Boolean) = column(BooleanColumn(name = name, value = value)) -fun Table.date(name: String, value: (E) -> Instant) = column(TimestampColumn(name = name, value = value)) -fun Table.datetime(name: String, value: (E) -> Instant) = column(TimestampColumn(name = name, value = value)) -fun Table.dateNullable(name: String, value: (E) -> Instant?) = column(NullableTimestampColumn(name = name, value = value)) -fun Table.datetimeNullable(name: String, value: (E) -> Instant?) = column(NullableTimestampColumn(name = name, value = value)) +fun Table.date(name: String, value: (E) -> Instant) = column(DatetimeColumn(name = name, value = value)) +fun Table.datetime(name: String, value: (E) -> Instant) = column(DatetimeColumn(name = name, value = value)) +fun Table.dateNullable(name: String, value: (E) -> Instant?) = column(NullableDatetimeColumn(name = name, value = value)) +fun Table.datetimeNullable(name: String, value: (E) -> Instant?) = column(NullableDatetimeColumn(name = name, value = value)) fun > Table.enum(name: String, value: (E) -> T) = column(EnumColumn(name = name, value = value)) fun Table.json(name: String, value: (E) -> T) = column(JsonColumn(name = name, value = value)) diff --git a/src/main/kotlin/io/tcds/orm/statement/Statement.kt b/src/main/kotlin/io/tcds/orm/statement/Statement.kt index f557d3e..439f5ae 100644 --- a/src/main/kotlin/io/tcds/orm/statement/Statement.kt +++ b/src/main/kotlin/io/tcds/orm/statement/Statement.kt @@ -2,7 +2,7 @@ package io.tcds.orm.statement import io.tcds.orm.Condition import io.tcds.orm.Param -import io.tcds.orm.column.TimestampColumn +import io.tcds.orm.column.DatetimeColumn import io.tcds.orm.extension.* import io.tcds.orm.param.InstantParam import java.time.Instant @@ -10,7 +10,7 @@ import java.time.LocalDateTime data class Statement(val conditions: MutableList>) { companion object { - fun deletedAt() = TimestampColumn("deleted_at") { Instant.now() } + fun deletedAt() = DatetimeColumn("deleted_at") { Instant.now() } } fun toStmt() = conditions.toStmt() diff --git a/src/test/kotlin/io/tcds/orm/TableBulkInsertTest.kt b/src/test/kotlin/io/tcds/orm/TableBulkInsertTest.kt new file mode 100644 index 0000000..a37f9a9 --- /dev/null +++ b/src/test/kotlin/io/tcds/orm/TableBulkInsertTest.kt @@ -0,0 +1,52 @@ +package io.tcds.orm + +import fixtures.Address +import fixtures.AddressTable +import io.mockk.* +import io.tcds.orm.connection.Connection +import io.tcds.orm.extension.toInstant +import io.tcds.orm.param.BooleanParam +import io.tcds.orm.param.InstantParam +import io.tcds.orm.param.StringParam +import org.junit.jupiter.api.Test + +class TableBulkInsertTest { + private val connection: Connection = mockk() + private val table = AddressTable(connection) + + private val first = Address.galaxyHighway() + private val second = Address.galaxyAvenue() + + @Test + fun `given the entry then invoke write in the connection`() { + every { connection.write(any(), any()) } returns mockk() + val entries = listOf(first, second) + + table.bulkInsert(entries) + + verify { + connection.write( + """ + INSERT INTO addresses (id,street,number,main,created_at) + VALUES + (?,?,?,?,?), + (?,?,?,?,?) + """.trimIndent(), + listOf( + // first + StringParam(table.id.name, first.id), + StringParam(table.street.name, first.street), + StringParam(table.number.name, first.number), + BooleanParam(table.main.name, first.main), + InstantParam(table.createdAt.name, first.createdAt.toInstant()), + // second + StringParam(table.id.name, second.id), + StringParam(table.street.name, second.street), + StringParam(table.number.name, second.number), + BooleanParam(table.main.name, second.main), + InstantParam(table.createdAt.name, second.createdAt.toInstant()), + ), + ) + } + } +} diff --git a/src/test/kotlin/io/tcds/orm/TableDdlTest.kt b/src/test/kotlin/io/tcds/orm/TableDdlTest.kt index 2a54be4..33b99e1 100644 --- a/src/test/kotlin/io/tcds/orm/TableDdlTest.kt +++ b/src/test/kotlin/io/tcds/orm/TableDdlTest.kt @@ -20,7 +20,7 @@ class TableDdlTest { `double` DECIMAL(10, 2) NOT NULL, `string` VARCHAR(255) NOT NULL, `boolean` BOOLEAN NOT NULL, - `instant` TIMESTAMP NOT NULL + `instant` DATETIME(6) NOT NULL ); """.trimIndent(), table.ddl(), diff --git a/src/test/kotlin/io/tcds/orm/TableInsertTest.kt b/src/test/kotlin/io/tcds/orm/TableInsertTest.kt index c6afd64..f3247ca 100644 --- a/src/test/kotlin/io/tcds/orm/TableInsertTest.kt +++ b/src/test/kotlin/io/tcds/orm/TableInsertTest.kt @@ -25,7 +25,7 @@ class TableInsertTest { verify { connection.write( - "INSERT INTO addresses (id, street, number, main, created_at) VALUES (?, ?, ?, ?, ?)", + "INSERT INTO addresses (id,street,number,main,created_at) VALUES (?,?,?,?,?)", listOf( StringParam(table.id.name, address.id), StringParam(table.street.name, address.street), diff --git a/src/test/kotlin/io/tcds/orm/column/TimestampColumnTest.kt b/src/test/kotlin/io/tcds/orm/column/DatetimeColumnTest.kt similarity index 80% rename from src/test/kotlin/io/tcds/orm/column/TimestampColumnTest.kt rename to src/test/kotlin/io/tcds/orm/column/DatetimeColumnTest.kt index aec7867..d21c25a 100644 --- a/src/test/kotlin/io/tcds/orm/column/TimestampColumnTest.kt +++ b/src/test/kotlin/io/tcds/orm/column/DatetimeColumnTest.kt @@ -8,13 +8,13 @@ import java.sql.PreparedStatement import java.sql.Timestamp import java.time.Instant -class TimestampColumnTest { +class DatetimeColumnTest { private val stmt: PreparedStatement = mockk() private val instant = Instant.now() - private val column = TimestampColumn("foo") { it.instant } + private val column = DatetimeColumn("foo") { it.instant } @Test - fun `given a column then describe its configuration`() = Assertions.assertEquals("foo" to "TIMESTAMP", column.describe()) + fun `given a column then describe its configuration`() = Assertions.assertEquals("foo" to "DATETIME", column.describe()) @Test fun `given a date value when it is not null then set the value into the statement`() { diff --git a/src/test/kotlin/io/tcds/orm/column/nullable/NullableTimestampColumnTest.kt b/src/test/kotlin/io/tcds/orm/column/nullable/NullableDatetimeColumnTest.kt similarity index 89% rename from src/test/kotlin/io/tcds/orm/column/nullable/NullableTimestampColumnTest.kt rename to src/test/kotlin/io/tcds/orm/column/nullable/NullableDatetimeColumnTest.kt index d267879..fb9cda8 100644 --- a/src/test/kotlin/io/tcds/orm/column/nullable/NullableTimestampColumnTest.kt +++ b/src/test/kotlin/io/tcds/orm/column/nullable/NullableDatetimeColumnTest.kt @@ -9,10 +9,10 @@ import java.sql.Timestamp import java.sql.Types import java.time.Instant -class NullableTimestampColumnTest { +class NullableDatetimeColumnTest { private val stmt: PreparedStatement = mockk() private val instant = Instant.now() - private val column = NullableTimestampColumn("foo") { it.instant } + private val column = NullableDatetimeColumn("foo") { it.instant } @Test fun `given a column then describe its configuration`() = Assertions.assertEquals("foo" to "DATETIME NULL", column.describe())