Skip to content

Commit

Permalink
GH-45 Shared transactions (Resolve #45)
Browse files Browse the repository at this point in the history
  • Loading branch information
dzikoysk committed Dec 4, 2023
1 parent e552cde commit 7134e0b
Show file tree
Hide file tree
Showing 10 changed files with 227 additions and 121 deletions.
22 changes: 12 additions & 10 deletions sqiffy-test/src/test/kotlin/com/dzikoysk/sqiffy/e2e/DslE2ETest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import com.dzikoysk.sqiffy.shared.H2Mode.MYSQL
import com.dzikoysk.sqiffy.shared.createH2DataSource
import com.dzikoysk.sqiffy.shared.createHikariDataSource
import com.dzikoysk.sqiffy.shared.createSQLiteDataSource
import com.dzikoysk.sqiffy.transaction.NoTransaction
import com.dzikoysk.sqiffy.transaction.Transaction
import com.zaxxer.hikari.HikariDataSource
import java.time.Instant
import java.time.LocalDate
Expand Down Expand Up @@ -83,7 +85,6 @@ internal abstract class DslE2ETest(
}
.where { UserTable.id eq insertedUser.id }
.execute()

assertThat(insertedUser).isNotNull
assertThat(updatedRecords).isEqualTo(1)
println("Inserted user: $insertedUser")
Expand All @@ -101,7 +102,6 @@ internal abstract class DslE2ETest(
)
}
.first()

assertThat(userFromDatabase).isNotNull
assertThat(userFromDatabase.name).isEqualTo("Giant Panda")
assertThat(userFromDatabase.role).isEqualTo(Role.ADMIN)
Expand All @@ -120,7 +120,6 @@ internal abstract class DslE2ETest(
.values(guildToInsert)
.map { guildToInsert.withId(id = it[GuildTable.id]) }
.first()

assertThat(insertedGuild).isNotNull
println("Inserted guild: $insertedGuild")

Expand All @@ -133,7 +132,6 @@ internal abstract class DslE2ETest(
.orderBy(UserTable.name to ASC)
.map { it[UserTable.name] to it[GuildTable.name] }
.first()

println(joinedData)
assertThat(joinedData).isEqualTo("Giant Panda" to "GLORIOUS MONKE")

Expand Down Expand Up @@ -161,7 +159,6 @@ internal abstract class DslE2ETest(
}
.map { it[GuildTable.name] }
.firstOrNull()

assertThat(matchedGuild).isNotNull
println(matchedGuild)

Expand All @@ -187,17 +184,22 @@ internal abstract class DslE2ETest(
)
}
.first()

assertThat(aggregatedData).isNotNull
println(aggregatedData)

val deletedCount = database.delete(GuildTable)
.where { GuildTable.id eq insertedGuild.id }
.execute()

val deletedCount = database.transaction { deleteGuild(insertedGuild.id, it) }
assertThat(deletedCount).isEqualTo(1)

val deletedCountTwo = deleteGuild(insertedGuild.id)
assertThat(deletedCountTwo).isEqualTo(0)
}

private fun deleteGuild(id: Int, transaction: Transaction = NoTransaction): Int =
transaction(database)
.delete(GuildTable)
.where { GuildTable.id eq id }
.execute()

@Test
fun `should create table with all default values`() {
database.generateChangeLog(
Expand Down
57 changes: 30 additions & 27 deletions sqiffy/src/main/kotlin/com/dzikoysk/sqiffy/SqiffyDatabase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,15 @@ import com.dzikoysk.sqiffy.changelog.Changelog
import com.dzikoysk.sqiffy.changelog.ChangelogBuilder
import com.dzikoysk.sqiffy.changelog.generator.SqlSchemeGenerator
import com.dzikoysk.sqiffy.changelog.generator.dialects.getSchemeGenerator
import com.dzikoysk.sqiffy.dsl.Table
import com.dzikoysk.sqiffy.dsl.TableWithAutogeneratedKey
import com.dzikoysk.sqiffy.dsl.Values
import com.dzikoysk.sqiffy.dsl.DslHandle
import com.dzikoysk.sqiffy.dsl.JdbiDslHandle
import com.dzikoysk.sqiffy.dsl.generator.SqlQueryGenerator
import com.dzikoysk.sqiffy.dsl.statements.AutogeneratedKeyInsertStatement
import com.dzikoysk.sqiffy.dsl.statements.DeleteStatement
import com.dzikoysk.sqiffy.dsl.statements.InsertStatement
import com.dzikoysk.sqiffy.dsl.statements.SelectStatement
import com.dzikoysk.sqiffy.dsl.statements.UpdateStatement
import com.dzikoysk.sqiffy.dsl.statements.UpdateValues
import com.dzikoysk.sqiffy.migrator.Migrator
import com.dzikoysk.sqiffy.transaction.HandleAccessor
import com.dzikoysk.sqiffy.transaction.JdbiTransaction
import com.dzikoysk.sqiffy.transaction.StandardHandleAccessor
import com.dzikoysk.sqiffy.transaction.Transaction
import com.dzikoysk.sqiffy.transaction.TransactionManager
import com.zaxxer.hikari.HikariDataSource
import java.io.Closeable
import kotlin.reflect.KClass
Expand All @@ -29,47 +27,52 @@ data class SqiffyDatabaseConfig(
val sqlQueryGenerator: SqlQueryGenerator,
val localJdbi: Jdbi,
val changelogBuilder: ChangelogBuilder,
val handleAccessor: HandleAccessor = StandardHandleAccessor(localJdbi),
)

abstract class SqiffyDatabase(state: SqiffyDatabaseConfig) : Closeable {
abstract class SqiffyDatabase(state: SqiffyDatabaseConfig) : DslHandle(), TransactionManager, Closeable {

internal val logger: SqiffyLogger = state.logger
internal val dialect: Dialect = state.dialect
internal val dataSource: HikariDataSource = state.dataSource
internal val sqlSchemeGenerator: SqlSchemeGenerator = state.sqlSchemeGenerator
internal val sqlQueryGenerator: SqlQueryGenerator = state.sqlQueryGenerator
internal val localJdbi: Jdbi = state.localJdbi
internal val changelogBuilder: ChangelogBuilder = state.changelogBuilder
private val changelogBuilder: ChangelogBuilder = state.changelogBuilder
private val handleAccessor = state.handleAccessor

override fun close() {
dataSource.close()
}

fun generateChangeLog(tables: List<KClass<*>>, functions: List<KProperty<*>> = emptyList()): Changelog =
changelogBuilder.generateChangeLogAtRuntime(tables = tables, functions = functions)

fun <RESULT> runMigrations(migrator: Migrator<RESULT>): RESULT =
migrator.runMigrations(this)

fun select(table: Table): SelectStatement =
SelectStatement(this, table)

fun insert(table: TableWithAutogeneratedKey, values: (Values) -> Unit): AutogeneratedKeyInsertStatement =
AutogeneratedKeyInsertStatement(this, table, Values().also { values.invoke(it) })

fun insert(table: Table, values: (Values) -> Unit): InsertStatement =
InsertStatement(this, table, Values().also { values.invoke(it) })

fun update(table: Table, values: (UpdateValues) -> Unit): UpdateStatement =
UpdateStatement(this, table, UpdateValues().also { values.invoke(it) })
override fun <T> transaction(block: (Transaction) -> T): T {
return localJdbi.inTransaction<T, Exception> { handle ->
block.invoke(JdbiTransaction(handle))
}
}

fun delete(table: Table): DeleteStatement =
DeleteStatement(this, table)
fun with(transaction: Transaction?): DslHandle =
when (transaction) {
is JdbiTransaction -> JdbiDslHandle(this, transaction.handle)
else -> this
}

fun getJdbi(): Jdbi =
localJdbi

fun getDialect(): Dialect =
dialect

override fun close() {
dataSource.close()
}
override fun getHandleAccessor(): HandleAccessor =
handleAccessor

override fun getDatabase(): SqiffyDatabase =
this

}
47 changes: 47 additions & 0 deletions sqiffy/src/main/kotlin/com/dzikoysk/sqiffy/dsl/DslHandle.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.dzikoysk.sqiffy.dsl

import com.dzikoysk.sqiffy.SqiffyDatabase
import com.dzikoysk.sqiffy.dsl.statements.AutogeneratedKeyInsertStatement
import com.dzikoysk.sqiffy.dsl.statements.DeleteStatement
import com.dzikoysk.sqiffy.dsl.statements.InsertStatement
import com.dzikoysk.sqiffy.dsl.statements.SelectStatement
import com.dzikoysk.sqiffy.dsl.statements.UpdateStatement
import com.dzikoysk.sqiffy.dsl.statements.UpdateValues
import com.dzikoysk.sqiffy.transaction.HandleAccessor
import org.jdbi.v3.core.Handle

abstract class DslHandle {

abstract fun getDatabase(): SqiffyDatabase

abstract fun getHandleAccessor(): HandleAccessor

fun select(table: Table): SelectStatement =
SelectStatement(getDatabase(), getHandleAccessor(), table)

fun insert(table: TableWithAutogeneratedKey, values: (Values) -> Unit): AutogeneratedKeyInsertStatement =
AutogeneratedKeyInsertStatement(getDatabase(), getHandleAccessor(), table, Values().also { values.invoke(it) })

fun insert(table: Table, values: (Values) -> Unit): InsertStatement =
InsertStatement(getDatabase(), getHandleAccessor(), table, Values().also { values.invoke(it) })

fun update(table: Table, values: (UpdateValues) -> Unit): UpdateStatement =
UpdateStatement(getDatabase(), getHandleAccessor(), table, UpdateValues().also { values.invoke(it) })

fun delete(table: Table): DeleteStatement =
DeleteStatement(getDatabase(), getHandleAccessor(), table)

}

class JdbiDslHandle(private val database: SqiffyDatabase, private val handle: Handle) : DslHandle(), HandleAccessor {

override fun <R> inHandle(body: (Handle) -> R): R =
body.invoke(handle)

override fun getHandleAccessor(): HandleAccessor =
this

override fun getDatabase(): SqiffyDatabase =
database

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ import com.dzikoysk.sqiffy.dsl.Statement
import com.dzikoysk.sqiffy.dsl.Table
import com.dzikoysk.sqiffy.dsl.generator.ParameterAllocator
import com.dzikoysk.sqiffy.dsl.generator.bindArguments
import com.dzikoysk.sqiffy.transaction.HandleAccessor
import org.slf4j.event.Level

open class DeleteStatement(
protected val database: SqiffyDatabase,
protected val handleAccessor: HandleAccessor,
protected val table: Table,
) : Statement {

Expand All @@ -20,23 +23,24 @@ open class DeleteStatement(
}

fun execute(): Int {
return database.getJdbi().withHandle<Int, Exception> { handle ->
val allocator = ParameterAllocator()

val expression = where?.let {
database.sqlQueryGenerator.createExpression(
allocator = allocator,
expression = where!!
)
}

val (query, queryArguments) = database.sqlQueryGenerator.createDeleteQuery(
tableName = table.getName(),
where = expression?.query
val allocator = ParameterAllocator()

val expression = where?.let {
database.sqlQueryGenerator.createExpression(
allocator = allocator,
expression = where!!
)
}

val (query, queryArguments) = database.sqlQueryGenerator.createDeleteQuery(
tableName = table.getName(),
where = expression?.query
)

val arguments = queryArguments + expression?.arguments
val arguments = queryArguments + expression?.arguments
database.logger.log(Level.DEBUG, "Executing query: $query with arguments: $arguments")

return handleAccessor.inHandle { handle ->
handle
.createUpdate(query)
.bindArguments(arguments)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.dzikoysk.sqiffy.dsl.statements

import com.dzikoysk.sqiffy.SqiffyDatabase
import com.dzikoysk.sqiffy.definition.DataType.SERIAL
import com.dzikoysk.sqiffy.transaction.HandleAccessor
import com.dzikoysk.sqiffy.dsl.Row
import com.dzikoysk.sqiffy.dsl.Statement
import com.dzikoysk.sqiffy.dsl.Table
Expand All @@ -13,6 +14,7 @@ import org.slf4j.event.Level

open class AbstractInsertStatement(
protected val database: SqiffyDatabase,
protected val handleAccessor: HandleAccessor,
protected val table: Table,
protected val values: Values
) : Statement {
Expand All @@ -22,18 +24,18 @@ open class AbstractInsertStatement(
.firstOrNull { it.dataType == SERIAL }

protected open fun <T> map(mapper: (Row) -> T): List<T> {
return database.getJdbi().inTransaction<List<T>, Exception> { handle ->
val allocator = ParameterAllocator()
val allocator = ParameterAllocator()

val (updateResult, customSelect) = database.sqlQueryGenerator.createInsertQuery(
allocator = allocator,
tableName = table.getName(),
columns = values.getColumns().map { it.toQueryColumn() },
autogeneratedKey = autogeneratedKey?.toQueryColumn()
)
val (updateResult, customSelect) = database.sqlQueryGenerator.createInsertQuery(
allocator = allocator,
tableName = table.getName(),
columns = values.getColumns().map { it.toQueryColumn() },
autogeneratedKey = autogeneratedKey?.toQueryColumn()
)

database.logger.log(Level.DEBUG, "Executing query: ${updateResult.query} with arguments: ${updateResult.arguments}")
database.logger.log(Level.DEBUG, "Executing query: ${updateResult.query} with arguments: ${updateResult.arguments}")

return handleAccessor.inHandle { handle ->
val insertResult = handle
.createUpdate(updateResult.query)
.bindArguments(updateResult.arguments, values)
Expand All @@ -42,10 +44,12 @@ open class AbstractInsertStatement(
when {
customSelect != null -> null
else ->
mapper(Row(
view = view,
autogeneratedKey = autogeneratedKey
))
mapper(
Row(
view = view,
autogeneratedKey = autogeneratedKey
)
)
}
}
.list()
Expand All @@ -67,9 +71,10 @@ open class AbstractInsertStatement(

open class InsertStatement(
database: SqiffyDatabase,
accessor: HandleAccessor,
table: Table,
values: Values
) : AbstractInsertStatement(database, table, values) {
) : AbstractInsertStatement(database, accessor, table, values) {

fun execute(): Unit =
map { }.firstOrNull() ?: Unit
Expand All @@ -78,9 +83,10 @@ open class InsertStatement(

open class AutogeneratedKeyInsertStatement(
database: SqiffyDatabase,
accessor: HandleAccessor,
table: Table,
values: Values
) : AbstractInsertStatement(database, table, values) {
) : AbstractInsertStatement(database, accessor, table, values) {

public override fun <T> map(mapper: (Row) -> T): List<T> =
super.map(mapper)
Expand Down
Loading

0 comments on commit 7134e0b

Please sign in to comment.