From d581da2d27a6deebce0f5604eb5312c939d8b0f1 Mon Sep 17 00:00:00 2001 From: Chantal Loncle <82039410+bog-walk@users.noreply.github.com> Date: Tue, 21 Nov 2023 15:02:08 -0500 Subject: [PATCH] feat: EXPOSED-220 Support multiple statements returning a result in exec() Fix KDocs Add test that confirms multiple statement exec works with parameterization args. --- .../org/jetbrains/exposed/sql/Transaction.kt | 2 +- .../exposed/sql/statements/Statement.kt | 6 +- .../statements/api/PreparedStatementApi.kt | 2 +- .../sql/tests/shared/ParameterizationTests.kt | 77 +++++++++++++++++-- .../sql/tests/shared/TransactionExecTests.kt | 2 +- 5 files changed, 76 insertions(+), 13 deletions(-) diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Transaction.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Transaction.kt index ad6cdce390..0977b59292 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Transaction.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Transaction.kt @@ -125,7 +125,7 @@ open class Transaction( * * The [explicitStatementType] can be manually set to avoid iterating over [StatementType] values for the best match. * - * **Note** `StatementType.MULTI` can be provided to enable execution of multiple concatenated statements. + * **Note** `StatementType.MULTI` can be set to enable execution of multiple concatenated statements. * However, if more than one [ResultSet] is generated, only the first will be used in the [transform] block. * * @return The result of [transform] on the [ResultSet] generated by the statement execution, diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/Statement.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/Statement.kt index 4f5f3fcecf..93c196de9f 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/Statement.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/Statement.kt @@ -165,12 +165,12 @@ enum class StatementType(val group: StatementGroup) { /** An EXEC statement to execute a stored procedure or command. */ EXEC(StatementGroup.DML), - /** A SHOW statement to provide information about database objects. */ - SHOW(StatementGroup.DML), - /** A PRAGMA statement to configure or query the internal database state. */ PRAGMA(StatementGroup.DML), + /** A SHOW statement to provide information about database objects. */ + SHOW(StatementGroup.DML), + /** Represents multiple statements of mixed types concatenated in a single string. */ MULTI(StatementGroup.DML), diff --git a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/api/PreparedStatementApi.kt b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/api/PreparedStatementApi.kt index dcb49ec4e5..397336e543 100644 --- a/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/api/PreparedStatementApi.kt +++ b/exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/statements/api/PreparedStatementApi.kt @@ -39,7 +39,7 @@ interface PreparedStatementApi { * Executes multiple SQL statements stored in a single [PreparedStatement]. * * @return A list of [StatementResult]s retrieved from the database, which may store either affected row counts - * or [ResultSet]s. + * or [ResultSet]s. The order of elements is based on the order of the statements in the `PreparedStatement`. */ fun executeMultiple(): List diff --git a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/ParameterizationTests.kt b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/ParameterizationTests.kt index 6fd09ccd60..df5d7a0ecb 100644 --- a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/ParameterizationTests.kt +++ b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/ParameterizationTests.kt @@ -1,23 +1,86 @@ package org.jetbrains.exposed.sql.tests.shared import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.statements.StatementType import org.jetbrains.exposed.sql.tests.DatabaseTestsBase +import org.jetbrains.exposed.sql.tests.TestDB +import org.jetbrains.exposed.sql.tests.inProperCase +import org.jetbrains.exposed.sql.transactions.TransactionManager +import org.jetbrains.exposed.sql.transactions.transaction +import org.junit.Assume import org.junit.Test +import kotlin.test.assertNotNull class ParameterizationTests : DatabaseTestsBase() { + object TempTable : Table("tmp") { + val name = varchar("foo", 50) + } + @Test fun testInsertWithQuotesAndGetItBack() { - val table = object : Table("tmp") { - val name = varchar("foo", 50) - } - - withTables(table) { + withTables(TempTable) { exec( - "INSERT INTO ${table.tableName} (foo) VALUES (?)", + "INSERT INTO ${TempTable.tableName} (foo) VALUES (?)", listOf(VarCharColumnType() to "John \"Johny\" Johnson") ) - assertEquals("John \"Johny\" Johnson", table.selectAll().single()[table.name]) + assertEquals("John \"Johny\" Johnson", TempTable.selectAll().single()[TempTable.name]) } } + + @Test + fun testParametersWithMultipleStatements() { + val supported = setOf(TestDB.MYSQL, TestDB.MARIADB, TestDB.POSTGRESQL, TestDB.SQLSERVER) + Assume.assumeTrue(supported.containsAll(TestDB.enabledDialects())) + + val dialect = TestDB.enabledDialects().first() + val urlExtra = when (dialect) { + TestDB.MYSQL -> "&allowMultiQueries=true" + TestDB.MARIADB -> "?&allowMultiQueries=true" + else -> "" + } + val db = Database.connect( + dialect.connection.invoke().plus(urlExtra), + dialect.driver, + dialect.user, + dialect.pass + ) + + transaction(db) { + try { + SchemaUtils.create(TempTable) + + val table = TempTable.tableName.inProperCase() + val column = TempTable.name.name.inProperCase() + + val result = exec( + """ + INSERT INTO $table ($column) VALUES (?); + INSERT INTO $table ($column) VALUES (?); + INSERT INTO $table ($column) VALUES (?); + DELETE FROM $table WHERE $table.$column LIKE ?; + SELECT COUNT(*) FROM $table; + """.trimIndent(), + args = listOf( + VarCharColumnType() to "Anne", + VarCharColumnType() to "Anya", + VarCharColumnType() to "Anna", + VarCharColumnType() to "Ann%", + ), + explicitStatementType = StatementType.MULTI + ) { resultSet -> + resultSet.next() + resultSet.getInt(1) + } + assertNotNull(result) + assertEquals(1, result) + + assertEquals("Anya", TempTable.selectAll().single()[TempTable.name]) + } finally { + SchemaUtils.drop(TempTable) + } + } + + TransactionManager.closeAndUnregister(db) + } } diff --git a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/TransactionExecTests.kt b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/TransactionExecTests.kt index 40ee798536..6e7a88ade7 100644 --- a/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/TransactionExecTests.kt +++ b/exposed-tests/src/test/kotlin/org/jetbrains/exposed/sql/tests/shared/TransactionExecTests.kt @@ -92,7 +92,7 @@ class TransactionExecTests : DatabaseTestsBase() { val columnAlias = "last_inserted_id" val selectLastIdStatement = when (testDb) { - TestDB.SQLSERVER -> "SELECT current_value AS $columnAlias FROM sys.sequences" + TestDB.SQLSERVER -> "SELECT current_value AS $columnAlias FROM sys.sequences" TestDB.ORACLE -> "SELECT ${ExecTable.id.autoIncColumnType?.autoincSeq}.CURRVAL AS $columnAlias FROM DUAL" TestDB.POSTGRESQL -> "SELECT lastval() AS $columnAlias" else -> "SELECT LAST_INSERT_ID() AS $columnAlias"