CompareExpression_test.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467
  1. "use strict";
  2. var assert = require("assert"),
  3. expressions = require("../../../../lib/pipeline/expressions/"),
  4. Expression = expressions.Expression,
  5. CompareExpression = require("../../../../lib/pipeline/expressions/CompareExpression"),
  6. VariablesParseState = require("../../../../Lib/pipeline/expressions/VariablesParseState"),
  7. VariablesIdGenerator = require("../../../../Lib/pipeline/expressions/VariablesIdGenerator"),
  8. utils = require("./utils"),
  9. constify = utils.constify,
  10. expressionToJson = utils.expressionToJson;
  11. // Mocha one-liner to make these tests self-hosted
  12. if(!module.parent)return(require.cache[__filename]=null,(new(require("mocha"))({ui:"exports",reporter:"spec",grep:process.env.TEST_GREP})).addFile(__filename).run(process.exit));
  13. var TestBase = function TestBase(overrides) {
  14. //NOTE: DEVIATION FROM MONGO: using this base class to make things easier to initialize
  15. for (var key in overrides)
  16. this[key] = overrides[key];
  17. },
  18. OptimizeBase = (function() {
  19. var klass = function OptimizeBase() {
  20. base.apply(this, arguments);
  21. },
  22. base = TestBase,
  23. proto = klass.prototype = Object.create(base.prototype);
  24. proto.run = function() {
  25. var specElement = this.spec,
  26. idGenerator = new VariablesIdGenerator(),
  27. vps = new VariablesParseState(idGenerator),
  28. expression = Expression.parseOperand(specElement, vps),
  29. optimized = expression.optimize();
  30. assert.deepEqual(constify(this.expectedOptimized()), expressionToJson(optimized));
  31. };
  32. return klass;
  33. })(),
  34. FieldRangeOptimize = (function() {
  35. var klass = function FieldRangeOptimize() {
  36. base.apply(this, arguments);
  37. },
  38. base = OptimizeBase,
  39. proto = klass.prototype = Object.create(base.prototype);
  40. proto.expectedOptimized = function(){
  41. return this.spec;
  42. };
  43. return klass;
  44. })(),
  45. NoOptimize = (function() {
  46. var klass = function NoOptimize() {
  47. base.apply(this, arguments);
  48. },
  49. base = OptimizeBase,
  50. proto = klass.prototype = Object.create(base.prototype);
  51. proto.expectedOptimized = function(){
  52. return this.spec;
  53. };
  54. return klass;
  55. })(),
  56. ExpectedResultBase = (function() {
  57. /** Check expected result for expressions depending on constants. */
  58. var klass = function ExpectedResultBase() {
  59. base.apply(this, arguments);
  60. },
  61. base = OptimizeBase,
  62. proto = klass.prototype = Object.create(base.prototype);
  63. proto.run = function() {
  64. base.prototype.run.call(this);
  65. var specElement = this.spec,
  66. idGenerator = new VariablesIdGenerator(),
  67. vps = new VariablesParseState(idGenerator),
  68. expression = Expression.parseOperand(specElement, vps);
  69. // Check expression spec round trip.
  70. assert.deepEqual(expressionToJson(expression), constify(specElement));
  71. // Check evaluation result.
  72. assert.strictEqual(expression.evaluate({}), this.expectedResult);
  73. // Check that the result is the same after optimizing.
  74. var optimized = expression.optimize();
  75. assert.strictEqual(optimized.evaluate({}), this.expectedResult);
  76. };
  77. proto.expectedOptimized = function() {
  78. return {$const:this.expectedResult};
  79. };
  80. return klass;
  81. })(),
  82. ExpectedTrue = (function(){
  83. var klass = function ExpectedTrue() {
  84. base.apply(this, arguments);
  85. },
  86. base = ExpectedResultBase,
  87. proto = klass.prototype = Object.create(base.prototype);
  88. proto.expectedResult = true;
  89. return klass;
  90. })(),
  91. ExpectedFalse = (function(){
  92. var klass = function ExpectedFalse() {
  93. base.apply(this, arguments);
  94. },
  95. base = ExpectedResultBase,
  96. proto = klass.prototype = Object.create(base.prototype);
  97. proto.expectedResult = false;
  98. return klass;
  99. })(),
  100. ParseError = (function(){
  101. var klass = function ParseError() {
  102. base.apply(this, arguments);
  103. },
  104. base = TestBase,
  105. proto = klass.prototype = Object.create(base.prototype);
  106. proto.run = function() {
  107. var specElement = this.spec,
  108. idGenerator = new VariablesIdGenerator(),
  109. vps = new VariablesParseState(idGenerator);
  110. assert.throws(function() {
  111. Expression.parseOperand(specElement, vps);
  112. });
  113. };
  114. return klass;
  115. })();
  116. exports.CompareExpression = {
  117. "constructor()": {
  118. "should throw Error if no args": function() {
  119. assert.throws(function() {
  120. new CompareExpression();
  121. });
  122. },
  123. "should throw if more than 1 args": function() {
  124. assert.throws(function() {
  125. new CompareExpression(1,2);
  126. });
  127. },
  128. "should not throw if 1 arg and arg is string": function() {
  129. assert.doesNotThrow(function() {
  130. new CompareExpression("a");
  131. });
  132. },
  133. },
  134. "#getOpName()": {
  135. "should return the correct op name; $eq, $ne, $gt, $gte, $lt, $lte, $cmp": function() {
  136. assert.equal(new CompareExpression(CompareExpression.CmpOp.EQ).getOpName(), "$eq");
  137. assert.equal(new CompareExpression(CompareExpression.CmpOp.NE).getOpName(), "$ne");
  138. assert.equal(new CompareExpression(CompareExpression.CmpOp.GT).getOpName(), "$gt");
  139. assert.equal(new CompareExpression(CompareExpression.CmpOp.GTE).getOpName(), "$gte");
  140. assert.equal(new CompareExpression(CompareExpression.CmpOp.LT).getOpName(), "$lt");
  141. assert.equal(new CompareExpression(CompareExpression.CmpOp.LTE).getOpName(), "$lte");
  142. assert.equal(new CompareExpression(CompareExpression.CmpOp.CMP).getOpName(), "$cmp");
  143. },
  144. },
  145. "#evaluate()": {
  146. /** $eq with first < second. */
  147. "EqLt": function EqLt() {
  148. new ExpectedFalse({
  149. spec: {$eq:[1,2]},
  150. }).run();
  151. },
  152. /** $eq with first == second. */
  153. "EqEq": function EqEq() {
  154. new ExpectedTrue({
  155. spec: {$eq:[1,1]},
  156. }).run();
  157. },
  158. /** $eq with first > second. */
  159. "EqGt": function EqEq() {
  160. new ExpectedFalse({
  161. spec: {$eq:[1,0]},
  162. }).run();
  163. },
  164. /** $ne with first < second. */
  165. "NeLt": function NeLt() {
  166. new ExpectedTrue({
  167. spec: {$ne:[1,2]},
  168. }).run();
  169. },
  170. /** $ne with first == second. */
  171. "NeEq": function NeEq() {
  172. new ExpectedFalse({
  173. spec: {$ne:[1,1]},
  174. }).run();
  175. },
  176. /** $ne with first > second. */
  177. "NeGt": function NeGt() {
  178. new ExpectedTrue({
  179. spec: {$ne:[1,0]},
  180. }).run();
  181. },
  182. /** $gt with first < second. */
  183. "GtLt": function GtLt() {
  184. new ExpectedFalse({
  185. spec: {$gt:[1,2]},
  186. }).run();
  187. },
  188. /** $gt with first == second. */
  189. "GtEq": function GtEq() {
  190. new ExpectedFalse({
  191. spec: {$gt:[1,1]},
  192. }).run();
  193. },
  194. /** $gt with first > second. */
  195. "GtGt": function GtGt() {
  196. new ExpectedTrue({
  197. spec: {$gt:[1,0]},
  198. }).run();
  199. },
  200. /** $gte with first < second. */
  201. "GteLt": function GteLt() {
  202. new ExpectedFalse({
  203. spec: {$gte:[1,2]},
  204. }).run();
  205. },
  206. /** $gte with first == second. */
  207. "GteEq": function GteEq() {
  208. new ExpectedTrue({
  209. spec: {$gte:[1,1]},
  210. }).run();
  211. },
  212. /** $gte with first > second. */
  213. "GteGt": function GteGt() {
  214. new ExpectedTrue({
  215. spec: {$gte:[1,0]},
  216. }).run();
  217. },
  218. /** $gte with first > second. */
  219. "LtLt": function LtLt() {
  220. new ExpectedTrue({
  221. spec: {$lt:[1,2]},
  222. }).run();
  223. },
  224. /** $lt with first == second. */
  225. "LtEq": function LtEq() {
  226. new ExpectedFalse({
  227. spec: {$lt:[1,1]},
  228. }).run();
  229. },
  230. /** $lt with first > second. */
  231. "LtGt": function LtGt() {
  232. new ExpectedFalse({
  233. spec: {$lt:[1,0]},
  234. }).run();
  235. },
  236. /** $lte with first < second. */
  237. "LteLt": function LteLt() {
  238. new ExpectedTrue({
  239. spec: {$lte:[1,2]},
  240. }).run();
  241. },
  242. /** $lte with first == second. */
  243. "LteEq": function LteEq() {
  244. new ExpectedTrue({
  245. spec: {$lte:[1,1]},
  246. }).run();
  247. },
  248. /** $lte with first > second. */
  249. "LteGt": function LteGt() {
  250. new ExpectedFalse({
  251. spec: {$lte:[1,0]},
  252. }).run();
  253. },
  254. /** $cmp with first < second. */
  255. "CmpLt": function CmpLt() {
  256. new ExpectedResultBase({
  257. spec: {$cmp:[1,2]},
  258. expectedResult: -1,
  259. }).run();
  260. },
  261. /** $cmp with first == second. */
  262. "CmpEq": function CmpEq() {
  263. new ExpectedResultBase({
  264. spec: {$cmp:[1,1]},
  265. expectedResult: 0,
  266. }).run();
  267. },
  268. /** $cmp with first > second. */
  269. "CmpGt": function CmpGt() {
  270. new ExpectedResultBase({
  271. spec: {$cmp:[1,0]},
  272. expectedResult: 1,
  273. }).run();
  274. },
  275. /** $cmp results are bracketed to an absolute value of 1. */
  276. "CmpBracketed": function CmpBracketed() {
  277. var test = new ExpectedResultBase({
  278. spec: {$cmp:["z","a"]},
  279. expectedResult: 1,
  280. }).run();
  281. },
  282. /** Zero operands provided. */
  283. "ZeroOperands": function ZeroOperands() {
  284. new ParseError({
  285. spec: {$ne:[]},
  286. }).run();
  287. },
  288. /** One operands provided. */
  289. "OneOperand": function OneOperand() {
  290. new ParseError({
  291. spec: {$eq:[1]},
  292. }).run();
  293. },
  294. /** Incompatible types can be compared. */
  295. "IncompatibleTypes": function IncompatibleTypes() {
  296. var specElement = {$ne:["a",1]},
  297. idGenerator = new VariablesIdGenerator(),
  298. vps = new VariablesParseState(idGenerator),
  299. expr = Expression.parseOperand(specElement, vps);
  300. assert.deepEqual(expr.evaluate({}), true);
  301. },
  302. /** Three operands provided. */
  303. "ThreeOperands": function ThreeOperands() {
  304. new ParseError({
  305. spec: {$gt:[2,3,4]},
  306. }).run();
  307. },
  308. /**
  309. * An expression depending on constants is optimized to a constant via
  310. * ExpressionNary::optimize().
  311. */
  312. "OptimizeConstants": function OptimizeConstants() {
  313. new OptimizeBase({
  314. spec: {$eq:[1,1]},
  315. expectedOptimized: function() {
  316. return {$const: true};
  317. },
  318. }).run();
  319. },
  320. /** $cmp is not optimized. */
  321. "NoOptimizeCmp": function NoOptimizeCmp() {
  322. new NoOptimize({
  323. spec: {$cmp:[1,"$a"]},
  324. }).run();
  325. },
  326. /** $ne is not optimized. */
  327. "NoOptimizeNe": function NoOptimizeNe() {
  328. new NoOptimize({
  329. spec: {$ne:[1,"$a"]},
  330. }).run();
  331. },
  332. /** No optimization is performend without a constant. */
  333. "NoOptimizeNoConstant": function NoOptimizeNoConstant() {
  334. new NoOptimize({
  335. spec: {$ne:["$a", "$b"]},
  336. }).run();
  337. },
  338. /** No optimization is performend without an immediate field path. */
  339. "NoOptimizeWithoutFieldPath": function NoOptimizeWithoutFieldPath() {
  340. new NoOptimize({
  341. spec: {$eq:[{$and:["$a"]},1]},
  342. }).run();
  343. },
  344. /** No optimization is performend without an immediate field path. */
  345. "NoOptimizeWithoutFieldPathReverse": function NoOptimizeWithoutFieldPathReverse() {
  346. new NoOptimize({
  347. spec: {$eq:[1,{$and:["$a"]}]},
  348. }).run();
  349. },
  350. /** An equality expression is optimized. */
  351. "OptimizeEq": function OptimizeEq() {
  352. new FieldRangeOptimize({
  353. spec: {$eq:["$a",1]},
  354. }).run();
  355. },
  356. /** A reverse sense equality expression is optimized. */
  357. "OptimizeEqReverse": function OptimizeEqReverse() {
  358. new FieldRangeOptimize({
  359. spec: {$eq:[1,"$a"]},
  360. }).run();
  361. },
  362. /** A $lt expression is optimized. */
  363. "OptimizeLt": function OptimizeLt() {
  364. new FieldRangeOptimize({
  365. spec: {$lt:["$a",1]},
  366. }).run();
  367. },
  368. /** A reverse sense $lt expression is optimized. */
  369. "OptimizeLtReverse": function OptimizeLtReverse() {
  370. new FieldRangeOptimize({
  371. spec: {$lt:[1,"$a"]},
  372. }).run();
  373. },
  374. /** A $lte expression is optimized. */
  375. "OptimizeLte": function OptimizeLte() {
  376. new FieldRangeOptimize({
  377. spec: {$lte:["$b",2]},
  378. }).run();
  379. },
  380. /** A reverse sense $lte expression is optimized. */
  381. "OptimizeLteReverse": function OptimizeLteReverse() {
  382. new FieldRangeOptimize({
  383. spec: {$lte:[2,"$b"]},
  384. }).run();
  385. },
  386. /** A $gt expression is optimized. */
  387. "OptimizeGt": function OptimizeGt() {
  388. new FieldRangeOptimize({
  389. spec: {$gt:["$b",2]},
  390. }).run();
  391. },
  392. /** A reverse sense $gt expression is optimized. */
  393. "OptimizeGtReverse": function OptimizeGtReverse() {
  394. new FieldRangeOptimize({
  395. spec: {$gt:["$b",2]},
  396. }).run();
  397. },
  398. /** A $gte expression is optimized. */
  399. "OptimizeGte": function OptimizeGte() {
  400. new FieldRangeOptimize({
  401. spec: {$gte:["$b",2]},
  402. }).run();
  403. },
  404. /** A reverse sense $gte expression is optimized. */
  405. "OptimizeGteReverse": function OptimizeGteReverse() {
  406. new FieldRangeOptimize({
  407. spec: {$gte:[2,"$b"]},
  408. }).run();
  409. },
  410. },
  411. };