Skip to content

Commit 9b57ed1

Browse files
feat(updateRequest): add support for all the available support operations
1 parent d2d992f commit 9b57ed1

File tree

8 files changed

+441
-57
lines changed

8 files changed

+441
-57
lines changed

src/dynamo/expression/request-expression-builder.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import { ExpressionType } from './type/expression-type.type'
1212
import { Expression } from './type/expression.type'
1313
import { RequestConditionFunction } from './type/request-condition-function'
1414
import { RequestSortKeyConditionFunction } from './type/sort-key-condition-function'
15-
import { UpdateAction, UpdateActionDef } from './type/update-action.type'
15+
import { UpdateActionDef } from './type/update-action-def'
16+
import { UPDATE_ACTION_DEFS } from './type/update-action-defs.const'
17+
import { UpdateAction } from './type/update-action.type'
1618
import { UpdateExpressionDefinitionChain } from './type/update-expression-definition-chain'
1719
import { UpdateExpressionDefinitionFunction } from './type/update-expression-definition-function'
1820
import { UpdateExpression } from './type/update-expression.type'
@@ -149,12 +151,7 @@ export class RequestExpressionBuilder {
149151
* parameters as another example
150152
*/
151153
private static createUpdateFunctions<T>(impl: (operation: UpdateActionDef) => any): T {
152-
// FIXME add all operators
153-
return <T>[
154-
new UpdateActionDef('SET', 'incrementBy'),
155-
new UpdateActionDef('SET', 'decrementBy'),
156-
new UpdateActionDef('SET', 'set'),
157-
].reduce(
154+
return <T>UPDATE_ACTION_DEFS.reduce(
158155
(result: T, updateActionDef: UpdateActionDef) => {
159156
Reflect.set(<any>result, updateActionDef.action, impl(updateActionDef))
160157

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { UpdateActionKeyword } from './update-action-keyword.type'
2+
import { UpdateAction } from './update-action.type'
3+
4+
export class UpdateActionDef {
5+
actionKeyword: UpdateActionKeyword
6+
action: UpdateAction
7+
8+
constructor(actionKeyWord: UpdateActionKeyword, action: UpdateAction) {
9+
this.actionKeyword = actionKeyWord
10+
this.action = action
11+
}
12+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { UpdateActionDef } from './update-action-def'
2+
3+
export const UPDATE_ACTION_DEFS: UpdateActionDef[] = [
4+
// SET
5+
new UpdateActionDef('SET', 'incrementBy'),
6+
new UpdateActionDef('SET', 'decrementBy'),
7+
new UpdateActionDef('SET', 'set'),
8+
new UpdateActionDef('SET', 'appendToList'),
9+
// REMOVE
10+
new UpdateActionDef('REMOVE', 'remove'),
11+
new UpdateActionDef('REMOVE', 'removeFromListAt'),
12+
// ADD
13+
new UpdateActionDef('ADD', 'add'),
14+
// DELETE
15+
new UpdateActionDef('DELETE', 'removeFromSet'),
16+
]

src/dynamo/expression/type/update-action.type.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,3 @@ export type UpdateAction =
2222
| 'removeFromListAt'
2323
| 'add'
2424
| 'removeFromSet'
25-
26-
export class UpdateActionDef {
27-
actionKeyword: UpdateActionKeyword
28-
action: UpdateAction
29-
30-
constructor(actionKeyWord: UpdateActionKeyword, action: UpdateAction) {
31-
this.actionKeyword = actionKeyWord
32-
this.action = action
33-
}
34-
}

src/dynamo/expression/type/update-expression-definition-chain.ts

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,42 +4,60 @@ import { UpdateExpressionDefinitionFunction } from './update-expression-definiti
44
* see http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html for full documentation
55
*/
66
export interface UpdateExpressionDefinitionChain {
7-
// SET operation TODO add support for ifNotExists
7+
/* ----------------------------------------------------------------
8+
SET operation TODO add support for ifNotExists
9+
---------------------------------------------------------------- */
810
incrementBy: (value: number) => UpdateExpressionDefinitionFunction
911
decrementBy: (value: number) => UpdateExpressionDefinitionFunction
12+
13+
/**
14+
* will update the item at the path, path can be a top level atribute or a nested attribute.
15+
* samples:
16+
* - persons.age
17+
* - places[0].address.street
18+
*/
1019
set: (value: any) => UpdateExpressionDefinitionFunction
11-
setAt: (value: any, at: number) => UpdateExpressionDefinitionFunction
1220

1321
/**
1422
* appends one or more values to the start or end of a list, value must be of type L(ist)
1523
*/
16-
appendToList: (value: any, position: 'START' | 'END') => UpdateExpressionDefinitionFunction
24+
appendToList: (value: any, position?: 'START' | 'END') => UpdateExpressionDefinitionFunction
1725

18-
// REMOVE operation
26+
/* ----------------------------------------------------------------
27+
REMOVE operation
28+
---------------------------------------------------------------- */
1929
remove: () => UpdateExpressionDefinitionFunction
2030

2131
/** removes an item at the given position(s), the remaining elements are shifted */
2232
removeFromListAt: (...positions: number[]) => UpdateExpressionDefinitionFunction
2333

24-
// ADD operation (only supports number and set type)
34+
/* ----------------------------------------------------------------
35+
ADD operation (only supports number and set type)
36+
AWS generally recommends to use SET rather than ADD
37+
---------------------------------------------------------------- */
2538

2639
/**
27-
* adds or manipulates a value, manipulation behaviour differs based on type of attribute
40+
* adds or manipulates a value to an attribute of type N(umber) or S(et), manipulation behaviour differs based on attribute type
41+
*
42+
* @param values {multiple values as vararg | Array | Set}
2843
*
2944
* --update-expression "ADD QuantityOnHand :q" \
3045
* --expression-attribute-values '{":q": {"N": "5"}}' \
3146
*
3247
* --update-expression "ADD Color :c" \
3348
* --expression-attribute-values '{":c": {"SS":["Orange", "Purple"]}}' \
3449
*/
35-
add: (value: any) => UpdateExpressionDefinitionFunction
36-
37-
// DELETE operation (only supports set type)
50+
add: (...values: any[]) => UpdateExpressionDefinitionFunction
3851

52+
/* ----------------------------------------------------------------
53+
DELETE operation (only supports set type)
54+
---------------------------------------------------------------- */
3955
/**
56+
* @param values {multiple values as vararg | Array | Set}
57+
* @returns {UpdateExpressionDefinitionFunction}
4058
*
4159
* --update-expression "DELETE Color :p" \
42-
* --expression-attribute-values '{":p": {"SS": ["Yellow", "Purple"]}}' \
60+
* --expression-attribute-values '{":p": {"SS": ["Yellow", "Purple"]}}'
4361
*/
44-
removeFromSet: (values: Set<any>) => UpdateExpressionDefinitionFunction
62+
removeFromSet: (...values: any[]) => UpdateExpressionDefinitionFunction
4563
}

src/dynamo/expression/update-expression-builder.ts

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import { AttributeMap, AttributeValue } from 'aws-sdk/clients/dynamodb'
2-
import { curryRight } from 'lodash'
32
import { Metadata } from '../../decorator/metadata/metadata'
43
import { PropertyMetadata } from '../../decorator/metadata/property-metadata.model'
54
import { Mapper } from '../../mapper/mapper'
5+
import { Util } from '../../mapper/util'
66
import { resolveAttributeNames } from './functions/attribute-names.function'
7-
import { isFunctionOperator } from './functions/is-function-operator.function'
8-
import { isNoParamFunctionOperator } from './functions/is-no-param-function-operator.function'
97
import { uniqAttributeValueName } from './functions/unique-attribute-value-name.function'
108
import { Expression } from './type/expression.type'
11-
import { UpdateAction, UpdateActionDef } from './type/update-action.type'
9+
import { UpdateActionDef } from './type/update-action-def'
10+
import { UpdateAction } from './type/update-action.type'
1211
import { UpdateExpression } from './type/update-expression.type'
1312

1413
export class UpdateExpressionBuilder {
@@ -32,6 +31,7 @@ export class UpdateExpressionBuilder {
3231
): UpdateExpression {
3332
// TODO investigate is there a use case for undefined desired to be a value
3433
// get rid of undefined values
34+
// FIXME should this not be a deep filter?
3535
values = values.filter(value => value !== undefined)
3636

3737
// TODO check if provided values are valid for given operation
@@ -83,22 +83,61 @@ export class UpdateExpressionBuilder {
8383
): UpdateExpression {
8484
let statement: string
8585
switch (operator.action) {
86-
case 'set':
87-
statement = `${namePlaceholder} = ${valuePlaceholder}`
88-
break
8986
case 'incrementBy':
9087
statement = `${namePlaceholder} = ${namePlaceholder} + ${valuePlaceholder}`
9188
break
9289
case 'decrementBy':
9390
statement = `${namePlaceholder} = ${namePlaceholder} - ${valuePlaceholder}`
9491
break
92+
case 'set':
93+
statement = `${namePlaceholder} = ${valuePlaceholder}`
94+
break
95+
case 'appendToList':
96+
const position = values.length > 1 ? values[values.length - 1] || 'END' : 'END'
97+
switch (position) {
98+
case 'END':
99+
statement = `${namePlaceholder} = list_append(${namePlaceholder}, ${valuePlaceholder})`
100+
break
101+
case 'START':
102+
statement = `${namePlaceholder} = list_append(${valuePlaceholder}, ${namePlaceholder})`
103+
break
104+
default:
105+
throw new Error("make sure to provide either 'START' or 'END' as value for position argument")
106+
}
107+
break
108+
case 'remove':
109+
statement = `${namePlaceholder}`
110+
break
111+
case 'removeFromListAt':
112+
const positions: number[] = values
113+
statement = values.map(pos => `${namePlaceholder}[${pos}]`).join(', ')
114+
break
115+
case 'add':
116+
// TODO add validation to make sure expressionAttributeValue to be N(umber) or S(et)
117+
statement = `${namePlaceholder} ${valuePlaceholder}`
118+
// TODO won't work for numbers, is always gonna be mapped to a collectio type
119+
if ((values.length === 1 && Array.isArray(values[0])) || Util.isSet(values[0])) {
120+
// dealing with arr | set as single argument
121+
} else {
122+
// dealing with vararg
123+
values[0] = [...values]
124+
}
125+
break
126+
case 'removeFromSet':
127+
// TODO add validation to make sure expressionAttributeValue to be S(et)
128+
statement = `${namePlaceholder} ${valuePlaceholder}`
129+
if ((values.length === 1 && Array.isArray(values[0])) || Util.isSet(values[0])) {
130+
// dealing with arr | set as single argument
131+
} else {
132+
// dealing with vararg
133+
values[0] = [...values]
134+
}
135+
break
95136
default:
96-
throw new Error('no implementation')
137+
throw new Error(`no implementation for action ${operator.action}`)
97138
}
98139

99-
// = [namePlaceholder, operator, valuePlaceholder].join(' ')
100-
// FIXME add hasValue logic
101-
const hasValue = true
140+
const hasValue = !UpdateExpressionBuilder.isNoValueAction(operator.action)
102141

103142
const attributeValues: AttributeMap = {}
104143
if (hasValue) {
@@ -116,4 +155,12 @@ export class UpdateExpressionBuilder {
116155
attributeValues,
117156
}
118157
}
158+
159+
private static isNoValueAction(action: UpdateAction) {
160+
return (
161+
action === 'remove' ||
162+
// special cases: values are used in statement instaed of expressionValues
163+
action === 'removeFromListAt'
164+
)
165+
}
119166
}

0 commit comments

Comments
 (0)