Repository: getmoto/moto

Issue:
1.3.14 regression: dynamodb ConditionExpression can't handle substitutions from ExpressionAttributeValues
The following test script works on `1.3.13`, and also directly on dynamodb.

It fails on `1.3.14` and on the current master (4d5bf1c5c6f0fb1e60951635ca70a74cddddb461) with error: `botocore.errorfactory.ConditionalCheckFailedException: An error occurred (ConditionalCheckFailedException) when calling the UpdateItem operation: A condition specified in the operation could not be evaluated.`

```python
import boto3
from moto import mock_dynamodb2

table_name = 'my-table'
table_schema = {
    'KeySchema': [{
        'AttributeName': 'partitionKey',
        'KeyType': 'HASH'
    }],
    'AttributeDefinitions': [{
        'AttributeName': 'partitionKey',
        'AttributeType': 'S'
    }],
}

item = {
    'partitionKey': 'pk-1',
    'myAttr': 5,
}

with mock_dynamodb2():
    dynamodb = boto3.resource('dynamodb')
    dynamodb.create_table(TableName=table_name, **table_schema)
    table = dynamodb.Table(table_name)
    table.put_item(Item=item)

    # try to update the item we put in the table, this works on v1.3.13 but fails on v1.3.14
    table.update_item(
        Key={'partitionKey': 'pk-1'},
        UpdateExpression='ADD myAttr :one',
        ExpressionAttributeValues={
            ':zero': 0,
            ':one': 1,
        },
        ConditionExpression='myAttr > :zero',
    )
```


Discussion:

Resolving patch:
diff --git a/moto/dynamodb2/comparisons.py b/moto/dynamodb2/comparisons.py
--- a/moto/dynamodb2/comparisons.py
+++ b/moto/dynamodb2/comparisons.py
@@ -977,10 +977,8 @@ def expr(self, item):
         lhs = self.lhs.expr(item)
         rhs = self.rhs.expr(item)
         # In python3 None is not a valid comparator when using < or > so must be handled specially
-        if lhs and rhs:
+        if lhs is not None and rhs is not None:
             return lhs < rhs
-        elif lhs is None and rhs:
-            return True
         else:
             return False
 
@@ -992,10 +990,8 @@ def expr(self, item):
         lhs = self.lhs.expr(item)
         rhs = self.rhs.expr(item)
         # In python3 None is not a valid comparator when using < or > so must be handled specially
-        if lhs and rhs:
+        if lhs is not None and rhs is not None:
             return lhs > rhs
-        elif lhs and rhs is None:
-            return True
         else:
             return False
 
@@ -1025,10 +1021,8 @@ def expr(self, item):
         lhs = self.lhs.expr(item)
         rhs = self.rhs.expr(item)
         # In python3 None is not a valid comparator when using < or > so must be handled specially
-        if lhs and rhs:
+        if lhs is not None and rhs is not None:
             return lhs <= rhs
-        elif lhs is None and rhs or lhs is None and rhs is None:
-            return True
         else:
             return False
 
@@ -1040,10 +1034,8 @@ def expr(self, item):
         lhs = self.lhs.expr(item)
         rhs = self.rhs.expr(item)
         # In python3 None is not a valid comparator when using < or > so must be handled specially
-        if lhs and rhs:
+        if lhs is not None and rhs is not None:
             return lhs >= rhs
-        elif lhs and rhs is None or lhs is None and rhs is None:
-            return True
         else:
             return False
 


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
@@ -1719,6 +1719,32 @@ def test_scan_filter4():
     assert response["Count"] == 0
 
 
+@mock_dynamodb2
+def test_scan_filter_should_not_return_non_existing_attributes():
+    table_name = "my-table"
+    item = {"partitionKey": "pk-2", "my-attr": 42}
+    # Create table
+    res = boto3.resource("dynamodb", region_name="us-east-1")
+    res.create_table(
+        TableName=table_name,
+        KeySchema=[{"AttributeName": "partitionKey", "KeyType": "HASH"}],
+        AttributeDefinitions=[{"AttributeName": "partitionKey", "AttributeType": "S"}],
+        BillingMode="PAY_PER_REQUEST",
+    )
+    table = res.Table(table_name)
+    # Insert items
+    table.put_item(Item={"partitionKey": "pk-1"})
+    table.put_item(Item=item)
+    # Verify a few operations
+    # Assert we only find the item that has this attribute
+    table.scan(FilterExpression=Attr("my-attr").lt(43))["Items"].should.equal([item])
+    table.scan(FilterExpression=Attr("my-attr").lte(42))["Items"].should.equal([item])
+    table.scan(FilterExpression=Attr("my-attr").gte(42))["Items"].should.equal([item])
+    table.scan(FilterExpression=Attr("my-attr").gt(41))["Items"].should.equal([item])
+    # Sanity check that we can't find the item if the FE is wrong
+    table.scan(FilterExpression=Attr("my-attr").gt(43))["Items"].should.equal([])
+
+
 @mock_dynamodb2
 def test_bad_scan_filter():
     client = boto3.client("dynamodb", region_name="us-east-1")
@@ -2505,6 +2531,48 @@ def test_condition_expressions():
         )
 
 
+@mock_dynamodb2
+def test_condition_expression_numerical_attribute():
+    dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
+    dynamodb.create_table(
+        TableName="my-table",
+        KeySchema=[{"AttributeName": "partitionKey", "KeyType": "HASH"}],
+        AttributeDefinitions=[{"AttributeName": "partitionKey", "AttributeType": "S"}],
+    )
+    table = dynamodb.Table("my-table")
+    table.put_item(Item={"partitionKey": "pk-pos", "myAttr": 5})
+    table.put_item(Item={"partitionKey": "pk-neg", "myAttr": -5})
+
+    # try to update the item we put in the table using numerical condition expression
+    # Specifically, verify that we can compare with a zero-value
+    # First verify that > and >= work on positive numbers
+    update_numerical_con_expr(
+        key="pk-pos", con_expr="myAttr > :zero", res="6", table=table
+    )
+    update_numerical_con_expr(
+        key="pk-pos", con_expr="myAttr >= :zero", res="7", table=table
+    )
+    # Second verify that < and <= work on negative numbers
+    update_numerical_con_expr(
+        key="pk-neg", con_expr="myAttr < :zero", res="-4", table=table
+    )
+    update_numerical_con_expr(
+        key="pk-neg", con_expr="myAttr <= :zero", res="-3", table=table
+    )
+
+
+def update_numerical_con_expr(key, con_expr, res, table):
+    table.update_item(
+        Key={"partitionKey": key},
+        UpdateExpression="ADD myAttr :one",
+        ExpressionAttributeValues={":zero": 0, ":one": 1},
+        ConditionExpression=con_expr,
+    )
+    table.get_item(Key={"partitionKey": key})["Item"]["myAttr"].should.equal(
+        Decimal(res)
+    )
+
+
 @mock_dynamodb2
 def test_condition_expression__attr_doesnt_exist():
     client = boto3.client("dynamodb", region_name="us-east-1")