diff --git a/.gitignore b/.gitignore index 3b111f7..3fc5e82 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ package-lock.json -*.csv \ No newline at end of file +*.csv +config.json diff --git a/README.md b/README.md index 343fb38..26160f2 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Join the chat at https://gitter.im/edasque/DynamoDBtoCSV](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/edasque/DynamoDBtoCSV?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -This application will export the content of a DynamoDB table into CSV (comma-separated values) output. All you need to do is update `config.json` with your AWS credentials and region. +This application will export the content of a DynamoDB table into CSV (comma-separated values) output. All you need to do is copy `config.example.json` to `config.json`, and update with your AWS credentials and region. The output is comma-separated and each field is enclosed by double quotes ("). Double quotes in the data as escaped as \" diff --git a/config.json b/config.example.json similarity index 100% rename from config.json rename to config.example.json diff --git a/dynamoDBtoCSV.js b/dynamoDBtoCSV.js index b54cfc2..37ce928 100644 --- a/dynamoDBtoCSV.js +++ b/dynamoDBtoCSV.js @@ -35,8 +35,8 @@ program const options = program.opts(); if (!options.table) { - console.log("You must specify a table"); - program.outputHelp(); + console.error("You must specify a table"); + program.outputHelp({ error: true }); process.exit(1); } @@ -77,26 +77,35 @@ if (options.mfa && options.profile) { // Update config to include MFA AWS.config.update({ credentials: creds }); } else if (options.mfa && !options.profile) { - console.log('error: MFA requires a profile(-p [profile]) to work'); + console.error('error: MFA requires a profile(-p [profile]) to work'); process.exit(1); } const dynamoDB = new AWS.DynamoDB(); +// Map attribute name selections to indexed aliases - to allow querying on fields that happen to have the same name as a reserved word. +const attributeIndexSelectionPairs = options.select?.split(',')?.map((attr, index) => [`#${index}`, attr.trim()]); +const selectionsByAttributeNames = attributeIndexSelectionPairs ? Object.fromEntries(attributeIndexSelectionPairs) : undefined; + +const ProjectionExpression = selectionsByAttributeNames ? Object.keys(selectionsByAttributeNames).join(",") : undefined; +const ExpressionAttributeNames = selectionsByAttributeNames; + const query = { TableName: options.table, IndexName: options.index, Select: options.count ? "COUNT" : (options.select ? "SPECIFIC_ATTRIBUTES" : (options.index ? "ALL_PROJECTED_ATTRIBUTES" : "ALL_ATTRIBUTES")), KeyConditionExpression: options.keyExpression, - ExpressionAttributeValues: JSON.parse(options.keyExpressionValues), - ProjectionExpression: options.select, + ExpressionAttributeValues: options.keyExpressionValues && JSON.parse(options.keyExpressionValues), + ProjectionExpression, + ExpressionAttributeNames, Limit: 1000 }; const scanQuery = { TableName: options.table, IndexName: options.index, - ProjectionExpression: options.select, + ProjectionExpression, + ExpressionAttributeNames, Limit: 1000 }; @@ -159,9 +168,9 @@ const appendStats = (params, items) => { const printStats = (stats) => { if (stats) { - console.log("\nSTATS\n----------"); + console.error("\nSTATS\n----------"); Object.keys(stats).forEach((key) => { - console.log(key + " = " + stats[key]); + console.error(key + " = " + stats[key]); }); writeCount += rowCount; rowCount = 0; @@ -228,8 +237,8 @@ const unparseData = (lastEvaluatedKey) => { console.log(endData); } // Print last evaluated key so process can be continued after stop. - console.log("last key:"); - console.log(lastEvaluatedKey); + console.error("last key:"); + console.error(lastEvaluatedKey); // reset write array. saves memory unMarshalledArray = [];