Repository: getmoto/moto

Issue:
moto.dynamodb2.update_item fails when an attribute name in ConditionExpression contains `.`
## How to reproduce the issue?
Call `.update_item()` with a `ConditionExpression` that references an `ExpressionAttributeName` containing dots. Working unittest example below. The `#second = test@foo.com` causes the [condition_op here](https://github.com/spulec/moto/blob/master/moto/dynamodb2/models/__init__.py#L1405) to fail. I've confirmed this works against AWS Dynamo.

```python
import unittest
import boto3
from moto import mock_dynamodb2


def conditionally_remove_nested_attribute(table, record_id, email_like_str):
    table.update_item(
        Key={"id": record_id},
        UpdateExpression="REMOVE #first.#second, #other",
        ExpressionAttributeNames={
            "#first": "first",
            "#second": email_like_str,
            "#third": "third",
            "#other": "other"
        },
        ExpressionAttributeValues={":value": "VALUE", ":one": 1},
        ConditionExpression="size(#first.#second.#third) = :one AND contains(#first.#second.#third, :value)",
        ReturnValues="ALL_NEW",
    )


class TestDynamoCondition(unittest.TestCase):
    @mock_dynamodb2
    def test_conditionally_remove_nested_attribute(self):
        dynamodb = boto3.resource("dynamodb", region_name="us-east-2")
        table_name = "Test"
        dynamodb.create_table(
            TableName=table_name,
            KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
            AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
        )
        table = dynamodb.Table(table_name)

        email_like_str = "test@foo.com"
        record = {
            "id": "key-0",
            "first": {
                email_like_str: {"third": {"VALUE"}},
            },
        }
        table.put_item(Item=record)

        expected = {
            "id": "key-0",
            "first": {},
        }
        conditionally_remove_nested_attribute(table, "key-0", email_like_str)

        assert table.get_item(Key={"id": "key-0"})["Item"] == expected


if __name__ == "__main__":
    unittest.main()
```

## Expected
`.update_item()` parses the attribute references split by `#`, such that when `{"#second": "foo@test.com"}`, expression parsing does not split the `.com` from the string and cause the attribute to not be found.

## Actual
`botocore.errorfactory.ConditionalCheckFailedException: An error occurred (ConditionalCheckFailedException) when calling the UpdateItem operation: A condition specified in the operation could not be evaluated.` is raised due to parsing failure caused by splitting nested attributes on `.`

## Version
```
❯ pip show moto
Name: moto
Version: 2.2.6

❯ pip show botocore
Name: botocore
Version: 1.21.37

❯ pip show boto3
Name: boto3
Version: 1.18.37
```



Discussion:
Hi @jgoist-ff, welcome to Moto! Thanks for raising this issue and for providing a reproducible test case - that's much appreciated.

Resolving patch:
diff --git a/moto/dynamodb2/models/dynamo_type.py b/moto/dynamodb2/models/dynamo_type.py
--- a/moto/dynamodb2/models/dynamo_type.py
+++ b/moto/dynamodb2/models/dynamo_type.py
@@ -179,11 +179,7 @@ def child_attr(self, key):
         Returns DynamoType or None.
         """
         if isinstance(key, str) and self.is_map():
-            if "." in key and key.split(".")[0] in self.value:
-                return self.value[key.split(".")[0]].child_attr(
-                    ".".join(key.split(".")[1:])
-                )
-            elif "." not in key and key in self.value:
+            if key in self.value:
                 return DynamoType(self.value[key])
 
         if isinstance(key, int) and self.is_list():


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
@@ -2720,329 +2720,6 @@ def test_dynamodb_streams_2():
     assert "LatestStreamArn" in resp["TableDescription"]
 
 
-@mock_dynamodb2
-def test_condition_expressions():
-    client = boto3.client("dynamodb", region_name="us-east-1")
-
-    # Create the DynamoDB table.
-    client.create_table(
-        TableName="test1",
-        AttributeDefinitions=[
-            {"AttributeName": "client", "AttributeType": "S"},
-            {"AttributeName": "app", "AttributeType": "S"},
-        ],
-        KeySchema=[
-            {"AttributeName": "client", "KeyType": "HASH"},
-            {"AttributeName": "app", "KeyType": "RANGE"},
-        ],
-        ProvisionedThroughput={"ReadCapacityUnits": 123, "WriteCapacityUnits": 123},
-    )
-    client.put_item(
-        TableName="test1",
-        Item={
-            "client": {"S": "client1"},
-            "app": {"S": "app1"},
-            "match": {"S": "match"},
-            "existing": {"S": "existing"},
-        },
-    )
-
-    client.put_item(
-        TableName="test1",
-        Item={
-            "client": {"S": "client1"},
-            "app": {"S": "app1"},
-            "match": {"S": "match"},
-            "existing": {"S": "existing"},
-        },
-        ConditionExpression="attribute_exists(#existing) AND attribute_not_exists(#nonexistent) AND #match = :match",
-        ExpressionAttributeNames={
-            "#existing": "existing",
-            "#nonexistent": "nope",
-            "#match": "match",
-        },
-        ExpressionAttributeValues={":match": {"S": "match"}},
-    )
-
-    client.put_item(
-        TableName="test1",
-        Item={
-            "client": {"S": "client1"},
-            "app": {"S": "app1"},
-            "match": {"S": "match"},
-            "existing": {"S": "existing"},
-        },
-        ConditionExpression="NOT(attribute_exists(#nonexistent1) AND attribute_exists(#nonexistent2))",
-        ExpressionAttributeNames={"#nonexistent1": "nope", "#nonexistent2": "nope2"},
-    )
-
-    client.put_item(
-        TableName="test1",
-        Item={
-            "client": {"S": "client1"},
-            "app": {"S": "app1"},
-            "match": {"S": "match"},
-            "existing": {"S": "existing"},
-        },
-        ConditionExpression="attribute_exists(#nonexistent) OR attribute_exists(#existing)",
-        ExpressionAttributeNames={"#nonexistent": "nope", "#existing": "existing"},
-    )
-
-    client.put_item(
-        TableName="test1",
-        Item={
-            "client": {"S": "client1"},
-            "app": {"S": "app1"},
-            "match": {"S": "match"},
-            "existing": {"S": "existing"},
-        },
-        ConditionExpression="#client BETWEEN :a AND :z",
-        ExpressionAttributeNames={"#client": "client"},
-        ExpressionAttributeValues={":a": {"S": "a"}, ":z": {"S": "z"}},
-    )
-
-    client.put_item(
-        TableName="test1",
-        Item={
-            "client": {"S": "client1"},
-            "app": {"S": "app1"},
-            "match": {"S": "match"},
-            "existing": {"S": "existing"},
-        },
-        ConditionExpression="#client IN (:client1, :client2)",
-        ExpressionAttributeNames={"#client": "client"},
-        ExpressionAttributeValues={
-            ":client1": {"S": "client1"},
-            ":client2": {"S": "client2"},
-        },
-    )
-
-    with pytest.raises(client.exceptions.ConditionalCheckFailedException):
-        client.put_item(
-            TableName="test1",
-            Item={
-                "client": {"S": "client1"},
-                "app": {"S": "app1"},
-                "match": {"S": "match"},
-                "existing": {"S": "existing"},
-            },
-            ConditionExpression="attribute_exists(#nonexistent1) AND attribute_exists(#nonexistent2)",
-            ExpressionAttributeNames={
-                "#nonexistent1": "nope",
-                "#nonexistent2": "nope2",
-            },
-        )
-
-    with pytest.raises(client.exceptions.ConditionalCheckFailedException):
-        client.put_item(
-            TableName="test1",
-            Item={
-                "client": {"S": "client1"},
-                "app": {"S": "app1"},
-                "match": {"S": "match"},
-                "existing": {"S": "existing"},
-            },
-            ConditionExpression="NOT(attribute_not_exists(#nonexistent1) AND attribute_not_exists(#nonexistent2))",
-            ExpressionAttributeNames={
-                "#nonexistent1": "nope",
-                "#nonexistent2": "nope2",
-            },
-        )
-
-    with pytest.raises(client.exceptions.ConditionalCheckFailedException):
-        client.put_item(
-            TableName="test1",
-            Item={
-                "client": {"S": "client1"},
-                "app": {"S": "app1"},
-                "match": {"S": "match"},
-                "existing": {"S": "existing"},
-            },
-            ConditionExpression="attribute_exists(#existing) AND attribute_not_exists(#nonexistent) AND #match = :match",
-            ExpressionAttributeNames={
-                "#existing": "existing",
-                "#nonexistent": "nope",
-                "#match": "match",
-            },
-            ExpressionAttributeValues={":match": {"S": "match2"}},
-        )
-
-    # Make sure update_item honors ConditionExpression as well
-    client.update_item(
-        TableName="test1",
-        Key={"client": {"S": "client1"}, "app": {"S": "app1"}},
-        UpdateExpression="set #match=:match",
-        ConditionExpression="attribute_exists(#existing)",
-        ExpressionAttributeNames={"#existing": "existing", "#match": "match"},
-        ExpressionAttributeValues={":match": {"S": "match"}},
-    )
-
-    with pytest.raises(client.exceptions.ConditionalCheckFailedException) as exc:
-        client.update_item(
-            TableName="test1",
-            Key={"client": {"S": "client1"}, "app": {"S": "app1"}},
-            UpdateExpression="set #match=:match",
-            ConditionExpression="attribute_not_exists(#existing)",
-            ExpressionAttributeValues={":match": {"S": "match"}},
-            ExpressionAttributeNames={"#existing": "existing", "#match": "match"},
-        )
-    _assert_conditional_check_failed_exception(exc)
-
-    with pytest.raises(client.exceptions.ConditionalCheckFailedException) as exc:
-        client.update_item(
-            TableName="test1",
-            Key={"client": {"S": "client2"}, "app": {"S": "app1"}},
-            UpdateExpression="set #match=:match",
-            ConditionExpression="attribute_exists(#existing)",
-            ExpressionAttributeValues={":match": {"S": "match"}},
-            ExpressionAttributeNames={"#existing": "existing", "#match": "match"},
-        )
-    _assert_conditional_check_failed_exception(exc)
-
-    with pytest.raises(client.exceptions.ConditionalCheckFailedException):
-        client.delete_item(
-            TableName="test1",
-            Key={"client": {"S": "client1"}, "app": {"S": "app1"}},
-            ConditionExpression="attribute_not_exists(#existing)",
-            ExpressionAttributeValues={":match": {"S": "match"}},
-            ExpressionAttributeNames={"#existing": "existing", "#match": "match"},
-        )
-
-
-def _assert_conditional_check_failed_exception(exc):
-    err = exc.value.response["Error"]
-    err["Code"].should.equal("ConditionalCheckFailedException")
-    err["Message"].should.equal("The conditional request failed")
-
-
-@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")
-
-    client.create_table(
-        TableName="test",
-        KeySchema=[{"AttributeName": "forum_name", "KeyType": "HASH"}],
-        AttributeDefinitions=[{"AttributeName": "forum_name", "AttributeType": "S"}],
-        ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
-    )
-
-    client.put_item(
-        TableName="test", Item={"forum_name": {"S": "foo"}, "ttl": {"N": "bar"}}
-    )
-
-    def update_if_attr_doesnt_exist():
-        # Test nonexistent top-level attribute.
-        client.update_item(
-            TableName="test",
-            Key={"forum_name": {"S": "the-key"}, "subject": {"S": "the-subject"}},
-            UpdateExpression="set #new_state=:new_state, #ttl=:ttl",
-            ConditionExpression="attribute_not_exists(#new_state)",
-            ExpressionAttributeNames={"#new_state": "foobar", "#ttl": "ttl"},
-            ExpressionAttributeValues={
-                ":new_state": {"S": "some-value"},
-                ":ttl": {"N": "12345.67"},
-            },
-            ReturnValues="ALL_NEW",
-        )
-
-    update_if_attr_doesnt_exist()
-
-    # Second time should fail
-    with pytest.raises(client.exceptions.ConditionalCheckFailedException) as exc:
-        update_if_attr_doesnt_exist()
-    _assert_conditional_check_failed_exception(exc)
-
-
-@mock_dynamodb2
-def test_condition_expression__or_order():
-    client = boto3.client("dynamodb", region_name="us-east-1")
-
-    client.create_table(
-        TableName="test",
-        KeySchema=[{"AttributeName": "forum_name", "KeyType": "HASH"}],
-        AttributeDefinitions=[{"AttributeName": "forum_name", "AttributeType": "S"}],
-        ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
-    )
-
-    # ensure that the RHS of the OR expression is not evaluated if the LHS
-    # returns true (as it would result an error)
-    client.update_item(
-        TableName="test",
-        Key={"forum_name": {"S": "the-key"}},
-        UpdateExpression="set #ttl=:ttl",
-        ConditionExpression="attribute_not_exists(#ttl) OR #ttl <= :old_ttl",
-        ExpressionAttributeNames={"#ttl": "ttl"},
-        ExpressionAttributeValues={":ttl": {"N": "6"}, ":old_ttl": {"N": "5"}},
-    )
-
-
-@mock_dynamodb2
-def test_condition_expression__and_order():
-    client = boto3.client("dynamodb", region_name="us-east-1")
-
-    client.create_table(
-        TableName="test",
-        KeySchema=[{"AttributeName": "forum_name", "KeyType": "HASH"}],
-        AttributeDefinitions=[{"AttributeName": "forum_name", "AttributeType": "S"}],
-        ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
-    )
-
-    # ensure that the RHS of the AND expression is not evaluated if the LHS
-    # returns true (as it would result an error)
-    with pytest.raises(client.exceptions.ConditionalCheckFailedException) as exc:
-        client.update_item(
-            TableName="test",
-            Key={"forum_name": {"S": "the-key"}},
-            UpdateExpression="set #ttl=:ttl",
-            ConditionExpression="attribute_exists(#ttl) AND #ttl <= :old_ttl",
-            ExpressionAttributeNames={"#ttl": "ttl"},
-            ExpressionAttributeValues={":ttl": {"N": "6"}, ":old_ttl": {"N": "5"}},
-        )
-    _assert_conditional_check_failed_exception(exc)
-
-
 @mock_dynamodb2
 def test_query_gsi_with_range_key():
     dynamodb = boto3.client("dynamodb", region_name="us-east-1")
diff --git a/tests/test_dynamodb2/test_dynamodb_condition_expressions.py b/tests/test_dynamodb2/test_dynamodb_condition_expressions.py
new file mode 100644
--- /dev/null
+++ b/tests/test_dynamodb2/test_dynamodb_condition_expressions.py
@@ -0,0 +1,367 @@
+import boto3
+import pytest
+import sure  # noqa
+
+from decimal import Decimal
+from moto import mock_dynamodb2
+
+
+@mock_dynamodb2
+def test_condition_expression_with_dot_in_attr_name():
+    dynamodb = boto3.resource("dynamodb", region_name="us-east-2")
+    table_name = "Test"
+    dynamodb.create_table(
+        TableName=table_name,
+        KeySchema=[{"AttributeName": "id", "KeyType": "HASH"}],
+        AttributeDefinitions=[{"AttributeName": "id", "AttributeType": "S"}],
+    )
+    table = dynamodb.Table(table_name)
+
+    email_like_str = "test@foo.com"
+    record = {
+        "id": "key-0",
+        "first": {email_like_str: {"third": {"VALUE"}},},
+    }
+    table.put_item(Item=record)
+
+    table.update_item(
+        Key={"id": "key-0"},
+        UpdateExpression="REMOVE #first.#second, #other",
+        ExpressionAttributeNames={
+            "#first": "first",
+            "#second": email_like_str,
+            "#third": "third",
+            "#other": "other",
+        },
+        ExpressionAttributeValues={":value": "VALUE", ":one": 1},
+        ConditionExpression="size(#first.#second.#third) = :one AND contains(#first.#second.#third, :value)",
+        ReturnValues="ALL_NEW",
+    )
+
+    item = table.get_item(Key={"id": "key-0"})["Item"]
+    item.should.equal(
+        {"id": "key-0", "first": {},}
+    )
+
+
+@mock_dynamodb2
+def test_condition_expressions():
+    client = boto3.client("dynamodb", region_name="us-east-1")
+
+    # Create the DynamoDB table.
+    client.create_table(
+        TableName="test1",
+        AttributeDefinitions=[
+            {"AttributeName": "client", "AttributeType": "S"},
+            {"AttributeName": "app", "AttributeType": "S"},
+        ],
+        KeySchema=[
+            {"AttributeName": "client", "KeyType": "HASH"},
+            {"AttributeName": "app", "KeyType": "RANGE"},
+        ],
+        ProvisionedThroughput={"ReadCapacityUnits": 123, "WriteCapacityUnits": 123},
+    )
+    client.put_item(
+        TableName="test1",
+        Item={
+            "client": {"S": "client1"},
+            "app": {"S": "app1"},
+            "match": {"S": "match"},
+            "existing": {"S": "existing"},
+        },
+    )
+
+    client.put_item(
+        TableName="test1",
+        Item={
+            "client": {"S": "client1"},
+            "app": {"S": "app1"},
+            "match": {"S": "match"},
+            "existing": {"S": "existing"},
+        },
+        ConditionExpression="attribute_exists(#existing) AND attribute_not_exists(#nonexistent) AND #match = :match",
+        ExpressionAttributeNames={
+            "#existing": "existing",
+            "#nonexistent": "nope",
+            "#match": "match",
+        },
+        ExpressionAttributeValues={":match": {"S": "match"}},
+    )
+
+    client.put_item(
+        TableName="test1",
+        Item={
+            "client": {"S": "client1"},
+            "app": {"S": "app1"},
+            "match": {"S": "match"},
+            "existing": {"S": "existing"},
+        },
+        ConditionExpression="NOT(attribute_exists(#nonexistent1) AND attribute_exists(#nonexistent2))",
+        ExpressionAttributeNames={"#nonexistent1": "nope", "#nonexistent2": "nope2"},
+    )
+
+    client.put_item(
+        TableName="test1",
+        Item={
+            "client": {"S": "client1"},
+            "app": {"S": "app1"},
+            "match": {"S": "match"},
+            "existing": {"S": "existing"},
+        },
+        ConditionExpression="attribute_exists(#nonexistent) OR attribute_exists(#existing)",
+        ExpressionAttributeNames={"#nonexistent": "nope", "#existing": "existing"},
+    )
+
+    client.put_item(
+        TableName="test1",
+        Item={
+            "client": {"S": "client1"},
+            "app": {"S": "app1"},
+            "match": {"S": "match"},
+            "existing": {"S": "existing"},
+        },
+        ConditionExpression="#client BETWEEN :a AND :z",
+        ExpressionAttributeNames={"#client": "client"},
+        ExpressionAttributeValues={":a": {"S": "a"}, ":z": {"S": "z"}},
+    )
+
+    client.put_item(
+        TableName="test1",
+        Item={
+            "client": {"S": "client1"},
+            "app": {"S": "app1"},
+            "match": {"S": "match"},
+            "existing": {"S": "existing"},
+        },
+        ConditionExpression="#client IN (:client1, :client2)",
+        ExpressionAttributeNames={"#client": "client"},
+        ExpressionAttributeValues={
+            ":client1": {"S": "client1"},
+            ":client2": {"S": "client2"},
+        },
+    )
+
+    with pytest.raises(client.exceptions.ConditionalCheckFailedException):
+        client.put_item(
+            TableName="test1",
+            Item={
+                "client": {"S": "client1"},
+                "app": {"S": "app1"},
+                "match": {"S": "match"},
+                "existing": {"S": "existing"},
+            },
+            ConditionExpression="attribute_exists(#nonexistent1) AND attribute_exists(#nonexistent2)",
+            ExpressionAttributeNames={
+                "#nonexistent1": "nope",
+                "#nonexistent2": "nope2",
+            },
+        )
+
+    with pytest.raises(client.exceptions.ConditionalCheckFailedException):
+        client.put_item(
+            TableName="test1",
+            Item={
+                "client": {"S": "client1"},
+                "app": {"S": "app1"},
+                "match": {"S": "match"},
+                "existing": {"S": "existing"},
+            },
+            ConditionExpression="NOT(attribute_not_exists(#nonexistent1) AND attribute_not_exists(#nonexistent2))",
+            ExpressionAttributeNames={
+                "#nonexistent1": "nope",
+                "#nonexistent2": "nope2",
+            },
+        )
+
+    with pytest.raises(client.exceptions.ConditionalCheckFailedException):
+        client.put_item(
+            TableName="test1",
+            Item={
+                "client": {"S": "client1"},
+                "app": {"S": "app1"},
+                "match": {"S": "match"},
+                "existing": {"S": "existing"},
+            },
+            ConditionExpression="attribute_exists(#existing) AND attribute_not_exists(#nonexistent) AND #match = :match",
+            ExpressionAttributeNames={
+                "#existing": "existing",
+                "#nonexistent": "nope",
+                "#match": "match",
+            },
+            ExpressionAttributeValues={":match": {"S": "match2"}},
+        )
+
+    # Make sure update_item honors ConditionExpression as well
+    client.update_item(
+        TableName="test1",
+        Key={"client": {"S": "client1"}, "app": {"S": "app1"}},
+        UpdateExpression="set #match=:match",
+        ConditionExpression="attribute_exists(#existing)",
+        ExpressionAttributeNames={"#existing": "existing", "#match": "match"},
+        ExpressionAttributeValues={":match": {"S": "match"}},
+    )
+
+    with pytest.raises(client.exceptions.ConditionalCheckFailedException) as exc:
+        client.update_item(
+            TableName="test1",
+            Key={"client": {"S": "client1"}, "app": {"S": "app1"}},
+            UpdateExpression="set #match=:match",
+            ConditionExpression="attribute_not_exists(#existing)",
+            ExpressionAttributeValues={":match": {"S": "match"}},
+            ExpressionAttributeNames={"#existing": "existing", "#match": "match"},
+        )
+    _assert_conditional_check_failed_exception(exc)
+
+    with pytest.raises(client.exceptions.ConditionalCheckFailedException) as exc:
+        client.update_item(
+            TableName="test1",
+            Key={"client": {"S": "client2"}, "app": {"S": "app1"}},
+            UpdateExpression="set #match=:match",
+            ConditionExpression="attribute_exists(#existing)",
+            ExpressionAttributeValues={":match": {"S": "match"}},
+            ExpressionAttributeNames={"#existing": "existing", "#match": "match"},
+        )
+    _assert_conditional_check_failed_exception(exc)
+
+    with pytest.raises(client.exceptions.ConditionalCheckFailedException):
+        client.delete_item(
+            TableName="test1",
+            Key={"client": {"S": "client1"}, "app": {"S": "app1"}},
+            ConditionExpression="attribute_not_exists(#existing)",
+            ExpressionAttributeValues={":match": {"S": "match"}},
+            ExpressionAttributeNames={"#existing": "existing", "#match": "match"},
+        )
+
+
+def _assert_conditional_check_failed_exception(exc):
+    err = exc.value.response["Error"]
+    err["Code"].should.equal("ConditionalCheckFailedException")
+    err["Message"].should.equal("The conditional request failed")
+
+
+@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")
+
+    client.create_table(
+        TableName="test",
+        KeySchema=[{"AttributeName": "forum_name", "KeyType": "HASH"}],
+        AttributeDefinitions=[{"AttributeName": "forum_name", "AttributeType": "S"}],
+        ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
+    )
+
+    client.put_item(
+        TableName="test", Item={"forum_name": {"S": "foo"}, "ttl": {"N": "bar"}}
+    )
+
+    def update_if_attr_doesnt_exist():
+        # Test nonexistent top-level attribute.
+        client.update_item(
+            TableName="test",
+            Key={"forum_name": {"S": "the-key"}, "subject": {"S": "the-subject"}},
+            UpdateExpression="set #new_state=:new_state, #ttl=:ttl",
+            ConditionExpression="attribute_not_exists(#new_state)",
+            ExpressionAttributeNames={"#new_state": "foobar", "#ttl": "ttl"},
+            ExpressionAttributeValues={
+                ":new_state": {"S": "some-value"},
+                ":ttl": {"N": "12345.67"},
+            },
+            ReturnValues="ALL_NEW",
+        )
+
+    update_if_attr_doesnt_exist()
+
+    # Second time should fail
+    with pytest.raises(client.exceptions.ConditionalCheckFailedException) as exc:
+        update_if_attr_doesnt_exist()
+    _assert_conditional_check_failed_exception(exc)
+
+
+@mock_dynamodb2
+def test_condition_expression__or_order():
+    client = boto3.client("dynamodb", region_name="us-east-1")
+
+    client.create_table(
+        TableName="test",
+        KeySchema=[{"AttributeName": "forum_name", "KeyType": "HASH"}],
+        AttributeDefinitions=[{"AttributeName": "forum_name", "AttributeType": "S"}],
+        ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
+    )
+
+    # ensure that the RHS of the OR expression is not evaluated if the LHS
+    # returns true (as it would result an error)
+    client.update_item(
+        TableName="test",
+        Key={"forum_name": {"S": "the-key"}},
+        UpdateExpression="set #ttl=:ttl",
+        ConditionExpression="attribute_not_exists(#ttl) OR #ttl <= :old_ttl",
+        ExpressionAttributeNames={"#ttl": "ttl"},
+        ExpressionAttributeValues={":ttl": {"N": "6"}, ":old_ttl": {"N": "5"}},
+    )
+
+
+@mock_dynamodb2
+def test_condition_expression__and_order():
+    client = boto3.client("dynamodb", region_name="us-east-1")
+
+    client.create_table(
+        TableName="test",
+        KeySchema=[{"AttributeName": "forum_name", "KeyType": "HASH"}],
+        AttributeDefinitions=[{"AttributeName": "forum_name", "AttributeType": "S"}],
+        ProvisionedThroughput={"ReadCapacityUnits": 1, "WriteCapacityUnits": 1},
+    )
+
+    # ensure that the RHS of the AND expression is not evaluated if the LHS
+    # returns true (as it would result an error)
+    with pytest.raises(client.exceptions.ConditionalCheckFailedException) as exc:
+        client.update_item(
+            TableName="test",
+            Key={"forum_name": {"S": "the-key"}},
+            UpdateExpression="set #ttl=:ttl",
+            ConditionExpression="attribute_exists(#ttl) AND #ttl <= :old_ttl",
+            ExpressionAttributeNames={"#ttl": "ttl"},
+            ExpressionAttributeValues={":ttl": {"N": "6"}, ":old_ttl": {"N": "5"}},
+        )
+    _assert_conditional_check_failed_exception(exc)