Skip to content

Commit 99c7534

Browse files
Merge pull request #188 from shiftcode/#159-not-contains
fix(conditions): making notContains actually work
2 parents 17a2b68 + e4697c5 commit 99c7534

File tree

6 files changed

+167
-38
lines changed

6 files changed

+167
-38
lines changed

src/dynamo/expression/condition-expression-builder.spec.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,41 @@ describe('expressions', () => {
246246
})
247247
})
248248

249+
describe('not_contains', () => {
250+
it('string subsequence', () => {
251+
const condition = buildFilterExpression('id', 'not_contains', ['substr'], undefined, metadataForModel(Form))
252+
expect(condition.statement).toBe('not_contains (#id, :id)')
253+
expect(condition.attributeNames).toEqual({ '#id': 'id' })
254+
expect(condition.attributeValues).toEqual({ ':id': { S: 'substr' } })
255+
})
256+
257+
it('value in set', () => {
258+
const condition = buildFilterExpression('types', 'not_contains', [2], undefined, metadataForModel(Form))
259+
expect(condition.statement).toBe('not_contains (#types, :types)')
260+
expect(condition.attributeNames).toEqual({ '#types': 'types' })
261+
expect(condition.attributeValues).toEqual({ ':types': { N: '2' } })
262+
})
263+
264+
it('value in set with custom mapper', () => {
265+
@Model()
266+
class MyModelWithCustomMappedSet {
267+
@CollectionProperty({ itemMapper: formIdMapper })
268+
formIds: Set<FormId>
269+
}
270+
271+
const condition = buildFilterExpression(
272+
'formIds',
273+
'not_contains',
274+
[new FormId(FormType.REQUEST, 1, 2019)],
275+
undefined,
276+
metadataForModel(MyModelWithCustomMappedSet),
277+
)
278+
expect(condition.statement).toBe('not_contains (#formIds, :formIds)')
279+
expect(condition.attributeNames).toEqual({ '#formIds': 'formIds' })
280+
expect(condition.attributeValues).toEqual({ ':formIds': { S: 'AF00012019' } })
281+
})
282+
})
283+
249284
it('in', () => {
250285
// property('myCollection').in(['myCollection', 'myOtherValue'])
251286
const condition = buildFilterExpression('myCollection', 'IN', [['myValue', 'myOtherValue']], undefined, undefined)

src/dynamo/expression/function-operators.const.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export const FUNCTION_OPERATORS: FunctionOperator[] = [
1212
'attribute_type',
1313
'begins_with',
1414
'contains',
15+
'not_contains',
1516
'IN',
1617
'BETWEEN',
1718
]

src/dynamo/expression/functions/operator-parameter-arity.function.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ export function operatorParameterArity(operator: ConditionOperator): number {
2626
case 'begins_with':
2727
case 'attribute_type':
2828
case 'contains':
29+
case 'not_contains':
2930
case 'IN':
3031
return 1
3132
case 'BETWEEN':

src/dynamo/expression/logical-operator/attribute.function.spec.ts

Lines changed: 128 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -8,53 +8,145 @@ import { or } from './or.function'
88
describe('chained conditions', () => {
99
let condition: Expression
1010

11-
it('strictly typed', () => {
12-
condition = attribute2(SimpleWithPartitionKeyModel, 'age').attributeExists()(undefined, undefined)
13-
expect(condition.statement).toBe('attribute_exists (#age)')
11+
describe('conditions', () => {
12+
it('equals', () => {
13+
condition = attribute('name').equals('foo')(undefined, undefined)
14+
expect(condition.statement).toBe('#name = :name')
15+
expect(condition.attributeValues).toEqual({ ':name': { S: 'foo' } })
16+
})
17+
it('eq', () => {
18+
condition = attribute('name').eq('foo')(undefined, undefined)
19+
expect(condition.statement).toBe('#name = :name')
20+
expect(condition.attributeValues).toEqual({ ':name': { S: 'foo' } })
21+
})
22+
it('ne', () => {
23+
condition = attribute('name').ne('foo')(undefined, undefined)
24+
expect(condition.statement).toBe('#name <> :name')
25+
expect(condition.attributeValues).toEqual({ ':name': { S: 'foo' } })
26+
})
27+
it('lte', () => {
28+
condition = attribute('age').lte(5)(undefined, undefined)
29+
expect(condition.statement).toBe('#age <= :age')
30+
expect(condition.attributeValues).toEqual({ ':age': { N: '5' } })
31+
})
32+
it('lt', () => {
33+
condition = attribute('age').lt(5)(undefined, undefined)
34+
expect(condition.statement).toBe('#age < :age')
35+
expect(condition.attributeValues).toEqual({ ':age': { N: '5' } })
36+
})
37+
it('gte', () => {
38+
condition = attribute('age').gte(5)(undefined, undefined)
39+
expect(condition.statement).toBe('#age >= :age')
40+
expect(condition.attributeValues).toEqual({ ':age': { N: '5' } })
41+
})
42+
it('gt', () => {
43+
condition = attribute('age').gt(5)(undefined, undefined)
44+
expect(condition.statement).toBe('#age > :age')
45+
expect(condition.attributeValues).toEqual({ ':age': { N: '5' } })
46+
})
47+
it('attributeNotExists', () => {
48+
condition = attribute('name').attributeNotExists()(undefined, undefined)
49+
expect(condition.statement).toBe('attribute_not_exists (#name)')
50+
})
51+
it('null', () => {
52+
condition = attribute('name').null()(undefined, undefined)
53+
expect(condition.statement).toBe('attribute_not_exists (#name)')
54+
})
55+
it('attributeExists', () => {
56+
condition = attribute('name').attributeExists()(undefined, undefined)
57+
expect(condition.statement).toBe('attribute_exists (#name)')
58+
})
59+
it('notNull', () => {
60+
condition = attribute('name').notNull()(undefined, undefined)
61+
expect(condition.statement).toBe('attribute_exists (#name)')
62+
})
63+
it('contains', () => {
64+
condition = attribute('name').contains('foo')(undefined, undefined)
65+
expect(condition.statement).toBe('contains (#name, :name)')
66+
expect(condition.attributeValues).toEqual({ ':name': { S: 'foo' } })
67+
})
68+
it('not_contains', () => {
69+
condition = attribute('name').notContains('foo')(undefined, undefined)
70+
expect(condition.statement).toBe('not_contains (#name, :name)')
71+
expect(condition.attributeValues).toEqual({ ':name': { S: 'foo' } })
72+
})
73+
it('type', () => {
74+
condition = attribute('age').type('N')(undefined, undefined)
75+
expect(condition.statement).toBe('attribute_type (#age, :age)')
76+
expect(condition.attributeValues).toEqual({ ':age': { S: 'N' } })
77+
})
78+
it('in', () => {
79+
condition = attribute('name').in(['aName'])(undefined, undefined)
80+
expect(condition.statement).toBe('#name IN (:name_0)')
81+
expect(condition.attributeValues).toEqual({ ':name_0': { S: 'aName' } })
82+
})
83+
it('beginsWith', () => {
84+
condition = attribute('name').beginsWith('foo')(undefined, undefined)
85+
expect(condition.statement).toBe('begins_with (#name, :name)')
86+
expect(condition.attributeValues).toEqual({ ':name': { S: 'foo' } })
87+
})
88+
it('between', () => {
89+
condition = attribute('age').between(18, 26)(undefined, undefined)
90+
expect(condition.statement).toBe('#age BETWEEN :age AND :age_2')
91+
expect(condition.attributeValues).toEqual({
92+
':age': { N: '18' },
93+
':age_2': { N: '26' },
94+
})
95+
})
1496
})
1597

16-
it('not', () => {
17-
condition = not(attribute('name').contains('SortedUpdateExpressions'))(undefined, undefined)
18-
expect(condition.statement).toBe('NOT contains (#name, :name)')
19-
})
98+
describe('combinations', () => {
99+
it('not', () => {
100+
condition = not(attribute('name').contains('SortedUpdateExpressions'))(undefined, undefined)
101+
expect(condition.statement).toBe('NOT contains (#name, :name)')
102+
})
20103

21-
it('and & not', () => {
22-
condition = and(not(attribute('name').contains('z')), attribute('name').beginsWith('Sta'))(undefined, undefined)
104+
it('and & not', () => {
105+
condition = and(not(attribute('name').contains('z')), attribute('name').beginsWith('Sta'))(undefined, undefined)
23106

24-
expect(condition.attributeNames).toBeDefined()
25-
expect(Object.keys(condition.attributeNames).length).toBe(1)
107+
expect(condition.attributeNames).toBeDefined()
108+
expect(Object.keys(condition.attributeNames).length).toBe(1)
26109

27-
expect(condition.attributeValues).toBeDefined()
28-
expect(Object.keys(condition.attributeValues).length).toBe(2)
29-
expect(condition.attributeValues[':name']).toEqual({ S: 'z' })
30-
expect(condition.attributeValues[':name_2']).toEqual({ S: 'Sta' })
110+
expect(condition.attributeValues).toBeDefined()
111+
expect(Object.keys(condition.attributeValues).length).toBe(2)
112+
expect(condition.attributeValues[':name']).toEqual({ S: 'z' })
113+
expect(condition.attributeValues[':name_2']).toEqual({ S: 'Sta' })
31114

32-
expect(condition.statement).toBe('(NOT contains (#name, :name) AND begins_with (#name, :name_2))')
33-
})
115+
expect(condition.statement).toBe('(NOT contains (#name, :name) AND begins_with (#name, :name_2))')
116+
})
34117

35-
it('or', () => {
36-
condition = or(attribute('age').gt(10), attribute('name').contains('SortedUpdateExpressions'))(undefined, undefined)
118+
it('or', () => {
119+
condition = or(attribute('age').gt(10), attribute('name').contains('SortedUpdateExpressions'))(
120+
undefined,
121+
undefined,
122+
)
37123

38-
expect(condition.statement).toBe('(#age > :age OR contains (#name, :name))')
39-
})
124+
expect(condition.statement).toBe('(#age > :age OR contains (#name, :name))')
125+
})
40126

41-
it('and', () => {
42-
condition = and(attribute('age').gt(10), attribute('name').contains('SortedUpdateExpressions'))(
43-
undefined,
44-
undefined,
45-
)
127+
it('and', () => {
128+
condition = and(attribute('age').gt(10), attribute('name').contains('SortedUpdateExpressions'))(
129+
undefined,
130+
undefined,
131+
)
46132

47-
expect(condition.statement).toBe('(#age > :age AND contains (#name, :name))')
48-
})
133+
expect(condition.statement).toBe('(#age > :age AND contains (#name, :name))')
134+
})
135+
136+
it('mixed', () => {
137+
condition = or(
138+
and(attribute('age').gt(10), attribute('name').contains('SortedUpdateExpressions')),
139+
attribute('doAddCondition').beginsWith('Start'),
140+
)(undefined, undefined)
49141

50-
it('mixed', () => {
51-
condition = or(
52-
and(attribute('age').gt(10), attribute('name').contains('SortedUpdateExpressions')),
53-
attribute('doAddCondition').beginsWith('Start'),
54-
)(undefined, undefined)
142+
expect(condition.statement).toBe(
143+
'((#age > :age AND contains (#name, :name)) OR begins_with (#doAddCondition, :doAddCondition))',
144+
)
145+
})
146+
})
55147

56-
expect(condition.statement).toBe(
57-
'((#age > :age AND contains (#name, :name)) OR begins_with (#doAddCondition, :doAddCondition))',
58-
)
148+
it('strictly typed', () => {
149+
condition = attribute2(SimpleWithPartitionKeyModel, 'age').attributeExists()(undefined, undefined)
150+
expect(condition.statement).toBe('attribute_exists (#age)')
59151
})
60152
})

src/dynamo/expression/type/condition-operator-alias.type.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export type OperatorAlias =
1717
| 'type'
1818
| 'beginsWith'
1919
| 'contains'
20-
| 'not_contains'
20+
| 'notContains'
2121
| 'in'
2222
| 'between'
2323
| 'attributeExists'

src/dynamo/expression/type/condition-operator-to-alias-map.const.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export const OPERATOR_TO_ALIAS_MAP: AliasedOperatorMapEntry = {
2727
attribute_exists: ['attributeExists', 'notNull'],
2828
attribute_type: 'type',
2929
contains: 'contains',
30-
not_contains: 'not_contains',
30+
not_contains: 'notContains',
3131
IN: 'in',
3232
begins_with: 'beginsWith',
3333
BETWEEN: 'between',

0 commit comments

Comments
 (0)