MatchDocumentSource.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. "use strict";
  2. var async = require("async"),
  3. matcher = require("../matcher/Matcher2.js"),
  4. DocumentSource = require("./DocumentSource");
  5. /**
  6. * A match document source built off of DocumentSource
  7. *
  8. * NOTE: THIS IS A DEVIATION FROM THE MONGO IMPLEMENTATION.
  9. * TODO: internally uses `sift` to fake it, which has bugs, so we need to reimplement this by porting the MongoDB implementation
  10. *
  11. * @class MatchDocumentSource
  12. * @namespace mungedb-aggregate.pipeline.documentSources
  13. * @module mungedb-aggregate
  14. * @constructor
  15. * @param {Object} query the match query to use
  16. * @param [ctx] {ExpressionContext}
  17. **/
  18. var MatchDocumentSource = module.exports = function MatchDocumentSource(query, ctx){
  19. if (arguments.length > 2) throw new Error("up to two args expected");
  20. if (!query) throw new Error("arg `query` is required");
  21. base.call(this, ctx);
  22. this.query = query; // save the query, so we can check it for deps later. THIS IS A DEVIATION FROM THE MONGO IMPLEMENTATION
  23. this.matcher = new matcher(query);
  24. }, klass = MatchDocumentSource, base = require('./DocumentSource'), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
  25. klass.matchName = "$match";
  26. proto.getSourceName = function getSourceName(){
  27. return klass.matchName;
  28. };
  29. proto.getNext = function getNext(callback) {
  30. var self = this,
  31. next;
  32. async.doWhilst(
  33. function(cb) {
  34. self.source.getNext(function(err, val) {
  35. next = val;
  36. if (self.matcher.matches(next))
  37. return cb(next);
  38. return cb();
  39. });
  40. },
  41. function() {
  42. return next !== DocumentSource.EOF;
  43. },
  44. function(doc) {
  45. if (!doc)
  46. return callback(null, DocumentSource.EOF);
  47. return callback(null, doc);
  48. }
  49. );
  50. };
  51. proto.coalesce = function coalesce(nextSource) {
  52. if (!(nextSource instanceof MatchDocumentSource))
  53. return false;
  54. this.matcher = new matcher({"$and": [this.getQuery(), nextSource.getQuery()]});
  55. return true;
  56. };
  57. proto.serialize = function(explain) {
  58. var out = {};
  59. out[this.getSourceName()] = this.getQuery();
  60. return out;
  61. };
  62. klass.uassertNoDisallowedClauses = function uassertNoDisallowedClauses(query) {
  63. for(var key in query){
  64. if(query.hasOwnProperty(key)){
  65. // can't use the Matcher API because this would segfault the constructor
  66. if (query[key] == "$where") throw new Error("code 16395; $where is not allowed inside of a $match aggregation expression");
  67. // geo breaks if it is not the first portion of the pipeline
  68. if (query[key] == "$near") throw new Error("code 16424; $near is not allowed inside of a $match aggregation expression");
  69. if (query[key] == "$within") throw new Error("code 16425; $within is not allowed inside of a $match aggregation expression");
  70. if (query[key] == "$nearSphere") throw new Error("code 16426; $nearSphere is not allowed inside of a $match aggregation expression");
  71. if (query[key] instanceof Object && query[key].constructor === Object) this.uassertNoDisallowedClauses(query[key]);
  72. }
  73. }
  74. };
  75. klass.createFromJson = function createFromJson(jsonElement, ctx) {
  76. if (!(jsonElement instanceof Object) || jsonElement.constructor !== Object) throw new Error("code 15959 ; the match filter must be an expression in an object");
  77. klass.uassertNoDisallowedClauses(jsonElement);
  78. var matcher = new MatchDocumentSource(jsonElement, ctx);
  79. return matcher;
  80. };
  81. proto.getQuery = function getQuery() {
  82. return this.matcher._pattern;
  83. };
  84. /** Returns the portion of the match that can safely be promoted to before a $redact.
  85. * If this returns an empty BSONObj, no part of this match may safely be promoted.
  86. *
  87. * To be safe to promote, removing a field from a document to be matched must not cause
  88. * that document to be accepted when it would otherwise be rejected. As an example,
  89. * {name: {$ne: "bob smith"}} accepts documents without a name field, which means that
  90. * running this filter before a redact that would remove the name field would leak
  91. * information. On the other hand, {age: {$gt:5}} is ok because it doesn't accept documents
  92. * that have had their age field removed.
  93. */
  94. proto.redactSafePortion = function redactSafePortion() {
  95. throw new Error("not implemented");
  96. };