Skip to content

Instantly share code, notes, and snippets.

@tywalch
Created October 28, 2021 03:43
Show Gist options
  • Save tywalch/914a2238d67f6a8f20bf9a744c86d994 to your computer and use it in GitHub Desktop.
Save tywalch/914a2238d67f6a8f20bf9a744c86d994 to your computer and use it in GitHub Desktop.
Updating GSIs with strings like it's possible with numbers
const DynamoDB = require("aws-sdk/clients/dynamodb");
const client = new DynamoDB.DocumentClient({
region: "us-east-1",
endpoint: "http://localhost:8000"
});
/**
* withNumbers demonstrates the update of a "composite" gsi sortkey without knowledge of the current
* composite value at the time of update. This works runs without error and operates as expected.
*/
async function withNumbers() {
const pk = 500;
const sk = 200;
const table = "with_numbers_example";
// clear prior runs
await client.delete({
Key: { pk, sk },
TableName: table
}).promise();
// create a new item
await client.put({
Item: {
number1: 500,
number2: 200,
number3: 10,
number4: 35,
number5: 5,
pk: 500,
sk: 200,
gsi1pk: 10, // single attribute "composite", should mirror `number3`
gsi1sk: 40, // two attribute composite, should mirror `number4` + `number5`
},
TableName: table
}).promise();
// An update to `number5` should update `number5` (the attribute itself) and recalculate `gsi1sk`
await client.update({
UpdateExpression: "SET #gsi1sk = :number5 + #number4, #number5 = :number5 + #number4",
ExpressionAttributeNames: {
"#gsi1sk": "gsi1sk",
"#number4": "number4",
"#number5": "number5",
},
ExpressionAttributeValues: {
":number5": 10,
},
Key: { pk, sk },
TableName: table
}).promise();
const results = await client.query({
IndexName: "gsi1pk-gsi1sk-index",
KeyConditionExpression: "#pk = :pk and #sk = :sk",
ExpressionAttributeNames: {
"#pk": "gsi1pk",
"#sk": "gsi1sk"
},
ExpressionAttributeValues: {
":pk": 10, // still equal to original `number3` value
":sk": 45 // now equal original `number4` value (35) + new `number5` value (10)
},
TableName: table
}).promise();
console.log(JSON.stringify(results, null, 4));
}
/**
* withStrings demonstrates the update of a "composite" gsi sortkey without knowledge of the current
* composite value at the time of update. This uses the same approach as `withNumbers` but does not
* have comparable functionality.
*/
async function withStrings() {
const pk = "mdinescu";
const sk = "#user";
const table = "with_strings_example";
// clear prior runs
await client.delete({
Key: { pk, sk },
TableName: table
}).promise();
// create a new item
await client.put({
Item: {
username: "mdinescu",
team: "alexa_games",
city: "irvine",
state: "ca",
pk: "mdinescu",
sk: "#user",
gsi1pk: "alexa_games", // single attribute "composite", should mirror `team`
gsi1sk: "irvine#ca", // two attribute composite, should mirror `city` + `state`
},
TableName: table
}).promise();
// An update to `city` should update `city` (the attribute itself) and recalculate `gsi1sk`
// Note: doesn't include "#" delimiter to keep things close to the original `withNumbers` example.
await client.update({
UpdateExpression: "SET #gsi1sk = :city + #state, #city = :city + #state",
ExpressionAttributeNames: {
"#gsi1sk": "gsi1sk",
"#city": "city",
"#state": "state",
},
ExpressionAttributeValues: {
":city": "oakland",
},
Key: { pk, sk },
TableName: table
}).promise();
const results = await client.query({
IndexName: "gsi1pk-gsi1sk-index",
KeyConditionExpression: "#pk = :pk and #sk = :sk",
ExpressionAttributeNames: {
"#pk": "gsi1pk",
"#sk": "gsi1sk"
},
ExpressionAttributeValues: {
":pk": "alexa_games", // still equal to original `team` value
":sk": "oakland#ca" // now equal new `city` value ("oakland") + original `state` value ("ca")
},
TableName: table
}).promise();
console.log(JSON.stringify(results, null, 4));
}
async function main() {
await withNumbers();
await withStrings();
}
main().catch(console.log);
{
"KeySchema":[
{
"AttributeName":"pk",
"KeyType":"HASH"
},
{
"AttributeName":"sk",
"KeyType":"RANGE"
}
],
"AttributeDefinitions":[
{
"AttributeName":"pk",
"AttributeType":"N"
},
{
"AttributeName":"sk",
"AttributeType":"N"
},
{
"AttributeName":"gsi1pk",
"AttributeType":"N"
},
{
"AttributeName":"gsi1sk",
"AttributeType":"N"
}
],
"GlobalSecondaryIndexes":[
{
"IndexName":"gsi1pk-gsi1sk-index",
"KeySchema":[
{
"AttributeName":"gsi1pk",
"KeyType":"HASH"
},
{
"AttributeName":"gsi1sk",
"KeyType":"RANGE"
}
],
"Projection":{
"ProjectionType":"ALL"
}
}
],
"BillingMode":"PAY_PER_REQUEST",
"TableName": "with_numbers_example"
}
{
"KeySchema":[
{
"AttributeName":"pk",
"KeyType":"HASH"
},
{
"AttributeName":"sk",
"KeyType":"RANGE"
}
],
"AttributeDefinitions":[
{
"AttributeName":"pk",
"AttributeType":"S"
},
{
"AttributeName":"sk",
"AttributeType":"S"
},
{
"AttributeName":"gsi1pk",
"AttributeType":"S"
},
{
"AttributeName":"gsi1sk",
"AttributeType":"S"
}
],
"GlobalSecondaryIndexes":[
{
"IndexName":"gsi1pk-gsi1sk-index",
"KeySchema":[
{
"AttributeName":"gsi1pk",
"KeyType":"HASH"
},
{
"AttributeName":"gsi1sk",
"KeyType":"RANGE"
}
],
"Projection":{
"ProjectionType":"ALL"
}
}
],
"BillingMode":"PAY_PER_REQUEST",
"TableName": "with_strings_example"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment