diff --git a/CHANGES.md b/CHANGES.md index 5eb9b3f..f1e522c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,8 @@ # Changelog ## Unreleased +- DynamoDB: Apply rough type evaluation and dispatching when computing + values for `UPDATE` statements ## 2024/08/17 v0.0.7 - DynamoDB: Fixed a syntax issue with `text` data type in `UPDATE` statements diff --git a/src/commons_codec/transform/dynamodb.py b/src/commons_codec/transform/dynamodb.py index bc439e2..d22cdb5 100644 --- a/src/commons_codec/transform/dynamodb.py +++ b/src/commons_codec/transform/dynamodb.py @@ -9,6 +9,7 @@ import simplejson as json import toolz +from commons_codec.util.data import is_container, is_number from commons_codec.vendor.boto3.dynamodb.types import TypeDeserializer logger = logging.getLogger(__name__) @@ -145,8 +146,9 @@ def values_to_update(self, keys: t.Dict[str, t.Dict[str, str]]) -> str: constraints: t.List[str] = [] for key_name, key_value in values_clause.items(): - key_value = str(key_value).replace("'", "''") - constraint = f"{self.DATA_COLUMN}['{key_name}'] = '{key_value}'" + if not is_container(key_value) and not is_number(key_value): + key_value = "'" + str(key_value).replace("'", "''") + "'" + constraint = f"{self.DATA_COLUMN}['{key_name}'] = {key_value}" constraints.append(constraint) return ", ".join(constraints) diff --git a/src/commons_codec/util/data.py b/src/commons_codec/util/data.py index d3930af..9c29d76 100644 --- a/src/commons_codec/util/data.py +++ b/src/commons_codec/util/data.py @@ -30,3 +30,7 @@ def is_number(s): pass return False + + +def is_container(value): + return isinstance(value, (dict, list, set)) diff --git a/tests/transform/test_dynamodb.py b/tests/transform/test_dynamodb.py index 86a6485..f3db2cb 100644 --- a/tests/transform/test_dynamodb.py +++ b/tests/transform/test_dynamodb.py @@ -55,7 +55,7 @@ }, "eventSource": "aws:dynamodb", } -MSG_MODIFY = { +MSG_MODIFY_BASIC = { "awsRegion": "us-east-1", "eventID": "24757579-ebfd-480a-956d-a1287d2ef707", "eventName": "MODIFY", @@ -84,6 +84,35 @@ }, "eventSource": "aws:dynamodb", } +MSG_MODIFY_NESTED = { + "awsRegion": "us-east-1", + "eventID": "24757579-ebfd-480a-956d-a1287d2ef707", + "eventName": "MODIFY", + "userIdentity": None, + "recordFormat": "application/json", + "tableName": "foo", + "dynamodb": { + "ApproximateCreationDateTime": 1720742302233719, + "Keys": {"device": {"S": "foo"}, "timestamp": {"S": "2024-07-12T01:17:42"}}, + "NewImage": { + "device": {"M": {"id": {"S": "bar"}, "serial": {"N": 12345}}}, + "tags": {"L": [{"S": "foo"}, {"S": "bar"}]}, + "empty_map": {"M": {}}, + "empty_list": {"L": []}, + "timestamp": {"S": "2024-07-12T01:17:42"}, + }, + "OldImage": { + "humidity": {"N": "84.84"}, + "temperature": {"N": "42.42"}, + "device": {"S": "foo"}, + "location": {"S": "Sydney"}, + "timestamp": {"S": "2024-07-12T01:17:42"}, + }, + "SizeBytes": 161, + "ApproximateCreationDateTimePrecision": "MICROSECOND", + }, + "eventSource": "aws:dynamodb", +} MSG_REMOVE = { "awsRegion": "us-east-1", "eventID": "ff4e68ab-0820-4a0c-80b2-38753e8e00e5", @@ -145,10 +174,18 @@ def test_decode_cdc_insert_nested(): ) -def test_decode_cdc_modify(): +def test_decode_cdc_modify_basic(): + assert ( + DynamoCDCTranslatorCrateDB(table_name="foo").to_sql(MSG_MODIFY_BASIC) == 'UPDATE "foo" ' + "SET data['humidity'] = 84.84, data['temperature'] = 55.66, data['location'] = 'Sydney' " + "WHERE data['device'] = 'foo' AND data['timestamp'] = '2024-07-12T01:17:42';" + ) + + +def test_decode_cdc_modify_nested(): assert ( - DynamoCDCTranslatorCrateDB(table_name="foo").to_sql(MSG_MODIFY) == 'UPDATE "foo" ' - "SET data['humidity'] = '84.84', data['temperature'] = '55.66', data['location'] = 'Sydney' " + DynamoCDCTranslatorCrateDB(table_name="foo").to_sql(MSG_MODIFY_NESTED) == 'UPDATE "foo" ' + "SET data['tags'] = ['foo', 'bar'], data['empty_map'] = {}, data['empty_list'] = [] " "WHERE data['device'] = 'foo' AND data['timestamp'] = '2024-07-12T01:17:42';" )