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.