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 09a5b6a  Use ANTLR grammar to evaluate KeyConditionExpression in query
09a5b6a is described below

commit 09a5b6adfb6106232f45292ee718a1f94d53c596
Author: Rahul Kumar <[email protected]>
AuthorDate: Wed Jan 28 23:50:09 2026 +0530

    Use ANTLR grammar to evaluate KeyConditionExpression in query
---
 .../apache/phoenix/ddb/service/QueryService.java   |   9 +-
 .../phoenix/ddb/KeyConditionsHolderTest.java       | 240 ++++++++++++++++++--
 .../test/java/org/apache/phoenix/ddb/QueryIT.java  |  38 ++++
 .../java/org/apache/phoenix/ddb/QueryIndex1IT.java | 100 +++++++++
 .../phoenix/ddb/utils/KeyConditionsHolder.java     | 248 +++++++++++++++++----
 5 files changed, 582 insertions(+), 53 deletions(-)

diff --git 
a/phoenix-ddb-rest/src/main/java/org/apache/phoenix/ddb/service/QueryService.java
 
b/phoenix-ddb-rest/src/main/java/org/apache/phoenix/ddb/service/QueryService.java
index 141a289..13bc8e2 100644
--- 
a/phoenix-ddb-rest/src/main/java/org/apache/phoenix/ddb/service/QueryService.java
+++ 
b/phoenix-ddb-rest/src/main/java/org/apache/phoenix/ddb/service/QueryService.java
@@ -120,8 +120,13 @@ public class QueryService {
                                 PhoenixUtils.getFullTableName(tableName, 
true)));
 
         // parse Key Conditions
-        KeyConditionsHolder keyConditions = new 
KeyConditionsHolder(keyCondExpr, exprAttrNames,
-                useIndex ? indexPKCols : tablePKCols, useIndex);
+        KeyConditionsHolder keyConditions;
+        try {
+            keyConditions = new KeyConditionsHolder(keyCondExpr, exprAttrNames,
+                    useIndex ? indexPKCols : tablePKCols, useIndex);
+        } catch (RuntimeException e) {
+            throw new ValidationException(e.getMessage());
+        }
         PColumn sortKeyPKCol = keyConditions.getSortKeyPKCol();
 
         // append all conditions for WHERE clause
diff --git 
a/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/KeyConditionsHolderTest.java
 
b/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/KeyConditionsHolderTest.java
index a714274..51d479a 100644
--- 
a/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/KeyConditionsHolderTest.java
+++ 
b/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/KeyConditionsHolderTest.java
@@ -18,21 +18,53 @@
 package org.apache.phoenix.ddb;
 
 import org.apache.phoenix.ddb.utils.KeyConditionsHolder;
+import org.apache.phoenix.schema.PColumn;
+import org.apache.phoenix.schema.PName;
+import org.apache.phoenix.schema.PNameFactory;
 import org.junit.Test;
 import org.junit.Assert;
+import org.mockito.Mockito;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 public class KeyConditionsHolderTest {
 
     private Map<String, String> exprAttrNames = new HashMap<>();
 
+    private List<PColumn> createPkCols(String partitionKey) {
+        List<PColumn> pkCols = new ArrayList<>();
+        PColumn mockColumn = Mockito.mock(PColumn.class);
+        PName mockName = PNameFactory.newName(partitionKey);
+        Mockito.when(mockColumn.getName()).thenReturn(mockName);
+        pkCols.add(mockColumn);
+        return pkCols;
+    }
+
+    private List<PColumn> createPkColsWithSortKey(String partitionKey, String 
sortKey) {
+        List<PColumn> pkCols = new ArrayList<>();
+        PColumn mockPartitionColumn = Mockito.mock(PColumn.class);
+        PName mockPartitionName = PNameFactory.newName(partitionKey);
+        
Mockito.when(mockPartitionColumn.getName()).thenReturn(mockPartitionName);
+        pkCols.add(mockPartitionColumn);
+
+        PColumn mockSortColumn = Mockito.mock(PColumn.class);
+        PName mockSortName = PNameFactory.newName(sortKey);
+        Mockito.when(mockSortColumn.getName()).thenReturn(mockSortName);
+        pkCols.add(mockSortColumn);
+
+        return pkCols;
+    }
+
+
     @Test(timeout = 120000)
     public void test1() {
         String keyCondExpr = "#0 = :0";
         exprAttrNames.put("#0", "hashKey");
-        KeyConditionsHolder keyConditions = new 
KeyConditionsHolder(keyCondExpr, exprAttrNames);
+        List<PColumn> pkCols = createPkCols("hashKey");
+        KeyConditionsHolder keyConditions = new 
KeyConditionsHolder(keyCondExpr, exprAttrNames, pkCols, false);
         Assert.assertEquals("hashKey", keyConditions.getPartitionKeyName());
         Assert.assertFalse(keyConditions.hasSortKey());
     }
@@ -42,7 +74,8 @@ public class KeyConditionsHolderTest {
         String keyCondExpr = "#0 = :0 AND #1 = :1";
         exprAttrNames.put("#0", "hashKey");
         exprAttrNames.put("#1", "sortKey");
-        KeyConditionsHolder keyConditions = new 
KeyConditionsHolder(keyCondExpr, exprAttrNames);
+        List<PColumn> pkCols = createPkColsWithSortKey("hashKey", "sortKey");
+        KeyConditionsHolder keyConditions = new 
KeyConditionsHolder(keyCondExpr, exprAttrNames, pkCols, false);
         Assert.assertEquals("hashKey", keyConditions.getPartitionKeyName());
         Assert.assertTrue(keyConditions.hasSortKey());
         Assert.assertEquals("sortKey", keyConditions.getSortKeyName());
@@ -55,7 +88,8 @@ public class KeyConditionsHolderTest {
         String keyCondExpr = "#0 = :0 AND #1 > :1";
         exprAttrNames.put("#0", "hashKey");
         exprAttrNames.put("#1", "sortKey");
-        KeyConditionsHolder keyConditions = new 
KeyConditionsHolder(keyCondExpr, exprAttrNames);
+        List<PColumn> pkCols = createPkColsWithSortKey("hashKey", "sortKey");
+        KeyConditionsHolder keyConditions = new 
KeyConditionsHolder(keyCondExpr, exprAttrNames, pkCols, false);
         Assert.assertEquals("hashKey", keyConditions.getPartitionKeyName());
         Assert.assertTrue(keyConditions.hasSortKey());
         Assert.assertEquals("sortKey", keyConditions.getSortKeyName());
@@ -68,7 +102,8 @@ public class KeyConditionsHolderTest {
         String keyCondExpr = "#0 = :0 AND #1 < :1";
         exprAttrNames.put("#0", "hashKey");
         exprAttrNames.put("#1", "sortKey");
-        KeyConditionsHolder keyConditions = new 
KeyConditionsHolder(keyCondExpr, exprAttrNames);
+        List<PColumn> pkCols = createPkColsWithSortKey("hashKey", "sortKey");
+        KeyConditionsHolder keyConditions = new 
KeyConditionsHolder(keyCondExpr, exprAttrNames, pkCols, false);
         Assert.assertEquals("hashKey", keyConditions.getPartitionKeyName());
         Assert.assertTrue(keyConditions.hasSortKey());
         Assert.assertEquals("sortKey", keyConditions.getSortKeyName());
@@ -81,7 +116,8 @@ public class KeyConditionsHolderTest {
         String keyCondExpr = "#0 = :0 AND #1 >= :1";
         exprAttrNames.put("#0", "hashKey");
         exprAttrNames.put("#1", "sortKey");
-        KeyConditionsHolder keyConditions = new 
KeyConditionsHolder(keyCondExpr, exprAttrNames);
+        List<PColumn> pkCols = createPkColsWithSortKey("hashKey", "sortKey");
+        KeyConditionsHolder keyConditions = new 
KeyConditionsHolder(keyCondExpr, exprAttrNames, pkCols, false);
         Assert.assertEquals("hashKey", keyConditions.getPartitionKeyName());
         Assert.assertTrue(keyConditions.hasSortKey());
         Assert.assertEquals("sortKey", keyConditions.getSortKeyName());
@@ -94,7 +130,8 @@ public class KeyConditionsHolderTest {
         String keyCondExpr = "#0 = :0 AND #1 <= :1";
         exprAttrNames.put("#0", "hashKey");
         exprAttrNames.put("#1", "sortKey");
-        KeyConditionsHolder keyConditions = new 
KeyConditionsHolder(keyCondExpr, exprAttrNames);
+        List<PColumn> pkCols = createPkColsWithSortKey("hashKey", "sortKey");
+        KeyConditionsHolder keyConditions = new 
KeyConditionsHolder(keyCondExpr, exprAttrNames, pkCols, false);
         Assert.assertEquals("hashKey", keyConditions.getPartitionKeyName());
         Assert.assertTrue(keyConditions.hasSortKey());
         Assert.assertEquals("sortKey", keyConditions.getSortKeyName());
@@ -107,7 +144,8 @@ public class KeyConditionsHolderTest {
         String keyCondExpr1 = "#0 = :0 AND #1 BETWEEN :1 AND :2";
         exprAttrNames.put("#0", "hashKey");
         exprAttrNames.put("#1", "sortKey");
-        KeyConditionsHolder keyConditions = new 
KeyConditionsHolder(keyCondExpr1, exprAttrNames);
+        List<PColumn> pkCols1 = createPkColsWithSortKey("hashKey", "sortKey");
+        KeyConditionsHolder keyConditions = new 
KeyConditionsHolder(keyCondExpr1, exprAttrNames, pkCols1, false);
         Assert.assertEquals("hashKey", keyConditions.getPartitionKeyName());
         Assert.assertTrue(keyConditions.hasSortKey());
         Assert.assertEquals("sortKey", keyConditions.getSortKeyName());
@@ -119,7 +157,8 @@ public class KeyConditionsHolderTest {
         String keyCondExpr2 = "#hashKey = :hashKey AND #sortKey BETWEEN 
:sortKeyFrom AND :sortKeyTo";
         exprAttrNames.put("#hashKey", "Id");
         exprAttrNames.put("#sortKey", "Title");
-        keyConditions = new KeyConditionsHolder(keyCondExpr2, exprAttrNames);
+        List<PColumn> pkCols = createPkCols("Id");
+        keyConditions = new KeyConditionsHolder(keyCondExpr2, exprAttrNames, 
pkCols, false);
         Assert.assertEquals("Id", keyConditions.getPartitionKeyName());
         Assert.assertTrue(keyConditions.hasSortKey());
         Assert.assertEquals("Title", keyConditions.getSortKeyName());
@@ -134,7 +173,8 @@ public class KeyConditionsHolderTest {
         String keyCondExpr = "#0 = :0 AND begins_with(#1, :1)";
         exprAttrNames.put("#0", "hashKey");
         exprAttrNames.put("#1", "sortKey");
-        KeyConditionsHolder keyConditions = new 
KeyConditionsHolder(keyCondExpr, exprAttrNames);
+        List<PColumn> pkCols = createPkColsWithSortKey("hashKey", "sortKey");
+        KeyConditionsHolder keyConditions = new 
KeyConditionsHolder(keyCondExpr, exprAttrNames, pkCols, false);
         Assert.assertEquals("hashKey", keyConditions.getPartitionKeyName());
         Assert.assertTrue(keyConditions.hasSortKey());
         Assert.assertEquals("sortKey", keyConditions.getSortKeyName());
@@ -146,7 +186,8 @@ public class KeyConditionsHolderTest {
     public void test9() {
         exprAttrNames = null;
         String keyCondExpr1 = "hashKey=:0 AND sortKey>:1";
-        KeyConditionsHolder keyConditions = new 
KeyConditionsHolder(keyCondExpr1, exprAttrNames);
+        List<PColumn> pkCols1 = createPkColsWithSortKey("hashKey", "sortKey");
+        KeyConditionsHolder keyConditions = new 
KeyConditionsHolder(keyCondExpr1, exprAttrNames, pkCols1, false);
         Assert.assertEquals("hashKey", keyConditions.getPartitionKeyName());
         Assert.assertTrue(keyConditions.hasSortKey());
         Assert.assertEquals("sortKey", keyConditions.getSortKeyName());
@@ -154,7 +195,8 @@ public class KeyConditionsHolderTest {
         Assert.assertEquals(":1", keyConditions.getSortKeyValue1());
 
         String keyCondExpr2 = "hashKey= :0 AND sortKey >:1";
-        keyConditions = new KeyConditionsHolder(keyCondExpr2, exprAttrNames);
+        List<PColumn> pkCols2 = createPkColsWithSortKey("hashKey", "sortKey");
+        keyConditions = new KeyConditionsHolder(keyCondExpr2, exprAttrNames, 
pkCols2, false);
         Assert.assertEquals("hashKey", keyConditions.getPartitionKeyName());
         Assert.assertTrue(keyConditions.hasSortKey());
         Assert.assertEquals("sortKey", keyConditions.getSortKeyName());
@@ -162,8 +204,8 @@ public class KeyConditionsHolderTest {
         Assert.assertEquals(":1", keyConditions.getSortKeyValue1());
         String keyCondExpr3 = "hashKey =:0 AND sortKey> :1";
 
-
-        keyConditions = new KeyConditionsHolder(keyCondExpr3, exprAttrNames);
+        List<PColumn> pkCols3 = createPkColsWithSortKey("hashKey", "sortKey");
+        keyConditions = new KeyConditionsHolder(keyCondExpr3, exprAttrNames, 
pkCols3, false);
         Assert.assertEquals("hashKey", keyConditions.getPartitionKeyName());
         Assert.assertTrue(keyConditions.hasSortKey());
         Assert.assertEquals("sortKey", keyConditions.getSortKeyName());
@@ -172,4 +214,176 @@ public class KeyConditionsHolderTest {
 
         exprAttrNames = new HashMap<>();
     }
+
+    @Test(timeout = 120000)
+    public void testSortKeyBeforePartitionKey() {
+        // Test that order of conditions doesn't matter - sort key can come 
before partition key
+        String keyCondExpr = "#sk > :v1 AND #pk = :v0";
+        exprAttrNames.put("#pk", "hashKey");
+        exprAttrNames.put("#sk", "sortKey");
+        List<PColumn> pkCols = createPkColsWithSortKey("hashKey", "sortKey");
+        KeyConditionsHolder keyConditions = new 
KeyConditionsHolder(keyCondExpr, exprAttrNames, pkCols, false);
+
+        // Verify partition key is still recognized
+        Assert.assertEquals("hashKey", keyConditions.getPartitionKeyName());
+        Assert.assertEquals(":v0", keyConditions.getPartitionValue());
+
+        // Verify sort key is still recognized
+        Assert.assertTrue(keyConditions.hasSortKey());
+        Assert.assertEquals("sortKey", keyConditions.getSortKeyName());
+        Assert.assertEquals(">", keyConditions.getSortKeyOperator());
+        Assert.assertEquals(":v1", keyConditions.getSortKeyValue1());
+    }
+
+    @Test(timeout = 120000)
+    public void testSortKeyWithEqualBeforePartitionKey() {
+        // Test that sort key using = operator and coming first is not 
mistaken for partition key
+        String keyCondExpr = "#sk = :v1 AND #pk = :v0";
+        exprAttrNames.put("#pk", "hashKey");
+        exprAttrNames.put("#sk", "sortKey");
+        List<PColumn> pkCols = createPkColsWithSortKey("hashKey", "sortKey");
+        KeyConditionsHolder keyConditions = new 
KeyConditionsHolder(keyCondExpr, exprAttrNames, pkCols, false);
+
+        // Verify partition key is still recognized
+        Assert.assertEquals("hashKey", keyConditions.getPartitionKeyName());
+        Assert.assertEquals(":v0", keyConditions.getPartitionValue());
+
+        // Verify sort key is still recognized
+        Assert.assertTrue(keyConditions.hasSortKey());
+        Assert.assertEquals("sortKey", keyConditions.getSortKeyName());
+        Assert.assertEquals("=", keyConditions.getSortKeyOperator());
+        Assert.assertEquals(":v1", keyConditions.getSortKeyValue1());
+    }
+
+    @Test(timeout = 120000)
+    public void testSortKeyBetweenBeforePartitionKey() {
+        // Test that BETWEEN condition for sort key can come before partition 
key
+        String keyCondExpr = "#sk BETWEEN :v3 AND :v4 AND #pk = :v1";
+        exprAttrNames.put("#pk", "hashKey");
+        exprAttrNames.put("#sk", "sortKey");
+        List<PColumn> pkCols = createPkColsWithSortKey("hashKey", "sortKey");
+        KeyConditionsHolder keyConditions = new 
KeyConditionsHolder(keyCondExpr, exprAttrNames, pkCols, false);
+
+        // Verify partition key is still recognized
+        Assert.assertEquals("hashKey", keyConditions.getPartitionKeyName());
+        Assert.assertEquals(":v1", keyConditions.getPartitionValue());
+
+        // Verify sort key BETWEEN condition is correctly parsed
+        Assert.assertTrue(keyConditions.hasSortKey());
+        Assert.assertEquals("sortKey", keyConditions.getSortKeyName());
+        Assert.assertTrue(keyConditions.hasBetween());
+        Assert.assertEquals("BETWEEN", keyConditions.getSortKeyOperator());
+        Assert.assertEquals(":v3", keyConditions.getSortKeyValue1());
+        Assert.assertEquals(":v4", keyConditions.getSortKeyValue2());
+    }
+
+    @Test(timeout = 120000)
+    public void testBeginsWithBeforePartitionKey() {
+        // Test that begins_with condition for sort key can come before 
partition key
+        String keyCondExpr = "begins_with(#sk, :v2) AND #pk = :v1";
+        exprAttrNames.put("#pk", "hashKey");
+        exprAttrNames.put("#sk", "sortKey");
+        List<PColumn> pkCols = createPkColsWithSortKey("hashKey", "sortKey");
+        KeyConditionsHolder keyConditions = new 
KeyConditionsHolder(keyCondExpr, exprAttrNames, pkCols, false);
+
+        // Verify partition key is still recognized
+        Assert.assertEquals("hashKey", keyConditions.getPartitionKeyName());
+        Assert.assertEquals(":v1", keyConditions.getPartitionValue());
+
+        // Verify sort key begins_with condition is correctly parsed
+        Assert.assertTrue(keyConditions.hasSortKey());
+        Assert.assertEquals("sortKey", keyConditions.getSortKeyName());
+        Assert.assertTrue(keyConditions.hasBeginsWith());
+        Assert.assertEquals(":v2", keyConditions.getBeginsWithSortKeyVal());
+    }
+
+
+    @Test(timeout = 120000, expected = IllegalArgumentException.class)
+    public void testNullPkCols() {
+        // Test that null pkCols throws IllegalArgumentException
+        String keyCondExpr = "#0 = :0";
+        exprAttrNames.put("#0", "hashKey");
+        new KeyConditionsHolder(keyCondExpr, exprAttrNames, null, false);
+    }
+
+    @Test(timeout = 120000, expected = IllegalArgumentException.class)
+    public void testEmptyPkCols() {
+        // Test that empty pkCols throws IllegalArgumentException
+        String keyCondExpr = "#0 = :0";
+        exprAttrNames.put("#0", "hashKey");
+        List<PColumn> pkCols = new ArrayList<>();
+        new KeyConditionsHolder(keyCondExpr, exprAttrNames, pkCols, false);
+    }
+
+    @Test(timeout = 120000, expected = IllegalArgumentException.class)
+    public void testMoreThanTwoPkCols() {
+        // Test that more than 2 pkCols throws IllegalArgumentException
+        String keyCondExpr = "#0 = :0";
+        exprAttrNames.put("#0", "hashKey");
+        List<PColumn> pkCols = new ArrayList<>();
+
+        PColumn mockColumn1 = Mockito.mock(PColumn.class);
+        PName mockName1 = PNameFactory.newName("col1");
+        Mockito.when(mockColumn1.getName()).thenReturn(mockName1);
+        pkCols.add(mockColumn1);
+
+        PColumn mockColumn2 = Mockito.mock(PColumn.class);
+        PName mockName2 = PNameFactory.newName("col2");
+        Mockito.when(mockColumn2.getName()).thenReturn(mockName2);
+        pkCols.add(mockColumn2);
+
+        PColumn mockColumn3 = Mockito.mock(PColumn.class);
+        PName mockName3 = PNameFactory.newName("col3");
+        Mockito.when(mockColumn3.getName()).thenReturn(mockName3);
+        pkCols.add(mockColumn3);
+
+        new KeyConditionsHolder(keyCondExpr, exprAttrNames, pkCols, false);
+    }
+
+    @Test(timeout = 120000)
+    public void testSinglePkColWithOnlyPartitionKey() {
+        // Test that single pkCol works correctly with partition key only
+        String keyCondExpr = "#pk = :v0";
+        exprAttrNames.put("#pk", "userId");
+        List<PColumn> pkCols = createPkCols("userId");
+        KeyConditionsHolder keyConditions = new 
KeyConditionsHolder(keyCondExpr, exprAttrNames, pkCols, false);
+
+        Assert.assertEquals("userId", keyConditions.getPartitionKeyName());
+        Assert.assertEquals(":v0", keyConditions.getPartitionValue());
+        Assert.assertFalse(keyConditions.hasSortKey());
+        Assert.assertNull(keyConditions.getSortKeyName());
+    }
+
+    @Test(timeout = 120000)
+    public void testTwoPkColsWithBothKeys() {
+        // Test that two pkCols works correctly with both partition and sort 
keys
+        String keyCondExpr = "#pk = :v0 AND #sk > :v1";
+        exprAttrNames.put("#pk", "userId");
+        exprAttrNames.put("#sk", "timestamp");
+        List<PColumn> pkCols = createPkColsWithSortKey("userId", "timestamp");
+        KeyConditionsHolder keyConditions = new 
KeyConditionsHolder(keyCondExpr, exprAttrNames, pkCols, false);
+
+        Assert.assertEquals("userId", keyConditions.getPartitionKeyName());
+        Assert.assertEquals(":v0", keyConditions.getPartitionValue());
+        Assert.assertTrue(keyConditions.hasSortKey());
+        Assert.assertEquals("timestamp", keyConditions.getSortKeyName());
+        Assert.assertEquals(">", keyConditions.getSortKeyOperator());
+        Assert.assertEquals(":v1", keyConditions.getSortKeyValue1());
+    }
+
+    @Test(timeout = 120000)
+    public void testFailedKeyConditionExpressionWithContains() {
+        String keyCondExpr = "#pk = :v0 AND contains(#sk, :v1)";
+        exprAttrNames.put("#pk", "userId");
+        exprAttrNames.put("#sk", "tags");
+        List<PColumn> pkCols = createPkColsWithSortKey("userId", "tags");
+        try {
+            new KeyConditionsHolder(keyCondExpr, exprAttrNames, pkCols, false);
+            Assert.fail("Invalid KeyConditionExpression expected");
+        } catch (RuntimeException e) {
+            Assert.assertEquals("Invalid KeyConditionExpression: 
DocumentFieldContainsParseNode",
+                e.getMessage());
+
+        }
+    }
 }
\ No newline at end of file
diff --git a/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/QueryIT.java 
b/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/QueryIT.java
index 8ed4582..953d97a 100644
--- a/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/QueryIT.java
+++ b/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/QueryIT.java
@@ -916,6 +916,44 @@ public class QueryIT {
         return item;
     }
 
+    @Test(timeout = 120000)
+    public void testInvalidNotEqualOperatorInKeyCondition() throws Exception {
+        // Create table
+        final String tableName = testName.getMethodName();
+        CreateTableRequest createTableRequest =
+                DDLTestUtils.getCreateTableRequest(tableName, "attr_0",
+                        ScalarAttributeType.S, "attr_1", 
ScalarAttributeType.N);
+        phoenixDBClientV2.createTable(createTableRequest);
+        dynamoDbClient.createTable(createTableRequest);
+
+        // Query request with <> operator in key condition
+        QueryRequest.Builder qr = QueryRequest.builder().tableName(tableName);
+        qr.keyConditionExpression("#0 <> :v0");
+        Map<String, String> exprAttrNames = new HashMap<>();
+        exprAttrNames.put("#0", "attr_0");
+        qr.expressionAttributeNames(exprAttrNames);
+        Map<String, AttributeValue> exprAttrVal = new HashMap<>();
+        exprAttrVal.put(":v0", AttributeValue.builder().s("B").build());
+        qr.expressionAttributeValues(exprAttrVal);
+
+        // Verify that both DynamoDB and Phoenix return 400
+        try {
+            dynamoDbClient.query(qr.build());
+            Assert.fail("Expected DynamoDbException for DynamoDB");
+        } catch (DynamoDbException e) {
+            Assert.assertEquals(400, e.statusCode());
+            Assert.assertTrue(e.getMessage().contains("Invalid operator used 
in KeyConditionExpression: <>"));
+        }
+
+        try {
+            phoenixDBClientV2.query(qr.build());
+            Assert.fail("Expected DynamoDbException for Phoenix");
+        } catch (DynamoDbException e) {
+            Assert.assertEquals(400, e.statusCode());
+            Assert.assertTrue(e.getMessage().contains("Invalid operator used 
in KeyConditionExpression:"));
+        }
+    }
+
     public static Map<String, AttributeValue> getItem4() {
         Map<String, AttributeValue> item = new HashMap<>();
         item.put("attr_0", AttributeValue.builder().s("B").build());
diff --git 
a/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/QueryIndex1IT.java 
b/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/QueryIndex1IT.java
index 65810a3..6538999 100644
--- a/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/QueryIndex1IT.java
+++ b/phoenix-ddb-rest/src/test/java/org/apache/phoenix/ddb/QueryIndex1IT.java
@@ -566,4 +566,104 @@ public class QueryIndex1IT {
         item.put("Id3", AttributeValue.builder().s("bar").build());
         return item;
     }
+
+    @Test(timeout = 120000)
+    public void testGlobalIndexSortKeyBeforePartitionKey() throws SQLException 
{
+        // create table with keys [attr_0]
+        final String tableName = testName.getMethodName();
+        final String indexName = "G_IDX_" + tableName;
+        CreateTableRequest createTableRequest =
+                DDLTestUtils.getCreateTableRequest(tableName, "attr_0",
+                        ScalarAttributeType.S, null, null);
+        // create index on IdS, Id2
+        createTableRequest = DDLTestUtils.addIndexToRequest(true, 
createTableRequest, indexName, "IdS",
+                ScalarAttributeType.S, "Id2", ScalarAttributeType.N);
+        phoenixDBClientV2.createTable(createTableRequest);
+        dynamoDbClient.createTable(createTableRequest);
+
+        //put items
+        PutItemRequest putItemRequest1 = 
PutItemRequest.builder().tableName(tableName).item(getItem1()).build();
+        PutItemRequest putItemRequest2 = 
PutItemRequest.builder().tableName(tableName).item(getItem2()).build();
+        phoenixDBClientV2.putItem(putItemRequest1);
+        phoenixDBClientV2.putItem(putItemRequest2);
+        dynamoDbClient.putItem(putItemRequest1);
+        dynamoDbClient.putItem(putItemRequest2);
+
+        //query request using index with sort key condition before partition 
key
+        QueryRequest.Builder qr = QueryRequest.builder().tableName(tableName);
+        qr.indexName(indexName);
+        qr.keyConditionExpression("#1 < :v1 AND #0 = :v0");
+        Map<String, String> exprAttrNames = new HashMap<>();
+        exprAttrNames.put("#0", "IdS");
+        exprAttrNames.put("#1", "Id2");
+        qr.expressionAttributeNames(exprAttrNames);
+        Map<String, AttributeValue> exprAttrVal = new HashMap<>();
+        exprAttrVal.put(":v0", AttributeValue.builder().s("101.01").build());
+        exprAttrVal.put(":v1", AttributeValue.builder().n("2.1").build());
+        qr.expressionAttributeValues(exprAttrVal);
+
+        // query result
+        QueryResponse phoenixResult = phoenixDBClientV2.query(qr.build());
+        QueryResponse dynamoResult = dynamoDbClient.query(qr.build());
+        Assert.assertEquals(dynamoResult.count(), phoenixResult.count());
+        Assert.assertEquals(dynamoResult.items(), phoenixResult.items());
+        Assert.assertEquals(dynamoResult.scannedCount(), 
phoenixResult.scannedCount());
+
+        // check last evaluated key
+        Assert.assertTrue(dynamoResult.lastEvaluatedKey().isEmpty());
+        Assert.assertTrue(phoenixResult.lastEvaluatedKey().isEmpty());
+
+        // explain plan
+        TestUtils.validateIndexUsed(qr.build(), url);
+    }
+
+    @Test(timeout = 120000)
+    public void testGlobalIndexBeginsWithBeforePartitionKey() throws 
SQLException {
+        // create table with keys [attr_0]
+        final String tableName = testName.getMethodName();
+        final String indexName = "G_IDX_" + tableName;
+        CreateTableRequest createTableRequest =
+                DDLTestUtils.getCreateTableRequest(tableName, "attr_0",
+                        ScalarAttributeType.S, null, null);
+        // create index on IdS, Id3
+        createTableRequest = DDLTestUtils.addIndexToRequest(true, 
createTableRequest, indexName, "IdS",
+                ScalarAttributeType.S, "Id3", ScalarAttributeType.S);
+        phoenixDBClientV2.createTable(createTableRequest);
+        dynamoDbClient.createTable(createTableRequest);
+
+        //put items
+        PutItemRequest putItemRequest1 = 
PutItemRequest.builder().tableName(tableName).item(getItem1()).build();
+        PutItemRequest putItemRequest2 = 
PutItemRequest.builder().tableName(tableName).item(getItem2()).build();
+        phoenixDBClientV2.putItem(putItemRequest1);
+        phoenixDBClientV2.putItem(putItemRequest2);
+        dynamoDbClient.putItem(putItemRequest1);
+        dynamoDbClient.putItem(putItemRequest2);
+
+        //query request using index with begins_with before partition key
+        QueryRequest.Builder qr = QueryRequest.builder().tableName(tableName);
+        qr.indexName(indexName);
+        qr.keyConditionExpression("begins_with(#1, :v1) AND #0 = :v0");
+        Map<String, String> exprAttrNames = new HashMap<>();
+        exprAttrNames.put("#0", "IdS");
+        exprAttrNames.put("#1", "Id3");
+        qr.expressionAttributeNames(exprAttrNames);
+        Map<String, AttributeValue> exprAttrVal = new HashMap<>();
+        exprAttrVal.put(":v0", AttributeValue.builder().s("101.01").build());
+        exprAttrVal.put(":v1", AttributeValue.builder().s("fo").build());
+        qr.expressionAttributeValues(exprAttrVal);
+
+        // query result
+        QueryResponse phoenixResult = phoenixDBClientV2.query(qr.build());
+        QueryResponse dynamoResult = dynamoDbClient.query(qr.build());
+        Assert.assertEquals(dynamoResult.count(), phoenixResult.count());
+        Assert.assertEquals(dynamoResult.items(), phoenixResult.items());
+        Assert.assertEquals(dynamoResult.scannedCount(), 
phoenixResult.scannedCount());
+
+        // check last evaluated key
+        Assert.assertTrue(dynamoResult.lastEvaluatedKey().isEmpty());
+        Assert.assertTrue(phoenixResult.lastEvaluatedKey().isEmpty());
+
+        // explain plan
+        TestUtils.validateIndexUsed(qr.build(), url);
+    }
 }
diff --git 
a/phoenix-ddb-utils/src/main/java/org/apache/phoenix/ddb/utils/KeyConditionsHolder.java
 
b/phoenix-ddb-utils/src/main/java/org/apache/phoenix/ddb/utils/KeyConditionsHolder.java
index d983090..bc8b23b 100644
--- 
a/phoenix-ddb-utils/src/main/java/org/apache/phoenix/ddb/utils/KeyConditionsHolder.java
+++ 
b/phoenix-ddb-utils/src/main/java/org/apache/phoenix/ddb/utils/KeyConditionsHolder.java
@@ -1,17 +1,25 @@
 package org.apache.phoenix.ddb.utils;
 
+import org.apache.phoenix.parse.AndParseNode;
+import org.apache.phoenix.parse.BetweenParseNode;
+import org.apache.phoenix.parse.BsonExpressionParser;
+import org.apache.phoenix.parse.ComparisonParseNode;
+import org.apache.phoenix.parse.LiteralParseNode;
+import org.apache.phoenix.parse.ParseNode;
+import org.apache.phoenix.parse.DocumentFieldBeginsWithParseNode;
 import org.apache.phoenix.schema.PColumn;
 import org.apache.phoenix.schema.types.PVarbinaryEncoded;
 import org.apache.phoenix.schema.types.PVarchar;
 
+import java.sql.SQLException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import java.util.List;
 
 /**
- * Helper class to parse KeyConditionExpression provided in a DynamoDB request:
+ * Helper class to parse KeyConditionExpression provided in request:
  * partitionKeyName = :partitionkeyval AND SortKeyCondition
  *
  * SortKeyCondition can be either sortKeyName op :sortkeyval
@@ -23,10 +31,8 @@ import java.util.regex.Pattern;
  */
 public class KeyConditionsHolder {
 
-    private static final String KEY_REGEX =
-            
"([#\\w]+)\\s*=\\s*(:\\w+)(?:\\s+AND\\s+(?:begins_with\\s*\\(\\s*([#\\w]+)\\s*,\\s*(:\\w+)\\s*\\)|([#\\w]+)\\s*(=|>|<|<=|>=|BETWEEN)\\s*(:\\w+)(?:\\s+AND\\s*(:\\w+))?))?";
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(KeyConditionsHolder.class);
 
-    private static final Pattern KEY_PATTERN = Pattern.compile(KEY_REGEX);
     private String keyCondExpr;
     private Map<String, String> exprAttrNames;
     private List<PColumn> pkCols;
@@ -47,12 +53,16 @@ public class KeyConditionsHolder {
     private PColumn partitionKeyPKCol;
     private PColumn sortKeyPKCol;
 
-    public KeyConditionsHolder(String keyCondExpr, Map<String, String> 
exprAttrNames) {
-        this(keyCondExpr, exprAttrNames, null, false);
-    }
-
     public KeyConditionsHolder(String keyCondExpr, Map<String, String> 
exprAttrNames,
                                List<PColumn> pkCols, boolean useIndex) {
+        if (pkCols == null) {
+            throw new IllegalArgumentException("pkCols cannot be null");
+        }
+        if (pkCols.isEmpty() || pkCols.size() > 2) {
+            throw new IllegalArgumentException(
+                "pkCols must contain 1 or 2 columns, but got " + 
pkCols.size());
+        }
+
         this.keyCondExpr = keyCondExpr;
         this.exprAttrNames = (exprAttrNames != null) ? exprAttrNames : new 
HashMap<>();
         this.pkCols = pkCols;
@@ -146,35 +156,197 @@ public class KeyConditionsHolder {
     }
 
     /**
-     * Parse the provided KeyConditionExpression into individual components.
+     * Parse the provided KeyConditionExpression into individual components
+     * using BsonExpressionParser and Phoenix ParseNode tree.
+     *
+     * Handles combinations like:
+     * 1. pk = :v1
+     * 2. pk = :v1 AND begins_with(sk, :v2)
+     * 3. pk = :v1 AND sk BETWEEN :v3 AND :v4
+     * 4. pk = :v1 AND sk = :v2 (and other comparison operators)
+     * 5. sk = :v2 (and other comparison operators) AND pk = :v1
+     * 6. begins_with(sk, :v2) AND  pk = :v1
      */
     private void parse() {
-        Matcher matcher = KEY_PATTERN.matcher(keyCondExpr);
-        if (!matcher.find()) {
-            throw new RuntimeException("Unable to parse Key Condition 
Expression: " + keyCondExpr);
-        }
-        String partitionKey = matcher.group(1);
-        this.partitionValue = matcher.group(2);
-        this.beginsWithSortKey = matcher.group(3);
-        this.beginsWithSortKeyVal = matcher.group(4);
-        String sortKey = matcher.group(5);
-        this.sortKeyOperator = matcher.group(6);
-        this.sortKeyValue1 = matcher.group(7);
-        this.sortKeyValue2 = matcher.group(8);
-
-        //partition key name
-        this.partitionKeyName = exprAttrNames.getOrDefault(partitionKey, 
partitionKey);
-
-        // sort key name
-        this.sortKeyName = (sortKey != null)
-                ? exprAttrNames.getOrDefault(sortKey, sortKey)
-                : (beginsWithSortKey != null)
-                ? exprAttrNames.getOrDefault(beginsWithSortKey, 
beginsWithSortKey)
-                : null;
-
-        if(pkCols != null) {
+        BsonExpressionParser bsonExpressionParser = new 
BsonExpressionParser(keyCondExpr);
+        ParseNode parseNode;
+        try {
+            parseNode = bsonExpressionParser.parseExpression();
+            LOGGER.trace("Parsing KeyConditionExpression: {} -> ParseNode 
type: {}",
+                        keyCondExpr, parseNode.getClass().getSimpleName());
+
+            // Set up PK columns
             this.partitionKeyPKCol = pkCols.get(0);
-            this.sortKeyPKCol = (pkCols.size()==2) ? pkCols.get(1) : null;
+            this.sortKeyPKCol = (pkCols.size() == 2) ? pkCols.get(1) : null;
+
+            if (parseNode instanceof AndParseNode) {
+                List<ParseNode> children = parseNode.getChildren();
+                String partitionKeyColName = 
CommonServiceUtils.getKeyNameFromBsonValueFunc(
+                        partitionKeyPKCol.getName().getString());
+
+                // Process each condition
+                for (ParseNode child : children) {
+                    if (child instanceof ComparisonParseNode) {
+                        processComparisonExpression((ComparisonParseNode) 
child, partitionKeyColName);
+                    } else {
+                        // Non-comparison nodes (BETWEEN, begins_with) are 
always sort key conditions
+                        processParseNode(child);
+                    }
+                }
+            }
+            else {
+                // Single condition - must be partition key with =
+                processParseNode(parseNode);
+            }
+        } catch (SQLException e) {
+            throw new RuntimeException(String.format("BsonExpressionParser 
failed for expression: %s", keyCondExpr), e);
         }
     }
-}
+
+    /**
+     * Process a ParseNode and extract key condition information.
+     * Based on Phoenix SQLComparisonExpressionUtils approach.
+     */
+    private void processParseNode(ParseNode parseNode) {
+        if (parseNode instanceof ComparisonParseNode) {
+            // Handle single comparison: pk = :v1
+            String partitionKeyColName = 
CommonServiceUtils.getKeyNameFromBsonValueFunc(
+                    partitionKeyPKCol.getName().getString());
+            processComparisonExpression((ComparisonParseNode) parseNode, 
partitionKeyColName);
+        } else if (parseNode instanceof BetweenParseNode) {
+            // Handle BETWEEN: sk BETWEEN :v1 AND :v2
+            processBetweenExpression((BetweenParseNode) parseNode);
+        } else if (parseNode instanceof DocumentFieldBeginsWithParseNode) {
+            // Handle document field begins_with
+            processDocumentBeginsWithExpression(parseNode);
+        }
+        else {
+          throw new RuntimeException(String.format("Invalid 
KeyConditionExpression: "
+              + "%s", parseNode.getClass().getSimpleName()));
+        }
+    }
+
+    /**
+     * Process comparison expressions (=, <, >, <=, >=)
+     */
+    private void processComparisonExpression(ComparisonParseNode compNode, 
String partitionKeyColName) {
+        List<ParseNode> children = compNode.getChildren();
+        ParseNode leftNode = children.get(0);
+        ParseNode rightNode = children.get(1);
+        String operatorName = compNode.getFilterOp().toString();
+        String sqlOperator = mapFilterOpToSqlOperator(operatorName);
+
+        String columnName = extractColumnName(leftNode);
+        String resolvedName = resolveAttributeName(columnName);
+        String bindVariable = extractBindVariable(rightNode);
+
+        LOGGER.trace("Comparison: {}='{}' {} (SQL: {}) {}='{}'",
+                    leftNode.getClass().getSimpleName(), columnName,
+                    operatorName, sqlOperator,
+                    rightNode.getClass().getSimpleName(), bindVariable);
+
+        // Identify partition key condition:
+        // 1. Must use = operator
+        // 2. Column matches partition key from schema
+        if (sqlOperator.equals("=") && 
resolvedName.equals(partitionKeyColName)) {
+            this.partitionKeyName = resolvedName;
+            this.partitionValue = bindVariable;
+            LOGGER.trace("Found partition key: {} = {}", 
this.partitionKeyName, this.partitionValue);
+        } else {
+            // Any other condition is sort key
+            this.sortKeyName = resolvedName;
+            this.sortKeyOperator = sqlOperator;
+            this.sortKeyValue1 = bindVariable;
+            LOGGER.trace("Found sort key: {} {} {}", this.sortKeyName, 
this.sortKeyOperator, this.sortKeyValue1);
+        }
+    }
+
+    /**
+     * Process BETWEEN expressions
+     */
+    private void processBetweenExpression(BetweenParseNode betweenNode) {
+        ParseNode columnNode = betweenNode.getChildren().get(0);
+        ParseNode lowerBoundNode = betweenNode.getChildren().get(1);
+        ParseNode upperBoundNode = betweenNode.getChildren().get(2);
+
+        String columnName = extractColumnName(columnNode);
+        String lowerValue = extractBindVariable(lowerBoundNode);
+        String upperValue = extractBindVariable(upperBoundNode);
+
+        if (columnName != null && lowerValue != null && upperValue != null) {
+            this.sortKeyName = resolveAttributeName(columnName);
+            this.sortKeyOperator = "BETWEEN";
+            this.sortKeyValue1 = lowerValue;
+            this.sortKeyValue2 = upperValue;
+            LOGGER.trace("Sort key: {} BETWEEN {} AND {}",
+                        this.sortKeyName, this.sortKeyValue1, 
this.sortKeyValue2);
+        }
+    }
+
+    /**
+     * Process document field begins_with expressions (different ParseNode 
type)
+     */
+    private void processDocumentBeginsWithExpression(ParseNode node) {
+        LOGGER.trace("Processing document begins_with: {}", 
node.getClass().getSimpleName());
+
+        List<ParseNode> children = node.getChildren();
+
+        String columnName = extractColumnName(children.get(0));
+        String bindVariable = extractBindVariable(children.get(1));
+
+        String resolvedColumnName = resolveAttributeName(columnName);
+        this.beginsWithSortKey = resolvedColumnName;
+        this.beginsWithSortKeyVal = bindVariable;
+        this.sortKeyName = resolvedColumnName;
+        LOGGER.trace("Set document begins_with: {} begins_with {}",
+                    this.beginsWithSortKey, this.beginsWithSortKeyVal);
+    }
+
+    /**
+     * Extract column name from a ParseNode.
+     * key conditions always use string literals for column names.
+     */
+    private String extractColumnName(ParseNode node) {
+        return (String) ((LiteralParseNode) node).getValue();
+    }
+
+    /**
+     * Extract bind variable from a ParseNode.
+     * key conditions always use string literals starting with ':' for bind 
variables.
+     */
+    private String extractBindVariable(ParseNode node) {
+        return (String) ((LiteralParseNode) node).getValue();
+    }
+
+    /**
+     * Map Phoenix FilterOp enum names to SQL operators
+     */
+    private String mapFilterOpToSqlOperator(String filterOpName) {
+        if (filterOpName == null) {
+            return "=";
+        }
+
+        switch (filterOpName.toUpperCase()) {
+            case "EQUAL":
+                return "=";
+            case "GREATER":
+                return ">";
+            case "GREATER_OR_EQUAL":
+                return ">=";
+            case "LESS":
+                return "<";
+            case "LESS_OR_EQUAL":
+                return "<=";
+            default:
+                throw new RuntimeException(String.format("Invalid operator 
used in "
+                    + "KeyConditionExpression: %s", 
filterOpName.toUpperCase()));
+        }
+    }
+
+    /**
+     * Resolve attribute name using expression attribute names map
+     */
+    private String resolveAttributeName(String name) {
+      return exprAttrNames.getOrDefault(name, name);
+    }
+}
\ No newline at end of file


Reply via email to