Last active
December 20, 2023 16:21
-
-
Save neilherbertuk/33baa098cb1b96aebdd875e59dcb713f to your computer and use it in GitHub Desktop.
Typescript script to rename attributes on a DynamoDB table
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * 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