diff --git a/packages/minimongo/NOTES b/packages/minimongo/NOTES.md similarity index 100% rename from packages/minimongo/NOTES rename to packages/minimongo/NOTES.md diff --git a/packages/minimongo/local_collection.js b/packages/minimongo/local_collection.js index 38b4d5c3a6..e3668eeb03 100644 --- a/packages/minimongo/local_collection.js +++ b/packages/minimongo/local_collection.js @@ -1468,6 +1468,24 @@ const MODIFIERS = { target[field] = new Date(); }, + $inc(target, field, arg) { + if (typeof arg !== 'number') { + throw MinimongoError('Modifier $inc allowed for numbers only', {field}); + } + + if (field in target) { + if (typeof target[field] !== 'number') { + throw MinimongoError( + 'Cannot apply $inc modifier to non-number', + {field} + ); + } + + target[field] += arg; + } else { + target[field] = arg; + } + }, $min(target, field, arg) { if (typeof arg !== 'number') { throw MinimongoError('Modifier $min allowed for numbers only', {field}); @@ -1508,24 +1526,64 @@ const MODIFIERS = { target[field] = arg; } }, - $inc(target, field, arg) { + $mul(target, field, arg) { if (typeof arg !== 'number') { - throw MinimongoError('Modifier $inc allowed for numbers only', {field}); + throw MinimongoError('Modifier $mul allowed for numbers only', {field}); } if (field in target) { if (typeof target[field] !== 'number') { throw MinimongoError( - 'Cannot apply $inc modifier to non-number', + 'Cannot apply $mul modifier to non-number', {field} ); } - target[field] += arg; + target[field] *= arg; } else { - target[field] = arg; + target[field] = 0; } }, + $rename(target, field, arg, keypath, doc) { + // no idea why mongo has this restriction.. + if (keypath === arg) { + throw MinimongoError('$rename source must differ from target', {field}); + } + + if (target === null) { + throw MinimongoError('$rename source field invalid', {field}); + } + + if (typeof arg !== 'string') { + throw MinimongoError('$rename target must be a string', {field}); + } + + if (arg.includes('\0')) { + // Null bytes are not allowed in Mongo field names + // https://docs.mongodb.com/manual/reference/limits/#Restrictions-on-Field-Names + throw MinimongoError( + 'The \'to\' field for $rename cannot contain an embedded null byte', + {field} + ); + } + + if (target === undefined) { + return; + } + + const object = target[field]; + + delete target[field]; + + const keyparts = arg.split('.'); + const target2 = findModTarget(doc, keyparts, {forbidArray: true}); + + if (target2 === null) { + throw MinimongoError('$rename target field invalid', {field}); + } + + target2[keyparts.pop()] = object; + }, $set(target, field, arg) { if (target !== Object(target)) { // not an array or an object const error = MinimongoError( @@ -1810,46 +1868,6 @@ const MODIFIERS = { !arg.some(element => LocalCollection._f._equal(object, element)) ); }, - $rename(target, field, arg, keypath, doc) { - // no idea why mongo has this restriction.. - if (keypath === arg) { - throw MinimongoError('$rename source must differ from target', {field}); - } - - if (target === null) { - throw MinimongoError('$rename source field invalid', {field}); - } - - if (typeof arg !== 'string') { - throw MinimongoError('$rename target must be a string', {field}); - } - - if (arg.includes('\0')) { - // Null bytes are not allowed in Mongo field names - // https://docs.mongodb.com/manual/reference/limits/#Restrictions-on-Field-Names - throw MinimongoError( - 'The \'to\' field for $rename cannot contain an embedded null byte', - {field} - ); - } - - if (target === undefined) { - return; - } - - const object = target[field]; - - delete target[field]; - - const keyparts = arg.split('.'); - const target2 = findModTarget(doc, keyparts, {forbidArray: true}); - - if (target2 === null) { - throw MinimongoError('$rename target field invalid', {field}); - } - - target2[keyparts.pop()] = object; - }, $bit(target, field, arg) { // XXX mongo only supports $bit on integers, and we only support // native javascript numbers (doubles) so far, so we can't support $bit diff --git a/packages/minimongo/minimongo_tests_client.js b/packages/minimongo/minimongo_tests_client.js index 658f8e12db..95526acaea 100644 --- a/packages/minimongo/minimongo_tests_client.js +++ b/packages/minimongo/minimongo_tests_client.js @@ -2659,6 +2659,27 @@ Tinytest.add('minimongo - modify', test => { modify({a: {b: 2}}, {$min: {'a.c': 10}}, {a: {b: 2, c: 10}}); exception({}, {$min: {_id: 1}}); + //$mul + modify({a: 1, b: 1}, {$mul: {b: 2}}, {a: 1, b: 2}); + modify({a: 1, b: 1}, {$mul: {c: 2}}, {a: 1, b: 1, c: 0}); + modify({a: 1, b: 2}, {$mul: {b: 2}}, {a: 1, b: 4}); + modify({a: 1, b: 2}, {$mul: {b: 10}}, {a: 1, b: 20}); + exception({a: 1}, {$mul: {a: '10'}}); + exception({a: 1}, {$mul: {a: true}}); + exception({a: 1}, {$mul: {a: [10]}}); + exception({a: '1'}, {$mul: {a: 10}}); + exception({a: [1]}, {$mul: {a: 10}}); + exception({a: {}}, {$mul: {a: 10}}); + exception({a: false}, {$mul: {a: 10}}); + exception({a: null}, {$mul: {a: 10}}); + exception({}, {$mul: {_id: 1}}); + modify({a: [1, 2]}, {$mul: {'a.1': 2}}, {a: [2, 1]}); + modify({a: [1, 2]}, {$mul: {'a.1': 3}}, {a: [3, 2]}); + modify({a: [1, 2]}, {$mul: {'a.2': 10}}, {a: [1, 20]}); + modify({a: [1, 2]}, {$mul: {'a.3': 10}}, {a: [1, 2, 0]}); + modify({a: {b: 2}}, {$mul: {'a.b': 1}}, {a: {b: 2}}); + modify({a: {b: 2}}, {$mul: {'a.c': 10}}, {a: {b: 2, c: 0}}); + // $max modify({a: 1, b: 2}, {$max: {b: 1}}, {a: 1, b: 2}); modify({a: 1, b: 2}, {$max: {b: 3}}, {a: 1, b: 3}); diff --git a/packages/minimongo/minimongo_tests_server.js b/packages/minimongo/minimongo_tests_server.js index eccb8a980a..596eff0781 100644 --- a/packages/minimongo/minimongo_tests_server.js +++ b/packages/minimongo/minimongo_tests_server.js @@ -35,8 +35,8 @@ Tinytest.add('minimongo - modifier affects selector', test => { // When top-level value is an object, it is treated as a literal, // so when you query col.find({ a: { foo: 1, bar: 2 } }) // it doesn't mean you are looking for anything that has 'a.foo' to be 1 and - // 'a.bar' to be 2, instead you are looking for 'a' to be exatly that object - // with exatly that order of keys. { a: { foo: 1, bar: 2, baz: 3 } } wouldn't + // 'a.bar' to be 2, instead you are looking for 'a' to be exactly that object + // with exactly that order of keys. { a: { foo: 1, bar: 2, baz: 3 } } wouldn't // match it. That's why in this selector 'a' would be important key, not a.foo // and a.bar. testSelectorPaths({