Skip to content

Commit

Permalink
Merge pull request #188 from shiftcode/#159-not-contains
Browse files Browse the repository at this point in the history
fix(conditions): making notContains actually work
  • Loading branch information
michaelwittwer authored Feb 13, 2019
2 parents 17a2b68 + e4697c5 commit 99c7534
Show file tree
Hide file tree
Showing 6 changed files with 167 additions and 38 deletions.
35 changes: 35 additions & 0 deletions src/dynamo/expression/condition-expression-builder.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,41 @@ describe('expressions', () => {
})
})

describe('not_contains', () => {
it('string subsequence', () => {
const condition = buildFilterExpression('id', 'not_contains', ['substr'], undefined, metadataForModel(Form))
expect(condition.statement).toBe('not_contains (#id, :id)')
expect(condition.attributeNames).toEqual({ '#id': 'id' })
expect(condition.attributeValues).toEqual({ ':id': { S: 'substr' } })
})

it('value in set', () => {
const condition = buildFilterExpression('types', 'not_contains', [2], undefined, metadataForModel(Form))
expect(condition.statement).toBe('not_contains (#types, :types)')
expect(condition.attributeNames).toEqual({ '#types': 'types' })
expect(condition.attributeValues).toEqual({ ':types': { N: '2' } })
})

it('value in set with custom mapper', () => {
@Model()
class MyModelWithCustomMappedSet {
@CollectionProperty({ itemMapper: formIdMapper })
formIds: Set<FormId>
}

const condition = buildFilterExpression(
'formIds',
'not_contains',
[new FormId(FormType.REQUEST, 1, 2019)],
undefined,
metadataForModel(MyModelWithCustomMappedSet),
)
expect(condition.statement).toBe('not_contains (#formIds, :formIds)')
expect(condition.attributeNames).toEqual({ '#formIds': 'formIds' })
expect(condition.attributeValues).toEqual({ ':formIds': { S: 'AF00012019' } })
})
})

it('in', () => {
// property('myCollection').in(['myCollection', 'myOtherValue'])
const condition = buildFilterExpression('myCollection', 'IN', [['myValue', 'myOtherValue']], undefined, undefined)
Expand Down
1 change: 1 addition & 0 deletions src/dynamo/expression/function-operators.const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const FUNCTION_OPERATORS: FunctionOperator[] = [
'attribute_type',
'begins_with',
'contains',
'not_contains',
'IN',
'BETWEEN',
]
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export function operatorParameterArity(operator: ConditionOperator): number {
case 'begins_with':
case 'attribute_type':
case 'contains':
case 'not_contains':
case 'IN':
return 1
case 'BETWEEN':
Expand Down
164 changes: 128 additions & 36 deletions src/dynamo/expression/logical-operator/attribute.function.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,53 +8,145 @@ import { or } from './or.function'
describe('chained conditions', () => {
let condition: Expression

it('strictly typed', () => {
condition = attribute2(SimpleWithPartitionKeyModel, 'age').attributeExists()(undefined, undefined)
expect(condition.statement).toBe('attribute_exists (#age)')
describe('conditions', () => {
it('equals', () => {
condition = attribute('name').equals('foo')(undefined, undefined)
expect(condition.statement).toBe('#name = :name')
expect(condition.attributeValues).toEqual({ ':name': { S: 'foo' } })
})
it('eq', () => {
condition = attribute('name').eq('foo')(undefined, undefined)
expect(condition.statement).toBe('#name = :name')
expect(condition.attributeValues).toEqual({ ':name': { S: 'foo' } })
})
it('ne', () => {
condition = attribute('name').ne('foo')(undefined, undefined)
expect(condition.statement).toBe('#name <> :name')
expect(condition.attributeValues).toEqual({ ':name': { S: 'foo' } })
})
it('lte', () => {
condition = attribute('age').lte(5)(undefined, undefined)
expect(condition.statement).toBe('#age <= :age')
expect(condition.attributeValues).toEqual({ ':age': { N: '5' } })
})
it('lt', () => {
condition = attribute('age').lt(5)(undefined, undefined)
expect(condition.statement).toBe('#age < :age')
expect(condition.attributeValues).toEqual({ ':age': { N: '5' } })
})
it('gte', () => {
condition = attribute('age').gte(5)(undefined, undefined)
expect(condition.statement).toBe('#age >= :age')
expect(condition.attributeValues).toEqual({ ':age': { N: '5' } })
})
it('gt', () => {
condition = attribute('age').gt(5)(undefined, undefined)
expect(condition.statement).toBe('#age > :age')
expect(condition.attributeValues).toEqual({ ':age': { N: '5' } })
})
it('attributeNotExists', () => {
condition = attribute('name').attributeNotExists()(undefined, undefined)
expect(condition.statement).toBe('attribute_not_exists (#name)')
})
it('null', () => {
condition = attribute('name').null()(undefined, undefined)
expect(condition.statement).toBe('attribute_not_exists (#name)')
})
it('attributeExists', () => {
condition = attribute('name').attributeExists()(undefined, undefined)
expect(condition.statement).toBe('attribute_exists (#name)')
})
it('notNull', () => {
condition = attribute('name').notNull()(undefined, undefined)
expect(condition.statement).toBe('attribute_exists (#name)')
})
it('contains', () => {
condition = attribute('name').contains('foo')(undefined, undefined)
expect(condition.statement).toBe('contains (#name, :name)')
expect(condition.attributeValues).toEqual({ ':name': { S: 'foo' } })
})
it('not_contains', () => {
condition = attribute('name').notContains('foo')(undefined, undefined)
expect(condition.statement).toBe('not_contains (#name, :name)')
expect(condition.attributeValues).toEqual({ ':name': { S: 'foo' } })
})
it('type', () => {
condition = attribute('age').type('N')(undefined, undefined)
expect(condition.statement).toBe('attribute_type (#age, :age)')
expect(condition.attributeValues).toEqual({ ':age': { S: 'N' } })
})
it('in', () => {
condition = attribute('name').in(['aName'])(undefined, undefined)
expect(condition.statement).toBe('#name IN (:name_0)')
expect(condition.attributeValues).toEqual({ ':name_0': { S: 'aName' } })
})
it('beginsWith', () => {
condition = attribute('name').beginsWith('foo')(undefined, undefined)
expect(condition.statement).toBe('begins_with (#name, :name)')
expect(condition.attributeValues).toEqual({ ':name': { S: 'foo' } })
})
it('between', () => {
condition = attribute('age').between(18, 26)(undefined, undefined)
expect(condition.statement).toBe('#age BETWEEN :age AND :age_2')
expect(condition.attributeValues).toEqual({
':age': { N: '18' },
':age_2': { N: '26' },
})
})
})

it('not', () => {
condition = not(attribute('name').contains('SortedUpdateExpressions'))(undefined, undefined)
expect(condition.statement).toBe('NOT contains (#name, :name)')
})
describe('combinations', () => {
it('not', () => {
condition = not(attribute('name').contains('SortedUpdateExpressions'))(undefined, undefined)
expect(condition.statement).toBe('NOT contains (#name, :name)')
})

it('and & not', () => {
condition = and(not(attribute('name').contains('z')), attribute('name').beginsWith('Sta'))(undefined, undefined)
it('and & not', () => {
condition = and(not(attribute('name').contains('z')), attribute('name').beginsWith('Sta'))(undefined, undefined)

expect(condition.attributeNames).toBeDefined()
expect(Object.keys(condition.attributeNames).length).toBe(1)
expect(condition.attributeNames).toBeDefined()
expect(Object.keys(condition.attributeNames).length).toBe(1)

expect(condition.attributeValues).toBeDefined()
expect(Object.keys(condition.attributeValues).length).toBe(2)
expect(condition.attributeValues[':name']).toEqual({ S: 'z' })
expect(condition.attributeValues[':name_2']).toEqual({ S: 'Sta' })
expect(condition.attributeValues).toBeDefined()
expect(Object.keys(condition.attributeValues).length).toBe(2)
expect(condition.attributeValues[':name']).toEqual({ S: 'z' })
expect(condition.attributeValues[':name_2']).toEqual({ S: 'Sta' })

expect(condition.statement).toBe('(NOT contains (#name, :name) AND begins_with (#name, :name_2))')
})
expect(condition.statement).toBe('(NOT contains (#name, :name) AND begins_with (#name, :name_2))')
})

it('or', () => {
condition = or(attribute('age').gt(10), attribute('name').contains('SortedUpdateExpressions'))(undefined, undefined)
it('or', () => {
condition = or(attribute('age').gt(10), attribute('name').contains('SortedUpdateExpressions'))(
undefined,
undefined,
)

expect(condition.statement).toBe('(#age > :age OR contains (#name, :name))')
})
expect(condition.statement).toBe('(#age > :age OR contains (#name, :name))')
})

it('and', () => {
condition = and(attribute('age').gt(10), attribute('name').contains('SortedUpdateExpressions'))(
undefined,
undefined,
)
it('and', () => {
condition = and(attribute('age').gt(10), attribute('name').contains('SortedUpdateExpressions'))(
undefined,
undefined,
)

expect(condition.statement).toBe('(#age > :age AND contains (#name, :name))')
})
expect(condition.statement).toBe('(#age > :age AND contains (#name, :name))')
})

it('mixed', () => {
condition = or(
and(attribute('age').gt(10), attribute('name').contains('SortedUpdateExpressions')),
attribute('doAddCondition').beginsWith('Start'),
)(undefined, undefined)

it('mixed', () => {
condition = or(
and(attribute('age').gt(10), attribute('name').contains('SortedUpdateExpressions')),
attribute('doAddCondition').beginsWith('Start'),
)(undefined, undefined)
expect(condition.statement).toBe(
'((#age > :age AND contains (#name, :name)) OR begins_with (#doAddCondition, :doAddCondition))',
)
})
})

expect(condition.statement).toBe(
'((#age > :age AND contains (#name, :name)) OR begins_with (#doAddCondition, :doAddCondition))',
)
it('strictly typed', () => {
condition = attribute2(SimpleWithPartitionKeyModel, 'age').attributeExists()(undefined, undefined)
expect(condition.statement).toBe('attribute_exists (#age)')
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export type OperatorAlias =
| 'type'
| 'beginsWith'
| 'contains'
| 'not_contains'
| 'notContains'
| 'in'
| 'between'
| 'attributeExists'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const OPERATOR_TO_ALIAS_MAP: AliasedOperatorMapEntry = {
attribute_exists: ['attributeExists', 'notNull'],
attribute_type: 'type',
contains: 'contains',
not_contains: 'not_contains',
not_contains: 'notContains',
IN: 'in',
begins_with: 'beginsWith',
BETWEEN: 'between',
Expand Down

0 comments on commit 99c7534

Please sign in to comment.