This is an automated email from the ASF dual-hosted git repository.

stoty pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/phoenix.git


The following commit(s) were added to refs/heads/master by this push:
     new 11f8e3c200 PHOENIX-6764 Implement Binary and Hexadecimal literals
11f8e3c200 is described below

commit 11f8e3c20068b3e62482f6b3cdd0214915767636
Author: Istvan Toth <[email protected]>
AuthorDate: Fri Aug 19 13:00:55 2022 +0200

    PHOENIX-6764 Implement Binary and Hexadecimal literals
    
    also changes to how binary types are converted to String in ResultSets and 
explain results
---
 .../end2end/BaseTenantSpecificViewIndexIT.java     |   6 +-
 .../phoenix/end2end/BinaryStringLiteralIT.java     | 142 +++++++++++++++++++++
 .../phoenix/end2end/RowValueConstructorIT.java     |   2 +-
 .../phoenix/end2end/SortMergeJoinMoreIT.java       |   8 +-
 .../org/apache/phoenix/end2end/UpsertSelectIT.java |   2 +-
 .../it/java/org/apache/phoenix/end2end/ViewIT.java |  11 +-
 .../phoenix/end2end/index/SaltedIndexIT.java       |  13 +-
 .../phoenix/end2end/join/HashJoinMoreIT.java       |   8 +-
 phoenix-core/src/main/antlr3/PhoenixSQL.g          |  77 ++++++++++-
 .../org/apache/phoenix/jdbc/PhoenixConnection.java |   3 +
 .../org/apache/phoenix/parse/ParseNodeFactory.java |  44 ++++++-
 .../phoenix/schema/types/PArrayDataType.java       |   2 +-
 .../org/apache/phoenix/schema/types/PBinary.java   |   3 -
 .../apache/phoenix/schema/types/PVarbinary.java    |  10 +-
 .../java/org/apache/phoenix/util/ByteUtil.java     |  50 +++++++-
 .../apache/phoenix/compile/WhereOptimizerTest.java |  13 +-
 .../org/apache/phoenix/parse/QueryParserTest.java  |  54 ++++++++
 .../org/apache/phoenix/query/QueryPlanTest.java    |   4 +-
 18 files changed, 402 insertions(+), 50 deletions(-)

diff --git 
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseTenantSpecificViewIndexIT.java
 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseTenantSpecificViewIndexIT.java
index fdce1c4b87..92eef8ef3d 100644
--- 
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseTenantSpecificViewIndexIT.java
+++ 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BaseTenantSpecificViewIndexIT.java
@@ -33,6 +33,7 @@ import org.apache.hadoop.hbase.util.Pair;
 import org.apache.phoenix.compile.ExplainPlan;
 import org.apache.phoenix.compile.ExplainPlanAttributes;
 import org.apache.phoenix.jdbc.PhoenixPreparedStatement;
+import org.apache.phoenix.schema.types.PVarbinary;
 import org.apache.phoenix.util.PhoenixRuntime;
 import org.apache.phoenix.util.SchemaUtil;
 
@@ -169,9 +170,10 @@ public abstract class BaseTenantSpecificViewIndexIT 
extends SplitSystemCatalogIT
             } else {
                 iteratorTypeAndScanSize = "PARALLEL 3-WAY";
                 clientSortAlgo = "CLIENT MERGE SORT";
-                keyRanges = " [0," + (Short.MIN_VALUE + expectedIndexIdOffset)
+                keyRanges = " [X'00'," + (Short.MIN_VALUE + 
expectedIndexIdOffset)
                     + ",'" + tenantId + "','" + valuePrefix + "v2-1'] - ["
-                    + (saltBuckets - 1) + "," + (Short.MIN_VALUE + 
expectedIndexIdOffset)
+                    + PVarbinary.INSTANCE.toStringLiteral(new byte[] 
{(byte)(saltBuckets - 1)})
+                    + "," + (Short.MIN_VALUE + expectedIndexIdOffset)
                     + ",'" + tenantId + "','" + valuePrefix + "v2-1']";
             }
             expectedTableName = "_IDX_" + tableName;
diff --git 
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/BinaryStringLiteralIT.java
 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BinaryStringLiteralIT.java
new file mode 100644
index 0000000000..a6632166d8
--- /dev/null
+++ 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/BinaryStringLiteralIT.java
@@ -0,0 +1,142 @@
+/*
+ * 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.assertTrue;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+/**
+ * End to end tests for Hexadecimal and Binary literals
+ */
+@Category(ParallelStatsDisabledTest.class)
+public class BinaryStringLiteralIT extends ParallelStatsDisabledIT {
+
+    private static String EMPTY = "";
+    private static String THREE_HEX = "0001AA";
+    private static String NINE_HEX = "0102030405607080F0";
+
+    private static String THREE_BIN = "00000000" + "00000001" + "10101010";
+    private static String NINE_BIN =
+            "00000001" + "00000010" + "00000011" + "00000100" + "00000101" + 
"01100000" + "01110000"
+                    + "10000000" + "11110000";
+
+    private String PARSER_STRESS = "x'0 12 ' --comment \n /* comment */ ' 34 
567' \n \n 'aA'";
+
+    private String toHex(String s) {
+        return "X'" + s + "'";
+    }
+
+    private String toBin(String s) {
+        return "B'" + s + "'";
+    }
+
+    private void insertRow(Statement stmt, String tableName, int id, String s) 
throws SQLException {
+        stmt.executeUpdate("UPSERT INTO " + tableName + " VALUES (" + id + "," 
+ s + "," + s + ")");
+    }
+
+    @Test
+    public void testBinary() throws Exception {
+        String tableName = generateUniqueName();
+
+        try (Connection conn = DriverManager.getConnection(getUrl());
+                Statement stmt = conn.createStatement();) {
+            String ddl =
+                    "CREATE TABLE " + tableName
+                            + " (id INTEGER NOT NULL PRIMARY KEY, b 
BINARY(10), vb VARBINARY)";
+            stmt.execute(ddl);
+            conn.commit();
+
+            insertRow(stmt, tableName, 1, toHex(EMPTY));
+            insertRow(stmt, tableName, 3, toHex(THREE_HEX));
+            insertRow(stmt, tableName, 9, toHex(NINE_HEX));
+            insertRow(stmt, tableName, 10, PARSER_STRESS);
+
+            insertRow(stmt, tableName, 101, toBin(EMPTY));
+            insertRow(stmt, tableName, 103, toBin(THREE_BIN));
+            insertRow(stmt, tableName, 109, toBin(NINE_BIN));
+
+            conn.commit();
+            ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName + " 
ORDER BY ID ASC");
+            assertTrue(rs.next());
+            assertEquals(1, rs.getInt(1));
+            assertEquals(null, rs.getString(2));
+            assertEquals(null, rs.getString(3));
+
+            assertTrue(rs.next());
+            assertEquals(3, rs.getInt(1));
+            assertEquals("0001aa00000000000000", rs.getString(2));
+            assertEquals("0001aa", rs.getString(3));
+
+            assertTrue(rs.next());
+            assertEquals(9, rs.getInt(1));
+            assertEquals("0102030405607080f000", rs.getString(2));
+            assertEquals("0102030405607080f0", rs.getString(3));
+
+            assertTrue(rs.next());
+            assertEquals(10, rs.getInt(1));
+            assertEquals("01234567aa0000000000", rs.getString(2));
+            assertEquals("01234567aa", rs.getString(3));
+
+            assertTrue(rs.next());
+            assertEquals(101, rs.getInt(1));
+            assertEquals(null, rs.getString(2));
+            assertEquals(null, rs.getString(3));
+
+            assertTrue(rs.next());
+            assertEquals(103, rs.getInt(1));
+            assertEquals("0001aa00000000000000", rs.getString(2));
+            assertEquals("0001aa", rs.getString(3));
+
+            assertTrue(rs.next());
+            assertEquals(109, rs.getInt(1));
+            assertEquals("0102030405607080f000", rs.getString(2));
+            assertEquals("0102030405607080f0", rs.getString(3));
+
+            assertFalse(rs.next());
+        }
+    }
+
+    @Test
+    public void testBinaryArray() throws Exception {
+        String tableName = generateUniqueName();
+
+        try (Connection conn = DriverManager.getConnection(getUrl());
+                Statement stmt = conn.createStatement();) {
+            String ddl =
+                    "CREATE TABLE " + tableName
+                            + " (id INTEGER NOT NULL PRIMARY KEY, b 
BINARY(10), a BINARY(10)[])";
+            stmt.execute(ddl);
+            conn.commit();
+
+            stmt.executeUpdate("UPSERT INTO " + tableName + " VALUES (" + 3 + 
"," + toHex(THREE_HEX) + ", ARRAY[" + toHex(THREE_HEX)+", "+toHex(THREE_HEX)+", 
"+toHex(THREE_HEX)+"])");
+
+            conn.commit();
+            ResultSet rs = stmt.executeQuery("SELECT * FROM " + tableName + " 
ORDER BY ID ASC");
+            assertTrue(rs.next());
+            assertEquals("0001aa00000000000000", rs.getString(2));
+            //FIXME we're using a different string representation here than 
for the scalar values
+            assertEquals("[X'0001aa00000000000000', X'0001aa00000000000000', 
X'0001aa00000000000000']", rs.getString(3));
+            assertFalse(rs.next());
+        }
+    }
+
+}
diff --git 
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/RowValueConstructorIT.java
 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/RowValueConstructorIT.java
index 47d63272f7..dee1792c90 100644
--- 
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/RowValueConstructorIT.java
+++ 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/RowValueConstructorIT.java
@@ -1238,7 +1238,7 @@ public class RowValueConstructorIT extends 
ParallelStatsDisabledIT {
                 explainPlanAttributes.getExplainScanType());
             assertEquals(tempTableWithCompositePK,
                 explainPlanAttributes.getTableName());
-            assertEquals(" [0,2] - [3,4]", 
explainPlanAttributes.getKeyRanges());
+            assertEquals(" [X'00',2] - [X'03',4]", 
explainPlanAttributes.getKeyRanges());
             assertEquals("CLIENT MERGE SORT",
                 explainPlanAttributes.getClientSortAlgo());
         } finally {
diff --git 
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/SortMergeJoinMoreIT.java 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SortMergeJoinMoreIT.java
index f1e6547833..b7e52859bb 100644
--- 
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/SortMergeJoinMoreIT.java
+++ 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/SortMergeJoinMoreIT.java
@@ -430,14 +430,14 @@ public class SortMergeJoinMoreIT extends 
ParallelStatsDisabledIT {
                 
                 String p = i == 0 ?
                         "SORT-MERGE-JOIN (INNER) TABLES\n" +
-                        "    CLIENT PARALLEL 2-WAY SKIP SCAN ON 2 RANGES OVER 
" + eventCountTableName + " [0,'5SEC',~1462993520000000000,'Tr/Bal'] - 
[1,'5SEC',~1462993420000000000,'Tr/Bal']\n" +
+                        "    CLIENT PARALLEL 2-WAY SKIP SCAN ON 2 RANGES OVER 
" + eventCountTableName + " [X'00','5SEC',~1462993520000000000,'Tr/Bal'] - 
[X'01','5SEC',~1462993420000000000,'Tr/Bal']\n" +
                         "        SERVER FILTER BY FIRST KEY ONLY\n" +
                         "        SERVER DISTINCT PREFIX FILTER OVER [BUCKET, 
TIMESTAMP, LOCATION]\n" +
                         "        SERVER AGGREGATE INTO ORDERED DISTINCT ROWS 
BY [BUCKET, TIMESTAMP, LOCATION]\n" +
                         "    CLIENT MERGE SORT\n" +
                         "    CLIENT SORTED BY [BUCKET, TIMESTAMP]\n" +
                         "AND (SKIP MERGE)\n" +
-                        "    CLIENT PARALLEL 2-WAY SKIP SCAN ON 2 RANGES OVER 
" + t[i] + " [0,'5SEC',~1462993520000000000,'Tr/Bal'] - 
[1,'5SEC',~1462993420000000000,'Tr/Bal']\n" +
+                        "    CLIENT PARALLEL 2-WAY SKIP SCAN ON 2 RANGES OVER 
" + t[i] + " [X'00','5SEC',~1462993520000000000,'Tr/Bal'] - 
[X'01','5SEC',~1462993420000000000,'Tr/Bal']\n" +
                         "        SERVER FILTER BY FIRST KEY ONLY AND 
SRC_LOCATION = DST_LOCATION\n" +
                         "        SERVER DISTINCT PREFIX FILTER OVER [BUCKET, 
\"TIMESTAMP\", SRC_LOCATION, DST_LOCATION]\n" +
                         "        SERVER AGGREGATE INTO ORDERED DISTINCT ROWS 
BY [BUCKET, \"TIMESTAMP\", SRC_LOCATION, DST_LOCATION]\n" +
@@ -446,14 +446,14 @@ public class SortMergeJoinMoreIT extends 
ParallelStatsDisabledIT {
                         "CLIENT AGGREGATE INTO DISTINCT ROWS BY [E.BUCKET, 
E.TIMESTAMP]"
                         :
                         "SORT-MERGE-JOIN (INNER) TABLES\n" +
-                        "    CLIENT PARALLEL 2-WAY SKIP SCAN ON 2 RANGES OVER 
" + eventCountTableName + " [0,'5SEC',~1462993520000000000,'Tr/Bal'] - 
[1,'5SEC',~1462993420000000000,'Tr/Bal']\n" +
+                        "    CLIENT PARALLEL 2-WAY SKIP SCAN ON 2 RANGES OVER 
" + eventCountTableName + " [X'00','5SEC',~1462993520000000000,'Tr/Bal'] - 
[X'01','5SEC',~1462993420000000000,'Tr/Bal']\n" +
                         "        SERVER FILTER BY FIRST KEY ONLY\n" +
                         "        SERVER DISTINCT PREFIX FILTER OVER [BUCKET, 
TIMESTAMP, LOCATION]\n" +
                         "        SERVER AGGREGATE INTO ORDERED DISTINCT ROWS 
BY [BUCKET, TIMESTAMP, LOCATION]\n" +
                         "    CLIENT MERGE SORT\n" +
                         "    CLIENT SORTED BY [BUCKET, TIMESTAMP]\n" +
                         "AND (SKIP MERGE)\n" +
-                        "    CLIENT PARALLEL 2-WAY SKIP SCAN ON 2 RANGES OVER 
" + t[i] + " [0,'5SEC',1462993420000000001,'Tr/Bal'] - 
[1,'5SEC',1462993520000000000,'Tr/Bal']\n" +
+                        "    CLIENT PARALLEL 2-WAY SKIP SCAN ON 2 RANGES OVER 
" + t[i] + " [X'00','5SEC',1462993420000000001,'Tr/Bal'] - 
[X'01','5SEC',1462993520000000000,'Tr/Bal']\n" +
                         "        SERVER FILTER BY FIRST KEY ONLY AND 
SRC_LOCATION = DST_LOCATION\n" +
                         "        SERVER DISTINCT PREFIX FILTER OVER [BUCKET, 
\"TIMESTAMP\", SRC_LOCATION, DST_LOCATION]\n" +
                         "        SERVER AGGREGATE INTO ORDERED DISTINCT ROWS 
BY [BUCKET, \"TIMESTAMP\", SRC_LOCATION, DST_LOCATION]\n" +
diff --git 
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectIT.java 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectIT.java
index 0febfe687d..a99ac5aab6 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/UpsertSelectIT.java
@@ -1640,7 +1640,7 @@ public class UpsertSelectIT extends 
ParallelStatsDisabledIT {
             rs = stmt.executeQuery("select * from " + t2);
             assertTrue(rs.next());
             assertEquals(2, rs.getLong(1));
-            assertEquals("[[128,0,0,54], [128,0,4,0]]", 
rs.getArray(2).toString());
+            assertEquals("[X'80000036', X'80000400']", 
rs.getArray(2).toString());
         }
     }
 
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewIT.java 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewIT.java
index 8bbc37d484..6f3dcb7f12 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewIT.java
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/ViewIT.java
@@ -63,6 +63,7 @@ import 
org.apache.phoenix.schema.export.DefaultSchemaRegistryRepository;
 import org.apache.phoenix.schema.export.DefaultSchemaWriter;
 import org.apache.phoenix.schema.export.SchemaRegistryRepository;
 import org.apache.phoenix.schema.export.SchemaRegistryRepositoryFactory;
+import org.apache.phoenix.schema.types.PVarbinary;
 import org.apache.phoenix.thirdparty.com.google.common.collect.Lists;
 import org.apache.phoenix.transaction.PhoenixTransactionProvider.Feature;
 import org.apache.phoenix.transaction.TransactionFactory;
@@ -1083,8 +1084,9 @@ public class ViewIT extends SplitSystemCatalogIT {
                 } else {
                     assertEquals("PARALLEL " + saltBuckets + "-WAY",
                         explainPlanAttributes.getIteratorTypeAndScanSize());
-                    assertEquals(" [0," + Short.MIN_VALUE + ",51] - ["
-                        + (saltBuckets - 1) + "," + Short.MIN_VALUE + ",51]",
+                    assertEquals(" [X'00'," + Short.MIN_VALUE + ",51] - ["
+                        + PVarbinary.INSTANCE.toStringLiteral(new byte[] 
{(byte)(saltBuckets - 1)})
+                        + "," + Short.MIN_VALUE + ",51]",
                         explainPlanAttributes.getKeyRanges());
                     assertEquals("CLIENT MERGE SORT",
                         explainPlanAttributes.getClientSortAlgo());
@@ -1149,8 +1151,9 @@ public class ViewIT extends SplitSystemCatalogIT {
                 } else {
                     assertEquals("PARALLEL " + saltBuckets + "-WAY",
                         explainPlanAttributes.getIteratorTypeAndScanSize());
-                    assertEquals(" [0," + (Short.MIN_VALUE + 1) + ",'foo'] - ["
-                        + (saltBuckets - 1) + "," + (Short.MIN_VALUE + 1)
+                    assertEquals(" [X'00'," + (Short.MIN_VALUE + 1) + ",'foo'] 
- ["
+                        + PVarbinary.INSTANCE.toStringLiteral(new byte[] 
{(byte)(saltBuckets - 1)})
+                        + "," + (Short.MIN_VALUE + 1)
                         + ",'foo']", explainPlanAttributes.getKeyRanges());
                     assertEquals("CLIENT MERGE SORT",
                         explainPlanAttributes.getClientSortAlgo());
diff --git 
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/SaltedIndexIT.java 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/SaltedIndexIT.java
index b1ed74e67f..a3ee7f7124 100644
--- 
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/SaltedIndexIT.java
+++ 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/index/SaltedIndexIT.java
@@ -34,6 +34,7 @@ import org.apache.phoenix.end2end.ParallelStatsDisabledTest;
 import org.apache.phoenix.jdbc.PhoenixConnection;
 import org.apache.phoenix.query.QueryServices;
 import org.apache.phoenix.schema.PTableKey;
+import org.apache.phoenix.schema.types.PVarbinary;
 import org.apache.phoenix.util.PropertiesUtil;
 import org.apache.phoenix.util.QueryUtil;
 import org.apache.phoenix.util.SchemaUtil;
@@ -148,8 +149,9 @@ public class SaltedIndexIT extends ParallelStatsDisabledIT {
         expectedPlan = indexSaltBuckets == null ? 
              "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexTableFullName + " 
[~'y']\n" +
              "    SERVER FILTER BY FIRST KEY ONLY" :
-            ("CLIENT PARALLEL 4-WAY RANGE SCAN OVER " + indexTableFullName + " 
[0,~'y'] - ["+(indexSaltBuckets.intValue()-1)+",~'y']\n" +
-
+            ("CLIENT PARALLEL 4-WAY RANGE SCAN OVER " + indexTableFullName + " 
[X'00',~'y'] - [" +
+             PVarbinary.INSTANCE.toStringLiteral(new byte[] 
{(byte)(indexSaltBuckets - 1)}) +
+             ",~'y']\n" +
              "    SERVER FILTER BY FIRST KEY ONLY\n" +
              "CLIENT MERGE SORT");
         assertEquals(expectedPlan,QueryUtil.getExplainPlan(rs));
@@ -170,9 +172,10 @@ public class SaltedIndexIT extends ParallelStatsDisabledIT 
{
         expectedPlan = indexSaltBuckets == null ? 
             "CLIENT PARALLEL 1-WAY RANGE SCAN OVER " + indexTableFullName + " 
[*] - [~'x']\n"
           + "    SERVER FILTER BY FIRST KEY ONLY" :
-            ("CLIENT PARALLEL 4-WAY RANGE SCAN OVER " + indexTableFullName + " 
[0,*] - ["+(indexSaltBuckets.intValue()-1)+",~'x']\n"
-
-           + "    SERVER FILTER BY FIRST KEY ONLY\n" +
+            ("CLIENT PARALLEL 4-WAY RANGE SCAN OVER " + indexTableFullName + " 
[X'00',*] - ["
+          + PVarbinary.INSTANCE.toStringLiteral(new byte[] 
{(byte)(indexSaltBuckets - 1)})
+          + ",~'x']\n"
+          + "    SERVER FILTER BY FIRST KEY ONLY\n" +
              "CLIENT MERGE SORT");
         assertEquals(expectedPlan,QueryUtil.getExplainPlan(rs));
         
diff --git 
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinMoreIT.java 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinMoreIT.java
index 226e0e3ae1..065f230bbf 100644
--- 
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinMoreIT.java
+++ 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/join/HashJoinMoreIT.java
@@ -839,21 +839,21 @@ public class HashJoinMoreIT extends 
ParallelStatsDisabledIT {
                         " GROUP BY C.BUCKET, C.\"TIMESTAMP\"";
                     
                 String p = i == 0 ?
-                        "CLIENT PARALLEL 2-WAY SKIP SCAN ON 2 RANGES OVER 
EVENT_COUNT [0,'5SEC',~1462993520000000000,'Tr/Bal'] - 
[1,'5SEC',~1462993420000000000,'Tr/Bal']\n" +
+                        "CLIENT PARALLEL 2-WAY SKIP SCAN ON 2 RANGES OVER 
EVENT_COUNT [X'00','5SEC',~1462993520000000000,'Tr/Bal'] - 
[X'01','5SEC',~1462993420000000000,'Tr/Bal']\n" +
                         "    SERVER FILTER BY FIRST KEY ONLY\n" +
                         "    SERVER AGGREGATE INTO DISTINCT ROWS BY 
[\"E.TIMESTAMP\", E.BUCKET]\n" +
                         "CLIENT MERGE SORT\n" +
                         "    PARALLEL INNER-JOIN TABLE 0 (SKIP MERGE)\n" +
-                        "        CLIENT PARALLEL 2-WAY SKIP SCAN ON 2 RANGES 
OVER " + t[i] + " [0,'5SEC',~1462993520000000000,'Tr/Bal'] - 
[1,'5SEC',~1462993420000000000,'Tr/Bal']\n" +
+                        "        CLIENT PARALLEL 2-WAY SKIP SCAN ON 2 RANGES 
OVER " + t[i] + " [X'00','5SEC',~1462993520000000000,'Tr/Bal'] - 
[X'01','5SEC',~1462993420000000000,'Tr/Bal']\n" +
                         "            SERVER FILTER BY FIRST KEY ONLY AND 
SRC_LOCATION = DST_LOCATION\n" +
                         "        CLIENT MERGE SORT"
                         :
-                        "CLIENT PARALLEL 2-WAY SKIP SCAN ON 2 RANGES OVER 
EVENT_COUNT [0,'5SEC',~1462993520000000000,'Tr/Bal'] - 
[1,'5SEC',~1462993420000000000,'Tr/Bal']\n" +
+                        "CLIENT PARALLEL 2-WAY SKIP SCAN ON 2 RANGES OVER 
EVENT_COUNT [X'00','5SEC',~1462993520000000000,'Tr/Bal'] - 
[X'01','5SEC',~1462993420000000000,'Tr/Bal']\n" +
                         "    SERVER FILTER BY FIRST KEY ONLY\n" +
                         "    SERVER AGGREGATE INTO DISTINCT ROWS BY 
[\"E.TIMESTAMP\", E.BUCKET]\n" +
                         "CLIENT MERGE SORT\n" +
                         "    PARALLEL INNER-JOIN TABLE 0 (SKIP MERGE)\n" +
-                        "        CLIENT PARALLEL 2-WAY SKIP SCAN ON 2 RANGES 
OVER " + t[i] + " [0,'5SEC',1462993420000000001,'Tr/Bal'] - 
[1,'5SEC',1462993520000000000,'Tr/Bal']\n" +
+                        "        CLIENT PARALLEL 2-WAY SKIP SCAN ON 2 RANGES 
OVER " + t[i] + " [X'00','5SEC',1462993420000000001,'Tr/Bal'] - 
[X'01','5SEC',1462993520000000000,'Tr/Bal']\n" +
                         "            SERVER FILTER BY FIRST KEY ONLY AND 
SRC_LOCATION = DST_LOCATION\n" +
                         "        CLIENT MERGE SORT";
                 
diff --git a/phoenix-core/src/main/antlr3/PhoenixSQL.g 
b/phoenix-core/src/main/antlr3/PhoenixSQL.g
index b0dc1898aa..f988acfbe9 100644
--- a/phoenix-core/src/main/antlr3/PhoenixSQL.g
+++ b/phoenix-core/src/main/antlr3/PhoenixSQL.g
@@ -110,7 +110,7 @@ tokens
     CYCLE='cycle';
     CASCADE='cascade';
     UPDATE='update';
-    STATISTICS='statistics';    
+    STATISTICS='statistics';
     COLUMNS='columns';
     TRACE='trace';
     ASYNC='async';
@@ -1152,7 +1152,10 @@ literal_or_bind returns [ParseNode ret]
 
 // Get a string, integer, double, date, boolean, or NULL value.
 literal returns [LiteralParseNode ret]
-    :   s=STRING_LITERAL {
+    :
+    h=hex_literal { ret = h; }
+    |   b=bin_literal { ret = b; }
+    |   s=STRING_LITERAL {
             ret = factory.literal(s.getText()); 
         }
     |   n=NUMBER {
@@ -1163,7 +1166,7 @@ literal returns [LiteralParseNode ret]
         }
     |   dbl=DOUBLE  {
             ret = factory.literal(Double.valueOf(dbl.getText()));
-        }    
+        }
     |   NULL {ret = factory.literal(null);}
     |   TRUE {ret = factory.literal(Boolean.TRUE);} 
     |   FALSE {ret = factory.literal(Boolean.FALSE);}
@@ -1175,13 +1178,33 @@ literal returns [LiteralParseNode ret]
             }
         }
     ;
-    
+
 int_or_long_literal returns [LiteralParseNode ret]
     :   n=NUMBER {
             ret = factory.intOrLong(n.getText());
         }
     ;
 
+hex_literal returns [LiteralParseNode ret]
+@init {StringBuilder sb = new StringBuilder();}
+    :
+    (
+    h=HEX_LITERAL { sb.append(h.getText()); }
+    (s=STRING_LITERAL { sb.append(factory.stringToHexLiteral(s.getText())); } 
)*
+    )
+    { ret = factory.hexLiteral(sb.toString()); }
+    ;
+
+bin_literal returns [LiteralParseNode ret]
+@init {StringBuilder sb = new StringBuilder();}
+    :
+    (
+    b=BIN_LITERAL { sb.append(b.getText()); }
+    (s=STRING_LITERAL { sb.append(factory.stringToBinLiteral(s.getText())); } 
)*
+    )
+    { ret = factory.binLiteral(sb.toString()); }
+    ;
+
 // Bind names are a colon followed by 1+ letter/digits/underscores, or '?' 
(unclear how Oracle acutally deals with this, but we'll just treat it as a 
special bind)
 bind_name returns [String ret]
     :   n=BIND_NAME { String bind = n.getText().substring(1); 
updateBind(bind); $ret = bind; } 
@@ -1217,6 +1240,28 @@ BIND_NAME
     : COLON (DIGIT)+
     ;
 
+HEX_LITERAL
+@init{ StringBuilder sb = new StringBuilder();}
+    :
+    X { $type = NAME;}
+    (
+    (FIELDCHAR) => FIELDCHAR+
+    | ('\'') => '\'' ' '* ( d=HEX_DIGIT { sb.append(d.getText()); } ' '* )* 
'\'' { $type=HEX_LITERAL; }
+    )?
+    { if ($type == HEX_LITERAL) { setText(sb.toString()); } }
+    ;
+
+BIN_LITERAL
+@init{ StringBuilder sb = new StringBuilder();}
+    :
+    B { $type = NAME;}
+    (
+    (FIELDCHAR) => FIELDCHAR+
+    | ('\'') =>  '\'' ' '* ( d=BIN_DIGIT { sb.append(d.getText()); } ' '* )* 
'\'' { $type=BIN_LITERAL; }
+    )?
+    { if ($type == BIN_LITERAL) { setText(sb.toString()); } }
+    ;
+
 NAME
     :    LETTER (FIELDCHAR)*
     |    '\"' (DBL_QUOTE_CHAR)* '\"'
@@ -1371,6 +1416,16 @@ DIGIT
     :    '0'..'9'
     ;
 
+fragment
+HEX_DIGIT
+    :   ('0'..'9' | 'a'..'f' | 'A'..'F')
+    ;
+
+fragment
+BIN_DIGIT
+    :   ('0' | '1')
+    ;
+
 // string literals
 STRING_LITERAL
 @init{ StringBuilder sb = new StringBuilder(); }
@@ -1390,6 +1445,16 @@ DBL_QUOTE_CHAR
     :   ( ~('\"') )+
     ;
 
+fragment
+X
+    : ( 'X' | 'x' )
+    ;
+
+fragment
+B
+    : ( 'B' | 'b' )
+    ;
+
 // escape sequence inside a string literal
 fragment
 CHAR_ESC
@@ -1413,7 +1478,7 @@ CHAR_ESC
 WS
     :   ( ' ' | '\t' | '\u2002' ) { $channel=HIDDEN; }
     ;
-    
+
 EOL
     :  ('\r' | '\n')
     { skip(); }
@@ -1439,7 +1504,7 @@ SL_COMMENT
 DOT
     : '.'
     ;
-    
+
 OTHER      
     : . { if (true) // to prevent compile error
               throw new RuntimeException("Unexpected char: '" + $text + "'"); 
} 
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixConnection.java 
b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixConnection.java
index 9d5cb28d18..774b0ce07d 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixConnection.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/jdbc/PhoenixConnection.java
@@ -102,6 +102,7 @@ import org.apache.phoenix.schema.PTableType;
 import org.apache.phoenix.schema.SchemaNotFoundException;
 import org.apache.phoenix.schema.TableNotFoundException;
 import org.apache.phoenix.schema.types.PArrayDataType;
+import org.apache.phoenix.schema.types.PBinary;
 import org.apache.phoenix.schema.types.PDataType;
 import org.apache.phoenix.schema.types.PDate;
 import org.apache.phoenix.schema.types.PDecimal;
@@ -361,6 +362,8 @@ public class PhoenixConnection implements MetaDataMutated, 
SQLCloseable, Phoenix
             formatters.put(PDecimal.INSTANCE,
                     FunctionArgumentType.NUMERIC.getFormatter(numberPattern));
             formatters.put(PVarbinary.INSTANCE, VarBinaryFormatter.INSTANCE);
+            formatters.put(PBinary.INSTANCE, VarBinaryFormatter.INSTANCE);
+
             // We do not limit the metaData on a connection less than the 
global
             // one,
             // as there's not much that will be cached here.
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 2bf1bf93f1..f4210c5365 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
@@ -30,6 +30,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 import 
org.apache.phoenix.thirdparty.com.google.common.annotations.VisibleForTesting;
 import 
org.apache.phoenix.thirdparty.com.google.common.collect.ArrayListMultimap;
 import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
+import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.hadoop.hbase.util.Pair;
 import org.apache.phoenix.expression.Expression;
 import org.apache.phoenix.expression.ExpressionType;
@@ -49,9 +50,11 @@ import org.apache.phoenix.schema.PTableType;
 import org.apache.phoenix.schema.SortOrder;
 import org.apache.phoenix.schema.TypeMismatchException;
 import org.apache.phoenix.schema.stats.StatisticsCollectionScope;
+import org.apache.phoenix.schema.types.PBinary;
 import org.apache.phoenix.schema.types.PDataType;
 import org.apache.phoenix.schema.types.PLong;
 import org.apache.phoenix.schema.types.PTimestamp;
+import org.apache.phoenix.util.ByteUtil;
 import org.apache.phoenix.util.SchemaUtil;
 
 import org.apache.phoenix.thirdparty.com.google.common.collect.ListMultimap;
@@ -79,7 +82,6 @@ public class ParseNodeFactory {
     private static final Multimap<String, BuiltInFunctionInfo> 
BUILT_IN_FUNCTION_MULTIMAP = ArrayListMultimap.create();
     private static final BigDecimal MAX_LONG = 
BigDecimal.valueOf(Long.MAX_VALUE);
 
-
     /**
      *
      * Key used to look up a built-in function using the combination of
@@ -555,7 +557,7 @@ public class ParseNodeFactory {
     public LiteralParseNode realNumber(String text) {
         return new LiteralParseNode(new BigDecimal(text, 
PDataType.DEFAULT_MATH_CONTEXT));
     }
-    
+
     public LiteralParseNode wholeNumber(String text) {
         int length = text.length();
         // We know it'll fit into long, might still fit into int
@@ -585,6 +587,44 @@ public class ParseNodeFactory {
         return new LiteralParseNode(l);
     }
 
+    public LiteralParseNode hexLiteral(String text) {
+        // The lexer has already removed everything but the digits
+        int length = text.length();
+        if (length % 2 != 0) {
+            throw new IllegalArgumentException("Hex literals must have an even 
number of digits");
+        }
+        byte[] bytes = Bytes.fromHex(text);
+        return new LiteralParseNode(bytes, PBinary.INSTANCE);
+    }
+
+    public String stringToHexLiteral(String in) {
+        String noSpace = in.replaceAll(" ", "");
+        if (!noSpace.matches("^[0-9a-fA-F]+$")) {
+            throw new IllegalArgumentException(
+                "Hex literal continuation line has non hex digit characters");
+        }
+        return noSpace;
+    }
+
+    public LiteralParseNode binLiteral(String text) {
+        // The lexer has already removed everything but the digits
+        int length = text.length();
+        if (length % 8 != 0) {
+            throw new IllegalArgumentException("Binary literals must have a 
multiple of 8 digits");
+        }
+        byte[] bytes = ByteUtil.fromAscii(text.toCharArray());
+        return new LiteralParseNode(bytes, PBinary.INSTANCE);
+    }
+
+    public String stringToBinLiteral(String in) {
+        String noSpace = in.replaceAll(" ", "");
+        if (!noSpace.matches("^[0-1]+$")) {
+            throw new IllegalArgumentException(
+                "Binary literal continuation line has non binary digit 
characters");
+        }
+        return noSpace;
+    }
+
     public CastParseNode cast(ParseNode expression, String dataType, Integer 
maxLength, Integer scale) {
         return new CastParseNode(expression, dataType, maxLength, scale, 
false);
     }
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PArrayDataType.java
 
b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PArrayDataType.java
index 497c90fdae..4ae1f23f8b 100644
--- 
a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PArrayDataType.java
+++ 
b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PArrayDataType.java
@@ -1144,7 +1144,7 @@ public abstract class PArrayDataType<T> extends 
PDataType<T> {
     }
 
     public static int estimateSize(int size, PDataType baseType) {
-        if (baseType.isFixedWidth()) {
+        if (baseType.isFixedWidth() && baseType.getByteSize() != null) {
             return baseType.getByteSize() * size;
         } else {
             return size * ValueSchema.ESTIMATED_VARIABLE_LENGTH_SIZE;
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PBinary.java 
b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PBinary.java
index 539bc87ad6..ec5a685222 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PBinary.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PBinary.java
@@ -187,9 +187,6 @@ public class PBinary extends PBinaryBase {
 
     @Override
     public String toStringLiteral(byte[] b, int offset, int length, Format 
formatter) {
-        if (length == 1) {
-            return Integer.toString(0xFF & b[offset]);
-        }
         return PVarbinary.INSTANCE.toStringLiteral(b, offset, length, 
formatter);
     }
 
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PVarbinary.java 
b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PVarbinary.java
index 1af460a306..579931344f 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PVarbinary.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/schema/types/PVarbinary.java
@@ -140,15 +140,11 @@ public class PVarbinary extends PBinaryBase {
     @Override
     public String toStringLiteral(byte[] b, int o, int length, Format 
formatter) {
         StringBuilder buf = new StringBuilder();
-        buf.append('[');
+        buf.append("X'");
         if (length > 0) {
-            for (int i = o; i < o + length; i++) {
-                buf.append(0xFF & b[i]);
-                buf.append(',');
-            }
-            buf.setLength(buf.length()-1);
+            buf.append(Bytes.toHex(b, o, length));
         }
-        buf.append(']');
+        buf.append("'");
         return buf.toString();
     }
 
diff --git a/phoenix-core/src/main/java/org/apache/phoenix/util/ByteUtil.java 
b/phoenix-core/src/main/java/org/apache/phoenix/util/ByteUtil.java
index 0cc9066b6c..f021d7bad5 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/util/ByteUtil.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/util/ByteUtil.java
@@ -56,7 +56,34 @@ public class ByteUtil {
             EMPTY_BYTE_ARRAY);
     public static final ImmutableBytesWritable EMPTY_IMMUTABLE_BYTE_ARRAY = 
new ImmutableBytesWritable(
             EMPTY_BYTE_ARRAY);
-    
+
+
+    /** Mask for bit 0 of a byte. */
+    private static final int BIT_0 = 0x01;
+
+    /** Mask for bit 1 of a byte. */
+    private static final int BIT_1 = 0x02;
+
+    /** Mask for bit 2 of a byte. */
+    private static final int BIT_2 = 0x04;
+
+    /** Mask for bit 3 of a byte. */
+    private static final int BIT_3 = 0x08;
+
+    /** Mask for bit 4 of a byte. */
+    private static final int BIT_4 = 0x10;
+
+    /** Mask for bit 5 of a byte. */
+    private static final int BIT_5 = 0x20;
+
+    /** Mask for bit 6 of a byte. */
+    private static final int BIT_6 = 0x40;
+
+    /** Mask for bit 7 of a byte. */
+    private static final int BIT_7 = 0x80;
+
+    private static final int[] BITS = {BIT_7, BIT_6, BIT_5, BIT_4, BIT_3, 
BIT_2, BIT_1, BIT_0};
+
     public static final Comparator<ImmutableBytesPtr> BYTES_PTR_COMPARATOR = 
new Comparator<ImmutableBytesPtr>() {
 
         @Override
@@ -652,4 +679,25 @@ public class ByteUtil {
         }
         return dst;
     }
+
+    // Adapted from the Commons Codec BinaryCodec, but treat the input as a 
byte sequence, without
+    // the endinanness reversion in the original code
+    public static byte[] fromAscii(final char[] ascii) {
+        if (ascii == null || ascii.length == 0) {
+            return EMPTY_BYTE_ARRAY;
+        }
+        final int asciiLength = ascii.length;
+        // get length/8 times bytes with 3 bit shifts to the right of the 
length
+        final byte[] l_raw = new byte[asciiLength >> 3];
+        // We incr index jj by 8 as we go along to not recompute indices using 
multiplication every
+        // time inside the loop.
+        for (int ii = 0, jj = 0; ii < l_raw.length; ii++, jj += 8) {
+            for (int bits = 0; bits < BITS.length; ++bits) {
+                if (ascii[jj + bits] == '1') {
+                    l_raw[ii] |= BITS[bits];
+                }
+            }
+        }
+        return l_raw;
+    }
 }
diff --git 
a/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereOptimizerTest.java 
b/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereOptimizerTest.java
index a451afedcb..26a674835a 100644
--- 
a/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereOptimizerTest.java
+++ 
b/phoenix-core/src/test/java/org/apache/phoenix/compile/WhereOptimizerTest.java
@@ -2735,8 +2735,8 @@ public class WhereOptimizerTest extends 
BaseConnectionlessQueryTest {
             assertTrue(filterList.getFilters().get(0) instanceof 
SkipScanFilter);
             assertTrue(filterList.getFilters().get(1) instanceof 
RowKeyComparisonFilter);
             RowKeyComparisonFilter rowKeyComparisonFilter 
=(RowKeyComparisonFilter) filterList.getFilters().get(1);
-            assertTrue(rowKeyComparisonFilter.toString().equals(
-                    "(OBJECT_ID, OBJECT_VERSION) IN 
([111,98,106,49,0,205,205,205,205],[111,98,106,50,0,206,206,206,206],[111,98,106,51,0,206,206,206,206])"));
+            assertEquals(rowKeyComparisonFilter.toString(),
+                    "(OBJECT_ID, OBJECT_VERSION) IN 
(X'6f626a3100cdcdcdcd',X'6f626a3200cececece',X'6f626a3300cececece')");
 
             assertTrue(queryPlan.getContext().getScanRanges().isPointLookup());
             assertArrayEquals(startKey, scan.getStartRow());
@@ -2780,8 +2780,8 @@ public class WhereOptimizerTest extends 
BaseConnectionlessQueryTest {
             scan = queryPlan.getContext().getScan();
             assertTrue(scan.getFilter() instanceof RowKeyComparisonFilter);
             rowKeyComparisonFilter = (RowKeyComparisonFilter)scan.getFilter();
-            assertTrue(rowKeyComparisonFilter.toString().equals(
-                    "((PK1, PK2) IN 
([128,0,0,2,128,0,0,3],[128,0,0,2,128,0,0,4]) AND PK3 = 5)"));
+            assertEquals(rowKeyComparisonFilter.toString(),
+                    "((PK1, PK2) IN (X'8000000280000003',X'8000000280000004') 
AND PK3 = 5)");
             assertArrayEquals(
                     scan.getStartRow(),
                     ByteUtil.concat(
@@ -2961,9 +2961,8 @@ public class WhereOptimizerTest extends 
BaseConnectionlessQueryTest {
 
             assertTrue(filterList.getFilters().get(1) instanceof 
RowKeyComparisonFilter);
             rowKeyComparisonFilter =(RowKeyComparisonFilter) 
filterList.getFilters().get(1);
-            assertTrue(rowKeyComparisonFilter.toString().equals(
-                    "((PK3, PK4) IN 
([127,255,255,251,128,0,0,5],[127,255,255,252,128,0,0,4])"+
-                    " AND (PK5, PK6, PK7) IN 
([128,0,0,5,127,255,255,249,128,0,0,7],[128,0,0,6,127,255,255,248,128,0,0,8]))"));
+            assertEquals(rowKeyComparisonFilter.toString(),
+                    "((PK3, PK4) IN (X'7ffffffb80000005',X'7ffffffc80000004') 
AND (PK5, PK6, PK7) IN 
(X'800000057ffffff980000007',X'800000067ffffff880000008'))");
             /**
              * RVC is not singleKey
              */
diff --git 
a/phoenix-core/src/test/java/org/apache/phoenix/parse/QueryParserTest.java 
b/phoenix-core/src/test/java/org/apache/phoenix/parse/QueryParserTest.java
index 8ad3a4fb61..305ec84610 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/parse/QueryParserTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/parse/QueryParserTest.java
@@ -66,6 +66,15 @@ public class QueryParserTest {
         }
     }
 
+    private void parseQueryThatShouldFailWithSQLException(String sql) throws 
Exception {
+        try {
+            parseQuery(sql);
+            fail("Query should throw a PhoenixParserException \n " + sql);
+        }
+        catch (SQLException e){
+        }
+    }
+
     @Test
     public void testParseGrantQuery() throws Exception {
 
@@ -925,6 +934,51 @@ public class QueryParserTest {
         // Expected failures.
         parseQueryThatShouldFail("SHOW CREATE VIEW foo");
         parseQueryThatShouldFail("SHOW CREATE TABLE 'foo'");
+    }
 
+    @Test
+    public void testBinaryLiteral() throws Exception {
+        // As per ISO/IEC 9075-2:2011(E) 5.3 <literal> page 163
+        // The literal syntax is:
+        //
+        // <binary string literal> ::=
+        // X <quote> [ <space>... ] [ { <hexit> [ <space>... ] <hexit> [ 
<space>... ] }... ] <quote>
+        // [ { <separator> <quote> [ <space>... ] [ { <hexit> [ <space>... ]
+        // <hexit> [ <space>... ] }... ] <quote> }... ]
+        //
+        // With the current grammar we only approximate the multi-line syntax, 
but
+        // we're close enough for most practical purposes
+        // (We do not enforce a new line before continuation lines)
+
+        // Happy paths
+        parseQuery("SELECT b, x from x WHERE x = x'00'");
+        parseQuery("SELECT b, x from x WHERE x = "
+                + "x'0 12 ' --comment \n /* comment */ '34 567' \n \n 'aA'");
+        parseQuery("SELECT b, x from x WHERE x = "
+                + "b'0 10 ' --comment \n /* comment */ '10 101' \n \n 
'00000000'");
+
+        // Expected failures.
+        // Space after 'x'
+        parseQueryThatShouldFailWithSQLException("SELECT b, x from x WHERE x = 
x '00'");
+        parseQueryThatShouldFailWithSQLException("SELECT b, x from x WHERE b = 
b '00'");
+
+        // Illegal digit character in first line
+        parseQueryThatShouldFail("SELECT b, x from x WHERE x = "
+                + "x'X0 12 ' --comment \n /* comment */ '34 5670' \n \n 'aA'");
+        parseQueryThatShouldFail("SELECT b, x from b WHERE b = "
+                + "b'B0 10 ' --comment \n /* comment */ '10 101' \n \n 
'000000000'");
+
+        // Illegal digit character in continuation line
+        parseQueryThatShouldFail("SELECT b, x from x WHERE x = "
+                + "x'0 12 ' --comment \n /* comment */ '34 5670' \n \n 'aA_'");
+        parseQueryThatShouldFail("SELECT b, x from x WHERE x = "
+                + "b'0 10 ' --comment \n /* comment */ '00 0000' \n \n '00_'");
+
+        // No digit between quotes in continuation line
+        parseQueryThatShouldFail("SELECT b, x from x WHERE x = "
+                + "x'0 12 ' --comment \n /* comment */ '34 5670' \n \n ''");
+        parseQueryThatShouldFail("SELECT b, x from x WHERE x = "
+                + "b'0 10 ' --comment \n /* comment */ '00 000' \n \n ''");
     }
+
 }
diff --git 
a/phoenix-core/src/test/java/org/apache/phoenix/query/QueryPlanTest.java 
b/phoenix-core/src/test/java/org/apache/phoenix/query/QueryPlanTest.java
index d87989c93e..cb7c261eec 100644
--- a/phoenix-core/src/test/java/org/apache/phoenix/query/QueryPlanTest.java
+++ b/phoenix-core/src/test/java/org/apache/phoenix/query/QueryPlanTest.java
@@ -248,7 +248,7 @@ public class QueryPlanTest extends 
BaseConnectionlessQueryTest {
             ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + 
query);
             String queryPlan = QueryUtil.getExplainPlan(rs);
             assertEquals(
-                    "CLIENT PARALLEL 20-WAY RANGE SCAN OVER FOO 
[0,'a',~'2016-01-28 23:59:59.999'] - [19,'a',~'2016-01-28 00:00:00.000']\n" + 
+                    "CLIENT PARALLEL 20-WAY RANGE SCAN OVER FOO 
[X'00','a',~'2016-01-28 23:59:59.999'] - [X'13','a',~'2016-01-28 
00:00:00.000']\n" +
                     "    SERVER FILTER BY FIRST KEY ONLY\n" + 
                     "CLIENT MERGE SORT", queryPlan);
         } finally {
@@ -274,7 +274,7 @@ public class QueryPlanTest extends 
BaseConnectionlessQueryTest {
             ResultSet rs = conn.createStatement().executeQuery("EXPLAIN " + 
query);
             String queryPlan = QueryUtil.getExplainPlan(rs);
             assertEquals(
-                    "CLIENT PARALLEL 20-WAY ROUND ROBIN RANGE SCAN OVER " + 
tableName + " [0,'a',~'2016-01-28 23:59:59.999'] - [19,'a',~'2016-01-28 
00:00:00.000']\n" + 
+                    "CLIENT PARALLEL 20-WAY ROUND ROBIN RANGE SCAN OVER " + 
tableName + " [X'00','a',~'2016-01-28 23:59:59.999'] - [X'13','a',~'2016-01-28 
00:00:00.000']\n" +
                     "    SERVER FILTER BY FIRST KEY ONLY", queryPlan);
         } finally {
             conn.close();

Reply via email to