Repository: getmoto/moto

Issue:
DynamoDB query does not fail when >= (gte) is used on table partition key
boto3=1.20.11
botocore=1.23.11
moto==2.2.16


When >= is used on DynamoDB query **KeyConditionExpression**, an exception is expected but the actual result is an empty list.

When **Actor** is the partition key of a table
Query KeyConditionExpression `Key("Actor").gt("K")` result in botocore ClientError, which is expected. But `Key("Actor").gte("K")` does not raise exception which is unexpected. AWS [documentation](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.KeyConditionExpressions) mentions _You must specify the partition key name and value as an equality condition._ 

Please see the test file below.


```py
from boto3.dynamodb.conditions import Attr, Key
import pytest
import boto3
from botocore.exceptions import ClientError
import json
from moto.dynamodb2 import mock_dynamodb2

TABLE_NAME = "Movie_Table"


def setup_table(records):
    """Creates table, add records and return the table object"""

    ddb_resource = boto3.resource("dynamodb", region_name="eu-west-2")
    table = ddb_resource.create_table(
        TableName=TABLE_NAME,
        KeySchema=[
            {"AttributeName": "Actor", "KeyType": "HASH"},
            {"AttributeName": "Movie", "KeyType": "RANGE"},
        ],
        AttributeDefinitions=[
            {"AttributeName": "Actor", "AttributeType": "S"},
            {"AttributeName": "Movie", "AttributeType": "S"}
        ],
        ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
    )

    for item in records:
        table.put_item(Item=item)

    return table


@pytest.fixture
def movie_records():
    return [
        {
            "Role": "C__p_r",
            "Movie": "_nt_rst_ll_r",
            "Genre": "Sci-fi/Adventure",
            "Year": "2014",
            "Actor": "M_tth_w McC_n__gh_y",
            "__Version__": "1",
        },
        {
            "Role": "_l_f",
            "Movie": "Fr_z_n",
            "Genre": "Family/Musical",
            "Year": "2013",
            "Actor": "J_sh G_d",
            "__Version__": "1",
        },
    ]


@mock_dynamodb2
def test_query(movie_records):
    table = setup_table(movie_records)
    conditions = Key("Actor").gte("K")
    # conditions = Key("Actor").gt("K") # Works as expected (Raises exception)

    with pytest.raises(ClientError):
        result = table.query(KeyConditionExpression=conditions)
```

Pytest log

```log
    @mock_dynamodb2
    def test_query(movie_records):
        table = setup_table(movie_records)
        conditions = Key("Actor").gte("K")
        # conditions = Key("Actor").gt("K") # Works as expected (Raises exception)

        with pytest.raises(ClientError):
>           result = table.query(KeyConditionExpression=conditions)
E           Failed: DID NOT RAISE <class 'botocore.exceptions.ClientError'>

test_query_bug.py:89: Failed
```


Discussion:
Thanks for raising this, and for providing the test case, @sujithcphilip, that's much appreciated. Marking it as a bug.

Resolving patch:
diff --git a/moto/dynamodb2/responses.py b/moto/dynamodb2/responses.py
--- a/moto/dynamodb2/responses.py
+++ b/moto/dynamodb2/responses.py
@@ -698,7 +698,7 @@ def query(self):
                 range_comparison = None
                 range_values = []
 
-            if "=" not in hash_key_expression:
+            if not re.search("[^<>]=", hash_key_expression):
                 return self.error(
                     "com.amazonaws.dynamodb.v20111205#ValidationException",
                     "Query key condition not supported",


Test patch:
diff --git a/tests/test_dynamodb2/test_dynamodb.py b/tests/test_dynamodb2/test_dynamodb.py
--- a/tests/test_dynamodb2/test_dynamodb.py
+++ b/tests/test_dynamodb2/test_dynamodb.py
@@ -3485,36 +3485,6 @@ def assert_failure_due_to_item_size_to_update(func, **kwargs):
     )
 
 
-@mock_dynamodb2
-# https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-KeyConditionExpression
-def test_hash_key_cannot_use_begins_with_operations():
-    dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
-    table = dynamodb.create_table(
-        TableName="test-table",
-        KeySchema=[{"AttributeName": "key", "KeyType": "HASH"}],
-        AttributeDefinitions=[{"AttributeName": "key", "AttributeType": "S"}],
-        ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
-    )
-
-    items = [
-        {"key": "prefix-$LATEST", "value": "$LATEST"},
-        {"key": "prefix-DEV", "value": "DEV"},
-        {"key": "prefix-PROD", "value": "PROD"},
-    ]
-
-    with table.batch_writer() as batch:
-        for item in items:
-            batch.put_item(Item=item)
-
-    table = dynamodb.Table("test-table")
-    with pytest.raises(ClientError) as ex:
-        table.query(KeyConditionExpression=Key("key").begins_with("prefix-"))
-    ex.value.response["Error"]["Code"].should.equal("ValidationException")
-    ex.value.response["Error"]["Message"].should.equal(
-        "Query key condition not supported"
-    )
-
-
 @mock_dynamodb2
 def test_update_supports_complex_expression_attribute_values():
     client = boto3.client("dynamodb", region_name="us-east-1")
diff --git a/tests/test_dynamodb2/test_dynamodb_exceptions.py b/tests/test_dynamodb2/test_dynamodb_exceptions.py
--- a/tests/test_dynamodb2/test_dynamodb_exceptions.py
+++ b/tests/test_dynamodb2/test_dynamodb_exceptions.py
@@ -3,6 +3,7 @@
 import sure  # noqa # pylint: disable=unused-import
 
 from botocore.exceptions import ClientError
+from boto3.dynamodb.conditions import Key
 from moto import mock_dynamodb2
 
 
@@ -374,3 +375,56 @@ def test_put_item_wrong_attribute_type():
     err["Message"].should.equal(
         "One or more parameter values were invalid: Type mismatch for key created_at expected: N actual: S"
     )
+
+
+@mock_dynamodb2
+# https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-KeyConditionExpression
+def test_hash_key_cannot_use_begins_with_operations():
+    dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
+    table = dynamodb.create_table(
+        TableName="test-table",
+        KeySchema=[{"AttributeName": "key", "KeyType": "HASH"}],
+        AttributeDefinitions=[{"AttributeName": "key", "AttributeType": "S"}],
+        ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
+    )
+
+    items = [
+        {"key": "prefix-$LATEST", "value": "$LATEST"},
+        {"key": "prefix-DEV", "value": "DEV"},
+        {"key": "prefix-PROD", "value": "PROD"},
+    ]
+
+    with table.batch_writer() as batch:
+        for item in items:
+            batch.put_item(Item=item)
+
+    table = dynamodb.Table("test-table")
+    with pytest.raises(ClientError) as ex:
+        table.query(KeyConditionExpression=Key("key").begins_with("prefix-"))
+    ex.value.response["Error"]["Code"].should.equal("ValidationException")
+    ex.value.response["Error"]["Message"].should.equal(
+        "Query key condition not supported"
+    )
+
+
+# Test this again, but with manually supplying an operator
+@mock_dynamodb2
+@pytest.mark.parametrize("operator", ["<", "<=", ">", ">="])
+def test_hash_key_can_only_use_equals_operations(operator):
+    dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
+    dynamodb.create_table(
+        TableName="test-table",
+        KeySchema=[{"AttributeName": "pk", "KeyType": "HASH"}],
+        AttributeDefinitions=[{"AttributeName": "pk", "AttributeType": "S"}],
+        ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
+    )
+    table = dynamodb.Table("test-table")
+
+    with pytest.raises(ClientError) as exc:
+        table.query(
+            KeyConditionExpression=f"pk {operator} :pk",
+            ExpressionAttributeValues={":pk": "p"},
+        )
+    err = exc.value.response["Error"]
+    err["Code"].should.equal("ValidationException")
+    err["Message"].should.equal("Query key condition not supported")