PHOENIX-1237 COALESCE Function - type of second parameter (Vaclav Loffelmann)


Project: http://git-wip-us.apache.org/repos/asf/phoenix/repo
Commit: http://git-wip-us.apache.org/repos/asf/phoenix/commit/797af91a
Tree: http://git-wip-us.apache.org/repos/asf/phoenix/tree/797af91a
Diff: http://git-wip-us.apache.org/repos/asf/phoenix/diff/797af91a

Branch: refs/heads/master
Commit: 797af91a6b77e3cbea51ec94ba5a01762efa293c
Parents: bba2eee
Author: James Taylor <jtay...@salesforce.com>
Authored: Thu Sep 18 15:41:27 2014 -0700
Committer: James Taylor <jtay...@salesforce.com>
Committed: Thu Sep 18 15:46:29 2014 -0700

----------------------------------------------------------------------
 .../end2end/ClientTimeArithmeticQueryIT.java    |   2 +-
 .../phoenix/end2end/CoalesceFunctionIT.java     | 273 ++++++++++++++++++-
 .../expression/function/CoalesceFunction.java   |  40 ++-
 3 files changed, 293 insertions(+), 22 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/phoenix/blob/797af91a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ClientTimeArithmeticQueryIT.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ClientTimeArithmeticQueryIT.java
 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ClientTimeArithmeticQueryIT.java
index 9be6fc1..98b233c 100644
--- 
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ClientTimeArithmeticQueryIT.java
+++ 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ClientTimeArithmeticQueryIT.java
@@ -537,7 +537,7 @@ public class ClientTimeArithmeticQueryIT extends 
BaseQueryIT {
     
     @Test
     public void testCoalesceFunction() throws Exception {
-        String query = "SELECT entity_id FROM aTable WHERE a_integer > 0 and 
coalesce(X_DECIMAL,0.0) = 0.0";
+        String query = "SELECT entity_id FROM aTable WHERE 
coalesce(X_DECIMAL,0.0) = 0.0";
         Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
         props.setProperty(PhoenixRuntime.CURRENT_SCN_ATTRIB, Long.toString(ts 
+ 10)); // Execute at timestamp 2
         Connection conn = DriverManager.getConnection(getUrl(), props);

http://git-wip-us.apache.org/repos/asf/phoenix/blob/797af91a/phoenix-core/src/it/java/org/apache/phoenix/end2end/CoalesceFunctionIT.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/CoalesceFunctionIT.java 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/CoalesceFunctionIT.java
index 48808ab..57599e6 100644
--- 
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/CoalesceFunctionIT.java
+++ 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/CoalesceFunctionIT.java
@@ -19,6 +19,7 @@ package org.apache.phoenix.end2end;
 
 import static org.apache.phoenix.util.TestUtil.ROW6;
 import static org.apache.phoenix.util.TestUtil.ROW7;
+import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -27,37 +28,285 @@ import java.sql.Connection;
 import java.sql.DriverManager;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
+import java.sql.SQLException;
 import java.util.Properties;
 
-import org.apache.phoenix.util.PhoenixRuntime;
+import org.apache.phoenix.schema.IllegalDataException;
+import org.apache.phoenix.util.PropertiesUtil;
+import org.junit.Assert;
 import org.junit.Test;
 import org.junit.experimental.categories.Category;
 
-@Category(ClientManagedTimeTest.class)
-public class CoalesceFunctionIT extends BaseClientManagedTimeIT {
+@Category(HBaseManagedTimeTest.class)
+public class CoalesceFunctionIT extends BaseHBaseManagedTimeIT {
+
     @Test
     public void testCoalesce() throws Exception {
-        long ts = nextTimestamp();
         String tenantId = getOrganizationId();
-        initATableValues(tenantId, getDefaultSplits(tenantId), null, ts);
-        Properties props = new Properties();
-        props.setProperty(PhoenixRuntime.CURRENT_SCN_ATTRIB, Long.toString(ts 
+ 1)); // Execute at timestamp 1
+        initATableValues(tenantId, getDefaultSplits(tenantId), getUrl());
+        Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
         Connection conn = DriverManager.getConnection(getUrl(), props);
         String query = "SELECT entity_id, a_integer + COALESCE(x_integer,1) 
FROM ATABLE WHERE organization_id = ? AND a_integer >= 6 AND a_integer <= 7";
         PreparedStatement statement = conn.prepareStatement(query);
         statement.setString(1, tenantId);
         ResultSet rs = statement.executeQuery();
-        
-        assertTrue (rs.next());
+
+        assertTrue(rs.next());
         assertEquals(ROW6, rs.getString(1));
         assertEquals(7, rs.getInt(2));
-        
-        assertTrue (rs.next());
+
+        assertTrue(rs.next());
         assertEquals(ROW7, rs.getString(1));
         assertEquals(12, rs.getInt(2));
-        
+
         assertFalse(rs.next());
         conn.close();
     }
 
+    @Test
+    public void coalesceWithSumExplicitLong() throws Exception {
+        Connection conn = DriverManager.getConnection(getUrl());
+
+        String ddl = "CREATE TABLE IF NOT EXISTS TEST_COALESCE("
+                + "    ID BIGINT NOT NULL, "
+                + "    COUNT BIGINT "
+                + "    CONSTRAINT pk PRIMARY KEY(ID))";
+        conn.createStatement().execute(ddl);
+
+        conn.createStatement().execute("UPSERT INTO TEST_COALESCE(ID, COUNT) 
VALUES(2, null)");
+        conn.commit();
+
+        ResultSet rs = conn.createStatement().executeQuery(
+                "SELECT "
+                + "COALESCE(SUM(COUNT), 0L) " //explicitly def long
+                + "FROM TEST_COALESCE "
+                + "GROUP BY ID");
+
+        assertTrue(rs.next());
+        assertEquals(0, rs.getLong(1));
+        assertFalse(rs.wasNull());
+    }
+
+    @Test
+    public void coalesceWithSumImplicitLong() throws Exception {
+        Connection conn = DriverManager.getConnection(getUrl());
+
+        String ddl = "CREATE TABLE IF NOT EXISTS TEST_COALESCE("
+                + "    ID BIGINT NOT NULL, "
+                + "    COUNT BIGINT "
+                + "    CONSTRAINT pk PRIMARY KEY(ID))";
+        conn.createStatement().execute(ddl);
+
+        conn.createStatement().execute("UPSERT INTO TEST_COALESCE(ID, COUNT) 
VALUES(2, null)");
+        conn.commit();
+
+        ResultSet rs = conn.createStatement().executeQuery(
+                "SELECT "
+                + "COALESCE(SUM(COUNT), 0) " // no long def
+                + "FROM TEST_COALESCE "
+                + "GROUP BY ID");
+
+        assertTrue(rs.next());
+        assertEquals(0, rs.getLong(1));
+        assertFalse(rs.wasNull());
+    }
+
+    @Test
+    public void coalesceWithSecondParamAsExpression() throws Exception {
+        Connection conn = DriverManager.getConnection(getUrl());
+
+        String ddl = "CREATE TABLE IF NOT EXISTS TEST_COALESCE("
+                + "    ID BIGINT NOT NULL, "
+                + "    COUNT BIGINT "
+                + "    CONSTRAINT pk PRIMARY KEY(ID))";
+        conn.createStatement().execute(ddl);
+
+        conn.createStatement().execute("UPSERT INTO TEST_COALESCE(ID, COUNT) 
VALUES(2, null)");
+        conn.commit();
+
+        ResultSet rs = conn.createStatement().executeQuery(
+                "SELECT "
+                + "COALESCE(SUM(COUNT), SUM(ID)) " // second param as 
expression
+                + "FROM TEST_COALESCE "
+                + "GROUP BY ID");
+
+        assertTrue(rs.next());
+        assertEquals(2, rs.getLong(1));
+        assertFalse(rs.wasNull());
+    }
+
+    @Test
+    public void nonTypedSecondParameterLong() throws Exception {
+        Connection conn = DriverManager.getConnection(getUrl());
+
+        String ddl = "CREATE TABLE IF NOT EXISTS TEST_COALESCE("
+                + "    ID BIGINT NOT NULL, "
+                + "    COUNT BIGINT " //first parameter to coalesce
+                + "    CONSTRAINT pk PRIMARY KEY(ID))";
+        conn.createStatement().execute(ddl);
+
+        conn.createStatement().execute("UPSERT INTO TEST_COALESCE(ID, COUNT) 
VALUES(2, null)");
+        conn.commit();
+
+        ResultSet rs = conn.createStatement().executeQuery(
+                "SELECT "
+                + "COALESCE(NTH_VALUE(COUNT, 100) WITHIN GROUP (ORDER BY COUNT 
DESC), 0) " //second param is int
+                + "FROM TEST_COALESCE "
+                + "GROUP BY ID");
+
+        assertTrue(rs.next());
+        assertEquals(0, rs.getLong(1));
+        assertFalse(rs.wasNull());
+    }
+
+    @Test
+    public void nonTypedSecondParameterUnsignedDataTypes() throws Exception {
+        Connection conn = DriverManager.getConnection(getUrl());
+
+        String[] dataTypes = {
+            "UNSIGNED_INT",
+            "UNSIGNED_LONG",
+            "UNSIGNED_TINYINT",
+            "UNSIGNED_SMALLINT",
+            "UNSIGNED_FLOAT",
+            "UNSIGNED_DOUBLE",
+            "UNSIGNED_TIME",
+            "UNSIGNED_DATE",
+            "UNSIGNED_TIMESTAMP"
+        };
+
+        for (String dataType : dataTypes) {
+
+            String ddl = "CREATE TABLE IF NOT EXISTS TEST_COALESCE("
+                    + "    ID BIGINT NOT NULL, "
+                    + "    COUNT " + dataType //first parameter to coalesce
+                    + "    CONSTRAINT pk PRIMARY KEY(ID))";
+            conn.createStatement().execute(ddl);
+
+            conn.createStatement().execute("UPSERT INTO TEST_COALESCE(ID, 
COUNT) VALUES(2, null)");
+            conn.commit();
+
+            //second param to coalesce is signed int
+            ResultSet rs = conn.createStatement().executeQuery(
+                    "SELECT "
+                    + "COALESCE(NTH_VALUE(COUNT, 100) WITHIN GROUP (ORDER BY 
COUNT DESC), 1) "
+                    + "FROM TEST_COALESCE "
+                    + "GROUP BY ID");
+
+            assertTrue(rs.next());
+            assertEquals(1, rs.getInt(1));
+            assertFalse(rs.wasNull());
+        }
+    }
+
+    @Test
+    public void testWithNthValueAggregationFunction() throws Exception {
+        Connection conn = DriverManager.getConnection(getUrl());
+
+        String ddl = "CREATE TABLE IF NOT EXISTS TEST_NTH("
+                + "    ID BIGINT NOT NULL, "
+                + "    DATE TIMESTAMP NOT NULL, "
+                + "    COUNT BIGINT "
+                + "    CONSTRAINT pk PRIMARY KEY(ID, DATE))";
+        conn.createStatement().execute(ddl);
+
+        conn.createStatement().execute("UPSERT INTO TEST_NTH(ID, DATE, COUNT) 
VALUES(1, CURRENT_TIME(), 1)");
+        conn.createStatement().execute("UPSERT INTO TEST_NTH(ID, DATE, COUNT) 
VALUES(1, CURRENT_TIME(), 2)");
+        conn.createStatement().execute("UPSERT INTO TEST_NTH(ID, DATE, COUNT) 
VALUES(2, CURRENT_TIME(), 1)");
+        conn.commit();
+
+        //second param to coalesce is signed int
+        ResultSet rs = conn.createStatement().executeQuery(
+                "SELECT "
+                + " COALESCE("
+                + "            NTH_VALUE(COUNT, 2000)" // should evaluate null
+                + "            WITHIN GROUP (ORDER BY COUNT DESC),"
+                + "       0)"
+                + "FROM TEST_NTH "
+                + "GROUP BY ID");
+
+        assertTrue(rs.next());
+        assertEquals(0, rs.getLong(1));
+        assertFalse(rs.wasNull());
+    }
+
+    @Test
+    public void wrongDataTypeOfSecondParameter() throws Exception {
+        Connection conn = DriverManager.getConnection(getUrl());
+
+        String ddl = "CREATE TABLE IF NOT EXISTS TEST_COALESCE("
+                + "    ID UNSIGNED_INT NOT NULL, "
+                + "    COUNT UNSIGNED_INT "
+                + "    CONSTRAINT pk PRIMARY KEY(ID))";
+        conn.createStatement().execute(ddl);
+
+        conn.createStatement().execute("UPSERT INTO TEST_COALESCE(ID, COUNT) 
VALUES(2, null)");
+        conn.commit();
+
+        try {
+            conn.createStatement().executeQuery(
+                    "SELECT "
+                    + "COALESCE(MIN(COUNT), -1) " // invalid value for 
UNSIGNED_INT
+                    + "FROM TEST_COALESCE "
+                    + "GROUP BY ID");
+
+            Assert.fail("CANNOT CONVERT TYPE exception expected");
+        } catch (SQLException e) {
+
+        }
+    }
+
+    @Test
+    public void testImplicitSecondArgCastingException() throws Exception {
+        Connection conn = DriverManager.getConnection(getUrl());
+
+        String ddl = "CREATE TABLE IF NOT EXISTS TEST_COALESCE("
+                + "    ID INTEGER NOT NULL, "
+                + "    COUNT UNSIGNED_INT " //first parameter to coalesce
+                + "    CONSTRAINT pk PRIMARY KEY(ID))";
+        conn.createStatement().execute(ddl);
+
+        conn.createStatement().execute("UPSERT INTO TEST_COALESCE(ID, COUNT) 
VALUES(-2, null)");
+        conn.commit();
+
+        try {
+            ResultSet rs = conn.createStatement().executeQuery(
+                    "SELECT "
+                    + "COALESCE(MIN(COUNT), ID) "
+                    + "FROM TEST_COALESCE "
+                    + "GROUP BY ID");
+
+            assertTrue(rs.next());
+            assertEquals(0, rs.getLong(1));
+            Assert.fail("Should not cast -2 to UNSIGNED_INT");
+        } catch (IllegalDataException e) {
+
+        }
+    }
+
+    @Test
+    public void testImplicitSecondArgCasting() throws Exception {
+        Connection conn = DriverManager.getConnection(getUrl());
+
+        String ddl = "CREATE TABLE IF NOT EXISTS TEST_COALESCE("
+                + "    ID DOUBLE NOT NULL, "
+                + "    COUNT INTEGER " //first parameter to coalesce
+                + "    CONSTRAINT pk PRIMARY KEY(ID))";
+        conn.createStatement().execute(ddl);
+
+        conn.createStatement().execute("UPSERT INTO TEST_COALESCE(ID, COUNT) 
VALUES(2.0, null)");
+        conn.commit();
+
+        ResultSet rs = conn.createStatement().executeQuery(
+                "SELECT "
+                + "COALESCE(MIN(COUNT), ID) "
+                + "FROM TEST_COALESCE "
+                + "GROUP BY ID");
+
+        assertTrue(rs.next());
+        assertEquals(2, rs.getLong(1));
+        assertFalse(rs.wasNull());
+    }
+
+
 }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/797af91a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/CoalesceFunction.java
----------------------------------------------------------------------
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/CoalesceFunction.java
 
b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/CoalesceFunction.java
index c95d05d..87fc908 100644
--- 
a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/CoalesceFunction.java
+++ 
b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/CoalesceFunction.java
@@ -23,7 +23,9 @@ import java.util.List;
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
 import org.apache.phoenix.exception.SQLExceptionCode;
 import org.apache.phoenix.exception.SQLExceptionInfo;
+import org.apache.phoenix.expression.CoerceExpression;
 import org.apache.phoenix.expression.Expression;
+import org.apache.phoenix.hbase.index.util.ImmutableBytesPtr;
 import org.apache.phoenix.parse.FunctionParseNode.Argument;
 import org.apache.phoenix.parse.FunctionParseNode.BuiltInFunction;
 import org.apache.phoenix.schema.PDataType;
@@ -31,15 +33,15 @@ import org.apache.phoenix.schema.tuple.Tuple;
 
 
 /**
- * 
+ *
  * Function used to provide an alternative value when the first argument is 
null.
  * Usage:
  * COALESCE(expr1,expr2)
- * If expr1 is not null, then it is returned, otherwise expr2 is returned. 
+ * If expr1 is not null, then it is returned, otherwise expr2 is returned.
  *
  * TODO: better bind parameter type matching, since arg2 must be coercible
  * to arg1. consider allowing a common base type?
- * 
+ *
  * @since 0.1
  */
 @BuiltInFunction(name=CoalesceFunction.NAME, args= {
@@ -53,10 +55,25 @@ public class CoalesceFunction extends ScalarFunction {
 
     public CoalesceFunction(List<Expression> children) throws SQLException {
         super(children);
-        if 
(!children.get(1).getDataType().isCoercibleTo(children.get(0).getDataType())) {
-            throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.CANNOT_CONVERT_TYPE)
-                .setMessage(getName() + " expected " + 
children.get(0).getDataType() + ", but got " + children.get(1).getDataType())
-                .build().buildException();
+
+        Expression firstChild = children.get(0);
+        Expression secondChild = children.get(1);
+
+        if (secondChild.isStateless() && secondChild.isDeterministic()) { // 
is literal
+
+            ImmutableBytesWritable ptr = new ImmutableBytesPtr();
+            secondChild.evaluate(null, ptr);
+
+            if 
(!secondChild.getDataType().isCoercibleTo(firstChild.getDataType(), 
secondChild.getDataType().toObject(ptr))) {
+                throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.CANNOT_CONVERT_TYPE)
+                    .setMessage(getName() + " expected " + 
firstChild.getDataType() + ", but got " + secondChild.getDataType())
+                    .build().buildException();
+            }
+        } else { // second parameter is expression
+            if (!secondChild.getDataType().isCoercibleTo(getDataType())) {
+                // cast explicitly
+                children.add(1, CoerceExpression.create(secondChild, 
firstChild.getDataType()));
+            }
         }
     }
 
@@ -67,7 +84,12 @@ public class CoalesceFunction extends ScalarFunction {
             return true;
         }
         if (tuple.isImmutable()) {
-            return children.get(1).evaluate(tuple, ptr);
+            Expression secondChild = children.get(1);
+            if (secondChild.evaluate(tuple, ptr)) {
+                // Coerce the type of the second child to the type of the 
first child
+                getDataType().coerceBytes(ptr, secondChild.getDataType(), 
secondChild.getSortOrder(), getSortOrder());
+                return true;
+            }
         }
         return false;
     }
@@ -98,7 +120,7 @@ public class CoalesceFunction extends ScalarFunction {
     public String getName() {
         return NAME;
     }
-    
+
     @Override
     public boolean requiresFinalEvaluation() {
         return true;

Reply via email to