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\"')");
 

Reply via email to