Skip to content

Commit

Permalink
enable projection expressions in scan command
Browse files Browse the repository at this point in the history
  • Loading branch information
Thomas Aribart committed Nov 1, 2023
1 parent e368009 commit 9f88c1b
Show file tree
Hide file tree
Showing 9 changed files with 67 additions and 10 deletions.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import isEmpty from 'lodash.isempty'
import { parseCapacityOption } from 'v1/commands/utils/parseOptions/parseCapacityOption'
import { rejectExtraOptions } from 'v1/commands/utils/parseOptions/rejectExtraOptions'
import { parseConsistentOption } from 'v1/commands/utils/parseOptions/parseConsistentOption'
import { parseProjection } from 'v1/commands/expression/projection/parse'
import { EntityV2 } from 'v1/entity'

import type { GetItemOptions } from '../options'
import { parseProjection } from '../projection'

type CommandOptions = Omit<GetCommandInput, 'TableName' | 'Key'>

Expand Down
2 changes: 1 addition & 1 deletion src/v1/commands/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ export { ScanCommand } from './scan'
export type { ScanOptions, ScanResponse } from './scan'
export { formatSavedItem } from './utils/formatSavedItem'
export { parseCondition } from './expression/condition/parse'
export { parseProjection } from './getItem/projection'
export { parseProjection } from './expression/projection/parse'
export * from './types'
4 changes: 2 additions & 2 deletions src/v1/commands/scan/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type ScanOptions<ENTITIES extends EntityV2 = EntityV2> = {
limit?: number
filters?: EntityV2 extends ENTITIES
? Record<string, Condition>
: { [ENTITY in ENTITIES as ENTITY['name']]: Condition<ENTITY> }
: { [ENTITY in ENTITIES as ENTITY['name']]?: Condition<ENTITY> }
} & (
| { segment?: never; totalSegments?: never }
// Either both segment & totalSegments are set, either none
Expand All @@ -32,7 +32,7 @@ export type ScanOptions<ENTITIES extends EntityV2 = EntityV2> = {
| {
attributes: EntityV2 extends ENTITIES
? Record<string, Condition>
: { [ENTITY in ENTITIES as ENTITY['name']]: AnyAttributePath<ENTITY>[] }
: { [ENTITY in ENTITIES as ENTITY['name']]?: AnyAttributePath<ENTITY>[] }
select?: SpecificAttributesSelectOption
}
)
36 changes: 30 additions & 6 deletions src/v1/commands/scan/scanParams/scanParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { parseLimitOption } from 'v1/commands/utils/parseOptions/parseLimitOptio
import { rejectExtraOptions } from 'v1/commands/utils/parseOptions/rejectExtraOptions'
import { isInteger } from 'v1/utils/validation/isInteger'
import { parseCondition } from 'v1/commands/expression/condition/parse'
import { parseProjection } from 'v1/commands/expression/projection/parse'

import type { ScanOptions } from '../options'

Expand Down Expand Up @@ -131,24 +132,43 @@ export const scanParams = <
const expressionAttributeNames: Record<string, string> = {}
const expressionAttributeValues: Record<string, any> = {}
const filterExpressions: string[] = []
const projectionExpressions: string[] = []

entities.forEach((entity, index) => {
const entityNameFilter = { attr: entity.entityAttributeName, eq: entity.name }
const entityFilter = filters[entity.name]

const {
ExpressionAttributeNames,
ExpressionAttributeValues,
ConditionExpression
ExpressionAttributeNames: filterExpressionAttributeNames,
ExpressionAttributeValues: filterExpressionAttributeValues,
ConditionExpression: filterExpression
} = parseCondition<EntityV2, Condition<EntityV2>>(
entity,
entityFilter !== undefined ? { and: [entityNameFilter, entityFilter] } : entityNameFilter,
index.toString()
)

Object.assign(expressionAttributeNames, ExpressionAttributeNames)
Object.assign(expressionAttributeValues, ExpressionAttributeValues)
filterExpressions.push(ConditionExpression)
Object.assign(expressionAttributeNames, filterExpressionAttributeNames)
Object.assign(expressionAttributeValues, filterExpressionAttributeValues)
filterExpressions.push(filterExpression)

const entityAttributes = attributes[entity.name]
if (entityAttributes !== undefined) {
/**
* @debt refactor "Would be better to have a single attribute list for all entities (typed as the intersection of all entities AnyAttributePath) but parseProjection is designed to work with entities so it's a big rework. Will do that later."
*/
const {
ExpressionAttributeNames: projectionExpressionAttributeNames,
ProjectionExpression: projectionExpression
} = parseProjection<EntityV2, AnyAttributePath[]>(
entity,
entityAttributes,
index.toString()
)

Object.assign(expressionAttributeNames, projectionExpressionAttributeNames)
projectionExpressions.push(projectionExpression)
}
})

if (!isEmpty(expressionAttributeNames)) {
Expand All @@ -165,6 +185,10 @@ export const scanParams = <
? filterExpressions[0]
: `(${filterExpressions.filter(Boolean).join(') OR (')})`
}

if (projectionExpressions.length > 0) {
commandOptions.ProjectionExpression = projectionExpressions.filter(Boolean).join(', ')
}
}

rejectExtraOptions(extraOptions)
Expand Down
33 changes: 33 additions & 0 deletions src/v1/commands/scan/scanParams/scanParams.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,39 @@ describe('scan', () => {
})
})

it('applies entity projection expression', () => {
const { ProjectionExpression, ExpressionAttributeNames } = TestTable.build(ScanCommand)
.entities(Entity1)
.options({ attributes: { entity1: ['age', 'name'] } })
.params()

expect(ProjectionExpression).toBe('#p0_1, #p0_2')
expect(ExpressionAttributeNames).toMatchObject({
'#p0_1': 'age',
'#p0_2': 'name'
})
})

it('applies two entity projection expressions', () => {
const { ProjectionExpression, ExpressionAttributeNames } = TestTable.build(ScanCommand)
.entities(Entity1, Entity2)
.options({
attributes: {
entity1: ['age', 'name'],
entity2: ['price', 'launchDate']
}
})
.params()

expect(ProjectionExpression).toBe('#p0_1, #p0_2, #p1_1, #p1_2')
expect(ExpressionAttributeNames).toMatchObject({
'#p0_1': 'age',
'#p0_2': 'name',
'#p1_1': 'price',
'#p1_2': 'launchDate'
})
})

it('fails on extra options', () => {
const invalidCall = () =>
TestTable.build(ScanCommand)
Expand Down

0 comments on commit 9f88c1b

Please sign in to comment.