Skip to content

Commit a3eade1

Browse files
authored
Merge pull request #436 from jgilbert01/task/key-name-safe-replacement
Task/key name safe replacement
2 parents 39a7f9b + 3f26aa7 commit a3eade1

File tree

4 files changed

+59
-25
lines changed

4 files changed

+59
-25
lines changed

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "aws-lambda-stream",
3-
"version": "1.1.17",
3+
"version": "1.1.18",
44
"description": "Create stream processors with AWS Lambda functions.",
55
"keywords": [
66
"aws",
@@ -128,4 +128,4 @@
128128
"dependencies": {
129129
"object-sizeof": "^2.6.0"
130130
}
131-
}
131+
}

src/sinks/dynamodb.js

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@ export const updateExpression = (Item) => {
1313
// If this attribute ends with '_delete'...assume we're deleting values from a set.
1414
const isDeleteSet = key.endsWith('_delete');
1515
const baseKey = isDeleteSet ? key.replace(/_delete$/, '') : key;
16-
acc.ExpressionAttributeNames[`#${baseKey}`] = baseKey;
16+
const alias = baseKey.replace(/([^a-z0-9_])/gi, (char) =>
17+
`_x${char.charCodeAt(0).toString(16)}_`);
18+
19+
acc.ExpressionAttributeNames[`#${alias}`] = baseKey;
1720

1821
if (value === null) {
19-
acc.removeClauses.push(`#${baseKey}`);
22+
acc.removeClauses.push(`#${alias}`);
2023
return acc;
2124
}
2225

@@ -25,19 +28,19 @@ export const updateExpression = (Item) => {
2528
if (!(setValue instanceof Set)) {
2629
setValue = new Set([setValue]);
2730
}
28-
acc.ExpressionAttributeValues[`:${key}`] = setValue;
29-
acc.deleteClauses.push(`#${baseKey} :${key}`);
31+
acc.ExpressionAttributeValues[`:${alias}_delete`] = setValue;
32+
acc.deleteClauses.push(`#${alias} :${alias}_delete`);
3033
return acc;
3134
}
3235

3336
if (value instanceof Set) {
34-
acc.ExpressionAttributeValues[`:${key}`] = value;
35-
acc.addClauses.push(`#${key} :${key}`);
37+
acc.ExpressionAttributeValues[`:${alias}`] = value;
38+
acc.addClauses.push(`#${alias} :${alias}`);
3639
return acc;
3740
}
3841

39-
acc.ExpressionAttributeValues[`:${key}`] = value;
40-
acc.setClauses.push(`#${key} = :${key}`);
42+
acc.ExpressionAttributeValues[`:${alias}`] = value;
43+
acc.setClauses.push(`#${alias} = :${alias}`);
4144
return acc;
4245
}, {
4346
ExpressionAttributeNames: {},

test/unit/sinks/dynamodb.test.js

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,26 @@ describe('sinks/dynamodb.js', () => {
2121

2222
it('should calculate updateExpression', () => {
2323
expect(updateExpression({
24-
id: '2f8ac025-d9e3-48f9-ba80-56487ddf0b89',
25-
name: 'Thing One',
26-
description: 'This is thing one.',
27-
status: undefined,
28-
status2: null,
29-
discriminator: 'thing',
30-
latched: true,
31-
ttl: ttl(1540454400000, 30),
32-
timestamp: 1540454400000,
24+
'id': '2f8ac025-d9e3-48f9-ba80-56487ddf0b89',
25+
'name': 'Thing One',
26+
'description': 'This is thing one.',
27+
'status': undefined,
28+
'status2': null,
29+
'discriminator': 'thing',
30+
'latched': true,
31+
'ttl': ttl(1540454400000, 30),
32+
'timestamp': 1540454400000,
33+
'some unsafe att name': true,
34+
'some unsafe att name to delete': null,
3335
})).to.deep.equal({
3436
ExpressionAttributeNames: {
3537
'#description': 'description',
3638
'#discriminator': 'discriminator',
3739
'#id': 'id',
3840
'#latched': 'latched',
3941
'#name': 'name',
40-
// '#status': 'status',
42+
'#some_x20_unsafe_x20_att_x20_name': 'some unsafe att name',
43+
'#some_x20_unsafe_x20_att_x20_name_x20_to_x20_delete': 'some unsafe att name to delete',
4144
'#status2': 'status2',
4245
'#timestamp': 'timestamp',
4346
'#ttl': 'ttl',
@@ -48,12 +51,11 @@ describe('sinks/dynamodb.js', () => {
4851
':id': '2f8ac025-d9e3-48f9-ba80-56487ddf0b89',
4952
':latched': true,
5053
':name': 'Thing One',
51-
// ':status': undefined,
52-
// ':status2': null,
54+
':some_x20_unsafe_x20_att_x20_name': true,
5355
':timestamp': 1540454400000,
5456
':ttl': 1543046400,
5557
},
56-
UpdateExpression: 'SET #id = :id, #name = :name, #description = :description, #discriminator = :discriminator, #latched = :latched, #ttl = :ttl, #timestamp = :timestamp REMOVE #status2',
58+
UpdateExpression: 'SET #id = :id, #name = :name, #description = :description, #discriminator = :discriminator, #latched = :latched, #ttl = :ttl, #timestamp = :timestamp, #some_x20_unsafe_x20_att_x20_name = :some_x20_unsafe_x20_att_x20_name REMOVE #status2, #some_x20_unsafe_x20_att_x20_name_x20_to_x20_delete',
5759
ReturnValues: 'ALL_NEW',
5860
});
5961
});
@@ -92,6 +94,35 @@ describe('sinks/dynamodb.js', () => {
9294
});
9395
});
9496

97+
it('should calculate updateExpression removing values from a set when attribute names have illegal characters if used as an alias', () => {
98+
const result = updateExpression({
99+
'some|tags_delete': new Set(['x', 'y']),
100+
'a-b': true,
101+
'a--b': false,
102+
'a|b': 1,
103+
});
104+
105+
expect(normalizeObj(result)).to.deep.equal({
106+
ExpressionAttributeNames: {
107+
'#some_x7c_tags': 'some|tags',
108+
'#a_x2d_b': 'a-b',
109+
'#a_x2d__x2d_b': 'a--b',
110+
'#a_x7c_b': 'a|b',
111+
},
112+
ExpressionAttributeValues: {
113+
':some_x7c_tags_delete': [
114+
'x',
115+
'y',
116+
],
117+
':a_x2d_b': true,
118+
':a_x2d__x2d_b': false,
119+
':a_x7c_b': 1,
120+
},
121+
UpdateExpression: 'SET #a_x2d_b = :a_x2d_b, #a_x2d__x2d_b = :a_x2d__x2d_b, #a_x7c_b = :a_x7c_b DELETE #some_x7c_tags :some_x7c_tags_delete',
122+
ReturnValues: 'ALL_NEW',
123+
});
124+
});
125+
95126
it('should wrap calculate updateExpression wrapping a delete set value in a set', () => {
96127
const result = updateExpression({
97128
tags_delete: 'x',

0 commit comments

Comments
 (0)