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

vjasani pushed a commit to branch PHOENIX-628-feature
in repository https://gitbox.apache.org/repos/asf/phoenix.git


The following commit(s) were added to refs/heads/PHOENIX-628-feature by this 
push:
     new 507f2e4a6a PHOENIX-7058 : Implement json_query function on the json 
object (#1696)
507f2e4a6a is described below

commit 507f2e4a6a021039fadc18b7292a347fd028547f
Author: RanganathG <ranganath.govardhanag...@gmail.com>
AuthorDate: Fri Oct 13 10:58:43 2023 +0530

    PHOENIX-7058 : Implement json_query function on the json object (#1696)
---
 .../{JsonValueIT.java => JsonFunctionsIT.java}     | 75 ++++++++++++++++++++--
 .../apache/phoenix/compile/ExpressionCompiler.java | 12 ++--
 .../org/apache/phoenix/compile/UpsertCompiler.java | 18 +++---
 .../apache/phoenix/expression/ExpressionType.java  |  3 +-
 .../function/DistinctCountAggregateFunction.java   |  2 +-
 .../expression/function/JsonModifyFunction.java    | 27 ++++----
 ...nModifyFunction.java => JsonQueryFunction.java} | 59 +++++++++--------
 ...alueParseNode.java => JsonModifyParseNode.java} | 24 ++++---
 ...ValueParseNode.java => JsonQueryParseNode.java} | 24 ++++---
 .../apache/phoenix/parse/JsonValueParseNode.java   | 15 ++---
 .../org/apache/phoenix/parse/SelectStatement.java  |  2 +-
 .../apache/phoenix/util/json/BsonDataFormat.java   | 25 ++++++++
 12 files changed, 182 insertions(+), 104 deletions(-)

diff --git 
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/json/JsonValueIT.java 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/json/JsonFunctionsIT.java
similarity index 88%
rename from 
phoenix-core/src/it/java/org/apache/phoenix/end2end/json/JsonValueIT.java
rename to 
phoenix-core/src/it/java/org/apache/phoenix/end2end/json/JsonFunctionsIT.java
index 3b777cfeec..71d886632e 100644
--- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/json/JsonValueIT.java
+++ 
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/json/JsonFunctionsIT.java
@@ -17,6 +17,11 @@
  */
 package org.apache.phoenix.end2end.json;
 
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.jayway.jsonpath.Configuration;
+import com.jayway.jsonpath.JsonPath;
+import com.jayway.jsonpath.spi.json.GsonJsonProvider;
 import org.apache.hadoop.hbase.TableName;
 import org.apache.phoenix.end2end.ParallelStatsDisabledIT;
 import org.apache.phoenix.util.PropertiesUtil;
@@ -32,7 +37,7 @@ import java.util.Properties;
 import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES;
 import static org.junit.Assert.*;
 
-public class JsonValueIT extends ParallelStatsDisabledIT {
+public class JsonFunctionsIT extends ParallelStatsDisabledIT {
     private String JsonDoc1 = "{  \n" +
             "     \"info\":{    \n" +
             "       \"type\":1,  \n" +
@@ -56,7 +61,11 @@ public class JsonValueIT extends ParallelStatsDisabledIT {
             "        \"doubletype\": 2.5, \n" +
             "        \"longtype\": 1490020778457845, \n" +
             "        \"intArray\": [1, 2, 3], \n" +
-            "        \"nullcheck\": null \n"+
+            "        \"nullcheck\": null, \n"+
+            "        \"boolArray\": [true, false, false], \n" +
+            "        \"doubleArray\": [1.2,2.3,3.4], \n" +
+            "        \"stringArray\": [\"hello\",\"world\"], \n" +
+            "        \"mixedArray\": [2, \"string\", 1.2 , false] \n" +
             "    }\n" +
             "}";
 
@@ -448,7 +457,7 @@ public class JsonValueIT extends ParallelStatsDisabledIT {
             TestUtil.dumpTable(conn, TableName.valueOf(tableName));
 
             String queryTemplate ="SELECT JSON_VALUE(jsoncol, '$.type'), 
JSON_VALUE(jsoncol, '$.info.address.town'), " +
-                "JSON_VALUE(jsoncol, '$.info.tags[1]'), JSON_VALUE(jsoncol, 
'$.info.tags'), JSON_VALUE(jsoncol, '$.info') " +
+                "JSON_VALUE(jsoncol, '$.info.tags[1]'), JSON_QUERY(jsoncol, 
'$.info.tags'), JSON_QUERY(jsoncol, '$.info') " +
                 " FROM " + tableName +
                 " WHERE JSON_VALUE(jsoncol, '$.name') = '%s'";
             String query = String.format(queryTemplate, "AndersenFamily");
@@ -457,6 +466,9 @@ public class JsonValueIT extends ParallelStatsDisabledIT {
             assertEquals("Basic", rs.getString(1));
             assertEquals("Bristol", rs.getString(2));
             assertEquals("Water polo", rs.getString(3));
+            // returned format is different
+            compareJson(rs.getString(4), JsonDoc1, "$.info.tags");
+            compareJson(rs.getString(5), JsonDoc1, "$.info");
             assertFalse(rs.next());
 
             // Now check for empty match
@@ -488,7 +500,7 @@ public class JsonValueIT extends ParallelStatsDisabledIT {
             conn.createStatement().execute("UPSERT INTO " + tableName + " 
SELECT pk, col, JSON_MODIFY(jsoncol, '$.info.tags[2]', '\"UpsertSelectVal\"') 
from " + tableName);
 
             String queryTemplate ="SELECT JSON_VALUE(jsoncol, '$.type'), 
JSON_VALUE(jsoncol, '$.info.address.town'), " +
-                "JSON_VALUE(jsoncol, '$.info.tags[1]'), JSON_VALUE(jsoncol, 
'$.info.tags'), JSON_VALUE(jsoncol, '$.info'), " +
+                "JSON_VALUE(jsoncol, '$.info.tags[1]'), JSON_QUERY(jsoncol, 
'$.info.tags'), JSON_QUERY(jsoncol, '$.info'), " +
                 "JSON_VALUE(jsoncol, '$.info.tags[2]') " +
                 " FROM " + tableName +
                 " WHERE JSON_VALUE(jsoncol, '$.name') = '%s'";
@@ -498,6 +510,8 @@ public class JsonValueIT extends ParallelStatsDisabledIT {
             assertEquals("Basic", rs.getString(1));
             assertEquals("Manchester", rs.getString(2));
             assertEquals("alto1", rs.getString(3));
+            assertEquals("[\"Sport\", \"alto1\", \"UpsertSelectVal\"]", 
rs.getString(4));
+            assertEquals("{\"type\": 1, \"address\": {\"town\": 
\"Manchester\", \"county\": \"Avon\", \"country\": \"England\"}, \"tags\": 
[\"Sport\", \"alto1\", \"UpsertSelectVal\"]}", rs.getString(5));
             assertEquals("UpsertSelectVal", rs.getString(6));
 
             // Now check for empty match
@@ -513,7 +527,6 @@ public class JsonValueIT extends ParallelStatsDisabledIT {
         String tableName = generateUniqueName();
         try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
             conn.setAutoCommit(true);
-            //String ddl = "create table " + tableName + " (pk integer primary 
key, col integer, jsoncol.v varchar)";
             String ddl = "create table if not exists " + tableName + " (pk 
integer primary key, col integer, jsoncol json)";
             conn.createStatement().execute(ddl);
             PreparedStatement stmt = conn.prepareStatement("UPSERT INTO " + 
tableName + " VALUES (?,?,?)");
@@ -522,14 +535,32 @@ public class JsonValueIT extends ParallelStatsDisabledIT {
             stmt.setString(3, JsonDoc2);
             stmt.execute();
             conn.commit();
-            ResultSet rs = conn.createStatement().executeQuery("SELECT 
JSON_VALUE(JSONCOL,'$.test'), JSON_VALUE(JSONCOL, '$.testCnt'), 
JSON_VALUE(JSONCOL, '$.infoTop[5].info.address.state'),JSON_VALUE(JSONCOL, 
'$.infoTop[4].tags[1]')  FROM "
-                + tableName + " WHERE JSON_VALUE(JSONCOL, '$.test')='test1'");
+            ResultSet rs = conn.createStatement().executeQuery("SELECT 
JSON_VALUE(JSONCOL,'$.test'), " +
+                    "JSON_VALUE(JSONCOL, '$.testCnt'), " +
+                    "JSON_VALUE(JSONCOL, '$.infoTop[5].info.address.state')," +
+                    "JSON_VALUE(JSONCOL, '$.infoTop[4].tags[1]'),  " +
+                    "JSON_QUERY(JSONCOL, '$.infoTop'), " +
+                    "JSON_QUERY(JSONCOL, '$.infoTop[5].info'), " +
+                    "JSON_QUERY(JSONCOL, '$.infoTop[5].friends') " +
+                    "FROM " + tableName + " WHERE JSON_VALUE(JSONCOL, 
'$.test')='test1'");
             assertTrue(rs.next());
             assertEquals("test1", rs.getString(1));
             assertEquals("SomeCnt1", rs.getString(2));
+            assertEquals("North Dakota", rs.getString(3));
+            assertEquals("sint", rs.getString(4));
+            compareJson(rs.getString(5), JsonDoc2, "$.infoTop");
+            compareJson(rs.getString(6), JsonDoc2, "$.infoTop[5].info");
+            compareJson(rs.getString(7), JsonDoc2, "$.infoTop[5].friends");
         }
     }
 
+    private void compareJson(String result, String json, String path) throws 
JsonProcessingException {
+        Configuration conf = Configuration.builder().jsonProvider(new 
GsonJsonProvider()).build();
+        Object read = JsonPath.using(conf).parse(json).read(path);
+        ObjectMapper mapper = new ObjectMapper();
+        assertEquals(mapper.readTree(read.toString()), 
mapper.readTree(result));
+    }
+
     @Test
     public void testSimpleJsonDatatypes() throws Exception {
         Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
@@ -572,4 +603,34 @@ public class JsonValueIT extends ParallelStatsDisabledIT {
             assertEquals(null, rs.getString(12));
         }
     }
+
+    @Test
+    public void testJsonQuery() throws Exception {
+        Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+        String tableName = generateUniqueName();
+        try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
+            conn.setAutoCommit(true);
+            String ddl = "create table if not exists " + tableName + " (pk 
integer primary key, col integer, jsoncol json)";
+            conn.createStatement().execute(ddl);
+            PreparedStatement stmt = conn.prepareStatement("UPSERT INTO " + 
tableName + " VALUES (?,?,?)");
+            stmt.setInt(1, 1);
+            stmt.setInt(2, 2);
+            stmt.setString(3, JsonDatatypes);
+            stmt.execute();
+            conn.commit();
+            ResultSet rs = conn.createStatement().executeQuery("SELECT " +
+                    "JSON_QUERY(JSONCOL, '$.datatypes.intArray')," +
+                    "JSON_QUERY(JSONCOL, '$.datatypes.boolArray')," +
+                    "JSON_QUERY(JSONCOL, '$.datatypes.doubleArray')," +
+                    "JSON_QUERY(JSONCOL, '$.datatypes.stringArray')," +
+                    "JSON_QUERY(JSONCOL, '$.datatypes.mixedArray')  FROM "
+                    + tableName + " WHERE JSON_VALUE(JSONCOL, 
'$.datatypes.stringtype')='someString'");
+            assertTrue(rs.next());
+            compareJson(rs.getString(1), JsonDatatypes, 
"$.datatypes.intArray");
+            compareJson(rs.getString(2), JsonDatatypes, 
"$.datatypes.boolArray");
+            compareJson(rs.getString(3), JsonDatatypes, 
"$.datatypes.doubleArray");
+            compareJson(rs.getString(4), JsonDatatypes, 
"$.datatypes.stringArray");
+            compareJson(rs.getString(5), JsonDatatypes, 
"$.datatypes.mixedArray");
+        }
+    }
 }
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 4aaf84a8d6..13d78a2fb9 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
@@ -495,13 +495,13 @@ public class ExpressionCompiler extends 
UnsupportedAllParseNodeVisitor<Expressio
         ParseNode rhsNode = node.getChildren().get(1);
         Expression lhs = children.get(0);
         Expression rhs = children.get(1);
-        if (!lhs.getDataType().isComparisonSupported()) {
-            throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.COMPARISON_UNSUPPORTED)
-                    .setMessage(" for type " + 
lhs.getDataType()).build().buildException();
+        if (lhs.getDataType() != null && 
!lhs.getDataType().isComparisonSupported()) {
+            throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.COMPARISON_UNSUPPORTED).setMessage(
+                    " for type " + lhs.getDataType()).build().buildException();
         }
-        if (!rhs.getDataType().isComparisonSupported()) {
-            throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.COMPARISON_UNSUPPORTED)
-                    .setMessage(" for type " + 
rhs.getDataType()).build().buildException();
+        if (rhs.getDataType() != null && 
!rhs.getDataType().isComparisonSupported()) {
+            throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.COMPARISON_UNSUPPORTED).setMessage(
+                    " for type " + rhs.getDataType()).build().buildException();
         }
         if ( rhs.getDataType() != null && lhs.getDataType() != null &&
                 !lhs.getDataType().isCoercibleTo(rhs.getDataType())  && 
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java 
b/phoenix-core/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java
index 1c14702d99..58078da5f3 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/compile/UpsertCompiler.java
@@ -576,8 +576,13 @@ public class UpsertCompiler {
         int idx=0;
         if (valueNodes != null) {
             for (ParseNode valueNode : valueNodes) {
-                PColumn column = table.getColumns().get(idx);
-                ColumnParseNode cpn =
+                List<PColumn> columns = table.getColumns();
+                if (idx == columns.size()) {
+                    break;
+                }
+                PColumn column = columns.get(idx);
+                ColumnParseNode
+                        cpn =
                         new ParseNodeFactory().column(null, 
column.getName().getString(), null);
                 if (SchemaUtil.isPKColumn(column)) {
                     whereNodes.add(new ParseNodeFactory().equal(cpn, 
valueNodes.get(idx)));
@@ -712,7 +717,6 @@ public class UpsertCompiler {
         if (valueNodes == null || isFunctionEvalNeeded) {
             queryPlanToBe = new 
QueryOptimizer(services).optimize(queryPlanToBe, statement, targetColumns, 
parallelIteratorFactoryToBe);
             projectorToBe = queryPlanToBe.getProjector();
-            runOnServer = true;
         }
         final List<PColumn> allColumns = allColumnsToBe;
         final RowProjector projector = projectorToBe;
@@ -833,9 +837,6 @@ public class UpsertCompiler {
                         UpsertValuesCompiler expressionBuilder = new 
UpsertValuesCompiler(context);
                         int nodeIndex = 0;
                         for (ParseNode valueNode : valueNodes) {
-                            if (!valueNode.isStateless()) {
-                                //   throw new 
SQLExceptionInfo.Builder(SQLExceptionCode.VALUE_IN_UPSERT_NOT_CONSTANT).build().buildException();
-                            }
                             PColumn column = 
allColumns.get(columnIndexes[nodeIndex]);
                             expressionBuilder.setColumn(column);
                             Expression expression = 
valueNode.accept(expressionBuilder);
@@ -847,8 +848,9 @@ public class UpsertCompiler {
                             constantExpressions.add(expression);
                             nodeIndex++;
                         }
-                        
scan.setAttribute(BaseScannerRegionObserver.UPSERT_SELECT_EXPRS, 
UngroupedAggregateRegionObserver.serialize(constantExpressions));
-                                       }
+                        
scan.setAttribute(BaseScannerRegionObserver.UPSERT_SELECT_EXPRS,
+                                
UngroupedAggregateRegionObserver.serialize(constantExpressions));
+                    }
                     return new ServerUpsertSelectMutationPlan(queryPlan, 
tableRef, originalQueryPlan, context, connection, scan, aggPlan, aggProjector, 
maxSize, maxSizeBytes);
                 }
             }
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 13c87ff6ac..fb8a485ce7 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
@@ -194,7 +194,8 @@ public enum ExpressionType {
     RowKeyBytesStringFunction(RowKeyBytesStringFunction.class),
     PhoenixRowTimestampFunction(PhoenixRowTimestampFunction.class),
     JsonValueFunction(JsonValueFunction.class),
-    JsonModifyFunction(JsonModifyFunction.class)
+    JsonModifyFunction(JsonModifyFunction.class),
+    JsonQueryFunction(JsonQueryFunction.class)
     ;
 
     ExpressionType(Class<? extends Expression> clazz) {
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/DistinctCountAggregateFunction.java
 
b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/DistinctCountAggregateFunction.java
index 4efdbfbfad..5f73fb0baa 100644
--- 
a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/DistinctCountAggregateFunction.java
+++ 
b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/DistinctCountAggregateFunction.java
@@ -102,7 +102,7 @@ public class DistinctCountAggregateFunction extends 
DelegateConstantToCountAggre
     @Override
     public boolean evaluate(Tuple tuple, ImmutableBytesWritable ptr) {
         for (Expression child : getChildren()) {
-            if (!child.getDataType().isComparisonSupported()) {
+            if (child.getDataType() != null && 
!child.getDataType().isComparisonSupported()) {
                 throw new ComparisonNotSupportedException(child.getDataType());
             }
         }
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/JsonModifyFunction.java
 
b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/JsonModifyFunction.java
index 37b67578af..a968d0b347 100644
--- 
a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/JsonModifyFunction.java
+++ 
b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/JsonModifyFunction.java
@@ -20,7 +20,7 @@ package org.apache.phoenix.expression.function;
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
 import org.apache.phoenix.expression.Expression;
 import org.apache.phoenix.parse.FunctionParseNode;
-import org.apache.phoenix.parse.JsonValueParseNode;
+import org.apache.phoenix.parse.JsonModifyParseNode;
 import org.apache.phoenix.schema.tuple.Tuple;
 import org.apache.phoenix.schema.types.PDataType;
 import org.apache.phoenix.schema.types.PJson;
@@ -33,16 +33,15 @@ import java.nio.ByteBuffer;
 import java.util.List;
 
 /**
- * Built-in function for JSON_MODIFY
- * JSON_MODIFY(<column_with_json/json_string>, <path> [returning <type>], 
newValue)
- * Updates the value of a property in a JSON string and returns the updated 
JSON string.
- *
+ * Built-in function for JSON_MODIFY 
JSON_MODIFY(<column_with_json/json_string>, <path> [returning
+ * <type>], newValue) Updates the value of a property in a JSON string and 
returns the updated JSON
+ * string.
  */
-@FunctionParseNode.BuiltInFunction(name = JsonModifyFunction.NAME, nodeClass = 
JsonValueParseNode.class,
-        args = {
-                @FunctionParseNode.Argument(allowedTypes = { PJson.class, 
PVarchar.class }),
-                @FunctionParseNode.Argument(allowedTypes = { PVarchar.class }) 
,
-                @FunctionParseNode.Argument(allowedTypes = { PVarchar.class 
})})
+@FunctionParseNode.BuiltInFunction(name = JsonModifyFunction.NAME,
+        nodeClass = JsonModifyParseNode.class,
+        args = { @FunctionParseNode.Argument(allowedTypes = { PJson.class, 
PVarchar.class }),
+                @FunctionParseNode.Argument(allowedTypes = { PVarchar.class }),
+                @FunctionParseNode.Argument(allowedTypes = { PVarchar.class }) 
})
 public class JsonModifyFunction extends ScalarFunction {
 
     public static final String NAME = "JSON_MODIFY";
@@ -85,8 +84,9 @@ public class JsonModifyFunction extends ScalarFunction {
             return true;
         }
 
-        String jsonPathExprStr = (String) PVarchar.INSTANCE.toObject(ptr,
-            getJSONPathExpr().getSortOrder());
+        String
+                jsonPathExprStr =
+                (String) PVarchar.INSTANCE.toObject(ptr, 
getJSONPathExpr().getSortOrder());
         if (jsonPathExprStr == null) {
             return true;
         }
@@ -95,8 +95,7 @@ public class JsonModifyFunction extends ScalarFunction {
             return false;
         }
 
-        String newVal = (String)PVarchar.INSTANCE.toObject(ptr,
-            getNewValueExpr().getSortOrder());
+        String newVal = (String) PVarchar.INSTANCE.toObject(ptr, 
getNewValueExpr().getSortOrder());
         ByteBuffer buffer = jsonDataFormat.updateValue(top, jsonPathExprStr, 
newVal);
         ptr.set(buffer.array(), buffer.arrayOffset(), buffer.limit());
         return true;
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/JsonModifyFunction.java
 
b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/JsonQueryFunction.java
similarity index 66%
copy from 
phoenix-core/src/main/java/org/apache/phoenix/expression/function/JsonModifyFunction.java
copy to 
phoenix-core/src/main/java/org/apache/phoenix/expression/function/JsonQueryFunction.java
index 37b67578af..9863439109 100644
--- 
a/phoenix-core/src/main/java/org/apache/phoenix/expression/function/JsonModifyFunction.java
+++ 
b/phoenix-core/src/main/java/org/apache/phoenix/expression/function/JsonQueryFunction.java
@@ -20,42 +20,40 @@ package org.apache.phoenix.expression.function;
 import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
 import org.apache.phoenix.expression.Expression;
 import org.apache.phoenix.parse.FunctionParseNode;
-import org.apache.phoenix.parse.JsonValueParseNode;
+import org.apache.phoenix.parse.JsonQueryParseNode;
 import org.apache.phoenix.schema.tuple.Tuple;
 import org.apache.phoenix.schema.types.PDataType;
 import org.apache.phoenix.schema.types.PJson;
+import org.apache.phoenix.schema.types.PVarbinary;
 import org.apache.phoenix.schema.types.PVarchar;
 import org.apache.phoenix.thirdparty.com.google.common.base.Preconditions;
 import org.apache.phoenix.util.json.JsonDataFormat;
 import org.apache.phoenix.util.json.JsonDataFormatFactory;
 
-import java.nio.ByteBuffer;
+import java.sql.Types;
 import java.util.List;
 
 /**
- * Built-in function for JSON_MODIFY
- * JSON_MODIFY(<column_with_json/json_string>, <path> [returning <type>], 
newValue)
- * Updates the value of a property in a JSON string and returns the updated 
JSON string.
- *
+ * Built-in function for JSON_QUERY JSON_QUERY(<column_with_json/json_string>, 
<path> [returning
+ * <type>]) Extracts an object or an array from a JSON string.
  */
-@FunctionParseNode.BuiltInFunction(name = JsonModifyFunction.NAME, nodeClass = 
JsonValueParseNode.class,
-        args = {
-                @FunctionParseNode.Argument(allowedTypes = { PJson.class, 
PVarchar.class }),
-                @FunctionParseNode.Argument(allowedTypes = { PVarchar.class }) 
,
-                @FunctionParseNode.Argument(allowedTypes = { PVarchar.class 
})})
-public class JsonModifyFunction extends ScalarFunction {
-
-    public static final String NAME = "JSON_MODIFY";
+@FunctionParseNode.BuiltInFunction(name = JsonQueryFunction.NAME,
+        nodeClass = JsonQueryParseNode.class,
+        args = { @FunctionParseNode.Argument(allowedTypes = { PJson.class, 
PVarbinary.class }),
+                @FunctionParseNode.Argument(allowedTypes = { PVarchar.class }) 
})
+public class JsonQueryFunction extends ScalarFunction {
+
+    public static final String NAME = "JSON_QUERY";
     private final JsonDataFormat
             jsonDataFormat =
             
JsonDataFormatFactory.getJsonDataFormat(JsonDataFormatFactory.DataFormat.BSON);
 
     // This is called from ExpressionType newInstance
-    public JsonModifyFunction() {
+    public JsonQueryFunction() {
 
     }
 
-    public JsonModifyFunction(List<Expression> children) {
+    public JsonQueryFunction(List<Expression> children) {
         super(children);
         Preconditions.checkNotNull(getJSONPathExpr());
     }
@@ -85,27 +83,28 @@ public class JsonModifyFunction extends ScalarFunction {
             return true;
         }
 
-        String jsonPathExprStr = (String) PVarchar.INSTANCE.toObject(ptr,
-            getJSONPathExpr().getSortOrder());
+        String
+                jsonPathExprStr =
+                (String) PVarchar.INSTANCE.toObject(ptr, 
getJSONPathExpr().getSortOrder());
         if (jsonPathExprStr == null) {
             return true;
         }
-
-        if (!getNewValueExpr().evaluate(tuple, ptr)) {
-            return false;
+        Object value = jsonDataFormat.getValue(top, jsonPathExprStr);
+        int valueType = jsonDataFormat.getValueType(top, jsonPathExprStr);
+        if (value != null) {
+            switch (valueType) {
+            case Types.ARRAY:
+            case Types.NVARCHAR:
+                ptr.set(PVarchar.INSTANCE.toBytes(value));
+                break;
+            default:
+                return false;
+            }
         }
 
-        String newVal = (String)PVarchar.INSTANCE.toObject(ptr,
-            getNewValueExpr().getSortOrder());
-        ByteBuffer buffer = jsonDataFormat.updateValue(top, jsonPathExprStr, 
newVal);
-        ptr.set(buffer.array(), buffer.arrayOffset(), buffer.limit());
         return true;
     }
 
-    private Expression getNewValueExpr() {
-        return getChildren().get(2);
-    }
-
     private Expression getColValExpr() {
         return getChildren().get(0);
     }
@@ -116,6 +115,6 @@ public class JsonModifyFunction extends ScalarFunction {
 
     @Override
     public PDataType getDataType() {
-        return PJson.INSTANCE;
+        return PVarchar.INSTANCE;
     }
 }
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/parse/JsonValueParseNode.java 
b/phoenix-core/src/main/java/org/apache/phoenix/parse/JsonModifyParseNode.java
similarity index 73%
copy from 
phoenix-core/src/main/java/org/apache/phoenix/parse/JsonValueParseNode.java
copy to 
phoenix-core/src/main/java/org/apache/phoenix/parse/JsonModifyParseNode.java
index e841873d46..596630b5fe 100644
--- 
a/phoenix-core/src/main/java/org/apache/phoenix/parse/JsonValueParseNode.java
+++ 
b/phoenix-core/src/main/java/org/apache/phoenix/parse/JsonModifyParseNode.java
@@ -19,32 +19,30 @@ package org.apache.phoenix.parse;
 
 import org.apache.phoenix.compile.StatementContext;
 import org.apache.phoenix.expression.Expression;
-import org.apache.phoenix.expression.LiteralExpression;
-import org.apache.phoenix.expression.function.JsonModifyFunction;
-import org.apache.phoenix.expression.function.JsonValueFunction;
 import org.apache.phoenix.expression.function.FunctionExpression;
-import org.apache.phoenix.schema.types.PJson;
+import org.apache.phoenix.expression.function.JsonModifyFunction;
 import org.apache.phoenix.schema.types.PDataType;
+import org.apache.phoenix.schema.types.PJson;
 
 import java.sql.SQLException;
 import java.util.List;
 
-public class JsonValueParseNode extends FunctionParseNode {
+/**
+ * ParseNode for JSON_MODIFY function.
+ */
+public class JsonModifyParseNode extends FunctionParseNode {
 
-    public JsonValueParseNode(String name, List<ParseNode> children, 
BuiltInFunctionInfo info) {
+    public JsonModifyParseNode(String name, List<ParseNode> children, 
BuiltInFunctionInfo info) {
         super(name, children, info);
     }
 
     @Override
-    public FunctionExpression create(List<Expression> children, 
StatementContext context) throws SQLException {
+    public FunctionExpression create(List<Expression> children, 
StatementContext context)
+            throws SQLException {
         PDataType dataType = children.get(0).getDataType();
         if (!dataType.isCoercibleTo(PJson.INSTANCE)) {
-            throw new SQLException(dataType + " type is unsupported for 
JSON_VALUE().");
-        }
-        if (getName().equalsIgnoreCase(JsonModifyFunction.NAME)) {
-            return new JsonModifyFunction(children);
-        } else {
-            return new JsonValueFunction(children);
+            throw new SQLException(dataType + " type is unsupported for 
JSON_MODIFY().");
         }
+        return new JsonModifyFunction(children);
     }
 }
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/parse/JsonValueParseNode.java 
b/phoenix-core/src/main/java/org/apache/phoenix/parse/JsonQueryParseNode.java
similarity index 72%
copy from 
phoenix-core/src/main/java/org/apache/phoenix/parse/JsonValueParseNode.java
copy to 
phoenix-core/src/main/java/org/apache/phoenix/parse/JsonQueryParseNode.java
index e841873d46..81093fbb5b 100644
--- 
a/phoenix-core/src/main/java/org/apache/phoenix/parse/JsonValueParseNode.java
+++ 
b/phoenix-core/src/main/java/org/apache/phoenix/parse/JsonQueryParseNode.java
@@ -19,32 +19,30 @@ package org.apache.phoenix.parse;
 
 import org.apache.phoenix.compile.StatementContext;
 import org.apache.phoenix.expression.Expression;
-import org.apache.phoenix.expression.LiteralExpression;
-import org.apache.phoenix.expression.function.JsonModifyFunction;
-import org.apache.phoenix.expression.function.JsonValueFunction;
 import org.apache.phoenix.expression.function.FunctionExpression;
-import org.apache.phoenix.schema.types.PJson;
+import org.apache.phoenix.expression.function.JsonQueryFunction;
 import org.apache.phoenix.schema.types.PDataType;
+import org.apache.phoenix.schema.types.PJson;
 
 import java.sql.SQLException;
 import java.util.List;
 
-public class JsonValueParseNode extends FunctionParseNode {
+/**
+ * ParseNode for JSON_QUERY function.
+ */
+public class JsonQueryParseNode extends FunctionParseNode {
 
-    public JsonValueParseNode(String name, List<ParseNode> children, 
BuiltInFunctionInfo info) {
+    public JsonQueryParseNode(String name, List<ParseNode> children, 
BuiltInFunctionInfo info) {
         super(name, children, info);
     }
 
     @Override
-    public FunctionExpression create(List<Expression> children, 
StatementContext context) throws SQLException {
+    public FunctionExpression create(List<Expression> children, 
StatementContext context)
+            throws SQLException {
         PDataType dataType = children.get(0).getDataType();
         if (!dataType.isCoercibleTo(PJson.INSTANCE)) {
-            throw new SQLException(dataType + " type is unsupported for 
JSON_VALUE().");
-        }
-        if (getName().equalsIgnoreCase(JsonModifyFunction.NAME)) {
-            return new JsonModifyFunction(children);
-        } else {
-            return new JsonValueFunction(children);
+            throw new SQLException(dataType + " type is unsupported for 
JSON_QUERY().");
         }
+        return new JsonQueryFunction(children);
     }
 }
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/parse/JsonValueParseNode.java 
b/phoenix-core/src/main/java/org/apache/phoenix/parse/JsonValueParseNode.java
index e841873d46..33f75fcc38 100644
--- 
a/phoenix-core/src/main/java/org/apache/phoenix/parse/JsonValueParseNode.java
+++ 
b/phoenix-core/src/main/java/org/apache/phoenix/parse/JsonValueParseNode.java
@@ -19,12 +19,10 @@ package org.apache.phoenix.parse;
 
 import org.apache.phoenix.compile.StatementContext;
 import org.apache.phoenix.expression.Expression;
-import org.apache.phoenix.expression.LiteralExpression;
-import org.apache.phoenix.expression.function.JsonModifyFunction;
-import org.apache.phoenix.expression.function.JsonValueFunction;
 import org.apache.phoenix.expression.function.FunctionExpression;
-import org.apache.phoenix.schema.types.PJson;
+import org.apache.phoenix.expression.function.JsonValueFunction;
 import org.apache.phoenix.schema.types.PDataType;
+import org.apache.phoenix.schema.types.PJson;
 
 import java.sql.SQLException;
 import java.util.List;
@@ -36,15 +34,12 @@ public class JsonValueParseNode extends FunctionParseNode {
     }
 
     @Override
-    public FunctionExpression create(List<Expression> children, 
StatementContext context) throws SQLException {
+    public FunctionExpression create(List<Expression> children, 
StatementContext context)
+            throws SQLException {
         PDataType dataType = children.get(0).getDataType();
         if (!dataType.isCoercibleTo(PJson.INSTANCE)) {
             throw new SQLException(dataType + " type is unsupported for 
JSON_VALUE().");
         }
-        if (getName().equalsIgnoreCase(JsonModifyFunction.NAME)) {
-            return new JsonModifyFunction(children);
-        } else {
-            return new JsonValueFunction(children);
-        }
+        return new JsonValueFunction(children);
     }
 }
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/parse/SelectStatement.java 
b/phoenix-core/src/main/java/org/apache/phoenix/parse/SelectStatement.java
index 436ff53d8c..53e82639f8 100644
--- a/phoenix-core/src/main/java/org/apache/phoenix/parse/SelectStatement.java
+++ b/phoenix-core/src/main/java/org/apache/phoenix/parse/SelectStatement.java
@@ -41,7 +41,7 @@ public class SelectStatement implements FilterableStatement {
     public static final SelectStatement SELECT_STAR =
             new SelectStatement(
                     null, null, false,
-                    Arrays.asList(new AliasedNode(null, new 
LiteralParseNode("PK")),new AliasedNode(null, new LiteralParseNode("COL")), new 
AliasedNode(null, new LiteralParseNode("JSONCOL")) ),
+                    Arrays.asList(),
                     null, Collections.<ParseNode>emptyList(),
                     null, Collections.<OrderByNode>emptyList(),
                     null, null, 0, false, false, 
Collections.<SelectStatement>emptyList(), new HashMap<String, UDFParseNode>(1));
diff --git 
a/phoenix-core/src/main/java/org/apache/phoenix/util/json/BsonDataFormat.java 
b/phoenix-core/src/main/java/org/apache/phoenix/util/json/BsonDataFormat.java
index 851d6c7ad1..b114240f86 100644
--- 
a/phoenix-core/src/main/java/org/apache/phoenix/util/json/BsonDataFormat.java
+++ 
b/phoenix-core/src/main/java/org/apache/phoenix/util/json/BsonDataFormat.java
@@ -33,6 +33,8 @@ import org.bson.io.ByteBufferBsonInput;
 
 import java.nio.ByteBuffer;
 import java.sql.Types;
+import java.util.List;
+import java.util.stream.Collectors;
 
 public class BsonDataFormat implements JsonDataFormat {
     @Override
@@ -65,6 +67,10 @@ public class BsonDataFormat implements JsonDataFormat {
     @Override
     public Object getValue(Object obj, String jsonPathExprStr) {
         BsonValue value = getBsonValue(jsonPathExprStr, (RawBsonDocument) obj);
+        return getValue(value);
+    }
+
+    private Object getValue(BsonValue value) {
         if (value != null) {
             switch (value.getBsonType()) {
             case INT32:
@@ -84,6 +90,10 @@ public class BsonDataFormat implements JsonDataFormat {
                 return value.asBinary().getData();
             case DATE_TIME:
                 return value.asDateTime().getValue();
+            case DOCUMENT:
+                return value.asDocument().toJson();
+            case ARRAY:
+                return readArray(value).toString();
             default:
                 return null;
             }
@@ -110,6 +120,21 @@ public class BsonDataFormat implements JsonDataFormat {
         return value;
     }
 
+    private List<Object> readArray(BsonValue value) {
+        return value.asArray().stream().map(e -> {
+            // The reason for handling string in a special way is because:
+            // Given a string array in JSON - ["hello","world"]
+            // A string array when converted to a string returns
+            // as [hello, world] - the quotes stripped
+            // This change allows to retain those quotes.
+            if (e.isString() || e.isSymbol()) {
+                return "\"" + getValue(e) + "\"";
+            } else {
+                return String.valueOf(getValue(e));
+            }
+        }).collect(Collectors.toList());
+    }
+
     private Configuration getConfiguration() {
         Configuration conf = Configuration.builder().jsonProvider(new 
BsonJsonProvider()).build();
         // This options will make us work in lax mode.


Reply via email to