This is an automated email from the ASF dual-hosted git repository.
vjasani pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/phoenix-adapters.git
The following commit(s) were added to refs/heads/main by this push:
new 7259bed Use uncovered index
7259bed is described below
commit 7259bedafb78af36d5770d49a1c819d31ed0037c
Author: Palash Chauhan <[email protected]>
AuthorDate: Wed Nov 12 21:44:32 2025 -0800
Use uncovered index
---
.../phoenix/ddb/service/CreateTableService.java | 4 +-
.../apache/phoenix/ddb/ConditionalPutItemIT.java | 7 -
.../java/org/apache/phoenix/ddb/PutItemIT.java | 4 -
.../java/org/apache/phoenix/ddb/QueryIndex4IT.java | 352 +++++++++++++++++++++
.../java/org/apache/phoenix/ddb/ScanIndex3IT.java | 330 +++++++++++++++++++
.../java/org/apache/phoenix/ddb/TestUtils.java | 2 +-
6 files changed, 685 insertions(+), 14 deletions(-)
diff --git
a/phoenix-ddb-rest/src/main/java/org/apache/phoenix/ddb/service/CreateTableService.java
b/phoenix-ddb-rest/src/main/java/org/apache/phoenix/ddb/service/CreateTableService.java
index b6c4bcc..290f558 100644
---
a/phoenix-ddb-rest/src/main/java/org/apache/phoenix/ddb/service/CreateTableService.java
+++
b/phoenix-ddb-rest/src/main/java/org/apache/phoenix/ddb/service/CreateTableService.java
@@ -158,9 +158,9 @@ public class CreateTableService {
}
final String finalIndexName =
PhoenixUtils.getInternalIndexName(tableName, indexName);
- indexDDLs.add("CREATE INDEX IF NOT EXISTS \"" + finalIndexName + "\"
ON "
+ indexDDLs.add("CREATE UNCOVERED INDEX IF NOT EXISTS \"" +
finalIndexName + "\" ON "
+ PhoenixUtils.getFullTableName(tableName, true) + " (" +
indexOn
- + ") INCLUDE (COL) WHERE " + indexHashKey + " IS NOT " + "NULL
" + ((indexSortKey
+ + ") WHERE " + indexHashKey + " IS NOT " + "NULL " +
((indexSortKey
!= null) ? " AND " + indexSortKey + " IS NOT " + "NULL " : "")
+ (isAsync ?
" ASYNC " :
"") + TableOptionsConfig.getIndexOptions());
diff --git
a/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/ConditionalPutItemIT.java
b/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/ConditionalPutItemIT.java
index 5c24c14..04d48a1 100644
---
a/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/ConditionalPutItemIT.java
+++
b/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/ConditionalPutItemIT.java
@@ -264,10 +264,6 @@ public class ConditionalPutItemIT {
Assert.assertTrue(rs.next());
Assert.assertEquals(rs.getLong(1),
Long.parseLong(item1.get("attr_1").n()));
Assert.assertEquals(rs.getString(2), item1.get("attr_0").s());
- Map<String, AttributeValue> indexRowItem =
- BsonDocumentToDdbAttributes.getFullItem((BsonDocument)
rs.getObject(3));
- Assert.assertEquals(indexRowItem, item1);
- Assert.assertEquals(indexRowItem, dynamoItem);
}
}
@@ -634,9 +630,6 @@ public class ConditionalPutItemIT {
Assert.assertTrue(rs.next());
Assert.assertEquals(rs.getDouble(1),
Double.parseDouble(item2.get("Id2").n()), 0.0);
Assert.assertEquals(rs.getString(2), item2.get("attr_0").s());
- Map<String, AttributeValue> indexRowItem =
- BsonDocumentToDdbAttributes.getFullItem((BsonDocument)
rs.getObject(3));
- Assert.assertEquals(indexRowItem, item2);
//TODO: uncomment when we have utility to compare sets
//Assert.assertEquals(dynamoItem, phoenixItem);
diff --git
a/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/PutItemIT.java
b/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/PutItemIT.java
index 1dedb24..bdfc408 100644
--- a/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/PutItemIT.java
+++ b/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/PutItemIT.java
@@ -256,10 +256,6 @@ public class PutItemIT {
Assert.assertEquals(rs.getString(1), item.get("Title").s());
Assert.assertEquals(rs.getString(2), item.get("attr_0").s());
Assert.assertEquals(rs.getDouble(3),
Double.parseDouble(item.get("attr_1").n()), 0.0);
- bsonDoc = (BsonDocument) rs.getObject(4);
- Map<String, AttributeValue> indexItem =
- BsonDocumentToDdbAttributes.getFullItem(bsonDoc);
- Assert.assertEquals(item, indexItem);
//TODO: uncomment when we have utility to compare sets
//Assert.assertEquals(dynamoItem, indexItem);
diff --git
a/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/QueryIndex4IT.java
b/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/QueryIndex4IT.java
new file mode 100644
index 0000000..2073a99
--- /dev/null
+++ b/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/QueryIndex4IT.java
@@ -0,0 +1,352 @@
+/*
+ * 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.ddb;
+
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.HBaseConfiguration;
+import org.apache.hadoop.hbase.HBaseTestingUtility;
+import org.apache.phoenix.ddb.rest.RESTServer;
+import org.apache.phoenix.end2end.ServerMetadataCacheTestImpl;
+import org.apache.phoenix.jdbc.PhoenixDriver;
+import org.apache.phoenix.util.PhoenixRuntime;
+import org.apache.phoenix.util.ServerUtil;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
+import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
+import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest;
+import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
+import software.amazon.awssdk.services.dynamodb.model.QueryRequest;
+import software.amazon.awssdk.services.dynamodb.model.QueryResponse;
+import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
+
+import static org.apache.phoenix.query.BaseTest.setUpConfigForMiniCluster;
+
+public class QueryIndex4IT {
+
+ private static final String TABLE_NAME = "QueryIndex4IT_Table";
+ private static final String INDEX_NAME = "gsi_QueryIndex4IT";
+ private static final int NUM_RECORDS = 20000;
+
+ private static DynamoDbClient dynamoDbClient;
+ private static DynamoDbClient phoenixDBClientV2;
+ private static String url;
+ private static HBaseTestingUtility utility = null;
+ private static String tmpDir;
+ private static RESTServer restServer = null;
+
+ @Rule
+ public final TestName testName = new TestName();
+
+ @BeforeClass
+ public static void initialize() throws Exception {
+ tmpDir = System.getProperty("java.io.tmpdir");
+ LocalDynamoDbTestBase.localDynamoDb().start();
+ Configuration conf = HBaseConfiguration.create();
+ utility = new HBaseTestingUtility(conf);
+ setUpConfigForMiniCluster(conf);
+
+ utility.startMiniCluster();
+ String zkQuorum = "localhost:" +
utility.getZkCluster().getClientPort();
+ url = PhoenixRuntime.JDBC_PROTOCOL +
PhoenixRuntime.JDBC_PROTOCOL_SEPARATOR + zkQuorum;
+
+ restServer = new RESTServer(utility.getConfiguration());
+ restServer.run();
+
+ phoenixDBClientV2 = LocalDynamoDB.createV2Client("http://" +
restServer.getServerAddress());
+ dynamoDbClient =
LocalDynamoDbTestBase.localDynamoDb().createV2Client();
+
+ createTableAndInsertData();
+ }
+
+ private static void createTableAndInsertData() {
+ CreateTableRequest createTableRequest =
DDLTestUtils.getCreateTableRequest(
+ TABLE_NAME, "pk", ScalarAttributeType.S, "sk",
ScalarAttributeType.N);
+ createTableRequest = DDLTestUtils.addIndexToRequest(true,
createTableRequest, INDEX_NAME,
+ "category", ScalarAttributeType.S, "score",
ScalarAttributeType.N);
+
+ phoenixDBClientV2.createTable(createTableRequest);
+ dynamoDbClient.createTable(createTableRequest);
+
+ for (int i = 0; i < NUM_RECORDS; i++) {
+ Map<String, AttributeValue> item = new HashMap<>();
+ item.put("pk", AttributeValue.builder().s("pk_" + (i %
100)).build());
+ item.put("sk",
AttributeValue.builder().n(String.valueOf(i)).build());
+ item.put("category", AttributeValue.builder().s("category_" + (i %
10)).build());
+ item.put("score",
AttributeValue.builder().n(String.valueOf(i)).build());
+ item.put("price", AttributeValue.builder().n(String.valueOf((i %
1000) + 1)).build());
+ item.put("active", AttributeValue.builder().bool(i % 2 ==
0).build());
+ item.put("name", AttributeValue.builder().s("item_" + i).build());
+ item.put("tags", AttributeValue.builder().ss("tag_" + (i % 5),
"common_tag").build());
+ item.put("counts", AttributeValue.builder().ns(String.valueOf(i %
10), String.valueOf((i % 20) + 100)).build());
+ item.put("bytes", AttributeValue.builder().bs(
+ software.amazon.awssdk.core.SdkBytes.fromByteArray(new
byte[]{(byte) (i % 128), 0x01}),
+ software.amazon.awssdk.core.SdkBytes.fromByteArray(new
byte[]{0x02, 0x03})).build());
+
+ Map<String, AttributeValue> nestedMap = new HashMap<>();
+ nestedMap.put("field1", AttributeValue.builder().s("nested_" + (i
% 3)).build());
+ nestedMap.put("field2",
AttributeValue.builder().n(String.valueOf(i % 100)).build());
+ item.put("metadata",
AttributeValue.builder().m(nestedMap).build());
+
+ List<AttributeValue> nestedList = new ArrayList<>();
+ nestedList.add(AttributeValue.builder().s("item_" + (i %
7)).build());
+ nestedList.add(AttributeValue.builder().n(String.valueOf(i %
50)).build());
+ nestedList.add(AttributeValue.builder().ss("nested_tag_" + (i %
4), "nested_common").build());
+ item.put("attributes",
AttributeValue.builder().l(nestedList).build());
+
+ PutItemRequest putRequest =
PutItemRequest.builder().tableName(TABLE_NAME).item(item).build();
+ phoenixDBClientV2.putItem(putRequest);
+ dynamoDbClient.putItem(putRequest);
+ }
+ }
+
+ @AfterClass
+ public static void stopLocalDynamoDb() throws Exception {
+ LocalDynamoDbTestBase.localDynamoDb().stop();
+ if (restServer != null) {
+ restServer.stop();
+ }
+ ServerUtil.ConnectionFactory.shutdown();
+ try {
+ DriverManager.deregisterDriver(PhoenixDriver.INSTANCE);
+ } finally {
+ if (utility != null) {
+ utility.shutdownMiniCluster();
+ }
+ ServerMetadataCacheTestImpl.resetCache();
+ }
+ System.setProperty("java.io.tmpdir", tmpDir);
+ }
+
+ @Test(timeout = 300000)
+ public void testEqualityKeyCondition() throws Exception {
+ QueryRequest.Builder qr =
QueryRequest.builder().tableName(TABLE_NAME).indexName(INDEX_NAME);
+ qr.keyConditionExpression("category = :cat");
+ Map<String, AttributeValue> exprAttrVal = new HashMap<>();
+ exprAttrVal.put(":cat",
AttributeValue.builder().s("category_5").build());
+ qr.expressionAttributeValues(exprAttrVal);
+
+ TestUtils.compareQueryOutputs(qr, phoenixDBClientV2, dynamoDbClient);
+ TestUtils.validateIndexUsed(qr.build(), url);
+ }
+
+ @Test(timeout = 300000)
+ public void testRangeKeyCondition() throws SQLException {
+ QueryRequest.Builder qr =
QueryRequest.builder().tableName(TABLE_NAME).indexName(INDEX_NAME);
+ qr.keyConditionExpression("category = :cat AND score > :minScore");
+ Map<String, AttributeValue> exprAttrVal = new HashMap<>();
+ exprAttrVal.put(":cat",
AttributeValue.builder().s("category_3").build());
+ exprAttrVal.put(":minScore",
AttributeValue.builder().n("5000").build());
+ qr.expressionAttributeValues(exprAttrVal);
+
+ TestUtils.compareQueryOutputs(qr, phoenixDBClientV2, dynamoDbClient);
+ TestUtils.validateIndexUsed(qr.build(), url);
+ }
+
+ @Test(timeout = 300000)
+ public void testBetweenKeyCondition() throws SQLException {
+ QueryRequest.Builder qr =
QueryRequest.builder().tableName(TABLE_NAME).indexName(INDEX_NAME);
+ qr.keyConditionExpression("category = :cat AND score BETWEEN :low AND
:high");
+ Map<String, AttributeValue> exprAttrVal = new HashMap<>();
+ exprAttrVal.put(":cat",
AttributeValue.builder().s("category_7").build());
+ exprAttrVal.put(":low", AttributeValue.builder().n("2000").build());
+ exprAttrVal.put(":high", AttributeValue.builder().n("8000").build());
+ qr.expressionAttributeValues(exprAttrVal);
+
+ TestUtils.compareQueryOutputs(qr, phoenixDBClientV2, dynamoDbClient);
+ TestUtils.validateIndexUsed(qr.build(), url);
+ }
+
+ @Test(timeout = 300000)
+ public void testLimit() throws SQLException {
+ QueryRequest.Builder qr =
QueryRequest.builder().tableName(TABLE_NAME).indexName(INDEX_NAME);
+ qr.keyConditionExpression("category = :cat");
+ qr.limit(50);
+ Map<String, AttributeValue> exprAttrVal = new HashMap<>();
+ exprAttrVal.put(":cat",
AttributeValue.builder().s("category_4").build());
+ qr.expressionAttributeValues(exprAttrVal);
+
+ QueryResponse phoenixResult = phoenixDBClientV2.query(qr.build());
+ QueryResponse dynamoResult = dynamoDbClient.query(qr.build());
+
+ Assert.assertEquals(dynamoResult.count(), phoenixResult.count());
+ Assert.assertTrue(ItemComparator.areItemsEqual(dynamoResult.items(),
phoenixResult.items()));
+ TestUtils.validateIndexUsed(qr.build(), url);
+ }
+
+ @Test(timeout = 300000)
+ public void testPagination() throws SQLException {
+ QueryRequest.Builder qr =
QueryRequest.builder().tableName(TABLE_NAME).indexName(INDEX_NAME);
+ qr.keyConditionExpression("category = :cat AND score >= :minScore");
+ qr.limit(100);
+ Map<String, AttributeValue> exprAttrVal = new HashMap<>();
+ exprAttrVal.put(":cat",
AttributeValue.builder().s("category_1").build());
+ exprAttrVal.put(":minScore", AttributeValue.builder().n("0").build());
+ qr.expressionAttributeValues(exprAttrVal);
+
+ TestUtils.compareQueryOutputs(qr, phoenixDBClientV2, dynamoDbClient);
+ TestUtils.validateIndexUsed(qr.build(), url);
+ }
+
+ @Test(timeout = 300000)
+ public void testScanIndexForward() throws SQLException {
+ QueryRequest.Builder qr =
QueryRequest.builder().tableName(TABLE_NAME).indexName(INDEX_NAME);
+ qr.keyConditionExpression("category = :cat");
+ qr.scanIndexForward(false);
+ qr.limit(100);
+ Map<String, AttributeValue> exprAttrVal = new HashMap<>();
+ exprAttrVal.put(":cat",
AttributeValue.builder().s("category_6").build());
+ qr.expressionAttributeValues(exprAttrVal);
+
+ QueryResponse phoenixResult = phoenixDBClientV2.query(qr.build());
+ QueryResponse dynamoResult = dynamoDbClient.query(qr.build());
+
+ Assert.assertEquals(dynamoResult.count(), phoenixResult.count());
+ Assert.assertTrue(ItemComparator.areItemsEqual(dynamoResult.items(),
phoenixResult.items()));
+ TestUtils.validateIndexUsed(qr.build(), url);
+ }
+
+ @Test(timeout = 300000)
+ public void testProjection() throws SQLException {
+ QueryRequest.Builder qr =
QueryRequest.builder().tableName(TABLE_NAME).indexName(INDEX_NAME);
+ qr.keyConditionExpression("category = :cat");
+ qr.projectionExpression("pk, score, price");
+ Map<String, AttributeValue> exprAttrVal = new HashMap<>();
+ exprAttrVal.put(":cat",
AttributeValue.builder().s("category_8").build());
+ qr.expressionAttributeValues(exprAttrVal);
+
+ TestUtils.compareQueryOutputs(qr, phoenixDBClientV2, dynamoDbClient);
+ TestUtils.validateIndexUsed(qr.build(), url);
+ }
+
+ @Test(timeout = 300000)
+ public void testComplexFilterExpression() throws SQLException {
+ QueryRequest.Builder qr =
QueryRequest.builder().tableName(TABLE_NAME).indexName(INDEX_NAME);
+ qr.keyConditionExpression("category = :cat AND score < :maxScore");
+ qr.filterExpression("(price BETWEEN :minPrice AND :maxPrice) OR
(active = :isActive AND #itemName <> :excludeName)");
+ Map<String, String> exprAttrNames = new HashMap<>();
+ exprAttrNames.put("#itemName", "name");
+ qr.expressionAttributeNames(exprAttrNames);
+ Map<String, AttributeValue> exprAttrVal = new HashMap<>();
+ exprAttrVal.put(":cat",
AttributeValue.builder().s("category_9").build());
+ exprAttrVal.put(":maxScore",
AttributeValue.builder().n("7000").build());
+ exprAttrVal.put(":minPrice",
AttributeValue.builder().n("100").build());
+ exprAttrVal.put(":maxPrice",
AttributeValue.builder().n("900").build());
+ exprAttrVal.put(":isActive",
AttributeValue.builder().bool(false).build());
+ exprAttrVal.put(":excludeName",
AttributeValue.builder().s("item_100").build());
+ qr.expressionAttributeValues(exprAttrVal);
+
+ TestUtils.compareQueryOutputs(qr, phoenixDBClientV2, dynamoDbClient);
+ TestUtils.validateIndexUsed(qr.build(), url);
+ }
+
+ @Test(timeout = 300000)
+ public void testLessThanEqual() throws SQLException {
+ QueryRequest.Builder qr =
QueryRequest.builder().tableName(TABLE_NAME).indexName(INDEX_NAME);
+ qr.keyConditionExpression("category = :cat AND score <= :maxScore");
+ Map<String, AttributeValue> exprAttrVal = new HashMap<>();
+ exprAttrVal.put(":cat",
AttributeValue.builder().s("category_0").build());
+ exprAttrVal.put(":maxScore",
AttributeValue.builder().n("5000").build());
+ qr.expressionAttributeValues(exprAttrVal);
+
+ TestUtils.compareQueryOutputs(qr, phoenixDBClientV2, dynamoDbClient);
+ TestUtils.validateIndexUsed(qr.build(), url);
+ }
+
+ @Test(timeout = 300000)
+ public void testFilterExpression1() throws SQLException {
+ QueryRequest.Builder qr =
QueryRequest.builder().tableName(TABLE_NAME).indexName(INDEX_NAME);
+ qr.keyConditionExpression("category = :cat");
+ qr.filterExpression("price > :minPrice AND active = :isActive");
+ Map<String, AttributeValue> exprAttrVal = new HashMap<>();
+ exprAttrVal.put(":cat",
AttributeValue.builder().s("category_2").build());
+ exprAttrVal.put(":minPrice",
AttributeValue.builder().n("500").build());
+ exprAttrVal.put(":isActive",
AttributeValue.builder().bool(true).build());
+ qr.expressionAttributeValues(exprAttrVal);
+
+ TestUtils.compareQueryOutputs(qr, phoenixDBClientV2, dynamoDbClient);
+ TestUtils.validateIndexUsed(qr.build(), url);
+ }
+
+ @Test(timeout = 300000)
+ public void testFilterExpression2() throws SQLException {
+ QueryRequest.Builder qr =
QueryRequest.builder().tableName(TABLE_NAME).indexName(INDEX_NAME);
+ qr.keyConditionExpression("category = :cat AND score > :minScore");
+ qr.filterExpression("price < :maxPrice AND active = :isActive");
+ qr.limit(200);
+ Map<String, AttributeValue> exprAttrVal = new HashMap<>();
+ exprAttrVal.put(":cat",
AttributeValue.builder().s("category_3").build());
+ exprAttrVal.put(":minScore",
AttributeValue.builder().n("3000").build());
+ exprAttrVal.put(":maxPrice",
AttributeValue.builder().n("600").build());
+ exprAttrVal.put(":isActive",
AttributeValue.builder().bool(true).build());
+ qr.expressionAttributeValues(exprAttrVal);
+
+ QueryResponse phoenixResult = phoenixDBClientV2.query(qr.build());
+ QueryResponse dynamoResult = dynamoDbClient.query(qr.build());
+
+ Assert.assertEquals(dynamoResult.count(), phoenixResult.count());
+ Assert.assertTrue(ItemComparator.areItemsEqual(dynamoResult.items(),
phoenixResult.items()));
+ TestUtils.validateIndexUsed(qr.build(), url);
+ }
+
+ @Test(timeout = 300000)
+ public void testFilterOnSetsAndNestedMap() throws SQLException {
+ QueryRequest.Builder qr =
QueryRequest.builder().tableName(TABLE_NAME).indexName(INDEX_NAME);
+ qr.keyConditionExpression("category = :cat");
+ qr.filterExpression("contains(tags, :tag) AND metadata.field1 =
:nestedVal");
+ Map<String, AttributeValue> exprAttrVal = new HashMap<>();
+ exprAttrVal.put(":cat",
AttributeValue.builder().s("category_2").build());
+ exprAttrVal.put(":tag", AttributeValue.builder().s("tag_1").build());
+ exprAttrVal.put(":nestedVal",
AttributeValue.builder().s("nested_1").build());
+ qr.expressionAttributeValues(exprAttrVal);
+
+ TestUtils.compareQueryOutputs(qr, phoenixDBClientV2, dynamoDbClient);
+ TestUtils.validateIndexUsed(qr.build(), url);
+ }
+
+ @Test(timeout = 300000)
+ public void testFilterOnNestedListAndSets() throws SQLException {
+ QueryRequest.Builder qr =
QueryRequest.builder().tableName(TABLE_NAME).indexName(INDEX_NAME);
+ qr.keyConditionExpression("category = :cat AND score < :maxScore");
+ qr.filterExpression("contains(counts, :count) AND size(#attributes) >=
:minSize");
+ Map<String, String> exprAttrNames = new HashMap<>();
+ exprAttrNames.put("#attributes", "attributes");
+ qr.expressionAttributeNames(exprAttrNames);
+ Map<String, AttributeValue> exprAttrVal = new HashMap<>();
+ exprAttrVal.put(":cat",
AttributeValue.builder().s("category_7").build());
+ exprAttrVal.put(":maxScore",
AttributeValue.builder().n("15000").build());
+ exprAttrVal.put(":count", AttributeValue.builder().n("105").build());
+ exprAttrVal.put(":minSize", AttributeValue.builder().n("2").build());
+ qr.expressionAttributeValues(exprAttrVal);
+
+ TestUtils.compareQueryOutputs(qr, phoenixDBClientV2, dynamoDbClient);
+ TestUtils.validateIndexUsed(qr.build(), url);
+ }
+}
+
diff --git
a/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/ScanIndex3IT.java
b/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/ScanIndex3IT.java
new file mode 100644
index 0000000..a57200a
--- /dev/null
+++ b/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/ScanIndex3IT.java
@@ -0,0 +1,330 @@
+/*
+ * 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.ddb;
+
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hbase.HBaseConfiguration;
+import org.apache.hadoop.hbase.HBaseTestingUtility;
+import org.apache.phoenix.ddb.rest.RESTServer;
+import org.apache.phoenix.end2end.ServerMetadataCacheTestImpl;
+import org.apache.phoenix.jdbc.PhoenixDriver;
+import org.apache.phoenix.util.PhoenixRuntime;
+import org.apache.phoenix.util.ServerUtil;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
+import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
+import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest;
+import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
+import software.amazon.awssdk.services.dynamodb.model.ScanRequest;
+import software.amazon.awssdk.services.dynamodb.model.ScanResponse;
+import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
+
+import static org.apache.phoenix.query.BaseTest.setUpConfigForMiniCluster;
+
+public class ScanIndex3IT {
+
+ private static final String TABLE_NAME = "ScanIndex3IT_Table";
+ private static final String INDEX_NAME = "gsi_ScanIndex3IT";
+ private static final int NUM_RECORDS = 18000;
+
+ private static DynamoDbClient dynamoDbClient;
+ private static DynamoDbClient phoenixDBClientV2;
+ private static String url;
+ private static HBaseTestingUtility utility = null;
+ private static String tmpDir;
+ private static RESTServer restServer = null;
+
+ @Rule
+ public final TestName testName = new TestName();
+
+ @BeforeClass
+ public static void initialize() throws Exception {
+ tmpDir = System.getProperty("java.io.tmpdir");
+ LocalDynamoDbTestBase.localDynamoDb().start();
+ Configuration conf = HBaseConfiguration.create();
+ utility = new HBaseTestingUtility(conf);
+ setUpConfigForMiniCluster(conf);
+
+ utility.startMiniCluster();
+ String zkQuorum = "localhost:" +
utility.getZkCluster().getClientPort();
+ url = PhoenixRuntime.JDBC_PROTOCOL +
PhoenixRuntime.JDBC_PROTOCOL_SEPARATOR + zkQuorum;
+
+ restServer = new RESTServer(utility.getConfiguration());
+ restServer.run();
+
+ phoenixDBClientV2 = LocalDynamoDB.createV2Client("http://" +
restServer.getServerAddress());
+ dynamoDbClient =
LocalDynamoDbTestBase.localDynamoDb().createV2Client();
+
+ createTableAndInsertData();
+ }
+
+ private static void createTableAndInsertData() {
+ CreateTableRequest createTableRequest =
DDLTestUtils.getCreateTableRequest(
+ TABLE_NAME, "pk", ScalarAttributeType.S, "sk",
ScalarAttributeType.N);
+ createTableRequest = DDLTestUtils.addIndexToRequest(true,
createTableRequest, INDEX_NAME,
+ "status", ScalarAttributeType.S, "timestamp",
ScalarAttributeType.N);
+
+ phoenixDBClientV2.createTable(createTableRequest);
+ dynamoDbClient.createTable(createTableRequest);
+
+ for (int i = 0; i < NUM_RECORDS; i++) {
+ Map<String, AttributeValue> item = new HashMap<>();
+ item.put("pk", AttributeValue.builder().s("pk_" + (i %
100)).build());
+ item.put("sk",
AttributeValue.builder().n(String.valueOf(i)).build());
+ item.put("status", AttributeValue.builder().s("status_" + (i %
10)).build());
+ item.put("timestamp",
AttributeValue.builder().n(String.valueOf(i)).build());
+ item.put("amount", AttributeValue.builder().n(String.valueOf((i %
1000) + 1)).build());
+ item.put("priority", AttributeValue.builder().n(String.valueOf(i %
10)).build());
+ item.put("active", AttributeValue.builder().bool(i % 2 ==
0).build());
+ item.put("description", AttributeValue.builder().s("description_"
+ i).build());
+ item.put("labels", AttributeValue.builder().ss("label_" + (i % 6),
"shared_label").build());
+ item.put("scores", AttributeValue.builder().ns(String.valueOf(i %
15), String.valueOf((i % 25) + 100)).build());
+ item.put("data", AttributeValue.builder().bs(
+ software.amazon.awssdk.core.SdkBytes.fromByteArray(new
byte[]{(byte) (i % 64), 0x01}),
+ software.amazon.awssdk.core.SdkBytes.fromByteArray(new
byte[]{0x0A, 0x0B})).build());
+
+ if (i % 5 == 0) {
+ item.put("optional_field",
AttributeValue.builder().s("optional_" + i).build());
+ }
+
+ Map<String, AttributeValue> nestedMap = new HashMap<>();
+ nestedMap.put("key1", AttributeValue.builder().s("value_" + (i %
4)).build());
+ nestedMap.put("key2", AttributeValue.builder().n(String.valueOf(i
% 80)).build());
+ item.put("config", AttributeValue.builder().m(nestedMap).build());
+
+ List<AttributeValue> nestedList = new ArrayList<>();
+ nestedList.add(AttributeValue.builder().s("list_item_" + (i %
8)).build());
+ nestedList.add(AttributeValue.builder().n(String.valueOf(i %
40)).build());
+ nestedList.add(AttributeValue.builder().ss("set_in_list_" + (i %
3), "list_common").build());
+ item.put("itms", AttributeValue.builder().l(nestedList).build());
+
+ PutItemRequest putRequest =
PutItemRequest.builder().tableName(TABLE_NAME).item(item).build();
+ phoenixDBClientV2.putItem(putRequest);
+ dynamoDbClient.putItem(putRequest);
+ }
+ }
+
+ @AfterClass
+ public static void stopLocalDynamoDb() throws Exception {
+ LocalDynamoDbTestBase.localDynamoDb().stop();
+ if (restServer != null) {
+ restServer.stop();
+ }
+ ServerUtil.ConnectionFactory.shutdown();
+ try {
+ DriverManager.deregisterDriver(PhoenixDriver.INSTANCE);
+ } finally {
+ if (utility != null) {
+ utility.shutdownMiniCluster();
+ }
+ ServerMetadataCacheTestImpl.resetCache();
+ }
+ System.setProperty("java.io.tmpdir", tmpDir);
+ }
+
+ @Test(timeout = 300000)
+ public void testFullTableScan() throws SQLException {
+ ScanRequest.Builder sr =
ScanRequest.builder().tableName(TABLE_NAME).indexName(INDEX_NAME);
+
+ TestUtils.compareScanOutputs(sr, phoenixDBClientV2, dynamoDbClient,
"pk", "sk",
+ ScalarAttributeType.S, ScalarAttributeType.N);
+ TestUtils.validateIndexUsed(sr.build(), url, "FULL SCAN ");
+ }
+
+ @Test(timeout = 300000)
+ public void testFilterExpression() throws SQLException {
+ ScanRequest.Builder sr =
ScanRequest.builder().tableName(TABLE_NAME).indexName(INDEX_NAME);
+ sr.filterExpression("amount > :minAmount AND active = :isActive");
+ Map<String, AttributeValue> exprAttrVal = new HashMap<>();
+ exprAttrVal.put(":minAmount",
AttributeValue.builder().n("500").build());
+ exprAttrVal.put(":isActive",
AttributeValue.builder().bool(true).build());
+ sr.expressionAttributeValues(exprAttrVal);
+
+ TestUtils.compareScanOutputs(sr, phoenixDBClientV2, dynamoDbClient,
"pk", "sk",
+ ScalarAttributeType.S, ScalarAttributeType.N);
+ TestUtils.validateIndexUsed(sr.build(), url, "FULL SCAN ");
+ }
+
+ @Test(timeout = 300000)
+ public void testLimit() throws SQLException {
+ ScanRequest.Builder sr =
ScanRequest.builder().tableName(TABLE_NAME).indexName(INDEX_NAME);
+ sr.limit(75);
+
+ ScanResponse phoenixResult = phoenixDBClientV2.scan(sr.build());
+ ScanResponse dynamoResult = dynamoDbClient.scan(sr.build());
+
+ Assert.assertEquals(dynamoResult.count(), phoenixResult.count());
+ Assert.assertTrue(ItemComparator.areItemsEqual(
+ TestUtils.sortItemsByPartitionAndSortKey(dynamoResult.items(),
"pk", "sk"),
+
TestUtils.sortItemsByPartitionAndSortKey(phoenixResult.items(), "pk", "sk")));
+ TestUtils.validateIndexUsed(sr.build(), url, "FULL SCAN ");
+ }
+
+ @Test(timeout = 300000)
+ public void testPagination() throws SQLException {
+ ScanRequest.Builder sr =
ScanRequest.builder().tableName(TABLE_NAME).indexName(INDEX_NAME);
+ sr.limit(150);
+
+ TestUtils.compareScanOutputs(sr, phoenixDBClientV2, dynamoDbClient,
"pk", "sk",
+ ScalarAttributeType.S, ScalarAttributeType.N);
+ TestUtils.validateIndexUsed(sr.build(), url, "FULL SCAN ");
+ }
+
+ @Test(timeout = 300000)
+ public void testProjection() throws SQLException {
+ ScanRequest.Builder sr =
ScanRequest.builder().tableName(TABLE_NAME).indexName(INDEX_NAME);
+ sr.projectionExpression("pk, #status, amount");
+ Map<String, String> exprAttrNames = new HashMap<>();
+ exprAttrNames.put("#status", "status");
+ sr.expressionAttributeNames(exprAttrNames);
+ sr.limit(50);
+
+ ScanResponse phoenixResult = phoenixDBClientV2.scan(sr.build());
+ ScanResponse dynamoResult = dynamoDbClient.scan(sr.build());
+
+ Assert.assertEquals(dynamoResult.count(), phoenixResult.count());
+ TestUtils.validateIndexUsed(sr.build(), url, "FULL SCAN ");
+ }
+
+ @Test(timeout = 300000)
+ public void testComplexFilterExpression1() throws SQLException {
+ ScanRequest.Builder sr =
ScanRequest.builder().tableName(TABLE_NAME).indexName(INDEX_NAME);
+ sr.filterExpression("(amount BETWEEN :minAmt AND :maxAmt) OR (active =
:isActive AND priority > :minPriority)");
+ Map<String, AttributeValue> exprAttrVal = new HashMap<>();
+ exprAttrVal.put(":minAmt", AttributeValue.builder().n("200").build());
+ exprAttrVal.put(":maxAmt", AttributeValue.builder().n("800").build());
+ exprAttrVal.put(":isActive",
AttributeValue.builder().bool(false).build());
+ exprAttrVal.put(":minPriority",
AttributeValue.builder().n("7").build());
+ sr.expressionAttributeValues(exprAttrVal);
+
+ TestUtils.compareScanOutputs(sr, phoenixDBClientV2, dynamoDbClient,
"pk", "sk",
+ ScalarAttributeType.S, ScalarAttributeType.N);
+ TestUtils.validateIndexUsed(sr.build(), url, "FULL SCAN ");
+ }
+
+ @Test(timeout = 300000)
+ public void testAttributeExists() throws SQLException {
+ ScanRequest.Builder sr =
ScanRequest.builder().tableName(TABLE_NAME).indexName(INDEX_NAME);
+ sr.filterExpression("attribute_exists(optional_field)");
+
+ TestUtils.compareScanOutputs(sr, phoenixDBClientV2, dynamoDbClient,
"pk", "sk",
+ ScalarAttributeType.S, ScalarAttributeType.N);
+ TestUtils.validateIndexUsed(sr.build(), url, "FULL SCAN ");
+ }
+
+ @Test(timeout = 300000)
+ public void testAttributeNotExists() throws SQLException {
+ ScanRequest.Builder sr =
ScanRequest.builder().tableName(TABLE_NAME).indexName(INDEX_NAME);
+ sr.filterExpression("attribute_not_exists(optional_field)");
+
+ TestUtils.compareScanOutputs(sr, phoenixDBClientV2, dynamoDbClient,
"pk", "sk",
+ ScalarAttributeType.S, ScalarAttributeType.N);
+ TestUtils.validateIndexUsed(sr.build(), url, "FULL SCAN ");
+ }
+
+ @Test(timeout = 300000)
+ public void testComplexFilterExpression2() throws SQLException {
+ ScanRequest.Builder sr =
ScanRequest.builder().tableName(TABLE_NAME).indexName(INDEX_NAME);
+ sr.filterExpression("amount > :minAmt AND priority >= :minPri AND
active = :isActive");
+ Map<String, AttributeValue> exprAttrVal = new HashMap<>();
+ exprAttrVal.put(":minAmt", AttributeValue.builder().n("600").build());
+ exprAttrVal.put(":minPri", AttributeValue.builder().n("5").build());
+ exprAttrVal.put(":isActive",
AttributeValue.builder().bool(true).build());
+ sr.expressionAttributeValues(exprAttrVal);
+
+ TestUtils.compareScanOutputs(sr, phoenixDBClientV2, dynamoDbClient,
"pk", "sk",
+ ScalarAttributeType.S, ScalarAttributeType.N);
+ TestUtils.validateIndexUsed(sr.build(), url, "FULL SCAN ");
+ }
+
+ @Test(timeout = 300000)
+ public void testNotCondition() throws SQLException {
+ ScanRequest.Builder sr =
ScanRequest.builder().tableName(TABLE_NAME).indexName(INDEX_NAME);
+ sr.filterExpression("NOT (#status = :excludeStatus)");
+ Map<String, String> exprAttrNames = new HashMap<>();
+ exprAttrNames.put("#status", "status");
+ sr.expressionAttributeNames(exprAttrNames);
+ Map<String, AttributeValue> exprAttrVal = new HashMap<>();
+ exprAttrVal.put(":excludeStatus",
AttributeValue.builder().s("status_0").build());
+ sr.expressionAttributeValues(exprAttrVal);
+
+ TestUtils.compareScanOutputs(sr, phoenixDBClientV2, dynamoDbClient,
"pk", "sk",
+ ScalarAttributeType.S, ScalarAttributeType.N);
+ TestUtils.validateIndexUsed(sr.build(), url, "FULL SCAN ");
+ }
+
+ @Test(timeout = 300000)
+ public void testInCondition() throws SQLException {
+ ScanRequest.Builder sr =
ScanRequest.builder().tableName(TABLE_NAME).indexName(INDEX_NAME);
+ sr.filterExpression("#status IN (:status1, :status2, :status3)");
+ Map<String, String> exprAttrNames = new HashMap<>();
+ exprAttrNames.put("#status", "status");
+ sr.expressionAttributeNames(exprAttrNames);
+ Map<String, AttributeValue> exprAttrVal = new HashMap<>();
+ exprAttrVal.put(":status1",
AttributeValue.builder().s("status_1").build());
+ exprAttrVal.put(":status2",
AttributeValue.builder().s("status_3").build());
+ exprAttrVal.put(":status3",
AttributeValue.builder().s("status_7").build());
+ sr.expressionAttributeValues(exprAttrVal);
+
+ TestUtils.compareScanOutputs(sr, phoenixDBClientV2, dynamoDbClient,
"pk", "sk",
+ ScalarAttributeType.S, ScalarAttributeType.N);
+ TestUtils.validateIndexUsed(sr.build(), url, "FULL SCAN ");
+ }
+
+ @Test(timeout = 300000)
+ public void testFilterOnSetsAndNestedMap() throws SQLException {
+ ScanRequest.Builder sr =
ScanRequest.builder().tableName(TABLE_NAME).indexName(INDEX_NAME);
+ sr.filterExpression("contains(labels, :label) AND config.key1 =
:configVal");
+ Map<String, AttributeValue> exprAttrVal = new HashMap<>();
+ exprAttrVal.put(":label",
AttributeValue.builder().s("label_3").build());
+ exprAttrVal.put(":configVal",
AttributeValue.builder().s("value_2").build());
+ sr.expressionAttributeValues(exprAttrVal);
+
+ TestUtils.compareScanOutputs(sr, phoenixDBClientV2, dynamoDbClient,
"pk", "sk",
+ ScalarAttributeType.S, ScalarAttributeType.N);
+ TestUtils.validateIndexUsed(sr.build(), url, "FULL SCAN ");
+ }
+
+ @Test(timeout = 300000)
+ public void testFilterOnNestedListAndMultipleSets() throws SQLException {
+ ScanRequest.Builder sr =
ScanRequest.builder().tableName(TABLE_NAME).indexName(INDEX_NAME);
+ sr.filterExpression("contains(scores, :score) AND size(itms) =
:listSize AND contains(labels, :label)");
+ Map<String, AttributeValue> exprAttrVal = new HashMap<>();
+ exprAttrVal.put(":score", AttributeValue.builder().n("110").build());
+ exprAttrVal.put(":listSize", AttributeValue.builder().n("3").build());
+ exprAttrVal.put(":label",
AttributeValue.builder().s("shared_label").build());
+ sr.expressionAttributeValues(exprAttrVal);
+
+ TestUtils.compareScanOutputs(sr, phoenixDBClientV2, dynamoDbClient,
"pk", "sk",
+ ScalarAttributeType.S, ScalarAttributeType.N);
+ TestUtils.validateIndexUsed(sr.build(), url, "FULL SCAN ");
+ }
+}
+
diff --git
a/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/TestUtils.java
b/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/TestUtils.java
index 4a51347..6b1c9d2 100644
--- a/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/TestUtils.java
+++ b/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/TestUtils.java
@@ -387,7 +387,7 @@ public class TestUtils {
qr.exclusiveStartKey(ddbResponse.lastEvaluatedKey());
} while (ddbResponse.hasLastEvaluatedKey());
Assert.assertEquals(ddbResult.size(), phoenixResult.size());
- Assert.assertEquals(ddbResult, phoenixResult);
+ Assert.assertTrue(ItemComparator.areItemsEqual(ddbResult,
phoenixResult));
}
public static void compareScanOutputs(ScanRequest.Builder sr,