SortDocumentSource_test.js 17 KB

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