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()); + + } + +}