Skip to content

Instantly share code, notes, and snippets.

@neilherbertuk
Last active December 20, 2023 16:21
Show Gist options
  • Select an option

  • Save neilherbertuk/33baa098cb1b96aebdd875e59dcb713f to your computer and use it in GitHub Desktop.

Select an option

Save neilherbertuk/33baa098cb1b96aebdd875e59dcb713f to your computer and use it in GitHub Desktop.
Typescript script to rename attributes on a DynamoDB table
/**
* Uses AWS-SDK v3
*/
import {
AttributeValue,
BatchWriteItemCommand,
BatchWriteItemCommandInput,
DynamoDBClient,
ScanCommand,
ScanCommandInput
} from '@aws-sdk/client-dynamodb';
/**
* Interface for Attribute Replacement Configuration
*/
interface IAttributeReplacement {
old: string;
new: string;
}
/**
* Class Constructor Interface
*/
interface IDynamoDBAttributeRenamerProps {
tableName: string;
attributes: IAttributeReplacement[] | IAttributeReplacement;
}
export class DynamoDBAttributeRenamer {
private ddbClient: DynamoDBClient = new DynamoDBClient();
private tableName: string;
private atributes: IAttributeReplacement[];
constructor(props: IDynamoDBAttributeRenamerProps) {
this.tableName = props.tableName;
if (!Array.isArray(props.attributes)) {
props.attributes = [props.attributes];
}
this.atributes = props.attributes;
this.handle();
}
/**
* handle()
*
* Entry point into this class, automatically called from the constructor
*/
public async handle() {
let records = await this.scanPaginateTable();
records = this.renameAttributes(records);
this.batchWriteRecords(records);
}
/**
* batchWriteRecords
*
* Batches write/put operations into batches of 20 and will replace the entire record with the one provided
*
* @param records Record<string, AttributeValue>[]
*/
private batchWriteRecords(records: Record<string, AttributeValue>[]) {
console.log('Batching Write Operations');
const batchSize = 20;
const batches = Array.from(
{length: Math.ceil(records.length / batchSize)},
(v, i) =>
records
.slice(i * batchSize, i * batchSize + batchSize)
.map(item => ({...item}))
);
console.log(`There are ${batches.length} batch(es) to process`);
batches.forEach(batch => {
const batchProps: BatchWriteItemCommandInput = {
RequestItems: {
[this.tableName]: [],
},
};
batch.forEach(item => {
batchProps.RequestItems![this.tableName].push({
PutRequest: {
Item: item,
},
});
});
console.log(batchProps);
try {
this.ddbClient.send(new BatchWriteItemCommand(batchProps));
} catch (err) {
console.error('Error sending batch put request to dynamodb', err);
}
});
}
/**
* renameAttributes
*
* @param results Record<string, AttributeValue>[]
* @returns Record<string, AttributeValue>[]
*/
private renameAttributes(
results: Record<string, AttributeValue>[]
): Record<string, AttributeValue>[] {
results = results.map(item => {
this.atributes.map(attribute => {
console.log(`Updating attributes for ${item.SK.S}`);
if (item[attribute.old]) {
console.log(`Renaming ${attribute.old} to ${attribute.new}`);
item[attribute.new] = item[attribute.old];
delete item[attribute.old];
}
});
return item;
});
return results;
}
/**
* scanPaginateTable
*
* Performs a paginated table scan until no more results are available
* Only returns entries where the `old` attribute names are present
*
* @returns Record<string, AttributeValue>[] = []
*/
private async scanPaginateTable() {
let lastEvaluatedKey: Record<string, AttributeValue> | null = null;
let scanComplete = false;
const records: Record<string, AttributeValue>[] = [];
const filterExpression = this.atributes
.map(attribute => {
return `attribute_exists(${attribute.old})`;
})
.join(' or ');
do {
let scanInput: ScanCommandInput = {
FilterExpression: filterExpression,
TableName: this.tableName,
Limit: 10,
};
if (lastEvaluatedKey) {
scanInput = {
...scanInput,
ExclusiveStartKey: lastEvaluatedKey,
};
}
try {
const command = new ScanCommand(scanInput);
const results = await this.ddbClient.send(command);
// Process the current page of results
console.log('Scan results:', results.Items);
if (results.Items) {
results.Items.forEach(item => {
records.push(item);
});
}
// Check if there are more results to retrieve
if (results.LastEvaluatedKey) {
lastEvaluatedKey = results.LastEvaluatedKey;
} else {
scanComplete = true;
}
} catch (error) {
console.error('Error scanning DynamoDB table:', error);
}
} while (!scanComplete);
return records;
}
}
new DynamoDBAttributeRenamer({
tableName: 'tbl_name_here',
attributes: [
{
old: 'old_attribute_name',
new: 'new_attribute_name',
}
],
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment