This is an automated email from the ASF dual-hosted git repository.
vjasani 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 8c3fdfc072 PHOENIX-7396 BSON_VALUE function to retrieve BSON field
value with given data type (#1962)
8c3fdfc072 is described below
commit 8c3fdfc072532f151efe4ff8d6cd47fb54179763
Author: Viraj Jasani <[email protected]>
AuthorDate: Wed Aug 28 16:22:06 2024 -0700
PHOENIX-7396 BSON_VALUE function to retrieve BSON field value with given
data type (#1962)
---
.../apache/phoenix/compile/ProjectionCompiler.java | 11 +-
.../BaseScannerRegionObserverConstants.java | 1 +
.../apache/phoenix/expression/ExpressionType.java | 3 +-
.../expression/function/BsonValueFunction.java | 173 ++++++++++++++
.../apache/phoenix/parse/BsonValueParseNode.java | 46 ++++
.../iterate/NonAggregateRegionScannerFactory.java | 8 +
.../java/org/apache/phoenix/end2end/Bson4IT.java | 266 +++++++++++++++++++++
.../phoenix/end2end/json/JsonFunctionsIT.java | 14 ++
8 files changed, 519 insertions(+), 3 deletions(-)
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/ProjectionCompiler.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/ProjectionCompiler.java
index 6300304c94..9690638932 100644
---
a/phoenix-core-client/src/main/java/org/apache/phoenix/compile/ProjectionCompiler.java
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/compile/ProjectionCompiler.java
@@ -47,6 +47,7 @@ import org.apache.phoenix.expression.LiteralExpression;
import org.apache.phoenix.expression.ProjectedColumnExpression;
import org.apache.phoenix.expression.SingleCellColumnExpression;
import org.apache.phoenix.expression.function.ArrayIndexFunction;
+import org.apache.phoenix.expression.function.BsonValueFunction;
import org.apache.phoenix.expression.function.JsonQueryFunction;
import org.apache.phoenix.expression.function.JsonValueFunction;
import org.apache.phoenix.expression.visitor.ExpressionVisitor;
@@ -505,11 +506,13 @@ public class ProjectionCompiler {
scanAttributes =
new String[] {
BaseScannerRegionObserverConstants.SPECIFIC_ARRAY_INDEX,
BaseScannerRegionObserverConstants.JSON_VALUE_FUNCTION,
-
BaseScannerRegionObserverConstants.JSON_QUERY_FUNCTION };
+
BaseScannerRegionObserverConstants.JSON_QUERY_FUNCTION,
+
BaseScannerRegionObserverConstants.BSON_VALUE_FUNCTION};
Map<String, Class> attributeToFunctionMap = new HashMap<String,
Class>() {{
put(scanAttributes[0], ArrayIndexFunction.class);
put(scanAttributes[1], JsonValueFunction.class);
put(scanAttributes[2], JsonQueryFunction.class);
+ put(scanAttributes[3], BsonValueFunction.class);
}};
// This map is to keep track of the positions that get swapped
with rearranging
// the functions in the serialized data to server.
@@ -808,7 +811,7 @@ public class ProjectionCompiler {
// this need not be done for group by clause with array or json.
Hence, the below check
if (!statement.isAggregate() && (ArrayIndexFunction.NAME.equals(
- node.getName()) || isJsonFunction(node)) &&
+ node.getName()) || isJsonFunction(node) ||
isBsonFunction(node)) &&
children.get(0) instanceof ProjectedColumnExpression) {
final List<KeyValueColumnExpression> indexKVs =
Lists.newArrayList();
final List<ProjectedColumnExpression> indexProjectedColumns =
Lists.newArrayList();
@@ -868,4 +871,8 @@ public class ProjectionCompiler {
return JsonValueFunction.NAME.equals(node.getName()) ||
JsonQueryFunction.NAME.equals(
node.getName());
}
+
+ private static boolean isBsonFunction(FunctionParseNode node) {
+ return BsonValueFunction.NAME.equals(node.getName());
+ }
}
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/coprocessorclient/BaseScannerRegionObserverConstants.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/coprocessorclient/BaseScannerRegionObserverConstants.java
index 194d2e1560..212592dbe2 100644
---
a/phoenix-core-client/src/main/java/org/apache/phoenix/coprocessorclient/BaseScannerRegionObserverConstants.java
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/coprocessorclient/BaseScannerRegionObserverConstants.java
@@ -94,6 +94,7 @@ public class BaseScannerRegionObserverConstants {
public static final String INDEX_FILTER_STR = "_IndexFilterStr";
public static final String JSON_VALUE_FUNCTION = "_JsonValueFunction";
public static final String JSON_QUERY_FUNCTION = "_JsonQueryFunction";
+ public static final String BSON_VALUE_FUNCTION = "_BsonValueFunction";
/*
* Attribute to denote that the index maintainer has been serialized using
its proto-buf presentation.
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/expression/ExpressionType.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/expression/ExpressionType.java
index 2b53f4c034..d71c07185a 100644
---
a/phoenix-core-client/src/main/java/org/apache/phoenix/expression/ExpressionType.java
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/expression/ExpressionType.java
@@ -198,7 +198,8 @@ public enum ExpressionType {
JsonExistsFunction(JsonExistsFunction.class),
JsonModifyFunction(JsonModifyFunction.class),
BsonConditionExpressionFunction(BsonConditionExpressionFunction.class),
- BsonUpdateExpressionFunction(BsonUpdateExpressionFunction.class);
+ BsonUpdateExpressionFunction(BsonUpdateExpressionFunction.class),
+ BsonValueFunction(BsonValueFunction.class);
ExpressionType(Class<? extends Expression> clazz) {
this.clazz = clazz;
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/expression/function/BsonValueFunction.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/expression/function/BsonValueFunction.java
new file mode 100644
index 0000000000..b7ff1088c4
--- /dev/null
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/expression/function/BsonValueFunction.java
@@ -0,0 +1,173 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.phoenix.expression.function;
+
+import java.util.Date;
+import java.util.List;
+
+import org.bson.BsonBinary;
+import org.bson.BsonBoolean;
+import org.bson.BsonDateTime;
+import org.bson.BsonNumber;
+import org.bson.BsonString;
+import org.bson.BsonValue;
+import org.bson.RawBsonDocument;
+
+import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
+import org.apache.phoenix.expression.Expression;
+import org.apache.phoenix.expression.LiteralExpression;
+import org.apache.phoenix.expression.util.bson.CommonComparisonExpressionUtils;
+import org.apache.phoenix.parse.BsonValueParseNode;
+import org.apache.phoenix.parse.FunctionParseNode;
+import org.apache.phoenix.schema.tuple.Tuple;
+import org.apache.phoenix.schema.types.PBoolean;
+import org.apache.phoenix.schema.types.PBson;
+import org.apache.phoenix.schema.types.PDataType;
+import org.apache.phoenix.schema.types.PDate;
+import org.apache.phoenix.schema.types.PDecimal;
+import org.apache.phoenix.schema.types.PDouble;
+import org.apache.phoenix.schema.types.PInteger;
+import org.apache.phoenix.schema.types.PJson;
+import org.apache.phoenix.schema.types.PLong;
+import org.apache.phoenix.schema.types.PVarbinary;
+//import org.apache.phoenix.schema.types.PVarbinaryEncoded;
+import org.apache.phoenix.schema.types.PVarchar;
+import org.apache.phoenix.thirdparty.com.google.common.base.Preconditions;
+import org.apache.phoenix.util.ByteUtil;
+
+/**
+ * BSON_VALUE function to retrieve the value of any field in BSON. This can be
used for any
+ * top-level or nested Bson fields.
+ * 1. The first argument represents BSON Object on which the function performs
scan.
+ * 2. The second argument represents the field key. The field key can
represent any top level or
+ * nested fields within the document. The caller should use "." notation for
accessing nested
+ * document elements and "[n]" notation for accessing nested array elements.
+ * Top level fields do not require any additional character.
+ * 3. The third argument represents the data type that the client expects the
value of the
+ * field to be converted to while returning the value.
+ */
[email protected](
+ name = BsonValueFunction.NAME,
+ nodeClass = BsonValueParseNode.class,
+ args = {
+ @FunctionParseNode.Argument(allowedTypes = {PJson.class, PBson.class,
PVarbinary.class}),
+ @FunctionParseNode.Argument(allowedTypes = {PVarchar.class},
isConstant = true),
+ @FunctionParseNode.Argument(allowedTypes = {PVarchar.class},
isConstant = true),
+ }
+)
+public class BsonValueFunction extends ScalarFunction {
+
+ public static final String NAME = "BSON_VALUE";
+
+ public BsonValueFunction() {
+ // no-op
+ }
+
+ public BsonValueFunction(List<Expression> children) {
+ super(children);
+ Preconditions.checkNotNull(getChildren().get(1));
+ Preconditions.checkNotNull(getChildren().get(2));
+ }
+
+ private PDataType<?> getPDataType() {
+ String dataType = (String) ((LiteralExpression)
getChildren().get(2)).getValue();
+ return PDataType.fromSqlTypeName(dataType);
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
+
+ @Override
+ public boolean evaluate(Tuple tuple, ImmutableBytesWritable ptr) {
+ if (!getChildren().get(0).evaluate(tuple, ptr)) {
+ return false;
+ }
+ if (ptr == null || ptr.getLength() == 0) {
+ return false;
+ }
+
+ Object object = PBson.INSTANCE.toObject(ptr,
getChildren().get(0).getSortOrder());
+ RawBsonDocument rawBsonDocument = (RawBsonDocument) object;
+
+ if (!getChildren().get(1).evaluate(tuple, ptr)) {
+ return false;
+ }
+ if (ptr.getLength() == 0) {
+ return false;
+ }
+
+ String documentFieldKey =
+ (String) PVarchar.INSTANCE.toObject(ptr,
getChildren().get(1).getSortOrder());
+ if (documentFieldKey == null) {
+ return false;
+ }
+
+ PDataType<?> bsonValueDataType = getPDataType();
+ BsonValue bsonValue =
+
CommonComparisonExpressionUtils.getFieldFromDocument(documentFieldKey,
rawBsonDocument);
+ if (bsonValue == null) {
+ ptr.set(ByteUtil.EMPTY_BYTE_ARRAY);
+ return true;
+ }
+ if (bsonValueDataType == PVarchar.INSTANCE) {
+ if (bsonValue instanceof BsonString) {
+ ptr.set(PVarchar.INSTANCE.toBytes(((BsonString)
bsonValue).getValue()));
+ } else if (bsonValue instanceof BsonNumber) {
+ ptr.set(PVarchar.INSTANCE.toBytes(
+ String.valueOf(((BsonNumber) bsonValue).doubleValue())));
+ } else if (bsonValue instanceof BsonBoolean) {
+ ptr.set(PVarchar.INSTANCE.toBytes(
+ String.valueOf(((BsonBoolean) bsonValue).getValue())));
+ } else if (bsonValue instanceof BsonBinary) {
+ ptr.set(PVarchar.INSTANCE.toBytes(((BsonBinary)
bsonValue).getData().toString()));
+ } else if (bsonValue instanceof BsonDateTime) {
+ ptr.set(PVarchar.INSTANCE.toBytes(
+ new Date(((BsonDateTime)
bsonValue).getValue()).toString()));
+ }
+ } else if (bsonValueDataType == PInteger.INSTANCE && bsonValue
instanceof BsonNumber) {
+ ptr.set(PInteger.INSTANCE.toBytes(((BsonNumber)
bsonValue).intValue()));
+ } else if (bsonValueDataType == PLong.INSTANCE && bsonValue instanceof
BsonNumber) {
+ ptr.set(PLong.INSTANCE.toBytes(((BsonNumber)
bsonValue).longValue()));
+ } else if (bsonValueDataType == PDouble.INSTANCE && bsonValue
instanceof BsonNumber) {
+ ptr.set(PDouble.INSTANCE.toBytes(((BsonNumber)
bsonValue).doubleValue()));
+ } else if (bsonValueDataType == PDecimal.INSTANCE && bsonValue
instanceof BsonNumber) {
+ ptr.set(PDecimal.INSTANCE.toBytes(((BsonNumber)
bsonValue).decimal128Value()));
+ } else if (bsonValueDataType == PBoolean.INSTANCE && bsonValue
instanceof BsonBoolean) {
+ ptr.set(PBoolean.INSTANCE.toBytes(((BsonBoolean)
bsonValue).getValue()));
+ } else if (bsonValueDataType == PVarbinary.INSTANCE && bsonValue
instanceof BsonBinary) {
+ ptr.set(PVarbinary.INSTANCE.toBytes(((BsonBinary)
bsonValue).getData()));
+// TODO : uncomment after PHOENIX-7357
+// } else if (bsonValueDataType == PVarbinaryEncoded.INSTANCE
+// && bsonValue instanceof BsonBinary) {
+// ptr.set(PVarbinaryEncoded.INSTANCE.toBytes(((BsonBinary)
bsonValue).getData()));
+ } else if (bsonValueDataType == PDate.INSTANCE && bsonValue instanceof
BsonDateTime) {
+ ptr.set(PDate.INSTANCE.toBytes(new Date(((BsonDateTime)
bsonValue).getValue())));
+ } else {
+ throw new IllegalArgumentException(
+ "The function data type does not match with actual data type");
+ }
+ return true;
+ }
+
+ @Override
+ public PDataType<?> getDataType() {
+ return getPDataType();
+ }
+}
diff --git
a/phoenix-core-client/src/main/java/org/apache/phoenix/parse/BsonValueParseNode.java
b/phoenix-core-client/src/main/java/org/apache/phoenix/parse/BsonValueParseNode.java
new file mode 100644
index 0000000000..9be1e51fa0
--- /dev/null
+++
b/phoenix-core-client/src/main/java/org/apache/phoenix/parse/BsonValueParseNode.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.phoenix.parse;
+
+import java.sql.SQLException;
+import java.util.List;
+
+import org.apache.phoenix.compile.StatementContext;
+import org.apache.phoenix.expression.Expression;
+import org.apache.phoenix.expression.function.BsonValueFunction;
+import org.apache.phoenix.expression.function.FunctionExpression;
+import org.apache.phoenix.schema.types.PBson;
+import org.apache.phoenix.schema.types.PDataType;
+import org.apache.phoenix.schema.types.PJson;
+
+public class BsonValueParseNode extends FunctionParseNode {
+
+ public BsonValueParseNode(String name, List<ParseNode> children,
BuiltInFunctionInfo info) {
+ super(name, children, info);
+ }
+
+ @Override
+ public FunctionExpression create(List<Expression> children,
StatementContext context)
+ throws SQLException {
+ PDataType<?> dataType = children.get(0).getDataType();
+ if (!dataType.isCoercibleTo(PJson.INSTANCE) &&
!dataType.isCoercibleTo(PBson.INSTANCE)) {
+ throw new SQLException(dataType + " type is unsupported for
BSON_VALUE().");
+ }
+ return new BsonValueFunction(children);
+ }
+}
diff --git
a/phoenix-core-server/src/main/java/org/apache/phoenix/iterate/NonAggregateRegionScannerFactory.java
b/phoenix-core-server/src/main/java/org/apache/phoenix/iterate/NonAggregateRegionScannerFactory.java
index 52cfad9104..72b7c7e5d5 100644
---
a/phoenix-core-server/src/main/java/org/apache/phoenix/iterate/NonAggregateRegionScannerFactory.java
+++
b/phoenix-core-server/src/main/java/org/apache/phoenix/iterate/NonAggregateRegionScannerFactory.java
@@ -56,6 +56,7 @@ import org.apache.phoenix.expression.KeyValueColumnExpression;
import org.apache.phoenix.expression.OrderByExpression;
import org.apache.phoenix.expression.SingleCellColumnExpression;
import org.apache.phoenix.expression.function.ArrayIndexFunction;
+import org.apache.phoenix.expression.function.BsonValueFunction;
import org.apache.phoenix.expression.function.JsonQueryFunction;
import org.apache.phoenix.expression.function.JsonValueFunction;
import org.apache.phoenix.expression.function.ScalarFunction;
@@ -231,6 +232,11 @@ public class NonAggregateRegionScannerFactory extends
RegionScannerFactory {
deserializeServerParsedPositionalExpressionInfoFromScan(scan,
BaseScannerRegionObserverConstants.JSON_VALUE_FUNCTION, serverParsedKVRefs);
}
+ if
(scan.getAttribute(BaseScannerRegionObserverConstants.BSON_VALUE_FUNCTION) !=
null) {
+ serverParsedJsonValueFuncRefs =
+ deserializeServerParsedPositionalExpressionInfoFromScan(scan,
+ BaseScannerRegionObserverConstants.BSON_VALUE_FUNCTION,
serverParsedKVRefs);
+ }
if (serverParsedJsonValueFuncRefs != null) {
Collections.addAll(resultList, serverParsedJsonValueFuncRefs);
}
@@ -325,6 +331,8 @@ public class NonAggregateRegionScannerFactory extends
RegionScannerFactory {
func = new JsonValueFunction();
} else if
(scanAttribute.equals(BaseScannerRegionObserverConstants.JSON_QUERY_FUNCTION))
{
func = new JsonQueryFunction();
+ } else if
(scanAttribute.equals(BaseScannerRegionObserverConstants.BSON_VALUE_FUNCTION)) {
+ func = new BsonValueFunction();
}
if (func != null) {
func.readFields(input);
diff --git a/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson4IT.java
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson4IT.java
new file mode 100644
index 0000000000..dfa8fa3c4d
--- /dev/null
+++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson4IT.java
@@ -0,0 +1,266 @@
+/*
+ * 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 java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.nio.charset.Charset;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Properties;
+
+import org.bson.BsonArray;
+import org.bson.BsonBinary;
+import org.bson.BsonDocument;
+import org.bson.BsonNull;
+import org.bson.BsonString;
+import org.bson.RawBsonDocument;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.phoenix.compile.ExplainPlan;
+import org.apache.phoenix.compile.ExplainPlanAttributes;
+import org.apache.phoenix.jdbc.PhoenixPreparedStatement;
+import org.apache.phoenix.thirdparty.com.google.common.base.Preconditions;
+import org.apache.phoenix.util.PropertiesUtil;
+
+import static org.apache.phoenix.util.TestUtil.TEST_PROPERTIES;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests for BSON.
+ */
+@Category(ParallelStatsDisabledTest.class)
+@RunWith(Parameterized.class)
+public class Bson4IT extends ParallelStatsDisabledIT {
+
+ private final boolean columnEncoded;
+ private final boolean coveredIndex;
+
+ public Bson4IT(boolean columnEncoded, boolean coveredIndex) {
+ this.columnEncoded = columnEncoded;
+ this.coveredIndex = coveredIndex;
+ }
+
+ @Parameterized.Parameters(name =
+ "Bson4IT_columnEncoded={0}, coveredIndex={1}")
+ public static synchronized Collection<Object[]> data() {
+ return Arrays.asList(
+ new Object[][] {
+ {false, false},
+ {false, true},
+ {true, false},
+ {true, true}
+ });
+ }
+
+ private static String getJsonString(String jsonFilePath) throws IOException {
+ URL fileUrl = Bson4IT.class.getClassLoader().getResource(jsonFilePath);
+ Preconditions.checkArgument(fileUrl != null, "File path " + jsonFilePath +
" seems invalid");
+ return FileUtils.readFileToString(new File(fileUrl.getFile()),
Charset.defaultCharset());
+ }
+
+ @Test
+ public void testBsonValueFunction() throws Exception {
+ Properties props = PropertiesUtil.deepCopy(TEST_PROPERTIES);
+ String tableName = generateUniqueName();
+ String indexName1 = "IDX1_" + tableName;
+ String indexName2 = "IDX2_" + tableName;
+ try (Connection conn = DriverManager.getConnection(getUrl(), props)) {
+ String ddl = "CREATE TABLE " + tableName
+ + " (PK1 VARCHAR NOT NULL, C1 VARCHAR, COL BSON"
+ + " CONSTRAINT pk PRIMARY KEY(PK1)) "
+ + (this.columnEncoded ? "" : "COLUMN_ENCODED_BYTES=0");
+
+ final String indexDdl1;
+ if (!this.coveredIndex) {
+ indexDdl1 = "CREATE UNCOVERED INDEX " + indexName1 + " ON " + tableName
+ + "(BSON_VALUE(COL, 'rather[3].outline.clock', 'VARCHAR')) WHERE "
+ + "BSON_VALUE(COL, 'rather[3].outline.clock', 'VARCHAR') IS NOT
NULL";
+ } else {
+ indexDdl1 = "CREATE INDEX " + indexName1 + " ON " + tableName
+ + "(BSON_VALUE(COL, 'rather[3].outline.clock', 'VARCHAR'))
INCLUDE(COL) WHERE "
+ + "BSON_VALUE(COL, 'rather[3].outline.clock', 'VARCHAR') IS NOT
NULL";
+ }
+
+ final String indexDdl2;
+ if (!this.coveredIndex) {
+ indexDdl2 = "CREATE UNCOVERED INDEX " + indexName2 + " ON " + tableName
+ + "(BSON_VALUE(COL, 'result[1].location.coordinates.longitude',
'DOUBLE')) WHERE "
+ + "BSON_VALUE(COL, 'result[1].location.coordinates.longitude',
'DOUBLE') IS NOT NULL";
+ } else {
+ indexDdl2 = "CREATE INDEX " + indexName2 + " ON " + tableName
+ + "(BSON_VALUE(COL, 'result[1].location.coordinates.longitude',
'DOUBLE')) "
+ + "INCLUDE(COL) WHERE "
+ + "BSON_VALUE(COL, 'result[1].location.coordinates.longitude',
'DOUBLE') IS NOT NULL";
+ }
+
+ conn.createStatement().execute(ddl);
+ conn.createStatement().execute(indexDdl1);
+ conn.createStatement().execute(indexDdl2);
+
+ String sample1 = getJsonString("json/sample_01.json");
+ String sample2 = getJsonString("json/sample_02.json");
+ String sample3 = getJsonString("json/sample_03.json");
+ BsonDocument bsonDocument1 = RawBsonDocument.parse(sample1);
+ BsonDocument bsonDocument2 = RawBsonDocument.parse(sample2);
+ BsonDocument bsonDocument3 = RawBsonDocument.parse(sample3);
+
+ PreparedStatement stmt =
+ conn.prepareStatement("UPSERT INTO " + tableName + " VALUES
(?,?,?)");
+ stmt.setString(1, "pk0001");
+ stmt.setString(2, "0002");
+ stmt.setObject(3, bsonDocument1);
+ stmt.executeUpdate();
+
+ stmt.setString(1, "pk1010");
+ stmt.setString(2, "1010");
+ stmt.setObject(3, bsonDocument2);
+ stmt.executeUpdate();
+
+ stmt.setString(1, "pk1011");
+ stmt.setString(2, "1011");
+ stmt.setObject(3, bsonDocument3);
+ stmt.executeUpdate();
+
+ conn.commit();
+
+ ResultSet rs = conn.createStatement().executeQuery("SELECT count(*) FROM
" + tableName);
+ assertTrue(rs.next());
+ assertEquals(3, rs.getInt(1));
+
+ rs = conn.createStatement().executeQuery("SELECT count(*) FROM " +
indexName1);
+ assertTrue(rs.next());
+ assertEquals(1, rs.getInt(1));
+
+ rs = conn.createStatement().executeQuery("SELECT count(*) FROM " +
indexName2);
+ assertTrue(rs.next());
+ assertEquals(1, rs.getInt(1));
+
+ PreparedStatement ps = conn.prepareStatement("SELECT PK1, COL FROM " +
tableName
+ + " WHERE BSON_VALUE(COL,
'result[1].location.coordinates.longitude', 'DOUBLE') = ?");
+ ps.setDouble(1, 52.3736);
+
+ rs = ps.executeQuery();
+
+ assertTrue(rs.next());
+ assertEquals("pk1011", rs.getString(1));
+ BsonDocument actualDoc = (BsonDocument) rs.getObject(2);
+ assertEquals(bsonDocument3, actualDoc);
+
+ assertFalse(rs.next());
+
+ validateIndexUsed(ps, indexName2);
+
+ ps = conn.prepareStatement("SELECT PK1, COL FROM " + tableName
+ + " WHERE BSON_VALUE(COL, 'rather[3].outline.clock', 'VARCHAR') =
?");
+ ps.setString(1, "personal");
+
+ rs = ps.executeQuery();
+
+ assertTrue(rs.next());
+ assertEquals("pk1010", rs.getString(1));
+ actualDoc = (BsonDocument) rs.getObject(2);
+ assertEquals(bsonDocument2, actualDoc);
+
+ assertFalse(rs.next());
+
+ validateIndexUsed(ps, indexName1);
+
+ BsonDocument updateExp = new BsonDocument()
+ .append("$ADD", new BsonDocument()
+ .append("new_samples",
+ new BsonDocument().append("$set",
+ new BsonArray(Arrays.asList(
+ new
BsonBinary(Bytes.toBytes("Sample10")),
+ new
BsonBinary(Bytes.toBytes("Sample12")),
+ new
BsonBinary(Bytes.toBytes("Sample13")),
+ new
BsonBinary(Bytes.toBytes("Sample14"))
+ )))))
+ .append("$DELETE_FROM_SET", new BsonDocument()
+ .append("new_samples",
+ new BsonDocument().append("$set",
+ new BsonArray(Arrays.asList(
+ new
BsonBinary(Bytes.toBytes("Sample02")),
+ new
BsonBinary(Bytes.toBytes("Sample03"))
+ )))))
+ .append("$SET", new BsonDocument()
+ .append("rather[3].outline.clock", new
BsonString("personal2")))
+ .append("$UNSET", new BsonDocument()
+ .append("rather[3].outline.halfway.so[2][2]", new
BsonNull()));
+
+ String conditionExpression =
+ "field_not_exists(newrecord) AND
field_exists(rather[3].outline.halfway.so[2][2])";
+
+ BsonDocument conditionDoc = new BsonDocument();
+ conditionDoc.put("$EXPR", new BsonString(conditionExpression));
+ conditionDoc.put("$VAL", new BsonDocument());
+
+ stmt = conn.prepareStatement("UPSERT INTO " + tableName
+ + " VALUES (?) ON DUPLICATE KEY UPDATE COL = CASE WHEN"
+ + " BSON_CONDITION_EXPRESSION(COL, '" + conditionDoc.toJson() +
"')"
+ + " THEN BSON_UPDATE_EXPRESSION(COL, '" + updateExp + "') ELSE
COL END");
+
+ stmt.setString(1, "pk1010");
+ stmt.executeUpdate();
+
+ conn.commit();
+
+ ps = conn.prepareStatement("SELECT PK1, COL FROM " + tableName
+ + " WHERE BSON_VALUE(COL, 'rather[3].outline.clock', 'VARCHAR') =
?");
+ ps.setString(1, "personal");
+
+ rs = ps.executeQuery();
+ assertFalse(rs.next());
+
+ validateIndexUsed(ps, indexName1);
+
+ ps = conn.prepareStatement("SELECT PK1, COL FROM " + tableName
+ + " WHERE BSON_VALUE(COL,
'result[1].location.coordinates.longitude', 'DOUBLE') = ?");
+ ps.setDouble(1, 52.37);
+
+ rs = ps.executeQuery();
+ assertFalse(rs.next());
+
+ validateIndexUsed(ps, indexName2);
+ }
+ }
+
+ private static void validateIndexUsed(PreparedStatement ps, String indexName)
+ throws SQLException {
+ ExplainPlan plan =
ps.unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan();
+ ExplainPlanAttributes explainPlanAttributes =
plan.getPlanStepsAsAttributes();
+ assertEquals(indexName, explainPlanAttributes.getTableName());
+ assertEquals("PARALLEL 1-WAY",
explainPlanAttributes.getIteratorTypeAndScanSize());
+ assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType());
+ }
+
+}
\ No newline at end of file
diff --git
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/json/JsonFunctionsIT.java
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/json/JsonFunctionsIT.java
index 05458cbdde..8f74ed304b 100644
---
a/phoenix-core/src/it/java/org/apache/phoenix/end2end/json/JsonFunctionsIT.java
+++
b/phoenix-core/src/it/java/org/apache/phoenix/end2end/json/JsonFunctionsIT.java
@@ -206,6 +206,20 @@ public class JsonFunctionsIT extends
ParallelStatsDisabledIT {
assertTrue(rs.next());
assertEquals("Bristol", rs.getString(1));
+ query =
+ "SELECT BSON_VALUE(jsoncol, 'info.address.town', 'VARCHAR')
FROM " + tableName
+ + " WHERE BSON_VALUE(jsoncol, 'infox.type', 'VARCHAR') IS
NULL";
+ rs = conn.createStatement().executeQuery(query);
+ assertTrue(rs.next());
+ assertEquals("Bristol", rs.getString(1));
+
+ query =
+ "SELECT BSON_VALUE(jsoncol, 'info.type', 'DOUBLE') FROM " +
tableName
+ + " WHERE BSON_VALUE(jsoncol, 'info.type', 'VARCHAR') IS
NOT NULL";
+ rs = conn.createStatement().executeQuery(query);
+ assertTrue(rs.next());
+ assertEquals(1.0, rs.getDouble(1), 0.0);
+
conn.createStatement().execute("UPSERT INTO " + tableName + " (pk,
col) VALUES(1,2" +
") ON DUPLICATE KEY UPDATE jsoncol = JSON_MODIFY(jsoncol,
'$.info.tags[1]', '\"alto1\"')");