ObjectExpression.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660
  1. "use strict";
  2. var assert = require("assert"),
  3. ObjectExpression = require("../../../../lib/pipeline/expressions/ObjectExpression"),
  4. ConstantExpression = require("../../../../lib/pipeline/expressions/ConstantExpression"),
  5. FieldPathExpression = require("../../../../lib/pipeline/expressions/FieldPathExpression"),
  6. AndExpression = require("../../../../lib/pipeline/expressions/AndExpression"),
  7. Variables = require("../../../../lib/pipeline/expressions/Variables");
  8. function assertEqualJson(actual, expected, message){
  9. if(actual.sort) {
  10. actual.sort();
  11. if(expected.sort) {
  12. expected.sort();
  13. }
  14. }
  15. assert.strictEqual(message + ": " + JSON.stringify(actual), message + ": " + JSON.stringify(expected));
  16. }
  17. /// An assertion for `ObjectExpression` instances based on Mongo's `ExpectedResultBase` class
  18. function assertExpectedResult(args) {
  19. {// check for required args
  20. if (args === undefined) throw new TypeError("missing arg: `args` is required");
  21. if (!("expected" in args)) throw new Error("missing arg: `args.expected` is required");
  22. if (!("expectedDependencies" in args)) throw new Error("missing arg: `args.expectedDependencies` is required");
  23. if (!("expectedJsonRepresentation" in args)) throw new Error("missing arg: `args.expectedJsonRepresentation` is required");
  24. }// check for required args
  25. {// base args if none provided
  26. if (args.source === undefined) args.source = {_id:0, a:1, b:2};
  27. if (args.expectedIsSimple === undefined) args.expectedIsSimple = false;
  28. if (args.expression === undefined) args.expression = ObjectExpression.createRoot(); //NOTE: replaces prepareExpression + _expression assignment
  29. }// base args if none provided
  30. // run implementation
  31. var result = {},
  32. variable = new Variables(1, args.source);
  33. args.expression.addToDocument(result, args.source, variable);
  34. assert.deepEqual(result, args.expected);
  35. var dependencies = {};
  36. args.expression.addDependencies(dependencies, [/*FAKING: includePath=true*/]);
  37. //dependencies.sort(), args.expectedDependencies.sort(); // NOTE: this is a minor hack added for munge because I'm pretty sure order doesn't matter for this anyhow
  38. assert.deepEqual(Object.keys(dependencies).sort(), Object.keys(args.expectedDependencies).sort());
  39. assert.deepEqual(args.expression.serialize(true), args.expectedJsonRepresentation);
  40. assert.deepEqual(args.expression.getIsSimple(), args.expectedIsSimple);
  41. }
  42. module.exports = {
  43. "ObjectExpression": {
  44. "constructor()": {
  45. "should not throw Error when constructing without args": function testConstructor(){
  46. assert.doesNotThrow(function(){
  47. ObjectExpression.create();
  48. });
  49. }
  50. },
  51. "#addDependencies":{
  52. "should be able to get dependencies for non-inclusion expressions": function testNonInclusionDependencies(){
  53. /** Dependencies for non inclusion expressions. */
  54. var expr = ObjectExpression.create();
  55. expr.addField("a", new ConstantExpression(5));
  56. assertEqualJson(expr.addDependencies({}, [/*FAKING: includePath=true*/]), {"_id":1});
  57. expr.excludeId = true;
  58. assertEqualJson(expr.addDependencies({}, []), {});
  59. expr.addField("b", FieldPathExpression.create("c.d"));
  60. var deps = {};
  61. expr.addDependencies(deps, []);
  62. assert.deepEqual(deps, {"c.d":1});
  63. expr.excludeId = false;
  64. deps = {};
  65. expr.addDependencies(deps, []);
  66. assert.deepEqual(deps, {"_id":1, "c.d":1});
  67. },
  68. "should be able to get dependencies for inclusion expressions": function testInclusionDependencies(){
  69. /** Dependencies for inclusion expressions. */
  70. var expr = ObjectExpression.create();
  71. expr.includePath( "a" );
  72. assertEqualJson(expr.addDependencies({}, [/*FAKING: includePath=true*/]), {"_id":1, "a":1});
  73. assert.throws(function(){
  74. expr.addDependencies({});
  75. }, Error);
  76. }
  77. },
  78. "#toJSON": {
  79. "should be able to convert to JSON representation and have constants represented by expressions": function testJson(){
  80. /** Serialize to a BSONObj, with constants represented by expressions. */
  81. var expr = ObjectExpression.create(true);
  82. expr.addField("foo.a", new ConstantExpression(5));
  83. assertEqualJson({foo:{a:{$const:5}}}, expr.serialize(true));
  84. }
  85. },
  86. "#optimize": {
  87. "should be able to optimize expression and sub-expressions": function testOptimize(){
  88. /** Optimizing an object expression optimizes its sub expressions. */
  89. var expr = ObjectExpression.createRoot();
  90. // Add inclusion.
  91. expr.includePath( "a" );
  92. // Add non inclusion.
  93. expr.addField( "b", new AndExpression());
  94. expr.optimize();
  95. // Optimizing 'expression' optimizes its non inclusion sub expressions, while inclusion sub expressions are passed through.
  96. assertEqualJson({a:{$const:null}, b:{$const:true}}, expr.serialize(true));
  97. }
  98. },
  99. "#evaluate()": {
  100. "should be able to provide an empty object": function testEmpty(){
  101. /** Empty object spec. */
  102. var expr = ObjectExpression.createRoot();
  103. assertExpectedResult({
  104. expression: expr,
  105. expected: {"_id":0},
  106. expectedDependencies: {"_id":1},
  107. expectedJsonRepresentation: {}
  108. });
  109. },
  110. "should be able to include 'a' field only": function testInclude(){
  111. /** Include 'a' field only. */
  112. var expr = ObjectExpression.createRoot();
  113. expr.includePath( "a" );
  114. assertExpectedResult({
  115. expression: expr,
  116. expected: {"_id":0, "a":1},
  117. expectedDependencies: {"_id":1, "a":1},
  118. expectedJsonRepresentation: {"a":{$const:null}}
  119. });
  120. },
  121. "should NOT be able to include missing 'a' field": function testMissingInclude(){
  122. /** Cannot include missing 'a' field. */
  123. var expr = ObjectExpression.createRoot();
  124. expr.includePath( "a" );
  125. assertExpectedResult({
  126. source: {"_id":0, "b":2},
  127. expression: expr,
  128. expected: {"_id":0},
  129. expectedDependencies: {"_id":1, "a":1},
  130. expectedJsonRepresentation: {"a":{$const:null}}
  131. });
  132. },
  133. "should be able to include '_id' field only": function testIncludeId(){
  134. /** Include '_id' field only. */
  135. var expr = ObjectExpression.createRoot();
  136. expr.includePath( "_id" );
  137. assertExpectedResult({
  138. expression: expr,
  139. expected: {"_id":0},
  140. expectedDependencies: {"_id":1},
  141. expectedJsonRepresentation: {"_id":{$const:null}}
  142. });
  143. },
  144. "should be able to exclude '_id' field": function testExcludeId(){
  145. /** Exclude '_id' field. */
  146. var expr = ObjectExpression.createRoot();
  147. expr.includePath( "b" );
  148. expr.excludeId = true;
  149. assertExpectedResult({
  150. expression: expr,
  151. expected: {"b":2},
  152. expectedDependencies: {"b":1},
  153. expectedJsonRepresentation: {"b":{$const:null}}
  154. });
  155. },
  156. "should be able to include fields in source document order regardless of inclusion order": function testSourceOrder(){
  157. /** Result order based on source document field order, not inclusion spec field order. */
  158. var expr = ObjectExpression.createRoot();
  159. expr.includePath( "b" );
  160. expr.includePath( "a" );
  161. assertExpectedResult({
  162. expression: expr,
  163. get expected() { return this.source; },
  164. expectedDependencies: {"_id":1, "a":1, "b":1},
  165. expectedJsonRepresentation: {"b":{$const:null}, "a":{$const:null}}
  166. });
  167. },
  168. "should be able to include a nested field": function testIncludeNested(){
  169. /** Include a nested field. */
  170. var expr = ObjectExpression.createRoot();
  171. expr.includePath( "a.b" );
  172. assertExpectedResult({
  173. source: {"_id":0, "a":{ "b":5, "c":6}, "z":2 },
  174. expression: expr,
  175. expected: {"_id":0, "a":{ "b":5} },
  176. expectedDependencies: {"_id":1, "a.b":1},
  177. expectedJsonRepresentation: {"a":{ "b":{$const:null}} }
  178. });
  179. },
  180. "should be able to include two nested fields": function testIncludeTwoNested(){
  181. /** Include two nested fields. */
  182. var expr = ObjectExpression.createRoot();
  183. expr.includePath( "a.b" );
  184. expr.includePath( "a.c" );
  185. assertExpectedResult({
  186. source: {"_id":0, "a":{ "b":5, "c":6}, "z":2 },
  187. expression: expr,
  188. expected: {"_id":0, "a":{ "b":5, "c":6} },
  189. expectedDependencies: {"_id":1, "a.b":1, "a.c":1},
  190. expectedJsonRepresentation: {"a":{ "b":{$const:null}, "c":{$const:null}} }
  191. });
  192. },
  193. "should be able to include two fields nested within different parents": function testIncludeTwoParentNested(){
  194. /** Include two fields nested within different parents. */
  195. var expr = ObjectExpression.createRoot();
  196. expr.includePath( "a.b" );
  197. expr.includePath( "c.d" );
  198. assertExpectedResult({
  199. source: {"_id":0, "a":{ "b":5 }, "c":{"d":6} },
  200. expression: expr,
  201. expected: {"_id":0, "a":{ "b":5}, "c":{"d":6} },
  202. expectedDependencies: {"_id":1, "a.b":1, "c.d":1},
  203. expectedJsonRepresentation: {"a":{"b":{$const:null}}, "c":{"d":{$const:null}} }
  204. });
  205. },
  206. "should be able to attempt to include a missing nested field": function testIncludeMissingNested(){
  207. /** Attempt to include a missing nested field. */
  208. var expr = ObjectExpression.createRoot();
  209. expr.includePath( "a.b" );
  210. assertExpectedResult({
  211. source: {"_id":0, "a":{ "c":6}, "z":2 },
  212. expression: expr,
  213. expected: {"_id":0, "a":{} },
  214. expectedDependencies: {"_id":1, "a.b":1},
  215. expectedJsonRepresentation: {"a":{ "b":{$const:null}} }
  216. });
  217. },
  218. "should be able to attempt to include a nested field within a non object": function testIncludeNestedWithinNonObject(){
  219. /** Attempt to include a nested field within a non object. */
  220. var expr = ObjectExpression.createRoot();
  221. expr.includePath( "a.b" );
  222. assertExpectedResult({
  223. source: {"_id":0, "a":2, "z":2},
  224. expression: expr,
  225. expected: {"_id":0},
  226. expectedDependencies: {"_id":1, "a.b":1},
  227. expectedJsonRepresentation: {"a":{ "b":{$const:null}} }
  228. });
  229. },
  230. "should be able to include a nested field within an array": function testIncludeArrayNested(){
  231. /** Include a nested field within an array. */
  232. var expr = ObjectExpression.createRoot();
  233. expr.includePath( "a.b" );
  234. assertExpectedResult({
  235. source: {_id:0,a:[{b:5,c:6},{b:2,c:9},{c:7},[],2],z:1},
  236. expression: expr,
  237. expected: {_id:0,a:[{b:5},{b:2},{}]},
  238. expectedDependencies: {"_id":1, "a.b":1},
  239. expectedJsonRepresentation: {"a":{ "b":{$const:null}} }
  240. });
  241. },
  242. "should NOT include non-root '_id' field implicitly": function testExcludeNonRootId(){
  243. /** Don't include not root '_id' field implicitly. */
  244. var expr = ObjectExpression.createRoot();
  245. expr.includePath( "a.b" );
  246. assertExpectedResult({
  247. source: {"_id":0, "a":{ "_id":1, "b":1} },
  248. expression: expr,
  249. expected: {"_id":0, "a":{ "b":1} },
  250. expectedDependencies: {"_id":1, "a.b":1},
  251. expectedJsonRepresentation: {"a":{ "b":{$const:null}}}
  252. });
  253. },
  254. "should be able to project a computed expression": function testComputed(){
  255. /** Project a computed expression. */
  256. var expr = ObjectExpression.createRoot();
  257. expr.addField("a", new ConstantExpression(5));
  258. assertExpectedResult({
  259. source: {"_id":0},
  260. expression: expr,
  261. expected: {"_id":0, "a":5},
  262. expectedDependencies: {"_id":1},
  263. expectedJsonRepresentation: {"a":{ "$const":5} },
  264. expectedIsSimple: false
  265. });
  266. },
  267. "should be able to project a computed expression replacing an existing field": function testComputedReplacement(){
  268. /** Project a computed expression replacing an existing field. */
  269. var expr = ObjectExpression.createRoot();
  270. expr.addField("a", new ConstantExpression(5));
  271. assertExpectedResult({
  272. source: {"_id":0, "a":99},
  273. expression: expr,
  274. expected: {"_id": 0, "a": 5},
  275. expectedDependencies: {"_id":1},
  276. expectedJsonRepresentation: {"a": {"$const": 5}},
  277. expectedIsSimple: false
  278. });
  279. },
  280. "should NOT be able to project an undefined value": function testComputedUndefined(){
  281. /** An undefined value is not projected.. */
  282. var expr = ObjectExpression.createRoot();
  283. expr.addField("a", new ConstantExpression(undefined));
  284. assertExpectedResult({
  285. source: {"_id":0},
  286. expression: expr,
  287. expected: {"_id":0},
  288. expectedDependencies: {"_id":1},
  289. expectedJsonRepresentation: {a:{$const:undefined}},
  290. expectedIsSimple: false
  291. });
  292. },
  293. "should be able to project a computed expression replacing an existing field with Undefined": function testComputedUndefinedReplacement(){
  294. /** Project a computed expression replacing an existing field with Undefined. */
  295. var expr = ObjectExpression.createRoot();
  296. expr.addField("a", new ConstantExpression(5));
  297. assertExpectedResult({
  298. source: {"_id":0, "a":99},
  299. expression: expr,
  300. expected: {"_id":0, "a":5},
  301. expectedDependencies: {"_id":1},
  302. expectedJsonRepresentation: {"a":{"$const":5}},
  303. expectedIsSimple: false
  304. });
  305. },
  306. "should be able to project a null value": function testComputedNull(){
  307. /** A null value is projected. */
  308. var expr = ObjectExpression.createRoot();
  309. expr.addField("a", new ConstantExpression(null));
  310. assertExpectedResult({
  311. source: {"_id":0},
  312. expression: expr,
  313. expected: {"_id":0, "a":null},
  314. expectedDependencies: {"_id":1},
  315. expectedJsonRepresentation: {"a":{"$const":null}},
  316. expectedIsSimple: false
  317. });
  318. },
  319. "should be able to project a nested value": function testComputedNested(){
  320. /** A nested value is projected. */
  321. var expr = ObjectExpression.createRoot();
  322. expr.addField("a.b", new ConstantExpression(5));
  323. assertExpectedResult({
  324. source: {"_id":0},
  325. expression: expr,
  326. expected: {"_id":0, "a":{"b":5}},
  327. expectedDependencies: {"_id":1},
  328. expectedJsonRepresentation: {"a":{"b":{"$const":5}}},
  329. expectedIsSimple: false
  330. });
  331. },
  332. "should be able to project a field path": function testComputedFieldPath(){
  333. /** A field path is projected. */
  334. var expr = ObjectExpression.createRoot();
  335. expr.addField("a", FieldPathExpression.create("x"));
  336. assertExpectedResult({
  337. source: {"_id":0, "x":4},
  338. expression: expr,
  339. expected: {"_id":0, "a":4},
  340. expectedDependencies: {"_id":1, "x":1},
  341. expectedJsonRepresentation: {"a":"$x"},
  342. expectedIsSimple: false
  343. });
  344. },
  345. "should be able to project a nested field path": function testComputedNestedFieldPath(){
  346. /** A nested field path is projected. */
  347. var expr = ObjectExpression.createRoot();
  348. expr.addField("a.b", FieldPathExpression.create("x.y"));
  349. assertExpectedResult({
  350. source: {"_id":0, "x":{"y":4}},
  351. expression: expr,
  352. expected: {"_id":0, "a":{"b":4}},
  353. expectedDependencies: {"_id":1, "x.y":1},
  354. expectedJsonRepresentation: {"a":{"b":"$x.y"}},
  355. expectedIsSimple: false
  356. });
  357. },
  358. "should NOT project an empty subobject expression for a missing field": function testEmptyNewSubobject(){
  359. /** An empty subobject expression for a missing field is not projected. */
  360. var expr = ObjectExpression.createRoot();
  361. // Create a sub expression returning an empty object.
  362. var subExpr = ObjectExpression.create();
  363. subExpr.addField("b", FieldPathExpression.create("a.b"));
  364. expr.addField( "a", subExpr );
  365. assertExpectedResult({
  366. source: {"_id":0},
  367. expression: expr,
  368. expected: {"_id":0},
  369. expectedDependencies: {"_id":1, 'a.b':1},
  370. expectedJsonRepresentation: {a:{b:"$a.b"}},
  371. expectedIsSimple: false
  372. });
  373. },
  374. "should be able to project a non-empty new subobject": function testNonEmptyNewSubobject(){
  375. /** A non empty subobject expression for a missing field is projected. */
  376. var expr = ObjectExpression.createRoot();
  377. // Create a sub expression returning an empty object.
  378. var subExpr = ObjectExpression.create();
  379. subExpr.addField("b", new ConstantExpression(6));
  380. expr.addField( "a", subExpr );
  381. assertExpectedResult({
  382. source: {"_id":0},
  383. expression: expr,
  384. expected: {"_id":0, "a":{ "b":6} },
  385. expectedDependencies: {"_id":1},
  386. expectedJsonRepresentation: {a:{b:{$const:6}}},
  387. expectedIsSimple: false
  388. });
  389. },
  390. "should be able to project two computed fields within a common parent": function testAdjacentDottedComputedFields(){
  391. /** Two computed fields within a common parent. */
  392. var expr = ObjectExpression.createRoot();
  393. expr.addField("a.b", new ConstantExpression(6));
  394. expr.addField("a.c", new ConstantExpression(7));
  395. assertExpectedResult({
  396. source: {"_id":0},
  397. expression: expr,
  398. expected: {"_id":0, "a":{ "b":6, "c":7} },
  399. expectedDependencies: {"_id":1},
  400. expectedJsonRepresentation: {a:{b:{$const:6},c:{$const:7}}},
  401. expectedIsSimple: false
  402. });
  403. },
  404. "should be able to project two computed fields within a common parent (w/ one case dotted)": function testAdjacentDottedAndNestedComputedFields(){
  405. /** Two computed fields within a common parent, in one case dotted. */
  406. var expr = ObjectExpression.createRoot();
  407. expr.addField("a.b", new ConstantExpression(6));
  408. var subExpr = ObjectExpression.create();
  409. subExpr.addField("c", new ConstantExpression( 7 ) );
  410. expr.addField("a", subExpr);
  411. assertExpectedResult({
  412. source: {"_id":0},
  413. expression: expr,
  414. expected: {"_id":0, "a":{ "b":6, "c":7} },
  415. expectedDependencies: {"_id":1},
  416. expectedJsonRepresentation: {a:{b:{$const:6},c:{$const:7}}},
  417. expectedIsSimple: false
  418. });
  419. },
  420. "should be able to project two computed fields within a common parent (in another case dotted)": function testAdjacentNestedAndDottedComputedFields(){
  421. /** Two computed fields within a common parent, in another case dotted. */
  422. var expr = ObjectExpression.createRoot();
  423. var subExpr = ObjectExpression.create();
  424. subExpr.addField("b", new ConstantExpression(6));
  425. expr.addField("a", subExpr );
  426. expr.addField("a.c", new ConstantExpression(7));
  427. assertExpectedResult({
  428. source: {"_id":0},
  429. expression: expr,
  430. expected: {"_id":0, "a":{ "b":6, "c":7} },
  431. expectedDependencies: {"_id":1},
  432. expectedJsonRepresentation: {a:{b:{$const:6},c:{$const:7}}},
  433. expectedIsSimple: false
  434. });
  435. },
  436. "should be able to project two computed fields within a common parent (nested rather than dotted)": function testAdjacentNestedComputedFields(){
  437. /** Two computed fields within a common parent, nested rather than dotted. */
  438. var expr = ObjectExpression.createRoot();
  439. var subExpr1 = ObjectExpression.create();
  440. subExpr1.addField("b", new ConstantExpression(6));
  441. expr.addField("a", subExpr1);
  442. var subExpr2 = ObjectExpression.create();
  443. subExpr2.addField("c", new ConstantExpression(7));
  444. expr.addField("a", subExpr2);
  445. assertExpectedResult({
  446. source: {"_id":0},
  447. expression: expr,
  448. expected: {"_id":0, "a":{ "b":6, "c":7} },
  449. expectedDependencies: {"_id":1},
  450. expectedJsonRepresentation: {a:{b:{$const:6},c:{$const:7}}},
  451. expectedIsSimple: false
  452. });
  453. },
  454. "should be able to project multiple nested fields out of order without affecting output order": function testAdjacentNestedOrdering(){
  455. /** Field ordering is preserved when nested fields are merged. */
  456. var expr = ObjectExpression.createRoot();
  457. expr.addField("a.b", new ConstantExpression(6));
  458. var subExpr = ObjectExpression.create();
  459. // Add field 'd' then 'c'. Expect the same field ordering in the result doc.
  460. subExpr.addField("d", new ConstantExpression(7));
  461. subExpr.addField("c", new ConstantExpression(8));
  462. expr.addField("a", subExpr);
  463. assertExpectedResult({
  464. source: {"_id":0},
  465. expression: expr,
  466. expected: {"_id":0, "a":{ "b":6, "d":7, "c":8} },
  467. expectedDependencies: {"_id":1},
  468. expectedJsonRepresentation: {a:{b:{$const:6},d:{$const:7},c:{$const:8}}},
  469. expectedIsSimple: false
  470. });
  471. },
  472. "should be able to project adjacent fields two levels deep": function testMultipleNestedFields(){
  473. /** Adjacent fields two levels deep. */
  474. var expr = ObjectExpression.createRoot();
  475. expr.addField("a.b.c", new ConstantExpression(6));
  476. var bSubExpression = ObjectExpression.create();
  477. bSubExpression.addField("d", new ConstantExpression(7));
  478. var aSubExpression = ObjectExpression.create();
  479. aSubExpression.addField("b", bSubExpression);
  480. expr.addField("a", aSubExpression);
  481. assertExpectedResult({
  482. source:{_id:0},
  483. expression: expr,
  484. expected: {"_id":0, "a":{ "b":{ "c":6, "d":7}}},
  485. expectedDependencies:{_id:1},
  486. expectedJsonRepresentation:{"a":{"b":{"c":{$const:6},"d":{$const:7}}}},
  487. expectedIsSimple:false
  488. });
  489. var res = expr.evaluateDocument(new Variables(1, {_id:1}));
  490. },
  491. "should throw an Error if two expressions generate the same field": function testConflictingExpressionFields(){
  492. /** Two expressions cannot generate the same field. */
  493. var expr = ObjectExpression.createRoot();
  494. expr.addField("a", new ConstantExpression(5));
  495. assert.throws(function(){
  496. expr.addField("a", new ConstantExpression(6)); // Duplicate field.
  497. }, Error);
  498. },
  499. "should throw an Error if an expression field conflicts with an inclusion field": function testConflictingInclusionExpressionFields(){
  500. /** An expression field conflicts with an inclusion field. */
  501. var expr = ObjectExpression.createRoot();
  502. expr.includePath("a");
  503. assert.throws(function(){
  504. expr.addField("a", new ConstantExpression(6));
  505. }, Error);
  506. },
  507. "should throw an Error if an inclusion field conflicts with an expression field": function testConflictingExpressionInclusionFields(){
  508. /** An inclusion field conflicts with an expression field. */
  509. var expr = ObjectExpression.createRoot();
  510. expr.addField("a", new ConstantExpression(5));
  511. assert.throws(function(){
  512. expr.includePath("a");
  513. }, Error);
  514. },
  515. "should throw an Error if an object expression conflicts with a constant expression": function testConflictingObjectConstantExpressionFields(){
  516. /** An object expression conflicts with a constant expression. */
  517. var expr = ObjectExpression.createRoot();
  518. var subExpr = ObjectExpression.create();
  519. subExpr.includePath("b");
  520. expr.addField("a", subExpr);
  521. assert.throws(function(){
  522. expr.addField("a.b", new ConstantExpression(6));
  523. }, Error);
  524. },
  525. "should throw an Error if a constant expression conflicts with an object expression": function testConflictingConstantObjectExpressionFields(){
  526. /** A constant expression conflicts with an object expression. */
  527. var expr = ObjectExpression.createRoot();
  528. expr.addField("a.b", new ConstantExpression(6));
  529. var subExpr = ObjectExpression.create();
  530. subExpr.includePath("b");
  531. assert.throws(function(){
  532. expr.addField("a", subExpr);
  533. }, Error);
  534. },
  535. "should throw an Error if two nested expressions cannot generate the same field": function testConflictingNestedFields(){
  536. /** Two nested expressions cannot generate the same field. */
  537. var expr = ObjectExpression.createRoot();
  538. expr.addField("a.b", new ConstantExpression(5));
  539. assert.throws(function(){
  540. expr.addField("a.b", new ConstantExpression(6)); // Duplicate field.
  541. }, Error);
  542. },
  543. "should throw an Error if an expression is created for a subfield of another expression": function testConflictingFieldAndSubfield(){
  544. /** An expression cannot be created for a subfield of another expression. */
  545. var expr = ObjectExpression.createRoot();
  546. expr.addField("a", new ConstantExpression(5));
  547. assert.throws(function(){
  548. expr.addField("a.b", new ConstantExpression(5));
  549. }, Error);
  550. },
  551. "should throw an Error if an expression is created for a nested field of another expression.": function testConflictingFieldAndNestedField(){
  552. /** An expression cannot be created for a nested field of another expression. */
  553. var expr = ObjectExpression.createRoot();
  554. expr.addField("a", new ConstantExpression(5));
  555. var subExpr = ObjectExpression.create();
  556. subExpr.addField("b", new ConstantExpression(5));
  557. assert.throws(function(){
  558. expr.addField("a", subExpr);
  559. }, Error);
  560. },
  561. "should throw an Error if an expression is created for a parent field of another expression": function testConflictingSubfieldAndField(){
  562. /** An expression cannot be created for a parent field of another expression. */
  563. var expr = ObjectExpression.createRoot();
  564. expr.addField("a.b", new ConstantExpression(5));
  565. assert.throws(function(){
  566. expr.addField("a", new ConstantExpression(5));
  567. }, Error);
  568. },
  569. "should throw an Error if an expression is created for a parent of a nested field": function testConflictingNestedFieldAndField(){
  570. /** An expression cannot be created for a parent of a nested field. */
  571. var expr = ObjectExpression.createRoot();
  572. var subExpr = ObjectExpression.create();
  573. subExpr.addField("b", new ConstantExpression(5));
  574. expr.addField("a", subExpr);
  575. assert.throws(function(){
  576. expr.addField("a", new ConstantExpression(5));
  577. }, Error);
  578. },
  579. "should be able to evaluate expressions in general": function testEvaluate(){
  580. /**
  581. * evaluate() does not supply an inclusion document.
  582. * Inclusion spec'd fields are not included.
  583. * (Inclusion specs are not generally expected/allowed in cases where evaluate is called instead of addToDocument.)
  584. */
  585. var expr = ObjectExpression.createRoot();
  586. expr.includePath("a");
  587. expr.addField("b", new ConstantExpression(5));
  588. expr.addField("c", FieldPathExpression.create("a"));
  589. var res = expr.evaluateInternal(new Variables(1, {_id:0, a:1}));
  590. assert.deepEqual({"b":5, "c":1}, res);
  591. }
  592. }
  593. }
  594. };
  595. if (!module.parent)(new(require("mocha"))()).ui("exports").reporter("spec").addFile(__filename).run(process.exit);