diff --git a/src/main/java/io/ebeaninternal/api/BindParams.java b/src/main/java/io/ebeaninternal/api/BindParams.java index 0d5651323..4188b20b2 100644 --- a/src/main/java/io/ebeaninternal/api/BindParams.java +++ b/src/main/java/io/ebeaninternal/api/BindParams.java @@ -41,6 +41,14 @@ public class BindParams implements Serializable { public BindParams() { } + /** + * Reset positioned parameters (usually due to bind parameter expansion). + */ + public void reset() { + bindHash = null; + positionedParameters.clear(); + } + public int queryBindHash() { int hc = namedParameters.hashCode(); for (Param positionedParameter : positionedParameters) { diff --git a/src/main/java/io/ebeaninternal/api/SpiSqlUpdate.java b/src/main/java/io/ebeaninternal/api/SpiSqlUpdate.java index a7c8c6e7f..3f2ee2c3e 100644 --- a/src/main/java/io/ebeaninternal/api/SpiSqlUpdate.java +++ b/src/main/java/io/ebeaninternal/api/SpiSqlUpdate.java @@ -4,6 +4,11 @@ import io.ebean.SqlUpdate; public interface SpiSqlUpdate extends SqlUpdate { + /** + * Return the sql taking into account bind parameter expansion. + */ + String getBaseSql(); + /** * Return the Bind parameters. */ diff --git a/src/main/java/io/ebeaninternal/server/core/DefaultSqlUpdate.java b/src/main/java/io/ebeaninternal/server/core/DefaultSqlUpdate.java index 7b028fa2b..e7d2195f6 100644 --- a/src/main/java/io/ebeaninternal/server/core/DefaultSqlUpdate.java +++ b/src/main/java/io/ebeaninternal/server/core/DefaultSqlUpdate.java @@ -9,6 +9,7 @@ import io.ebeaninternal.api.SpiSqlUpdate; import io.ebeaninternal.api.SpiTransaction; import java.io.Serializable; +import java.util.Collection; /** * A SQL Update Delete or Insert statement that can be executed. For the times @@ -35,9 +36,14 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate { private final BindParams bindParams; /** - * The sql update or delete statement. + * The original sql update or delete statement. */ - private final String sql; + private final String origSql; + + /** + * The sql taking into account bind parameter expansion. + */ + private String baseSql; /** * The actual sql with named parameters converted. @@ -66,6 +72,8 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate { */ private int addPos; + private int bindExpansion; + private boolean getGeneratedKeys; private Object generatedKey; @@ -89,7 +97,8 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate { */ public DefaultSqlUpdate(SpiEbeanServer server, String sql, BindParams bindParams) { this.server = server; - this.sql = sql; + this.origSql = sql; + this.baseSql = sql; this.bindParams = bindParams; } @@ -221,11 +230,21 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate { @Override public void setGeneratedSql(String generatedSql) { this.generatedSql = generatedSql; + this.baseSql = origSql; + if (bindExpansion > 0) { + bindParams.reset(); + bindExpansion = 0; + } } @Override public String getSql() { - return sql; + return origSql; + } + + @Override + public String getBaseSql() { + return baseSql; } @Override @@ -253,21 +272,47 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate { return this; } + private SqlUpdate setParamWithBindExpansion(int position, Collection values, String bindLiteral) { + + StringBuilder sqlExpand = new StringBuilder(values.size() * 2); + position = position + bindExpansion; + int offset = 0; + for (Object val : values) { + if (offset > 0) { + sqlExpand.append(","); + } + sqlExpand.append("?"); + bindParams.setParameter(position + offset++, val); + } + bindExpansion += (offset - 1); + baseSql = baseSql.replace(bindLiteral, sqlExpand.toString()); + return this; + } + @Override public SqlUpdate setParameter(int position, Object value) { - bindParams.setParameter(position, value); + + if (value instanceof Collection) { + String bindLiteral = "?" + position; + int pos = baseSql.indexOf(bindLiteral); + if (pos > -1) { + return setParamWithBindExpansion(position, (Collection) value, bindLiteral); + } + } + + bindParams.setParameter(bindExpansion + position, value); return this; } @Override public SqlUpdate setNull(int position, int jdbcType) { - bindParams.setNullParameter(position, jdbcType); + bindParams.setNullParameter(bindExpansion + position, jdbcType); return this; } @Override public SqlUpdate setNullParameter(int position, int jdbcType) { - bindParams.setNullParameter(position, jdbcType); + bindParams.setNullParameter(bindExpansion + position, jdbcType); return this; } diff --git a/src/main/java/io/ebeaninternal/server/persist/ExeUpdateSql.java b/src/main/java/io/ebeaninternal/server/persist/ExeUpdateSql.java index 5de0ec48b..85b3024ba 100644 --- a/src/main/java/io/ebeaninternal/server/persist/ExeUpdateSql.java +++ b/src/main/java/io/ebeaninternal/server/persist/ExeUpdateSql.java @@ -87,13 +87,11 @@ class ExeUpdateSql { SpiSqlUpdate updateSql = request.getUpdateSql(); SpiTransaction t = request.getTransaction(); - String sql = updateSql.getSql(); - BindParams bindParams = updateSql.getBindParams(); // process named parameters if required + String sql = updateSql.getBaseSql(); sql = BindParamsParser.parse(bindParams, sql); - updateSql.setGeneratedSql(sql); PreparedStatement pstmt; if (batchThisRequest) { @@ -112,6 +110,7 @@ class ExeUpdateSql { } request.setBindLog(bindLog); + updateSql.setGeneratedSql(sql); // derive the statement type (for TransactionEvent) parseUpdate(sql, request); diff --git a/src/test/java/org/tests/update/TestSqlUpdateBindMultipleLists.java b/src/test/java/org/tests/update/TestSqlUpdateBindMultipleLists.java index 5019339c0..a20b042a3 100644 --- a/src/test/java/org/tests/update/TestSqlUpdateBindMultipleLists.java +++ b/src/test/java/org/tests/update/TestSqlUpdateBindMultipleLists.java @@ -1,8 +1,10 @@ package org.tests.update; import io.ebean.BaseTestCase; +import io.ebean.DB; import io.ebean.Ebean; import io.ebean.SqlUpdate; +import io.ebean.Transaction; import org.ebeantest.LoggedSqlCollector; import org.junit.Test; @@ -34,6 +36,219 @@ public class TestSqlUpdateBindMultipleLists extends BaseTestCase { assertThat(sql).hasSize(2); } + @Test + public void positionParamsExpansion() { + + SqlUpdate sqlUpdate = Ebean.createSqlUpdate("delete from o_customer where id in (?1)"); + + sqlUpdate.setParameter(1, asList(9991, 9992, 9993)); + sqlUpdate.execute(); + + assertEquals("delete from o_customer where id in (?,?,?)", sqlUpdate.getGeneratedSql()); + + sqlUpdate = Ebean.createSqlUpdate("delete from o_customer where id in (?1)"); + + sqlUpdate.setParameter(1, asList(9991, 9993)); + sqlUpdate.execute(); + + assertEquals("delete from o_customer where id in (?,?)", sqlUpdate.getGeneratedSql()); + + sqlUpdate = Ebean.createSqlUpdate("delete from o_customer where id in (?1)"); + + sqlUpdate.setParameter(1, asList(9993)); + sqlUpdate.execute(); + + assertEquals("delete from o_customer where id in (?)", sqlUpdate.getGeneratedSql()); + } + + @Test + public void positionParamsExpansion_withPrePost() { + + SqlUpdate sqlUpdate = Ebean.createSqlUpdate("delete from o_customer where id > ? and id in (?2) and id < ?"); + + sqlUpdate.setParameter(1, 90); + sqlUpdate.setParameter(2, asList(9991, 9992, 9993)); + sqlUpdate.setParameter(3, 91); + sqlUpdate.execute(); + + assertEquals("delete from o_customer where id > ? and id in (?,?,?) and id < ?", sqlUpdate.getGeneratedSql()); + } + + @Test + public void positionParamsExpansion_withPrePost_usingParam() { + + SqlUpdate sqlUpdate = Ebean.createSqlUpdate("delete from o_customer where id > ? and id in (?2) and id < ?"); + sqlUpdate.setParams(90, asList(9991, 9992, 9993), 91); + sqlUpdate.execute(); + + assertEquals("delete from o_customer where id > ? and id in (?,?,?) and id < ?", sqlUpdate.getGeneratedSql()); + } + + @Test + public void positionParamsExpansion_withPre() { + + SqlUpdate sqlUpdate = Ebean.createSqlUpdate("delete from o_customer where name = ? and id in (?2)"); + + sqlUpdate.setParameter(1, "Foo"); + sqlUpdate.setParameter(2, asList(9991, 9992, 9993)); + sqlUpdate.execute(); + + assertEquals("delete from o_customer where name = ? and id in (?,?,?)", sqlUpdate.getGeneratedSql()); + } + + @Test + public void positionParamsExpansion_multi() { + + SqlUpdate upd = DB.sqlUpdate("delete from o_customer where id in (?1) and name in (?2) and id not in (?3)"); + + try (Transaction transaction = DB.beginTransaction()) { + transaction.setBatchMode(true); + + upd.setParams(asList(9991), asList("Foo"), asList("Bar")); + upd.execute(); + + upd.setParams(asList(9991, 9992), asList("Foo", "Bar"), asList(1,2,3,4)); + upd.execute(); + + upd.setParams(asList(9991), asList("Foo", "Bar", "Baz"), asList(1,2)); + upd.execute(); + + transaction.commit(); + } + } + + @Test + public void positionParamsExpansion_withPost() { + + SqlUpdate sqlUpdate = Ebean.createSqlUpdate("delete from o_customer where id in (?1) and name = ?"); + + sqlUpdate.setParameter(1, asList(9991, 9992, 9993)); + sqlUpdate.setParameter(2, "Foo"); + sqlUpdate.execute(); + + assertEquals("delete from o_customer where id in (?,?,?) and name = ?", sqlUpdate.getGeneratedSql()); + } + + @Test + public void positionParamsExpansion_withPost_andBatch_execute() { + + SqlUpdate upd = DB.sqlUpdate("delete from o_customer where id in (?1) and name = ?"); + + try (Transaction transaction = DB.beginTransaction()) { + transaction.setBatchMode(true); + + upd.setParams(asList(9991, 9992, 9993), "Foo"); + upd.execute(); + + upd.setParams(asList(9991, 9992), "Bar"); + upd.execute(); + + upd.setParams(asList(9991, 9992, 9999), "Baz"); + upd.execute(); + + transaction.commit(); + } + } + + @Test + public void positionParamsExpansion_withPost_addBatch_execute() { + + SqlUpdate upd = DB.sqlUpdate("delete from o_customer where id in (?1) and name = ?"); + + try (Transaction transaction = DB.beginTransaction()) { + transaction.setBatchMode(true); + + upd.setParams(asList(9991, 9992, 9993), "Foo"); + upd.addBatch(); + + upd.setParams(asList(9991, 9992), "Bar"); + upd.addBatch(); + + upd.setParams(asList(9991, 9992, 9999), "Baz"); + upd.addBatch(); + + transaction.commit(); + } + } + + @Test + public void test_multipleLists_asPositioned() { + + SqlUpdate sqlUpdate = DB.sqlUpdate("delete from o_customer where id in (?1) and name in (?2)"); + + try (Transaction transaction = DB.beginTransaction()) { + + sqlUpdate.setParameter(1, asList(9991, 9992, 9993)); + sqlUpdate.setParameter(2, asList("rob", "jim")); + sqlUpdate.execute(); + assertEquals("delete from o_customer where id in (?,?,?) and name in (?,?)", sqlUpdate.getGeneratedSql()); + + sqlUpdate.setParams(asList(9991, 9992), asList("rob", "jim", "sd")); + sqlUpdate.execute(); + assertEquals("delete from o_customer where id in (?,?) and name in (?,?,?)", sqlUpdate.getGeneratedSql()); + + sqlUpdate.setParameter(1, asList(9991, 9992)); + sqlUpdate.setParameter(2, asList("rob", "jim")); + sqlUpdate.execute(); + assertEquals("delete from o_customer where id in (?,?) and name in (?,?)", sqlUpdate.getGeneratedSql()); + + sqlUpdate.setParams(asList(9991), asList("rob", "jim")); + sqlUpdate.execute(); + assertEquals("delete from o_customer where id in (?) and name in (?,?)", sqlUpdate.getGeneratedSql()); + + + sqlUpdate.setParams(asList(9992), asList("ro3b", "j3im")); + sqlUpdate.execute(); + assertEquals("delete from o_customer where id in (?) and name in (?,?)", sqlUpdate.getGeneratedSql()); + + sqlUpdate.setParameter(1, asList(9992, 4545)); + sqlUpdate.setParameter(2, asList("ro3b")); + sqlUpdate.execute(); + assertEquals("delete from o_customer where id in (?,?) and name in (?)", sqlUpdate.getGeneratedSql()); + + transaction.commit(); + } + } + + @Test + public void test_multipleLists_asPositioned_withBatch() { + + SqlUpdate sqlUpdate = DB.sqlUpdate("delete from o_customer where id in (?1) and name in (?2)"); + + try (Transaction transaction = DB.beginTransaction()) { + transaction.setBatchMode(true); + + sqlUpdate.setParameter(1, asList(9991, 9992, 9993)); + sqlUpdate.setParameter(2, asList("rob", "jim")); + sqlUpdate.execute(); + assertEquals("delete from o_customer where id in (?,?,?) and name in (?,?)", sqlUpdate.getGeneratedSql()); + + sqlUpdate.setParams(asList(9991, 9992), asList("rob", "jim", "sd")); + sqlUpdate.execute(); + assertEquals("delete from o_customer where id in (?,?) and name in (?,?,?)", sqlUpdate.getGeneratedSql()); + + sqlUpdate.setParameter(1, asList(9991, 9992)); + sqlUpdate.setParameter(2, asList("rob", "jim")); + sqlUpdate.execute(); + assertEquals("delete from o_customer where id in (?,?) and name in (?,?)", sqlUpdate.getGeneratedSql()); + + sqlUpdate.setParams(asList(9991), asList("rob", "jim")); + sqlUpdate.execute(); + assertEquals("delete from o_customer where id in (?) and name in (?,?)", sqlUpdate.getGeneratedSql()); + + + sqlUpdate.setParams(asList(9992), asList("ro3b", "j3im")); + sqlUpdate.execute(); + assertEquals("delete from o_customer where id in (?) and name in (?,?)", sqlUpdate.getGeneratedSql()); + + sqlUpdate.setParameter(1, asList(9992, 4545)); + sqlUpdate.setParameter(2, asList("ro3b")); + sqlUpdate.execute(); + assertEquals("delete from o_customer where id in (?,?) and name in (?)", sqlUpdate.getGeneratedSql()); + + transaction.commit(); + } + } @Test public void test_multipleLists() {