Skip to content

Commit

Permalink
feat(updateRequest): add support for all the available support operat…
Browse files Browse the repository at this point in the history
…ions
  • Loading branch information
michaelwittwer committed Oct 9, 2017
1 parent d2d992f commit 9b57ed1
Show file tree
Hide file tree
Showing 8 changed files with 441 additions and 57 deletions.
11 changes: 4 additions & 7 deletions src/dynamo/expression/request-expression-builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ import { ExpressionType } from './type/expression-type.type'
import { Expression } from './type/expression.type'
import { RequestConditionFunction } from './type/request-condition-function'
import { RequestSortKeyConditionFunction } from './type/sort-key-condition-function'
import { UpdateAction, UpdateActionDef } from './type/update-action.type'
import { UpdateActionDef } from './type/update-action-def'
import { UPDATE_ACTION_DEFS } from './type/update-action-defs.const'
import { UpdateAction } from './type/update-action.type'
import { UpdateExpressionDefinitionChain } from './type/update-expression-definition-chain'
import { UpdateExpressionDefinitionFunction } from './type/update-expression-definition-function'
import { UpdateExpression } from './type/update-expression.type'
Expand Down Expand Up @@ -149,12 +151,7 @@ export class RequestExpressionBuilder {
* parameters as another example
*/
private static createUpdateFunctions<T>(impl: (operation: UpdateActionDef) => any): T {
// FIXME add all operators
return <T>[
new UpdateActionDef('SET', 'incrementBy'),
new UpdateActionDef('SET', 'decrementBy'),
new UpdateActionDef('SET', 'set'),
].reduce(
return <T>UPDATE_ACTION_DEFS.reduce(
(result: T, updateActionDef: UpdateActionDef) => {
Reflect.set(<any>result, updateActionDef.action, impl(updateActionDef))

Expand Down
12 changes: 12 additions & 0 deletions src/dynamo/expression/type/update-action-def.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { UpdateActionKeyword } from './update-action-keyword.type'
import { UpdateAction } from './update-action.type'

export class UpdateActionDef {
actionKeyword: UpdateActionKeyword
action: UpdateAction

constructor(actionKeyWord: UpdateActionKeyword, action: UpdateAction) {
this.actionKeyword = actionKeyWord
this.action = action
}
}
16 changes: 16 additions & 0 deletions src/dynamo/expression/type/update-action-defs.const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { UpdateActionDef } from './update-action-def'

export const UPDATE_ACTION_DEFS: UpdateActionDef[] = [
// SET
new UpdateActionDef('SET', 'incrementBy'),
new UpdateActionDef('SET', 'decrementBy'),
new UpdateActionDef('SET', 'set'),
new UpdateActionDef('SET', 'appendToList'),
// REMOVE
new UpdateActionDef('REMOVE', 'remove'),
new UpdateActionDef('REMOVE', 'removeFromListAt'),
// ADD
new UpdateActionDef('ADD', 'add'),
// DELETE
new UpdateActionDef('DELETE', 'removeFromSet'),
]
10 changes: 0 additions & 10 deletions src/dynamo/expression/type/update-action.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,3 @@ export type UpdateAction =
| 'removeFromListAt'
| 'add'
| 'removeFromSet'

export class UpdateActionDef {
actionKeyword: UpdateActionKeyword
action: UpdateAction

constructor(actionKeyWord: UpdateActionKeyword, action: UpdateAction) {
this.actionKeyword = actionKeyWord
this.action = action
}
}
40 changes: 29 additions & 11 deletions src/dynamo/expression/type/update-expression-definition-chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,60 @@ import { UpdateExpressionDefinitionFunction } from './update-expression-definiti
* see http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html for full documentation
*/
export interface UpdateExpressionDefinitionChain {
// SET operation TODO add support for ifNotExists
/* ----------------------------------------------------------------
SET operation TODO add support for ifNotExists
---------------------------------------------------------------- */
incrementBy: (value: number) => UpdateExpressionDefinitionFunction
decrementBy: (value: number) => UpdateExpressionDefinitionFunction

/**
* will update the item at the path, path can be a top level atribute or a nested attribute.
* samples:
* - persons.age
* - places[0].address.street
*/
set: (value: any) => UpdateExpressionDefinitionFunction
setAt: (value: any, at: number) => UpdateExpressionDefinitionFunction

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

// REMOVE operation
/* ----------------------------------------------------------------
REMOVE operation
---------------------------------------------------------------- */
remove: () => UpdateExpressionDefinitionFunction

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

// ADD operation (only supports number and set type)
/* ----------------------------------------------------------------
ADD operation (only supports number and set type)
AWS generally recommends to use SET rather than ADD
---------------------------------------------------------------- */

/**
* adds or manipulates a value, manipulation behaviour differs based on type of attribute
* adds or manipulates a value to an attribute of type N(umber) or S(et), manipulation behaviour differs based on attribute type
*
* @param values {multiple values as vararg | Array | Set}
*
* --update-expression "ADD QuantityOnHand :q" \
* --expression-attribute-values '{":q": {"N": "5"}}' \
*
* --update-expression "ADD Color :c" \
* --expression-attribute-values '{":c": {"SS":["Orange", "Purple"]}}' \
*/
add: (value: any) => UpdateExpressionDefinitionFunction

// DELETE operation (only supports set type)
add: (...values: any[]) => UpdateExpressionDefinitionFunction

/* ----------------------------------------------------------------
DELETE operation (only supports set type)
---------------------------------------------------------------- */
/**
* @param values {multiple values as vararg | Array | Set}
* @returns {UpdateExpressionDefinitionFunction}
*
* --update-expression "DELETE Color :p" \
* --expression-attribute-values '{":p": {"SS": ["Yellow", "Purple"]}}' \
* --expression-attribute-values '{":p": {"SS": ["Yellow", "Purple"]}}'
*/
removeFromSet: (values: Set<any>) => UpdateExpressionDefinitionFunction
removeFromSet: (...values: any[]) => UpdateExpressionDefinitionFunction
}
69 changes: 58 additions & 11 deletions src/dynamo/expression/update-expression-builder.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { AttributeMap, AttributeValue } from 'aws-sdk/clients/dynamodb'
import { curryRight } from 'lodash'
import { Metadata } from '../../decorator/metadata/metadata'
import { PropertyMetadata } from '../../decorator/metadata/property-metadata.model'
import { Mapper } from '../../mapper/mapper'
import { Util } from '../../mapper/util'
import { resolveAttributeNames } from './functions/attribute-names.function'
import { isFunctionOperator } from './functions/is-function-operator.function'
import { isNoParamFunctionOperator } from './functions/is-no-param-function-operator.function'
import { uniqAttributeValueName } from './functions/unique-attribute-value-name.function'
import { Expression } from './type/expression.type'
import { UpdateAction, UpdateActionDef } from './type/update-action.type'
import { UpdateActionDef } from './type/update-action-def'
import { UpdateAction } from './type/update-action.type'
import { UpdateExpression } from './type/update-expression.type'

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

// TODO check if provided values are valid for given operation
Expand Down Expand Up @@ -83,22 +83,61 @@ export class UpdateExpressionBuilder {
): UpdateExpression {
let statement: string
switch (operator.action) {
case 'set':
statement = `${namePlaceholder} = ${valuePlaceholder}`
break
case 'incrementBy':
statement = `${namePlaceholder} = ${namePlaceholder} + ${valuePlaceholder}`
break
case 'decrementBy':
statement = `${namePlaceholder} = ${namePlaceholder} - ${valuePlaceholder}`
break
case 'set':
statement = `${namePlaceholder} = ${valuePlaceholder}`
break
case 'appendToList':
const position = values.length > 1 ? values[values.length - 1] || 'END' : 'END'
switch (position) {
case 'END':
statement = `${namePlaceholder} = list_append(${namePlaceholder}, ${valuePlaceholder})`
break
case 'START':
statement = `${namePlaceholder} = list_append(${valuePlaceholder}, ${namePlaceholder})`
break
default:
throw new Error("make sure to provide either 'START' or 'END' as value for position argument")
}
break
case 'remove':
statement = `${namePlaceholder}`
break
case 'removeFromListAt':
const positions: number[] = values
statement = values.map(pos => `${namePlaceholder}[${pos}]`).join(', ')
break
case 'add':
// TODO add validation to make sure expressionAttributeValue to be N(umber) or S(et)
statement = `${namePlaceholder} ${valuePlaceholder}`
// TODO won't work for numbers, is always gonna be mapped to a collectio type
if ((values.length === 1 && Array.isArray(values[0])) || Util.isSet(values[0])) {
// dealing with arr | set as single argument
} else {
// dealing with vararg
values[0] = [...values]
}
break
case 'removeFromSet':
// TODO add validation to make sure expressionAttributeValue to be S(et)
statement = `${namePlaceholder} ${valuePlaceholder}`
if ((values.length === 1 && Array.isArray(values[0])) || Util.isSet(values[0])) {
// dealing with arr | set as single argument
} else {
// dealing with vararg
values[0] = [...values]
}
break
default:
throw new Error('no implementation')
throw new Error(`no implementation for action ${operator.action}`)
}

// = [namePlaceholder, operator, valuePlaceholder].join(' ')
// FIXME add hasValue logic
const hasValue = true
const hasValue = !UpdateExpressionBuilder.isNoValueAction(operator.action)

const attributeValues: AttributeMap = {}
if (hasValue) {
Expand All @@ -116,4 +155,12 @@ export class UpdateExpressionBuilder {
attributeValues,
}
}

private static isNoValueAction(action: UpdateAction) {
return (
action === 'remove' ||
// special cases: values are used in statement instaed of expressionValues
action === 'removeFromListAt'
)
}
}
Loading

0 comments on commit 9b57ed1

Please sign in to comment.