CompareExpression_test.js 12 KB

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