From 68b6ec5b6080f4eb92a0a88336fa2ee6b0c769aa Mon Sep 17 00:00:00 2001 From: Lukas Eder Date: Mon, 25 Aug 2014 18:10:08 +0200 Subject: [PATCH] [#2674] Add support for stored procedures in MockConnection / MockDataProvider / MockExecuteContext --- .../org/jooq/tools/jdbc/MockDataProvider.java | 17 +- .../jooq/tools/jdbc/MockExecuteContext.java | 22 +- .../org/jooq/tools/jdbc/MockStatement.java | 224 ++++++++++++------ .../test/java/org/jooq/test/AbstractTest.java | 3 +- .../src/test/java/org/jooq/test/MockTest.java | 47 +++- 5 files changed, 231 insertions(+), 82 deletions(-) diff --git a/jOOQ/src/main/java/org/jooq/tools/jdbc/MockDataProvider.java b/jOOQ/src/main/java/org/jooq/tools/jdbc/MockDataProvider.java index 4eff96bf78..f75bdcae1f 100644 --- a/jOOQ/src/main/java/org/jooq/tools/jdbc/MockDataProvider.java +++ b/jOOQ/src/main/java/org/jooq/tools/jdbc/MockDataProvider.java @@ -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 { *
  • MockStatement does not distinguish between "batch" and * "single" statements. However... * *
  • *
  • It is recommended to return as many MockResult objects @@ -133,6 +134,10 @@ public interface MockDataProvider { *
  • {@link MockExecuteContext#columnNames()}
  • * * + *
  • 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 + * Record.
  • * * * @param ctx The execution context. diff --git a/jOOQ/src/main/java/org/jooq/tools/jdbc/MockExecuteContext.java b/jOOQ/src/main/java/org/jooq/tools/jdbc/MockExecuteContext.java index e237e97238..26811f6a4d 100644 --- a/jOOQ/src/main/java/org/jooq/tools/jdbc/MockExecuteContext.java +++ b/jOOQ/src/main/java/org/jooq/tools/jdbc/MockExecuteContext.java @@ -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 { * MockStatement */ 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 { * MockStatement */ 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 { * MockStatement */ 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(); + } } diff --git a/jOOQ/src/main/java/org/jooq/tools/jdbc/MockStatement.java b/jOOQ/src/main/java/org/jooq/tools/jdbc/MockStatement.java index 800eee2ada..dd9e608b00 100644 --- a/jOOQ/src/main/java/org/jooq/tools/jdbc/MockStatement.java +++ b/jOOQ/src/main/java/org/jooq/tools/jdbc/MockStatement.java @@ -71,6 +71,8 @@ import java.util.Calendar; import java.util.List; import java.util.Map; +import org.jooq.Record; + /** * A mock statement. *

    @@ -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 sql; - private final List> bindings; - private MockResult[] result; - private int resultIndex; - private boolean isClosed; + private final MockDataProvider data; + private final List sql; + private final List> bindings; + private final List 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(); this.bindings = new ArrayList>(); + this.outParameterTypes = new ArrayList(); 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 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> 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> 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; } // ------------------------------------------------------------------------- diff --git a/jOOQ/src/test/java/org/jooq/test/AbstractTest.java b/jOOQ/src/test/java/org/jooq/test/AbstractTest.java index e4b3507642..896f7a9084 100644 --- a/jOOQ/src/test/java/org/jooq/test/AbstractTest.java +++ b/jOOQ/src/test/java/org/jooq/test/AbstractTest.java @@ -81,6 +81,7 @@ public abstract class AbstractTest { protected PreparedStatement statement; protected DSLContext create; protected Result resultEmpty; + protected Table1Record recordOne; protected Result resultOne; protected Result resultTwo; protected Result> 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); diff --git a/jOOQ/src/test/java/org/jooq/test/MockTest.java b/jOOQ/src/test/java/org/jooq/test/MockTest.java index e500c30a60..dfae1d3a15 100644 --- a/jOOQ/src/test/java/org/jooq/test/MockTest.java +++ b/jOOQ/src/test/java/org/jooq/test/MockTest.java @@ -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()); + } }