From 6ecb77dcc28891244263802b110031f1e9ce524f Mon Sep 17 00:00:00 2001
From: rbygrave
Date: Sat, 22 Nov 2014 23:40:14 +1300
Subject: [PATCH] Fix for #206 - Reuse SqlUpdate when binding a list that can
vary in size
---
src/main/java/com/avaje/ebean/SqlUpdate.java | 5 +
.../avaje/ebeaninternal/api/BindParams.java | 89 +++--
.../avaje/ebeaninternal/api/SpiSqlUpdate.java | 2 +
.../server/core/DefaultSqlUpdate.java | 16 +-
.../ebeaninternal/server/persist/Binder.java | 28 +-
.../server/persist/ExeUpdateSql.java | 1 +
.../server/util/BindParamsParser.java | 356 +++++++++---------
.../ebeaninternal/api/BindParamsTest.java | 40 ++
.../server/util/BindParamsParserTest.java | 35 ++
.../TestSqlUpdateBindMultipleLists.java | 67 ++++
10 files changed, 406 insertions(+), 233 deletions(-)
create mode 100644 src/test/java/com/avaje/ebeaninternal/api/BindParamsTest.java
create mode 100644 src/test/java/com/avaje/ebeaninternal/server/util/BindParamsParserTest.java
create mode 100644 src/test/java/com/avaje/tests/update/TestSqlUpdateBindMultipleLists.java
diff --git a/src/main/java/com/avaje/ebean/SqlUpdate.java b/src/main/java/com/avaje/ebean/SqlUpdate.java
index aaa4833a8..716478070 100644
--- a/src/main/java/com/avaje/ebean/SqlUpdate.java
+++ b/src/main/java/com/avaje/ebean/SqlUpdate.java
@@ -91,6 +91,11 @@ public interface SqlUpdate {
*/
public String getSql();
+ /**
+ * Return the generated sql that has named parameters converted to positioned parameters.
+ */
+ public String getGeneratedSql();
+
/**
* Return the timeout used to execute this statement.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/api/BindParams.java b/src/main/java/com/avaje/ebeaninternal/api/BindParams.java
index 23f43cd63..358bbbf8b 100644
--- a/src/main/java/com/avaje/ebeaninternal/api/BindParams.java
+++ b/src/main/java/com/avaje/ebeaninternal/api/BindParams.java
@@ -31,6 +31,12 @@ public class BindParams implements Serializable {
*/
private String preparedSql;
+ /**
+ * Bind hash and count used to detect when the bind values have changed such
+ * that the generated SQL (with named parameters) needs to be recalculated.
+ */
+ private int[] bindHash;
+
public BindParams() {
}
@@ -51,20 +57,33 @@ public class BindParams implements Serializable {
*
*/
public void buildQueryPlanHash(HashQueryPlanBuilder builder) {
+ int[] vals = calcQueryPlanHash();
+ builder.add(vals[0]).bind(vals[1]);
+ }
+
+ /**
+ * Calculate and return a query plan bind hash with total bind count.
+ */
+ public int[] calcQueryPlanHash() {
+ int tempBindCount;
+ int bc = 0;
int hc = 31;
for (Param param : positionedParameters) {
- hc = hc * 31 + param.queryBindCount();
+ tempBindCount = param.queryBindCount();
+ bc += tempBindCount;
+ hc = hc * 31 + tempBindCount;
}
for (Map.Entry entry : namedParameters.entrySet()) {
+ tempBindCount = entry.getValue().queryBindCount();
+ bc += tempBindCount;
hc = hc * 31 + entry.getKey().hashCode();
- hc = hc * 31 + entry.getValue().queryBindCount();
+ hc = hc * 31 + tempBindCount;
}
- int bindCount = positionedParameters.size() + namedParameters.size();
- builder.add(hc).bind(bindCount);
+ return new int[]{hc, bc};
}
-
+
/**
* Return a deep copy of the BindParams.
*/
@@ -110,7 +129,7 @@ public class BindParams implements Serializable {
* parameters ordered.
*/
public boolean requiresNamedParamsPrepare() {
- return !namedParameters.isEmpty() && positionedParameters.isEmpty();
+ return !namedParameters.isEmpty();
}
/**
@@ -150,7 +169,7 @@ public class BindParams implements Serializable {
}
private Param getParam(String name) {
- Param p = (Param) namedParameters.get(name);
+ Param p = namedParameters.get(name);
if (p == null) {
p = new Param();
namedParameters.put(name, p);
@@ -165,7 +184,7 @@ public class BindParams implements Serializable {
positionedParameters.add(new Param());
}
}
- return (Param) positionedParameters.get(position - 1);
+ return positionedParameters.get(position - 1);
}
/**
@@ -253,7 +272,29 @@ public class BindParams implements Serializable {
return preparedSql;
}
- /**
+ /**
+ * Return true if the bind hash and count has not changed.
+ */
+ public boolean isSameBindHash() {
+
+ if (bindHash == null) {
+ bindHash = calcQueryPlanHash();
+ return false;
+ }
+ int[] oldPlan = bindHash;
+ bindHash = calcQueryPlanHash();
+ return bindHash[0] == oldPlan[0] && bindHash[1] == oldPlan[1];
+ }
+
+ /**
+ * Create a new positioned parameters orderedList.
+ */
+ public OrderedList createOrderedList() {
+ positionedParameters.clear();
+ return new OrderedList(positionedParameters);
+ }
+
+ /**
* The bind parameters in the correct binding order.
*
* This is the result of converting sql with named parameters
@@ -327,8 +368,6 @@ public class BindParams implements Serializable {
private Object outValue;
- private int textLocation;
-
/**
* Construct a Parameter.
*/
@@ -368,17 +407,8 @@ public class BindParams implements Serializable {
}
public boolean equals(Object o) {
- if (o == null) {
- return false;
- }
- if (o == this) {
- return true;
- }
- if (o instanceof Param) {
- return hashCode() == o.hashCode();
- }
- return false;
- }
+ return o != null && (o == this || (o instanceof Param) && hashCode() == o.hashCode());
+ }
/**
* Return true if this is an In parameter that needs to be bound before
@@ -463,21 +493,6 @@ public class BindParams implements Serializable {
this.outValue = out;
}
- /**
- * Return the location this parameter was found in the sql text.
- */
- public int getTextLocation() {
- return textLocation;
- }
-
- /**
- * Set the location in the sql text this parameter was located. This is
- * used to control order for named parameters.
- */
- public void setTextLocation(int textLocation) {
- this.textLocation = textLocation;
- }
-
/**
* If true do not include this value in a transaction log.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiSqlUpdate.java b/src/main/java/com/avaje/ebeaninternal/api/SpiSqlUpdate.java
index d2797247e..fe8d55006 100644
--- a/src/main/java/com/avaje/ebeaninternal/api/SpiSqlUpdate.java
+++ b/src/main/java/com/avaje/ebeaninternal/api/SpiSqlUpdate.java
@@ -5,4 +5,6 @@ import com.avaje.ebean.SqlUpdate;
public interface SpiSqlUpdate extends SqlUpdate {
public BindParams getBindParams();
+
+ public void setGeneratedSql(String sql);
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultSqlUpdate.java b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultSqlUpdate.java
index 1960b543d..b188e9358 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/DefaultSqlUpdate.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/DefaultSqlUpdate.java
@@ -93,6 +93,11 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
*/
private final String sql;
+ /**
+ * The actual sql with named parameters converted.
+ */
+ private String generatedSql;
+
/**
* Some descriptive text that can be put into the transaction log.
*/
@@ -170,7 +175,16 @@ public final class DefaultSqlUpdate implements Serializable, SpiSqlUpdate {
return this;
}
- public String getSql() {
+ public String getGeneratedSql() {
+ return generatedSql;
+ }
+
+ @Override
+ public void setGeneratedSql(String generatedSql) {
+ this.generatedSql = generatedSql;
+ }
+
+ public String getSql() {
return sql;
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/Binder.java b/src/main/java/com/avaje/ebeaninternal/server/persist/Binder.java
index 7c92099fd..bf45101a1 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/persist/Binder.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/persist/Binder.java
@@ -47,7 +47,7 @@ public class Binder {
ArrayList list = bindValues.values();
for (int i = 0; i < list.size(); i++) {
- BindValues.Value bindValue = (BindValues.Value) list.get(i);
+ BindValues.Value bindValue = list.get(i);
if (bindValue.isComment()) {
if (bindBuf != null) {
bindBuf.append(bindValue.getName());
@@ -226,14 +226,12 @@ public class Binder {
try {
switch (dataType) {
case java.sql.Types.BOOLEAN:
- Boolean bo = (Boolean) data;
- b.setBoolean(bo.booleanValue());
+ b.setBoolean((Boolean) data);
break;
- case java.sql.Types.BIT:
- // Types.BIT should map to Java Boolean
- Boolean bitBool = (Boolean) data;
- b.setBoolean(bitBool.booleanValue());
- break;
+ case java.sql.Types.BIT:
+ // Types.BIT should map to Java Boolean
+ b.setBoolean((Boolean) data);
+ break;
case java.sql.Types.VARCHAR:
b.setString((String) data);
@@ -244,32 +242,32 @@ public class Binder {
break;
case java.sql.Types.TINYINT:
- b.setByte(((Byte) data).byteValue());
+ b.setByte((Byte) data);
break;
case java.sql.Types.SMALLINT:
- b.setShort(((Short) data).shortValue());
+ b.setShort((Short) data);
break;
case java.sql.Types.INTEGER:
- b.setInt(((Integer) data).intValue());
+ b.setInt((Integer) data);
break;
case java.sql.Types.BIGINT:
- b.setLong(((Long) data).longValue());
+ b.setLong((Long) data);
break;
case java.sql.Types.REAL:
- b.setFloat(((Float) data).floatValue());
+ b.setFloat((Float) data);
break;
case java.sql.Types.FLOAT:
// DB Float in theory maps to Java Double type
- b.setDouble(((Double) data).doubleValue());
+ b.setDouble((Double) data);
break;
case java.sql.Types.DOUBLE:
- b.setDouble(((Double) data).doubleValue());
+ b.setDouble((Double) data);
break;
case java.sql.Types.NUMERIC:
diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeUpdateSql.java b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeUpdateSql.java
index d374fdf0e..c0b839f5e 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeUpdateSql.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeUpdateSql.java
@@ -98,6 +98,7 @@ public class ExeUpdateSql {
// process named parameters if required
sql = BindParamsParser.parse(bindParams, sql);
+ updateSql.setGeneratedSql(sql);
boolean logSql = request.isLogSql();
diff --git a/src/main/java/com/avaje/ebeaninternal/server/util/BindParamsParser.java b/src/main/java/com/avaje/ebeaninternal/server/util/BindParamsParser.java
index ab129b3f1..56ef81325 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/util/BindParamsParser.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/util/BindParamsParser.java
@@ -18,205 +18,201 @@ import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
*/
public class BindParamsParser {
+ public static final String ENCRYPTKEY_PREFIX = "encryptkey_";
+ public static final String ENCRYPTKEY_GAP = "___";
- public static final String ENCRYPTKEY_PREFIX = "encryptkey_";
- public static final String ENCRYPTKEY_GAP = "___";
+ private static final int ENCRYPTKEY_PREFIX_LEN = ENCRYPTKEY_PREFIX.length();
+ private static final int ENCRYPTKEY_GAP_LEN = ENCRYPTKEY_GAP.length();
- private static final int ENCRYPTKEY_PREFIX_LEN = ENCRYPTKEY_PREFIX.length();
- private static final int ENCRYPTKEY_GAP_LEN = ENCRYPTKEY_GAP.length();
+ /**
+ * Used to parse sql looking for named parameters.
+ */
+ private static final String quote = "'";
- /**
- * Used to parse sql looking for named parameters.
- */
- private static final String quote = "'";
+ /**
+ * Used to parse sql looking for named parameters.
+ */
+ private static final String colon = ":";
- /**
- * Used to parse sql looking for named parameters.
- */
- private static final String colon = ":";
+ private final BindParams params;
+ private final String sql;
- private final BindParams params;
- private final String sql;
+ private final BeanDescriptor> beanDescriptor;
- private final BeanDescriptor> beanDescriptor;
+ public static String parse(BindParams params, String sql) {
+ return parse(params, sql, null);
+ }
- public static String parse(BindParams params, String sql) {
- return parse(params, sql, null);
- }
-
- public static String parse(BindParams params, String sql, BeanDescriptor> beanDescriptor) {
- return new BindParamsParser(params, sql, beanDescriptor).parseSql();
+ public static String parse(BindParams params, String sql, BeanDescriptor> beanDescriptor) {
+ return new BindParamsParser(params, sql, beanDescriptor).parseSql();
+ }
+
+ public static OrderedList parseNamedParams(BindParams params, String sql) {
+ return new BindParamsParser(params, sql, null).parseSqlNamedParams();
+ }
+
+ private BindParamsParser(BindParams params, String sql, BeanDescriptor> beanDescriptor) {
+ this.params = params;
+ this.sql = sql;
+ this.beanDescriptor = beanDescriptor;
+ }
+
+ /**
+ * Used for parsing having clauses with named parameters.
+ *
+ * The issue here is that BindParams contains named parameters for
+ * both where and having clauses. BindParams.positionedParameters is
+ * used for the where and the OrderedList for the having.
+ *
+ */
+ private OrderedList parseSqlNamedParams() {
+ OrderedList orderedList = new OrderedList();
+ parseNamedParams(orderedList);
+ return orderedList;
+ }
+
+ /**
+ * Parse the sql changed named parameters to positioned parameters if required.
+ *
+ * The sql is used when named parameters are used.
+ *
+ *
+ * This is used in most cases of named parameters. The case it is NOT used for is
+ * named parameters in a having clause. In this case some of the named parameters
+ * could be for a where clause and some for the having clause.
+ *
+ */
+ private String parseSql() {
+
+ if (params.isSameBindHash()) {
+ String preparedSql = params.getPreparedSql();
+ if (preparedSql != null && preparedSql.length() > 0) {
+ // the sql has already been parsed and positionedParameters are set in order
+ return preparedSql;
+ }
}
- public static OrderedList parseNamedParams(BindParams params, String sql) {
- return new BindParamsParser(params, sql, null).parseSqlNamedParams();
+ String preparedSql;
+ if (params.requiresNamedParamsPrepare()) {
+ // convert named parameters into ordered list
+ OrderedList orderedList = params.createOrderedList();
+ parseNamedParams(orderedList);
+ preparedSql = orderedList.getPreparedSql();
+ } else {
+ preparedSql = sql;
}
-
- private BindParamsParser(BindParams params, String sql, BeanDescriptor> beanDescriptor) {
- this.params = params;
- this.sql = sql;
- this.beanDescriptor = beanDescriptor;
+ params.setPreparedSql(preparedSql);
+ return preparedSql;
+ }
+
+ /**
+ * Named parameters need to be parsed and replaced with ?.
+ */
+ private void parseNamedParams(OrderedList orderedList) {
+
+ parseNamedParams(0, orderedList);
+ }
+
+ private void parseNamedParams(int startPos, OrderedList orderedList) {
+
+ if (sql == null) {
+ throw new PersistenceException("query does not contain any named bind parameters?");
}
-
- /**
- * Used for parsing having clauses with named parameters.
- *
- * The issue here is that BindParams contains named parameters for
- * both where and having clauses. BindParams.positionedParameters is
- * used for the where and the OrderedList for the having.
- *
- */
- private OrderedList parseSqlNamedParams() {
- OrderedList orderedList = new OrderedList();
- parseNamedParams(orderedList);
- return orderedList;
+ if (startPos > sql.length()) {
+ return;
}
-
- /**
- * Parse the sql changed named parameters to positioned parameters if required.
- *
- * The sql is used when named parameters are used.
- *
- *
- * This is used in most cases of named parameters. The case it is NOT used for is
- * named parameters in a having clause. In this case some of the named parameters
- * could be for a where clause and some for the having clause.
- *
- */
- private String parseSql() {
-
- String preparedSql = params.getPreparedSql();
- if (preparedSql != null && preparedSql.length() > 0){
- // the sql has already been parsed and
- // positionedParameters are set in order
- return preparedSql;
- }
-
- String prepardSql;
- if (params.requiresNamedParamsPrepare()) {
- OrderedList orderedList = new OrderedList(params.positionedParameters());
-
- parseNamedParams(orderedList);
- prepardSql = orderedList.getPreparedSql();
+
+ // search for quotes and named params... in order...
+ int beginQuotePos = sql.indexOf(quote, startPos);
+ int nameParamStart = sql.indexOf(colon, startPos);
+ if (beginQuotePos > 0 && beginQuotePos < nameParamStart) {
+ // the quote precedes the named parameter...
+ // find and add up to the end quote
+ int endQuotePos = sql.indexOf(quote, beginQuotePos + 1);
+ String sub = sql.substring(startPos, endQuotePos + 1);
+ orderedList.appendSql(sub);
+
+ // start again after the end quote
+ parseNamedParams(endQuotePos + 1, orderedList);
+
+ } else {
+ if (nameParamStart < 0) {
+ // no more params, add the rest
+ String sub = sql.substring(startPos, sql.length());
+ orderedList.appendSql(sub);
+
+ } else {
+ // find the end of the parameter name
+ int endOfParam = nameParamStart + 1;
+ do {
+ char c = sql.charAt(endOfParam);
+ if (c != '_' && !Character.isLetterOrDigit(c)) {
+ break;
+ }
+ endOfParam++;
+ } while (endOfParam < sql.length());
+
+ // add the named parameter value to bindList
+ String paramName = sql.substring(nameParamStart + 1, endOfParam);
+
+ Param param;
+ if (paramName.startsWith(ENCRYPTKEY_PREFIX)) {
+ param = addEncryptKeyParam(paramName);
} else {
- prepardSql = sql;
- }
- params.setPreparedSql(prepardSql);
- return prepardSql;
- }
-
-
-
- /**
- * Named parameters need to be parsed and replaced with ?.
- */
- private void parseNamedParams(OrderedList orderedList) {
-
- parseNamedParams(0, orderedList);
- }
-
- private void parseNamedParams(int startPos, OrderedList orderedList) {
-
- if (sql == null){
- throw new PersistenceException("query does not contain any named bind parameters?");
- }
- if (startPos > sql.length()) {
- return;
+ param = params.getParameter(paramName);
}
- // search for quotes and named params... in order...
- int beginQuotePos = sql.indexOf(quote, startPos);
- int nameParamStart = sql.indexOf(colon, startPos);
- if (beginQuotePos > 0 && beginQuotePos < nameParamStart) {
- // the quote precedes the named parameter...
- // find and add up to the end quote
- int endQuotePos = sql.indexOf(quote, beginQuotePos + 1);
- String sub = sql.substring(startPos, endQuotePos + 1);
- orderedList.appendSql(sub);
+ if (param == null) {
+ String msg = "Bind value is not set or null for [" + paramName + "] in [" + sql + "]";
+ throw new PersistenceException(msg);
+ }
- // start again after the end quote
- parseNamedParams(endQuotePos + 1, orderedList);
+ String sub = sql.substring(startPos, nameParamStart);
+ orderedList.appendSql(sub);
- } else {
- if (nameParamStart < 0) {
- // no more params, add the rest
- String sub = sql.substring(startPos, sql.length());
- orderedList.appendSql(sub);
-
- } else {
- // find the end of the parameter name
- int endOfParam = nameParamStart + 1;
- do {
- char c = sql.charAt(endOfParam);
- if (c != '_' && !Character.isLetterOrDigit(c)) {
- break;
- }
- endOfParam++;
- } while (endOfParam < sql.length());
-
- // add the named parameter value to bindList
- String paramName = sql.substring(nameParamStart + 1, endOfParam);
-
- Param param;
- if (paramName.startsWith(ENCRYPTKEY_PREFIX)){
- param = addEncryptKeyParam(paramName);
- } else {
- param = params.getParameter(paramName);
- }
-
- if (param == null) {
- String msg = "Bind value is not set or null for [" + paramName
- + "] in [" + sql+ "]";
- throw new PersistenceException(msg);
- }
-
- String sub = sql.substring(startPos, nameParamStart);
- orderedList.appendSql(sub);
-
- // check if inValue is a Collection type...
- Object inValue = param.getInValue();
- if (inValue != null && inValue instanceof Collection>){
- // Chop up Collection parameter into a number
- // of individual parameters and add each one individually
- Collection> collection = (Collection>)inValue;
- int c = 0;
- for (Object elVal : collection) {
- if (++c > 1){
- orderedList.appendSql(",");
- }
- orderedList.appendSql("?");
- BindParams.Param elParam = new BindParams.Param();
- elParam.setInValue(elVal);
- orderedList.add(elParam);
- }
-
- } else {
- // its a normal scalar value parameter...
- orderedList.add(param);
- orderedList.appendSql("?");
- }
-
- // continue on after the end of the parameter
- parseNamedParams(endOfParam, orderedList);
+ // check if inValue is a Collection type...
+ Object inValue = param.getInValue();
+ if (inValue != null && inValue instanceof Collection>) {
+ // Chop up Collection parameter into a number
+ // of individual parameters and add each one individually
+ Collection> collection = (Collection>) inValue;
+ int c = 0;
+ for (Object elVal : collection) {
+ if (++c > 1) {
+ orderedList.appendSql(",");
}
- }
- }
+ orderedList.appendSql("?");
+ BindParams.Param elParam = new BindParams.Param();
+ elParam.setInValue(elVal);
+ orderedList.add(elParam);
+ }
- /**
- * Add an encryption key bind parameter.
- */
- private Param addEncryptKeyParam(String keyNamedParam) {
-
-
- int pos = keyNamedParam.indexOf(ENCRYPTKEY_GAP, ENCRYPTKEY_PREFIX_LEN);
-
- String tableName = keyNamedParam.substring(ENCRYPTKEY_PREFIX_LEN, pos);
- String columnName = keyNamedParam.substring(pos+ENCRYPTKEY_GAP_LEN);
-
- EncryptKey key = beanDescriptor.getEncryptKey(tableName, columnName);
- String strKey = key.getStringValue();
-
- return params.setEncryptionKey(keyNamedParam, strKey);
+ } else {
+ // its a normal scalar value parameter...
+ orderedList.add(param);
+ orderedList.appendSql("?");
+ }
+
+ // continue on after the end of the parameter
+ parseNamedParams(endOfParam, orderedList);
+ }
}
+ }
+
+ /**
+ * Add an encryption key bind parameter.
+ */
+ private Param addEncryptKeyParam(String keyNamedParam) {
+
+ int pos = keyNamedParam.indexOf(ENCRYPTKEY_GAP, ENCRYPTKEY_PREFIX_LEN);
+
+ String tableName = keyNamedParam.substring(ENCRYPTKEY_PREFIX_LEN, pos);
+ String columnName = keyNamedParam.substring(pos + ENCRYPTKEY_GAP_LEN);
+
+ EncryptKey key = beanDescriptor.getEncryptKey(tableName, columnName);
+ String strKey = key.getStringValue();
+
+ return params.setEncryptionKey(keyNamedParam, strKey);
+ }
}
diff --git a/src/test/java/com/avaje/ebeaninternal/api/BindParamsTest.java b/src/test/java/com/avaje/ebeaninternal/api/BindParamsTest.java
new file mode 100644
index 000000000..50ffe0680
--- /dev/null
+++ b/src/test/java/com/avaje/ebeaninternal/api/BindParamsTest.java
@@ -0,0 +1,40 @@
+package com.avaje.ebeaninternal.api;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.*;
+
+public class BindParamsTest {
+
+ @Test
+ public void test_hash() {
+
+ BindParams bindParams = new BindParams();
+
+ List ids = Arrays.asList("1", "2", "3");
+ bindParams.setParameter("ids", ids);
+ BindParams.Param param = bindParams.getParameter("ids");
+ assertEquals(3, param.queryBindCount());
+ assertFalse(bindParams.isSameBindHash());
+
+ List ids2 = Arrays.asList("1", "2", "3", "4");
+ bindParams.setParameter("ids", ids2);
+ assertEquals(4, param.queryBindCount());
+ assertFalse(bindParams.isSameBindHash());
+
+ List ids3 = Arrays.asList("2", "99", "44");
+ bindParams.setParameter("ids", ids3);
+ assertEquals(3, param.queryBindCount());
+ assertFalse(bindParams.isSameBindHash());
+
+
+ List ids4 = Arrays.asList("4545", "3499", "3444");
+ bindParams.setParameter("ids", ids4);
+ assertEquals(3, param.queryBindCount());
+ assertTrue(bindParams.isSameBindHash());
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/avaje/ebeaninternal/server/util/BindParamsParserTest.java b/src/test/java/com/avaje/ebeaninternal/server/util/BindParamsParserTest.java
new file mode 100644
index 000000000..522ca81b4
--- /dev/null
+++ b/src/test/java/com/avaje/ebeaninternal/server/util/BindParamsParserTest.java
@@ -0,0 +1,35 @@
+package com.avaje.ebeaninternal.server.util;
+
+import com.avaje.ebeaninternal.api.BindParams;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+import static org.junit.Assert.assertEquals;
+
+public class BindParamsParserTest {
+
+ @Test
+ public void testParse() throws Exception {
+
+ String dml = "delete from foo where id in (:ids)";
+ BindParams bindParams = new BindParams();
+
+ bindParams.setParameter("ids", Arrays.asList("1", "2", "3"));
+ String sql1 = BindParamsParser.parse(bindParams, dml);
+ assertEquals("delete from foo where id in (?,?,?)", sql1);
+
+ bindParams.setParameter("ids", Arrays.asList("451", "52"));
+ sql1 = BindParamsParser.parse(bindParams, dml);
+ assertEquals("delete from foo where id in (?,?)", sql1);
+
+ bindParams.setParameter("ids", Arrays.asList("545", "656"));
+ sql1 = BindParamsParser.parse(bindParams, dml);
+ assertEquals("delete from foo where id in (?,?)", sql1);
+
+ bindParams.setParameter("ids", Arrays.asList("545df", "df656", "SDF", "sdf"));
+ sql1 = BindParamsParser.parse(bindParams, dml);
+ assertEquals("delete from foo where id in (?,?,?,?)", sql1);
+
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/avaje/tests/update/TestSqlUpdateBindMultipleLists.java b/src/test/java/com/avaje/tests/update/TestSqlUpdateBindMultipleLists.java
new file mode 100644
index 000000000..4590fbf1a
--- /dev/null
+++ b/src/test/java/com/avaje/tests/update/TestSqlUpdateBindMultipleLists.java
@@ -0,0 +1,67 @@
+package com.avaje.tests.update;
+
+import com.avaje.ebean.BaseTestCase;
+import com.avaje.ebean.Ebean;
+import com.avaje.ebean.SqlUpdate;
+import org.junit.Test;
+
+import static java.util.Arrays.asList;
+import static org.junit.Assert.assertEquals;
+
+public class TestSqlUpdateBindMultipleLists extends BaseTestCase {
+
+ @Test
+ public void test() {
+
+ SqlUpdate sqlUpdate = Ebean.createSqlUpdate("delete from o_customer where id in (:ids)");
+
+ sqlUpdate.setParameter("ids", asList(9991, 9992, 9993));
+ sqlUpdate.execute();
+ assertEquals("delete from o_customer where id in (?,?,?)", sqlUpdate.getGeneratedSql());
+
+ // 3 parameters in the IN clause
+ sqlUpdate.setParameter("ids", asList(9991, 9992));
+ sqlUpdate.execute();
+ assertEquals("delete from o_customer where id in (?,?)", sqlUpdate.getGeneratedSql());
+
+ }
+
+
+ @Test
+ public void test_multipleLists() {
+
+ SqlUpdate sqlUpdate = Ebean.createSqlUpdate("delete from o_customer where id in (:ids) and name in (:names)");
+
+ sqlUpdate.setParameter("ids", asList(9991, 9992, 9993));
+ sqlUpdate.setParameter("names", asList("rob", "jim"));
+ sqlUpdate.execute();
+ assertEquals("delete from o_customer where id in (?,?,?) and name in (?,?)", sqlUpdate.getGeneratedSql());
+
+ sqlUpdate.setParameter("ids", asList(9991, 9992));
+ sqlUpdate.setParameter("names", asList("rob", "jim", "sd"));
+ sqlUpdate.execute();
+ assertEquals("delete from o_customer where id in (?,?) and name in (?,?,?)", sqlUpdate.getGeneratedSql());
+
+ sqlUpdate.setParameter("ids", asList(9991, 9992));
+ sqlUpdate.setParameter("names", asList("rob", "jim"));
+ sqlUpdate.execute();
+ assertEquals("delete from o_customer where id in (?,?) and name in (?,?)", sqlUpdate.getGeneratedSql());
+
+ sqlUpdate.setParameter("ids", asList(9991));
+ sqlUpdate.setParameter("names", asList("rob", "jim"));
+ sqlUpdate.execute();
+ assertEquals("delete from o_customer where id in (?) and name in (?,?)", sqlUpdate.getGeneratedSql());
+
+ sqlUpdate.setParameter("ids", asList(9992));
+ sqlUpdate.setParameter("names", asList("ro3b", "j3im"));
+ sqlUpdate.execute();
+ assertEquals("delete from o_customer where id in (?) and name in (?,?)", sqlUpdate.getGeneratedSql());
+
+ sqlUpdate.setParameter("ids", asList(9992,4545));
+ sqlUpdate.setParameter("names", asList("ro3b"));
+ sqlUpdate.execute();
+ assertEquals("delete from o_customer where id in (?,?) and name in (?)", sqlUpdate.getGeneratedSql());
+
+ }
+
+}