document.js 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. // Copyright 2015 The MemDB Authors.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
  12. // implied. See the License for the specific language governing
  13. // permissions and limitations under the License. See the AUTHORS file
  14. // for names of contributors.
  15. 'use strict';
  16. var P = require('bluebird');
  17. var _ = require('lodash');
  18. var should = require('should');
  19. var assert = require('assert');
  20. var Document = require('../../app/document'); // jshint ignore:line
  21. var utils = require('../../app/utils');
  22. var logger = require('memdb-logger').getLogger('test', __filename);
  23. describe('document test', function(){
  24. it('find', function(){
  25. var value = {k1 : 1, k2 : 1};
  26. var doc = new Document({_id : '1', doc : value});
  27. // Get all fields
  28. doc.find('c1').should.eql(value);
  29. // Get specified fields
  30. doc.find('c1', 'k1').should.eql({_id : '1', k1 : 1});
  31. doc.find('c1', {'k1' : true}).should.eql({_id : '1', k1 : 1});
  32. doc.find('c1', {'k1' : false}).should.eql({_id : '1', k2 : 1});
  33. });
  34. it('update', function(cb){
  35. var doc = new Document({_id : '1', doc : null, watchedFields : ['k1']});
  36. doc.on('updateUncommited', function(connId, field, oldValue, newValue){
  37. logger.debug(field, oldValue, newValue);
  38. });
  39. return P.try(function(){
  40. // Lock for write
  41. return doc.lock('c1');
  42. })
  43. .then(function(){
  44. should(doc.update.bind(doc, 'c1', {k1 : 1})).throw();
  45. doc.insert('c1', {k1 : 1});
  46. doc.find('c1').should.eql({_id : '1', k1 : 1});
  47. //replace doc
  48. doc.update('c1', {k1 : 2, k2 : 2});
  49. doc.find('c1').should.eql({_id : '1', k1 : 2, k2 : 2});
  50. //$set $unset
  51. doc.update('c1', {k1 : 1});
  52. doc.update('c1', {$set : {k1 : 2, k2 : 2}});
  53. doc.find('c1').should.eql({_id : '1', k1 : 2, k2 : 2});
  54. doc.update('c1', {$unset : {k2 : true}});
  55. doc.find('c1').should.eql({_id : '1', k1 : 2});
  56. doc.update('c1', {$set : {'k2.k3' : 3}});
  57. doc.find('c1').should.eql({_id : '1', k1 : 2, k2 : {k3 : 3}});
  58. //$inc
  59. doc.update('c1', {k1 : 1});
  60. doc.update('c1', {$inc : {k1 : 2}});
  61. doc.find('c1').should.eql({_id : '1', k1 : 3});
  62. //$push $pushAll $pop $addToSet $pull
  63. doc.update('c1', {});
  64. doc.update('c1', {$push : {k1 : 1}});
  65. doc.update('c1', {$push : {k1 : 2}});
  66. doc.find('c1').should.eql({_id : '1', k1 : [1, 2]});
  67. doc.update('c1', {$pop : {k1 : 1}});
  68. doc.find('c1').should.eql({_id : '1', k1 : [1]});
  69. doc.update('c1', {$addToSet: {k1 : 1}});
  70. doc.update('c1', {$addToSet: {k1 : 1}});
  71. doc.update('c1', {$addToSet: {k1 : 2}});
  72. doc.find('c1').should.eql({_id : '1', k1 : [1, 2]});
  73. doc.update('c1', {$pull : {k1 : 2}});
  74. doc.find('c1').should.eql({_id : '1', k1 : [1]});
  75. doc.update('c1', {$pushAll : {k1 : [2, 3]}});
  76. doc.find('c1').should.eql({_id : '1', k1 : [1, 2, 3]});
  77. //multiple modifiers
  78. doc.update('c1', {k1 : 0, k2 : 1, k3 : [1]});
  79. doc.update('c1', {$set : {k1 : 1}, $inc : {k2 : 1}, $push : {k3 : 2}});
  80. doc.find('c1').should.eql({_id : '1', k1 : 1, k2 : 2, k3 : [1, 2]});
  81. })
  82. .nodeify(cb);
  83. });
  84. it('insert/remove', function(cb){
  85. var value = {k1 : 1};
  86. // Init with non-exist doc
  87. var doc = new Document({_id : '1', exist : false});
  88. return P.try(function(){
  89. assert(doc.find('c1') === null);
  90. // Lock for write
  91. return doc.lock('c1');
  92. })
  93. .then(function(){
  94. // insert doc
  95. doc.insert('c1', value);
  96. doc.find('c1').should.eql(value);
  97. // should throw exception when insert existing doc
  98. should(doc.insert.bind(doc, 'c1', value)).throw();
  99. // remove doc
  100. doc.remove('c1');
  101. assert(doc.find('c1') === null);
  102. // should throw exception when remove non-exist doc
  103. should(doc.remove.bind(doc, 'c1')).throw();
  104. })
  105. .nodeify(cb);
  106. });
  107. it('commit/rollback/lock/unlock', function(cb){
  108. var doc = new Document({_id : '1', doc : {k1 : 1}, watchedFields : ['k1']});
  109. doc.on('updateUncommited', function(connId, field, oldValue, newValue){
  110. logger.debug(field, oldValue, newValue);
  111. });
  112. return P.try(function(){
  113. // should throw when write without lock
  114. should(doc.remove.bind(doc, 'c1')).throw();
  115. return doc.lock('c1')
  116. .then(function(){
  117. // lock twice should be ok
  118. return doc.lock('c1');
  119. });
  120. })
  121. .then(function(){
  122. doc.update('c1', {k1 : 2});
  123. doc.find('c1', {_id : false}).should.eql({k1 : 2});
  124. doc.rollback('c1');
  125. // should rolled back
  126. doc.find('c1', {_id : false}).should.eql({k1 : 1});
  127. // should unlocked
  128. should(doc.remove.bind(doc, 'c1')).throw();
  129. return doc.lock('c1');
  130. })
  131. .then(function(){
  132. doc.update('c1', {k1 : 2});
  133. doc.commit('c1');
  134. // should commited
  135. doc.find('c1', {_id : false}).should.eql({k1 : 2});
  136. // should unlocked
  137. should(doc.remove.bind(doc, 'c1')).throw();
  138. // remove and rollback
  139. return doc.lock('c1');
  140. })
  141. .then(function(){
  142. doc.remove('c1');
  143. doc.rollback('c1');
  144. assert(doc.find('c1') !== null);
  145. // remove and commit
  146. return doc.lock('c1');
  147. })
  148. .then(function(){
  149. doc.remove('c1');
  150. doc.commit('c1');
  151. assert(doc.find('c1') === null);
  152. // insert and rollback
  153. return doc.lock('c1');
  154. })
  155. .then(function(){
  156. doc.insert('c1', {k1 : 1});
  157. doc.rollback('c1');
  158. assert(doc.find('c1') === null);
  159. // insert and commit
  160. return doc.lock('c1');
  161. })
  162. .then(function(){
  163. doc.insert('c1', {k1 : 1});
  164. doc.commit('c1');
  165. doc.find('c1', {_id : false}).should.eql({k1 : 1});
  166. })
  167. .nodeify(cb);
  168. });
  169. it('read from other connections', function(cb){
  170. var value = {k1 : 1, k2 : 1};
  171. var doc = new Document({_id : '1', doc : utils.clone(value), watchedFields : ['k1', 'k2']});
  172. doc.on('updateUncommited', function(connId, field, oldValue, newValue){
  173. logger.debug(field, oldValue, newValue);
  174. });
  175. return P.try(function(){
  176. return doc.lock('c1');
  177. })
  178. .then(function(){
  179. // update field
  180. doc.update('c1', {k1 : 2});
  181. // read from c2
  182. doc.find('c2', 'k1').should.eql({'_id' : '1', k1 : 1});
  183. doc.find('c2', {_id : false}).should.eql(value);
  184. // add field
  185. doc.update('c1', {k3 : 1});
  186. doc.find('c2', {_id : false}).should.eql(value);
  187. // remove field
  188. doc.update('c1', {k2 : undefined});
  189. doc.find('c2', {_id : false}).should.eql(value);
  190. // replace doc
  191. doc.update('c1', {k1 : 1}, {replace : true});
  192. doc.find('c2', {_id : false}).should.eql(value);
  193. // commit
  194. doc.commit('c1');
  195. // c2 should see the newest value now
  196. doc.find('c2', {_id : false}).should.eql({k1 : 1});
  197. // remove and commit
  198. return doc.lock('c1');
  199. })
  200. .then(function(){
  201. doc.remove('c1');
  202. assert(doc.find('c2') !== null);
  203. doc.commit('c1');
  204. assert(doc.find('c2') === null);
  205. // insert and commit
  206. return doc.lock('c1');
  207. })
  208. .then(function(){
  209. doc.insert('c1', utils.clone(value));
  210. assert(doc.find('c2') === null);
  211. doc.commit('c1');
  212. doc.find('c2', {_id : false}).should.eql(value);
  213. })
  214. .nodeify(cb);
  215. });
  216. it('concurrency', function(cb){
  217. var doc = new Document({_id : '1', doc : {k : 0}, watchedFields : ['k']});
  218. doc.on('updateUncommited', function(connId, field, oldValue, newValue){
  219. logger.debug(field, oldValue, newValue);
  220. });
  221. var concurrency = 4;
  222. // Simulate non-atomic check and update
  223. return P.map(_.range(concurrency), function(connId){
  224. var value = null;
  225. return P.delay(_.random(10))
  226. .then(function(){
  227. logger.trace('%s start lock', connId);
  228. return doc.lock(connId);
  229. })
  230. .then(function(){
  231. logger.trace('%s got lock', connId);
  232. value = doc.find(connId, {_id : false});
  233. })
  234. .delay(_.random(20))
  235. .then(function(){
  236. value.k++;
  237. doc.update(connId, value);
  238. doc.commit(connId);
  239. logger.trace('%s commited', connId);
  240. });
  241. })
  242. .then(function(){
  243. //Result should equal to concurrency
  244. doc.find(null, {_id : false}).should.eql({k : concurrency});
  245. })
  246. .nodeify(cb);
  247. });
  248. it.skip('too large doc', function(cb){
  249. var doc = new Document({_id : 1});
  250. doc.lock(1);
  251. should(function(){
  252. doc.insert(1, {tooLargeArray : _.range(1000000)});
  253. }).throw();
  254. cb();
  255. });
  256. });