/*
  Title: Ghosting with DynamoDB and BatchWriteItem.
  Example For: AWS Lambda/Node.js and Dynamo DB completed on 7/20/2016.
  Introduction: With Dynamo DB there are a few troubling limitations that may require, depending on your scenario, unique solutions in a modern
  data intensive environment.

  In this scenario, let's assume the following:
  1. You have anywhere from 1 to 50 rows of data in a DynamoDB table that have to be updated
  2. Currently there is no way to perform a batch update with DynamoDB; instead you are required to instead do a batch delete and then a batch insert.
     Even in that case, you are limited to a paltry 25 items to delete or insert at a time.
  3. You will never have a set number of rows that will be updated, other than knowing no more than 50 rows will be updated during a single function request
  4. Your DynamoDB table contains a unique primary key which is a positive number (zero or greater than zero)
  5. You won't be using any 3rd party libraries, rather just using AWS provided functions with AWS Lambda/Node.js and DynamoDB

  As is the case with most database tables, you won't be able to reference arbitrary ID numbers for keys since it is possible that those ID numbers
  will have already been used to store data you may not want to have deleted and updated.  The question is: How do you uniformily ensure that
  a maximum of 50 rows of data is deleted and updated WITHOUT touching data you do NOT want to be deleted and updated...and yet, still allow as little
  as 1 row of "real" data to be updated?

  The solution is simple.  As long as the table uses a positive primary key sequence and the architecture/application software was developed to work
  with positive primary keys (for example, all traditional RDBMS tables (such as SQL) I've encountered utilize positive primary keys), you can reliably
  delete and update a given range of rows - such as 50 rows in this case - WITHOUT affecting "real" rows of data by utilizing negative primary keys.  
  Once you have created the "ghost" or "filler" rows you plan on inserting with negative primary keys via an array, you can update that array with the 
  real row(s) of data (which would have positive primary keys).  Then, when you perform the delete and insert operations for 50 rows, only the real row(s)
  of data will be affected as seen by the architecture/application software.

  This may be considered "ghosts in the machine", but as you can see, it's not random segments of code.  :)
*/

var AWS = require("aws-sdk");
var batClient = new AWS.DynamoDB();

exports.handler = function(event, context) {
/* STEP 1: Array of "real" data that should replace data in Dynamo table.  In this example, the array may have one or up to 50 rows to replace in Dynamo. */
retainEntries = [];
retainEntries[retainEntries.length] = { "ID": 1, "SomeValue": "some value 1" };
retainEntries[retainEntries.length] = { "ID": 2, "SomeValue": "some value 2" };

/* STEP 2: Create the maximum number of rows to be deleted at once, or created at once.  In the case of this example, 50. */
/*
  NOTE: Notice we are specifying a NEGATIVE ID as the key.  Most DB systems only utilize POSITIVE ID's (Dynamo accepts NEGATIVE ID's for keys).
  If the architecture/application software (such as Lambda Node.js function or other) is geared toward only handling POSITIVE ID keys
  the rows being deleted and added (as fillers) won't be seen.  This allows us to batch delete a block of rows with specific ID's without deleting
  actual rows (with the exception of what was specified in the array "retainEntries") and the same with the batch insert operation.
*/
delKeys25x1 = []; delKeys25x2 = [];
insKeys25x1 = []; insKeys25x2 = [];
nada = []; nada[0] = { "S": "some value" };
for (var dk = 1; dk <= 25; dk++) {
	/* Assemble Delete Keys for ghost/filler data */
	delKeys25x1[delKeys25x1.length] = { "DeleteRequest": { "Key": { "ID": { "N": (dk * -1).toString() } } } };
	delKeys25x2[delKeys25x2.length] = { "DeleteRequest": { "Key": { "ID": { "N": ((dk + 25) * -1).toString() } } } };
	/* Assemble Insert Data for ghost/filler data */
	insKeys25x1[insKeys25x1.length] = { "PutRequest": { "Item": { "ID": { "N": (dk * -1).toString() }, "SomeValue": { "L": nada } } } };
	insKeys25x2[insKeys25x2.length] = { "PutRequest": { "Item": { "ID": { "N": ((dk + 25) * -1).toString() }, "SomeValue": { "L": nada } } } };
}

/* STEP 3: Go through the array of data which should be updated.  In this case 3 rows will have actual data, the rest is ghost/filler data. */
var tmp_ID = 0;
tmp_SomeValue = [];
var tmp_ixoffset = 0;
for (var x = 0; x < retainEntries.length; x++) {
	var tmp_ID = retainEntries[x].ID;
	tmp_SomeValue[0] = { "S": retainEntries[x].SomeValue.toString() };
	if ((x + 1) > 0 && (x + 1) <= 25) {
		delKeys25x1[x] = { "DeleteRequest": { "Key": { "ID": { "N": tmp_ID.toString() } } } };
		insKeys25x1[x] = { "PutRequest": { "Item": { "ID": { "N": tmp_ID.toString() }, "SomeValue": { "L": tmp_SomeValue } } } };
	}
	if ((x + 1) > 25 && (x + 1) <= 50) {
		tmp_ixoffset = x - 25;
		delKeys25x2[tmp_ixoffset] = { "DeleteRequest": { "Key": { "ID": { "N": tmp_ID.toString() } } } };
		insKeys25x2[tmp_ixoffset] = { "PutRequest": { "Item": { "ID": { "N": tmp_ID.toString() }, "SomeValue": { "L": tmp_SomeValue } } } };
	}
}

/* STEP 4: Perform DELETE operation for 50 rows */
params = {
	RequestItems: {
		"YourTable": delKeys25x1
	},
	ReturnConsumedCapacity: "NONE",
	ReturnItemCollectionMetrics: "NONE"
};
batClient.batchWriteItem(params, function(err, data) {
	/* For example, assume no error */
	params = {
		RequestItems: {
			"YourTable": delKeys25x2
		},
		ReturnConsumedCapacity: "NONE",
		ReturnItemCollectionMetrics: "NONE"
	};
	batClient.batchWriteItem(params, function(err, data) {
		/* For example, assume no error */
		/* STEP 5: Perform INSERT operation for 50 rows */
		params = {
			RequestItems: {
				"YourTable": insKeys25x1
			},
			ReturnConsumedCapacity: "NONE",
			ReturnItemCollectionMetrics: "NONE"
		};
		batClient.batchWriteItem(params, function(err, data) {
			/* For example, assume no error */

			params = {
				RequestItems: {
					"YourTable": insKeys25x2
				},
				ReturnConsumedCapacity: "NONE",
				ReturnItemCollectionMetrics: "NONE"
			};
			batClient.batchWriteItem(params, function(err, data) {
				/* For example, assume no error */
				/* Completed. */
				context.done(null, { "msg": "Action Complete." });
			});
		});
	});
});

};