ObjectExpression.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. var ObjectExpression = module.exports = (function(){
  2. // CONSTRUCTOR
  3. /** Create an empty expression. Until fields are added, this will evaluate to an empty document (object). **/
  4. var klass = function ObjectExpression(){
  5. if(arguments.length !== 0) throw new Error("zero args expected");
  6. this._excludeId = false; /// <Boolean> for if _id is to be excluded
  7. this._expressions = {}; /// <Object<Expression>> mapping from fieldname to Expression to generate the value NULL expression means include from source document
  8. this._order = []; /// <Array<String>> this is used to maintain order for generated fields not in the source document
  9. }, base = require("./Expression"), proto = klass.prototype = Object.create(base.prototype, {constructor:{value:klass}});
  10. // DEPENDENCIES
  11. var Value = require("../Value");
  12. // INSTANCE VARIABLES
  13. /** <Boolean> for if _id is to be excluded **/
  14. proto._excludeId = undefined;
  15. /** <Object<Expression>> mapping from fieldname to Expression to generate the value NULL expression means include from source document **/
  16. proto._expressions = undefined;
  17. /** <Array<String>> this is used to maintain order for generated fields not in the source document **/
  18. proto._order = [];
  19. // PROTOTYPE MEMBERS
  20. /**
  21. * evaluate(), but return a Document instead of a Value-wrapped Document.
  22. *
  23. * @param pDocument the input Document
  24. * @returns the result document
  25. **/
  26. proto.evaluateDocument = function evaluateDocument(doc){
  27. throw new Error("FINISH evaluateDocument"); //TODO:...
  28. /*
  29. intrusive_ptr<Document> ExpressionObject::evaluateDocument(
  30. const intrusive_ptr<Document> &pDocument) const {
  31. // create and populate the result
  32. intrusive_ptr<Document> pResult(
  33. Document::create(getSizeHint()));
  34. addToDocument(pResult,
  35. Document::create(), // No inclusion field matching.
  36. pDocument);
  37. return pResult;
  38. }
  39. */
  40. };
  41. proto.evaluate = function evaluate(doc){
  42. throw new Error("FINISH evaluate"); //TODO:...
  43. //return Value::createDocument(evaluateDocument(pDocument));
  44. };
  45. proto.optimize = function optimize(){
  46. for (var key in this._expressions) {
  47. var expr = this._expressions[key];
  48. if (expr !== undefined && expr !== null) this._expressions[key] = expr.optimize();
  49. }
  50. return this;
  51. };
  52. proto.getIsSimple = function getIsSimple(){
  53. for (var key in this._expressions) {
  54. var expr = this._expressions[key];
  55. if (expr !== undefined && expr !== null && !expr.getIsSimple()) return false;
  56. }
  57. return true;
  58. };
  59. proto.addDependencies = function addDependencies(deps, path){
  60. var pathStr;
  61. if (path instanceof Array) {
  62. if (path.length === 0) {
  63. // we are in the top level of a projection so _id is implicit
  64. if (!this._excludeId) deps.insert("_id");
  65. } else {
  66. pathStr = new FieldPath(path).getPath() + ".";
  67. }
  68. } else {
  69. if (this._excludeId) throw new Error("_excludeId is true!");
  70. }
  71. for (var key in this._expressions) {
  72. var expr = this._expressions[key];
  73. if (expr !== undefined && expr !== null){
  74. if (path instanceof Array) path.push(key);
  75. expr.addDependencies(deps, path);
  76. if (path instanceof Array) path.pop();
  77. }else{ // inclusion
  78. if(path === undefined || path === null) throw new Error("inclusion not supported in objects nested in $expressions; code 16407");
  79. deps.insert(pathStr + key);
  80. }
  81. }
  82. return deps;
  83. };
  84. /**
  85. * evaluate(), but add the evaluated fields to a given document instead of creating a new one.
  86. *
  87. * @param pResult the Document to add the evaluated expressions to
  88. * @param pDocument the input Document for this level
  89. * @param rootDoc the root of the whole input document
  90. **/
  91. proto.addToDocument = function addToDocument(result, document, rootDoc){
  92. throw new Error("FINISH addToDocument"); //TODO:...
  93. /*
  94. void ExpressionObject::addToDocument(
  95. const intrusive_ptr<Document> &pResult,
  96. const intrusive_ptr<Document> &pDocument,
  97. const intrusive_ptr<Document> &rootDoc
  98. ) const
  99. {
  100. const bool atRoot = (pDocument == rootDoc);
  101. ExpressionMap::const_iterator end = _expressions.end();
  102. // This is used to mark fields we've done so that we can add the ones we haven't
  103. set<string> doneFields;
  104. FieldIterator fields(pDocument);
  105. while(fields.more()) {
  106. Document::FieldPair field (fields.next());
  107. ExpressionMap::const_iterator exprIter = _expressions.find(field.first);
  108. // This field is not supposed to be in the output (unless it is _id)
  109. if (exprIter == end) {
  110. if (!_excludeId && atRoot && field.first == Document::idName) {
  111. // _id from the root doc is always included (until exclusion is supported)
  112. // not updating doneFields since "_id" isn't in _expressions
  113. pResult->addField(field.first, field.second);
  114. }
  115. continue;
  116. }
  117. // make sure we don't add this field again
  118. doneFields.insert(exprIter->first);
  119. Expression* expr = exprIter->second.get();
  120. if (!expr) {
  121. // This means pull the matching field from the input document
  122. pResult->addField(field.first, field.second);
  123. continue;
  124. }
  125. ExpressionObject* exprObj = dynamic_cast<ExpressionObject*>(expr);
  126. BSONType valueType = field.second->getType();
  127. if ((valueType != Object && valueType != Array) || !exprObj ) {
  128. // This expression replace the whole field
  129. intrusive_ptr<const Value> pValue(expr->evaluate(rootDoc));
  130. // don't add field if nothing was found in the subobject
  131. if (exprObj && pValue->getDocument()->getFieldCount() == 0)
  132. continue;
  133. // Don't add non-existent values (note: different from NULL); this is consistent with existing selection syntax which doesn't force the appearnance of non-existent fields.
  134. // TODO make missing distinct from Undefined
  135. if (pValue->getType() != Undefined)
  136. pResult->addField(field.first, pValue);
  137. continue;
  138. }
  139. // Check on the type of the input value. If it's an object, just walk down into that recursively, and add it to the result.
  140. if (valueType == Object) {
  141. intrusive_ptr<Document> doc = Document::create(exprObj->getSizeHint());
  142. exprObj->addToDocument(doc,
  143. field.second->getDocument(),
  144. rootDoc);
  145. pResult->addField(field.first, Value::createDocument(doc));
  146. }
  147. else if (valueType == Array) {
  148. // If it's an array, we have to do the same thing, but to each array element. Then, add the array of results to the current document.
  149. vector<intrusive_ptr<const Value> > result;
  150. intrusive_ptr<ValueIterator> pVI(field.second->getArray());
  151. while(pVI->more()) {
  152. intrusive_ptr<const Value> next = pVI->next();
  153. // can't look for a subfield in a non-object value.
  154. if (next->getType() != Object)
  155. continue;
  156. intrusive_ptr<Document> doc = Document::create(exprObj->getSizeHint());
  157. exprObj->addToDocument(doc,
  158. next->getDocument(),
  159. rootDoc);
  160. result.push_back(Value::createDocument(doc));
  161. }
  162. pResult->addField(field.first,
  163. Value::createArray(result));
  164. }
  165. else {
  166. verify( false );
  167. }
  168. }
  169. if (doneFields.size() == _expressions.size())
  170. return;
  171. // add any remaining fields we haven't already taken care of
  172. for (vector<string>::const_iterator i(_order.begin()); i!=_order.end(); ++i) {
  173. ExpressionMap::const_iterator it = _expressions.find(*i);
  174. string fieldName(it->first);
  175. // if we've already dealt with this field, above, do nothing
  176. if (doneFields.count(fieldName))
  177. continue;
  178. // this is a missing inclusion field
  179. if (!it->second)
  180. continue;
  181. intrusive_ptr<const Value> pValue(it->second->evaluate(rootDoc));
  182. // Don't add non-existent values (note: different from NULL); this is consistent with existing selection syntax which doesn't force the appearnance of non-existent fields.
  183. if (pValue->getType() == Undefined)
  184. continue;
  185. // don't add field if nothing was found in the subobject
  186. if (dynamic_cast<ExpressionObject*>(it->second.get())
  187. && pValue->getDocument()->getFieldCount() == 0)
  188. continue;
  189. pResult->addField(fieldName, pValue);
  190. }
  191. }
  192. */
  193. };
  194. /** estimated number of fields that will be output **/
  195. proto.getSizeHint = function getSizeHint(){
  196. throw new Error("FINISH getSizeHint"); //TODO:...
  197. /*
  198. // Note: this can overestimate, but that is better than underestimating
  199. return _expressions.size() + (_excludeId ? 0 : 1);
  200. */
  201. };
  202. /**
  203. * Add a field to the document expression.
  204. *
  205. * @param fieldPath the path the evaluated expression will have in the result Document
  206. * @param pExpression the expression to evaluate obtain this field's Value in the result Document
  207. **/
  208. proto.addField = function addField(path, pExpression){
  209. var fieldPart = path.fields[0],
  210. haveExpr = this._expressions.hasOwnProperty(fieldPart),
  211. expr = this._expressions[fieldPart];
  212. var subObj = expr instanceof ObjectExpression ? expr : undefined;
  213. if(!haveExpr){
  214. this._order.push(fieldPart);
  215. }
  216. throw new Error("FINISH addField"); //TODO:...
  217. /*
  218. void ExpressionObject::addField(const FieldPath &fieldPath, const intrusive_ptr<Expression> &pExpression) {
  219. const string fieldPart = fieldPath.getFieldName(0);
  220. const bool haveExpr = _expressions.count(fieldPart);
  221. intrusive_ptr<Expression>& expr = _expressions[fieldPart]; // inserts if !haveExpr
  222. intrusive_ptr<ExpressionObject> subObj = dynamic_cast<ExpressionObject*>(expr.get());
  223. if (!haveExpr) {
  224. _order.push_back(fieldPart);
  225. }
  226. else { // we already have an expression or inclusion for this field
  227. if (fieldPath.getPathLength() == 1) {
  228. // This expression is for right here
  229. ExpressionObject* newSubObj = dynamic_cast<ExpressionObject*>(pExpression.get());
  230. uassert(16400, str::stream()
  231. << "can't add an expression for field " << fieldPart
  232. << " because there is already an expression for that field"
  233. << " or one of its sub-fields.",
  234. subObj && newSubObj); // we can merge them
  235. // Copy everything from the newSubObj to the existing subObj
  236. // This is for cases like { $project:{ 'b.c':1, b:{ a:1 } } }
  237. for (vector<string>::const_iterator it (newSubObj->_order.begin());
  238. it != newSubObj->_order.end();
  239. ++it) {
  240. // asserts if any fields are dupes
  241. subObj->addField(*it, newSubObj->_expressions[*it]);
  242. }
  243. return;
  244. }
  245. else {
  246. // This expression is for a subfield
  247. uassert(16401, str::stream()
  248. << "can't add an expression for a subfield of " << fieldPart
  249. << " because there is already an expression that applies to"
  250. << " the whole field",
  251. subObj);
  252. }
  253. }
  254. if (fieldPath.getPathLength() == 1) {
  255. verify(!haveExpr); // haveExpr case handled above.
  256. expr = pExpression;
  257. return;
  258. }
  259. if (!haveExpr)
  260. expr = subObj = ExpressionObject::create();
  261. subObj->addField(fieldPath.tail(), pExpression);
  262. }
  263. */
  264. };
  265. /**
  266. * Add a field path to the set of those to be included.
  267. *
  268. * Note that including a nested field implies including everything on the path leading down to it.
  269. *
  270. * @param fieldPath the name of the field to be included
  271. **/
  272. proto.includePath = function includePath(path){
  273. this.addField(path);
  274. };
  275. /**
  276. * Get a count of the added fields.
  277. *
  278. * @returns how many fields have been added
  279. **/
  280. proto.getFieldCount = function getFieldCount(){
  281. var e; console.warn(e=new Error("CALLER SHOULD BE USING #expressions.length instead!")); console.log(e.stack);
  282. return this._expressions.length;
  283. };
  284. /*TODO: ... remove this?
  285. inline ExpressionObject::BuilderPathSink::BuilderPathSink(
  286. BSONObjBuilder *pB):
  287. pBuilder(pB) {
  288. }
  289. */
  290. /*TODO: ... remove this?
  291. inline ExpressionObject::PathPusher::PathPusher(
  292. vector<string> *pTheVPath, const string &s):
  293. pvPath(pTheVPath) {
  294. pvPath->push_back(s);
  295. }
  296. inline ExpressionObject::PathPusher::~PathPusher() {
  297. pvPath->pop_back();
  298. }
  299. */
  300. /**
  301. * Specialized BSON conversion that allows for writing out a $project specification.
  302. * This creates a standalone object, which must be added to a containing object with a name
  303. *
  304. * @param pBuilder where to write the object to
  305. * @param requireExpression see Expression::addToBsonObj
  306. **/
  307. //TODO: proto.documentToBson = ...?
  308. //TODO: proto.addToBsonObj = ...?
  309. //TODO: proto.addToBsonArray = ...?
  310. /*
  311. /// Visitor abstraction used by emitPaths(). Each path is recorded by calling path().
  312. class PathSink {
  313. public:
  314. virtual ~PathSink() {};
  315. /// Record a path.
  316. /// @param path the dotted path string
  317. /// @param include if true, the path is included; if false, the path is excluded
  318. virtual void path(const string &path, bool include) = 0;
  319. };
  320. /// Utility object for collecting emitPaths() results in a BSON object.
  321. class BuilderPathSink :
  322. public PathSink {
  323. public:
  324. // virtuals from PathSink
  325. virtual void path(const string &path, bool include);
  326. /// Create a PathSink that writes paths to a BSONObjBuilder, to create an object in the form of { path:is_included,...}
  327. /// This object uses a builder pointer that won't guarantee the lifetime of the builder, so make sure it outlasts the use of this for an emitPaths() call.
  328. /// @param pBuilder to the builder to write paths to
  329. BuilderPathSink(BSONObjBuilder *pBuilder);
  330. private:
  331. BSONObjBuilder *pBuilder;
  332. };
  333. /// utility class used by emitPaths()
  334. class PathPusher :
  335. boost::noncopyable {
  336. public:
  337. PathPusher(vector<string> *pvPath, const string &s);
  338. ~PathPusher();
  339. private:
  340. vector<string> *pvPath;
  341. };
  342. */
  343. //void excludeId(bool b) { _excludeId = b; }
  344. return klass;
  345. })();