diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/StoredProcedureQuery.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/StoredProcedureQuery.java index 93fb940798..dc52306710 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/StoredProcedureQuery.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/StoredProcedureQuery.java @@ -186,15 +186,17 @@ public ResultObjectProvider executeQuery(StoreQuery q, Object[] params, Range ra stmnt = conn.prepareCall(_proc.getCallSQL()); final StoredProcedureQuery spq = (StoredProcedureQuery) q; + // no parameter values at all: leave the (un)binding to the driver + final boolean bind = params != null && params.length > 0; for (Column c : spq.getProcedure().getInColumns()) { - if (params != null && c.getIndex() < params.length) { + if (bind) { dict.setUnknown(stmnt, c.getIndex() + 1, params[c.getIndex()], c); } } for (Column c : spq.getProcedure().getInOutColumns()) { final int index = c.getIndex() + 1; stmnt.registerOutParameter(index, c.getType()); - if (params != null && index - 1 < params.length) { + if (bind) { dict.setUnknown(stmnt, index, params[index - 1], c); } } @@ -265,21 +267,29 @@ public Object[] toParameterArray(StoreQuery q, Map userParams) { StoredProcedureQuery storedProcedureQuery = (StoredProcedureQuery) q; Column[] inCols = storedProcedureQuery.getProcedure().getInColumns(); Column[] inOutCols = storedProcedureQuery.getProcedure().getInOutColumns(); - int paramCount = inCols.length + inOutCols.length; - if (paramCount == 0) return NO_PARAM; + if (inCols.length + inOutCols.length == 0) return NO_PARAM; + // the values are indexed by the parameter position, OUT parameters leave a gap + int paramCount = 0; + for (final Column[] columns : asList(inCols, inOutCols)) { + for (Column c : columns) { + paramCount = Math.max(paramCount, c.getIndex() + 1); + } + } Object[] array = new Object[paramCount]; - int i = 0; for (final Column[] columns : asList(inCols, inOutCols)) { for (Column c : columns) { - // Try by name first, then by 1-based position (matching user's declaration) - Object val = userParams.get(c.getIdentifier().getName()); - if (val == null) { + // Try by name first, then by 1-based position (matching user's declaration). + // An explicit null binding wins over a lookup by position. + String name = c.getIdentifier().getName(); + Object val = null; + if (userParams.containsKey(name)) { + val = userParams.get(name); + } else if (userParams.containsKey(c.getIndex() + 1)) { val = userParams.get(c.getIndex() + 1); - } - if (val == null) { + } else if (userParams.containsKey(c.getIndex())) { val = userParams.get(c.getIndex()); } - array[i++] = val; + array[c.getIndex()] = val; } } return array; diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/procedure/TestStoredProcedureQueryFixes.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/procedure/TestStoredProcedureQueryFixes.java index 4b0c9499e4..4fab54619b 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/procedure/TestStoredProcedureQueryFixes.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/jdbc/procedure/TestStoredProcedureQueryFixes.java @@ -186,6 +186,67 @@ public void testGetSingleResultNonUniqueResultException() throws Exception { } } + /** + * Test that a named parameter explicitly bound to null is not replaced by + * the value registered under the position of that parameter. + */ + public void testExplicitNullNamedParameterWinsOverPosition() throws Exception { + Procedures.inParamsInteger = -1; + Procedures.inParamsString = null; + + EntityManager em = emf.createEntityManager(); + try { + exec(em, "DROP PROCEDURE TESTINS", true); + exec(em, "CREATE PROCEDURE TESTINS(SOME_NUMBER INTEGER,SOME_STRING VARCHAR(255)) " + + "PARAMETER STYLE JAVA LANGUAGE JAVA EXTERNAL NAME " + + "'" + Procedures.class.getName() + ".inParams'", false); + + StoredProcedureQuery spq = em.createStoredProcedureQuery("TESTINS"); + spq.registerStoredProcedureParameter("SOME_NUMBER", Integer.class, ParameterMode.IN); + spq.registerStoredProcedureParameter("SOME_STRING", String.class, ParameterMode.IN); + spq.registerStoredProcedureParameter(2, String.class, ParameterMode.IN); + spq.setParameter("SOME_NUMBER", 42); + spq.setParameter(2, "positional"); + spq.setParameter("SOME_STRING", null); + assertFalse(spq.execute()); + + assertEquals(42, Procedures.inParamsInteger); + assertNull(Procedures.inParamsString); + } finally { + em.close(); + } + } + + /** + * Test that IN parameters following an OUT parameter are bound at their position. + */ + public void testInParameterAfterOutParameter() throws Exception { + EntityManager em = emf.createEntityManager(); + try { + exec(em, "DROP PROCEDURE XSUM", true); + exec(em, "CREATE PROCEDURE XSUM(IN A INTEGER,OUT S INTEGER,IN B INTEGER) " + + "PARAMETER STYLE JAVA LANGUAGE JAVA EXTERNAL NAME " + + "'" + TestStoredProcedureQueryFixes.class.getName() + ".sum'", false); + + StoredProcedureQuery spq = em.createStoredProcedureQuery("XSUM"); + spq.registerStoredProcedureParameter(1, Integer.class, ParameterMode.IN); + spq.registerStoredProcedureParameter(2, Integer.class, ParameterMode.OUT); + spq.registerStoredProcedureParameter(3, Integer.class, ParameterMode.IN); + spq.setParameter(1, 3); + spq.setParameter(3, 4); + spq.execute(); + + assertEquals(7, spq.getOutputParameterValue(2)); + } finally { + em.close(); + } + } + + // Derby stored procedure: s = a + b + public static void sum(int a, int[] s, int b) { + s[0] = a + b; + } + // Derby stored procedure: returns empty result set public static void emptyResultSet(java.sql.ResultSet[] rs) throws Exception { java.sql.Connection c = java.sql.DriverManager.getConnection("jdbc:default:connection");