Author: wspeirs Date: Tue Feb 26 19:52:25 2013 New Revision: 1450376 URL: http://svn.apache.org/r1450376 Log: Added null parameter binding
Modified: commons/proper/dbutils/branches/2_0/src/main/java/org/apache/commons/dbutils/AbstractExecutor.java commons/proper/dbutils/branches/2_0/src/main/java/org/apache/commons/dbutils/BatchExecutor.java commons/proper/dbutils/branches/2_0/src/test/java/org/apache/commons/dbutils/AbstractExecutorTest.java Modified: commons/proper/dbutils/branches/2_0/src/main/java/org/apache/commons/dbutils/AbstractExecutor.java URL: http://svn.apache.org/viewvc/commons/proper/dbutils/branches/2_0/src/main/java/org/apache/commons/dbutils/AbstractExecutor.java?rev=1450376&r1=1450375&r2=1450376&view=diff ============================================================================== --- commons/proper/dbutils/branches/2_0/src/main/java/org/apache/commons/dbutils/AbstractExecutor.java (original) +++ commons/proper/dbutils/branches/2_0/src/main/java/org/apache/commons/dbutils/AbstractExecutor.java Tue Feb 26 19:52:25 2013 @@ -21,6 +21,7 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.sql.Types; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -136,16 +137,72 @@ abstract class AbstractExecutor<T extend * @return this execution object to provide the fluent style. * @throws SQLException thrown if the parameter is not found, already bound, or there is an issue binding it. */ - public T bind(String name, final Object value) throws SQLException { + public T bind(final String name, final Object value) throws SQLException { return bind(name, value, true); } + + /** + * Binds null to a parameter. + * Types.VARCHAR is used as the type's parameter. + * This usually works, but fails with some Oracle and MS SQL drivers. + * @param name the name of the parameter. + * @return this execution object to provide the fluent style. + * @throws SQLException throw if the parameter is not found, already bound, or there is an issue binding null. + */ + public T bindNull(final String name) throws SQLException { + return bindNull(name, Types.VARCHAR, true); + } + + /** + * Binds null to a parameter, specifying the parameter's type. + * @param name the name of the parameter. + * @param sqlType the type of the parameter. + * @return this execution object to provide the fluent style. + * @throws SQLException throw if the parameter is not found, already bound, or there is an issue binding null. + */ + public T bindNull(final String name, final int sqlType) throws SQLException { + return bindNull(name, sqlType, true); + } + + /** + * Given a param name and sqlType, binds a null to that parameter. + * @param name the name of the parameter. + * @param sqlType the type of the parameter. + * @param removeFromPosMap if the param should be removed from the pos map. + * @return this + * @throws SQLException if there is an SQLException during binding. + */ + protected T bindNull(String name, int sqlType, boolean removeFromPosMap) throws SQLException { + name = name.replace(COLON, ""); // so we can take ":name" or "name" + + final List<Integer> pos = removeFromPosMap ? paramPosMap.remove(name) : paramPosMap.get(name); + + if(pos == null) { + throw new SQLException(name + " is not found in the SQL statement"); + } + + // go through and bind all of the positions for this name + for(Integer p:pos) { + stmt.setNull(p, sqlType); + } + + // add the param and value to our map + paramValueMap.put(name, null); + + // suppressed because the casting will always work here + @SuppressWarnings("unchecked") + final T ret = (T) this; + + return ret; + } /** * Binds value to name, but does not do the bookkeeping. * @param name the parameter name. * @param value the value. + * @param removeFromPosMap if the param should be removed from the pos map. * @return this - * @throws SQLException if there is any SQLException during binding. + * @throws SQLException if there is an SQLException during binding. */ protected T bind(String name, final Object value, boolean removeFromPosMap) throws SQLException { name = name.replace(COLON, ""); // so we can take ":name" or "name" @@ -188,7 +245,6 @@ abstract class AbstractExecutor<T extend * @throws SQLException if a database access error occurs */ protected void rethrow(SQLException cause) throws SQLException { - String causeMessage = cause.getMessage(); if (causeMessage == null) { Modified: commons/proper/dbutils/branches/2_0/src/main/java/org/apache/commons/dbutils/BatchExecutor.java URL: http://svn.apache.org/viewvc/commons/proper/dbutils/branches/2_0/src/main/java/org/apache/commons/dbutils/BatchExecutor.java?rev=1450376&r1=1450375&r2=1450376&view=diff ============================================================================== --- commons/proper/dbutils/branches/2_0/src/main/java/org/apache/commons/dbutils/BatchExecutor.java (original) +++ commons/proper/dbutils/branches/2_0/src/main/java/org/apache/commons/dbutils/BatchExecutor.java Tue Feb 26 19:52:25 2013 @@ -2,6 +2,7 @@ package org.apache.commons.dbutils; import java.sql.Connection; import java.sql.SQLException; +import java.sql.Types; /** * This class provides the ability to execute a batch of statements. @@ -30,9 +31,32 @@ public class BatchExecutor extends Abstr */ @Override public BatchExecutor bind(final String name, final Object value) throws SQLException { - bind(name, value, false); - - return this; + return bind(name, value, false); + } + + /** + * Binds null to a parameter. + * Types.VARCHAR is used as the type's parameter. + * This usually works, but fails with some Oracle and MS SQL drivers. + * @param name the name of the parameter. + * @return this execution object to provide the fluent style. + * @throws SQLException throw if the parameter is not found, already bound, or there is an issue binding null. + */ + @Override + public BatchExecutor bindNull(final String name) throws SQLException { + return bindNull(name, Types.VARCHAR, false); + } + + /** + * Binds null to a parameter, specifying the parameter's type. + * @param name the name of the parameter. + * @param sqlType the type of the parameter. + * @return this execution object to provide the fluent style. + * @throws SQLException throw if the parameter is not found, already bound, or there is an issue binding null. + */ + @Override + public BatchExecutor bindNull(final String name, final int sqlType) throws SQLException { + return bindNull(name, sqlType, false); } /** Modified: commons/proper/dbutils/branches/2_0/src/test/java/org/apache/commons/dbutils/AbstractExecutorTest.java URL: http://svn.apache.org/viewvc/commons/proper/dbutils/branches/2_0/src/test/java/org/apache/commons/dbutils/AbstractExecutorTest.java?rev=1450376&r1=1450375&r2=1450376&view=diff ============================================================================== --- commons/proper/dbutils/branches/2_0/src/test/java/org/apache/commons/dbutils/AbstractExecutorTest.java (original) +++ commons/proper/dbutils/branches/2_0/src/test/java/org/apache/commons/dbutils/AbstractExecutorTest.java Tue Feb 26 19:52:25 2013 @@ -24,6 +24,7 @@ import static org.mockito.Mockito.when; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.sql.Types; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; @@ -105,4 +106,17 @@ public class AbstractExecutorTest { verify(stmt, times(1)).setObject(1, "first_name"); verify(stmt, times(1)).setObject(2, "last_name"); } + + @Test + public void testNullBind() throws SQLException { + createExecutor("select * from blah :first = first and :last=last"); + + verify(conn, times(1)).prepareStatement("select * from blah ? = first and ?=last"); + + executor.bindNull("first") + .bindNull(":last", Types.NULL); + + verify(stmt, times(1)).setNull(1, Types.VARCHAR); + verify(stmt, times(1)).setNull(2, Types.NULL); + } }