API Docs for: 0.8.0
Show:

File: include/dao/dao.js

/*
    Copyright (C) 2016  PencilBlue, LLC

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
'use strict';

//dependencies
var ObjectID = require('mongodb').ObjectID;
var util     = require('../util.js');

module.exports = function DAOModule(pb) {

    /**
     * Controlls the data model
     *
     * @module Database
     * @class DAO
     * @constructor
     * @param {String} [dbName] Will default to the config.db.name DB when not
     * provided.
     * @main Database
     */
    function DAO(dbName){

        /**
         * The name of the DB that this instance is intended to interact with.  By
         * default, it goes to the name of the DB provided by system configuration
         * property db.name.
         * @property dbName
         * @type {String}
         */
        this.dbName = util.isNullOrUndefined(dbName) ? pb.config.db.name : dbName;
    }

    /**
     * Static variable to indicate that all indices of a document should be
     * retrieved
     *
     * @property PROJECT_ALL
     * @type {Object}
     */
    DAO.PROJECT_ALL = Object.freeze({});

    /**
     * Static variable to indicate that documents should be retrieve from anywhere
     *
     * @property ANYWHERE
     * @type {Object}
     */
    DAO.ANYWHERE = Object.freeze({});

    /**
     * Static variable to indicate that documents should be returned in their
     * natural order
     *
     * @property NATURAL_ORDER
     * @type {Array}
     */
    DAO.NATURAL_ORDER = Object.freeze([]);

    /**
     * Static varible to sort ascending
     *
     * @property ASC
     * @type {Number}
     */
    DAO.ASC = 1;

    /**
     * Static variable to sort descending
     *
     * @property DESC
     * @type {Number}
     */
    DAO.DESC = -1;

    /**
     * Retrieves an object by ID
     *
     * @method loadById
     * @param {String}   id         The unique id of the object
     * @param {String}   collection The collection the object is in
     * @param {Object}   Key value pair object to exclude the retrival of data
     * @param {Function} cb         Callback function
     */
    DAO.prototype.loadById = function(id, collection, opts, cb){
        this.loadByValues(DAO.getIdWhere(id), collection, opts, cb);
    };

    /**
     * Retrieves objects matching a key value pair
     *
     * @method loadByValue
     * @param {String}   key        The key to search for
     * @param {*}        val        The value to search for
     * @param {String}   collection The collection to search in
     * @param {Object}   [opts] Key value pair object to exclude the retrieval of data
     * @param {Function} cb         Callback function
     */
    DAO.prototype.loadByValue = function(key, val, collection, opts, cb) {
        var where = {};
        where[key] = val;
        this.loadByValues(where, collection, opts, cb);
    };

    /**
     * Retrieves object matching several key value pairs
     *
     * @method loadByValues
     * @param {Object}   where      Key value pair object
     * @param {String}   collection The collection to search in
     * @param {Object}   [opts] Key value pair object to exclude the retrieval of data
     * @param {Function} cb         Callback function
     */
    DAO.prototype.loadByValues = function(where, collection, opts, cb) {
        if (util.isFunction(opts)) {
            cb = opts;
            opts = null;
        }
        if (!util.isObject(opts)) {
            opts = { };
        }

        var options = {
            where: where,
            select: opts.select || DAO.PROJECT_ALL,
            order: opts.order || DAO.NATURAL_ORDER,
            limit: 1
        };

        this.q(collection, options, function(err, result){
           cb(err, util.isArray(result) && result.length > 0 ? result[0] : null);
        });
    };

    /**
     * Gets the count of objects matching criteria
     *
     * @method count
     * @param  {String}   entityType The type of object to search for
     * @param  {Object}   where      Key value pair object
     * @param  {Function} cb         Callback function
     */
    DAO.prototype.count = function(entityType, where, cb) {
        var options = {
            count: true,
            entityType: entityType,
            where: where
        };
        this._doQuery(options, function(err, cursor) {
            if (util.isError(err)) {
                return cb(err);
            }
            cursor.count(cb);
        });
    };

    /**
     * Determines if an object extists matching criteria
     *
     * @method exists
     * @param  {String}   collection The collection to search in
     * @param  {Object}   where      Key value pair object
     * @param  {Function} cb         Callback function
     */
    DAO.prototype.exists = function(collection, where, cb) {
        this.count(collection, where, function(err, count) {
            cb(err, count > 0);
        });
    };

    /**
     * Determines if there is only a single document that matches the specified query
     *
     * @method unique
     * @param  {String}   collection    The collection to search in
     * @param  {Object}   where         Key value pair object
     * @param  {String}   [exclusionId] Object Id to exclude from the search
     * @param  {Function} cb            Callback function
     */
    DAO.prototype.unique = function(collection, where, exclusionId, cb) {
        if (util.isFunction(exclusionId)) {
            cb          = exclusionId;
            exclusionId = null;
        }

        //validate parameters
        if (!util.isObject(where) || !util.isString(collection)) {
            return cb(new Error("The collection and where parameters are required"));
        }

        //set the exclusion
        if (exclusionId) {
            where[DAO.getIdField()] = DAO.getNotIdField(exclusionId);
        }
        //checks to see how many docs were available
        this.count(collection, where, function(err, count) {
            cb(err, count === 0);
        });
    };

    /**
     * Queries the database. Added in the 0.2.5 release
     * @method q
     * @param  {String} collection The type of object to search for
     * @param  {Object} [options] The options for the query
     * @param {Object} [options.where={}] The conditions under which results are
     * returned
     * @param  {Object} [options.select] Selection type object
     * @param  {Array} [options.order]  Order by array (MongoDB syntax)
     * @param  {Integer} [options.limit] Number of documents to retrieve
     * @param  {Integer} [options.offset] Start index of retrieval
     * @param {Function} [options.handler] A function that takes two paramters.
     * The first, the Cursor object that contains the results of the query.  The
     * second is a callback that takes two parameters.  An error if occurred and by
     * default, the documents returned by the query.  Custom handlers may provide
     * whatever value it wishes including the cursor if it wishes to handle the
     * results itself.
     * @param {Function} cb A callback function that takes two parameters.  The
     * first, an error, if occurred and the second is the result provided by the
     * handler.  By default it provides an array of objects that represent the
     * items returned by the query.
     */
    DAO.prototype.q = function(collection, options, cb) {
        if (util.isFunction(options)) {
            cb      = options;
            options = {};
        }
        else if (!util.isObject(options)) {
            return cb(new Error('OPTIONS_PARAM_MUST_BE_OBJECT'));
        }

        //execute the query
        var self = this;
        var opts = {
            entityType: collection,
            where: options.where,
            select: options.select,
            order: options.order,
            limit: options.limit,
            offset: options.offset
        };
        this._doQuery(opts, function(err, cursor) {
            if (util.isError(err)) {
                return cb(err);
            }

            //handle cursor
            var handler = util.isFunction(options.handler) ? options.handler : self.toArrayCursorHandler;
            handler(cursor, function(err, docs) {

                //close the cursor
                cursor.close(function(err){
                    if (util.isError(err)) {
                        pb.log.error("DAO: An error occurred while attempting to close the cursor. %s", err.stack);
                    }
                });

                //callback with results
                cb(err, docs);
            });
        });
    };

    /**
     * A cursor handler that iterates over each result from the query that created
     * the cursor and places the result into an array.  The array of documents is
     * provided as the second argument in the callback.
     * @method toArrayCursorHandler
     * @param {Cursor} cursor
     * @param {Function} cb
     */
    DAO.prototype.toArrayCursorHandler = function(cursor, cb) {
        cursor.toArray(cb);
    };

    /**
     * The actual implementation for querying.  The function does not do the same
     * type checking as the wrapper function "query".  This funciton is responsible
     * for doing the heavy lifting and returning the result back to the calling intity.
     * @protected
     * @method _doQuery
     * @param {String} entityType The collection to query
     * @param {Object} [where={}] The where clause
     * @param {Object} [select={}] The fields to project
     * @param {Array} [order] The ordering
     * @param {Array} [orderBy] The ordering. Parameter orderBy is deprecated, use order instead.
     * @param {Integer} [limit] The maximum number of results to return
     * @param {Integer} [offset] The number of results to skip before returning results.
     * @return {Cursor} The MongoDB cursor that provides the results of the query
     */
    DAO.prototype._doQuery = function(options, cb) {
        if (!util.isString(options.entityType)) {
            return cb(Error('An entity type must be specified!'));
        }

        //set defaults
        var entityType = options.entityType;
        var where      = options.where  ? options.where : DAO.ANYWHERE;
        var select     = options.select ? options.select : DAO.PROJECT_ALL;
        var offset     = options.offset ? options.offset : 0;

        //get reference to the db
        var self = this;
        this.getDb(function(err, db) {
            if (util.isError(err)) {
                return cb(err);
            }

            //assemble the query
            var cursor = db.collection(options.entityType)
                .find(where, select)
                .skip(offset);

            //apply sort order
            var orderBy = options.order || options.orderBy;
            if (orderBy) {
                cursor.sort(orderBy);
            }

            //apply maximum number of results to return
            if (options.limit) {
                cursor.limit(options.limit);
            }

            //ensure that an "id" value is provided
            if (!options.count) {
                cursor.map(DAO.mapSimpleIdField);
            }

            //log the result
            if(pb.config.db.query_logging){
                var query = "DAO: %s %j FROM %s.%s WHERE %s";
                var args = [options.count ? 'COUNT' : 'SELECT', select, self.dbName, entityType, util.inspect(where, {breakLength: Infinity})];
                if (typeof orderBy !== 'undefined') {
                    query += " ORDER BY %j";
                    args.push(orderBy);
                }
                if (typeof options.limit !== 'undefined') {
                    query += " LIMIT %d, OFFSET %d";
                    args.push(options.limit, offset);
                }
                args.unshift(query);
                pb.log.info(util.format.apply(util, args));
            }
            cb(null, cursor);
        });
    };

    /**
     * Retrieves a reference to the DB with active connection
     * @method getDb
     * @param {Function} cb
     */
    DAO.prototype.getDb = function(cb) {
        pb.dbm.getDb(this.dbName, cb);
    };

    /**
     * Inserts or replaces an existing document with the specified DB Object.
     * An insert is distinguished from an update based the presence of the _id
     * field.
     * @method save
     * @param {Object} dbObj The system object to persist
     * @param {Object} [options] See http://mongodb.github.io/node-mongodb-native/api-generated/collection.html#save
     * @param {Function} cb A callback that takes two parameters.  The first, an
     * error, if occurred.  The second is the object that was persisted
     */
    DAO.prototype.save = function(dbObj, options, cb) {
        if (util.isFunction(options)) {
            cb      = options;
            options = {};
        }
        else if (!util.isObject(options)) {
            return cb(new Error('OPTIONS_PARAM_MUST_BE_OBJECT'));
        }
        if (!util.isObject(dbObj)) {
            return cb(new Error('The dbObj parameter must be an object'));
        }

        //ensure an object_type was specified & update common fields
        dbObj.object_type = dbObj.object_type || options.object_type;
        DAO.updateChangeHistory(dbObj);

        //log interaction
        if (pb.config.db.query_logging) {
            var msg;
            if (dbObj._id) {
                msg = util.format('UPDATE %s WHERE ID=%s', dbObj.object_type, dbObj._id);
            }
            else {
                msg = util.format('INSERT INTO %s', dbObj.object_type);
            }
            pb.log.info(msg);
        }

        //retrieve db reference
        this.getDb(function(err, db) {
            if (util.isError(err)) {
                return cb(err);
            }

            //execute persistence operation
            db.collection(dbObj.object_type).save(dbObj, options, function(err/*, writeOpResult*/) {
                DAO.mapSimpleIdField(dbObj);
                cb(err, dbObj);
            });
        });
    };

    /**
     * Provides a mechanism to save an array of objects all from the same
     * collection.  The function handles updates and inserts.  The difference is
     * determined by the truth value of the ID field of each object.
     * @method saveBatch
     * @param {Array} objArray The array of objects to persist
     * @param {String} collection The collection to persist the objects to
     * @param {Object} [options] See http://mongodb.github.io/node-mongodb-native/api-generated/collection.html#initializeunorderedbulkop
     * @param {Function} cb A callback that takes two arguments.  The first is an
     * error, if occurred. The second is the second parameter of the callback
     * described here: http://mongodb.github.io/node-mongodb-native/api-generated/unordered.html#execute
     */
    DAO.prototype.saveBatch = function(objArray, collection, options, cb) {
        if (util.isFunction(options)) {
            cb      = options;
        }
        if (!util.isArray(objArray)) {
            return cb(new Error('The objArray parameter must be an Array'));
        }
        else if (!util.isString(collection)) {
            return cb(new Error('COLLECTION_MUST_BE_STR'));
        }

        //retrieve db reference
        this.getDb(function(err, db) {
            if (util.isError(err)) {
                return cb(err);
            }

            //initialize the batch operation
            var col = db.collection(collection);
            var batch = col.initializeUnorderedBulkOp();

            //build the batch
            objArray.forEach(function(item) {

                item.object_type = collection;
                DAO.updateChangeHistory(item);
                if (item._id) {
                    batch.find(DAO.getIdWhere(item._id)).updateOne({ $set: item });
                    delete item._id;
                }
                else {
                    batch.insert(item);
                }
            });
            batch.execute(cb);
        });
    };

    /**
     * Updates a specific set of fields. This is handy for performing upserts.
     * @method updateFields
     * @param {String} collection The collection to update object(s) in
     * @param {Object} query The where clause to execute to find the existing object
     * @param {Object} updates The updates to perform
     * @param {Object} [options] Any options to go along with the update
     * @param {Boolean} [options.upsert=false] Inserts the object is not found
     * @param {Boolean} [options.multi=false] Updates multiple records if the query
     * finds more than 1
     * @param {Function} cb
     */
    DAO.prototype.updateFields = function(collection, query, updates, options, cb) {
        if (util.isFunction(options)) {
            cb = options;
            options = {};
        }

        if (pb.config.db.query_logging) {
            pb.log.info('UPDATE %s.%s %s WHERE %s WITH OPTIONS %s', this.dbName, collection, JSON.stringify(updates), JSON.stringify(query), JSON.stringify(options));
        }
        this.getDb(function(err, db) {
            if (util.isError(err)) {
                return cb(err);
            }

            //execute update
            db.collection(collection).update(query, updates, options, cb);
        });
    };

    /**
     * Removes an object from persistence
     *
     * @method deleteById
     * @param {String|ObjectID} oid The Id of the object to remove
     * @param {String} collection The collection the object is in
     * @param {Function} [cb] A callback that takes two parameters.  The first is
     * an error, if occurred.  The second is the number of records deleted by the
     * execution of the command.
     * @return {Promise} Promise object iff a callback is not provided
     */
    DAO.prototype.deleteById = function(oid, collection, cb) {
        if (!pb.validation.isId(oid)) {
            return cb(new Error('An id must be specified in order to delete'));
        }

        var where = DAO.getIdWhere(oid);
        this.delete(where, collection, cb);
    };

    /**
     * Removes objects from persistence that match criteria
     *
     * @method delete
     * @param {Object} where Key value pair object
     * @param {String} collection The collection to search in
     * @param {Object} [options] See http://mongodb.github.io/node-mongodb-native/api-generated/collection.html#remove
     * @param {Function} cb A callback that provides two parameter. The first is an
     * error, if occurred.  The second is the number of records that were removed
     * from persistence.
     */
    DAO.prototype.delete = function(where, collection, options, cb) {
        if (util.isFunction(options)) {
            cb = options;
            options = {};
        }
        else if (!util.isObject(options)) {
            return cb(new Error('OPTIONS_PARAM_MUST_BE_OBJECT'));
        }

        //require param error checking
        if (!util.isObject(where)) {
            return cb(new Error('WHERE_CLAUSE_MUST_BE_OBJECT'));
        }
        else if (!util.isString(collection)) {
            return cb(new Error('COLLECTION_MUST_BE_STR'));
        }

        //log interaction
        if(pb.config.db.query_logging){
            pb.log.info('DAO: DELETE FROM %s.%s WHERE %s', this.dbName, collection, JSON.stringify(where));
        }

        //execute delete command
        pb.dbm.getDb(this.dbName, function(err, db) {
            if (util.isError(err)) {
                return cb(err);
            }
            db.collection(collection).remove(where, options, cb);
        });
    };

    /**
     * Sends a command to the DB.
     * http://mongodb.github.io/node-mongodb-native/api-generated/db.html#command
     * @method command
     * @param {Object} The command to execute
     * @param {Function} cb A callback that provides two parameters: cb(Error, [RESULT])
     */
    DAO.prototype.command = function(command, cb) {
        if (!util.isObject(command)) {
            cb(new Error('COMMAND_MUST_BE_OBJECT'));
            return;
        }

        //execute command
        pb.dbm.getDb(this.dbName, function(err, db) {
            if (util.isError(err)) {
                return cb(err);
            }
            db.command(command, cb);
        });
    };

    /**
     * Attempts to create an index.  If the collection already exists then the
     * operation is skipped.
     * http://mongodb.github.io/node-mongodb-native/api-generated/collection.html#ensureindex
     * @method ensureIndex
     * @param {Object} procedure The objects containing the necessary parameters
     * and options to create the index.
     *  @param {String} procedure.collection The collection to build an index for
     *  @param {Object} procedure.spec An object that specifies one or more fields
     * and sort direction for the index.
     *  @param {Object} [procedure.options={}] An optional parameter that can
     * specify the options for the index.
     * @param {Function} cb A callback that provides two parameters: cb(Error, [RESULT])
     */
    DAO.prototype.ensureIndex = function(procedure, cb) {
        if (!util.isObject(procedure)) {
            cb(new Error('PROCEDURE_MUST_BE_OBJECT'));
            return;
        }

        //extract needed values
        var collection = procedure.collection;
        var spec       = procedure.spec;
        var options    = procedure.options || {};

        //execute command
        pb.dbm.getDb(this.dbName, function(err, db) {
            if (util.isError(err)) {
                return cb(err);
            }
            db.collection(collection).ensureIndex(spec, options, cb);
        });
    };

    /**
     * Retrieves indexes for the specified collection
     * @method indexInfo
     * @param {String} collection
     * @param {Object} [options={}]
     * @param {Function} cb
     */
    DAO.prototype.indexInfo = function(collection, options, cb) {
        if (util.isFunction(options)) {
            cb = options;
            options = {};
        }
        pb.dbm.getDb(this.dbName, function(err, db) {
            if (util.isError(err)) {
                return cb(err);
            }

            db.indexInformation(collection, options, cb);
        });
    };

    /**
     * Drops the specified index from the given collection
     * @method dropIndex
     * @param {String} collection
     * @param {String} indexName
     * @param {Object} [options={}]
     * @param {Function} cb
     */
    DAO.prototype.dropIndex = function(collection, indexName, options, cb) {
        if (util.isFunction(options)) {
            cb = options;
            options = {};
        }

        pb.dbm.getDb(this.dbName, function(err, db) {
            if (util.isError(err)) {
                return cb(err);
            }
            db.collection(collection).dropIndex(indexName, options, cb);
        });
    };

    /**
     * Determines if a collection exists in the DB
     * @method entityExists
     * @param {String} entity The name of the collection
     * @param {Function} cb A callback that takes two parameters. The first, an
     * error, if occurred. The second is a boolean where TRUE means the entity
     * exists, FALSE if not.
     */
    DAO.prototype.entityExists = function(entity, cb) {
        this.listCollections({name: entity}, function(err, results) {
            cb(err, util.isArray(results) && results.length === 1);
        });
    };

    /**
     * Creates a collection in the DB
     * @method createEntity
     * @param {String} entityName
     * @param {Object} [options] The options for the collection. See
     * http://mongodb.github.io/node-mongodb-native/api-generated/db.html#createcollection
     * @param {Function} cb A callback that takes two parameters. The first, an
     * Error, if occurred. The second is the result of the creation command.
     */
    DAO.prototype.createEntity = function(entityName, options, cb) {
        if (util.isFunction(options)) {
            cb      = options;
            options = {};
        }
        else if (!util.isObject(options)) {
            return cb(new Error('OPTIONS_PARAM_MUST_BE_OBJECT'));
        }

        pb.dbm.getDb(this.dbName, function(err, db) {
            if (util.isError(err)) {
                return cb(err);
            }
            db.createCollection(entityName, options, cb);
        });
    };

    /**
     * Gets all collection names
     * @method listCollections
     * @param {Object} [filter] The filter to specify what collection(s) to search for
     * @param {Function} cb A callback that takes two parameters. The first, an
     * Error, if occurred. The second is the result listCollections command.
     */
    DAO.prototype.listCollections = function(filter, cb) {
        var options = {
          namesOnly: true
        };

        pb.dbm.getDb(this.dbName, function(err, db) {
            if (util.isError(err)) {
                return cb(err);
            }
            db.listCollections(filter, options).toArray(function(err, results) {
                if (util.isError(err)) {
                    return cb(err);
                }
                cb(err, results);
            });
        });
    };

    /**
     * Creates a basic where clause based on the specified Id
     * @deprecated since 0.4.0
     * @static
     * @method getIDWhere
     * @param {String} oid Object Id String
     * @return {Object}    Where clause
     */
    DAO.getIDWhere = function(oid){
        pb.log.warn('DAO: getIDWhere is deprecated.  Use getIdWhere instead');
        return DAO.getIdWhere(oid);
    };

    /**
     * Creates a basic where clause based on the specified Id
     * @static
     * @method getIdWhere
     * @param {String} oid Object Id String
     * @return {Object} Where clause
     */
    DAO.getIdWhere = function(oid) {
        return {
            _id: DAO.getObjectId(oid)
        };
    };

    /**
     * Creates a where clause that equates to select where [idProp] is in the
     * specified array of values.
     * @deprecated
     * @static
     * @method getIDInWhere
     * @param {Array} objArray The array of acceptable values
     * @param {String} idProp The property that holds a referenced ID value
     * @return {Object} Where clause
     */
    DAO.getIDInWhere = function(objArray, idProp) {
        pb.log.warn('DAO: getIDInWhere is deprecated. Use getIdInWhere instead');
        return DAO.getIdInWhere(objArray, idProp);
    };

    /**
     * Creates a where clause that equates to select where [idProp] is in the
     * specified array of values.
     * @static
     * @method getIdInWhere
     * @param {Array} objArray The array of acceptable values
     * @param {String} [idProp] The property that holds a referenced ID value
     * @return {Object} Where clause
     */
    DAO.getIdInWhere = function(objArray, idProp) {
        var seen = {};
        var idArray = [];
        for(var i = 0; i < objArray.length; i++) {

            var rawId;
            if (idProp) {
                rawId = objArray[i][idProp];
            }
            else{
                rawId = objArray[i];
            }
            if (!seen[rawId]) {
                seen[rawId] = true;
                idArray.push(DAO.getObjectId(rawId));
            }
        }
        return {
            _id: {$in: idArray}
        };
    };

    /**
     * Creates a where clause that equates to select where [idProp] is not in the
     * specified array of values.
     * @static
     * @method getIDInWhere
     * @param {Array} objArray The array of acceptable values
     * @param {String} idProp The property that holds a referenced ID value
     * @return {Object} Where clause
     */
    DAO.getIdNotInWhere = function(objArray, idProp) {
        var idArray = [];
        for(var i = 0; i < objArray.length; i++) {

            var rawId;
            if (idProp) {
                rawId = objArray[i][idProp];
            }
            else{
                rawId = objArray[i];
            }
            idArray.push(DAO.getObjectId(rawId));
        }
        return {
            _id: {$nin: idArray}
        };
    };

    /**
     * Creates a basic where clause based on not equalling the specified Id
     * @deprecated
     * @static
     * @method getNotIDWhere
     * @param {String} oid Object Id String
     * @return {Object}    Where clause
     */
    DAO.getNotIDWhere = function(oid) {
        pb.log.warn('DAO: getNotIDField is deprecated. Use getNotIdField instead');
        return DAO.getNotIdWhere(oid);
    };

    /**
     * Creates a basic where clause based on not equalling the specified Id
     * @static
     * @method getNotIdWhere
     * @param {String} oid Object Id String
     * @return {Object}    Where clause
     */
    DAO.getNotIdWhere = function(oid) {
        return {
            _id: DAO.getNotIdField(oid)
        };
    };

    /**
     * Creates a where clause that indicates to select where the '_id' field does
     * not equal the specified value.
     * @deprecated since 0.4.0
     * @static
     * @method getNotIDField
     * @return {Object} Where clause
     */
    DAO.getNotIDField = function(oid) {
        pb.log.warn('DAO: getNotIDField is deprecated. Use getNotIdField instead');
        return DAO.getNotIdField(oid);
    };

    /**
     * Creates a where clause that indicates to select where the '_id' field does
     * not equal the specified value.
     * @static
     * @method getNotIdField
     * @return {Object} Where clause
     */
    DAO.getNotIdField = function(oid) {
        return {$ne: DAO.getObjectId(oid)};
    };

    /**
     * Creates an MongoDB ObjectID object
     * @deprecated since 0.4.0
     * @static
     * @method getObjectID
     * @param {String} oid Object Id String
     * @return {Object}    ObjectID object
     */
    DAO.getObjectID = function(oid) {
        pb.log.warn('DAO: getObjectID is deprecated. Use getObjectId instead');
        return DAO.getObjectId(oid);
    };

    /**
     * Creates an MongoDB ObjectID object
     * @static
     * @method getObjectId
     * @param {String} oid Object Id String
     * @return {Object}    ObjectID object
     */
    DAO.getObjectId = function(oid) {
        try {
           return new ObjectID(oid + '');
        }
        catch(err) {
            return oid;
        }
    };

    /**
     * Updates a DB object with a created time stamp and last modified time stamp.
     * @static
     * @method updateChangeHistory
     * @param {Object} dbObject Object to update
     */
    DAO.updateChangeHistory = function(dbObject){
        if (util.isNullOrUndefined(dbObject)) {
            throw new Error("The dbObject parameter is required");
        }

        var now = new Date();
        if (util.isNullOrUndefined(dbObject._id)) {
            dbObject.created = now;
        }

        //update for current changes
        dbObject.last_modified = now;

        // for the mongo implementation we ensure that the ID is also standardized.  We include the _id but prefer
        // having a standard "id" to keep consistent across all DB platforms that we might support.
        if (dbObject._id) {
            dbObject.id = dbObject._id;
        }
    };

    /**
     * Transfers a system object from one type to another.  The system specific
     * properties are cleared so that when the object is persisted it will receive
     * its own properties.
     * @static
     * @method transfer
     * @param {Object} obj The object to convert
     * @param {String} to The type to convert it to
     */
    DAO.transfer = function(obj, to) {
        if (!util.isObject(obj) || !util.isString(to)) {
            throw new Error('OBJ_TO_PARAMS_MUST_BE');
        }

        delete obj._id;
        delete obj.id;
        delete obj.created;
        delete obj.last_modified;
        obj.object_type = to;
    };

    /**
     * Retrieves the field in system objects that represents the unique identifier.
     * The default implementation returns the mongo field '_id'.
     * @static
     * @method getIdField
     * @return {String} '_id'
     */
    DAO.getIdField = function() {
        return '_id';
    };

    /**
     * Determines if two object IDs are equal
     * @static
     * @method areIdsEqual
     * @param {ObjectID|String} id1
     * @param {ObjectID|String} id2
     * @return {Boolean} TRUE if IDs are equal
     */
    DAO.areIdsEqual = function(id1, id2) {
        return id1.toString() === id2.toString();
    };

    /**
     * Used to help transition over to eliminating the MongoDB _id field.
     * @static
     * @method mapSimpleIdField
     * @param doc
     * @return {object}
     */
    DAO.mapSimpleIdField = function(doc) {
        if (typeof doc.id === 'undefined') {
            doc.id = doc._id;
        }
        return doc;
    };

    //exports
    return DAO;
};