[#2674] Add support for stored procedures in MockConnection / MockDataProvider / MockExecuteContext

This commit is contained in:
Lukas Eder 2014-08-25 18:10:08 +02:00
parent 587155ab1f
commit 68b6ec5b60
5 changed files with 231 additions and 82 deletions

View File

@ -46,6 +46,7 @@ import java.sql.Statement;
import org.jooq.DSLContext;
import org.jooq.Query;
import org.jooq.Record;
import org.jooq.ResultQuery;
/**
@ -94,12 +95,12 @@ public interface MockDataProvider {
* <li><code>MockStatement</code> does not distinguish between "batch" and
* "single" statements. However...
* <ul>
* <li>A {@link MockExecuteContext#batchSQL()} with more than one SQL
* string is a strong indicator for a "multi-batch statement", as understood
* by jOOQ's {@link DSLContext#batch(Query...)}.</li>
* <li>A {@link MockExecuteContext#batchBindings()} with more than one
* bind variable array is a strong indicator for a "single-batch statement",
* as understood by jOOQ's {@link DSLContext#batch(Query)}.</li>
* <li>A {@link MockExecuteContext#batchSQL()} with more than one SQL string
* is a strong indicator for a "multi-batch statement", as understood by
* jOOQ's {@link DSLContext#batch(Query...)}.</li>
* <li>A {@link MockExecuteContext#batchBindings()} with more than one bind
* variable array is a strong indicator for a "single-batch statement", as
* understood by jOOQ's {@link DSLContext#batch(Query)}.</li>
* </ul>
* </li>
* <li>It is recommended to return as many <code>MockResult</code> objects
@ -133,6 +134,10 @@ public interface MockDataProvider {
* <li> {@link MockExecuteContext#columnNames()}</li>
* </ul>
* </li>
* <li>OUT parameters from stored procedures are generated from the first
* {@link MockResult}'s first {@link Record}. If OUT parameters are
* requested, implementors must ensure the presence of such a
* <code>Record</code>.</li>
* </ul>
*
* @param ctx The execution context.

View File

@ -64,6 +64,8 @@ public class MockExecuteContext {
private final int[] columnIndexes;
private final String[] columnNames;
private final int[] outParameterTypes;
/**
* Create a new mock execution context.
*
@ -71,7 +73,7 @@ public class MockExecuteContext {
* @param bindings The bind variable(s)
*/
public MockExecuteContext(String[] sql, Object[][] bindings) {
this(sql, bindings, Statement.NO_GENERATED_KEYS, null, null);
this(sql, bindings, Statement.NO_GENERATED_KEYS, null, null, null);
}
/**
@ -83,7 +85,7 @@ public class MockExecuteContext {
* <code>MockStatement</code>
*/
public MockExecuteContext(String[] sql, Object[][] bindings, int autoGeneratedKeys) {
this(sql, bindings, autoGeneratedKeys, null, null);
this(sql, bindings, autoGeneratedKeys, null, null, null);
}
/**
@ -96,7 +98,7 @@ public class MockExecuteContext {
* <code>MockStatement</code>
*/
public MockExecuteContext(String[] sql, Object[][] bindings, int[] columnIndexes) {
this(sql, bindings, Statement.RETURN_GENERATED_KEYS, columnIndexes, null);
this(sql, bindings, Statement.RETURN_GENERATED_KEYS, columnIndexes, null, null);
}
/**
@ -109,16 +111,17 @@ public class MockExecuteContext {
* <code>MockStatement</code>
*/
public MockExecuteContext(String[] sql, Object[][] bindings, String[] columnNames) {
this(sql, bindings, Statement.RETURN_GENERATED_KEYS, null, columnNames);
this(sql, bindings, Statement.RETURN_GENERATED_KEYS, null, columnNames, null);
}
MockExecuteContext(String[] sql, Object[][] bindings, int autoGeneratedKeys, int[] columnIndexes,
String[] columnNames) {
String[] columnNames, int[] outParameterTypes) {
this.sql = sql;
this.bindings = bindings;
this.autoGeneratedKeys = autoGeneratedKeys;
this.columnIndexes = columnIndexes;
this.columnNames = columnNames;
this.outParameterTypes = outParameterTypes;
}
/**
@ -216,4 +219,13 @@ public class MockExecuteContext {
public String[] columnNames() {
return columnNames;
}
/**
* Get the types of registered out parameters, if any.
*
* @return the types of registered out parameters, if any.
*/
public int[] outParameterTypes() {
return outParameterTypes == null ? new int[0] : outParameterTypes.clone();
}
}

View File

@ -71,6 +71,8 @@ import java.util.Calendar;
import java.util.List;
import java.util.Map;
import org.jooq.Record;
/**
* A mock statement.
* <p>
@ -86,26 +88,28 @@ import java.util.Map;
*/
public class MockStatement extends JDBC41Statement implements CallableStatement {
private final MockConnection connection;
private final MockConnection connection;
private final MockDataProvider data;
private final List<String> sql;
private final List<List<Object>> bindings;
private MockResult[] result;
private int resultIndex;
private boolean isClosed;
private final MockDataProvider data;
private final List<String> sql;
private final List<List<Object>> bindings;
private final List<Integer> outParameterTypes;
private MockResult[] result;
private int resultIndex;
private boolean resultWasNull;
private boolean isClosed;
// Execution parameters
int resultSetType = ResultSet.TYPE_FORWARD_ONLY;
int resultSetConcurrency = ResultSet.CONCUR_READ_ONLY;
int resultSetHoldability = ResultSet.CLOSE_CURSORS_AT_COMMIT;
int autoGeneratedKeys = Statement.NO_GENERATED_KEYS;
int[] columnIndexes;
String[] columnNames;
int resultSetType = ResultSet.TYPE_FORWARD_ONLY;
int resultSetConcurrency = ResultSet.CONCUR_READ_ONLY;
int resultSetHoldability = ResultSet.CLOSE_CURSORS_AT_COMMIT;
int autoGeneratedKeys = Statement.NO_GENERATED_KEYS;
int[] columnIndexes;
String[] columnNames;
// Statement properties
private int queryTimeout;
private int maxRows;
private int queryTimeout;
private int maxRows;
public MockStatement(MockConnection connection, MockDataProvider data) {
this(connection, data, null);
@ -116,6 +120,7 @@ public class MockStatement extends JDBC41Statement implements CallableStatement
this.data = data;
this.sql = new ArrayList<String>();
this.bindings = new ArrayList<List<Object>>();
this.outParameterTypes = new ArrayList<Integer>();
if (sql != null) {
this.sql.add(sql);
@ -140,6 +145,12 @@ public class MockStatement extends JDBC41Statement implements CallableStatement
}
}
private void ensureOutParameterTypesCapacity(int index) {
if (outParameterTypes.size() < index) {
outParameterTypes.addAll(nCopies(index - outParameterTypes.size(), (Integer) null));
}
}
private void checkNotClosed() throws SQLException {
if (isClosed) {
throw new SQLException("Connection is already closed");
@ -172,13 +183,25 @@ public class MockStatement extends JDBC41Statement implements CallableStatement
new Object[][] { bindings().toArray() },
localAutoGeneratedKeys,
localColumnIndexes,
localColumnNames
localColumnNames,
unbox(outParameterTypes)
);
MockStatement.this.result = data.execute(context);
result = data.execute(context);
return result != null && result.length > 0 && result[resultIndex].data != null;
}
private static final int[] unbox(List<Integer> list) {
int[] array = new int[list.size()];
for (int i = 0; i < array.length; i++) {
Integer value = list.get(i);
array[i] = value == null ? 0 : value;
}
return array;
}
@Override
public ResultSet getGeneratedKeys() throws SQLException {
return getResultSet();
@ -481,137 +504,200 @@ public class MockStatement extends JDBC41Statement implements CallableStatement
// XXX: Bind variables from CallableStatement
// -------------------------------------------------------------------------
private Record outParameters() throws SQLException {
if (result == null || result.length == 0 || result[0].data == null || result[0].data.size() == 0)
throw new SQLException("No OUT Parameters available");
return result[0].data.get(0);
}
private int translate(int parameterIndex) throws SQLException {
if (parameterIndex > outParameterTypes.size())
throw new SQLException("OUT parameter index too high: " + parameterIndex);
int index = -1;
for (int i = 0; i < parameterIndex; i++)
if (outParameterTypes.get(i) != null)
index++;
return index;
}
@Override
public void registerOutParameter(int parameterIndex, int sqlType) throws SQLException {
checkNotClosed();
ensureBindingsCapacity(parameterIndex);
ensureOutParameterTypesCapacity(parameterIndex);
outParameterTypes.set(parameterIndex - 1, sqlType);
}
@Override
public void registerOutParameter(int parameterIndex, int sqlType, int scale) throws SQLException {
checkNotClosed();
ensureBindingsCapacity(parameterIndex);
registerOutParameter(parameterIndex, sqlType);
}
@Override
public void registerOutParameter(int parameterIndex, int sqlType, String typeName) throws SQLException {
checkNotClosed();
ensureBindingsCapacity(parameterIndex);
registerOutParameter(parameterIndex, sqlType);
}
@Override
public boolean wasNull() throws SQLException {
return false;
return resultWasNull;
}
@Override
public String getString(int parameterIndex) throws SQLException {
return null;
String value = outParameters().getValue(translate(parameterIndex), String.class);
resultWasNull = value == null;
return value;
}
@Override
public String getNString(int parameterIndex) throws SQLException {
return null;
String value = outParameters().getValue(translate(parameterIndex), String.class);
resultWasNull = value == null;
return value;
}
@Override
public boolean getBoolean(int parameterIndex) throws SQLException {
return false;
Boolean value = outParameters().getValue(translate(parameterIndex), Boolean.class);
resultWasNull = value == null;
return value == null ? false : value;
}
@Override
public byte getByte(int parameterIndex) throws SQLException {
return 0;
Byte value = outParameters().getValue(translate(parameterIndex), Byte.class);
resultWasNull = value == null;
return value == null ? (byte) 0 : value;
}
@Override
public short getShort(int parameterIndex) throws SQLException {
return 0;
Short value = outParameters().getValue(translate(parameterIndex), Short.class);
resultWasNull = value == null;
return value == null ? (short) 0 : value;
}
@Override
public int getInt(int parameterIndex) throws SQLException {
return 0;
Integer value = outParameters().getValue(translate(parameterIndex), Integer.class);
resultWasNull = value == null;
return value == null ? 0 : value;
}
@Override
public long getLong(int parameterIndex) throws SQLException {
return 0;
Long value = outParameters().getValue(translate(parameterIndex), Long.class);
resultWasNull = value == null;
return value == null ? 0L : value;
}
@Override
public float getFloat(int parameterIndex) throws SQLException {
return 0;
Float value = outParameters().getValue(translate(parameterIndex), Float.class);
resultWasNull = value == null;
return value == null ? 0.0f : value;
}
@Override
public double getDouble(int parameterIndex) throws SQLException {
return 0;
Double value = outParameters().getValue(translate(parameterIndex), Double.class);
resultWasNull = value == null;
return value == null ? 0.0 : value;
}
@Override
public BigDecimal getBigDecimal(int parameterIndex, int scale) throws SQLException {
return null;
}
@Override
public byte[] getBytes(int parameterIndex) throws SQLException {
return null;
}
@Override
public Date getDate(int parameterIndex) throws SQLException {
return null;
}
@Override
public Time getTime(int parameterIndex) throws SQLException {
return null;
}
@Override
public Timestamp getTimestamp(int parameterIndex) throws SQLException {
return null;
}
@Override
public Object getObject(int parameterIndex) throws SQLException {
return null;
BigDecimal value = outParameters().getValue(translate(parameterIndex), BigDecimal.class);
resultWasNull = value == null;
return value;
}
@Override
public BigDecimal getBigDecimal(int parameterIndex) throws SQLException {
return null;
BigDecimal value = outParameters().getValue(translate(parameterIndex), BigDecimal.class);
resultWasNull = value == null;
return value;
}
@Override
public byte[] getBytes(int parameterIndex) throws SQLException {
byte[] value = outParameters().getValue(translate(parameterIndex), byte[].class);
resultWasNull = value == null;
return value;
}
@Override
public Object getObject(int parameterIndex, Map<String, Class<?>> map) throws SQLException {
return null;
}
@Override
public Array getArray(int parameterIndex) throws SQLException {
return null;
public Date getDate(int parameterIndex) throws SQLException {
Date value = outParameters().getValue(translate(parameterIndex), Date.class);
resultWasNull = value == null;
return value;
}
@Override
public Date getDate(int parameterIndex, Calendar cal) throws SQLException {
return null;
Date value = outParameters().getValue(translate(parameterIndex), Date.class);
resultWasNull = value == null;
return value;
}
@Override
public Time getTime(int parameterIndex) throws SQLException {
Time value = outParameters().getValue(translate(parameterIndex), Time.class);
resultWasNull = value == null;
return value;
}
@Override
public Time getTime(int parameterIndex, Calendar cal) throws SQLException {
return null;
Time value = outParameters().getValue(translate(parameterIndex), Time.class);
resultWasNull = value == null;
return value;
}
@Override
public Timestamp getTimestamp(int parameterIndex) throws SQLException {
Timestamp value = outParameters().getValue(translate(parameterIndex), Timestamp.class);
resultWasNull = value == null;
return value;
}
@Override
public Timestamp getTimestamp(int parameterIndex, Calendar cal) throws SQLException {
return null;
Timestamp value = outParameters().getValue(translate(parameterIndex), Timestamp.class);
resultWasNull = value == null;
return value;
}
@Override
public Object getObject(int parameterIndex) throws SQLException {
Object value = outParameters().getValue(translate(parameterIndex));
resultWasNull = value == null;
return value;
}
@Override
public Object getObject(int parameterIndex, Map<String, Class<?>> map) throws SQLException {
Object value = outParameters().getValue(translate(parameterIndex));
resultWasNull = value == null;
return value;
}
@Override
public Array getArray(int parameterIndex) throws SQLException {
Array value = outParameters().getValue(translate(parameterIndex), Array.class);
resultWasNull = value == null;
return value;
}
@Override
public URL getURL(int parameterIndex) throws SQLException {
return null;
URL value = outParameters().getValue(translate(parameterIndex), URL.class);
resultWasNull = value == null;
return value;
}
// -------------------------------------------------------------------------

View File

@ -81,6 +81,7 @@ public abstract class AbstractTest {
protected PreparedStatement statement;
protected DSLContext create;
protected Result<Table1Record> resultEmpty;
protected Table1Record recordOne;
protected Result<Table1Record> resultOne;
protected Result<Table1Record> resultTwo;
protected Result<Record3<String, String, String>> resultStrings;
@ -102,7 +103,7 @@ public abstract class AbstractTest {
resultEmpty = create.newResult(TABLE1);
resultOne = create.newResult(TABLE1);
resultOne.add(create.newRecord(TABLE1));
resultOne.add(recordOne = create.newRecord(TABLE1));
resultOne.get(0).setValue(FIELD_ID1, 1);
resultOne.get(0).setValue(FIELD_NAME1, "1");
resultOne.get(0).changed(false);

View File

@ -59,8 +59,10 @@ import static org.junit.Assert.fail;
import java.io.File;
import java.io.RandomAccessFile;
import java.sql.CallableStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.List;
import org.jooq.Constants;
@ -154,7 +156,7 @@ public class MockTest extends AbstractTest {
execute0(ctx);
return new MockResult[] {
new MockResult(0, resultOne)
new MockResult(recordOne)
};
}
}
@ -509,4 +511,47 @@ public class MockTest extends AbstractTest {
assertFalse(r.next());
}
@Test
public void testCallableStatements() throws SQLException {
MockConnection connection = new MockConnection(new MockDataProvider() {
@Override
public MockResult[] execute(MockExecuteContext ctx) throws SQLException {
assertEquals("{ ? = call my_function(?, ?, ?, ?) }", ctx.sql());
assertEquals(5, ctx.bindings().length);
assertEquals(null, ctx.bindings()[0]);
assertEquals(2, ctx.bindings()[1]);
assertEquals(null, ctx.bindings()[2]);
assertEquals(4, ctx.bindings()[3]);
assertEquals(null, ctx.bindings()[4]);
assertEquals(5, ctx.outParameterTypes().length);
assertEquals(Types.INTEGER, ctx.outParameterTypes()[0]);
assertEquals(0, ctx.outParameterTypes()[1]);
assertEquals(Types.VARCHAR, ctx.outParameterTypes()[2]);
assertEquals(0, ctx.outParameterTypes()[3]);
assertEquals(Types.DATE, ctx.outParameterTypes()[4]);
return new MockResult[] { new MockResult(recordOne) };
}
});
CallableStatement stmt = connection.prepareCall("{ ? = call my_function(?, ?, ?, ?) }");
stmt.registerOutParameter(1, Types.INTEGER);
stmt.setInt(2, 2);
stmt.registerOutParameter(3, Types.VARCHAR);
stmt.setInt(4, 4);
stmt.registerOutParameter(5, Types.DATE);
stmt.executeUpdate();
assertEquals(1, stmt.getInt(1));
assertFalse(stmt.wasNull());
assertEquals("1", stmt.getString(3));
assertFalse(stmt.wasNull());
assertNull(stmt.getDate(5));
assertTrue(stmt.wasNull());
}
}