diff --git a/phoenix-ddb-rest/src/main/java/org/apache/phoenix/ddb/service/utils/DQLUtils.java b/phoenix-ddb-rest/src/main/java/org/apache/phoenix/ddb/service/utils/DQLUtils.java index de64a72..5fddeb8 100644 --- a/phoenix-ddb-rest/src/main/java/org/apache/phoenix/ddb/service/utils/DQLUtils.java +++ b/phoenix-ddb-rest/src/main/java/org/apache/phoenix/ddb/service/utils/DQLUtils.java @@ -187,6 +187,10 @@ public static void addFilterCondition(boolean isQuery, StringBuilder queryBuilde */ public static void setKeyValueOnStatement(PreparedStatement stmt, int index, Map attrVal, boolean isBeginsWith) throws SQLException { + if (attrVal == null) { + throw new ValidationException( + "An expression attribute value used is not defined."); + } if (attrVal.containsKey("N")) { stmt.setDouble(index, Double.parseDouble((String) attrVal.get("N"))); } else if (attrVal.containsKey("S")) { 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 d18491e..ce5922b 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,130 @@ public void testInvalidNotEqualOperatorInKeyCondition() throws Exception { } } + // PHOENIX-7900: Test missing ExpressionAttributeValues + @Test(timeout = 120000) + public void testQueryWithMissingPartitionKeyValue() throws Exception { + final String tableName = testName.getMethodName(); + CreateTableRequest createTableRequest = + DDLTestUtils.getCreateTableRequest(tableName, "pk", + ScalarAttributeType.S, "sk", ScalarAttributeType.S); + phoenixDBClientV2.createTable(createTableRequest); + dynamoDbClient.createTable(createTableRequest); + + Map item = new HashMap<>(); + item.put("pk", AttributeValue.builder().s("test-pk").build()); + item.put("sk", AttributeValue.builder().s("test-sk").build()); + phoenixDBClientV2.putItem(PutItemRequest.builder().tableName(tableName).item(item).build()); + + // Query with KeyConditionExpression that references :v0, but don't provide :v0 + Map exprAttrVals = new HashMap<>(); + // :v0 is missing! + QueryRequest queryRequest = QueryRequest.builder() + .tableName(tableName) + .keyConditionExpression("pk = :v0") + .expressionAttributeValues(exprAttrVals) + .build(); + + // Verify both DynamoDB and Phoenix return 400 + try { + dynamoDbClient.query(queryRequest); + Assert.fail("Expected DynamoDbException for DynamoDB"); + } catch (DynamoDbException e) { + Assert.assertEquals(400, e.statusCode()); + } + + try { + phoenixDBClientV2.query(queryRequest); + Assert.fail("Expected DynamoDbException for Phoenix"); + } catch (DynamoDbException e) { + Assert.assertEquals(400, e.statusCode()); + Assert.assertTrue(e.awsErrorDetails().errorCode().contains("ValidationException")); + } + } + + @Test(timeout = 120000) + public void testQueryWithMissingSortKeyValue() throws Exception { + final String tableName = testName.getMethodName(); + CreateTableRequest createTableRequest = + DDLTestUtils.getCreateTableRequest(tableName, "pk", + ScalarAttributeType.S, "sk", ScalarAttributeType.N); + phoenixDBClientV2.createTable(createTableRequest); + dynamoDbClient.createTable(createTableRequest); + + Map item = new HashMap<>(); + item.put("pk", AttributeValue.builder().s("test-pk").build()); + item.put("sk", AttributeValue.builder().n("10").build()); + phoenixDBClientV2.putItem(PutItemRequest.builder().tableName(tableName).item(item).build()); + + // Query with pk = :v0 AND sk < :v1, provide :v0 but not :v1 + Map exprAttrVals = new HashMap<>(); + exprAttrVals.put(":v0", AttributeValue.builder().s("test-pk").build()); + // :v1 is missing! + QueryRequest queryRequest = QueryRequest.builder() + .tableName(tableName) + .keyConditionExpression("pk = :v0 AND sk < :v1") + .expressionAttributeValues(exprAttrVals) + .build(); + + // Verify both DynamoDB and Phoenix return 400 + try { + dynamoDbClient.query(queryRequest); + Assert.fail("Expected DynamoDbException for DynamoDB"); + } catch (DynamoDbException e) { + Assert.assertEquals(400, e.statusCode()); + } + + try { + phoenixDBClientV2.query(queryRequest); + Assert.fail("Expected DynamoDbException for Phoenix"); + } catch (DynamoDbException e) { + Assert.assertEquals(400, e.statusCode()); + Assert.assertTrue(e.awsErrorDetails().errorCode().contains("ValidationException")); + } + } + + @Test(timeout = 120000) + public void testQueryWithMissingBetweenValue() throws Exception { + final String tableName = testName.getMethodName(); + CreateTableRequest createTableRequest = + DDLTestUtils.getCreateTableRequest(tableName, "pk", + ScalarAttributeType.S, "sk", ScalarAttributeType.N); + phoenixDBClientV2.createTable(createTableRequest); + dynamoDbClient.createTable(createTableRequest); + + Map item = new HashMap<>(); + item.put("pk", AttributeValue.builder().s("test-pk").build()); + item.put("sk", AttributeValue.builder().n("10").build()); + phoenixDBClientV2.putItem(PutItemRequest.builder().tableName(tableName).item(item).build()); + + // Query with BETWEEN but missing second value + Map exprAttrVals = new HashMap<>(); + exprAttrVals.put(":v0", AttributeValue.builder().s("test-pk").build()); + exprAttrVals.put(":v1", AttributeValue.builder().n("5").build()); + // :v2 is missing! + QueryRequest queryRequest = QueryRequest.builder() + .tableName(tableName) + .keyConditionExpression("pk = :v0 AND sk BETWEEN :v1 AND :v2") + .expressionAttributeValues(exprAttrVals) + .build(); + + // Verify both DynamoDB and Phoenix return 400 + try { + dynamoDbClient.query(queryRequest); + Assert.fail("Expected DynamoDbException for DynamoDB"); + } catch (DynamoDbException e) { + Assert.assertEquals(400, e.statusCode()); + } + + try { + phoenixDBClientV2.query(queryRequest); + Assert.fail("Expected DynamoDbException for Phoenix"); + } catch (DynamoDbException e) { + Assert.assertEquals(400, e.statusCode()); + Assert.assertTrue(e.awsErrorDetails().errorCode().contains("ValidationException")); + } + } + public static Map getItem4() { Map item = new HashMap<>(); item.put("attr_0", AttributeValue.builder().s("B").build());