CompareExpression_test.js 12 KB

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