Repository: getmoto/moto

Issue:
dynamodb update item with if_not_exists in update-expression
When I did update_item with Update expression as `SET a = if_not_exists(a, :a)`, I got `ConditionalCheckFailedException`.

code to reproduce:
```
import boto3
db = boto3.resource('dynamodb')
table = db.Table('xxx')
table.update_item(
  Key={'id': 'xxx-1', 'timestamp': 123},
  UpdateExpression='SET created_at = if_not_exists(created_at, :created_at)',
  ExpressionAttributeValues={':created_at': 123}
)
```

when executing the above code, get the below error.
```
# ConditionalCheckFailedException: An error occurred (ConditionalCheckFailedException) when calling the UpdateItem operation: 
```

the below is the expected response.
```
##### expected response #####
{'ResponseMetadata': {'HTTPHeaders': {'connection': 'keep-alive',
   'content-length': '2',
   'content-type': 'application/x-amz-json-1.0',
   'date': 'Wed, 22 Nov 2017 09:04:38 GMT',
   'server': 'Server',
   'x-amz-crc32': '2745614147',
   'x-amzn-requestid': 'M7QP71DV81RKGTSPO4J2QU29M7VV4KQNSO5AEMVJF66Q9ASUAAJG'},
  'HTTPStatusCode': 200,
  'RequestId': 'M7QP71DV81RKGTSPO4J2QU29M7VV4KQNSO5AEMVJF66Q9ASUAAJG',
  'RetryAttempts': 0}}
```

version : `1.1.25`


Discussion:
I'll have a look at this.

Resolving patch:
diff --git a/moto/dynamodb2/models.py b/moto/dynamodb2/models.py
--- a/moto/dynamodb2/models.py
+++ b/moto/dynamodb2/models.py
@@ -135,7 +135,9 @@ def update(self, update_expression, expression_attribute_names, expression_attri
         assert len(parts) % 2 == 0, "Mismatched operators and values in update expression: '{}'".format(update_expression)
         for action, valstr in zip(parts[:-1:2], parts[1::2]):
             action = action.upper()
-            values = valstr.split(',')
+
+            # "Should" retain arguments in side (...)
+            values = re.split(r',(?![^(]*\))', valstr)
             for value in values:
                 # A Real value
                 value = value.lstrip(":").rstrip(",").strip()
@@ -145,9 +147,23 @@ def update(self, update_expression, expression_attribute_names, expression_attri
                 if action == "REMOVE":
                     self.attrs.pop(value, None)
                 elif action == 'SET':
-                    key, value = value.split("=")
+                    key, value = value.split("=", 1)
                     key = key.strip()
                     value = value.strip()
+
+                    # If not exists, changes value to a default if needed, else its the same as it was
+                    if value.startswith('if_not_exists'):
+                        # Function signature
+                        match = re.match(r'.*if_not_exists\((?P<path>.+),\s*(?P<default>.+)\).*', value)
+                        if not match:
+                            raise TypeError
+
+                        path, value = match.groups()
+
+                        # If it already exists, get its value so we dont overwrite it
+                        if path in self.attrs:
+                            value = self.attrs[path].cast_value
+
                     if value in expression_attribute_values:
                         value = DynamoType(expression_attribute_values[value])
                     else:


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
@@ -1067,3 +1067,71 @@ def test_update_item_on_map():
 
     resp = table.scan()
     resp['Items'][0]['body'].should.equal({'nested': {'data': 'new_value'}})
+
+
+# https://github.com/spulec/moto/issues/1358
+@mock_dynamodb2
+def test_update_if_not_exists():
+    dynamodb = boto3.resource('dynamodb', region_name='us-east-1')
+
+    # Create the DynamoDB table.
+    dynamodb.create_table(
+        TableName='users',
+        KeySchema=[
+            {
+                'AttributeName': 'forum_name',
+                'KeyType': 'HASH'
+            },
+            {
+                'AttributeName': 'subject',
+                'KeyType': 'RANGE'
+            },
+        ],
+        AttributeDefinitions=[
+            {
+                'AttributeName': 'forum_name',
+                'AttributeType': 'S'
+            },
+            {
+                'AttributeName': 'subject',
+                'AttributeType': 'S'
+            },
+        ],
+        ProvisionedThroughput={
+            'ReadCapacityUnits': 5,
+            'WriteCapacityUnits': 5
+        }
+    )
+    table = dynamodb.Table('users')
+
+    table.put_item(Item={
+        'forum_name': 'the-key',
+        'subject': '123'
+    })
+
+    table.update_item(Key={
+        'forum_name': 'the-key',
+        'subject': '123'
+        },
+        UpdateExpression='SET created_at = if_not_exists(created_at, :created_at)',
+        ExpressionAttributeValues={
+            ':created_at': 123
+        }
+    )
+
+    resp = table.scan()
+    assert resp['Items'][0]['created_at'] == 123
+
+    table.update_item(Key={
+        'forum_name': 'the-key',
+        'subject': '123'
+        },
+        UpdateExpression='SET created_at = if_not_exists(created_at, :created_at)',
+        ExpressionAttributeValues={
+            ':created_at': 456
+        }
+    )
+
+    resp = table.scan()
+    # Still the original value
+    assert resp['Items'][0]['created_at'] == 123