Hi

This is a patch which implements typed arrays in function results.
It removes the "feature" where elements of function array results are converted 
to columns.

Regards, Noel.

Lukas Eder wrote:
>> As for me issue 3 is definitely a bug. But this happens because CALL
>> statement translates elements of array result to columns. For example:
>> CALL (1,2,3) will return row with 3 columns. Indeed this behavior was
>> introduced before me (see rev. 2427 Call.query) and I'm not a fan of
>> this feature at all too but I've kept backward compatibility at time
>> of my changes.
> Ah, I understand. So with the current versions of H2, it is difficult
> to retrieve an array from a stored function correctly, if using a
> CallableStatement... While I register only one OUT parameter for the
> return value with java.sql.Types.ARRAY as a type, I have to retrieve
> three (or five or seven) values from the CallableStatement.
>
> I understand backwards compatibility thoughts. But this design will
> cause major trouble, should you ever decide to allow for regular OUT
> parameters as other databases do. Right now, this is not an issue, but
> HSQLDB also went from Java-based stored functions to a PL/SQL-like
> syntax with regular OUT parameters. Besides, the current behaviour is
> inconsistent with calling the function in a normal PreparedStatement
> as in "select f_arrays1(?) from dual", where an actual Object[] array
> is returned.
>
> In my case, I can hide these facts from client code and always use the
> PreparedStatement syntax. But for H2, I suggest you remove that
> "feature" in the next major release. I doubt that a lot of people will
> rely on that feature, since it isn't really documented. But that's
> your decision.
>
> Cheers
> Lukas
>

-- 
You received this message because you are subscribed to the Google Groups "H2 
Database" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/h2-database?hl=en.

Index: src/main/org/h2/command/dml/Call.java
===================================================================
--- src/main/org/h2/command/dml/Call.java       (revision 3533)
+++ src/main/org/h2/command/dml/Call.java       (working copy)
@@ -69,16 +69,6 @@
         case Value.RESULT_SET:
             ResultSet rs = ((ValueResultSet) v).getResultSet();
             return LocalResult.read(session, rs, maxrows);
-        case Value.ARRAY:
-            Value[] list = ((ValueArray) v).getList();
-            Expression[] expr = expression.getExpressionColumns(session);
-            if (expr.length != list.length) {
-                throw DbException.throwInternalError();
-            }
-            LocalResult result = new LocalResult(session, expr, expr.length);
-            result.addRow(list);
-            result.done();
-            return result;
         }
         LocalResult result = new LocalResult(session, expressions, 1);
         Value[] row = { v };
Index: src/main/org/h2/engine/FunctionAlias.java
===================================================================
--- src/main/org/h2/engine/FunctionAlias.java   (revision 3533)
+++ src/main/org/h2/engine/FunctionAlias.java   (working copy)
@@ -11,6 +11,7 @@
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.sql.Connection;
+import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import org.h2.Driver;
@@ -30,6 +31,7 @@
 import org.h2.util.Utils;
 import org.h2.value.DataType;
 import org.h2.value.Value;
+import org.h2.value.ValueArray;
 import org.h2.value.ValueNull;
 
 /**
@@ -352,6 +354,14 @@
                 Object o;
                 if (Value.class.isAssignableFrom(paramClass)) {
                     o = v;
+                } else if (v.getType() == Value.ARRAY && paramClass.isArray() 
&& paramClass.getComponentType() != Object.class) {
+                    ValueArray argVA = (ValueArray) v;
+                    Object[] objArray = (Object[]) 
Array.newInstance(paramClass.getComponentType(), argVA.getList().length);
+                    int componentType = 
DataType.getTypeFromClass(paramClass.getComponentType());
+                    for (int i=0; i<objArray.length; i++) {
+                        objArray[i] = 
argVA.getList()[i].convertTo(componentType).getObject();
+                    }
+                    o = objArray;
                 } else {
                     v = v.convertTo(type);
                     o = v.getObject();
@@ -359,10 +369,10 @@
                 if (o == null) {
                     if (paramClass.isPrimitive()) {
                         if (columnList) {
-                            // if the column list is requested, the parameters 
may
-                            // be null
-                            // need to set to default value,
-                            // otherwise the function can't be called at all
+                            // If the column list is requested, the parameters 
may
+                            // be null.
+                            // Need to set to default value,
+                            // otherwise the function can't be called at all.
                             o = 
DataType.getDefaultForPrimitiveType(paramClass);
                         } else {
                             // NULL for a java primitive: return NULL
@@ -409,6 +419,15 @@
                 if (Value.class.isAssignableFrom(method.getReturnType())) {
                     return (Value) returnValue;
                 }
+                if (dataType == Value.ARRAY) {
+                    Object[] returnValueArray = (Object[]) returnValue;
+                    Value[] vals = new Value[returnValueArray.length];
+                    for (int i=0; i<returnValueArray.length; i++) {
+                        vals[i] = DataType.convertToValue(session, 
returnValueArray[i], Value.ARRAY);
+                    }
+                    ValueArray va = 
ValueArray.get(returnValue.getClass().getComponentType(), vals);
+                    return va;
+                }
                 Value ret = DataType.convertToValue(session, returnValue, 
dataType);
                 return ret.convertTo(dataType);
             } finally {
Index: src/main/org/h2/value/ValueArray.java
===================================================================
--- src/main/org/h2/value/ValueArray.java       (revision 3533)
+++ src/main/org/h2/value/ValueArray.java       (working copy)
@@ -6,6 +6,7 @@
  */
 package org.h2.value;
 
+import java.lang.reflect.Array;
 import java.sql.PreparedStatement;
 import java.util.ArrayList;
 import org.h2.engine.Constants;
@@ -18,12 +19,18 @@
  */
 public class ValueArray extends Value {
 
+    private final Class<?> componentType;
     private final Value[] values;
     private int hash;
 
-    private ValueArray(Value[] list) {
+    private ValueArray(Class<?> componentType, Value[] list) {
+        this.componentType = componentType;
         this.values = list;
     }
+    
+    private ValueArray(Value[] list) {
+        this(Object.class, list);
+    }
 
     /**
      * Get or create a array value for the given value array.
@@ -36,6 +43,17 @@
         return new ValueArray(list);
     }
 
+    /**
+     * Get or create a array value for the given value array.
+     * Do not clone the data.
+     *
+     * @param list the value array
+     * @return the value
+     */
+    public static ValueArray get(Class<?> componentType, Value[] list) {
+        return new ValueArray(componentType, list);
+    }
+    
     public int hashCode() {
         if (hash != 0) {
             return hash;
@@ -55,6 +73,10 @@
     public int getType() {
         return Value.ARRAY;
     }
+    
+    public Class<?> getComponentType() {
+        return componentType;
+    }
 
     public long getPrecision() {
         long p = 0;
@@ -94,7 +116,7 @@
 
     public Object getObject() {
         int len = values.length;
-        Object[] list = new Object[len];
+        Object[] list = (Object[]) Array.newInstance(componentType, len);
         for (int i = 0; i < len; i++) {
             list[i] = values[i].getObject();
         }
Index: src/test/org/h2/test/TestBase.java
===================================================================
--- src/test/org/h2/test/TestBase.java  (revision 3533)
+++ src/test/org/h2/test/TestBase.java  (working copy)
@@ -579,6 +579,30 @@
     }
 
     /**
+     * Check if two values are equal, and if not throw an exception.
+     *
+     * @param expected the expected value
+     * @param actual the actual value
+     * @throws AssertionError if the values are not equal
+     */
+    public void assertEquals(Object[] expected, Object[] actual) {
+        if (expected == null || actual == null) {
+            assertTrue(expected == actual);
+            return;
+        }
+        assertEquals(expected.length, actual.length);
+        for (int i = 0; i < expected.length; i++) {
+            if (expected[i] == null || actual[i] == null) {
+                if (expected[i] != actual[i]) {
+                    fail("[" + i + "]: expected: " + expected[i] + " actual: " 
+ actual[i]);
+                }
+            } else if (!expected[i].equals(actual[i])) {
+                fail("[" + i + "]: expected: " + expected[i] + " actual: " + 
actual[i]);
+            }
+        }
+    }
+    
+    /**
      * Check if two readers are equal, and if not throw an exception.
      *
      * @param expected the expected value
@@ -677,6 +701,23 @@
     }
 
     /**
+     * Check if two values are equal, and if not throw an exception.
+     *
+     * @param expected the expected value
+     * @param actual the actual value
+     * @throws AssertionError if the values are not equal
+     */
+    protected void assertEquals(Object expected, Object actual) {
+        if (expected == null || actual == null) {
+            if (expected != actual) {
+                fail("expected: " + expected + " actual: " + actual);
+            }
+        } else if (!expected.equals(actual)) {
+            fail("expected: " + expected + " actual: " + actual);
+        }
+    }
+    
+    /**
      * Check if two result sets are equal, and if not throw an exception.
      *
      * @param message the message to use if the check fails
@@ -775,6 +816,21 @@
      * @param actual the actual value
      * @throws AssertionError if the values are not equal
      */
+    protected void assertEquals(Double expected, Double actual) {
+        if (expected == null || actual == null) {
+            assertTrue(expected == null && actual == null);
+        } else {
+            assertEquals(expected.doubleValue(), actual.doubleValue());
+        }
+    }
+    
+    /**
+     * Check if two values are equal, and if not throw an exception.
+     *
+     * @param expected the expected value
+     * @param actual the actual value
+     * @throws AssertionError if the values are not equal
+     */
     protected void assertEquals(float expected, float actual) {
         if (expected != actual) {
             if (Float.isNaN(expected) && Float.isNaN(actual)) {
Index: src/test/org/h2/test/db/TestFunctions.java
===================================================================
--- src/test/org/h2/test/db/TestFunctions.java  (revision 3533)
+++ src/test/org/h2/test/db/TestFunctions.java  (working copy)
@@ -12,6 +12,7 @@
 import java.io.OutputStream;
 import java.math.BigDecimal;
 import java.sql.Blob;
+import java.sql.CallableStatement;
 import java.sql.Connection;
 import java.sql.DatabaseMetaData;
 import java.sql.DriverManager;
@@ -23,7 +24,6 @@
 import java.util.ArrayList;
 import java.util.Properties;
 import java.util.UUID;
-
 import org.h2.api.AggregateFunction;
 import org.h2.test.TestBase;
 import org.h2.tools.SimpleResultSet;
@@ -66,6 +66,7 @@
         testFunctions();
         testFileRead();
         testValue();
+        testArrayParameters();
         deleteDb("functions");
         IOUtils.deleteRecursive(TEMP_DIR, true);
     }
@@ -551,10 +552,10 @@
 
         stat.execute("CREATE ALIAS ARRAY FOR \"" + getClass().getName() + 
".getArray\"");
         rs = stat.executeQuery("CALL ARRAY()");
-        assertEquals(2, rs.getMetaData().getColumnCount());
+        assertEquals(1, rs.getMetaData().getColumnCount());
         rs.next();
-        assertEquals(0, rs.getInt(1));
-        assertEquals("Hello", rs.getString(2));
+        assertEquals(0, ((Integer) ((Object[]) 
rs.getObject(1))[0]).intValue());
+        assertEquals("Hello", (String) ((Object[]) rs.getObject(1))[1]);
         assertFalse(rs.next());
 
         stat.execute("CREATE ALIAS ROOT FOR \"" + getClass().getName() + 
".root\"");
@@ -675,6 +676,31 @@
         conn.close();
     }
 
+    private void testArrayParameters() throws SQLException {
+        deleteDb("functions");
+        Connection conn = getConnection("functions");
+        Statement stat = conn.createStatement();
+        ResultSet rs;
+        stat.execute("CREATE ALIAS f_arrays1 AS " 
+                + "$$ Integer[] f_arrays1(Integer[] in_array) "
+                + "{ return in_array; } $$;");
+
+        PreparedStatement stmt = conn.prepareStatement("select f_arrays1(?) 
from dual");
+        stmt.setObject(1, new Integer[] { 1, 2 });
+        rs = stmt.executeQuery();
+        rs.next();
+        assertEquals(Integer[].class, rs.getObject(1).getClass());
+
+        CallableStatement call = conn.prepareCall("{ ? = call f_arrays1(?) }");
+        call.setObject(2, new Integer[] { 2, 1 });
+        call.registerOutParameter(1, Types.ARRAY);
+        call.execute();
+        assertEquals(Integer[].class, call.getArray(1).getArray().getClass());
+        assertEquals(new Integer[] { 2, 1 }, (Integer[])call.getObject(1));
+
+        conn.close();
+    }
+
     private void assertCallResult(String expected, Statement stat, String sql) 
throws SQLException {
         ResultSet rs = stat.executeQuery("CALL " + sql);
         rs.next();
Index: src/test/org/h2/test/jaqu/UpdateTest.java
===================================================================
--- src/test/org/h2/test/jaqu/UpdateTest.java   (revision 3533)
+++ src/test/org/h2/test/jaqu/UpdateTest.java   (working copy)
@@ -56,7 +56,7 @@
 
         Product p2 = new Product();
         Product pChang2 = 
db.from(p2).where(p2.productName).is("Chang").selectFirst();
-        assertEquals(19.5, pChang2.unitPrice);
+        assertEquals(new Double(19.5), pChang2.unitPrice);
         assertEquals(16, pChang2.unitsInStock.intValue());
 
         // undo update
@@ -90,7 +90,7 @@
 
         Product p2 = new Product();
         Product pChang2 = 
db.from(p2).where(p2.productName).is("Chang").selectFirst();
-        assertEquals(19.5, pChang2.unitPrice);
+        assertEquals(new Double(19.5), pChang2.unitPrice);
         assertEquals(16, pChang2.unitsInStock.intValue());
 
         // undo update
@@ -129,7 +129,7 @@
         // confirm the data was properly updated
         Product revised = db.from(p).where(p.productId).is(1).selectFirst();
         assertEquals("updated", revised.productName);
-        assertEquals(original.unitPrice + 3.14, revised.unitPrice);
+        assertEquals(new Double(original.unitPrice + 3.14), revised.unitPrice);
         assertEquals(original.unitsInStock + 2, 
revised.unitsInStock.intValue());
 
         // restore the data

Reply via email to