exports.$set = function $set(obj, path, val){ var key = path.split('.').pop(); obj = dot.parent(obj, path, true); switch (type(obj)) { case 'object': if (!eql(obj[key], val)) { return function(){ obj[key] = val; return val; }; } break; case 'array': if (numeric(key)) { if (!eql(obj[key], val)) { return function(){ obj[key] = val; return val; }; } } else { throw new Error('can\'t append to array using string field name [' + key + ']'); } break; default: throw new Error('$set only supports object not ' + type(obj)); } };
exports.$inc = function $inc(obj, path, inc){ if ('number' != type(inc)) { throw new Error('Modifier $inc allowed for numbers only'); } obj = dot.parent(obj, path, true); var key = path.split('.').pop(); switch (type(obj)) { case 'array': case 'object': if (obj.hasOwnProperty(key)) { if ('number' != type(obj[key])) { throw new Error('Cannot apply $inc modifier to non-number'); } return function(){ obj[key] += inc; return inc; }; } else if('object' == type(obj) || numeric(key)){ return function(){ obj[key] = inc; return inc; }; } else { throw new Error('can\'t append to array using string field name [' + key + ']'); } break; default: throw new Error('Cannot apply $inc modifier to non-number'); } };
exports.$pullAll = function $pullAll(obj, path, val){ if ('array' != type(val)) { throw new Error('Modifier $pushAll/pullAll allowed for arrays only'); } obj = dot.parent(obj, path, true); var key = path.split('.').pop(); var t = type(obj); switch (t) { case 'object': if (obj.hasOwnProperty(key)) { if ('array' == type(obj[key])) { var pulled = []; var splice = pull(obj[key], val, pulled); if (pulled.length) { return function(){ splice(); return pulled; }; } } else { throw new Error('Cannot apply $pull/$pullAll modifier to non-array'); } } break; case 'array': if (obj.hasOwnProperty(key)) { if ('array' == type(obj[key])) { var pulled = []; var splice = pull(obj[key], val, pulled); if (pulled.length) { return function(){ splice(); return pulled; }; } } else { throw new Error('Cannot apply $pull/$pullAll modifier to non-array'); } } else { debug('ignoring pull to non array'); } break; default: if ('undefined' != t) { throw new Error('LEFT_SUBFIELD only supports Object: hello not: ' + t); } } };
exports.$pushAll = function $pushAll(obj, path, val){ if ('array' != type(val)) { throw new Error('Modifier $pushAll/pullAll allowed for arrays only'); } obj = dot.parent(obj, path, true); var key = path.split('.').pop(); switch (type(obj)) { case 'object': if (obj.hasOwnProperty(key)) { if ('array' == type(obj[key])) { return function(){ obj[key].push.apply(obj[key], val); return val; }; } else { throw new Error('Cannot apply $push/$pushAll modifier to non-array'); } } else { return function(){ obj[key] = val; return val; }; } break; case 'array': if (obj.hasOwnProperty(key)) { if ('array' == type(obj[key])) { return function(){ obj[key].push.apply(obj[key], val); return val; }; } else { throw new Error('Cannot apply $push/$pushAll modifier to non-array'); } } else if (numeric(key)) { return function(){ obj[key] = val; return val; }; } else { throw new Error('can\'t append to array using string field name [' + key + ']'); } break; } };
return function(){ var val = p[key]; delete p[key]; // target does initialize the path var newp = dot.parent(obj, newKey, true); // and also fails silently upon type mismatch if ('object' == type(newp)) { newp[newKey.split('.').pop()] = val; } else { debug('invalid $rename target path type'); } // returns the name of the new key return newKey; };
exports.$unset = function $unset(obj, path){ var key = path.split('.').pop(); obj = dot.parent(obj, path); switch (type(obj)) { case 'array': case 'object': if (obj.hasOwnProperty(key)) { return function(){ // reminder: `delete arr[1]` === `delete arr['1']` [!] delete obj[key]; }; } else { // we fail silently debug('ignoring unset of inexisting key'); } } };
exports.$rename = function $rename(obj, path, newKey){ // target = source if (path == newKey) { throw new Error('$rename source must differ from target'); } // target is parent of source if (0 === path.indexOf(newKey + '.')) { throw new Error('$rename target may not be a parent of source'); } var p = dot.parent(obj, path); var t = type(p); if ('object' == t) { var key = path.split('.').pop(); if (p.hasOwnProperty(key)) { return function(){ var val = p[key]; delete p[key]; // target does initialize the path var newp = dot.parent(obj, newKey, true); // and also fails silently upon type mismatch if ('object' == type(newp)) { newp[newKey.split('.').pop()] = val; } else { debug('invalid $rename target path type'); } // returns the name of the new key return newKey; }; } else { debug('ignoring rename from inexisting source'); } } else if ('undefined' != t) { throw new Error('$rename source field invalid'); } };
exports.$pop = function $pop(obj, path, val){ obj = dot.parent(obj, path); var key = path.split('.').pop(); // we make sure the array is not just the parent of the main key switch (type(obj)) { case 'array': case 'object': if (obj.hasOwnProperty(key)) { switch (type(obj[key])) { case 'array': if (obj[key].length) { return function(){ if (-1 == val) { return obj[key].shift(); } else { // mongodb allows any value to pop return obj[key].pop(); } }; } break; case 'undefined': debug('ignoring pop to inexisting key'); break; default: throw new Error('Cannot apply $pop modifier to non-array'); } } else { debug('ignoring pop to inexisting key'); } break; case 'undefined': debug('ignoring pop to inexisting key'); break; } };
exports.$addToSet = function $addToSet(obj, path, val, recursing){ if (!recursing && 'array' == type(val.$each)) { var fns = []; for (var i = 0, l = val.$each.length; i < l; i++) { var fn = $addToSet(obj, path, val.$each[i], true); if (fn) fns.push(fn); } if (fns.length) { return function(){ var values = []; for (var i = 0; i < fns.length; i++) values.push(fns[i]()); return values; }; } else { return; } } obj = dot.parent(obj, path, true); var key = path.split('.').pop(); switch (type(obj)) { case 'object': if (obj.hasOwnProperty(key)) { if ('array' == type(obj[key])) { if (!has(obj[key], val)) { return function(){ obj[key].push(val); return val; }; } } else { throw new Error('Cannot apply $addToSet modifier to non-array'); } } else { return function(){ obj[key] = [val]; return val; }; } break; case 'array': if (obj.hasOwnProperty(key)) { if ('array' == type(obj[key])) { if (!has(obj[key], val)) { return function(){ obj[key].push(val); return val; }; } } else { throw new Error('Cannot apply $addToSet modifier to non-array'); } } else if (numeric(key)) { return function(){ obj[key] = [val]; return val; }; } else { throw new Error('can\'t append to array using string field name [' + key + ']'); } break; } };