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 <[email protected]> Authored: Mon Jul 14 23:12:39 2014 +0530 Committer: anoopsjohn <[email protected]> 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()); }
