phoenix-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From anoopsamj...@apache.org
Subject git commit: PHOENIX-1002 Add support for % operator.(Kyle Buzsaki)
Date Mon, 14 Jul 2014 17:43:08 GMT
Repository: phoenix
Updated Branches:
  refs/heads/master 7b3135260 -> b12ba69b3


PHOENIX-1002 Add support for % operator.(Kyle Buzsaki)


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

Branch: refs/heads/master
Commit: b12ba69b300c84a692584935640cd984e2501f9b
Parents: 7b31352
Author: anoopsjohn <anoopsamjohn@gmail.com>
Authored: Mon Jul 14 23:12:39 2014 +0530
Committer: anoopsjohn <anoopsamjohn@gmail.com>
Committed: Mon Jul 14 23:12:39 2014 +0530

----------------------------------------------------------------------
 .../phoenix/end2end/ArithmeticQueryIT.java      |  92 +++++++
 .../phoenix/end2end/ModulusExpressionIT.java    | 186 +++++++++++++
 phoenix-core/src/main/antlr3/PhoenixSQL.g       |  15 +-
 .../phoenix/compile/ExpressionCompiler.java     | 275 ++++++++++---------
 .../phoenix/expression/ExpressionType.java      |   4 +-
 .../phoenix/expression/ModulusExpression.java   |  99 +++++++
 .../apache/phoenix/parse/ModulusParseNode.java  |  47 ++++
 .../apache/phoenix/parse/ParseNodeFactory.java  |   4 +
 .../apache/phoenix/parse/ParseNodeRewriter.java |  10 +
 .../apache/phoenix/parse/ParseNodeVisitor.java  |   4 +
 .../StatelessTraverseAllParseNodeVisitor.java   |   5 +
 .../parse/TraverseAllParseNodeVisitor.java      |   5 +
 .../parse/TraverseNoParseNodeVisitor.java       |  11 +
 .../parse/UnsupportedAllParseNodeVisitor.java   |  10 +
 14 files changed, 637 insertions(+), 130 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/phoenix/blob/b12ba69b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ArithmeticQueryIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ArithmeticQueryIT.java b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ArithmeticQueryIT.java
index 62a1639..33545a4 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ArithmeticQueryIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ArithmeticQueryIT.java
@@ -669,6 +669,29 @@ public class ArithmeticQueryIT extends BaseHBaseManagedTimeIT {
     }
     
     @Test
+    public void testOrderOfOperationsAdditionModulus() throws Exception {
+        Connection conn = DriverManager.getConnection(getUrl());
+        initIntegerTable(conn);
+        ResultSet rs;
+        
+        // 6 + 4 % 3
+        // 6 + 1
+        // 7
+        rs = conn.createStatement().executeQuery("SELECT six + four % three FROM ARITHMETIC_TEST");
+        assertTrue(rs.next());
+        assertEquals(7, rs.getLong(1));
+        assertFalse(rs.next());
+        
+        // 4 % 3 + 6
+        // 1 + 6
+        // 7
+        rs = conn.createStatement().executeQuery("SELECT four % three + six FROM ARITHMETIC_TEST");
+        assertTrue(rs.next());
+        assertEquals(7, rs.getLong(1));
+        assertFalse(rs.next());
+    }
+    
+    @Test
     public void testOrderOfOperationsSubtrationMultiplication() throws Exception {
         Connection conn = DriverManager.getConnection(getUrl());
         initIntegerTable(conn);
@@ -715,6 +738,29 @@ public class ArithmeticQueryIT extends BaseHBaseManagedTimeIT {
     }
     
     @Test
+    public void testOrderOfOperationsSubtractionModulus() throws Exception {
+        Connection conn = DriverManager.getConnection(getUrl());
+        initIntegerTable(conn);
+        ResultSet rs;
+        
+        // 6 - 4 % 3
+        // 6 - 1
+        // 5
+        rs = conn.createStatement().executeQuery("SELECT six - four % three FROM ARITHMETIC_TEST");
+        assertTrue(rs.next());
+        assertEquals(5, rs.getLong(1));
+        assertFalse(rs.next());
+        
+        // 4 % 3 - 6
+        // 1 - 6
+        // -5
+        rs = conn.createStatement().executeQuery("SELECT four % three - six FROM ARITHMETIC_TEST");
+        assertTrue(rs.next());
+        assertEquals(-5, rs.getLong(1));
+        assertFalse(rs.next());
+    }
+    
+    @Test
     public void testOrderOfOperationsMultiplicationDivision() throws Exception {
         Connection conn = DriverManager.getConnection(getUrl());
         initIntegerTable(conn);
@@ -736,4 +782,50 @@ public class ArithmeticQueryIT extends BaseHBaseManagedTimeIT {
         assertEquals(6, rs.getLong(1));
         assertFalse(rs.next());
     }
+    
+    @Test
+    public void testOrderOfOperationsMultiplicationModulus() throws Exception {
+        Connection conn = DriverManager.getConnection(getUrl());
+        initIntegerTable(conn);
+        ResultSet rs;
+        
+        // 6 * 4 % 3
+        // 24 % 3
+        // 0
+        rs = conn.createStatement().executeQuery("SELECT six * four % three FROM ARITHMETIC_TEST");
+        assertTrue(rs.next());
+        assertEquals(0, rs.getLong(1));
+        assertFalse(rs.next());
+        
+        // 4 % 3 * 6
+        // 1 * 6
+        // 6
+        rs = conn.createStatement().executeQuery("SELECT four % three * six FROM ARITHMETIC_TEST");
+        assertTrue(rs.next());
+        assertEquals(6, rs.getLong(1));
+        assertFalse(rs.next());
+    }
+    
+    @Test
+    public void testOrderOfOperationsDivisionModulus() throws Exception {
+        Connection conn = DriverManager.getConnection(getUrl());
+        initIntegerTable(conn);
+        ResultSet rs;
+        
+        // 6 / 4 % 3
+        // 1 % 3     (integer division)
+        // 1
+        rs = conn.createStatement().executeQuery("SELECT six / four % three FROM ARITHMETIC_TEST");
+        assertTrue(rs.next());
+        assertEquals(1, rs.getLong(1));
+        assertFalse(rs.next());
+        
+        // 4 % 3 / 6
+        // 1 / 6
+        // 0         (integer division)
+        rs = conn.createStatement().executeQuery("SELECT four % three / six FROM ARITHMETIC_TEST");
+        assertTrue(rs.next());
+        assertEquals(0, rs.getLong(1));
+        assertFalse(rs.next());
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/phoenix/blob/b12ba69b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ModulusExpressionIT.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ModulusExpressionIT.java
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ModulusExpressionIT.java
new file mode 100644
index 0000000..5055793
--- /dev/null
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ModulusExpressionIT.java
@@ -0,0 +1,186 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.phoenix.end2end;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+@Category(HBaseManagedTimeTest.class)
+public class ModulusExpressionIT extends BaseHBaseManagedTimeIT {
+    
+    private static final long SMALL_VALUE = 31L;
+    private static final long LARGE_VALUE = 0x5dec6f3847021a9bL;
+    
+    private static final long[] DIVIDENDS = {Long.MAX_VALUE, LARGE_VALUE, SMALL_VALUE, 0,
-SMALL_VALUE, -LARGE_VALUE, Long.MIN_VALUE};
+    private static final long[] DIVISORS = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 14, 31, 127, 1024};
+    
+    private void initTable(Connection conn, long value) throws SQLException {
+        String ddl = "CREATE TABLE MODULUS_TEST (pk BIGINT NOT NULL PRIMARY KEY, kv BIGINT)";
+        conn.createStatement().execute(ddl);
+        String dml = "UPSERT INTO MODULUS_TEST VALUES(?)";
+        PreparedStatement stmt = conn.prepareStatement(dml);
+        stmt.setLong(1, value);
+        stmt.execute();
+        conn.commit();        
+    }
+    
+    private void testDividend(long dividend) throws SQLException {
+        Connection conn = DriverManager.getConnection(getUrl());
+        initTable(conn, dividend);
+        
+        for(long divisor : DIVISORS) {
+            long remainder = dividend % divisor;
+            String sql = "SELECT pk % " + divisor + " FROM MODULUS_TEST";
+            
+            ResultSet rs = conn.createStatement().executeQuery(sql);
+            assertTrue(rs.next());
+            assertEquals(remainder, rs.getLong(1));
+            assertFalse(rs.next());
+        }
+    }
+    
+    @Test
+    public void testSmallPositiveDividend() throws SQLException {
+        testDividend(SMALL_VALUE);
+    }
+    
+    @Test
+    public void testLargePositiveDividend() throws SQLException {
+        testDividend(LARGE_VALUE);
+    }
+    
+    @Test
+    public void testLongMaxDividend() throws SQLException {
+        testDividend(Long.MAX_VALUE);
+    }      
+    
+    @Test
+    public void testSmallNegativeDividend() throws Exception {
+        testDividend(-1 * SMALL_VALUE);
+    }
+    
+    @Test
+    public void testLargeNegativeDividend() throws SQLException {
+        testDividend(-1 * LARGE_VALUE);
+    }
+    
+    @Test
+    public void testLongMinDividend() throws SQLException {
+        testDividend(Long.MIN_VALUE);
+    }   
+    
+    @Test
+    public void testZeroDividend() throws SQLException {
+        testDividend(0);
+    }
+    
+    @Test
+    public void testZeroDivisor() throws SQLException {
+        Connection conn = DriverManager.getConnection(getUrl());
+        initTable(conn, 0);
+        
+        for(long dividend : DIVIDENDS) {
+            try {
+                String sql = "SELECT " + dividend + " % pk FROM MODULUS_TEST";
+
+                // workaround for parser not being able to parse Long.MIN_VALUE
+                // see: https://issues.apache.org/jira/browse/PHOENIX-1061
+                if(dividend == Long.MIN_VALUE) {
+                    sql = "SELECT (" + (dividend + 1) + " + -1) % pk FROM MODULUS_TEST";
+                }
+
+                ResultSet rs = conn.createStatement().executeQuery(sql);
+                rs.next();
+                rs.getLong(1);
+                fail("modulus by zero: dividend: " + dividend + ". divisor : 0");
+            }
+            catch (ArithmeticException ex) {
+                // success
+            }
+        }
+    }
+    
+    @Test
+    public void testNullDividend() throws SQLException {
+        Connection conn = DriverManager.getConnection(getUrl());
+        initTable(conn, SMALL_VALUE);
+        
+        for(long divisor : DIVISORS) {
+            String sql = "SELECT kv % " + divisor + " FROM MODULUS_TEST";
+            
+            ResultSet rs = conn.createStatement().executeQuery(sql);
+            assertTrue(rs.next());
+            assertNull(rs.getObject(1));
+            assertFalse(rs.next());
+        }
+    }
+    
+    @Test
+    public void testNullDivisor() throws SQLException {
+        Connection conn = DriverManager.getConnection(getUrl());
+        initTable(conn, SMALL_VALUE);
+        
+        for(long dividend : DIVIDENDS) {
+            String sql = "SELECT " + dividend + " % kv FROM MODULUS_TEST";
+            
+            // workaround for parser not being able to parse Long.MIN_VALUE
+            // see: https://issues.apache.org/jira/browse/PHOENIX-1061
+            if(dividend == Long.MIN_VALUE) {
+                sql = "SELECT (" + (dividend + 1) + " + -1) % kv FROM MODULUS_TEST";
+            }
+            
+            ResultSet rs = conn.createStatement().executeQuery(sql);
+            assertTrue(rs.next());
+            assertNull(rs.getObject(1));
+            assertFalse(rs.next());
+        }
+    }
+    
+    @Test
+    public void testNullEverything() throws SQLException {
+        Connection conn = DriverManager.getConnection(getUrl());
+        initTable(conn, SMALL_VALUE);
+        
+        String sql = "SELECT null % kv FROM MODULUS_TEST";
+        
+        ResultSet rs = conn.createStatement().executeQuery(sql);
+        assertTrue(rs.next());
+        assertNull(rs.getObject(1));
+        assertFalse(rs.next());
+        
+        sql = "SELECT kv % null FROM MODULUS_TEST";
+        
+        rs = conn.createStatement().executeQuery(sql);
+        assertTrue(rs.next());
+        assertNull(rs.getObject(1));
+        assertFalse(rs.next());
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/phoenix/blob/b12ba69b/phoenix-core/src/main/antlr3/PhoenixSQL.g
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/antlr3/PhoenixSQL.g b/phoenix-core/src/main/antlr3/PhoenixSQL.g
index 0f7aba0..dbcca4f 100644
--- a/phoenix-core/src/main/antlr3/PhoenixSQL.g
+++ b/phoenix-core/src/main/antlr3/PhoenixSQL.g
@@ -731,15 +731,18 @@ subtract_expression returns [ParseNode ret]
 
 concat_expression returns [ParseNode ret]
 @init{List<ParseNode> l = new ArrayList<ParseNode>(4); }
-    :   i=multiply_divide_expression {l.add(i);} (CONCAT i=multiply_divide_expression {l.add(i);})*
{ $ret = l.size() == 1 ? l.get(0) : factory.concat(l); }
+    :   i=multiply_divide_modulo_expression {l.add(i);} (CONCAT i=multiply_divide_modulo_expression
{l.add(i);})* { $ret = l.size() == 1 ? l.get(0) : factory.concat(l); }
     ;
 
-multiply_divide_expression returns [ParseNode ret]
+multiply_divide_modulo_expression returns [ParseNode ret]
 @init{ParseNode lhs = null; List<ParseNode> l;}
     :   i=negate_expression {lhs = i;} 
-        (op=(ASTERISK | DIVIDE) rhs=negate_expression {
+        (op=(ASTERISK | DIVIDE | PERCENT) rhs=negate_expression {
             l = Arrays.asList(lhs, rhs); 
-            lhs = (op.getType() == ASTERISK ? factory.multiply(l) : factory.divide(l) );
+            // determine the expression type based on the operator found
+            lhs = op.getType() == ASTERISK ? factory.multiply(l)
+                : op.getType() == DIVIDE   ? factory.divide(l)
+                : factory.modulus(l);
             }
         )*
         { $ret = lhs; }
@@ -1048,6 +1051,10 @@ ASTERISK
 DIVIDE
     :   '/'
     ;
+    
+PERCENT
+    :   '%'
+    ;
 
 OUTER_JOIN
     : '(' '+' ')'

http://git-wip-us.apache.org/repos/asf/phoenix/blob/b12ba69b/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionCompiler.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionCompiler.java
b/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionCompiler.java
index 30011d6..5650ed5 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionCompiler.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/ExpressionCompiler.java
@@ -64,6 +64,7 @@ import org.apache.phoenix.expression.TimestampSubtractExpression;
 import org.apache.phoenix.expression.function.ArrayAllComparisonExpression;
 import org.apache.phoenix.expression.function.ArrayAnyComparisonExpression;
 import org.apache.phoenix.expression.function.InlineArrayElemRefExpression;
+import org.apache.phoenix.expression.ModulusExpression;
 import org.apache.phoenix.parse.AddParseNode;
 import org.apache.phoenix.parse.AndParseNode;
 import org.apache.phoenix.parse.ArithmeticParseNode;
@@ -83,6 +84,7 @@ import org.apache.phoenix.parse.InListParseNode;
 import org.apache.phoenix.parse.IsNullParseNode;
 import org.apache.phoenix.parse.LikeParseNode;
 import org.apache.phoenix.parse.LiteralParseNode;
+import org.apache.phoenix.parse.ModulusParseNode;
 import org.apache.phoenix.parse.MultiplyParseNode;
 import org.apache.phoenix.parse.NotParseNode;
 import org.apache.phoenix.parse.OrParseNode;
@@ -667,6 +669,100 @@ public class ExpressionCompiler extends UnsupportedAllParseNodeVisitor<Expressio
         // Otherwise create and return the expression
         return wrapGroupByExpression(expression);
     }
+    
+    @Override
+    public boolean visitEnter(AddParseNode node) throws SQLException {
+        return true;
+    }
+
+    @Override
+    public Expression visitLeave(AddParseNode node, List<Expression> children) throws
SQLException {
+        return visitLeave(node, children,
+                new ArithmeticExpressionBinder() {
+            @Override
+            public PDatum getBindMetaData(int i, List<Expression> children, final Expression
expression) {
+                PDataType type = expression.getDataType();
+                if (type != null && type.isCoercibleTo(PDataType.DATE)) {
+                    return new PDatum() {
+                        @Override
+                        public boolean isNullable() {
+                            return expression.isNullable();
+                        }
+                        @Override
+                        public PDataType getDataType() {
+                            return PDataType.DECIMAL;
+                        }
+                        @Override
+                        public Integer getMaxLength() {
+                            return expression.getMaxLength();
+                        }
+                        @Override
+                        public Integer getScale() {
+                            return expression.getScale();
+                        }
+                        @Override
+                        public SortOrder getSortOrder() {
+                            return expression.getSortOrder();
+                        }
+                    };
+                }
+                return expression;
+            }
+        },
+        new ArithmeticExpressionFactory() {
+            @Override
+            public Expression create(ArithmeticParseNode node, List<Expression> children)
throws SQLException {
+                boolean foundDate = false;
+                boolean isDeterministic = true;
+                PDataType theType = null;
+                for(int i = 0; i < children.size(); i++) {
+                    Expression e = children.get(i);
+                    isDeterministic &= e.isDeterministic();
+                    PDataType type = e.getDataType();
+                    if (type == null) {
+                        continue; 
+                    } else if (type.isCoercibleTo(PDataType.TIMESTAMP)) {
+                        if (foundDate) {
+                            throw TypeMismatchException.newException(type, node.toString());
+                        }
+                        if (theType == null || (theType != PDataType.TIMESTAMP &&
theType != PDataType.UNSIGNED_TIMESTAMP)) {
+                            theType = type;
+                        }
+                        foundDate = true;
+                    }else if (type == PDataType.DECIMAL) {
+                        if (theType == null || !theType.isCoercibleTo(PDataType.TIMESTAMP))
{
+                            theType = PDataType.DECIMAL;
+                        }
+                    } else if (type.isCoercibleTo(PDataType.LONG)) {
+                        if (theType == null) {
+                            theType = PDataType.LONG;
+                        }
+                    } else if (type.isCoercibleTo(PDataType.DOUBLE)) {
+                        if (theType == null) {
+                            theType = PDataType.DOUBLE;
+                        }
+                    } else {
+                        throw TypeMismatchException.newException(type, node.toString());
+                    }
+                }
+                if (theType == PDataType.DECIMAL) {
+                    return new DecimalAddExpression(children);
+                } else if (theType == PDataType.LONG) {
+                    return new LongAddExpression(children);
+                } else if (theType == PDataType.DOUBLE) {
+                    return new DoubleAddExpression(children);
+                } else if (theType == null) {
+                    return LiteralExpression.newConstant(null, theType, isDeterministic);
+                } else if (theType == PDataType.TIMESTAMP || theType == PDataType.UNSIGNED_TIMESTAMP)
{
+                    return new TimestampAddExpression(children);
+                } else if (theType.isCoercibleTo(PDataType.DATE)) {
+                    return new DateAddExpression(children);
+                } else {
+                    throw TypeMismatchException.newException(theType, node.toString());
+                }
+            }
+        });
+    }
 
     @Override
     public boolean visitEnter(SubtractParseNode node) throws SQLException {
@@ -674,8 +770,7 @@ public class ExpressionCompiler extends UnsupportedAllParseNodeVisitor<Expressio
     }
 
     @Override
-    public Expression visitLeave(SubtractParseNode node,
-            List<Expression> children) throws SQLException {
+    public Expression visitLeave(SubtractParseNode node, List<Expression> children)
throws SQLException {
         return visitLeave(node, children, new ArithmeticExpressionBinder() {
             @Override
             public PDatum getBindMetaData(int i, List<Expression> children,
@@ -843,100 +938,6 @@ public class ExpressionCompiler extends UnsupportedAllParseNodeVisitor<Expressio
     }
 
     @Override
-    public boolean visitEnter(AddParseNode node) throws SQLException {
-        return true;
-    }
-
-    @Override
-    public Expression visitLeave(AddParseNode node, List<Expression> children) throws
SQLException {
-        return visitLeave(node, children,
-                new ArithmeticExpressionBinder() {
-            @Override
-            public PDatum getBindMetaData(int i, List<Expression> children, final Expression
expression) {
-                PDataType type = expression.getDataType();
-                if (type != null && type.isCoercibleTo(PDataType.DATE)) {
-                    return new PDatum() {
-                        @Override
-                        public boolean isNullable() {
-                            return expression.isNullable();
-                        }
-                        @Override
-                        public PDataType getDataType() {
-                            return PDataType.DECIMAL;
-                        }
-                        @Override
-                        public Integer getMaxLength() {
-                            return expression.getMaxLength();
-                        }
-                        @Override
-                        public Integer getScale() {
-                            return expression.getScale();
-                        }
-                        @Override
-                        public SortOrder getSortOrder() {
-                            return expression.getSortOrder();
-                        }
-                    };
-                }
-                return expression;
-            }
-        },
-        new ArithmeticExpressionFactory() {
-            @Override
-            public Expression create(ArithmeticParseNode node, List<Expression> children)
throws SQLException {
-                boolean foundDate = false;
-                boolean isDeterministic = true;
-                PDataType theType = null;
-                for(int i = 0; i < children.size(); i++) {
-                    Expression e = children.get(i);
-                    isDeterministic &= e.isDeterministic();
-                    PDataType type = e.getDataType();
-                    if (type == null) {
-                        continue; 
-                    } else if (type.isCoercibleTo(PDataType.TIMESTAMP)) {
-                        if (foundDate) {
-                            throw TypeMismatchException.newException(type, node.toString());
-                        }
-                        if (theType == null || (theType != PDataType.TIMESTAMP &&
theType != PDataType.UNSIGNED_TIMESTAMP)) {
-                            theType = type;
-                        }
-                        foundDate = true;
-                    }else if (type == PDataType.DECIMAL) {
-                        if (theType == null || !theType.isCoercibleTo(PDataType.TIMESTAMP))
{
-                            theType = PDataType.DECIMAL;
-                        }
-                    } else if (type.isCoercibleTo(PDataType.LONG)) {
-                        if (theType == null) {
-                            theType = PDataType.LONG;
-                        }
-                    } else if (type.isCoercibleTo(PDataType.DOUBLE)) {
-                        if (theType == null) {
-                            theType = PDataType.DOUBLE;
-                        }
-                    } else {
-                        throw TypeMismatchException.newException(type, node.toString());
-                    }
-                }
-                if (theType == PDataType.DECIMAL) {
-                    return new DecimalAddExpression(children);
-                } else if (theType == PDataType.LONG) {
-                    return new LongAddExpression(children);
-                } else if (theType == PDataType.DOUBLE) {
-                    return new DoubleAddExpression(children);
-                } else if (theType == null) {
-                    return LiteralExpression.newConstant(null, theType, isDeterministic);
-                } else if (theType == PDataType.TIMESTAMP || theType == PDataType.UNSIGNED_TIMESTAMP)
{
-                    return new TimestampAddExpression(children);
-                } else if (theType.isCoercibleTo(PDataType.DATE)) {
-                    return new DateAddExpression(children);
-                } else {
-                    throw TypeMismatchException.newException(theType, node.toString());
-                }
-            }
-        });
-    }
-
-    @Override
     public boolean visitEnter(MultiplyParseNode node) throws SQLException {
         return true;
     }
@@ -981,37 +982,8 @@ public class ExpressionCompiler extends UnsupportedAllParseNodeVisitor<Expressio
             }
         });
     }
-
-    @Override
-    public boolean visitEnter(ArrayAnyComparisonNode node) throws SQLException {
-        return true;
-    }
-
-    @Override
-    public Expression visitLeave(ArrayAnyComparisonNode node, List<Expression> children)
throws SQLException {
-        return new ArrayAnyComparisonExpression(children);
-    }
-
-    @Override
-    public boolean visitEnter(ArrayAllComparisonNode node) throws SQLException {
-        return true;
-    }
     
     @Override
-    public boolean visitEnter(ArrayElemRefNode node) throws SQLException {
-        return true;
-    }
-    
-    @Override
-    public Expression visitLeave(ArrayElemRefNode node, List<Expression> l) throws
SQLException {
-        return new InlineArrayElemRefExpression(l);
-    }
-    
-    @Override
-    public Expression visitLeave(ArrayAllComparisonNode node, List<Expression> children)
throws SQLException {
-        return new ArrayAllComparisonExpression(children);
-    }
-    @Override
     public boolean visitEnter(DivideParseNode node) throws SQLException {
         return true;
     }
@@ -1071,6 +1043,59 @@ public class ExpressionCompiler extends UnsupportedAllParseNodeVisitor<Expressio
             }
         });
     }
+    
+    @Override
+    public boolean visitEnter(ModulusParseNode node) throws SQLException {
+        return true;
+    }
+
+    @Override
+    public Expression visitLeave(ModulusParseNode node, List<Expression> children)
throws SQLException {
+        return visitLeave(node, children, null, new ArithmeticExpressionFactory() {
+            @Override
+            public Expression create(ArithmeticParseNode node, List<Expression> children)
throws SQLException {
+                // ensure integer types
+                for(Expression child : children) {
+                    PDataType type = child.getDataType();
+                    if(type != null && !type.isCoercibleTo(PDataType.LONG)) {
+                        throw TypeMismatchException.newException(type, node.toString());
+                    }
+                }
+                
+                return new ModulusExpression(children);
+            }
+        });
+    }
+    
+    @Override
+    public boolean visitEnter(ArrayAnyComparisonNode node) throws SQLException {
+        return true;
+    }
+
+    @Override
+    public Expression visitLeave(ArrayAnyComparisonNode node, List<Expression> children)
throws SQLException {
+        return new ArrayAnyComparisonExpression(children);
+    }
+
+    @Override
+    public boolean visitEnter(ArrayAllComparisonNode node) throws SQLException {
+        return true;
+    }
+    
+    @Override
+    public boolean visitEnter(ArrayElemRefNode node) throws SQLException {
+        return true;
+    }
+    
+    @Override
+    public Expression visitLeave(ArrayElemRefNode node, List<Expression> l) throws
SQLException {
+        return new InlineArrayElemRefExpression(l);
+    }
+    
+    @Override
+    public Expression visitLeave(ArrayAllComparisonNode node, List<Expression> children)
throws SQLException {
+        return new ArrayAllComparisonExpression(children);
+    }
 
     public static void throwNonAggExpressionInAggException(String nonAggregateExpression)
throws SQLException {
         throw new SQLExceptionInfo.Builder(SQLExceptionCode.AGGREGATE_WITH_NOT_GROUP_BY_COLUMN)

http://git-wip-us.apache.org/repos/asf/phoenix/blob/b12ba69b/phoenix-core/src/main/java/org/apache/phoenix/expression/ExpressionType.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/expression/ExpressionType.java
b/phoenix-core/src/main/java/org/apache/phoenix/expression/ExpressionType.java
index 30b1dbb..326e0ea 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/expression/ExpressionType.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/expression/ExpressionType.java
@@ -178,7 +178,9 @@ public enum ExpressionType {
     LastValueFunction(LastValueFunction.class),
     ArrayAnyComparisonExpression(ArrayAnyComparisonExpression.class),
     ArrayAllComparisonExpression(ArrayAllComparisonExpression.class),
-    InlineArrayElemRefExpression(InlineArrayElemRefExpression.class);
+    InlineArrayElemRefExpression(InlineArrayElemRefExpression.class),
+    ModulusExpression(ModulusExpression.class);
+    
     ExpressionType(Class<? extends Expression> clazz) {
         this.clazz = clazz;
     }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/b12ba69b/phoenix-core/src/main/java/org/apache/phoenix/expression/ModulusExpression.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/expression/ModulusExpression.java
b/phoenix-core/src/main/java/org/apache/phoenix/expression/ModulusExpression.java
new file mode 100644
index 0000000..c8dfe93
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/expression/ModulusExpression.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.phoenix.expression;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
+import org.apache.phoenix.expression.Expression;
+import org.apache.phoenix.expression.function.ScalarFunction;
+import org.apache.phoenix.parse.FunctionParseNode.Argument;
+import org.apache.phoenix.parse.FunctionParseNode.BuiltInFunction;
+import org.apache.phoenix.schema.PDataType;
+import org.apache.phoenix.schema.tuple.Tuple;
+
+
+/**
+ * 
+ * Implementation of the LENGTH(<string>) build-in function. <string> is the
string
+ * of characters we want to find the length of. If <string> is NULL or empty, null
+ * is returned.
+ * 
+ * 
+ * @since 0.1
+ */
+public class ModulusExpression extends ArithmeticExpression {
+
+    public ModulusExpression() { }
+
+    public ModulusExpression(List<Expression> children) throws SQLException {
+        super(children);
+    }
+
+    private Expression getDividendExpression() {
+        return children.get(0);
+    }
+    
+    private Expression getDivisorExpression() {
+        return children.get(1);
+    }
+
+    @Override
+    public boolean evaluate(Tuple tuple, ImmutableBytesWritable ptr) {
+        // get the dividend
+        Expression dividendExpression = getDividendExpression();
+        if (!dividendExpression.evaluate(tuple, ptr)) {
+            return false;
+        }
+        if (ptr.getLength() == 0) {
+            return true;
+        }
+        long dividend = dividendExpression.getDataType().getCodec().decodeLong(ptr, dividendExpression.getSortOrder());
+        
+        // get the divisor
+        Expression divisorExpression = getDivisorExpression();
+        if (!divisorExpression.evaluate(tuple, ptr)) {
+            return false;
+        }
+        if (ptr.getLength() == 0) {
+            return true;
+        }
+        long divisor = divisorExpression.getDataType().getCodec().decodeLong(ptr, divisorExpression.getSortOrder());
+        
+        // actually perform modulus
+        long remainder = dividend % divisor;
+        
+        // return the result, use encodeLong to avoid extra Long allocation
+        byte[] resultPtr=new byte[PDataType.LONG.getByteSize()];
+        getDataType().getCodec().encodeLong(remainder, resultPtr, 0);
+        ptr.set(resultPtr);
+        return true;
+    }
+
+    @Override
+    public PDataType getDataType() {
+        return PDataType.LONG;
+    }
+
+    @Override
+    protected String getOperatorString() {
+        return " % ";
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/phoenix/blob/b12ba69b/phoenix-core/src/main/java/org/apache/phoenix/parse/ModulusParseNode.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/ModulusParseNode.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/ModulusParseNode.java
new file mode 100644
index 0000000..553e13f
--- /dev/null
+++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/ModulusParseNode.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.phoenix.parse;
+
+import java.sql.SQLException;
+import java.util.Collections;
+import java.util.List;
+
+
+
+/**
+ * 
+ * Node representing modulus in a SQL expression
+ *
+ * 
+ * @since 0.1
+ */
+public class ModulusParseNode extends ArithmeticParseNode {
+
+    ModulusParseNode(List<ParseNode> children) {
+        super(children);
+    }
+
+    @Override
+    public <T> T accept(ParseNodeVisitor<T> visitor) throws SQLException {
+        List<T> l = Collections.emptyList();
+        if (visitor.visitEnter(this)) {
+            l = acceptChildren(visitor);
+        }
+        return visitor.visitLeave(this, l);
+    }
+}

http://git-wip-us.apache.org/repos/asf/phoenix/blob/b12ba69b/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
index ab38b8d..4b27696 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeFactory.java
@@ -198,6 +198,10 @@ public class ParseNodeFactory {
     public MultiplyParseNode multiply(List<ParseNode> children) {
         return new MultiplyParseNode(children);
     }
+    
+    public ModulusParseNode modulus(List<ParseNode> children) {
+        return new ModulusParseNode(children);
+    }
 
     public AndParseNode and(List<ParseNode> children) {
         return new AndParseNode(children);

http://git-wip-us.apache.org/repos/asf/phoenix/blob/b12ba69b/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeRewriter.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeRewriter.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeRewriter.java
index 88601f2..582ec99 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeRewriter.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeRewriter.java
@@ -277,6 +277,16 @@ public class ParseNodeRewriter extends TraverseAllParseNodeVisitor<ParseNode>
{
     }
     
     @Override
+    public ParseNode visitLeave(ModulusParseNode node, List<ParseNode> nodes) throws
SQLException {
+        return leaveCompoundNode(node, nodes, new CompoundNodeFactory() {
+            @Override
+            public ParseNode createNode(List<ParseNode> children) {
+                return NODE_FACTORY.modulus(children);
+            }
+        });
+    }
+    
+    @Override
     public ParseNode visitLeave(final FunctionParseNode node, List<ParseNode> nodes)
throws SQLException {
         return leaveCompoundNode(node, nodes, new CompoundNodeFactory() {
             @Override

http://git-wip-us.apache.org/repos/asf/phoenix/blob/b12ba69b/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeVisitor.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeVisitor.java b/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeVisitor.java
index a35894b..5308677 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeVisitor.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/ParseNodeVisitor.java
@@ -18,6 +18,7 @@
 package org.apache.phoenix.parse;
 
 import java.sql.SQLException;
+import java.sql.SQLFeatureNotSupportedException;
 import java.util.List;
 
 
@@ -65,6 +66,9 @@ public interface ParseNodeVisitor<E> {
     
     public boolean visitEnter(MultiplyParseNode node) throws SQLException;
     public E visitLeave(MultiplyParseNode node, List<E> l) throws SQLException;
+
+    public boolean visitEnter(ModulusParseNode node) throws SQLException;
+    public E visitLeave(ModulusParseNode node, List<E> l) throws SQLException;
     
     public boolean visitEnter(DivideParseNode node) throws SQLException;
     public E visitLeave(DivideParseNode node, List<E> l) throws SQLException;

http://git-wip-us.apache.org/repos/asf/phoenix/blob/b12ba69b/phoenix-core/src/main/java/org/apache/phoenix/parse/StatelessTraverseAllParseNodeVisitor.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/StatelessTraverseAllParseNodeVisitor.java
b/phoenix-core/src/main/java/org/apache/phoenix/parse/StatelessTraverseAllParseNodeVisitor.java
index 889edd3..0be5e01 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/parse/StatelessTraverseAllParseNodeVisitor.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/StatelessTraverseAllParseNodeVisitor.java
@@ -68,6 +68,11 @@ public class StatelessTraverseAllParseNodeVisitor extends TraverseAllParseNodeVi
     }
 
     @Override
+    public Void visitLeave(ModulusParseNode node, List<Void> l) throws SQLException
{
+        return null;
+    }
+
+    @Override
     public Void visitLeave(SubtractParseNode node, List<Void> l) throws SQLException
{
         return null;
     }

http://git-wip-us.apache.org/repos/asf/phoenix/blob/b12ba69b/phoenix-core/src/main/java/org/apache/phoenix/parse/TraverseAllParseNodeVisitor.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/TraverseAllParseNodeVisitor.java
b/phoenix-core/src/main/java/org/apache/phoenix/parse/TraverseAllParseNodeVisitor.java
index f85b9b3..af20278 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/parse/TraverseAllParseNodeVisitor.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/TraverseAllParseNodeVisitor.java
@@ -98,6 +98,11 @@ public abstract class TraverseAllParseNodeVisitor<T> extends BaseParseNodeVisito
     public boolean visitEnter(DivideParseNode node) throws SQLException {
         return true;
     }
+    
+    @Override
+    public boolean visitEnter(ModulusParseNode node) throws SQLException {
+        return true;
+    }
 
     @Override
     public boolean visitEnter(BetweenParseNode node) throws SQLException {

http://git-wip-us.apache.org/repos/asf/phoenix/blob/b12ba69b/phoenix-core/src/main/java/org/apache/phoenix/parse/TraverseNoParseNodeVisitor.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/TraverseNoParseNodeVisitor.java
b/phoenix-core/src/main/java/org/apache/phoenix/parse/TraverseNoParseNodeVisitor.java
index 18cccd5..4c0fbea 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/parse/TraverseNoParseNodeVisitor.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/TraverseNoParseNodeVisitor.java
@@ -203,6 +203,17 @@ public abstract class TraverseNoParseNodeVisitor<T> extends BaseParseNodeVisitor
     public T visitLeave(DivideParseNode node, List<T> l) throws SQLException {
         return null;
     }
+    
+    @Override
+    public boolean visitEnter(ModulusParseNode node) throws SQLException {
+        return false;
+    }
+
+    @Override
+    public T visitLeave(ModulusParseNode node, List<T> l) throws SQLException {
+        return null;
+    }
+    
     @Override
     public boolean visitEnter(StringConcatParseNode node) throws SQLException {
         return false;

http://git-wip-us.apache.org/repos/asf/phoenix/blob/b12ba69b/phoenix-core/src/main/java/org/apache/phoenix/parse/UnsupportedAllParseNodeVisitor.java
----------------------------------------------------------------------
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/parse/UnsupportedAllParseNodeVisitor.java
b/phoenix-core/src/main/java/org/apache/phoenix/parse/UnsupportedAllParseNodeVisitor.java
index 9121bcc..43cb0c3 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/parse/UnsupportedAllParseNodeVisitor.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/UnsupportedAllParseNodeVisitor.java
@@ -206,6 +206,16 @@ abstract public class UnsupportedAllParseNodeVisitor<E> extends
BaseParseNodeVis
     }
 
     @Override
+    public boolean visitEnter(ModulusParseNode node) throws SQLException {
+        throw new SQLFeatureNotSupportedException(node.toString());
+    }
+
+    @Override
+    public E visitLeave(ModulusParseNode node, List<E> l) throws SQLException {
+        throw new SQLFeatureNotSupportedException(node.toString());
+    }
+
+    @Override
     public boolean visitEnter(DivideParseNode node) throws SQLException {
         throw new SQLFeatureNotSupportedException(node.toString());
     }


Mime
View raw message