This is an automated email from the ASF dual-hosted git repository. apurtell pushed a commit to branch PHOENIX-7562-feature in repository https://gitbox.apache.org/repos/asf/phoenix.git
commit 098c7928d4770dbab491b5984732a555fb52fa8f Author: Viraj Jasani <vjas...@apache.org> AuthorDate: Wed May 7 18:14:12 2025 -0700 PHOENIX-7517 Allow BSON as primary key column (#2141) --- .../org/apache/phoenix/schema/types/PBson.java | 10 -- .../org/apache/phoenix/schema/types/PVarchar.java | 2 +- .../java/org/apache/phoenix/end2end/Bson4IT.java | 148 ++++++++++++++++++++- .../apache/phoenix/schema/types/PDataTypeTest.java | 2 +- .../org/apache/phoenix/util/IndexUtilTest.java | 2 + 5 files changed, 145 insertions(+), 19 deletions(-) diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PBson.java b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PBson.java index c1c3274bb2..1beacefa03 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PBson.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PBson.java @@ -42,16 +42,6 @@ public class PBson extends PVarbinary { super("BSON", PDataType.BSON_TYPE, byte[].class, null, 49); } - @Override - public boolean canBePrimaryKey() { - return false; - } - - @Override - public boolean isComparisonSupported() { - return false; - } - @Override public int toBytes(Object object, byte[] bytes, int offset) { if (object == null) { diff --git a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVarchar.java b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVarchar.java index 911edf3f4e..451f892ea0 100644 --- a/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVarchar.java +++ b/phoenix-core-client/src/main/java/org/apache/phoenix/schema/types/PVarchar.java @@ -101,7 +101,7 @@ public class PVarchar extends PDataType<String> { @Override public boolean isCoercibleTo(PDataType targetType) { return equalsAny(targetType, this, PChar.INSTANCE, PVarbinary.INSTANCE, PBinary.INSTANCE, - PVarbinaryEncoded.INSTANCE); + PVarbinaryEncoded.INSTANCE, PBson.INSTANCE); } @Override 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 index 2296fad4ae..26b2172875 100644 --- a/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson4IT.java +++ b/phoenix-core/src/it/java/org/apache/phoenix/end2end/Bson4IT.java @@ -27,11 +27,13 @@ import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.sql.Statement; import java.util.Arrays; import java.util.Collection; import java.util.Properties; import org.apache.hadoop.hbase.util.Pair; +import org.apache.phoenix.jdbc.PhoenixStatement; import org.apache.phoenix.schema.types.PDouble; import org.bson.BsonArray; import org.bson.BsonBinary; @@ -167,7 +169,7 @@ public class Bson4IT extends ParallelStatsDisabledIT { assertFalse(rs.next()); - validateIndexUsed(ps, indexName2); + validateExplainPlan(ps, indexName2, "RANGE SCAN "); ps = conn.prepareStatement("SELECT PK1, COL FROM " + tableName + " WHERE BSON_VALUE(COL, 'rather[3].outline.clock', 'VARCHAR') = ?"); @@ -182,7 +184,7 @@ public class Bson4IT extends ParallelStatsDisabledIT { assertFalse(rs.next()); - validateIndexUsed(ps, indexName1); + validateExplainPlan(ps, indexName1, "RANGE SCAN "); BsonDocument updateExp = new BsonDocument() .append("$ADD", new BsonDocument() @@ -230,7 +232,7 @@ public class Bson4IT extends ParallelStatsDisabledIT { rs = ps.executeQuery(); assertFalse(rs.next()); - validateIndexUsed(ps, indexName1); + validateExplainPlan(ps, indexName1, "RANGE SCAN "); ps = conn.prepareStatement("SELECT PK1, COL FROM " + tableName + " WHERE BSON_VALUE(COL, 'result[1].location.coordinates.longitude', 'DOUBLE') = ?"); @@ -239,7 +241,7 @@ public class Bson4IT extends ParallelStatsDisabledIT { rs = ps.executeQuery(); assertFalse(rs.next()); - validateIndexUsed(ps, indexName2); + validateExplainPlan(ps, indexName2, "RANGE SCAN "); } } @@ -426,6 +428,127 @@ public class Bson4IT extends ParallelStatsDisabledIT { } } + @Test + public void testBsonPk() 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 " + tableName + + " (PK1 BSON NOT NULL, C1 VARCHAR" + + " CONSTRAINT pk PRIMARY KEY(PK1))"; + conn.createStatement().execute(ddl); + + 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); + + upsertRowsWithBsonPkCol(conn, tableName, bsonDocument1, bsonDocument2, bsonDocument3); + + String conditionExpression = + "press = :press AND track[0].shot[2][0].city.standard[5] = :softly"; + + BsonDocument conditionDoc = new BsonDocument(); + conditionDoc.put("$EXPR", new BsonString(conditionExpression)); + conditionDoc.put("$VAL", new BsonDocument() + .append(":press", new BsonString("beat")) + .append(":softly", new BsonString("softly"))); + + String query = "SELECT * FROM " + tableName + + " WHERE PK1 = ?"; + PreparedStatement pst = conn.prepareStatement(query); + pst.setObject(1, bsonDocument1); + ResultSet rs = pst.executeQuery(query); + + assertTrue(rs.next()); + assertEquals("0002", rs.getString(2)); + BsonDocument document1 = (BsonDocument) rs.getObject(1); + assertEquals(bsonDocument1, document1); + + assertFalse(rs.next()); + + validateExplainPlan(pst, tableName, "POINT LOOKUP ON 1 KEY "); + + query = "SELECT * FROM " + tableName + + " WHERE PK1 = CAST('" + sample2 + "' AS BSON)"; + Statement stmt = conn.createStatement(); + rs = stmt.executeQuery(query); + + assertTrue(rs.next()); + assertEquals("1010", rs.getString(2)); + BsonDocument document2 = (BsonDocument) rs.getObject(1); + assertEquals(bsonDocument2, document2); + + assertFalse(rs.next()); + + // TODO : Fix this separately, using CAST with PK column results into full table scan due + // to bug. + // validateExplainPlan(stmt, query, tableName, "POINT LOOKUP ON 1 KEY "); + + query = "SELECT * FROM " + tableName + + " WHERE PK1 != CAST('" + sample1 + "' AS BSON)"; + stmt = conn.createStatement(); + rs = stmt.executeQuery(query); + + assertTrue(rs.next()); + assertEquals("1011", rs.getString(2)); + document2 = (BsonDocument) rs.getObject(1); + assertEquals(bsonDocument3, document2); + + assertTrue(rs.next()); + assertEquals("1010", rs.getString(2)); + BsonDocument document3 = (BsonDocument) rs.getObject(1); + assertEquals(bsonDocument2, document3); + + assertFalse(rs.next()); + + validateExplainPlan(stmt, query, tableName, "FULL SCAN "); + + query = "SELECT * FROM " + tableName; + rs = conn.createStatement().executeQuery(query); + + assertTrue(rs.next()); + assertEquals("0002", rs.getString(2)); + document1 = (BsonDocument) rs.getObject(1); + assertEquals(bsonDocument1, document1); + + assertTrue(rs.next()); + assertEquals("1011", rs.getString(2)); + document2 = (BsonDocument) rs.getObject(1); + assertEquals(bsonDocument3, document2); + + assertTrue(rs.next()); + assertEquals("1010", rs.getString(2)); + document3 = (BsonDocument) rs.getObject(1); + assertEquals(bsonDocument2, document3); + + assertFalse(rs.next()); + } + } + + private static void upsertRowsWithBsonPkCol(Connection conn, String tableName, + BsonDocument bsonDocument1, + BsonDocument bsonDocument2, + BsonDocument bsonDocument3) + throws SQLException { + PreparedStatement stmt = + conn.prepareStatement("UPSERT INTO " + tableName + " VALUES (?,?)"); + stmt.setString(2, "0002"); + stmt.setObject(1, bsonDocument1); + stmt.executeUpdate(); + + stmt.setString(2, "1010"); + stmt.setObject(1, bsonDocument2); + stmt.executeUpdate(); + + stmt.setString(2, "1011"); + stmt.setObject(1, bsonDocument3); + stmt.executeUpdate(); + } + private static void upsertRows(Connection conn, String tableName, BsonDocument bsonDocument1, BsonDocument bsonDocument2, BsonDocument bsonDocument3) throws SQLException { @@ -495,13 +618,24 @@ public class Bson4IT extends ParallelStatsDisabledIT { assertEquals(RawBsonDocument.parse(getJsonString(jsonPath)), resultSet.getObject(3)); } - private static void validateIndexUsed(PreparedStatement ps, String indexName) + private static void validateExplainPlan(PreparedStatement ps, String tableName, String scanType) throws SQLException { ExplainPlan plan = ps.unwrap(PhoenixPreparedStatement.class).optimizeQuery().getExplainPlan(); + validatePlan(tableName, scanType, plan); + } + + private static void validateExplainPlan(Statement stmt, String query, String tableName, + String scanType) + throws SQLException { + ExplainPlan plan = stmt.unwrap(PhoenixStatement.class).optimizeQuery(query).getExplainPlan(); + validatePlan(tableName, scanType, plan); + } + + private static void validatePlan(String tableName, String scanType, ExplainPlan plan) { ExplainPlanAttributes explainPlanAttributes = plan.getPlanStepsAsAttributes(); - assertEquals(indexName, explainPlanAttributes.getTableName()); + assertEquals(tableName, explainPlanAttributes.getTableName()); assertEquals("PARALLEL 1-WAY", explainPlanAttributes.getIteratorTypeAndScanSize()); - assertEquals("RANGE SCAN ", explainPlanAttributes.getExplainScanType()); + assertEquals(scanType, explainPlanAttributes.getExplainScanType()); } } \ No newline at end of file diff --git a/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PDataTypeTest.java b/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PDataTypeTest.java index 31b9505d7e..66d8bb7cf1 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PDataTypeTest.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/schema/types/PDataTypeTest.java @@ -1790,7 +1790,7 @@ public class PDataTypeTest { + "VARBINARY=[BINARY, VARBINARY, VARBINARY_ENCODED], " + "VARBINARY ARRAY=[BINARY ARRAY, VARBINARY ARRAY], " + "VARBINARY_ENCODED=[BINARY, VARBINARY, VARBINARY_ENCODED], " - + "VARCHAR=[BINARY, CHAR, VARBINARY, VARBINARY_ENCODED, VARCHAR], " + + "VARCHAR=[BINARY, BSON, CHAR, VARBINARY, VARBINARY_ENCODED, VARCHAR], " + "VARCHAR ARRAY=[BINARY ARRAY, CHAR ARRAY, VARBINARY ARRAY, VARCHAR ARRAY]}", coercibleToMap.toString()); } diff --git a/phoenix-core/src/test/java/org/apache/phoenix/util/IndexUtilTest.java b/phoenix-core/src/test/java/org/apache/phoenix/util/IndexUtilTest.java index 77a3b7b325..fb0cde51bf 100644 --- a/phoenix-core/src/test/java/org/apache/phoenix/util/IndexUtilTest.java +++ b/phoenix-core/src/test/java/org/apache/phoenix/util/IndexUtilTest.java @@ -38,6 +38,7 @@ public class IndexUtilTest { + "BINARY ARRAY=BINARY ARRAY, " + "BOOLEAN=BOOLEAN, " + "BOOLEAN ARRAY=BOOLEAN ARRAY, " + + "BSON=BSON, " + "CHAR=CHAR, " + "CHAR ARRAY=CHAR ARRAY, " + "DATE=DATE, " @@ -92,6 +93,7 @@ public class IndexUtilTest { + "BINARY ARRAY=BINARY ARRAY, " + "BOOLEAN=DECIMAL, " + "BOOLEAN ARRAY=BOOLEAN ARRAY, " + + "BSON=BSON, " + "CHAR=VARCHAR, " + "CHAR ARRAY=CHAR ARRAY, " + "DATE=DECIMAL, "