Skip to content

Commit 229418f

Browse files
feat(projection-expression): add support for projection expression
- add projection expression support for bacth-get-single-table.request and read-many.request - also fix issue where attribute name was not mapped to correct dynamoDB attribute name if metadata was specified
1 parent f92737f commit 229418f

9 files changed

+102
-40
lines changed

src/dynamo/request/batchgetsingletable/batch-get-single-table.request.spec.ts

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import * as DynamoDB from 'aws-sdk/clients/dynamodb'
33
// tslint:disable:no-unused-expression
44
// tslint:disable:no-non-null-assertion
55
import { resetDynamoEasyConfig } from '../../../../test/helper/resetDynamoEasyConfig.function'
6-
import { SimpleWithCompositePartitionKeyModel, SimpleWithPartitionKeyModel } from '../../../../test/models'
6+
import {
7+
ComplexModel,
8+
SimpleWithCompositePartitionKeyModel,
9+
SimpleWithPartitionKeyModel,
10+
} from '../../../../test/models'
711
import { updateDynamoEasyConfig } from '../../../config/update-config.function'
812
import { getTableName } from '../../get-table-name.function'
913
import { BatchGetSingleTableRequest } from './batch-get-single-table.request'
@@ -60,7 +64,7 @@ describe('batch get', () => {
6064
})
6165
})
6266

63-
it('ConsistentRead', () => {
67+
it('consistent read', () => {
6468
const request = new BatchGetSingleTableRequest<any>(<any>null, SimpleWithPartitionKeyModel, [{ id: 'myId' }])
6569
request.consistentRead()
6670
expect(request.params.RequestItems).toBeDefined()
@@ -78,6 +82,19 @@ describe('batch get', () => {
7882
'#name': 'name',
7983
})
8084
})
85+
86+
it('projection expression (custom db attribute name)', () => {
87+
const request = new BatchGetSingleTableRequest<ComplexModel>(<any>null, ComplexModel, [
88+
{ id: 'myId', creationDate: new Date() },
89+
])
90+
request.projectionExpression('active')
91+
expect(request.params.RequestItems).toBeDefined()
92+
expect(request.params.RequestItems[getTableName(ComplexModel)]).toBeDefined()
93+
expect(request.params.RequestItems[getTableName(ComplexModel)].ProjectionExpression).toBe('#active')
94+
expect(request.params.RequestItems[getTableName(ComplexModel)].ExpressionAttributeNames).toEqual({
95+
'#active': 'isActive',
96+
})
97+
})
8198
})
8299

83100
describe('should return appropriate value', () => {

src/dynamo/request/batchgetsingletable/batch-get-single-table.request.ts

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import { ModelConstructor } from '../../../model/model-constructor'
1111
import { batchGetItemsFetchAll } from '../../batchget/batch-get-utils'
1212
import { BATCH_GET_DEFAULT_TIME_SLOT, BATCH_GET_MAX_REQUEST_ITEM_COUNT } from '../../batchget/batch-get.const'
1313
import { DynamoDbWrapper } from '../../dynamo-db-wrapper'
14-
import { resolveAttributeNames } from '../../expression/functions/attribute-names.function'
1514
import { BaseRequest } from '../base.request'
15+
import { addProjectionExpressionParam } from '../helper/add-projection-expression-param.function'
1616
import { BatchGetSingleTableResponse } from './batch-get-single-table.response'
1717

1818
/**
@@ -49,19 +49,11 @@ export class BatchGetSingleTableRequest<T> extends BaseRequest<
4949
}
5050

5151
/**
52-
* Specifies the list of document attributes to be returned from the table instead of returning the entire document
53-
* @param attributesToGet List of document attributes to be returned
52+
* Specifies the list of model attributes to be returned from the table instead of returning the entire document
53+
* @param attributesToGet List of model attributes to be returned
5454
*/
55-
projectionExpression(...attributesToGet: string[]): BatchGetSingleTableRequest<T> {
56-
// tslint:disable-next-line:no-unnecessary-callback-wrapper
57-
const resolved = attributesToGet.map(a => resolveAttributeNames(a))
58-
this.params.RequestItems[this.tableName].ProjectionExpression = resolved.map(attr => attr.placeholder).join(', ')
59-
Object.values(resolved).forEach(r => {
60-
this.params.RequestItems[this.tableName].ExpressionAttributeNames = {
61-
...this.params.RequestItems[this.tableName].ExpressionAttributeNames,
62-
...r.attributeNames,
63-
}
64-
})
55+
projectionExpression(...attributesToGet: Array<keyof T | string>): BatchGetSingleTableRequest<T> {
56+
addProjectionExpressionParam(attributesToGet, this.params.RequestItems[this.tableName], this.metadata)
6557
return this
6658
}
6759

src/dynamo/request/get/get.request.spec.ts

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
// tslint:disable:no-unused-expression
22
import * as DynamoDB from 'aws-sdk/clients/dynamodb'
3-
import { SimpleWithCompositePartitionKeyModel, SimpleWithPartitionKeyModel } from '../../../../test/models'
3+
import {
4+
ComplexModel,
5+
SimpleWithCompositePartitionKeyModel,
6+
SimpleWithPartitionKeyModel,
7+
} from '../../../../test/models'
48
import { updateDynamoEasyConfig } from '../../../config/update-config.function'
59
import { Attributes } from '../../../mapper/type/attribute.type'
610
import { getTableName } from '../../get-table-name.function'
@@ -19,7 +23,7 @@ describe('GetRequest', () => {
1923
})
2024
})
2125

22-
describe('correct params', () => {
26+
describe('correct params (simple model)', () => {
2327
let request: GetRequest<SimpleWithPartitionKeyModel>
2428

2529
beforeEach(() => {
@@ -53,6 +57,23 @@ describe('GetRequest', () => {
5357
})
5458
})
5559

60+
describe('correct params (complex model)', () => {
61+
let request: GetRequest<ComplexModel>
62+
63+
beforeEach(() => {
64+
request = new GetRequest(<any>null, ComplexModel, 'partitionKeyValue', new Date())
65+
})
66+
67+
it('projection expression', () => {
68+
request.projectionExpression('active')
69+
70+
const params = request.params
71+
expect(params.ProjectionExpression).toBe('#active')
72+
expect(params.ExpressionAttributeNames).toEqual({ '#active': 'isActive' })
73+
expect(Object.keys(params).length).toBe(4)
74+
})
75+
})
76+
5677
describe('maps response item', () => {
5778
const jsItem: SimpleWithPartitionKeyModel = { age: 20, id: 'my-id' }
5879
const dbItem: Attributes<SimpleWithPartitionKeyModel> = { age: { N: '20' }, id: { S: 'my-id' } }

src/dynamo/request/get/get.request.ts

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,16 @@ import { createKeyAttributes, fromDb } from '../../../mapper/mapper'
88
import { Attributes } from '../../../mapper/type/attribute.type'
99
import { ModelConstructor } from '../../../model/model-constructor'
1010
import { DynamoDbWrapper } from '../../dynamo-db-wrapper'
11-
import { resolveAttributeNames } from '../../expression/functions/attribute-names.function'
11+
import { addProjectionExpressionParam } from '../helper/add-projection-expression-param.function'
1212
import { StandardRequest } from '../standard.request'
1313
import { GetResponse } from './get.response'
1414

15+
export interface GetRequestProjected<T>
16+
extends StandardRequest<Partial<T>, DynamoDB.GetItemInput, GetRequest<Partial<T>>> {
17+
execFullResponse(): Promise<GetResponse<Partial<T>>>
18+
exec(): Promise<Partial<T> | null>
19+
}
20+
1521
/**
1622
* Request class for the GetItem operation.
1723
*/
@@ -33,17 +39,12 @@ export class GetRequest<T> extends StandardRequest<T, DynamoDB.GetItemInput, Get
3339
}
3440

3541
/**
36-
* Specifies the list of document attributes to be returned from the table instead of returning the entire document
37-
* @param attributesToGet List of document attributes to be returned
42+
* Specifies the list of model attributes to be returned from the table instead of returning the entire document
43+
* @param attributesToGet List of model attributes to be returned
3844
*/
39-
projectionExpression(...attributesToGet: string[]): GetRequest<T> {
40-
// tslint:disable-next-line:no-unnecessary-callback-wrapper
41-
const resolved = attributesToGet.map(a => resolveAttributeNames(a))
42-
this.params.ProjectionExpression = resolved.map(attr => attr.placeholder).join(', ')
43-
Object.values(resolved).forEach(r => {
44-
this.params.ExpressionAttributeNames = { ...this.params.ExpressionAttributeNames, ...r.attributeNames }
45-
})
46-
return this
45+
projectionExpression(...attributesToGet: Array<keyof T | string>): GetRequestProjected<T> {
46+
addProjectionExpressionParam(attributesToGet, this.params, this.metadata)
47+
return <GetRequestProjected<T>>(<any>this)
4748
}
4849

4950
execFullResponse(): Promise<GetResponse<T>> {
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import * as DynamoDB from 'aws-sdk/clients/dynamodb'
2+
import { Metadata } from '../../../decorator/metadata/metadata'
3+
import { resolveAttributeNames } from '../../expression/functions/attribute-names.function'
4+
5+
/**
6+
* Adds ProjectionExpression param and expressionAttributeNames to the params object
7+
*/
8+
export function addProjectionExpressionParam<T>(
9+
attributesToGet: Array<keyof T | string>,
10+
params: DynamoDB.QueryInput | DynamoDB.ScanInput | DynamoDB.GetItemInput | DynamoDB.KeysAndAttributes,
11+
metadata?: Metadata<T>,
12+
): void {
13+
const resolved = attributesToGet.map(attributeToGet => resolveAttributeNames(<string>attributeToGet, metadata))
14+
params.ProjectionExpression = resolved.map(attr => attr.placeholder).join(', ')
15+
resolved.forEach(r => {
16+
params.ExpressionAttributeNames = { ...params.ExpressionAttributeNames, ...r.attributeNames }
17+
})
18+
}

src/dynamo/request/query/query.response.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import * as DynamoDB from 'aws-sdk/clients/dynamodb'
99
*/
1010
export interface QueryResponse<T> {
1111
/**
12-
* An array of item attributes that match the query criteria. Each element in this array consists of an attribute name and the value for that attribute, as specified by ProjectionExpression.
12+
* An array of item attributes that match the query criteria. Each element in this array consists of an attribute name and the value for that attribute (subset of attributes if ProjectionExpression was defined).
1313
*/
1414
Items: T[]
1515
/**

src/dynamo/request/read-many.request.spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as DynamoDB from 'aws-sdk/clients/dynamodb'
22
import {
3+
ComplexModel,
34
ModelWithABunchOfIndexes,
45
SimpleWithCompositePartitionKeyModel,
56
SimpleWithPartitionKeyModel,
@@ -124,6 +125,23 @@ describe('ReadManyRequest', () => {
124125
})
125126
})
126127

128+
describe('projectionExpression', () => {
129+
beforeEach(() => {
130+
request = new TestRequest(ComplexModel)
131+
})
132+
133+
it('should set param for projection expression', () => {
134+
request.projectionExpression('active', 'lastUpdated')
135+
expect(request.params.ProjectionExpression).toBe('#active, #lastUpdated')
136+
expect(request.params.ExpressionAttributeNames).toEqual({ '#active': 'isActive', '#lastUpdated': 'lastUpdated' })
137+
})
138+
139+
it('should return instance', () => {
140+
const r = request.projectionExpression('active')
141+
expect(r).toBe(request)
142+
})
143+
})
144+
127145
describe('conditions functions', () => {
128146
beforeEach(() => {
129147
request = new TestRequest(SimpleWithPartitionKeyModel)

src/dynamo/request/read-many.request.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { fromDb } from '../../mapper/mapper'
1010
import { Attributes } from '../../mapper/type/attribute.type'
1111
import { ModelConstructor } from '../../model/model-constructor'
1212
import { DynamoDbWrapper } from '../dynamo-db-wrapper'
13-
import { resolveAttributeNames } from '../expression/functions/attribute-names.function'
1413
import { and } from '../expression/logical-operator/and.function'
1514
import { addExpression } from '../expression/param-util'
1615
import { addCondition } from '../expression/request-expression-builder'
@@ -19,6 +18,7 @@ import {
1918
RequestConditionFunctionTyped,
2019
} from '../expression/type/condition-expression-definition-chain'
2120
import { ConditionExpressionDefinitionFunction } from '../expression/type/condition-expression-definition-function'
21+
import { addProjectionExpressionParam } from './helper/add-projection-expression-param.function'
2222
import { QueryRequest } from './query/query.request'
2323
import { QueryResponse } from './query/query.response'
2424
import { ScanRequest } from './scan/scan.request'
@@ -108,16 +108,11 @@ export abstract class ReadManyRequest<
108108
}
109109

110110
/**
111-
* Specifies the list of document attributes to be returned from the table instead of returning the entire document
112-
* @param attributesToGet List of document attributes to be returned
111+
* Specifies the list of model attributes to be returned from the table instead of returning the entire document
112+
* @param attributesToGet List of model attributes to be returned
113113
*/
114-
projectionExpression(...attributesToGet: string[]): R {
115-
// tslint:disable-next-line:no-unnecessary-callback-wrapper
116-
const resolved = attributesToGet.map(a => resolveAttributeNames(a))
117-
this.params.ProjectionExpression = resolved.map(attr => attr.placeholder).join(', ')
118-
Object.values(resolved).forEach(r => {
119-
this.params.ExpressionAttributeNames = { ...this.params.ExpressionAttributeNames, ...r.attributeNames }
120-
})
114+
projectionExpression(...attributesToGet: Array<keyof T | string>): R {
115+
addProjectionExpressionParam(attributesToGet, this.params, this.metadata)
121116
return <any>this
122117
}
123118

src/dynamo/request/scan/scan.response.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as DynamoDB from 'aws-sdk/clients/dynamodb'
55

66
export interface ScanResponse<T> {
77
/**
8-
* An array of item attributes that match the scan criteria. Each element in this array consists of an attribute name and the value for that attribute, as specified by ProjectionExpression.
8+
* An array of item attributes that match the scan criteria. Each element in this array consists of an attribute name and the value for that attribute (subset of attributes if ProjectionExpression was defined).
99
*/
1010
Items: T[]
1111
/**

0 commit comments

Comments
 (0)