Skip to content

Commit

Permalink
feat(core): add consistent read support to count, find, findOne, and …
Browse files Browse the repository at this point in the history
…exists
  • Loading branch information
whimzyLive committed May 6, 2022
1 parent 822b34a commit 96f0953
Show file tree
Hide file tree
Showing 6 changed files with 229 additions and 143 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@
"unmarshall"
],
"editor.formatOnSave": true,
"typescript.tsdk": "node_modules/typescript/lib"
"typescript.tsdk": "node_modules/typescript/lib",
"jest.autoRun": "off"
}
1 change: 0 additions & 1 deletion __test__/github-issues/157/issue-157.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ test('correctly queries matching items when values for set type is not a native
IndexName: 'email-index-v2',
KeyConditionExpression: '#KY_CE_email = :KY_CE_email',
Limit: 1,
ScanIndexForward: true,
TableName: 'user-v2',
});

Expand Down
29 changes: 16 additions & 13 deletions packages/core/src/classes/manager/__test__/entity-manager.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,15 +230,22 @@ test('finds one entity by given primary key', async () => {
}),
});

const userEntity = await manager.findOne<User, UserPrimaryKey>(User, {
id: '1',
});
const userEntity = await manager.findOne<User, UserPrimaryKey>(
User,
{
id: '1',
},
{
consistentRead: true,
}
);
expect(dcMock.get).toHaveBeenCalledTimes(1);
expect(dcMock.get).toHaveBeenCalledWith({
Key: {
PK: 'USER#1',
SK: 'USER#1',
},
ConsistentRead: true,
TableName: 'test-table',
});
expect(userEntity).toEqual({
Expand Down Expand Up @@ -304,6 +311,7 @@ test('checks if given item exists', async () => {
id: '1',
},
{
consistentRead: true,
returnConsumedCapacity: CONSUMED_CAPACITY_TYPE.INDEXES,
}
);
Expand All @@ -314,6 +322,7 @@ test('checks if given item exists', async () => {
SK: 'USER#1',
},
TableName: 'test-table',
ConsistentRead: true,
ReturnConsumedCapacity: 'INDEXES',
});
expect(userEntity).toEqual(true);
Expand Down Expand Up @@ -1060,7 +1069,6 @@ test('finds items matching given query params', async () => {
KeyConditionExpression:
'(#KY_CE_PK = :KY_CE_PK) AND (begins_with(#KY_CE_SK, :KY_CE_SK))',
Limit: 10,
ScanIndexForward: true,
TableName: 'test-table',
});
expect(users).toEqual({
Expand Down Expand Up @@ -1110,6 +1118,7 @@ test('finds items matching given query params and options', async () => {
keyCondition: {
BEGINS_WITH: 'USER#',
},
consistentRead: true,
where: {
AND: {
age: {
Expand Down Expand Up @@ -1141,12 +1150,12 @@ test('finds items matching given query params and options', async () => {
':FE_age_start': 1,
':FE_name': 'Me',
},
ConsistentRead: true,
KeyConditionExpression:
'(#KY_CE_PK = :KY_CE_PK) AND (begins_with(#KY_CE_SK, :KY_CE_SK))',
FilterExpression:
'(#FE_age BETWEEN :FE_age_start AND :FE_age_end) AND (#FE_name = :FE_name) AND (attribute_exists(#FE_status))',
Limit: 10,
ScanIndexForward: true,
TableName: 'test-table',
});
expect(users).toEqual({
Expand Down Expand Up @@ -1199,7 +1208,6 @@ test('finds items with alternate syntax', async () => {
KeyConditionExpression:
'(#KY_CE_PK = :KY_CE_PK) AND (begins_with(#KY_CE_SK, :KY_CE_SK))',
Limit: 10,
ScanIndexForward: true,
TableName: 'test-table',
});
expect(users).toEqual({
Expand Down Expand Up @@ -1272,7 +1280,6 @@ test('finds item from given cursor position', async () => {
KeyConditionExpression:
'(#KY_CE_PK = :KY_CE_PK) AND (begins_with(#KY_CE_SK, :KY_CE_SK))',
Limit: 10,
ScanIndexForward: true,
TableName: 'test-table',
});
});
Expand Down Expand Up @@ -1333,7 +1340,6 @@ test('queries items until limit is met', async () => {
KeyConditionExpression:
'(#KY_CE_PK = :KY_CE_PK) AND (begins_with(#KY_CE_SK, :KY_CE_SK))',
Limit: 2000,
ScanIndexForward: true,
TableName: 'test-table',
},
],
Expand All @@ -1351,7 +1357,6 @@ test('queries items until limit is met', async () => {
KeyConditionExpression:
'(#KY_CE_PK = :KY_CE_PK) AND (begins_with(#KY_CE_SK, :KY_CE_SK))',
Limit: 2000,
ScanIndexForward: true,
TableName: 'test-table',
},
],
Expand All @@ -1378,6 +1383,7 @@ test('counts items matching given query params', async () => {
keyCondition: {
BEGINS_WITH: 'USER#',
},
consistentRead: true,
}
);

Expand All @@ -1394,7 +1400,7 @@ test('counts items matching given query params', async () => {
KeyConditionExpression:
'(#KY_CE_PK = :KY_CE_PK) AND (begins_with(#KY_CE_SK, :KY_CE_SK))',
Select: 'COUNT',
ScanIndexForward: true,
ConsistentRead: true,
TableName: 'test-table',
});
expect(usersCount).toEqual(132);
Expand Down Expand Up @@ -1466,7 +1472,6 @@ test('counts items with multiple requests', async () => {
FilterExpression:
'(#FE_age BETWEEN :FE_age_start AND :FE_age_end) AND (#FE_name = :FE_name) AND (attribute_exists(#FE_status))',
Select: 'COUNT',
ScanIndexForward: true,
TableName: 'test-table',
},
],
Expand All @@ -1492,7 +1497,6 @@ test('counts items with multiple requests', async () => {
FilterExpression:
'(#FE_age BETWEEN :FE_age_start AND :FE_age_end) AND (#FE_name = :FE_name) AND (attribute_exists(#FE_status))',
Select: 'COUNT',
ScanIndexForward: true,
TableName: 'test-table',
},
],
Expand All @@ -1518,7 +1522,6 @@ test('counts items with multiple requests', async () => {
FilterExpression:
'(#FE_age BETWEEN :FE_age_start AND :FE_age_end) AND (#FE_name = :FE_name) AND (attribute_exists(#FE_status))',
Select: 'COUNT',
ScanIndexForward: true,
TableName: 'test-table',
},
],
Expand Down
68 changes: 64 additions & 4 deletions packages/core/src/classes/manager/entity-manager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
CONSUMED_CAPACITY_TYPE,
EntityAttributes,
EntityTarget,
MANAGER_NAME,
Expand Down Expand Up @@ -95,6 +96,16 @@ export interface EntityManagerFindOptions<Entity, PartitionKey> {
* @default all attributes are fetched
*/
select?: ProjectionKeys<Entity>;

/**
* Perform a consistent read on the table, consumes twice as much RCUs then normal
*
* @description Strongly consistent reads are not supported on global secondary indexes.
* If you query a global secondary index with ConsistentRead set to true,
* you will receive a ValidationException.
* @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ConsistentRead
*/
consistentRead?: boolean;
}

export interface EntityManagerCountOptions<Entity, PartitionKey> {
Expand All @@ -115,6 +126,16 @@ export interface EntityManagerCountOptions<Entity, PartitionKey> {
* are read
*/
where?: FilterOptions<Entity, PartitionKey>;

/**
* Perform a consistent read on the table, consumes twice as much RCUs then normal
*
* @description Strongly consistent reads are not supported on global secondary indexes.
* If you query a global secondary index with ConsistentRead set to true,
* you will receive a ValidationException.
* @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ConsistentRead
*/
consistentRead?: boolean;
}

export interface EntityManagerFindOneOptions<Entity> {
Expand All @@ -123,6 +144,35 @@ export interface EntityManagerFindOneOptions<Entity> {
* @default all attributes are fetched
*/
select?: ProjectionKeys<Entity>;

/**
* Perform a consistent read on the table, consumes twice as much RCUs then normal
*/
consistentRead?: boolean;
}

export interface EntityManagerExistsOptions {
/**
* Perform a consistent read on the table, consumes twice as much RCUs then normal
*
* @description Strongly consistent reads are not supported on global secondary indexes.
* If you query a global secondary index with ConsistentRead set to true,
* you will receive a ValidationException.
* @see https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-ConsistentRead
*/
consistentRead?: boolean;

/**
* @deprecated - Provide the "requestId" in the next parameter instead
* Only available here for backwards compatibility
*/
requestId?: string;

/**
* @deprecated - Provide the "returnConsumedCapacity" in the next parameter instead
* Only available here for backwards compatibility
*/
returnConsumedCapacity?: CONSUMED_CAPACITY_TYPE;
}

export class EntityManager {
Expand Down Expand Up @@ -262,6 +312,7 @@ export class EntityManager {
async exists<Entity, KeyAttributes = Partial<Entity>>(
entityClass: EntityTarget<Entity>,
attributes: KeyAttributes,
options?: EntityManagerExistsOptions,
metadataOptions?: MetadataOptions
) {
if (isEmptyObject(attributes)) {
Expand Down Expand Up @@ -314,14 +365,21 @@ export class EntityManager {
return !!(await this.findOne(
entityClass,
attributes,
undefined,
metadataOptions
{consistentRead: options?.consistentRead},
{
requestId: options?.requestId ?? metadataOptions?.requestId,
returnConsumedCapacity:
options?.returnConsumedCapacity ??
metadataOptions?.returnConsumedCapacity,
}
));
}

// try finding entity by unique attribute
if (!isEmptyObject(uniqueAttributes)) {
const requestId = getUniqueRequestId(metadataOptions?.requestId);
const requestId = getUniqueRequestId(
options?.requestId ?? metadataOptions?.requestId
);
if (Object.keys(uniqueAttributes).length > 1) {
throw new Error('Can only query one unique attribute at a time.');
}
Expand All @@ -345,7 +403,9 @@ export class EntityManager {
const response = await this.connection.documentClient.get({
Key: {...parsedPrimaryKey},
TableName: metadata.table.name,
ReturnConsumedCapacity: metadataOptions?.returnConsumedCapacity,
ConsistentRead: options?.consistentRead,
ReturnConsumedCapacity:
options?.requestId ?? metadataOptions?.returnConsumedCapacity,
});
// stats
if (response?.ConsumedCapacity) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,27 @@ test('transforms get item requests for inherited class', () => {
});
});

test('transforms get item requests with consistent read options', () => {
const getItem = transformer.toDynamoGetItem(
Customer,
{
id: '1',
email: 'user@example.com',
},
{
consistentRead: true,
}
);
expect(getItem).toEqual({
Key: {
PK: 'CUS#1',
SK: 'CUS#user@example.com',
},
TableName: 'test-table',
ConsistentRead: true,
});
});

/**
* @group toDynamoPutItem
*/
Expand Down Expand Up @@ -1222,6 +1243,29 @@ test('transforms simple query item request', () => {
});
});

test('transforms simple query item request with consistent read option', () => {
const queryItem = transformer.toDynamoQueryItem<User, UserPrimaryKey>(
User,
{
id: '1',
},
{
consistentRead: true,
}
);
expect(queryItem).toEqual({
ExpressionAttributeNames: {
'#KY_CE_PK': 'PK',
},
ExpressionAttributeValues: {
':KY_CE_PK': 'USER#1',
},
KeyConditionExpression: '#KY_CE_PK = :KY_CE_PK',
TableName: 'test-table',
ConsistentRead: true,
});
});

test('transforms simple query item request with projection expression', () => {
const queryItem = transformer.toDynamoQueryItem<User, UserPrimaryKey>(
User,
Expand All @@ -1241,7 +1285,6 @@ test('transforms simple query item request with projection expression', () => {
ExpressionAttributeValues: {
':KY_CE_PK': 'USER#1',
},
ScanIndexForward: true,
KeyConditionExpression: '#KY_CE_PK = :KY_CE_PK',
TableName: 'test-table',
ProjectionExpression: '#PE_status, #PE_name',
Expand All @@ -1265,7 +1308,6 @@ test('transforms simple count query item request', () => {
ExpressionAttributeValues: {
':KY_CE_PK': 'USER#1',
},
ScanIndexForward: true,
KeyConditionExpression: '#KY_CE_PK = :KY_CE_PK',
TableName: 'test-table',
Select: 'COUNT',
Expand Down Expand Up @@ -1295,7 +1337,6 @@ test('transforms query item request with filter input', () => {
':KY_CE_PK': 'USER#1',
':FE_name': 'suzan',
},
ScanIndexForward: true,
KeyConditionExpression: '#KY_CE_PK = :KY_CE_PK',
FilterExpression: '#FE_name = :FE_name',
TableName: 'test-table',
Expand Down

0 comments on commit 96f0953

Please sign in to comment.