SortDocumentSource.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. "use strict";
  2. var assert = require("assert"),
  3. async = require("async"),
  4. DocumentSource = require("../../../../lib/pipeline/documentSources/DocumentSource"),
  5. SortDocumentSource = require("../../../../lib/pipeline/documentSources/SortDocumentSource"),
  6. LimitDocumentSource = require("../../../../lib/pipeline/documentSources/LimitDocumentSource"),
  7. CursorDocumentSource = require("../../../../lib/pipeline/documentSources/CursorDocumentSource"),
  8. ArrayRunner = require("../../../../lib/query/ArrayRunner"),
  9. FieldPathExpression = require("../../../../lib/pipeline/expressions/FieldPathExpression");
  10. var getCursorDocumentSource = function(values) {
  11. return new CursorDocumentSource(null, new ArrayRunner(values), null);
  12. };
  13. /// An assertion for `ObjectExpression` instances based on Mongo's `ExpectedResultBase` class
  14. function assertExpectedResult(args) {
  15. {// check for required args
  16. if (args === undefined) throw new TypeError("missing arg: `args` is required");
  17. if (args.spec && args.throw === undefined) args.throw = true; // Assume that spec only tests expect an error to be thrown
  18. //if (args.spec === undefined) throw new Error("missing arg: `args.spec` is required");
  19. if (args.expected !== undefined && args.docs === undefined) throw new Error("must provide docs with expected value");
  20. }// check for required args
  21. // run implementation
  22. if(args.expected && args.docs){
  23. var sds = SortDocumentSource.createFromJson(args.spec),
  24. next,
  25. results = [],
  26. cds = new CursorDocumentSource(null, new ArrayRunner(args.docs), null);
  27. sds.setSource(cds);
  28. async.whilst(
  29. function() {
  30. return next !== null;
  31. },
  32. function(done) {
  33. sds.getNext(function(err, doc) {
  34. if(err) return done(err);
  35. next = doc;
  36. if(next === null) {
  37. return done();
  38. } else {
  39. results.push(next);
  40. return done();
  41. }
  42. });
  43. },
  44. function(err) {
  45. assert.equal(JSON.stringify(results), JSON.stringify(args.expected));
  46. if(args.done) {
  47. return args.done();
  48. }
  49. }
  50. );
  51. }else{
  52. if(args.throw) {
  53. assert.throws(function(){
  54. SortDocumentSource.createFromJson(args.spec);
  55. });
  56. } else {
  57. assert.doesNotThrow(function(){
  58. var gds = SortDocumentSource.createFromJson(args.spec);
  59. });
  60. }
  61. }
  62. }
  63. module.exports = {
  64. "SortDocumentSource": {
  65. "constructor()": {
  66. // $sort spec is not an object
  67. "should throw Error when constructing without args": function testConstructor(){
  68. assertExpectedResult({"throw":true});
  69. },
  70. // $sort spec is not an object
  71. "should throw Error when $sort spec is not an object": function testConstructor(){
  72. assertExpectedResult({spec:"Foo"});
  73. },
  74. // $sort spec is an empty object
  75. "should throw Error when $sort spec is an empty object": function testConstructor(){
  76. assertExpectedResult({spec:{}});
  77. },
  78. // $sort _id is specified as an invalid object expression
  79. "should throw error when _id is an invalid object expression": function testConstructor(){
  80. assertExpectedResult({
  81. spec:{_id:{$add:1, $and:1}},
  82. });
  83. },
  84. },
  85. "#getSourceName()": {
  86. "should return the correct source name; $sort": function testSourceName(){
  87. var sds = new SortDocumentSource();
  88. assert.strictEqual(sds.getSourceName(), "$sort");
  89. }
  90. },
  91. "#getFactory()": {
  92. "should return the constructor for this class": function factoryIsConstructor(){
  93. assert.strictEqual(new SortDocumentSource().getFactory(), SortDocumentSource);
  94. }
  95. },
  96. "#getNext()": {
  97. /** Assert that iterator state accessors consistently report the source is exhausted. */
  98. "should return EOF if there are no more sources": function noSources(next){
  99. var cds = getCursorDocumentSource([{"a": 1}]);
  100. var sds = SortDocumentSource.createFromJson({"sort":1});
  101. sds.setSource(cds);
  102. sds.getNext(function(err, val) {
  103. assert.deepEqual(val, {a:1});
  104. sds.getNext(function(err, val) {
  105. if (err) throw err;
  106. assert.equal(val, null);
  107. return next();
  108. });
  109. });
  110. },
  111. "should not return EOF if there are documents": function hitSort(next){
  112. var cds = getCursorDocumentSource([{a: 1}]);
  113. var sds = SortDocumentSource.createFromJson({"sort":1});
  114. sds.setSource(cds);
  115. async.series([
  116. cds.getNext.bind(cds),
  117. ],
  118. function(err,res) {
  119. if (err) throw err;
  120. assert.notEqual(res, null);
  121. return next();
  122. }
  123. );
  124. },
  125. "should return the current document source": function currSource(next){
  126. var cds = getCursorDocumentSource([{a: 1}]);
  127. var sds = SortDocumentSource.createFromJson({"sort":1});
  128. sds.setSource(cds);
  129. async.series([
  130. cds.getNext.bind(cds),
  131. ],
  132. function(err,res) {
  133. if (err) throw err;
  134. assert.deepEqual(res, [ { a: 1 } ]);
  135. return next();
  136. }
  137. );
  138. },
  139. "should return next document when moving to the next source sorted descending": function nextSource(next){
  140. var cds = getCursorDocumentSource([{a: 1}, {b:2}]);
  141. var sds = SortDocumentSource.createFromJson({"sort":1});
  142. sds.setSource(cds);
  143. async.series([
  144. cds.getNext.bind(cds),
  145. ],
  146. function(err,res) {
  147. if (err) throw err;
  148. assert.deepEqual(res, [ { a: 1 } ]);
  149. return next();
  150. }
  151. );
  152. },
  153. "should return false for no sources remaining sorted descending": function noMoar(next){
  154. var cds = getCursorDocumentSource([{a: 1}, {b:2}]);
  155. var sds = SortDocumentSource.createFromJson({"sort":1});
  156. sds.setSource(cds);
  157. async.series([
  158. cds.getNext.bind(cds),
  159. cds.getNext.bind(cds),
  160. ],
  161. function(err,res) {
  162. if (err) throw err;
  163. assert.deepEqual(res, [ { a: 1 }, { b: 2 } ]);
  164. return next();
  165. }
  166. );
  167. }
  168. },
  169. "#serialize()": {
  170. "should throw an error when trying to serialize": function serialize() {
  171. var sds = new SortDocumentSource();
  172. assert.throws(sds.serialize.bind(sds));
  173. }
  174. },
  175. "#serializeToArray()": {
  176. /**
  177. * Check that the BSON representation generated by the souce matches the BSON it was
  178. * created with.
  179. */
  180. "should have equal json representation": function serializeToArrayCheck(next){
  181. var sds = SortDocumentSource.createFromJson({"sort":1}, {});
  182. var array = [];
  183. sds.serializeToArray(array, false);
  184. assert.deepEqual(array, [{"$sort":{"sort":1}}]);
  185. return next();
  186. },
  187. "should create an object representation of the SortDocumentSource": function serializeToArrayTest(next){
  188. var sds = new SortDocumentSource();
  189. var fieldPathVar;
  190. sds.vSortKey.push(new FieldPathExpression("b", fieldPathVar) );
  191. var array = [];
  192. sds.serializeToArray(array, false);
  193. assert.deepEqual(array, [{"$sort":{"":-1}}] );
  194. return next();
  195. }
  196. },
  197. "#createFromJson()": {
  198. "should return a new SortDocumentSource object from an input JSON object": function createTest(next){
  199. var sds = SortDocumentSource.createFromJson({a:1});
  200. assert.strictEqual(sds.constructor, SortDocumentSource);
  201. var t = [];
  202. sds.serializeToArray(t, false);
  203. assert.deepEqual(t, [{"$sort":{"a":1}}] );
  204. return next();
  205. },
  206. "should return a new SortDocumentSource object from an input JSON object with a descending field": function createTest(next){
  207. var sds = SortDocumentSource.createFromJson({a:-1});
  208. assert.strictEqual(sds.constructor, SortDocumentSource);
  209. var t = [];
  210. sds.serializeToArray(t, false);
  211. assert.deepEqual(t, [{"$sort":{"a":-1}}]);
  212. return next();
  213. },
  214. "should return a new SortDocumentSource object from an input JSON object with dotted paths": function createTest(next){
  215. var sds = SortDocumentSource.createFromJson({ "a.b":1 });
  216. assert.strictEqual(sds.constructor, SortDocumentSource);
  217. var t = [];
  218. sds.serializeToArray(t, false);
  219. assert.deepEqual(t, [{"$sort":{"a.b":1}}]);
  220. return next();
  221. },
  222. "should throw an exception when not passed an object": function createTest(next){
  223. assert.throws(function() {
  224. var sds = SortDocumentSource.createFromJson(7);
  225. });
  226. return next();
  227. },
  228. "should throw an exception when passed an empty object": function createTest(next){
  229. assert.throws(function() {
  230. var sds = SortDocumentSource.createFromJson({});
  231. });
  232. return next();
  233. },
  234. "should throw an exception when passed an object with a non number value": function createTest(next){
  235. assert.throws(function() {
  236. var sds = SortDocumentSource.createFromJson({a:"b"});
  237. });
  238. return next();
  239. },
  240. "should throw an exception when passed an object with a non valid number value": function createTest(next){
  241. assert.throws(function() {
  242. var sds = SortDocumentSource.createFromJson({a:14});
  243. });
  244. next();
  245. }
  246. },
  247. "#sort": {
  248. "should sort a single document": function singleValue(next) {
  249. var cds = getCursorDocumentSource([{_id:0, a: 1}]);
  250. var sds = new SortDocumentSource();
  251. sds.addKey("_id", false);
  252. sds.setSource(cds);
  253. sds.getNext(function(err, actual) {
  254. if (err) throw err;
  255. assert.deepEqual(actual, {_id:0, a:1});
  256. return next();
  257. });
  258. },
  259. "should sort two documents": function twoValue(next) {
  260. var cds = getCursorDocumentSource([{_id:0, a: 1}, {_id:1, a:0}]);
  261. var sds = new SortDocumentSource();
  262. sds.addKey("_id", false);
  263. sds.setSource(cds);
  264. async.series([
  265. sds.getNext.bind(sds),
  266. sds.getNext.bind(sds),
  267. ],
  268. function(err,res) {
  269. if (err) throw err;
  270. assert.deepEqual([ { _id: 1, a: 0 }, { _id: 0, a: 1 } ], res);
  271. return next();
  272. }
  273. );
  274. },
  275. "should sort two documents in ascending order": function ascendingValue(next) {
  276. var cds = getCursorDocumentSource([{_id:0, a: 1}, {_id:5, a:12}, {_id:1, a:0}]);
  277. var sds = new SortDocumentSource();
  278. sds.addKey("_id", true);
  279. sds.setSource(cds);
  280. var docs = [], i = 0;
  281. async.doWhilst(
  282. function(cb) {
  283. sds.getNext(function(err, val) {
  284. docs[i] = val;
  285. return cb(err);
  286. });
  287. },
  288. function() {
  289. return docs[i++] !== null;
  290. },
  291. function(err) {
  292. if (err) throw err;
  293. assert.deepEqual([{_id:0, a: 1}, {_id:1, a:0}, {_id:5, a:12}, null], docs);
  294. return next();
  295. }
  296. );
  297. },
  298. "should sort documents with a compound key": function compoundKeySort(next) {
  299. var cds = getCursorDocumentSource([{_id:0, a: 1, b:3}, {_id:5, a:12, b:7}, {_id:1, a:0, b:2}]);
  300. var sds = SortDocumentSource.createFromJson({"sort":1});
  301. sds.addKey("a", false);
  302. sds.addKey("b", false);
  303. sds.setSource(cds);
  304. var docs = [], i = 0;
  305. async.doWhilst(
  306. function(cb) {
  307. sds.getNext(function(err, val) {
  308. docs[i] = val;
  309. return cb(err);
  310. });
  311. },
  312. function() {
  313. return docs[i++] !== null;
  314. },
  315. function(err) {
  316. if (err) throw err;
  317. assert.deepEqual([{_id:5, a:12, b:7}, {_id:0, a:1, b:3}, {_id:1, a:0, b:2}, null], docs);
  318. return next();
  319. }
  320. );
  321. },
  322. "should sort documents with a compound key in ascending order": function compoundAscendingKeySort(next) {
  323. var cds = getCursorDocumentSource([{_id:0, a: 1, b:3}, {_id:5, a:12, b:7}, {_id:1, a:0, b:2}]);
  324. var sds = new SortDocumentSource();
  325. sds.addKey("a", true);
  326. sds.addKey("b", true);
  327. sds.setSource(cds);
  328. var docs = [], i = 0;
  329. async.doWhilst(
  330. function(cb) {
  331. sds.getNext(function(err, val) {
  332. docs[i] = val;
  333. return cb(err);
  334. });
  335. },
  336. function() {
  337. return docs[i++] !== null;
  338. },
  339. function(err) {
  340. if (err) throw err;
  341. assert.deepEqual([{_id:1, a:0, b:2}, {_id:0, a:1, b:3}, {_id:5, a:12, b:7}, null], docs);
  342. return next();
  343. }
  344. );
  345. },
  346. "should sort documents with a compound key in mixed order": function compoundMixedKeySort(next) {
  347. var cds = getCursorDocumentSource([{_id:0, a: 1, b:3}, {_id:5, a:12, b:7}, {_id:1, a:0, b:2}, {_id:8, a:7, b:42}]);
  348. var sds = new SortDocumentSource();
  349. sds.addKey("a", true);
  350. sds.addKey("b", false);
  351. sds.setSource(cds);
  352. var docs = [], i = 0;
  353. async.doWhilst(
  354. function(cb) {
  355. sds.getNext(function(err, val) {
  356. docs[i] = val;
  357. return cb(err);
  358. });
  359. },
  360. function() {
  361. return docs[i++] !== null;
  362. },
  363. function(err) {
  364. if (err) throw err;
  365. assert.deepEqual([{_id:1, a:0, b:2}, {_id:0, a:1, b:3}, {_id:8, a:7, b:42}, {_id:5, a:12, b:7}, null], docs);
  366. return next();
  367. }
  368. );
  369. },
  370. "should not sort different types": function diffTypesSort(next) {
  371. var cds = getCursorDocumentSource([{_id:0, a: 1}, {_id:1, a:"foo"}]);
  372. var sds = new SortDocumentSource();
  373. sds.addKey("a", false);
  374. assert.throws(sds.setSource(cds));
  375. return next();
  376. },
  377. "should sort docs with missing fields": function missingFields(next) {
  378. var cds = getCursorDocumentSource([{_id:0, a: 1}, {_id:1}]);
  379. var sds = new SortDocumentSource();
  380. sds.addKey("a", true);
  381. sds.setSource(cds);
  382. var docs = [], i = 0;
  383. async.doWhilst(
  384. function(cb) {
  385. sds.getNext(function(err, val) {
  386. docs[i] = val;
  387. return cb(err);
  388. });
  389. },
  390. function() {
  391. return docs[i++] !== null;
  392. },
  393. function(err) {
  394. if (err) throw err;
  395. assert.deepEqual([{_id:1}, {_id:0, a:1}, null], docs);
  396. return next();
  397. }
  398. );
  399. },
  400. "should sort docs with null fields": function nullFields(next) {
  401. var cds = getCursorDocumentSource([{_id:0, a: 1}, {_id:1, a: null}]);
  402. var sds = new SortDocumentSource();
  403. sds.addKey("a", true);
  404. sds.setSource(cds);
  405. var docs = [], i = 0;
  406. async.doWhilst(
  407. function(cb) {
  408. sds.getNext(function(err, val) {
  409. docs[i] = val;
  410. return cb(err);
  411. });
  412. },
  413. function() {
  414. return docs[i++] !== null;
  415. },
  416. function(err) {
  417. if (err) throw err;
  418. assert.deepEqual([{_id:1, a:null}, {_id:0, a:1}, null], docs);
  419. return next();
  420. }
  421. );
  422. },
  423. "should not support a missing object nested in an array": function missingObjectWithinArray(next) {
  424. var cds = getCursorDocumentSource([{_id:0, a: [1]}, {_id:1, a:[0]}]);
  425. var sds = new SortDocumentSource();
  426. assert.throws(function() {
  427. sds.addKey("a.b", false);
  428. sds.setSource(cds);
  429. var c = [];
  430. while (!sds.eof()) {
  431. c.push(sds.getCurrent());
  432. sds.advance();
  433. }
  434. });
  435. return next();
  436. },
  437. "should compare nested values from within an array": function extractArrayValues(next) {
  438. var cds = getCursorDocumentSource([{_id:0,a:[{b:1},{b:2}]}, {_id:1,a:[{b:1},{b:1}]}]);
  439. var sds = new SortDocumentSource();
  440. sds.addKey("a.b", true);
  441. sds.setSource(cds);
  442. var docs = [], i = 0;
  443. async.doWhilst(
  444. function(cb) {
  445. sds.getNext(function(err, val) {
  446. docs[i] = val;
  447. return cb(err);
  448. });
  449. },
  450. function() {
  451. return docs[i++] !== null;
  452. },
  453. function(err) {
  454. if (err) throw err;
  455. assert.deepEqual([{_id:1,a:[{b:1},{b:1}]},{_id:0,a:[{b:1},{b:2}]}, null], docs);
  456. return next();
  457. }
  458. );
  459. }
  460. },
  461. "#coalesce()": {
  462. "should return false when coalescing a non-limit source": function nonLimitSource(next) {
  463. var cds = getCursorDocumentSource([{_id:0,a:[{b:1},{b:2}]}, {_id:1,a:[{b:1},{b:1}]} ]);
  464. var sds = SortDocumentSource.createFromJson({a:1});
  465. var newSrc = sds.coalesce(cds);
  466. assert.equal(newSrc, false);
  467. return next();
  468. },
  469. "should return limit source when coalescing a limit source": function limitSource(next) {
  470. var sds = SortDocumentSource.createFromJson({a:1}),
  471. lds = LimitDocumentSource.createFromJson(1);
  472. // TODO: add missing test cases.
  473. // array json getLimit
  474. // getShardSource
  475. // getMergeSource
  476. var newSrc = sds.coalesce(LimitDocumentSource.createFromJson(10));
  477. assert.ok(newSrc instanceof LimitDocumentSource);
  478. assert.equal(sds.getLimit(), 10);
  479. assert.equal(newSrc.limit, 10);
  480. sds.coalesce(LimitDocumentSource.createFromJson(5));
  481. assert.equal(sds.getLimit(), 5);
  482. var arr = [];
  483. sds.serializeToArray(arr);
  484. assert.deepEqual(arr, [{$sort: {a:1}}, {$limit: 5}]);
  485. // TODO: add missing test cases
  486. // doc array get limit
  487. // getShardSource
  488. // get MergeSource
  489. return next();
  490. },
  491. },
  492. "#dependencies": {
  493. /** Dependant field paths. */
  494. "should have Dependant field paths": function dependencies(next) {
  495. var sds = SortDocumentSource.createFromJson({sort: 1});
  496. sds.addKey('a', true);
  497. sds.addKey('b.c', false);
  498. var deps = {fields: {}, needWholeDocument: false, needTextScore: false};
  499. assert.equal('SEE_NEXT', sds.getDependencies(deps));
  500. // Sort keys are now part of deps fields.
  501. assert.equal(3, Object.keys(deps.fields).length);
  502. assert.equal(1, deps.fields.a);
  503. assert.equal(1, deps.fields['b.c']);
  504. assert.equal(false, deps.needWholeDocument);
  505. assert.equal(false, deps.needTextScore);
  506. return next();
  507. }
  508. }
  509. }
  510. };
  511. if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).grep(process.env.MOCHA_GREP || '').run(process.exit);