dynamodb-put-item-statement.js

"use strict";
var DynamoDbSqlishParser = require("../lib/dynamodb-sqlish-parser.js");
var Statement = require('../lib/dynamodb-statement.js');
const DynamoDbDataModels = require("../lib/dynamodb-data-models.js");

/**
 * SQL-ish PutItem statement class for AWS DynamoDB.
 *
 * @param {string|object} opt
 * SQL-ish PutItem statement as string or parameter object
 * for PutItem API.
 *
 * @constructor
 */
function DynamoDbPutItemStatement(opt) {
    Statement.apply(this, Array.from(arguments));
    this._itemAttr = null;
    if(typeof(opt) === "string") {
        opt = DynamoDbPutItemStatement.parse(opt);
        this._itemAttr = {
            names: opt._attrNames,
            values: opt._attrNames
        };
    }
    if(!("TableName" in opt)) {
        throw new Error("TableName required");
    }
    if(!("Item" in opt)) {
        throw new Error("Item required");
    }
    this.setTableName(opt.TableName);
    this.item = DynamoDbSqlishParser.parseItemListToMap(opt.Item);
    if("ConditionExpression" in opt) {
        this.conditionExpression =
            this.parseConditionExpression( opt.ConditionExpression );
    }
}


DynamoDbPutItemStatement.prototype = new Statement();

DynamoDbPutItemStatement.parse = function(sqlish) {
    var opt = {};
    var st = DynamoDbSqlishParser.parsePutItem(sqlish);

    var tableName = st.getTerm("table-name");
    if(!tableName.match) {
        throw new Error("the table-name not found");
    }
    opt.TableName = st.getWordsList("table-name")[0].join("");

    opt._attrNames =
        st.getTerm("attribute-list")
        .getWordsList("attribute-name")
        .map( (arr) => { return arr[0]; } );
    
    opt._values =
        st.getTerm("value-list")
        .getWordsList("value")
        .map( (arr) => { return arr[0]; } );

    if(opt._attrNames.length != opt._values.length) {
        throw new Error("the number of attribute names and values are not match.");
    }

    opt.Item = opt._attrNames.map((name, index) => {
        return [name, opt._values[index]].join("=");
    }).join(",");

    var whereClause = st.getTerm("where-key-clause");
    if(whereClause.match) {
        opt.ConditionExpression =
            whereClause.getWordsList("condition-expression")[0].join(" ");
    }

    return opt;
};

/**
 * Set an item to this statement.
 * The item will be put to the DynamoDB when this statement ran.
 *
 * @param {array|object} values
 * If this is array, it must contain values as same order for the
 * attributes specified as SQL in the constructor.
 * If this is an object, the key is an attribute names.
 * Whether each cases above, the attribute names must be specified by
 * SQL-ish statement at the constructor.
 * The values will be converted to DynamoDB Map Object.
 * So, you can specify a value as is. 
 *
 * @returns {undefined}
 */
DynamoDbPutItemStatement.prototype.setValues = function(values) {
    if(Array.isArray(values)) {
        if(this._itemAttr == null) {
            throw new Error("could not set values. there is no attrinute names.");
        } else if(values.length != this._itemAttr.names.length) {
            throw new Error("The invalid values. its length unmatch to names.");
        } else {
            this._itemAttr.names.forEach( (name, i) => {
                this.item[name] = DynamoDbDataModels.obj2map(values[i]);
            });
        }
    } else if(typeof(values) === "object") {
        Object.keys(values).forEach( name => {
            if(!(name in this.item)) {
                throw new Error("name not exists in attributes");
            }
            this.item[name] = DynamoDbDataModels.obj2map(values[name]);
        });
    }
};

/**
 * Run the statement in async.
 *
 * @param {array|object} args
 * This parameter can be what is same to the parameter of `setValues`
 * method. If it is an array, it contains the values of attributes.
 * Or if it is an object, its key that do not start with ':'
 * represents the attribute names.
 *
 * @param {Function} callback
 * The callback function to get an error.
 * The declaration is (err) => { ... }.
 *
 * @returns {undefined}
 *
 * @see #setValues
 */
DynamoDbPutItemStatement.prototype.run = function(args, callback) {
    var argsClass = this.classifyValuesAndPlaceholders(args);
    let values = argsClass.values;
    let placeholders = argsClass.placeholders;

    if(values != null) {
        this.setValues(values);
    }
    var param = this.getParameter(placeholders);
    Statement.assertAllParamSpecified(param);
    this.dynamodb.putItem(param, callback);
};

DynamoDbPutItemStatement.prototype.classifyValuesAndPlaceholders = function(args) {
    let values = null;
    let placeholders = {};
    if(Array.isArray(args)) {
        values = args;
    } else if(typeof(args) === "object") {
        Object.keys(args).forEach( key => {
            if(key.match("^:")) {
                placeholders[key] = args[key];
            } else {
                if(values == null) {
                    values = {};
                }
                values[key] = args[key];
            }
        });
    }
    return { values: values, placeholders: placeholders };
};

DynamoDbPutItemStatement.prototype.getParameter = function(args) {
    var opt = Statement.prototype.getParameter.call(this, args);
    if(this.conditionExpression) {
        opt.ConditionExpression = this.conditionExpression;
    }
    if(this.item) {
        opt.Item = this.item;
    }
    return opt;
};
module.exports = DynamoDbPutItemStatement;