summaryrefslogtreecommitdiff
path: root/src/mongo/db/pipeline
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/pipeline')
-rwxr-xr-xsrc/mongo/db/pipeline/accumulator.cpp79
-rwxr-xr-xsrc/mongo/db/pipeline/accumulator.h260
-rwxr-xr-xsrc/mongo/db/pipeline/accumulator_add_to_set.cpp79
-rwxr-xr-xsrc/mongo/db/pipeline/accumulator_avg.cpp87
-rwxr-xr-xsrc/mongo/db/pipeline/accumulator_first.cpp49
-rwxr-xr-xsrc/mongo/db/pipeline/accumulator_last.cpp48
-rwxr-xr-xsrc/mongo/db/pipeline/accumulator_min_max.cpp67
-rwxr-xr-xsrc/mongo/db/pipeline/accumulator_push.cpp73
-rwxr-xr-xsrc/mongo/db/pipeline/accumulator_single_value.cpp32
-rwxr-xr-xsrc/mongo/db/pipeline/accumulator_sum.cpp89
-rwxr-xr-xsrc/mongo/db/pipeline/builder.cpp133
-rwxr-xr-xsrc/mongo/db/pipeline/builder.h101
-rwxr-xr-xsrc/mongo/db/pipeline/doc_mem_monitor.cpp68
-rwxr-xr-xsrc/mongo/db/pipeline/doc_mem_monitor.h94
-rwxr-xr-xsrc/mongo/db/pipeline/document.cpp222
-rwxr-xr-xsrc/mongo/db/pipeline/document.h249
-rwxr-xr-xsrc/mongo/db/pipeline/document_source.cpp107
-rwxr-xr-xsrc/mongo/db/pipeline/document_source.h1157
-rwxr-xr-xsrc/mongo/db/pipeline/document_source_bson_array.cpp93
-rw-r--r--src/mongo/db/pipeline/document_source_command_shards.cpp131
-rwxr-xr-xsrc/mongo/db/pipeline/document_source_cursor.cpp228
-rwxr-xr-xsrc/mongo/db/pipeline/document_source_filter.cpp105
-rwxr-xr-xsrc/mongo/db/pipeline/document_source_filter_base.cpp89
-rwxr-xr-xsrc/mongo/db/pipeline/document_source_group.cpp422
-rw-r--r--src/mongo/db/pipeline/document_source_limit.cpp112
-rw-r--r--src/mongo/db/pipeline/document_source_match.cpp106
-rwxr-xr-xsrc/mongo/db/pipeline/document_source_out.cpp67
-rw-r--r--src/mongo/db/pipeline/document_source_project.cpp149
-rw-r--r--src/mongo/db/pipeline/document_source_skip.cpp125
-rwxr-xr-xsrc/mongo/db/pipeline/document_source_sort.cpp222
-rwxr-xr-xsrc/mongo/db/pipeline/document_source_unwind.cpp293
-rw-r--r--src/mongo/db/pipeline/expression.cpp2608
-rwxr-xr-xsrc/mongo/db/pipeline/expression.h1199
-rwxr-xr-xsrc/mongo/db/pipeline/expression_context.cpp57
-rwxr-xr-xsrc/mongo/db/pipeline/expression_context.h91
-rw-r--r--src/mongo/db/pipeline/field_path.cpp101
-rwxr-xr-xsrc/mongo/db/pipeline/field_path.h115
-rw-r--r--src/mongo/db/pipeline/pipeline.cpp477
-rwxr-xr-xsrc/mongo/db/pipeline/pipeline.h225
-rwxr-xr-xsrc/mongo/db/pipeline/pipeline_d.cpp200
-rwxr-xr-xsrc/mongo/db/pipeline/pipeline_d.h62
-rw-r--r--src/mongo/db/pipeline/value.cpp1129
-rwxr-xr-xsrc/mongo/db/pipeline/value.h480
43 files changed, 11880 insertions, 0 deletions
diff --git a/src/mongo/db/pipeline/accumulator.cpp b/src/mongo/db/pipeline/accumulator.cpp
new file mode 100755
index 00000000000..b100154783f
--- /dev/null
+++ b/src/mongo/db/pipeline/accumulator.cpp
@@ -0,0 +1,79 @@
+/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pch.h"
+#include "db/pipeline/accumulator.h"
+
+#include "db/jsobj.h"
+#include "util/mongoutils/str.h"
+
+namespace mongo {
+ using namespace mongoutils;
+
+ void Accumulator::addOperand(
+ const intrusive_ptr<Expression> &pExpression) {
+ uassert(15943, str::stream() << "group accumulator " <<
+ getOpName() << " only accepts one operand",
+ vpOperand.size() < 1);
+
+ ExpressionNary::addOperand(pExpression);
+ }
+
+ Accumulator::Accumulator():
+ ExpressionNary() {
+ }
+
+ void Accumulator::opToBson(
+ BSONObjBuilder *pBuilder, string opName,
+ string fieldName, bool requireExpression) const {
+ verify(vpOperand.size() == 1);
+ BSONObjBuilder builder;
+ vpOperand[0]->addToBsonObj(&builder, opName, requireExpression);
+ pBuilder->append(fieldName, builder.done());
+ }
+
+ void Accumulator::addToBsonObj(
+ BSONObjBuilder *pBuilder, string fieldName,
+ bool requireExpression) const {
+ opToBson(pBuilder, getOpName(), fieldName, requireExpression);
+ }
+
+ void Accumulator::addToBsonArray(BSONArrayBuilder *pBuilder) const {
+ verify(false); // these can't appear in arrays
+ }
+
+ void agg_framework_reservedErrors() {
+ uassert(16030, "reserved error", false);
+ uassert(16031, "reserved error", false);
+ uassert(16032, "reserved error", false);
+ uassert(16033, "reserved error", false);
+
+ uassert(16036, "reserved error", false);
+ uassert(16037, "reserved error", false);
+ uassert(16038, "reserved error", false);
+ uassert(16039, "reserved error", false);
+ uassert(16040, "reserved error", false);
+ uassert(16041, "reserved error", false);
+ uassert(16042, "reserved error", false);
+ uassert(16043, "reserved error", false);
+ uassert(16044, "reserved error", false);
+ uassert(16045, "reserved error", false);
+ uassert(16046, "reserved error", false);
+ uassert(16047, "reserved error", false);
+ uassert(16048, "reserved error", false);
+ uassert(16049, "reserved error", false);
+ }
+}
diff --git a/src/mongo/db/pipeline/accumulator.h b/src/mongo/db/pipeline/accumulator.h
new file mode 100755
index 00000000000..4ca6c94b086
--- /dev/null
+++ b/src/mongo/db/pipeline/accumulator.h
@@ -0,0 +1,260 @@
+/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "mongo/pch.h"
+
+#include <boost/unordered_set.hpp>
+#include "db/pipeline/value.h"
+#include "db/pipeline/expression.h"
+#include "bson/bsontypes.h"
+
+namespace mongo {
+ class ExpressionContext;
+
+ class Accumulator :
+ public ExpressionNary {
+ public:
+ // virtuals from ExpressionNary
+ virtual void addOperand(const intrusive_ptr<Expression> &pExpression);
+ virtual void addToBsonObj(
+ BSONObjBuilder *pBuilder, string fieldName,
+ bool requireExpression) const;
+ virtual void addToBsonArray(BSONArrayBuilder *pBuilder) const;
+
+ /*
+ Get the accumulated value.
+
+ @returns the accumulated value
+ */
+ virtual intrusive_ptr<const Value> getValue() const = 0;
+
+ protected:
+ Accumulator();
+
+ /*
+ Convenience method for doing this for accumulators. The pattern
+ is always the same, so a common implementation works, but requires
+ knowing the operator name.
+
+ @param pBuilder the builder to add to
+ @param fieldName the projected name
+ @param opName the operator name
+ */
+ void opToBson(
+ BSONObjBuilder *pBuilder, string fieldName, string opName,
+ bool requireExpression) const;
+ };
+
+
+ class AccumulatorAddToSet :
+ public Accumulator {
+ public:
+ // virtuals from Expression
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual intrusive_ptr<const Value> getValue() const;
+ virtual const char *getOpName() const;
+
+ /*
+ Create an appending accumulator.
+
+ @param pCtx the expression context
+ @returns the created accumulator
+ */
+ static intrusive_ptr<Accumulator> create(
+ const intrusive_ptr<ExpressionContext> &pCtx);
+
+ private:
+ AccumulatorAddToSet(const intrusive_ptr<ExpressionContext> &pTheCtx);
+ typedef boost::unordered_set<intrusive_ptr<const Value>, Value::Hash > SetType;
+ mutable SetType set;
+ mutable SetType::iterator itr;
+ intrusive_ptr<ExpressionContext> pCtx;
+ };
+
+
+ /*
+ This isn't a finished accumulator, but rather a convenient base class
+ for others such as $first, $last, $max, $min, and similar. It just
+ provides a holder for a single Value, and the getter for that. The
+ holder is protected so derived classes can manipulate it.
+ */
+ class AccumulatorSingleValue :
+ public Accumulator {
+ public:
+ // virtuals from Expression
+ virtual intrusive_ptr<const Value> getValue() const;
+
+ protected:
+ AccumulatorSingleValue();
+
+ mutable intrusive_ptr<const Value> pValue; /* current min/max */
+ };
+
+
+ class AccumulatorFirst :
+ public AccumulatorSingleValue {
+ public:
+ // virtuals from Expression
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual const char *getOpName() const;
+
+ /*
+ Create the accumulator.
+
+ @returns the created accumulator
+ */
+ static intrusive_ptr<Accumulator> create(
+ const intrusive_ptr<ExpressionContext> &pCtx);
+
+ private:
+ AccumulatorFirst();
+ };
+
+
+ class AccumulatorLast :
+ public AccumulatorSingleValue {
+ public:
+ // virtuals from Expression
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual const char *getOpName() const;
+
+ /*
+ Create the accumulator.
+
+ @returns the created accumulator
+ */
+ static intrusive_ptr<Accumulator> create(
+ const intrusive_ptr<ExpressionContext> &pCtx);
+
+ private:
+ AccumulatorLast();
+ };
+
+
+ class AccumulatorSum :
+ public Accumulator {
+ public:
+ // virtuals from Accumulator
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual intrusive_ptr<const Value> getValue() const;
+ virtual const char *getOpName() const;
+
+ /*
+ Create a summing accumulator.
+
+ @param pCtx the expression context
+ @returns the created accumulator
+ */
+ static intrusive_ptr<Accumulator> create(
+ const intrusive_ptr<ExpressionContext> &pCtx);
+
+ protected: /* reused by AccumulatorAvg */
+ AccumulatorSum();
+
+ mutable BSONType totalType;
+ mutable long long longTotal;
+ mutable double doubleTotal;
+ // count is only used by AccumulatorAvg, but lives here to avoid counting non-numeric values
+ mutable long long count;
+ };
+
+
+ class AccumulatorMinMax :
+ public AccumulatorSingleValue {
+ public:
+ // virtuals from Expression
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual const char *getOpName() const;
+
+ /*
+ Create either the max or min accumulator.
+
+ @returns the created accumulator
+ */
+ static intrusive_ptr<Accumulator> createMin(
+ const intrusive_ptr<ExpressionContext> &pCtx);
+ static intrusive_ptr<Accumulator> createMax(
+ const intrusive_ptr<ExpressionContext> &pCtx);
+
+ private:
+ AccumulatorMinMax(int theSense);
+
+ int sense; /* 1 for min, -1 for max; used to "scale" comparison */
+ };
+
+
+ class AccumulatorPush :
+ public Accumulator {
+ public:
+ // virtuals from Expression
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual intrusive_ptr<const Value> getValue() const;
+ virtual const char *getOpName() const;
+
+ /*
+ Create an appending accumulator.
+
+ @param pCtx the expression context
+ @returns the created accumulator
+ */
+ static intrusive_ptr<Accumulator> create(
+ const intrusive_ptr<ExpressionContext> &pCtx);
+
+ private:
+ AccumulatorPush(const intrusive_ptr<ExpressionContext> &pTheCtx);
+
+ mutable vector<intrusive_ptr<const Value> > vpValue;
+ intrusive_ptr<ExpressionContext> pCtx;
+ };
+
+
+ class AccumulatorAvg :
+ public AccumulatorSum {
+ typedef AccumulatorSum Super;
+ public:
+ // virtuals from Accumulator
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual intrusive_ptr<const Value> getValue() const;
+ virtual const char *getOpName() const;
+
+ /*
+ Create an averaging accumulator.
+
+ @param pCtx the expression context
+ @returns the created accumulator
+ */
+ static intrusive_ptr<Accumulator> create(
+ const intrusive_ptr<ExpressionContext> &pCtx);
+
+ private:
+ static const char subTotalName[];
+ static const char countName[];
+
+ AccumulatorAvg(const intrusive_ptr<ExpressionContext> &pCtx);
+
+ intrusive_ptr<ExpressionContext> pCtx;
+ };
+
+}
diff --git a/src/mongo/db/pipeline/accumulator_add_to_set.cpp b/src/mongo/db/pipeline/accumulator_add_to_set.cpp
new file mode 100755
index 00000000000..86d4366ff0c
--- /dev/null
+++ b/src/mongo/db/pipeline/accumulator_add_to_set.cpp
@@ -0,0 +1,79 @@
+/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pch.h"
+#include "accumulator.h"
+
+#include "db/pipeline/expression_context.h"
+#include "db/pipeline/value.h"
+
+namespace mongo {
+ intrusive_ptr<const Value> AccumulatorAddToSet::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ verify(vpOperand.size() == 1);
+ intrusive_ptr<const Value> prhs(vpOperand[0]->evaluate(pDocument));
+
+ if (prhs->getType() == Undefined)
+ ; /* nothing to add to the array */
+ else if (!pCtx->getDoingMerge())
+ set.insert(prhs);
+ else {
+ /*
+ If we're in the router, we need to take apart the arrays we
+ receive and put their elements into the array we are collecting.
+ If we didn't, then we'd get an array of arrays, with one array
+ from each shard that responds.
+ */
+ verify(prhs->getType() == Array);
+
+ intrusive_ptr<ValueIterator> pvi(prhs->getArray());
+ while(pvi->more()) {
+ intrusive_ptr<const Value> pElement(pvi->next());
+ set.insert(pElement);
+ }
+ }
+
+ return Value::getNull();
+ }
+
+ intrusive_ptr<const Value> AccumulatorAddToSet::getValue() const {
+ vector<intrusive_ptr<const Value> > valVec;
+
+ for (itr = set.begin(); itr != set.end(); ++itr) {
+ valVec.push_back(*itr);
+ }
+ /* there is no issue of scope since createArray copy constructs */
+ return Value::createArray(valVec);
+ }
+
+ AccumulatorAddToSet::AccumulatorAddToSet(
+ const intrusive_ptr<ExpressionContext> &pTheCtx):
+ Accumulator(),
+ set(),
+ pCtx(pTheCtx) {
+ }
+
+ intrusive_ptr<Accumulator> AccumulatorAddToSet::create(
+ const intrusive_ptr<ExpressionContext> &pCtx) {
+ intrusive_ptr<AccumulatorAddToSet> pAccumulator(
+ new AccumulatorAddToSet(pCtx));
+ return pAccumulator;
+ }
+
+ const char *AccumulatorAddToSet::getOpName() const {
+ return "$addToSet";
+ }
+}
diff --git a/src/mongo/db/pipeline/accumulator_avg.cpp b/src/mongo/db/pipeline/accumulator_avg.cpp
new file mode 100755
index 00000000000..3e69b204fbb
--- /dev/null
+++ b/src/mongo/db/pipeline/accumulator_avg.cpp
@@ -0,0 +1,87 @@
+/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pch.h"
+#include "accumulator.h"
+
+#include "db/pipeline/document.h"
+#include "db/pipeline/expression_context.h"
+#include "db/pipeline/value.h"
+
+namespace mongo {
+
+ const char AccumulatorAvg::subTotalName[] = "subTotal";
+ const char AccumulatorAvg::countName[] = "count";
+
+ intrusive_ptr<const Value> AccumulatorAvg::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ if (!pCtx->getDoingMerge()) {
+ Super::evaluate(pDocument);
+ }
+ else {
+ /*
+ If we're in the router, we expect an object that contains
+ both a subtotal and a count. This is what getValue() produced
+ below.
+ */
+ intrusive_ptr<const Value> prhs(vpOperand[0]->evaluate(pDocument));
+ verify(prhs->getType() == Object);
+ intrusive_ptr<Document> pShardDoc(prhs->getDocument());
+
+ intrusive_ptr<const Value> pSubTotal(pShardDoc->getValue(subTotalName));
+ verify(pSubTotal.get());
+ doubleTotal += pSubTotal->getDouble();
+
+ intrusive_ptr<const Value> pCount(pShardDoc->getValue(countName));
+ verify(pCount.get());
+ count += pCount->getLong();
+ }
+
+ return Value::getZero();
+ }
+
+ intrusive_ptr<Accumulator> AccumulatorAvg::create(
+ const intrusive_ptr<ExpressionContext> &pCtx) {
+ intrusive_ptr<AccumulatorAvg> pA(new AccumulatorAvg(pCtx));
+ return pA;
+ }
+
+ intrusive_ptr<const Value> AccumulatorAvg::getValue() const {
+ if (!pCtx->getInShard()) {
+ double avg = 0;
+ if (count)
+ avg = doubleTotal / static_cast<double>(count);
+
+ return Value::createDouble(avg);
+ }
+
+ intrusive_ptr<Document> pDocument(Document::create());
+ pDocument->addField(subTotalName, Value::createDouble(doubleTotal));
+ pDocument->addField(countName, Value::createLong(count));
+
+ return Value::createDocument(pDocument);
+ }
+
+ AccumulatorAvg::AccumulatorAvg(
+ const intrusive_ptr<ExpressionContext> &pTheCtx):
+ AccumulatorSum(),
+ pCtx(pTheCtx) {
+ }
+
+ const char *AccumulatorAvg::getOpName() const {
+ return "$avg";
+ }
+}
diff --git a/src/mongo/db/pipeline/accumulator_first.cpp b/src/mongo/db/pipeline/accumulator_first.cpp
new file mode 100755
index 00000000000..53d8f9595e9
--- /dev/null
+++ b/src/mongo/db/pipeline/accumulator_first.cpp
@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pch.h"
+#include "accumulator.h"
+
+#include "db/pipeline/value.h"
+
+namespace mongo {
+
+ intrusive_ptr<const Value> AccumulatorFirst::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ verify(vpOperand.size() == 1);
+
+ /* only remember the first value seen */
+ if (!pValue.get())
+ pValue = vpOperand[0]->evaluate(pDocument);
+
+ return pValue;
+ }
+
+ AccumulatorFirst::AccumulatorFirst():
+ AccumulatorSingleValue() {
+ }
+
+ intrusive_ptr<Accumulator> AccumulatorFirst::create(
+ const intrusive_ptr<ExpressionContext> &pCtx) {
+ intrusive_ptr<AccumulatorFirst> pAccumulator(
+ new AccumulatorFirst());
+ return pAccumulator;
+ }
+
+ const char *AccumulatorFirst::getOpName() const {
+ return "$first";
+ }
+}
diff --git a/src/mongo/db/pipeline/accumulator_last.cpp b/src/mongo/db/pipeline/accumulator_last.cpp
new file mode 100755
index 00000000000..d934e64111b
--- /dev/null
+++ b/src/mongo/db/pipeline/accumulator_last.cpp
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pch.h"
+#include "accumulator.h"
+
+#include "db/pipeline/value.h"
+
+namespace mongo {
+
+ intrusive_ptr<const Value> AccumulatorLast::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ verify(vpOperand.size() == 1);
+
+ /* always remember the last value seen */
+ pValue = vpOperand[0]->evaluate(pDocument);
+
+ return pValue;
+ }
+
+ AccumulatorLast::AccumulatorLast():
+ AccumulatorSingleValue() {
+ }
+
+ intrusive_ptr<Accumulator> AccumulatorLast::create(
+ const intrusive_ptr<ExpressionContext> &pCtx) {
+ intrusive_ptr<AccumulatorLast> pAccumulator(
+ new AccumulatorLast());
+ return pAccumulator;
+ }
+
+ const char *AccumulatorLast::getOpName() const {
+ return "$last";
+ }
+}
diff --git a/src/mongo/db/pipeline/accumulator_min_max.cpp b/src/mongo/db/pipeline/accumulator_min_max.cpp
new file mode 100755
index 00000000000..aec461bab02
--- /dev/null
+++ b/src/mongo/db/pipeline/accumulator_min_max.cpp
@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pch.h"
+#include "accumulator.h"
+
+#include "db/pipeline/value.h"
+
+namespace mongo {
+
+ intrusive_ptr<const Value> AccumulatorMinMax::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ verify(vpOperand.size() == 1);
+ intrusive_ptr<const Value> prhs(vpOperand[0]->evaluate(pDocument));
+
+ /* if this is the first value, just use it */
+ if (!pValue.get())
+ pValue = prhs;
+ else {
+ /* compare with the current value; swap if appropriate */
+ int cmp = Value::compare(pValue, prhs) * sense;
+ if (cmp > 0)
+ pValue = prhs;
+ }
+
+ return pValue;
+ }
+
+ AccumulatorMinMax::AccumulatorMinMax(int theSense):
+ AccumulatorSingleValue(),
+ sense(theSense) {
+ verify((sense == 1) || (sense == -1));
+ }
+
+ intrusive_ptr<Accumulator> AccumulatorMinMax::createMin(
+ const intrusive_ptr<ExpressionContext> &pCtx) {
+ intrusive_ptr<AccumulatorMinMax> pAccumulator(
+ new AccumulatorMinMax(1));
+ return pAccumulator;
+ }
+
+ intrusive_ptr<Accumulator> AccumulatorMinMax::createMax(
+ const intrusive_ptr<ExpressionContext> &pCtx) {
+ intrusive_ptr<AccumulatorMinMax> pAccumulator(
+ new AccumulatorMinMax(-1));
+ return pAccumulator;
+ }
+
+ const char *AccumulatorMinMax::getOpName() const {
+ if (sense == 1)
+ return "$min";
+ return "$max";
+ }
+}
diff --git a/src/mongo/db/pipeline/accumulator_push.cpp b/src/mongo/db/pipeline/accumulator_push.cpp
new file mode 100755
index 00000000000..b7a6370d77f
--- /dev/null
+++ b/src/mongo/db/pipeline/accumulator_push.cpp
@@ -0,0 +1,73 @@
+/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pch.h"
+#include "accumulator.h"
+
+#include "db/pipeline/expression_context.h"
+#include "db/pipeline/value.h"
+
+namespace mongo {
+ intrusive_ptr<const Value> AccumulatorPush::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ verify(vpOperand.size() == 1);
+ intrusive_ptr<const Value> prhs(vpOperand[0]->evaluate(pDocument));
+
+ if (prhs->getType() == Undefined)
+ ; /* nothing to add to the array */
+ else if (!pCtx->getDoingMerge())
+ vpValue.push_back(prhs);
+ else {
+ /*
+ If we're in the router, we need to take apart the arrays we
+ receive and put their elements into the array we are collecting.
+ If we didn't, then we'd get an array of arrays, with one array
+ from each shard that responds.
+ */
+ verify(prhs->getType() == Array);
+
+ intrusive_ptr<ValueIterator> pvi(prhs->getArray());
+ while(pvi->more()) {
+ intrusive_ptr<const Value> pElement(pvi->next());
+ vpValue.push_back(pElement);
+ }
+ }
+
+ return Value::getNull();
+ }
+
+ intrusive_ptr<const Value> AccumulatorPush::getValue() const {
+ return Value::createArray(vpValue);
+ }
+
+ AccumulatorPush::AccumulatorPush(
+ const intrusive_ptr<ExpressionContext> &pTheCtx):
+ Accumulator(),
+ vpValue(),
+ pCtx(pTheCtx) {
+ }
+
+ intrusive_ptr<Accumulator> AccumulatorPush::create(
+ const intrusive_ptr<ExpressionContext> &pCtx) {
+ intrusive_ptr<AccumulatorPush> pAccumulator(
+ new AccumulatorPush(pCtx));
+ return pAccumulator;
+ }
+
+ const char *AccumulatorPush::getOpName() const {
+ return "$push";
+ }
+}
diff --git a/src/mongo/db/pipeline/accumulator_single_value.cpp b/src/mongo/db/pipeline/accumulator_single_value.cpp
new file mode 100755
index 00000000000..ea12ee333a2
--- /dev/null
+++ b/src/mongo/db/pipeline/accumulator_single_value.cpp
@@ -0,0 +1,32 @@
+/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pch.h"
+#include "accumulator.h"
+
+#include "db/pipeline/value.h"
+
+namespace mongo {
+
+ intrusive_ptr<const Value> AccumulatorSingleValue::getValue() const {
+ return pValue;
+ }
+
+ AccumulatorSingleValue::AccumulatorSingleValue():
+ pValue(intrusive_ptr<const Value>()) {
+ }
+
+}
diff --git a/src/mongo/db/pipeline/accumulator_sum.cpp b/src/mongo/db/pipeline/accumulator_sum.cpp
new file mode 100755
index 00000000000..775158c72cf
--- /dev/null
+++ b/src/mongo/db/pipeline/accumulator_sum.cpp
@@ -0,0 +1,89 @@
+/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pch.h"
+#include "accumulator.h"
+
+#include "db/pipeline/value.h"
+
+namespace mongo {
+
+ intrusive_ptr<const Value> AccumulatorSum::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ verify(vpOperand.size() == 1);
+ intrusive_ptr<const Value> prhs(vpOperand[0]->evaluate(pDocument));
+
+ BSONType rhsType = prhs->getType();
+
+ // do nothing with non numeric types
+ if (!(rhsType == NumberInt || rhsType == NumberLong || rhsType == NumberDouble))
+ return Value::getZero();
+
+ // upgrade to the widest type required to hold the result
+ totalType = Value::getWidestNumeric(totalType, rhsType);
+
+ if (totalType == NumberInt || totalType == NumberLong) {
+ long long v = prhs->coerceToLong();
+ longTotal += v;
+ doubleTotal += v;
+ }
+ else if (totalType == NumberDouble) {
+ double v = prhs->coerceToDouble();
+ doubleTotal += v;
+ }
+ else {
+ // non numerics should have returned above so we should never get here
+ verify(false);
+ }
+
+ count++;
+
+ return Value::getZero();
+ }
+
+ intrusive_ptr<Accumulator> AccumulatorSum::create(
+ const intrusive_ptr<ExpressionContext> &pCtx) {
+ intrusive_ptr<AccumulatorSum> pSummer(new AccumulatorSum());
+ return pSummer;
+ }
+
+ intrusive_ptr<const Value> AccumulatorSum::getValue() const {
+ if (totalType == NumberLong) {
+ return Value::createLong(longTotal);
+ }
+ else if (totalType == NumberDouble) {
+ return Value::createDouble(doubleTotal);
+ }
+ else if (totalType == NumberInt) {
+ return Value::createIntOrLong(longTotal);
+ }
+ else {
+ massert(16000, "$sum resulted in a non-numeric type", false);
+ }
+ }
+
+ AccumulatorSum::AccumulatorSum():
+ Accumulator(),
+ totalType(NumberInt),
+ longTotal(0),
+ doubleTotal(0),
+ count(0) {
+ }
+
+ const char *AccumulatorSum::getOpName() const {
+ return "$sum";
+ }
+}
diff --git a/src/mongo/db/pipeline/builder.cpp b/src/mongo/db/pipeline/builder.cpp
new file mode 100755
index 00000000000..8af427d8c9f
--- /dev/null
+++ b/src/mongo/db/pipeline/builder.cpp
@@ -0,0 +1,133 @@
+/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pch.h"
+
+#include "db/jsobj.h"
+#include "db/pipeline/builder.h"
+
+
+namespace mongo {
+
+ void BuilderObj::append() {
+ pBuilder->appendNull(fieldName);
+ }
+
+ void BuilderObj::appendUndefined() {
+ pBuilder->appendUndefined(fieldName);
+ }
+
+ void BuilderObj::append(bool b) {
+ pBuilder->append(fieldName, b);
+ }
+
+ void BuilderObj::append(int i) {
+ pBuilder->append(fieldName, i);
+ }
+
+ void BuilderObj::append(long long ll) {
+ pBuilder->append(fieldName, ll);
+ }
+
+ void BuilderObj::append(double d) {
+ pBuilder->append(fieldName, d);
+ }
+
+ void BuilderObj::append(string s) {
+ pBuilder->append(fieldName, s);
+ }
+
+ void BuilderObj::append(const OID &o) {
+ pBuilder->append(fieldName, o);
+ }
+
+ void BuilderObj::append(const Date_t &d) {
+ pBuilder->append(fieldName, d);
+ }
+
+ void BuilderObj::append(BSONObjBuilder *pDone) {
+ pBuilder->append(fieldName, pDone->done());
+ }
+
+ void BuilderObj::append(BSONArrayBuilder *pDone) {
+ pBuilder->append(fieldName, pDone->arr());
+ }
+
+ void BuilderObj::append(const OpTime& ot) {
+ pBuilder->appendTimestamp(fieldName, ot.getSecs(), ot.getInc());
+ }
+
+ BuilderObj::BuilderObj(
+ BSONObjBuilder *pObjBuilder, string theFieldName):
+ pBuilder(pObjBuilder),
+ fieldName(theFieldName) {
+ }
+
+
+ void BuilderArray::append() {
+ pBuilder->appendNull();
+ }
+
+ void BuilderArray::appendUndefined() {
+ pBuilder->appendUndefined();
+ }
+
+ void BuilderArray::append(bool b) {
+ pBuilder->append(b);
+ }
+
+ void BuilderArray::append(int i) {
+ pBuilder->append(i);
+ }
+
+ void BuilderArray::append(long long ll) {
+ pBuilder->append(ll);
+ }
+
+ void BuilderArray::append(double d) {
+ pBuilder->append(d);
+ }
+
+ void BuilderArray::append(string s) {
+ pBuilder->append(s);
+ }
+
+ void BuilderArray::append(const OID &o) {
+ pBuilder->append(o);
+ }
+
+ void BuilderArray::append(const Date_t &d) {
+ pBuilder->append(d);
+ }
+
+ void BuilderArray::append(BSONObjBuilder *pDone) {
+ pBuilder->append(pDone->done());
+ }
+
+ void BuilderArray::append(BSONArrayBuilder *pDone) {
+ pBuilder->append(pDone->arr());
+ }
+
+ void BuilderArray::append(const OpTime& ot) {
+ pBuilder->appendTimestamp(ot.getSecs(), ot.getInc());
+ }
+
+ BuilderArray::BuilderArray(
+ BSONArrayBuilder *pArrayBuilder):
+ pBuilder(pArrayBuilder) {
+ }
+
+}
diff --git a/src/mongo/db/pipeline/builder.h b/src/mongo/db/pipeline/builder.h
new file mode 100755
index 00000000000..6b6a265aeea
--- /dev/null
+++ b/src/mongo/db/pipeline/builder.h
@@ -0,0 +1,101 @@
+/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "mongo/pch.h"
+
+namespace mongo {
+
+ class BSONArrayBuilder;
+ class BSONObjBuilder;
+
+ /*
+ Generic Builder.
+
+ The methods to append items to an object (on BSONObjBuilder) and an array
+ (on BSONArrayBuilder) differ only by their inclusion of a field name.
+ For more complicated implementations of addToBsonObj() and
+ addToBsonArray(), it makes sense to abstract that out and use
+ this generic builder that always looks the same, and then implement
+ addToBsonObj() and addToBsonArray() by using a common method.
+ */
+ class Builder :
+ boost::noncopyable {
+ public:
+ virtual ~Builder() {};
+
+ virtual void append() = 0; // append a null
+ virtual void appendUndefined() = 0;
+ virtual void append(bool b) = 0;
+ virtual void append(int i) = 0;
+ virtual void append(long long ll) = 0;
+ virtual void append(double d) = 0;
+ virtual void append(string s) = 0;
+ virtual void append(const OID &o) = 0;
+ virtual void append(const Date_t &d) = 0;
+ virtual void append(const OpTime& ot) = 0;
+ virtual void append(BSONObjBuilder *pDone) = 0;
+ virtual void append(BSONArrayBuilder *pDone) = 0;
+ };
+
+ class BuilderObj :
+ public Builder {
+ public:
+ // virtuals from Builder
+ virtual void append();
+ virtual void appendUndefined();
+ virtual void append(bool b);
+ virtual void append(int i);
+ virtual void append(long long ll);
+ virtual void append(double d);
+ virtual void append(string s);
+ virtual void append(const OID &o);
+ virtual void append(const Date_t &d);
+ virtual void append(const OpTime& ot);
+ virtual void append(BSONObjBuilder *pDone);
+ virtual void append(BSONArrayBuilder *pDone);
+
+ BuilderObj(BSONObjBuilder *pBuilder, string fieldName);
+
+ private:
+ BSONObjBuilder *pBuilder;
+ string fieldName;
+ };
+
+ class BuilderArray :
+ public Builder {
+ public:
+ // virtuals from Builder
+ virtual void append();
+ virtual void appendUndefined();
+ virtual void append(bool b);
+ virtual void append(int i);
+ virtual void append(long long ll);
+ virtual void append(double d);
+ virtual void append(string s);
+ virtual void append(const OID &o);
+ virtual void append(const Date_t &d);
+ virtual void append(const OpTime& ot);
+ virtual void append(BSONObjBuilder *pDone);
+ virtual void append(BSONArrayBuilder *pDone);
+
+ BuilderArray(BSONArrayBuilder *pBuilder);
+
+ private:
+ BSONArrayBuilder *pBuilder;
+ };
+}
diff --git a/src/mongo/db/pipeline/doc_mem_monitor.cpp b/src/mongo/db/pipeline/doc_mem_monitor.cpp
new file mode 100755
index 00000000000..3cbe14e8f40
--- /dev/null
+++ b/src/mongo/db/pipeline/doc_mem_monitor.cpp
@@ -0,0 +1,68 @@
+/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pch.h"
+#include "db/pipeline/doc_mem_monitor.h"
+#include "util/systeminfo.h"
+
+namespace mongo {
+
+ DocMemMonitor::DocMemMonitor(StringWriter *pW) {
+ /*
+ Use the default values.
+
+ Currently, we warn in log at 5%, and assert at 10%.
+ */
+ size_t errorRam = SystemInfo::getPhysicalRam() / 10;
+ size_t warnRam = errorRam / 2;
+
+ init(pW, warnRam, errorRam);
+ }
+
+ DocMemMonitor::DocMemMonitor(StringWriter *pW,
+ size_t warnLimit, size_t errorLimit) {
+ init(pW, warnLimit, errorLimit);
+ }
+
+ void DocMemMonitor::addToTotal(size_t amount) {
+ totalUsed += amount;
+
+ if (!warned) {
+ if (warnLimit && (totalUsed > warnLimit)) {
+ stringstream ss;
+ ss << "warning, 5% of physical RAM used for ";
+ pWriter->writeString(ss);
+ ss << endl;
+ warning() << ss.str();
+ warned = true;
+ }
+ }
+
+ if (errorLimit) {
+ uassert(15944, "terminating request: request heap use exceeded 10% of physical RAM", (totalUsed <= errorLimit));
+ }
+ }
+
+ void DocMemMonitor::init(StringWriter *pW,
+ size_t warnLimit, size_t errorLimit) {
+ this->pWriter = pW;
+ this->warnLimit = warnLimit;
+ this->errorLimit = errorLimit;
+
+ warned = false;
+ totalUsed = 0;
+ }
+}
diff --git a/src/mongo/db/pipeline/doc_mem_monitor.h b/src/mongo/db/pipeline/doc_mem_monitor.h
new file mode 100755
index 00000000000..b0f06c32b89
--- /dev/null
+++ b/src/mongo/db/pipeline/doc_mem_monitor.h
@@ -0,0 +1,94 @@
+/**
+ * Copyright 2011 (c) 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "mongo/pch.h"
+#include "util/string_writer.h"
+
+
+namespace mongo {
+
+ /*
+ This utility class provides an easy way to total up, monitor, warn, and
+ signal an error when the amount of memory used for an operation exceeds
+ given thresholds.
+
+ Create a local instance of this class, and then inform it of any memory
+ that you consume using addToTotal().
+
+ Warnings or errors are issued as usage exceeds certain fractions of
+ physical memory on the host, as determined by SystemInfo.
+
+ This class is not guaranteed to warn or signal errors if the host system
+ does not support the ability to report its memory, as per the warnings
+ for SystemInfo in systeminfo.h.
+ */
+ class DocMemMonitor {
+ public:
+ /*
+ Constructor.
+
+ Uses default limits for warnings and errors.
+
+ The StringWriter parameter must outlive the DocMemMonitor instance.
+
+ @param pWriter string writer that provides information about the
+ operation being monitored
+ */
+ DocMemMonitor(StringWriter *pWriter);
+
+ /*
+ Constructor.
+
+ This variant allows explicit selection of the limits. Note that
+ limits of zero are treated as infinite.
+
+ The StringWriter parameter must outlive the DocMemMonitor instance.
+
+ @param pWriter string writer that provides information about the
+ operation being monitored
+ @param warnLimit the amount of ram to issue (log) a warning for
+ @param errorLimit the amount of ram to throw an error for
+ */
+ DocMemMonitor(StringWriter *pWriter, size_t warnLimit,
+ size_t errorLimit);
+
+ /*
+ Increment the total amount of memory used by the given amount. If
+ the warning threshold is exceeded, a warning will be logged. If the
+ error threshold is exceeded, an error will be thrown.
+
+ @param amount the amount of memory to add to the current total
+ */
+ void addToTotal(size_t amount);
+
+ private:
+ /*
+ Real constructor body.
+
+ Provides common construction for all the variant constructors.
+ */
+ void init(StringWriter *pW, size_t warnLimit, size_t errorLimit);
+
+ bool warned;
+ size_t totalUsed;
+ size_t warnLimit;
+ size_t errorLimit;
+ StringWriter *pWriter;
+ };
+
+}
diff --git a/src/mongo/db/pipeline/document.cpp b/src/mongo/db/pipeline/document.cpp
new file mode 100755
index 00000000000..e34af40b8dd
--- /dev/null
+++ b/src/mongo/db/pipeline/document.cpp
@@ -0,0 +1,222 @@
+/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pch.h"
+#include <boost/functional/hash.hpp>
+#include "db/jsobj.h"
+#include "db/pipeline/document.h"
+#include "db/pipeline/value.h"
+#include "util/mongoutils/str.h"
+
+namespace mongo {
+ using namespace mongoutils;
+
+ string Document::idName("_id");
+
+ intrusive_ptr<Document> Document::createFromBsonObj(BSONObj* pBsonObj) {
+ return new Document(pBsonObj);
+ }
+
+ Document::Document(BSONObj* pBsonObj) {
+ const int fields = pBsonObj->nFields();
+ vFieldName.reserve(fields);
+ vpValue.reserve(fields);
+ BSONObjIterator bsonIterator(pBsonObj->begin());
+ while(bsonIterator.more()) {
+ BSONElement bsonElement(bsonIterator.next());
+ string fieldName(bsonElement.fieldName());
+
+ // LATER grovel through structures???
+ intrusive_ptr<const Value> pValue(
+ Value::createFromBsonElement(&bsonElement));
+
+ vFieldName.push_back(fieldName);
+ vpValue.push_back(pValue);
+ }
+ }
+
+ void Document::toBson(BSONObjBuilder* pBuilder) const {
+ const size_t n = vFieldName.size();
+ for(size_t i = 0; i < n; ++i)
+ vpValue[i]->addToBsonObj(pBuilder, vFieldName[i]);
+ }
+
+ intrusive_ptr<Document> Document::create(size_t sizeHint) {
+ intrusive_ptr<Document> pDocument(new Document(sizeHint));
+ return pDocument;
+ }
+
+ Document::Document(size_t sizeHint):
+ vFieldName(),
+ vpValue() {
+ if (sizeHint) {
+ vFieldName.reserve(sizeHint);
+ vpValue.reserve(sizeHint);
+ }
+ }
+
+ intrusive_ptr<Document> Document::clone() {
+ const size_t n = vFieldName.size();
+ intrusive_ptr<Document> pNew(Document::create(n));
+ for(size_t i = 0; i < n; ++i)
+ pNew->addField(vFieldName[i], vpValue[i]);
+
+ return pNew;
+ }
+
+ Document::~Document() {
+ }
+
+ FieldIterator *Document::createFieldIterator() {
+ return new FieldIterator(intrusive_ptr<Document>(this));
+ }
+
+ intrusive_ptr<const Value> Document::getValue(const string &fieldName) {
+ /*
+ For now, assume the number of fields is small enough that iteration
+ is ok. Later, if this gets large, we can create a map into the
+ vector for these lookups.
+
+ Note that because of the schema-less nature of this data, we always
+ have to look, and can't assume that the requested field is always
+ in a particular place as we would with a statically compilable
+ reference.
+ */
+ const size_t n = vFieldName.size();
+ for(size_t i = 0; i < n; ++i) {
+ if (fieldName.compare(vFieldName[i]) == 0)
+ return vpValue[i];
+ }
+
+ return(intrusive_ptr<const Value>());
+ }
+
+ void Document::addField(const string &fieldName,
+ const intrusive_ptr<const Value> &pValue) {
+ vFieldName.push_back(fieldName);
+ vpValue.push_back(pValue);
+ }
+
+ void Document::setField(size_t index,
+ const string &fieldName,
+ const intrusive_ptr<const Value> &pValue) {
+ /* special case: should this field be removed? */
+ if (!pValue.get()) {
+ vFieldName.erase(vFieldName.begin() + index);
+ vpValue.erase(vpValue.begin() + index);
+ return;
+ }
+
+ /* set the indicated field */
+ vFieldName[index] = fieldName;
+ vpValue[index] = pValue;
+ }
+
+ intrusive_ptr<const Value> Document::getField(const string &fieldName) const {
+ const size_t n = vFieldName.size();
+ for(size_t i = 0; i < n; ++i) {
+ if (fieldName.compare(vFieldName[i]) == 0)
+ return vpValue[i];
+ }
+
+ /* if we got here, there's no such field */
+ return intrusive_ptr<const Value>();
+ }
+
+ size_t Document::getApproximateSize() const {
+ size_t size = sizeof(Document);
+ const size_t n = vpValue.size();
+ for(size_t i = 0; i < n; ++i)
+ size += vpValue[i]->getApproximateSize();
+
+ return size;
+ }
+
+ size_t Document::getFieldIndex(const string &fieldName) const {
+ const size_t n = vFieldName.size();
+ size_t i = 0;
+ for(; i < n; ++i) {
+ if (fieldName.compare(vFieldName[i]) == 0)
+ break;
+ }
+
+ return i;
+ }
+
+ void Document::hash_combine(size_t &seed) const {
+ const size_t n = vFieldName.size();
+ for(size_t i = 0; i < n; ++i) {
+ boost::hash_combine(seed, vFieldName[i]);
+ vpValue[i]->hash_combine(seed);
+ }
+ }
+
+ int Document::compare(const intrusive_ptr<Document> &rL,
+ const intrusive_ptr<Document> &rR) {
+ const size_t lSize = rL->vFieldName.size();
+ const size_t rSize = rR->vFieldName.size();
+
+ for(size_t i = 0; true; ++i) {
+ if (i >= lSize) {
+ if (i >= rSize)
+ return 0; // documents are the same length
+
+ return -1; // left document is shorter
+ }
+
+ if (i >= rSize)
+ return 1; // right document is shorter
+
+ const int nameCmp = rL->vFieldName[i].compare(rR->vFieldName[i]);
+ if (nameCmp)
+ return nameCmp; // field names are unequal
+
+ const int valueCmp = Value::compare(rL->vpValue[i], rR->vpValue[i]);
+ if (valueCmp)
+ return valueCmp; // fields are unequal
+ }
+
+ /* NOTREACHED */
+ verify(false);
+ return 0;
+ }
+
+ string Document::toString() const {
+ // this is a temporary hack and it should only be used for debugging
+ BSONObjBuilder bb;
+ toBson(&bb);
+ return bb.done().toString();
+ }
+
+ /* ----------------------- FieldIterator ------------------------------- */
+
+ FieldIterator::FieldIterator(const intrusive_ptr<Document> &pTheDocument):
+ pDocument(pTheDocument),
+ index(0) {
+ }
+
+ bool FieldIterator::more() const {
+ return (index < pDocument->vFieldName.size());
+ }
+
+ pair<string, intrusive_ptr<const Value> > FieldIterator::next() {
+ verify(more());
+ pair<string, intrusive_ptr<const Value> > result(
+ pDocument->vFieldName[index], pDocument->vpValue[index]);
+ ++index;
+ return result;
+ }
+}
diff --git a/src/mongo/db/pipeline/document.h b/src/mongo/db/pipeline/document.h
new file mode 100755
index 00000000000..026ab85e245
--- /dev/null
+++ b/src/mongo/db/pipeline/document.h
@@ -0,0 +1,249 @@
+/**
+ * Copyright 2011 (c) 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "mongo/pch.h"
+
+#include "util/intrusive_counter.h"
+
+namespace mongo {
+ class BSONObj;
+ class FieldIterator;
+ class Value;
+
+ class Document :
+ public IntrusiveCounterUnsigned {
+ public:
+ ~Document();
+
+ /*
+ Create a new Document from the given BSONObj.
+
+ Document field values may be pointed to in the BSONObj, so it
+ must live at least as long as the resulting Document.
+
+ @returns shared pointer to the newly created Document
+ */
+ static intrusive_ptr<Document> createFromBsonObj(BSONObj* pBsonObj);
+
+ /*
+ Create a new empty Document.
+
+ @param sizeHint a hint at what the number of fields will be; if
+ known, this can be used to increase memory allocation efficiency
+ @returns shared pointer to the newly created Document
+ */
+ static intrusive_ptr<Document> create(size_t sizeHint = 0);
+
+ /*
+ Clone a document.
+
+ The new document shares all the fields' values with the original.
+
+ This is not a deep copy. Only the fields on the top-level document
+ are cloned.
+
+ @returns the shallow clone of the document
+ */
+ intrusive_ptr<Document> clone();
+
+ /*
+ Add this document to the BSONObj under construction with the
+ given BSONObjBuilder.
+ */
+ void toBson(BSONObjBuilder *pBsonObjBuilder) const;
+
+ /*
+ Create a new FieldIterator that can be used to examine the
+ Document's fields.
+ */
+ FieldIterator *createFieldIterator();
+
+ /*
+ Get the value of the specified field.
+
+ @param fieldName the name of the field
+ @return point to the requested field
+ */
+ intrusive_ptr<const Value> getValue(const string &fieldName);
+
+ /*
+ Add the given field to the Document.
+
+ BSON documents' fields are ordered; the new Field will be
+ appened to the current list of fields.
+
+ It is an error to add a field that has the same name as another
+ field.
+ */
+ void addField(const string &fieldName,
+ const intrusive_ptr<const Value> &pValue);
+
+ /*
+ Set the given field to be at the specified position in the
+ Document. This will replace any field that is currently in that
+ position. The index must be within the current range of field
+ indices, otherwise behavior is undefined.
+
+ pValue.get() may be NULL, in which case the field will be
+ removed. fieldName is ignored in this case.
+
+ @param index the field index in the list of fields
+ @param fieldName the new field name
+ @param pValue the new Value
+ */
+ void setField(size_t index,
+ const string &fieldName,
+ const intrusive_ptr<const Value> &pValue);
+
+ /*
+ Convenience type for dealing with fields.
+ */
+ typedef pair<string, intrusive_ptr<const Value> > FieldPair;
+
+ /*
+ Get the indicated field.
+
+ @param index the field index in the list of fields
+ @returns the field name and value of the field
+ */
+ FieldPair getField(size_t index) const;
+
+ /*
+ Get the number of fields in the Document.
+
+ @returns the number of fields in the Document
+ */
+ size_t getFieldCount() const;
+
+ /*
+ Get the index of the given field.
+
+ @param fieldName the name of the field
+ @returns the index of the field, or if it does not exist, the number
+ of fields (getFieldCount())
+ */
+ size_t getFieldIndex(const string &fieldName) const;
+
+ /*
+ Get a field by name.
+
+ @param fieldName the name of the field
+ @returns the value of the field
+ */
+ intrusive_ptr<const Value> getField(const string &fieldName) const;
+
+ /*
+ Get the approximate storage size of the document, in bytes.
+
+ Under the assumption that field name strings are shared, they are
+ not included in the total.
+
+ @returns the approximate storage
+ */
+ size_t getApproximateSize() const;
+
+ /*
+ Compare two documents.
+
+ BSON document field order is significant, so this just goes through
+ the fields in order. The comparison is done in roughly the same way
+ as strings are compared, but comparing one field at a time instead
+ of one character at a time.
+ */
+ static int compare(const intrusive_ptr<Document> &rL,
+ const intrusive_ptr<Document> &rR);
+
+ static string idName; // shared "_id"
+
+ /*
+ Calculate a hash value.
+
+ Meant to be used to create composite hashes suitable for
+ boost classes such as unordered_map<>.
+
+ @param seed value to augment with this' hash
+ */
+ void hash_combine(size_t &seed) const;
+
+ // For debugging purposes only!
+ string toString() const;
+
+ private:
+ friend class FieldIterator;
+
+ Document(size_t sizeHint);
+ Document(BSONObj* pBsonObj);
+
+ /* these two vectors parallel each other */
+ vector<string> vFieldName;
+ vector<intrusive_ptr<const Value> > vpValue;
+ };
+
+
+ class FieldIterator :
+ boost::noncopyable {
+ public:
+ /*
+ Ask if there are more fields to return.
+
+ @return true if there are more fields, false otherwise
+ */
+ bool more() const;
+
+ /*
+ Move the iterator to point to the next field and return it.
+
+ @return the next field's <name, Value>
+ */
+ Document::FieldPair next();
+
+ /*
+ Constructor.
+
+ @param pDocument points to the document whose fields are being
+ iterated
+ */
+ FieldIterator(const intrusive_ptr<Document> &pDocument);
+
+ private:
+ friend class Document;
+
+ /*
+ We'll hang on to the original document to ensure we keep the
+ fieldPtr vector alive.
+ */
+ intrusive_ptr<Document> pDocument;
+ size_t index; // current field in iteration
+ };
+}
+
+
+/* ======================= INLINED IMPLEMENTATIONS ========================== */
+
+namespace mongo {
+
+ inline size_t Document::getFieldCount() const {
+ return vFieldName.size();
+ }
+
+ inline Document::FieldPair Document::getField(size_t index) const {
+ verify( index < vFieldName.size() );
+ return FieldPair(vFieldName[index], vpValue[index]);
+ }
+
+}
diff --git a/src/mongo/db/pipeline/document_source.cpp b/src/mongo/db/pipeline/document_source.cpp
new file mode 100755
index 00000000000..045ff4c3726
--- /dev/null
+++ b/src/mongo/db/pipeline/document_source.cpp
@@ -0,0 +1,107 @@
+/**
+* Copyright (C) 2011 10gen Inc.
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License, version 3,
+* as published by the Free Software Foundation.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "pch.h"
+
+#include "db/pipeline/document_source.h"
+#include "db/pipeline/expression_context.h"
+
+namespace mongo {
+
+ DocumentSource::DocumentSource(
+ const intrusive_ptr<ExpressionContext> &pCtx):
+ pSource(NULL),
+ step(-1),
+ pExpCtx(pCtx),
+ nRowsOut(0) {
+ }
+
+ DocumentSource::~DocumentSource() {
+ }
+
+ const char *DocumentSource::getSourceName() const {
+ static const char unknown[] = "[UNKNOWN]";
+ return unknown;
+ }
+
+ void DocumentSource::setSource(DocumentSource *pTheSource) {
+ verify(!pSource);
+ pSource = pTheSource;
+ }
+
+ bool DocumentSource::coalesce(
+ const intrusive_ptr<DocumentSource> &pNextSource) {
+ return false;
+ }
+
+ void DocumentSource::optimize() {
+ }
+
+ bool DocumentSource::advance() {
+ pExpCtx->checkForInterrupt(); // might not return
+ return false;
+ }
+
+ void DocumentSource::dispose() {
+ if ( pSource ) {
+ // This is requried for the DocumentSourceCursor to release its read lock, see
+ // SERVER-6123.
+ pSource->dispose();
+ }
+ }
+
+ void DocumentSource::addToBsonArray(
+ BSONArrayBuilder *pBuilder, bool explain) const {
+ BSONObjBuilder insides;
+ sourceToBson(&insides, explain);
+
+/* No statistics at this time
+ if (explain) {
+ insides.append("nOut", nOut);
+ }
+*/
+
+ pBuilder->append(insides.done());
+ }
+
+ void DocumentSource::writeString(stringstream &ss) const {
+ BSONArrayBuilder bab;
+ addToBsonArray(&bab);
+ BSONArray ba(bab.arr());
+ ss << ba.toString(/* isArray */true);
+ // our toString should use standard string types.....
+ }
+
+ BSONObj DocumentSource::depsToProjection(const set<string>& deps) {
+ BSONObjBuilder bb;
+ if (deps.count("_id") == 0)
+ bb.append("_id", 0);
+
+ string last;
+ for (set<string>::const_iterator it(deps.begin()), end(deps.end()); it!=end; ++it) {
+ if (!last.empty() && str::startsWith(*it, last)) {
+ // we are including a parent of *it so we don't need to
+ // include this field explicitly. In fact, due to
+ // SERVER-6527 if we included this field, the parent
+ // wouldn't be fully included.
+ continue;
+ }
+ last = *it + '.';
+ bb.append(*it, 1);
+ }
+ return bb.obj();
+ }
+}
diff --git a/src/mongo/db/pipeline/document_source.h b/src/mongo/db/pipeline/document_source.h
new file mode 100755
index 00000000000..329564ac490
--- /dev/null
+++ b/src/mongo/db/pipeline/document_source.h
@@ -0,0 +1,1157 @@
+/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "mongo/pch.h"
+
+#include <boost/unordered_map.hpp>
+#include "util/intrusive_counter.h"
+#include "db/clientcursor.h"
+#include "db/jsobj.h"
+#include "db/pipeline/document.h"
+#include "db/pipeline/expression.h"
+#include "mongo/db/pipeline/expression_context.h"
+#include "db/pipeline/value.h"
+#include "util/string_writer.h"
+#include "mongo/db/projection.h"
+
+namespace mongo {
+ class Accumulator;
+ class Cursor;
+ class Document;
+ class Expression;
+ class ExpressionContext;
+ class ExpressionFieldPath;
+ class ExpressionObject;
+ class Matcher;
+ class Shard;
+ class ShardChunkManager;
+
+ class DocumentSource :
+ public IntrusiveCounterUnsigned,
+ public StringWriter {
+ public:
+ virtual ~DocumentSource();
+
+ // virtuals from StringWriter
+ virtual void writeString(stringstream &ss) const;
+
+ /**
+ Set the step for a user-specified pipeline step.
+
+ The step is used for diagnostics.
+
+ @param step step number 0 to n.
+ */
+ void setPipelineStep(int step);
+
+ /**
+ Get the user-specified pipeline step.
+
+ @returns the step number, or -1 if it has never been set
+ */
+ int getPipelineStep() const;
+
+ /**
+ Is the source at EOF?
+
+ @returns true if the source has no more Documents to return.
+ */
+ virtual bool eof() = 0;
+
+ /**
+ Advance the state of the DocumentSource so that it will return the
+ next Document.
+
+ The default implementation returns false, after checking for
+ interrupts. Derived classes can call the default implementation
+ in their own implementations in order to check for interrupts.
+
+ @returns whether there is another document to fetch, i.e., whether or
+ not getCurrent() will succeed. This default implementation always
+ returns false.
+ */
+ virtual bool advance();
+
+ /** @returns the current Document without advancing.
+ *
+ * some implementations do the equivalent of verify(!eof()) so check eof() first
+ */
+ virtual intrusive_ptr<Document> getCurrent() = 0;
+
+ /**
+ * Inform the source that it is no longer needed and may release its resources. After
+ * dispose() is called the source must still be able to handle iteration requests, but may
+ * become eof().
+ * NOTE: For proper mutex yielding, dispose() must be called on any DocumentSource that will
+ * not be advanced until eof(), see SERVER-6123.
+ */
+ virtual void dispose();
+
+ /**
+ Get the source's name.
+
+ @returns the string name of the source as a constant string;
+ this is static, and there's no need to worry about adopting it
+ */
+ virtual const char *getSourceName() const;
+
+ /**
+ Set the underlying source this source should use to get Documents
+ from.
+
+ It is an error to set the source more than once. This is to
+ prevent changing sources once the original source has been started;
+ this could break the state maintained by the DocumentSource.
+
+ This pointer is not reference counted because that has led to
+ some circular references. As a result, this doesn't keep
+ sources alive, and is only intended to be used temporarily for
+ the lifetime of a Pipeline::run().
+
+ @param pSource the underlying source to use
+ */
+ virtual void setSource(DocumentSource *pSource);
+
+ /**
+ Attempt to coalesce this DocumentSource with its successor in the
+ document processing pipeline. If successful, the successor
+ DocumentSource should be removed from the pipeline and discarded.
+
+ If successful, this operation can be applied repeatedly, in an
+ attempt to coalesce several sources together.
+
+ The default implementation is to do nothing, and return false.
+
+ @param pNextSource the next source in the document processing chain.
+ @returns whether or not the attempt to coalesce was successful or not;
+ if the attempt was not successful, nothing has been changed
+ */
+ virtual bool coalesce(const intrusive_ptr<DocumentSource> &pNextSource);
+
+ /**
+ Optimize the pipeline operation, if possible. This is a local
+ optimization that only looks within this DocumentSource. For best
+ results, first coalesce compatible sources using coalesce().
+
+ This is intended for any operations that include expressions, and
+ provides a hook for those to optimize those operations.
+
+ The default implementation is to do nothing.
+ */
+ virtual void optimize();
+
+ enum GetDepsReturn {
+ NOT_SUPPORTED, // This means the set should be ignored
+ EXHAUSTIVE, // This means that everything needed should be in the set
+ SEE_NEXT, // Add the next Source's deps to the set
+ };
+
+ /** Get the fields this operation needs to do its job.
+ * Deps should be in "a.b.c" notation
+ *
+ * @param deps results are added here. NOT CLEARED
+ */
+ virtual GetDepsReturn getDependencies(set<string>& deps) const {
+ return NOT_SUPPORTED;
+ }
+
+ /** This takes dependencies from getDependencies and
+ * returns a projection that includes all of them
+ */
+ static BSONObj depsToProjection(const set<string>& deps);
+
+ /**
+ Add the DocumentSource to the array builder.
+
+ The default implementation calls sourceToBson() in order to
+ convert the inner part of the object which will be added to the
+ array being built here.
+
+ @param pBuilder the array builder to add the operation to.
+ @param explain create explain output
+ */
+ virtual void addToBsonArray(BSONArrayBuilder *pBuilder,
+ bool explain = false) const;
+
+ protected:
+ /**
+ Base constructor.
+ */
+ DocumentSource(const intrusive_ptr<ExpressionContext> &pExpCtx);
+
+ /**
+ Create an object that represents the document source. The object
+ will have a single field whose name is the source's name. This
+ will be used by the default implementation of addToBsonArray()
+ to add this object to a pipeline being represented in BSON.
+
+ @param pBuilder a blank object builder to write to
+ @param explain create explain output
+ */
+ virtual void sourceToBson(BSONObjBuilder *pBuilder,
+ bool explain) const = 0;
+
+ /*
+ Most DocumentSources have an underlying source they get their data
+ from. This is a convenience for them.
+
+ The default implementation of setSource() sets this; if you don't
+ need a source, override that to verify(). The default is to
+ verify() if this has already been set.
+ */
+ DocumentSource *pSource;
+
+ /*
+ The zero-based user-specified pipeline step. Used for diagnostics.
+ Will be set to -1 for artificial pipeline steps that were not part
+ of the original user specification.
+ */
+ int step;
+
+ intrusive_ptr<ExpressionContext> pExpCtx;
+
+ /*
+ for explain: # of rows returned by this source
+
+ This is *not* unsigned so it can be passed to BSONObjBuilder.append().
+ */
+ long long nRowsOut;
+ };
+
+ /** This class marks DocumentSources that should be split between the router and the shards
+ * See Pipeline::splitForSharded() for details
+ */
+ class SplittableDocumentSource : public DocumentSource {
+ public:
+ /** returns a source to be run on the shards.
+ * if NULL, don't run on shards
+ */
+ virtual intrusive_ptr<DocumentSource> getShardSource() = 0;
+
+ /** returns a source that combines results from shards.
+ * if NULL, don't run on router
+ */
+ virtual intrusive_ptr<DocumentSource> getRouterSource() = 0;
+ protected:
+ SplittableDocumentSource(intrusive_ptr<ExpressionContext> ctx) :DocumentSource(ctx) {}
+ };
+
+
+ class DocumentSourceBsonArray :
+ public DocumentSource {
+ public:
+ // virtuals from DocumentSource
+ virtual ~DocumentSourceBsonArray();
+ virtual bool eof();
+ virtual bool advance();
+ virtual intrusive_ptr<Document> getCurrent();
+ virtual void setSource(DocumentSource *pSource);
+
+ /**
+ Create a document source based on a BSON array.
+
+ This is usually put at the beginning of a chain of document sources
+ in order to fetch data from the database.
+
+ CAUTION: the BSON is not read until the source is used. Any
+ elements that appear after these documents must not be read until
+ this source is exhausted.
+
+ @param pBsonElement the BSON array to treat as a document source
+ @param pExpCtx the expression context for the pipeline
+ @returns the newly created document source
+ */
+ static intrusive_ptr<DocumentSourceBsonArray> create(
+ BSONElement *pBsonElement,
+ const intrusive_ptr<ExpressionContext> &pExpCtx);
+
+ protected:
+ // virtuals from DocumentSource
+ virtual void sourceToBson(BSONObjBuilder *pBuilder, bool explain) const;
+
+ private:
+ DocumentSourceBsonArray(BSONElement *pBsonElement,
+ const intrusive_ptr<ExpressionContext> &pExpCtx);
+
+ BSONObj embeddedObject;
+ BSONObjIterator arrayIterator;
+ BSONElement currentElement;
+ bool haveCurrent;
+ };
+
+
+ class DocumentSourceCommandShards :
+ public DocumentSource {
+ public:
+ // virtuals from DocumentSource
+ virtual ~DocumentSourceCommandShards();
+ virtual bool eof();
+ virtual bool advance();
+ virtual intrusive_ptr<Document> getCurrent();
+ virtual void setSource(DocumentSource *pSource);
+
+ /* convenient shorthand for a commonly used type */
+ typedef map<Shard, BSONObj> ShardOutput;
+
+ /**
+ Create a DocumentSource that wraps the output of many shards
+
+ @param shardOutput output from the individual shards
+ @param pExpCtx the expression context for the pipeline
+ @returns the newly created DocumentSource
+ */
+ static intrusive_ptr<DocumentSourceCommandShards> create(
+ const ShardOutput& shardOutput,
+ const intrusive_ptr<ExpressionContext>& pExpCtx);
+
+ protected:
+ // virtuals from DocumentSource
+ virtual void sourceToBson(BSONObjBuilder *pBuilder, bool explain) const;
+
+ private:
+ DocumentSourceCommandShards(const ShardOutput& shardOutput,
+ const intrusive_ptr<ExpressionContext>& pExpCtx);
+
+ /**
+ Advance to the next document, setting pCurrent appropriately.
+
+ Adjusts pCurrent, pBsonSource, and iterator, as needed. On exit,
+ pCurrent is the Document to return, or NULL. If NULL, this
+ indicates there is nothing more to return.
+ */
+ void getNextDocument();
+
+ bool newSource; // set to true for the first item of a new source
+ intrusive_ptr<DocumentSourceBsonArray> pBsonSource;
+ intrusive_ptr<Document> pCurrent;
+ ShardOutput::const_iterator iterator;
+ ShardOutput::const_iterator listEnd;
+ };
+
+
+ /**
+ * Constructs and returns Documents from the BSONObj objects produced by a supplied Cursor.
+ * An object of this type may only be used by one thread, see SERVER-6123.
+ */
+ class DocumentSourceCursor :
+ public DocumentSource {
+ public:
+ /**
+ * Holds a Cursor and all associated state required to access the cursor. An object of this
+ * type may only be used by one thread.
+ */
+ struct CursorWithContext {
+ /** Takes a read lock that will be held for the lifetime of the object. */
+ CursorWithContext( const string& ns );
+
+ // Must be the first struct member for proper construction and destruction, as other
+ // members may depend on the read lock it acquires.
+ Client::ReadContext _readContext;
+ shared_ptr<ShardChunkManager> _chunkMgr;
+ ClientCursor::Holder _cursor;
+ };
+
+ // virtuals from DocumentSource
+ virtual ~DocumentSourceCursor();
+ virtual bool eof();
+ virtual bool advance();
+ virtual intrusive_ptr<Document> getCurrent();
+ virtual void setSource(DocumentSource *pSource);
+
+ /**
+ * Release the Cursor and the read lock it requires, but without changing the other data.
+ * Releasing the lock is required for proper concurrency, see SERVER-6123. This
+ * functionality is also used by the explain version of pipeline execution.
+ */
+ virtual void dispose();
+
+ /**
+ Create a document source based on a cursor.
+
+ This is usually put at the beginning of a chain of document sources
+ in order to fetch data from the database.
+
+ @param pCursor the cursor to use to fetch data
+ @param pExpCtx the expression context for the pipeline
+ */
+ static intrusive_ptr<DocumentSourceCursor> create(
+ const shared_ptr<CursorWithContext>& cursorWithContext,
+ const intrusive_ptr<ExpressionContext> &pExpCtx);
+
+ /*
+ Record the namespace. Required for explain.
+
+ @param namespace the namespace
+ */
+ void setNamespace(const string &ns);
+
+ /*
+ Record the query that was specified for the cursor this wraps, if
+ any.
+
+ This should be captured after any optimizations are applied to
+ the pipeline so that it reflects what is really used.
+
+ This gets used for explain output.
+
+ @param pBsonObj the query to record
+ */
+ void setQuery(const shared_ptr<BSONObj> &pBsonObj);
+
+ /*
+ Record the sort that was specified for the cursor this wraps, if
+ any.
+
+ This should be captured after any optimizations are applied to
+ the pipeline so that it reflects what is really used.
+
+ This gets used for explain output.
+
+ @param pBsonObj the sort to record
+ */
+ void setSort(const shared_ptr<BSONObj> &pBsonObj);
+
+ void setProjection(BSONObj projection);
+ protected:
+ // virtuals from DocumentSource
+ virtual void sourceToBson(BSONObjBuilder *pBuilder, bool explain) const;
+
+ private:
+ DocumentSourceCursor(
+ const shared_ptr<CursorWithContext>& cursorWithContext,
+ const intrusive_ptr<ExpressionContext> &pExpCtx);
+
+ void findNext();
+
+ intrusive_ptr<Document> pCurrent;
+
+ string ns; // namespace
+
+ /*
+ The bson dependencies must outlive the Cursor wrapped by this
+ source. Therefore, bson dependencies must appear before pCursor
+ in order cause its destructor to be called *after* pCursor's.
+ */
+ shared_ptr<BSONObj> pQuery;
+ shared_ptr<BSONObj> pSort;
+ shared_ptr<Projection> _projection; // shared with pClientCursor
+
+ shared_ptr<CursorWithContext> _cursorWithContext;
+
+ ClientCursor::Holder& cursor();
+ const ShardChunkManager* chunkMgr() { return _cursorWithContext->_chunkMgr.get(); }
+
+ bool canUseCoveredIndex();
+
+ /*
+ Yield the cursor sometimes.
+
+ If the state of the world changed during the yield such that we
+ are unable to continue execution of the query, this will release the
+ client cursor, and throw an error. NOTE This differs from the
+ behavior of most other operations, see SERVER-2454.
+ */
+ void yieldSometimes();
+ };
+
+
+ /*
+ This contains all the basic mechanics for filtering a stream of
+ Documents, except for the actual predicate evaluation itself. This was
+ factored out so we could create DocumentSources that use both Matcher
+ style predicates as well as full Expressions.
+ */
+ class DocumentSourceFilterBase :
+ public DocumentSource {
+ public:
+ // virtuals from DocumentSource
+ virtual ~DocumentSourceFilterBase();
+ virtual bool eof();
+ virtual bool advance();
+ virtual intrusive_ptr<Document> getCurrent();
+
+ /**
+ Create a BSONObj suitable for Matcher construction.
+
+ This is used after filter analysis has moved as many filters to
+ as early a point as possible in the document processing pipeline.
+ See db/Matcher.h and the associated wiki documentation for the
+ format. This conversion is used to move back to the low-level
+ find() Cursor mechanism.
+
+ @param pBuilder the builder to write to
+ */
+ virtual void toMatcherBson(BSONObjBuilder *pBuilder) const = 0;
+
+ protected:
+ DocumentSourceFilterBase(
+ const intrusive_ptr<ExpressionContext> &pExpCtx);
+
+ /**
+ Test the given document against the predicate and report if it
+ should be accepted or not.
+
+ @param pDocument the document to test
+ @returns true if the document matches the filter, false otherwise
+ */
+ virtual bool accept(const intrusive_ptr<Document> &pDocument) const = 0;
+
+ private:
+
+ void findNext();
+
+ bool unstarted;
+ bool hasNext;
+ intrusive_ptr<Document> pCurrent;
+ };
+
+
+ class DocumentSourceFilter :
+ public DocumentSourceFilterBase {
+ public:
+ // virtuals from DocumentSource
+ virtual ~DocumentSourceFilter();
+ virtual bool coalesce(const intrusive_ptr<DocumentSource> &pNextSource);
+ virtual void optimize();
+ virtual const char *getSourceName() const;
+
+ /**
+ Create a filter.
+
+ @param pBsonElement the raw BSON specification for the filter
+ @param pExpCtx the expression context for the pipeline
+ @returns the filter
+ */
+ static intrusive_ptr<DocumentSource> createFromBson(
+ BSONElement *pBsonElement,
+ const intrusive_ptr<ExpressionContext> &pExpCtx);
+
+ /**
+ Create a filter.
+
+ @param pFilter the expression to use to filter
+ @param pExpCtx the expression context for the pipeline
+ @returns the filter
+ */
+ static intrusive_ptr<DocumentSourceFilter> create(
+ const intrusive_ptr<Expression> &pFilter,
+ const intrusive_ptr<ExpressionContext> &pExpCtx);
+
+ /**
+ Create a BSONObj suitable for Matcher construction.
+
+ This is used after filter analysis has moved as many filters to
+ as early a point as possible in the document processing pipeline.
+ See db/Matcher.h and the associated wiki documentation for the
+ format. This conversion is used to move back to the low-level
+ find() Cursor mechanism.
+
+ @param pBuilder the builder to write to
+ */
+ void toMatcherBson(BSONObjBuilder *pBuilder) const;
+
+ static const char filterName[];
+
+ protected:
+ // virtuals from DocumentSource
+ virtual void sourceToBson(BSONObjBuilder *pBuilder, bool explain) const;
+
+ // virtuals from DocumentSourceFilterBase
+ virtual bool accept(const intrusive_ptr<Document> &pDocument) const;
+
+ private:
+ DocumentSourceFilter(const intrusive_ptr<Expression> &pFilter,
+ const intrusive_ptr<ExpressionContext> &pExpCtx);
+
+ intrusive_ptr<Expression> pFilter;
+ };
+
+
+ class DocumentSourceGroup :
+ public SplittableDocumentSource {
+ public:
+ // virtuals from DocumentSource
+ virtual ~DocumentSourceGroup();
+ virtual bool eof();
+ virtual bool advance();
+ virtual const char *getSourceName() const;
+ virtual intrusive_ptr<Document> getCurrent();
+ virtual GetDepsReturn getDependencies(set<string>& deps) const;
+
+ /**
+ Create a new grouping DocumentSource.
+
+ @param pExpCtx the expression context for the pipeline
+ @returns the DocumentSource
+ */
+ static intrusive_ptr<DocumentSourceGroup> create(
+ const intrusive_ptr<ExpressionContext> &pExpCtx);
+
+ /**
+ Set the Id Expression.
+
+ Documents that pass through the grouping Document are grouped
+ according to this key. This will generate the id_ field in the
+ result documents.
+
+ @param pExpression the group key
+ */
+ void setIdExpression(const intrusive_ptr<Expression> &pExpression);
+
+ /**
+ Add an accumulator.
+
+ Accumulators become fields in the Documents that result from
+ grouping. Each unique group document must have it's own
+ accumulator; the accumulator factory is used to create that.
+
+ @param fieldName the name the accumulator result will have in the
+ result documents
+ @param pAccumulatorFactory used to create the accumulator for the
+ group field
+ */
+ void addAccumulator(string fieldName,
+ intrusive_ptr<Accumulator> (*pAccumulatorFactory)(
+ const intrusive_ptr<ExpressionContext> &),
+ const intrusive_ptr<Expression> &pExpression);
+
+ /**
+ Create a grouping DocumentSource from BSON.
+
+ This is a convenience method that uses the above, and operates on
+ a BSONElement that has been deteremined to be an Object with an
+ element named $group.
+
+ @param pBsonElement the BSONELement that defines the group
+ @param pExpCtx the expression context
+ @returns the grouping DocumentSource
+ */
+ static intrusive_ptr<DocumentSource> createFromBson(
+ BSONElement *pBsonElement,
+ const intrusive_ptr<ExpressionContext> &pExpCtx);
+
+ // Virtuals for SplittableDocumentSource
+ virtual intrusive_ptr<DocumentSource> getShardSource();
+ virtual intrusive_ptr<DocumentSource> getRouterSource();
+
+ static const char groupName[];
+
+ protected:
+ // virtuals from DocumentSource
+ virtual void sourceToBson(BSONObjBuilder *pBuilder, bool explain) const;
+
+ private:
+ DocumentSourceGroup(const intrusive_ptr<ExpressionContext> &pExpCtx);
+
+ /*
+ Before returning anything, this source must fetch everything from
+ the underlying source and group it. populate() is used to do that
+ on the first call to any method on this source. The populated
+ boolean indicates that this has been done.
+ */
+ void populate();
+ bool populated;
+
+ intrusive_ptr<Expression> pIdExpression;
+
+ typedef boost::unordered_map<intrusive_ptr<const Value>,
+ vector<intrusive_ptr<Accumulator> >, Value::Hash> GroupsType;
+ GroupsType groups;
+
+ /*
+ The field names for the result documents and the accumulator
+ factories for the result documents. The Expressions are the
+ common expressions used by each instance of each accumulator
+ in order to find the right-hand side of what gets added to the
+ accumulator. Note that each of those is the same for each group,
+ so we can share them across all groups by adding them to the
+ accumulators after we use the factories to make a new set of
+ accumulators for each new group.
+
+ These three vectors parallel each other.
+ */
+ vector<string> vFieldName;
+ vector<intrusive_ptr<Accumulator> (*)(
+ const intrusive_ptr<ExpressionContext> &)> vpAccumulatorFactory;
+ vector<intrusive_ptr<Expression> > vpExpression;
+
+
+ intrusive_ptr<Document> makeDocument(
+ const GroupsType::iterator &rIter);
+
+ GroupsType::iterator groupsIterator;
+ intrusive_ptr<Document> pCurrent;
+ };
+
+
+ class DocumentSourceMatch :
+ public DocumentSourceFilterBase {
+ public:
+ // virtuals from DocumentSource
+ virtual ~DocumentSourceMatch();
+ virtual const char *getSourceName() const;
+
+ /**
+ Create a filter.
+
+ @param pBsonElement the raw BSON specification for the filter
+ @returns the filter
+ */
+ static intrusive_ptr<DocumentSource> createFromBson(
+ BSONElement *pBsonElement,
+ const intrusive_ptr<ExpressionContext> &pCtx);
+
+ /**
+ Create a BSONObj suitable for Matcher construction.
+
+ This is used after filter analysis has moved as many filters to
+ as early a point as possible in the document processing pipeline.
+ See db/Matcher.h and the associated wiki documentation for the
+ format. This conversion is used to move back to the low-level
+ find() Cursor mechanism.
+
+ @param pBuilder the builder to write to
+ */
+ void toMatcherBson(BSONObjBuilder *pBuilder) const;
+
+ static const char matchName[];
+
+ protected:
+ // virtuals from DocumentSource
+ virtual void sourceToBson(BSONObjBuilder *pBuilder, bool explain) const;
+
+ // virtuals from DocumentSourceFilterBase
+ virtual bool accept(const intrusive_ptr<Document> &pDocument) const;
+
+ private:
+ DocumentSourceMatch(const BSONObj &query,
+ const intrusive_ptr<ExpressionContext> &pExpCtx);
+
+ Matcher matcher;
+ };
+
+
+ class DocumentSourceOut :
+ public DocumentSource {
+ public:
+ // virtuals from DocumentSource
+ virtual ~DocumentSourceOut();
+ virtual bool eof();
+ virtual bool advance();
+ virtual const char *getSourceName() const;
+ virtual intrusive_ptr<Document> getCurrent();
+
+ /**
+ Create a document source for output and pass-through.
+
+ This can be put anywhere in a pipeline and will store content as
+ well as pass it on.
+
+ @param pBsonElement the raw BSON specification for the source
+ @param pExpCtx the expression context for the pipeline
+ @returns the newly created document source
+ */
+ static intrusive_ptr<DocumentSourceOut> createFromBson(
+ BSONElement *pBsonElement,
+ const intrusive_ptr<ExpressionContext> &pExpCtx);
+
+ static const char outName[];
+
+ protected:
+ // virtuals from DocumentSource
+ virtual void sourceToBson(BSONObjBuilder *pBuilder, bool explain) const;
+
+ private:
+ DocumentSourceOut(BSONElement *pBsonElement,
+ const intrusive_ptr<ExpressionContext> &pExpCtx);
+ };
+
+
+ class DocumentSourceProject :
+ public DocumentSource {
+ public:
+ // virtuals from DocumentSource
+ virtual ~DocumentSourceProject();
+ virtual bool eof();
+ virtual bool advance();
+ virtual const char *getSourceName() const;
+ virtual intrusive_ptr<Document> getCurrent();
+ virtual void optimize();
+
+ virtual GetDepsReturn getDependencies(set<string>& deps) const;
+
+ /**
+ Create a new projection DocumentSource from BSON.
+
+ This is a convenience for directly handling BSON, and relies on the
+ above methods.
+
+ @param pBsonElement the BSONElement with an object named $project
+ @param pExpCtx the expression context for the pipeline
+ @returns the created projection
+ */
+ static intrusive_ptr<DocumentSource> createFromBson(
+ BSONElement *pBsonElement,
+ const intrusive_ptr<ExpressionContext> &pExpCtx);
+
+ static const char projectName[];
+
+ /** projection as specified by the user */
+ BSONObj getRaw() const { return _raw; }
+
+ protected:
+ // virtuals from DocumentSource
+ virtual void sourceToBson(BSONObjBuilder *pBuilder, bool explain) const;
+
+ private:
+ DocumentSourceProject(const intrusive_ptr<ExpressionContext> &pExpCtx);
+
+ // configuration state
+ intrusive_ptr<ExpressionObject> pEO;
+ BSONObj _raw;
+
+#if defined(_DEBUG)
+ // this is used in DEBUG builds to ensure we are compatible
+ Projection _simpleProjection;
+#endif
+ };
+
+
+ class DocumentSourceSort :
+ public SplittableDocumentSource {
+ public:
+ // virtuals from DocumentSource
+ virtual ~DocumentSourceSort();
+ virtual bool eof();
+ virtual bool advance();
+ virtual const char *getSourceName() const;
+ virtual intrusive_ptr<Document> getCurrent();
+
+ virtual GetDepsReturn getDependencies(set<string>& deps) const;
+
+ /*
+ TODO
+ Adjacent sorts should reduce to the last sort.
+ virtual bool coalesce(const intrusive_ptr<DocumentSource> &pNextSource);
+ */
+
+ /**
+ Create a new sorting DocumentSource.
+
+ @param pExpCtx the expression context for the pipeline
+ @returns the DocumentSource
+ */
+ static intrusive_ptr<DocumentSourceSort> create(
+ const intrusive_ptr<ExpressionContext> &pExpCtx);
+
+ // Virtuals for SplittableDocumentSource
+ // All work for sort is done in router currently
+ // TODO: do partial sorts on the shards then merge in the router
+ // Not currently possible due to DocumentSource's cursor-like interface
+ virtual intrusive_ptr<DocumentSource> getShardSource() { return NULL; }
+ virtual intrusive_ptr<DocumentSource> getRouterSource() { return this; }
+
+ /**
+ Add sort key field.
+
+ Adds a sort key field to the key being built up. A concatenated
+ key is built up by calling this repeatedly.
+
+ @param fieldPath the field path to the key component
+ @param ascending if true, use the key for an ascending sort,
+ otherwise, use it for descending
+ */
+ void addKey(const string &fieldPath, bool ascending);
+
+ /**
+ Write out an object whose contents are the sort key.
+
+ @param pBuilder initialized object builder.
+ @param fieldPrefix specify whether or not to include the field prefix
+ */
+ void sortKeyToBson(BSONObjBuilder *pBuilder, bool usePrefix) const;
+
+ /**
+ Create a sorting DocumentSource from BSON.
+
+ This is a convenience method that uses the above, and operates on
+ a BSONElement that has been deteremined to be an Object with an
+ element named $group.
+
+ @param pBsonElement the BSONELement that defines the group
+ @param pExpCtx the expression context for the pipeline
+ @returns the grouping DocumentSource
+ */
+ static intrusive_ptr<DocumentSource> createFromBson(
+ BSONElement *pBsonElement,
+ const intrusive_ptr<ExpressionContext> &pExpCtx);
+
+
+ static const char sortName[];
+
+ protected:
+ // virtuals from DocumentSource
+ virtual void sourceToBson(BSONObjBuilder *pBuilder, bool explain) const;
+
+ private:
+ DocumentSourceSort(const intrusive_ptr<ExpressionContext> &pExpCtx);
+
+ /*
+ Before returning anything, this source must fetch everything from
+ the underlying source and group it. populate() is used to do that
+ on the first call to any method on this source. The populated
+ boolean indicates that this has been done.
+ */
+ void populate();
+ bool populated;
+
+ /* these two parallel each other */
+ typedef vector<intrusive_ptr<ExpressionFieldPath> > SortPaths;
+ SortPaths vSortKey;
+ vector<bool> vAscending;
+
+ /*
+ Compare two documents according to the specified sort key.
+
+ @param rL reference to the left document
+ @param rR reference to the right document
+ @returns a number less than, equal to, or greater than zero,
+ indicating pL < pR, pL == pR, or pL > pR, respectively
+ */
+ int compare(const intrusive_ptr<Document> &pL,
+ const intrusive_ptr<Document> &pR);
+
+ /*
+ This is a utility class just for the STL sort that is done
+ inside.
+ */
+ class Comparator {
+ public:
+ bool operator()(
+ const intrusive_ptr<Document> &pL,
+ const intrusive_ptr<Document> &pR) {
+ return (pSort->compare(pL, pR) < 0);
+ }
+
+ inline Comparator(DocumentSourceSort *pS):
+ pSort(pS) {
+ }
+
+ private:
+ DocumentSourceSort *pSort;
+ };
+
+ typedef vector<intrusive_ptr<Document> > VectorType;
+ VectorType documents;
+
+ VectorType::iterator docIterator;
+ intrusive_ptr<Document> pCurrent;
+ };
+
+
+ class DocumentSourceLimit :
+ public DocumentSource {
+ public:
+ // virtuals from DocumentSource
+ virtual ~DocumentSourceLimit();
+ virtual bool eof();
+ virtual bool advance();
+ virtual intrusive_ptr<Document> getCurrent();
+ virtual const char *getSourceName() const;
+ virtual bool coalesce(const intrusive_ptr<DocumentSource> &pNextSource);
+
+ virtual GetDepsReturn getDependencies(set<string>& deps) const {
+ return SEE_NEXT; // This doesn't affect needed fields
+ }
+
+ /**
+ Create a new limiting DocumentSource.
+
+ @param pExpCtx the expression context for the pipeline
+ @returns the DocumentSource
+ */
+ static intrusive_ptr<DocumentSourceLimit> create(
+ const intrusive_ptr<ExpressionContext> &pExpCtx);
+
+ /**
+ Create a limiting DocumentSource from BSON.
+
+ This is a convenience method that uses the above, and operates on
+ a BSONElement that has been deteremined to be an Object with an
+ element named $limit.
+
+ @param pBsonElement the BSONELement that defines the limit
+ @param pExpCtx the expression context
+ @returns the grouping DocumentSource
+ */
+ static intrusive_ptr<DocumentSource> createFromBson(
+ BSONElement *pBsonElement,
+ const intrusive_ptr<ExpressionContext> &pExpCtx);
+
+ static const char limitName[];
+
+ protected:
+ // virtuals from DocumentSource
+ virtual void sourceToBson(BSONObjBuilder *pBuilder, bool explain) const;
+
+ private:
+ DocumentSourceLimit(
+ const intrusive_ptr<ExpressionContext> &pExpCtx);
+
+ long long limit;
+ long long count;
+ intrusive_ptr<Document> pCurrent;
+ };
+
+ class DocumentSourceSkip :
+ public DocumentSource {
+ public:
+ // virtuals from DocumentSource
+ virtual ~DocumentSourceSkip();
+ virtual bool eof();
+ virtual bool advance();
+ virtual intrusive_ptr<Document> getCurrent();
+ virtual const char *getSourceName() const;
+ virtual bool coalesce(const intrusive_ptr<DocumentSource> &pNextSource);
+
+ virtual GetDepsReturn getDependencies(set<string>& deps) const {
+ return SEE_NEXT; // This doesn't affect needed fields
+ }
+
+ /**
+ Create a new skipping DocumentSource.
+
+ @param pExpCtx the expression context
+ @returns the DocumentSource
+ */
+ static intrusive_ptr<DocumentSourceSkip> create(
+ const intrusive_ptr<ExpressionContext> &pExpCtx);
+
+ /**
+ Create a skipping DocumentSource from BSON.
+
+ This is a convenience method that uses the above, and operates on
+ a BSONElement that has been deteremined to be an Object with an
+ element named $skip.
+
+ @param pBsonElement the BSONELement that defines the skip
+ @param pExpCtx the expression context
+ @returns the grouping DocumentSource
+ */
+ static intrusive_ptr<DocumentSource> createFromBson(
+ BSONElement *pBsonElement,
+ const intrusive_ptr<ExpressionContext> &pExpCtx);
+
+ static const char skipName[];
+
+ protected:
+ // virtuals from DocumentSource
+ virtual void sourceToBson(BSONObjBuilder *pBuilder, bool explain) const;
+
+ private:
+ DocumentSourceSkip(const intrusive_ptr<ExpressionContext> &pExpCtx);
+
+ /*
+ Skips initial documents.
+ */
+ void skipper();
+
+ long long skip;
+ long long count;
+ intrusive_ptr<Document> pCurrent;
+ };
+
+
+ class DocumentSourceUnwind :
+ public DocumentSource {
+ public:
+ // virtuals from DocumentSource
+ virtual ~DocumentSourceUnwind();
+ virtual bool eof();
+ virtual bool advance();
+ virtual const char *getSourceName() const;
+ virtual intrusive_ptr<Document> getCurrent();
+
+ virtual GetDepsReturn getDependencies(set<string>& deps) const;
+
+ /**
+ Create a new projection DocumentSource from BSON.
+
+ This is a convenience for directly handling BSON, and relies on the
+ above methods.
+
+ @param pBsonElement the BSONElement with an object named $project
+ @param pExpCtx the expression context for the pipeline
+ @returns the created projection
+ */
+ static intrusive_ptr<DocumentSource> createFromBson(
+ BSONElement *pBsonElement,
+ const intrusive_ptr<ExpressionContext> &pExpCtx);
+
+ static const char unwindName[];
+
+ protected:
+ // virtuals from DocumentSource
+ virtual void sourceToBson(BSONObjBuilder *pBuilder, bool explain) const;
+
+ private:
+ DocumentSourceUnwind(const intrusive_ptr<ExpressionContext> &pExpCtx);
+
+ /**
+ * Lazily construct the _unwinder and initialize the iterator state of this DocumentSource.
+ * To be called by all members that depend on the iterator state.
+ */
+ void lazyInit();
+
+ /**
+ * If the _unwinder is exhausted and the source may be advanced, advance the pSource and
+ * reset the _unwinder's source document.
+ */
+ void mayAdvanceSource();
+
+ /** Specify the field to unwind. */
+ void unwindPath(const FieldPath &fieldPath);
+
+ // Configuration state.
+ scoped_ptr<FieldPath> _unwindPath;
+
+ // Iteration state.
+ class Unwinder;
+ scoped_ptr<Unwinder> _unwinder;
+ };
+
+}
+
+
+/* ======================= INLINED IMPLEMENTATIONS ========================== */
+
+namespace mongo {
+
+ inline void DocumentSource::setPipelineStep(int s) {
+ step = s;
+ }
+
+ inline int DocumentSource::getPipelineStep() const {
+ return step;
+ }
+
+ inline void DocumentSourceGroup::setIdExpression(
+ const intrusive_ptr<Expression> &pExpression) {
+ pIdExpression = pExpression;
+ }
+}
diff --git a/src/mongo/db/pipeline/document_source_bson_array.cpp b/src/mongo/db/pipeline/document_source_bson_array.cpp
new file mode 100755
index 00000000000..79bce29220a
--- /dev/null
+++ b/src/mongo/db/pipeline/document_source_bson_array.cpp
@@ -0,0 +1,93 @@
+/**
+ * Copyright 2011 (c) 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pch.h"
+
+#include "db/pipeline/document_source.h"
+#include "db/pipeline/document.h"
+
+
+namespace mongo {
+
+ DocumentSourceBsonArray::~DocumentSourceBsonArray() {
+ }
+
+ bool DocumentSourceBsonArray::eof() {
+ return !haveCurrent;
+ }
+
+ bool DocumentSourceBsonArray::advance() {
+ DocumentSource::advance(); // check for interrupts
+
+ if (eof())
+ return false;
+
+ if (!arrayIterator.more()) {
+ haveCurrent = false;
+ return false;
+ }
+
+ currentElement = arrayIterator.next();
+ return true;
+ }
+
+ intrusive_ptr<Document> DocumentSourceBsonArray::getCurrent() {
+ verify(haveCurrent);
+ BSONObj documentObj(currentElement.Obj());
+ intrusive_ptr<Document> pDocument(
+ Document::createFromBsonObj(&documentObj));
+ return pDocument;
+ }
+
+ void DocumentSourceBsonArray::setSource(DocumentSource *pSource) {
+ /* this doesn't take a source */
+ verify(false);
+ }
+
+ DocumentSourceBsonArray::DocumentSourceBsonArray(
+ BSONElement *pBsonElement,
+ const intrusive_ptr<ExpressionContext> &pExpCtx):
+ DocumentSource(pExpCtx),
+ embeddedObject(pBsonElement->embeddedObject()),
+ arrayIterator(embeddedObject),
+ haveCurrent(false) {
+ if (arrayIterator.more()) {
+ currentElement = arrayIterator.next();
+ haveCurrent = true;
+ }
+ }
+
+ intrusive_ptr<DocumentSourceBsonArray> DocumentSourceBsonArray::create(
+ BSONElement *pBsonElement,
+ const intrusive_ptr<ExpressionContext> &pExpCtx) {
+
+ verify(pBsonElement->type() == Array);
+ intrusive_ptr<DocumentSourceBsonArray> pSource(
+ new DocumentSourceBsonArray(pBsonElement, pExpCtx));
+
+ return pSource;
+ }
+
+ void DocumentSourceBsonArray::sourceToBson(
+ BSONObjBuilder *pBuilder, bool explain) const {
+
+ if (explain) {
+ BSONObj empty;
+
+ pBuilder->append("bsonArray", empty);
+ }
+ }
+}
diff --git a/src/mongo/db/pipeline/document_source_command_shards.cpp b/src/mongo/db/pipeline/document_source_command_shards.cpp
new file mode 100644
index 00000000000..a0d5423b449
--- /dev/null
+++ b/src/mongo/db/pipeline/document_source_command_shards.cpp
@@ -0,0 +1,131 @@
+/**
+ * Copyright 2011 (c) 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pch.h"
+
+#include "mongo/db/pipeline/document_source.h"
+#include "mongo/s/shard.h"
+
+namespace mongo {
+
+ DocumentSourceCommandShards::~DocumentSourceCommandShards() {
+ }
+
+ bool DocumentSourceCommandShards::eof() {
+ /* if we haven't even started yet, do so */
+ if (!pCurrent.get())
+ getNextDocument();
+
+ return (pCurrent.get() == NULL);
+ }
+
+ bool DocumentSourceCommandShards::advance() {
+ DocumentSource::advance(); // check for interrupts
+
+ if (eof())
+ return false;
+
+ /* advance */
+ getNextDocument();
+
+ return (pCurrent.get() != NULL);
+ }
+
+ intrusive_ptr<Document> DocumentSourceCommandShards::getCurrent() {
+ verify(!eof());
+ return pCurrent;
+ }
+
+ void DocumentSourceCommandShards::setSource(DocumentSource *pSource) {
+ /* this doesn't take a source */
+ verify(false);
+ }
+
+ void DocumentSourceCommandShards::sourceToBson(
+ BSONObjBuilder *pBuilder, bool explain) const {
+ /* this has no BSON equivalent */
+ verify(false);
+ }
+
+ DocumentSourceCommandShards::DocumentSourceCommandShards(
+ const ShardOutput& shardOutput,
+ const intrusive_ptr<ExpressionContext> &pExpCtx):
+ DocumentSource(pExpCtx),
+ newSource(false),
+ pBsonSource(),
+ pCurrent(),
+ iterator(shardOutput.begin()),
+ listEnd(shardOutput.end())
+ {}
+
+ intrusive_ptr<DocumentSourceCommandShards>
+ DocumentSourceCommandShards::create(
+ const ShardOutput& shardOutput,
+ const intrusive_ptr<ExpressionContext> &pExpCtx) {
+ intrusive_ptr<DocumentSourceCommandShards> pSource(
+ new DocumentSourceCommandShards(shardOutput, pExpCtx));
+ return pSource;
+ }
+
+ void DocumentSourceCommandShards::getNextDocument() {
+ while(true) {
+ if (!pBsonSource.get()) {
+ /* if there aren't any more futures, we're done */
+ if (iterator == listEnd) {
+ pCurrent.reset();
+ return;
+ }
+
+ /* grab the next command result */
+ BSONObj resultObj = iterator->second;
+
+ uassert(16390, str::stream() << "sharded pipeline failed on shard " <<
+ iterator->first.getName() << ": " <<
+ resultObj.toString(),
+ resultObj["ok"].trueValue());
+
+ /* grab the result array out of the shard server's response */
+ BSONElement resultArray = resultObj["result"];
+ massert(16391, str::stream() << "no result array? shard:" <<
+ iterator->first.getName() << ": " <<
+ resultObj.toString(),
+ resultArray.type() == Array);
+
+ // done with error checking, don't need the shard name anymore
+ ++iterator;
+
+ if (resultArray.embeddedObject().isEmpty()){
+ // this shard had no results, on to the next one
+ continue;
+ }
+
+ pBsonSource = DocumentSourceBsonArray::create(&resultArray, pExpCtx);
+ newSource = true;
+ }
+
+ /* if we're done with this shard's results, try the next */
+ if (pBsonSource->eof() ||
+ (!newSource && !pBsonSource->advance())) {
+ pBsonSource.reset();
+ continue;
+ }
+
+ pCurrent = pBsonSource->getCurrent();
+ newSource = false;
+ return;
+ }
+ }
+}
diff --git a/src/mongo/db/pipeline/document_source_cursor.cpp b/src/mongo/db/pipeline/document_source_cursor.cpp
new file mode 100755
index 00000000000..c99504ad446
--- /dev/null
+++ b/src/mongo/db/pipeline/document_source_cursor.cpp
@@ -0,0 +1,228 @@
+/**
+ * Copyright 2011 (c) 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "mongo/pch.h"
+
+#include "mongo/db/pipeline/document_source.h"
+
+#include "mongo/db/clientcursor.h"
+#include "mongo/db/instance.h"
+#include "mongo/db/pipeline/document.h"
+#include "mongo/s/d_logic.h"
+
+namespace mongo {
+
+ DocumentSourceCursor::CursorWithContext::CursorWithContext( const string& ns )
+ : _readContext( ns ) // Take a read lock.
+ , _chunkMgr(shardingState.needShardChunkManager( ns )
+ ? shardingState.getShardChunkManager( ns )
+ : ShardChunkManagerPtr())
+ {}
+
+ DocumentSourceCursor::~DocumentSourceCursor() {
+ }
+
+ bool DocumentSourceCursor::eof() {
+ /* if we haven't gotten the first one yet, do so now */
+ if (!pCurrent.get())
+ findNext();
+
+ return (pCurrent.get() == NULL);
+ }
+
+ bool DocumentSourceCursor::advance() {
+ DocumentSource::advance(); // check for interrupts
+
+ /* if we haven't gotten the first one yet, do so now */
+ if (!pCurrent.get())
+ findNext();
+
+ findNext();
+ return (pCurrent.get() != NULL);
+ }
+
+ intrusive_ptr<Document> DocumentSourceCursor::getCurrent() {
+ /* if we haven't gotten the first one yet, do so now */
+ if (!pCurrent.get())
+ findNext();
+
+ return pCurrent;
+ }
+
+ void DocumentSourceCursor::dispose() {
+ _cursorWithContext.reset();
+ }
+
+ ClientCursor::Holder& DocumentSourceCursor::cursor() {
+ verify( _cursorWithContext );
+ verify( _cursorWithContext->_cursor );
+ return _cursorWithContext->_cursor;
+ }
+
+ bool DocumentSourceCursor::canUseCoveredIndex() {
+ // We can't use a covered index when we have a chunk manager because we
+ // need to examine the object to see if it belongs on this shard
+ return (!chunkMgr() &&
+ cursor()->ok() && cursor()->c()->keyFieldsOnly());
+ }
+
+ void DocumentSourceCursor::yieldSometimes() {
+ try { // SERVER-5752 may make this try unnecessary
+ // if we are index only we don't need the recored
+ bool cursorOk = cursor()->yieldSometimes(canUseCoveredIndex()
+ ? ClientCursor::DontNeed
+ : ClientCursor::WillNeed);
+ uassert( 16028, "collection or database disappeared when cursor yielded", cursorOk );
+ }
+ catch(SendStaleConfigException& e){
+ // We want to ignore this because the migrated documents will be filtered out of the
+ // cursor anyway and, we don't want to restart the aggregation after every migration.
+
+ log() << "Config changed during aggregation - command will resume" << endl;
+ // useful for debugging but off by default to avoid looking like a scary error.
+ LOG(1) << "aggregation stale config exception: " << e.what() << endl;
+ }
+ }
+
+ void DocumentSourceCursor::findNext() {
+
+ if ( !_cursorWithContext ) {
+ pCurrent.reset();
+ return;
+ }
+
+ for( ; cursor()->ok(); cursor()->advance() ) {
+
+ yieldSometimes();
+ if ( !cursor()->ok() ) {
+ // The cursor was exhausted during the yield.
+ break;
+ }
+
+ if ( !cursor()->currentMatches() || cursor()->currentIsDup() )
+ continue;
+
+ // grab the matching document
+ BSONObj documentObj;
+ if (canUseCoveredIndex()) {
+ // Can't have a Chunk Manager if we are here
+ documentObj = cursor()->c()->keyFieldsOnly()->hydrate(cursor()->currKey());
+ }
+ else {
+ documentObj = cursor()->current();
+
+ // check to see if this is a new object we don't own yet
+ // because of a chunk migration
+ if ( chunkMgr() && ! chunkMgr()->belongsToMe(documentObj) )
+ continue;
+
+ if (_projection) {
+ documentObj = _projection->transform(documentObj);
+ }
+ }
+
+ pCurrent = Document::createFromBsonObj(&documentObj);
+
+ cursor()->advance();
+ return;
+ }
+
+ // If we got here, there aren't any more documents.
+ // The CursorWithContext (and its read lock) must be released, see SERVER-6123.
+ dispose();
+ pCurrent.reset();
+ }
+
+ void DocumentSourceCursor::setSource(DocumentSource *pSource) {
+ /* this doesn't take a source */
+ verify(false);
+ }
+
+ void DocumentSourceCursor::sourceToBson(
+ BSONObjBuilder *pBuilder, bool explain) const {
+
+ /* this has no analog in the BSON world, so only allow it for explain */
+ if (explain)
+ {
+ BSONObj bsonObj;
+
+ pBuilder->append("query", *pQuery);
+
+ if (pSort.get())
+ {
+ pBuilder->append("sort", *pSort);
+ }
+
+ BSONObj projectionSpec;
+ if (_projection) {
+ projectionSpec = _projection->getSpec();
+ pBuilder->append("projection", projectionSpec);
+ }
+
+ // construct query for explain
+ BSONObjBuilder queryBuilder;
+ queryBuilder.append("$query", *pQuery);
+ if (pSort.get())
+ queryBuilder.append("$orderby", *pSort);
+ queryBuilder.append("$explain", 1);
+ Query query(queryBuilder.obj());
+
+ DBDirectClient directClient;
+ BSONObj explainResult(directClient.findOne(ns, query, _projection
+ ? &projectionSpec
+ : NULL));
+
+ pBuilder->append("cursor", explainResult);
+ }
+ }
+
+ DocumentSourceCursor::DocumentSourceCursor(
+ const shared_ptr<CursorWithContext>& cursorWithContext,
+ const intrusive_ptr<ExpressionContext> &pCtx):
+ DocumentSource(pCtx),
+ pCurrent(),
+ _cursorWithContext( cursorWithContext )
+ {}
+
+ intrusive_ptr<DocumentSourceCursor> DocumentSourceCursor::create(
+ const shared_ptr<CursorWithContext>& cursorWithContext,
+ const intrusive_ptr<ExpressionContext> &pExpCtx) {
+ verify( cursorWithContext );
+ verify( cursorWithContext->_cursor );
+ intrusive_ptr<DocumentSourceCursor> pSource(
+ new DocumentSourceCursor( cursorWithContext, pExpCtx ) );
+ return pSource;
+ }
+
+ void DocumentSourceCursor::setNamespace(const string &n) {
+ ns = n;
+ }
+
+ void DocumentSourceCursor::setQuery(const shared_ptr<BSONObj> &pBsonObj) {
+ pQuery = pBsonObj;
+ }
+
+ void DocumentSourceCursor::setSort(const shared_ptr<BSONObj> &pBsonObj) {
+ pSort = pBsonObj;
+ }
+
+ void DocumentSourceCursor::setProjection(BSONObj projection) {
+ verify(!_projection);
+ _projection.reset(new Projection);
+ _projection->init(projection);
+ cursor()->fields = _projection;
+ }
+}
diff --git a/src/mongo/db/pipeline/document_source_filter.cpp b/src/mongo/db/pipeline/document_source_filter.cpp
new file mode 100755
index 00000000000..b9cb0a369d1
--- /dev/null
+++ b/src/mongo/db/pipeline/document_source_filter.cpp
@@ -0,0 +1,105 @@
+/**
+* Copyright (C) 2011 10gen Inc.
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License, version 3,
+* as published by the Free Software Foundation.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "pch.h"
+
+#include "db/pipeline/document_source.h"
+
+#include "db/jsobj.h"
+#include "db/pipeline/expression.h"
+#include "db/pipeline/value.h"
+
+namespace mongo {
+
+ const char DocumentSourceFilter::filterName[] = "$filter";
+
+ DocumentSourceFilter::~DocumentSourceFilter() {
+ }
+
+ const char *DocumentSourceFilter::getSourceName() const {
+ return filterName;
+ }
+
+ bool DocumentSourceFilter::coalesce(
+ const intrusive_ptr<DocumentSource> &pNextSource) {
+
+ /* we only know how to coalesce other filters */
+ DocumentSourceFilter *pDocFilter =
+ dynamic_cast<DocumentSourceFilter *>(pNextSource.get());
+ if (!pDocFilter)
+ return false;
+
+ /*
+ Two adjacent filters can be combined by creating a conjunction of
+ their predicates.
+ */
+ intrusive_ptr<ExpressionNary> pAnd(ExpressionAnd::create());
+ pAnd->addOperand(pFilter);
+ pAnd->addOperand(pDocFilter->pFilter);
+ pFilter = pAnd;
+
+ return true;
+ }
+
+ void DocumentSourceFilter::optimize() {
+ pFilter = pFilter->optimize();
+ }
+
+ void DocumentSourceFilter::sourceToBson(
+ BSONObjBuilder *pBuilder, bool explain) const {
+ pFilter->addToBsonObj(pBuilder, filterName, false);
+ }
+
+ bool DocumentSourceFilter::accept(
+ const intrusive_ptr<Document> &pDocument) const {
+ intrusive_ptr<const Value> pValue(pFilter->evaluate(pDocument));
+ return pValue->coerceToBool();
+ }
+
+ intrusive_ptr<DocumentSource> DocumentSourceFilter::createFromBson(
+ BSONElement *pBsonElement,
+ const intrusive_ptr<ExpressionContext> &pCtx) {
+ uassert(15946, "a document filter expression must be an object",
+ pBsonElement->type() == Object);
+
+ Expression::ObjectCtx oCtx(0);
+ intrusive_ptr<Expression> pExpression(
+ Expression::parseObject(pBsonElement, &oCtx));
+ intrusive_ptr<DocumentSourceFilter> pFilter(
+ DocumentSourceFilter::create(pExpression, pCtx));
+
+ return pFilter;
+ }
+
+ intrusive_ptr<DocumentSourceFilter> DocumentSourceFilter::create(
+ const intrusive_ptr<Expression> &pFilter,
+ const intrusive_ptr<ExpressionContext> &pExpCtx) {
+ intrusive_ptr<DocumentSourceFilter> pSource(
+ new DocumentSourceFilter(pFilter, pExpCtx));
+ return pSource;
+ }
+
+ DocumentSourceFilter::DocumentSourceFilter(
+ const intrusive_ptr<Expression> &pTheFilter,
+ const intrusive_ptr<ExpressionContext> &pExpCtx):
+ DocumentSourceFilterBase(pExpCtx),
+ pFilter(pTheFilter) {
+ }
+
+ void DocumentSourceFilter::toMatcherBson(BSONObjBuilder *pBuilder) const {
+ pFilter->toMatcherBson(pBuilder);
+ }
+}
diff --git a/src/mongo/db/pipeline/document_source_filter_base.cpp b/src/mongo/db/pipeline/document_source_filter_base.cpp
new file mode 100755
index 00000000000..3354b3c6bc2
--- /dev/null
+++ b/src/mongo/db/pipeline/document_source_filter_base.cpp
@@ -0,0 +1,89 @@
+/**
+* Copyright (C) 2011 10gen Inc.
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License, version 3,
+* as published by the Free Software Foundation.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "pch.h"
+
+#include "db/pipeline/document_source.h"
+
+#include "db/jsobj.h"
+#include "db/pipeline/expression.h"
+#include "db/pipeline/value.h"
+
+namespace mongo {
+
+ DocumentSourceFilterBase::~DocumentSourceFilterBase() {
+ }
+
+ void DocumentSourceFilterBase::findNext() {
+ /* only do this the first time */
+ if (unstarted) {
+ hasNext = !pSource->eof();
+ unstarted = false;
+ }
+
+ while(hasNext) {
+ boost::intrusive_ptr<Document> pDocument(pSource->getCurrent());
+ hasNext = pSource->advance();
+
+ if (accept(pDocument)) {
+ pCurrent = pDocument;
+ return;
+ }
+ }
+
+ pCurrent.reset();
+ }
+
+ bool DocumentSourceFilterBase::eof() {
+ if (unstarted)
+ findNext();
+
+ return (pCurrent.get() == NULL);
+ }
+
+ bool DocumentSourceFilterBase::advance() {
+ DocumentSource::advance(); // check for interrupts
+
+ if (unstarted)
+ findNext();
+
+ /*
+ This looks weird after the above, but is correct. Note that calling
+ getCurrent() when first starting already yields the first document
+ in the collection. Calling advance() without using getCurrent()
+ first will skip over the first item.
+ */
+ findNext();
+
+ return (pCurrent.get() != NULL);
+ }
+
+ boost::intrusive_ptr<Document> DocumentSourceFilterBase::getCurrent() {
+ if (unstarted)
+ findNext();
+
+ verify(pCurrent.get() != NULL);
+ return pCurrent;
+ }
+
+ DocumentSourceFilterBase::DocumentSourceFilterBase(
+ const intrusive_ptr<ExpressionContext> &pExpCtx):
+ DocumentSource(pExpCtx),
+ unstarted(true),
+ hasNext(false),
+ pCurrent() {
+ }
+}
diff --git a/src/mongo/db/pipeline/document_source_group.cpp b/src/mongo/db/pipeline/document_source_group.cpp
new file mode 100755
index 00000000000..5b30b27f144
--- /dev/null
+++ b/src/mongo/db/pipeline/document_source_group.cpp
@@ -0,0 +1,422 @@
+/**
+* Copyright (C) 2011 10gen Inc.
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License, version 3,
+* as published by the Free Software Foundation.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "pch.h"
+
+#include "db/pipeline/document_source.h"
+
+#include "db/jsobj.h"
+#include "db/pipeline/accumulator.h"
+#include "db/pipeline/document.h"
+#include "db/pipeline/expression.h"
+#include "db/pipeline/expression_context.h"
+#include "db/pipeline/value.h"
+
+namespace mongo {
+ const char DocumentSourceGroup::groupName[] = "$group";
+
+ DocumentSourceGroup::~DocumentSourceGroup() {
+ }
+
+ const char *DocumentSourceGroup::getSourceName() const {
+ return groupName;
+ }
+
+ bool DocumentSourceGroup::eof() {
+ if (!populated)
+ populate();
+
+ return (groupsIterator == groups.end());
+ }
+
+ bool DocumentSourceGroup::advance() {
+ DocumentSource::advance(); // check for interrupts
+
+ if (!populated)
+ populate();
+
+ verify(groupsIterator != groups.end());
+
+ ++groupsIterator;
+ if (groupsIterator == groups.end()) {
+ pCurrent.reset();
+ return false;
+ }
+
+ pCurrent = makeDocument(groupsIterator);
+ return true;
+ }
+
+ intrusive_ptr<Document> DocumentSourceGroup::getCurrent() {
+ if (!populated)
+ populate();
+
+ return pCurrent;
+ }
+
+ void DocumentSourceGroup::sourceToBson(
+ BSONObjBuilder *pBuilder, bool explain) const {
+ BSONObjBuilder insides;
+
+ /* add the _id */
+ pIdExpression->addToBsonObj(&insides, Document::idName.c_str(), true);
+
+ /* add the remaining fields */
+ const size_t n = vFieldName.size();
+ for(size_t i = 0; i < n; ++i) {
+ intrusive_ptr<Accumulator> pA((*vpAccumulatorFactory[i])(pExpCtx));
+ pA->addOperand(vpExpression[i]);
+ pA->addToBsonObj(&insides, vFieldName[i], true);
+ }
+
+ pBuilder->append(groupName, insides.done());
+ }
+
+ DocumentSource::GetDepsReturn DocumentSourceGroup::getDependencies(set<string>& deps) const {
+ // add the _id
+ pIdExpression->addDependencies(deps);
+
+ // add the rest
+ const size_t n = vFieldName.size();
+ for(size_t i = 0; i < n; ++i) {
+ intrusive_ptr<Accumulator> pA((*vpAccumulatorFactory[i])(pExpCtx));
+ pA->addOperand(vpExpression[i]);
+ pA->addDependencies(deps);
+ }
+
+ return EXHAUSTIVE;
+ }
+
+ intrusive_ptr<DocumentSourceGroup> DocumentSourceGroup::create(
+ const intrusive_ptr<ExpressionContext> &pExpCtx) {
+ intrusive_ptr<DocumentSourceGroup> pSource(
+ new DocumentSourceGroup(pExpCtx));
+ return pSource;
+ }
+
+ DocumentSourceGroup::DocumentSourceGroup(
+ const intrusive_ptr<ExpressionContext> &pExpCtx):
+ SplittableDocumentSource(pExpCtx),
+ populated(false),
+ pIdExpression(),
+ groups(),
+ vFieldName(),
+ vpAccumulatorFactory(),
+ vpExpression() {
+ }
+
+ void DocumentSourceGroup::addAccumulator(
+ string fieldName,
+ intrusive_ptr<Accumulator> (*pAccumulatorFactory)(
+ const intrusive_ptr<ExpressionContext> &),
+ const intrusive_ptr<Expression> &pExpression) {
+ vFieldName.push_back(fieldName);
+ vpAccumulatorFactory.push_back(pAccumulatorFactory);
+ vpExpression.push_back(pExpression);
+ }
+
+
+ struct GroupOpDesc {
+ const char *pName;
+ intrusive_ptr<Accumulator> (*pFactory)(
+ const intrusive_ptr<ExpressionContext> &);
+ };
+
+ static int GroupOpDescCmp(const void *pL, const void *pR) {
+ return strcmp(((const GroupOpDesc *)pL)->pName,
+ ((const GroupOpDesc *)pR)->pName);
+ }
+
+ /*
+ Keep these sorted alphabetically so we can bsearch() them using
+ GroupOpDescCmp() above.
+ */
+ static const GroupOpDesc GroupOpTable[] = {
+ {"$addToSet", AccumulatorAddToSet::create},
+ {"$avg", AccumulatorAvg::create},
+ {"$first", AccumulatorFirst::create},
+ {"$last", AccumulatorLast::create},
+ {"$max", AccumulatorMinMax::createMax},
+ {"$min", AccumulatorMinMax::createMin},
+ {"$push", AccumulatorPush::create},
+ {"$sum", AccumulatorSum::create},
+ };
+
+ static const size_t NGroupOp = sizeof(GroupOpTable)/sizeof(GroupOpTable[0]);
+
+ intrusive_ptr<DocumentSource> DocumentSourceGroup::createFromBson(
+ BSONElement *pBsonElement,
+ const intrusive_ptr<ExpressionContext> &pExpCtx) {
+ uassert(15947, "a group's fields must be specified in an object",
+ pBsonElement->type() == Object);
+
+ intrusive_ptr<DocumentSourceGroup> pGroup(
+ DocumentSourceGroup::create(pExpCtx));
+ bool idSet = false;
+
+ BSONObj groupObj(pBsonElement->Obj());
+ BSONObjIterator groupIterator(groupObj);
+ while(groupIterator.more()) {
+ BSONElement groupField(groupIterator.next());
+ const char *pFieldName = groupField.fieldName();
+
+ if (strcmp(pFieldName, Document::idName.c_str()) == 0) {
+ uassert(15948, "a group's _id may only be specified once",
+ !idSet);
+
+ BSONType groupType = groupField.type();
+
+ if (groupType == Object) {
+ /*
+ Use the projection-like set of field paths to create the
+ group-by key.
+ */
+ Expression::ObjectCtx oCtx(Expression::ObjectCtx::DOCUMENT_OK);
+ intrusive_ptr<Expression> pId(
+ Expression::parseObject(&groupField, &oCtx));
+
+ pGroup->setIdExpression(pId);
+ idSet = true;
+ }
+ else if (groupType == String) {
+ string groupString(groupField.str());
+ const char *pGroupString = groupString.c_str();
+ if ((groupString.length() == 0) ||
+ (pGroupString[0] != '$'))
+ goto StringConstantId;
+
+ string pathString(
+ Expression::removeFieldPrefix(groupString));
+ intrusive_ptr<ExpressionFieldPath> pFieldPath(
+ ExpressionFieldPath::create(pathString));
+ pGroup->setIdExpression(pFieldPath);
+ idSet = true;
+ }
+ else {
+ /* pick out the constant types that are allowed */
+ switch(groupType) {
+ case NumberDouble:
+ case String:
+ case Object:
+ case Array:
+ case jstOID:
+ case Bool:
+ case Date:
+ case NumberInt:
+ case Timestamp:
+ case NumberLong:
+ case jstNULL:
+ StringConstantId: // from string case above
+ {
+ intrusive_ptr<const Value> pValue(
+ Value::createFromBsonElement(&groupField));
+ intrusive_ptr<ExpressionConstant> pConstant(
+ ExpressionConstant::create(pValue));
+ pGroup->setIdExpression(pConstant);
+ idSet = true;
+ break;
+ }
+
+ default:
+ uassert(15949, str::stream() <<
+ "a group's _id may not include fields of BSON type " << groupType,
+ false);
+ }
+ }
+ }
+ else {
+ /*
+ Treat as a projection field with the additional ability to
+ add aggregation operators.
+ */
+ uassert(16414, str::stream() <<
+ "the group aggregate field name '" << pFieldName <<
+ "' cannot be used because $group's field names cannot contain '.'",
+ !str::contains(pFieldName, '.') );
+
+ uassert(15950, str::stream() <<
+ "the group aggregate field name '" <<
+ pFieldName << "' cannot be an operator name",
+ pFieldName[0] != '$');
+
+ uassert(15951, str::stream() <<
+ "the group aggregate field '" << pFieldName <<
+ "' must be defined as an expression inside an object",
+ groupField.type() == Object);
+
+ BSONObj subField(groupField.Obj());
+ BSONObjIterator subIterator(subField);
+ size_t subCount = 0;
+ for(; subIterator.more(); ++subCount) {
+ BSONElement subElement(subIterator.next());
+
+ /* look for the specified operator */
+ GroupOpDesc key;
+ key.pName = subElement.fieldName();
+ const GroupOpDesc *pOp =
+ (const GroupOpDesc *)bsearch(
+ &key, GroupOpTable, NGroupOp, sizeof(GroupOpDesc),
+ GroupOpDescCmp);
+
+ uassert(15952, str::stream() <<
+ "unknown group operator '" <<
+ key.pName << "'",
+ pOp);
+
+ intrusive_ptr<Expression> pGroupExpr;
+
+ BSONType elementType = subElement.type();
+ if (elementType == Object) {
+ Expression::ObjectCtx oCtx(
+ Expression::ObjectCtx::DOCUMENT_OK);
+ pGroupExpr = Expression::parseObject(
+ &subElement, &oCtx);
+ }
+ else if (elementType == Array) {
+ uassert(15953, str::stream() <<
+ "aggregating group operators are unary (" <<
+ key.pName << ")", false);
+ }
+ else { /* assume its an atomic single operand */
+ pGroupExpr = Expression::parseOperand(&subElement);
+ }
+
+ pGroup->addAccumulator(
+ pFieldName, pOp->pFactory, pGroupExpr);
+ }
+
+ uassert(15954, str::stream() <<
+ "the computed aggregate '" <<
+ pFieldName << "' must specify exactly one operator",
+ subCount == 1);
+ }
+ }
+
+ uassert(15955, "a group specification must include an _id", idSet);
+
+ return pGroup;
+ }
+
+ void DocumentSourceGroup::populate() {
+ for(bool hasNext = !pSource->eof(); hasNext;
+ hasNext = pSource->advance()) {
+ intrusive_ptr<Document> pDocument(pSource->getCurrent());
+
+ /* get the _id value */
+ intrusive_ptr<const Value> pId(pIdExpression->evaluate(pDocument));
+
+ /* treat Undefined the same as NULL SERVER-4674 */
+ if (pId->getType() == Undefined)
+ pId = Value::getNull();
+
+ /*
+ Look for the _id value in the map; if it's not there, add a
+ new entry with a blank accumulator.
+ */
+ vector<intrusive_ptr<Accumulator> > *pGroup;
+ GroupsType::iterator it(groups.find(pId));
+ if (it != groups.end()) {
+ /* point at the existing accumulators */
+ pGroup = &it->second;
+ }
+ else {
+ /* insert a new group into the map */
+ groups.insert(it,
+ pair<intrusive_ptr<const Value>,
+ vector<intrusive_ptr<Accumulator> > >(
+ pId, vector<intrusive_ptr<Accumulator> >()));
+
+ /* find the accumulator vector (the map value) */
+ it = groups.find(pId);
+ pGroup = &it->second;
+
+ /* add the accumulators */
+ const size_t n = vpAccumulatorFactory.size();
+ pGroup->reserve(n);
+ for(size_t i = 0; i < n; ++i) {
+ intrusive_ptr<Accumulator> pAccumulator(
+ (*vpAccumulatorFactory[i])(pExpCtx));
+ pAccumulator->addOperand(vpExpression[i]);
+ pGroup->push_back(pAccumulator);
+ }
+ }
+
+ /* point at the existing key */
+ // unneeded atm // pId = it.first;
+
+ /* tickle all the accumulators for the group we found */
+ const size_t n = pGroup->size();
+ for(size_t i = 0; i < n; ++i)
+ (*pGroup)[i]->evaluate(pDocument);
+ }
+
+ /* start the group iterator */
+ groupsIterator = groups.begin();
+ if (groupsIterator != groups.end())
+ pCurrent = makeDocument(groupsIterator);
+ populated = true;
+ }
+
+ intrusive_ptr<Document> DocumentSourceGroup::makeDocument(
+ const GroupsType::iterator &rIter) {
+ vector<intrusive_ptr<Accumulator> > *pGroup = &rIter->second;
+ const size_t n = vFieldName.size();
+ intrusive_ptr<Document> pResult(Document::create(1 + n));
+
+ /* add the _id field */
+ pResult->addField(Document::idName, rIter->first);
+
+ /* add the rest of the fields */
+ for(size_t i = 0; i < n; ++i) {
+ intrusive_ptr<const Value> pValue((*pGroup)[i]->getValue());
+ if (pValue->getType() != Undefined)
+ pResult->addField(vFieldName[i], pValue);
+ }
+
+ return pResult;
+ }
+
+ intrusive_ptr<DocumentSource> DocumentSourceGroup::getShardSource() {
+ return this; // No modifications necessary when on shard
+ }
+
+ intrusive_ptr<DocumentSource> DocumentSourceGroup::getRouterSource() {
+ intrusive_ptr<ExpressionContext> pMergerExpCtx = pExpCtx->clone();
+ pMergerExpCtx->setDoingMerge(true);
+ intrusive_ptr<DocumentSourceGroup> pMerger(DocumentSourceGroup::create(pMergerExpCtx));
+
+ /* the merger will use the same grouping key */
+ pMerger->setIdExpression(ExpressionFieldPath::create(
+ Document::idName.c_str()));
+
+ const size_t n = vFieldName.size();
+ for(size_t i = 0; i < n; ++i) {
+ /*
+ The merger's output field names will be the same, as will the
+ accumulator factories. However, for some accumulators, the
+ expression to be accumulated will be different. The original
+ accumulator may be collecting an expression based on a field
+ expression or constant. Here, we accumulate the output of the
+ same name from the prior group.
+ */
+ pMerger->addAccumulator(
+ vFieldName[i], vpAccumulatorFactory[i],
+ ExpressionFieldPath::create(vFieldName[i]));
+ }
+
+ return pMerger;
+ }
+}
diff --git a/src/mongo/db/pipeline/document_source_limit.cpp b/src/mongo/db/pipeline/document_source_limit.cpp
new file mode 100644
index 00000000000..8bbcaff2113
--- /dev/null
+++ b/src/mongo/db/pipeline/document_source_limit.cpp
@@ -0,0 +1,112 @@
+/**
+* Copyright (C) 2011 10gen Inc.
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License, version 3,
+* as published by the Free Software Foundation.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "pch.h"
+
+#include "db/pipeline/document_source.h"
+
+#include "db/jsobj.h"
+#include "db/pipeline/document.h"
+#include "db/pipeline/expression.h"
+#include "db/pipeline/expression_context.h"
+#include "db/pipeline/value.h"
+
+namespace mongo {
+ const char DocumentSourceLimit::limitName[] = "$limit";
+
+ DocumentSourceLimit::DocumentSourceLimit(
+ const intrusive_ptr<ExpressionContext> &pExpCtx):
+ DocumentSource(pExpCtx),
+ limit(0),
+ count(0) {
+ }
+
+ DocumentSourceLimit::~DocumentSourceLimit() {
+ }
+
+ const char *DocumentSourceLimit::getSourceName() const {
+ return limitName;
+ }
+
+ bool DocumentSourceLimit::coalesce(
+ const intrusive_ptr<DocumentSource> &pNextSource) {
+ DocumentSourceLimit *pLimit =
+ dynamic_cast<DocumentSourceLimit *>(pNextSource.get());
+
+ /* if it's not another $skip, we can't coalesce */
+ if (!pLimit)
+ return false;
+
+ /* we need to limit by the minimum of the two limits */
+ if (pLimit->limit < limit)
+ limit = pLimit->limit;
+ return true;
+ }
+
+ bool DocumentSourceLimit::eof() {
+ return pSource->eof() || count >= limit;
+ }
+
+ bool DocumentSourceLimit::advance() {
+ DocumentSource::advance(); // check for interrupts
+
+ ++count;
+ if (count >= limit) {
+
+ pCurrent.reset();
+
+ // This is requried for the DocumentSourceCursor to release its read lock, see
+ // SERVER-6123.
+ pSource->dispose();
+
+ return false;
+ }
+ pCurrent = pSource->getCurrent();
+ return pSource->advance();
+ }
+
+ intrusive_ptr<Document> DocumentSourceLimit::getCurrent() {
+ return pSource->getCurrent();
+ }
+
+ void DocumentSourceLimit::sourceToBson(
+ BSONObjBuilder *pBuilder, bool explain) const {
+ pBuilder->append("$limit", limit);
+ }
+
+ intrusive_ptr<DocumentSourceLimit> DocumentSourceLimit::create(
+ const intrusive_ptr<ExpressionContext> &pExpCtx) {
+ intrusive_ptr<DocumentSourceLimit> pSource(
+ new DocumentSourceLimit(pExpCtx));
+ return pSource;
+ }
+
+ intrusive_ptr<DocumentSource> DocumentSourceLimit::createFromBson(
+ BSONElement *pBsonElement,
+ const intrusive_ptr<ExpressionContext> &pExpCtx) {
+ uassert(15957, "the limit must be specified as a number",
+ pBsonElement->isNumber());
+
+ intrusive_ptr<DocumentSourceLimit> pLimit(
+ DocumentSourceLimit::create(pExpCtx));
+
+ pLimit->limit = (int)pBsonElement->numberLong();
+ uassert(15958, "the limit must be positive",
+ pLimit->limit > 0);
+
+ return pLimit;
+ }
+}
diff --git a/src/mongo/db/pipeline/document_source_match.cpp b/src/mongo/db/pipeline/document_source_match.cpp
new file mode 100644
index 00000000000..c444cec3abe
--- /dev/null
+++ b/src/mongo/db/pipeline/document_source_match.cpp
@@ -0,0 +1,106 @@
+/**
+* Copyright (C) 2011 10gen Inc.
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License, version 3,
+* as published by the Free Software Foundation.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "pch.h"
+
+#include "db/pipeline/document_source.h"
+
+#include "db/jsobj.h"
+#include "db/matcher.h"
+#include "db/pipeline/document.h"
+#include "db/pipeline/expression.h"
+
+namespace mongo {
+
+ const char DocumentSourceMatch::matchName[] = "$match";
+
+ DocumentSourceMatch::~DocumentSourceMatch() {
+ }
+
+ const char *DocumentSourceMatch::getSourceName() const {
+ return matchName;
+ }
+
+ void DocumentSourceMatch::sourceToBson(
+ BSONObjBuilder *pBuilder, bool explain) const {
+ const BSONObj *pQuery = matcher.getQuery();
+ pBuilder->append(matchName, *pQuery);
+ }
+
+ bool DocumentSourceMatch::accept(
+ const intrusive_ptr<Document> &pDocument) const {
+
+ /*
+ The matcher only takes BSON documents, so we have to make one.
+
+ LATER
+ We could optimize this by making a document with only the
+ fields referenced by the Matcher. We could do this by looking inside
+ the Matcher's BSON before it is created, and recording those. The
+ easiest implementation might be to hold onto an ExpressionDocument
+ in here, and give that pDocument to create the created subset of
+ fields, and then convert that instead.
+ */
+ BSONObjBuilder objBuilder;
+ pDocument->toBson(&objBuilder);
+ BSONObj obj(objBuilder.done());
+
+ return matcher.matches(obj);
+ }
+
+ static void uassertNoDisallowedClauses(BSONObj query) {
+ BSONForEach(e, query) {
+ // can't use the Matcher API because this would segfault the constructor
+ uassert(16395, "$where is not allowed inside of a $match aggregation expression",
+ ! str::equals(e.fieldName(), "$where"));
+ // geo breaks if it is not the first portion of the pipline
+ uassert(16424, "$near is not allowed inside of a $match aggregation expression",
+ ! str::equals(e.fieldName(), "$near"));
+ uassert(16425, "$within is not allowed inside of a $match aggregation expression",
+ ! str::equals(e.fieldName(), "$within"));
+ uassert(16426, "$nearSphere is not allowed inside of a $match aggregation expression",
+ ! str::equals(e.fieldName(), "$nearSphere"));
+ if (e.isABSONObj())
+ uassertNoDisallowedClauses(e.Obj());
+ }
+ }
+
+ intrusive_ptr<DocumentSource> DocumentSourceMatch::createFromBson(
+ BSONElement *pBsonElement,
+ const intrusive_ptr<ExpressionContext> &pExpCtx) {
+ uassert(15959, "the match filter must be an expression in an object",
+ pBsonElement->type() == Object);
+
+ uassertNoDisallowedClauses(pBsonElement->Obj());
+
+ intrusive_ptr<DocumentSourceMatch> pMatcher(
+ new DocumentSourceMatch(pBsonElement->Obj(), pExpCtx));
+
+ return pMatcher;
+ }
+
+ void DocumentSourceMatch::toMatcherBson(BSONObjBuilder *pBuilder) const {
+ const BSONObj *pQuery = matcher.getQuery();
+ pBuilder->appendElements(*pQuery);
+ }
+
+ DocumentSourceMatch::DocumentSourceMatch(
+ const BSONObj &query,
+ const intrusive_ptr<ExpressionContext> &pExpCtx):
+ DocumentSourceFilterBase(pExpCtx),
+ matcher(query) {
+ }
+}
diff --git a/src/mongo/db/pipeline/document_source_out.cpp b/src/mongo/db/pipeline/document_source_out.cpp
new file mode 100755
index 00000000000..c19e066efe6
--- /dev/null
+++ b/src/mongo/db/pipeline/document_source_out.cpp
@@ -0,0 +1,67 @@
+/**
+ * Copyright 2011 (c) 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pch.h"
+
+#include "db/pipeline/document_source.h"
+
+
+namespace mongo {
+
+ const char DocumentSourceOut::outName[] = "$out";
+
+ DocumentSourceOut::~DocumentSourceOut() {
+ }
+
+ const char *DocumentSourceOut::getSourceName() const {
+ return outName;
+ }
+
+ bool DocumentSourceOut::eof() {
+ return pSource->eof();
+ }
+
+ bool DocumentSourceOut::advance() {
+ DocumentSource::advance(); // check for interrupts
+
+ return pSource->advance();
+ }
+
+ boost::intrusive_ptr<Document> DocumentSourceOut::getCurrent() {
+ return pSource->getCurrent();
+ }
+
+ DocumentSourceOut::DocumentSourceOut(
+ BSONElement *pBsonElement,
+ const intrusive_ptr<ExpressionContext> &pExpCtx):
+ DocumentSource(pExpCtx) {
+ verify(false && "unimplemented");
+ }
+
+ intrusive_ptr<DocumentSourceOut> DocumentSourceOut::createFromBson(
+ BSONElement *pBsonElement,
+ const intrusive_ptr<ExpressionContext> &pExpCtx) {
+ intrusive_ptr<DocumentSourceOut> pSource(
+ new DocumentSourceOut(pBsonElement, pExpCtx));
+
+ return pSource;
+ }
+
+ void DocumentSourceOut::sourceToBson(
+ BSONObjBuilder *pBuilder, bool explain) const {
+ verify(false); // CW TODO
+ }
+}
diff --git a/src/mongo/db/pipeline/document_source_project.cpp b/src/mongo/db/pipeline/document_source_project.cpp
new file mode 100644
index 00000000000..bbd969fa997
--- /dev/null
+++ b/src/mongo/db/pipeline/document_source_project.cpp
@@ -0,0 +1,149 @@
+/**
+ * Copyright 2011 (c) 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pch.h"
+#include "db/pipeline/document_source.h"
+
+#include "db/jsobj.h"
+#include "db/pipeline/document.h"
+#include "db/pipeline/expression.h"
+#include "db/pipeline/value.h"
+
+namespace mongo {
+
+ const char DocumentSourceProject::projectName[] = "$project";
+
+ DocumentSourceProject::~DocumentSourceProject() {
+ }
+
+ DocumentSourceProject::DocumentSourceProject(const intrusive_ptr<ExpressionContext> &pExpCtx)
+ : DocumentSource(pExpCtx)
+ , pEO(ExpressionObject::create())
+ { }
+
+ const char *DocumentSourceProject::getSourceName() const {
+ return projectName;
+ }
+
+ bool DocumentSourceProject::eof() {
+ return pSource->eof();
+ }
+
+ bool DocumentSourceProject::advance() {
+ DocumentSource::advance(); // check for interrupts
+
+ return pSource->advance();
+ }
+
+ intrusive_ptr<Document> DocumentSourceProject::getCurrent() {
+ intrusive_ptr<Document> pInDocument(pSource->getCurrent());
+ verify(pInDocument);
+
+ /* create the result document */
+ const size_t sizeHint = pEO->getSizeHint();
+ intrusive_ptr<Document> pResultDocument(Document::create(sizeHint));
+
+ /*
+ Use the ExpressionObject to create the base result.
+
+ If we're excluding fields at the top level, leave out the _id if
+ it is found, because we took care of it above.
+ */
+ pEO->addToDocument(pResultDocument, pInDocument, /*root=*/pInDocument);
+
+#if defined(_DEBUG)
+ if (!_simpleProjection.getSpec().isEmpty()) {
+ // Make sure we return the same results as Projection class
+
+ BSONObjBuilder inputBuilder;
+ pSource->getCurrent()->toBson(&inputBuilder);
+ BSONObj input = inputBuilder.done();
+
+ BSONObjBuilder outputBuilder;
+ pResultDocument->toBson(&outputBuilder);
+ BSONObj output = outputBuilder.done();
+
+ BSONObj projected = _simpleProjection.transform(input);
+
+ if (projected != output) {
+ log() << "$project applied incorrectly: " << getRaw() << endl;
+ log() << "input: " << input << endl;
+ log() << "out: " << output << endl;
+ log() << "projected: " << projected << endl;
+ verify(false); // exits in _DEBUG builds
+ }
+ }
+#endif
+
+ return pResultDocument;
+ }
+
+ void DocumentSourceProject::optimize() {
+ intrusive_ptr<Expression> pE(pEO->optimize());
+ pEO = dynamic_pointer_cast<ExpressionObject>(pE);
+ }
+
+ void DocumentSourceProject::sourceToBson(
+ BSONObjBuilder *pBuilder, bool explain) const {
+ BSONObjBuilder insides;
+ pEO->documentToBson(&insides, true);
+ pBuilder->append(projectName, insides.done());
+ }
+
+ intrusive_ptr<DocumentSource> DocumentSourceProject::createFromBson(
+ BSONElement *pBsonElement,
+ const intrusive_ptr<ExpressionContext> &pExpCtx) {
+ /* validate */
+ uassert(15969, str::stream() << projectName <<
+ " specification must be an object",
+ pBsonElement->type() == Object);
+
+ intrusive_ptr<DocumentSourceProject> pProject(new DocumentSourceProject(pExpCtx));
+
+ BSONObj projectObj(pBsonElement->Obj());
+ pProject->_raw = projectObj.getOwned(); // probably not necessary, but better to be safe
+
+ Expression::ObjectCtx objectCtx(
+ Expression::ObjectCtx::DOCUMENT_OK
+ | Expression::ObjectCtx::TOP_LEVEL
+ | Expression::ObjectCtx::INCLUSION_OK
+ );
+
+ intrusive_ptr<Expression> parsed = Expression::parseObject(pBsonElement, &objectCtx);
+ ExpressionObject* exprObj = dynamic_cast<ExpressionObject*>(parsed.get());
+ massert(16402, "parseObject() returned wrong type of Expression", exprObj);
+ uassert(16403, "$projection requires at least one output field", exprObj->getFieldCount());
+
+ pProject->pEO = exprObj;
+
+#if defined(_DEBUG)
+ if (exprObj->isSimple()) {
+ set<string> deps;
+ vector<string> path;
+ exprObj->addDependencies(deps, &path);
+ pProject->_simpleProjection.init(depsToProjection(deps));
+ }
+#endif
+
+ return pProject;
+ }
+
+ DocumentSource::GetDepsReturn DocumentSourceProject::getDependencies(set<string>& deps) const {
+ vector<string> path; // empty == top-level
+ pEO->addDependencies(deps, &path);
+ return EXHAUSTIVE;
+ }
+}
diff --git a/src/mongo/db/pipeline/document_source_skip.cpp b/src/mongo/db/pipeline/document_source_skip.cpp
new file mode 100644
index 00000000000..d4c1fc2caa6
--- /dev/null
+++ b/src/mongo/db/pipeline/document_source_skip.cpp
@@ -0,0 +1,125 @@
+/**
+* Copyright (C) 2011 10gen Inc.
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License, version 3,
+* as published by the Free Software Foundation.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "pch.h"
+
+#include "db/pipeline/document_source.h"
+
+#include "db/jsobj.h"
+#include "db/pipeline/document.h"
+#include "db/pipeline/expression.h"
+#include "db/pipeline/expression_context.h"
+#include "db/pipeline/value.h"
+
+namespace mongo {
+
+ const char DocumentSourceSkip::skipName[] = "$skip";
+
+ DocumentSourceSkip::DocumentSourceSkip(
+ const intrusive_ptr<ExpressionContext> &pExpCtx):
+ DocumentSource(pExpCtx),
+ skip(0),
+ count(0) {
+ }
+
+ DocumentSourceSkip::~DocumentSourceSkip() {
+ }
+
+ const char *DocumentSourceSkip::getSourceName() const {
+ return skipName;
+ }
+
+ bool DocumentSourceSkip::coalesce(
+ const intrusive_ptr<DocumentSource> &pNextSource) {
+ DocumentSourceSkip *pSkip =
+ dynamic_cast<DocumentSourceSkip *>(pNextSource.get());
+
+ /* if it's not another $skip, we can't coalesce */
+ if (!pSkip)
+ return false;
+
+ /* we need to skip over the sum of the two consecutive $skips */
+ skip += pSkip->skip;
+ return true;
+ }
+
+ void DocumentSourceSkip::skipper() {
+ if (count == 0) {
+ while (!pSource->eof() && count++ < skip) {
+ pSource->advance();
+ }
+ }
+
+ if (pSource->eof()) {
+ pCurrent.reset();
+ return;
+ }
+
+ pCurrent = pSource->getCurrent();
+ }
+
+ bool DocumentSourceSkip::eof() {
+ skipper();
+ return pSource->eof();
+ }
+
+ bool DocumentSourceSkip::advance() {
+ DocumentSource::advance(); // check for interrupts
+
+ if (eof()) {
+ pCurrent.reset();
+ return false;
+ }
+
+ pCurrent = pSource->getCurrent();
+ return pSource->advance();
+ }
+
+ intrusive_ptr<Document> DocumentSourceSkip::getCurrent() {
+ skipper();
+ return pCurrent;
+ }
+
+ void DocumentSourceSkip::sourceToBson(
+ BSONObjBuilder *pBuilder, bool explain) const {
+ pBuilder->append("$skip", skip);
+ }
+
+ intrusive_ptr<DocumentSourceSkip> DocumentSourceSkip::create(
+ const intrusive_ptr<ExpressionContext> &pExpCtx) {
+ intrusive_ptr<DocumentSourceSkip> pSource(
+ new DocumentSourceSkip(pExpCtx));
+ return pSource;
+ }
+
+ intrusive_ptr<DocumentSource> DocumentSourceSkip::createFromBson(
+ BSONElement *pBsonElement,
+ const intrusive_ptr<ExpressionContext> &pExpCtx) {
+ uassert(15972, str::stream() << DocumentSourceSkip::skipName <<
+ ": the value to skip must be a number",
+ pBsonElement->isNumber());
+
+ intrusive_ptr<DocumentSourceSkip> pSkip(
+ DocumentSourceSkip::create(pExpCtx));
+
+ pSkip->skip = pBsonElement->numberLong();
+ uassert(15956, str::stream() << DocumentSourceSkip::skipName <<
+ ": the number to skip cannot be negative",
+ pSkip->skip >= 0);
+
+ return pSkip;
+ }
+}
diff --git a/src/mongo/db/pipeline/document_source_sort.cpp b/src/mongo/db/pipeline/document_source_sort.cpp
new file mode 100755
index 00000000000..4b911894cdb
--- /dev/null
+++ b/src/mongo/db/pipeline/document_source_sort.cpp
@@ -0,0 +1,222 @@
+/**
+* Copyright (C) 2011 10gen Inc.
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU Affero General Public License, version 3,
+* as published by the Free Software Foundation.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU Affero General Public License for more details.
+*
+* You should have received a copy of the GNU Affero General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "pch.h"
+
+#include "db/pipeline/document_source.h"
+
+#include "db/jsobj.h"
+#include "db/pipeline/doc_mem_monitor.h"
+#include "db/pipeline/document.h"
+#include "db/pipeline/expression.h"
+#include "db/pipeline/expression_context.h"
+#include "db/pipeline/value.h"
+
+
+namespace mongo {
+ const char DocumentSourceSort::sortName[] = "$sort";
+
+ DocumentSourceSort::~DocumentSourceSort() {
+ }
+
+ const char *DocumentSourceSort::getSourceName() const {
+ return sortName;
+ }
+
+ bool DocumentSourceSort::eof() {
+ if (!populated)
+ populate();
+
+ return (docIterator == documents.end());
+ }
+
+ bool DocumentSourceSort::advance() {
+ DocumentSource::advance(); // check for interrupts
+
+ if (!populated)
+ populate();
+
+ verify(docIterator != documents.end());
+
+ ++docIterator;
+ if (docIterator == documents.end()) {
+ pCurrent.reset();
+ return false;
+ }
+ pCurrent = *docIterator;
+
+ return true;
+ }
+
+ intrusive_ptr<Document> DocumentSourceSort::getCurrent() {
+ if (!populated)
+ populate();
+
+ return pCurrent;
+ }
+
+ void DocumentSourceSort::sourceToBson(
+ BSONObjBuilder *pBuilder, bool explain) const {
+ BSONObjBuilder insides;
+ sortKeyToBson(&insides, false);
+ pBuilder->append(sortName, insides.done());
+ }
+
+ intrusive_ptr<DocumentSourceSort> DocumentSourceSort::create(
+ const intrusive_ptr<ExpressionContext> &pExpCtx) {
+ intrusive_ptr<DocumentSourceSort> pSource(
+ new DocumentSourceSort(pExpCtx));
+ return pSource;
+ }
+
+ DocumentSourceSort::DocumentSourceSort(
+ const intrusive_ptr<ExpressionContext> &pExpCtx):
+ SplittableDocumentSource(pExpCtx),
+ populated(false) {
+ }
+
+ void DocumentSourceSort::addKey(const string &fieldPath, bool ascending) {
+ intrusive_ptr<ExpressionFieldPath> pE(
+ ExpressionFieldPath::create(fieldPath));
+ vSortKey.push_back(pE);
+ vAscending.push_back(ascending);
+ }
+
+ void DocumentSourceSort::sortKeyToBson(
+ BSONObjBuilder *pBuilder, bool usePrefix) const {
+ /* add the key fields */
+ const size_t n = vSortKey.size();
+ for(size_t i = 0; i < n; ++i) {
+ /* create the "field name" */
+ stringstream ss;
+ vSortKey[i]->writeFieldPath(ss, usePrefix);
+
+ /* append a named integer based on the sort order */
+ pBuilder->append(ss.str(), (vAscending[i] ? 1 : -1));
+ }
+ }
+ DocumentSource::GetDepsReturn DocumentSourceSort::getDependencies(set<string>& deps) const {
+ for(size_t i = 0; i < vSortKey.size(); ++i) {
+ vSortKey[i]->addDependencies(deps);
+ }
+
+ return SEE_NEXT;
+ }
+
+
+ intrusive_ptr<DocumentSource> DocumentSourceSort::createFromBson(
+ BSONElement *pBsonElement,
+ const intrusive_ptr<ExpressionContext> &pExpCtx) {
+ uassert(15973, str::stream() << " the " <<
+ sortName << " key specification must be an object",
+ pBsonElement->type() == Object);
+
+ intrusive_ptr<DocumentSourceSort> pSort(
+ DocumentSourceSort::create(pExpCtx));
+
+ /* check for then iterate over the sort object */
+ size_t sortKeys = 0;
+ for(BSONObjIterator keyIterator(pBsonElement->Obj().begin());
+ keyIterator.more();) {
+ BSONElement keyField(keyIterator.next());
+ const char *pKeyFieldName = keyField.fieldName();
+ int sortOrder = 0;
+
+ uassert(15974, str::stream() << sortName <<
+ " key ordering must be specified using a number",
+ keyField.isNumber());
+ sortOrder = (int)keyField.numberInt();
+
+ uassert(15975, str::stream() << sortName <<
+ " key ordering must be 1 (for ascending) or -1 (for descending",
+ ((sortOrder == 1) || (sortOrder == -1)));
+
+ pSort->addKey(pKeyFieldName, (sortOrder > 0));
+ ++sortKeys;
+ }
+
+ uassert(15976, str::stream() << sortName <<
+ " must have at least one sort key", (sortKeys > 0));
+
+ return pSort;
+ }
+
+ void DocumentSourceSort::populate() {
+ /* make sure we've got a sort key */
+ verify(vSortKey.size());
+
+ /* track and warn about how much physical memory has been used */
+ DocMemMonitor dmm(this);
+
+ /* pull everything from the underlying source */
+ for(bool hasNext = !pSource->eof(); hasNext;
+ hasNext = pSource->advance()) {
+ intrusive_ptr<Document> pDocument(pSource->getCurrent());
+ documents.push_back(pDocument);
+
+ dmm.addToTotal(pDocument->getApproximateSize());
+ }
+
+ /* sort the list */
+ Comparator comparator(this);
+ sort(documents.begin(), documents.end(), comparator);
+
+ /* start the sort iterator */
+ docIterator = documents.begin();
+
+ if (docIterator != documents.end())
+ pCurrent = *docIterator;
+ populated = true;
+ }
+
+ int DocumentSourceSort::compare(
+ const intrusive_ptr<Document> &pL, const intrusive_ptr<Document> &pR) {
+
+ /*
+ populate() already checked that there is a non-empty sort key,
+ so we shouldn't have to worry about that here.
+
+ However, the tricky part is what to do is none of the sort keys are
+ present. In this case, consider the document less.
+ */
+ const size_t n = vSortKey.size();
+ for(size_t i = 0; i < n; ++i) {
+ /* evaluate the sort keys */
+ ExpressionFieldPath *pE = vSortKey[i].get();
+ intrusive_ptr<const Value> pLeft(pE->evaluate(pL));
+ intrusive_ptr<const Value> pRight(pE->evaluate(pR));
+
+ /*
+ Compare the two values; if they differ, return. If they are
+ the same, move on to the next key.
+ */
+ int cmp = Value::compare(pLeft, pRight);
+ if (cmp) {
+ /* if necessary, adjust the return value by the key ordering */
+ if (!vAscending[i])
+ cmp = -cmp;
+
+ return cmp;
+ }
+ }
+
+ /*
+ If we got here, everything matched (or didn't exist), so we'll
+ consider the documents equal for purposes of this sort.
+ */
+ return 0;
+ }
+}
diff --git a/src/mongo/db/pipeline/document_source_unwind.cpp b/src/mongo/db/pipeline/document_source_unwind.cpp
new file mode 100755
index 00000000000..16e431c02d3
--- /dev/null
+++ b/src/mongo/db/pipeline/document_source_unwind.cpp
@@ -0,0 +1,293 @@
+/**
+ * Copyright 2011 (c) 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pch.h"
+#include "db/pipeline/document_source.h"
+
+#include "db/jsobj.h"
+#include "db/pipeline/document.h"
+#include "db/pipeline/expression.h"
+#include "db/pipeline/value.h"
+
+namespace mongo {
+
+ /** Helper class to unwind arrays within a series of documents. */
+ class DocumentSourceUnwind::Unwinder {
+ public:
+ /** @param unwindPath is the field path to the array to unwind. */
+ Unwinder(const FieldPath& unwindPath);
+ /** Reset the unwinder to unwind a new document. */
+ void resetDocument(const intrusive_ptr<Document>& document);
+ /** @return true if done unwinding the last document passed to resetDocument(). */
+ bool eof() const;
+ /**
+ * Try to advance to the next document unwound from the document passed to resetDocument().
+ * @return true if advanced to a new unwound document, but false if done advancing.
+ */
+ void advance();
+ /**
+ * @return the current document unwound from the document provided to resetDocuemnt(), using
+ * the current value in the array located at the provided unwindPath. But @return
+ * intrusive_ptr<Document>() if resetDocument() has not been called or the results to unwind
+ * have been exhausted.
+ */
+ intrusive_ptr<Document> getCurrent() const;
+ private:
+ /**
+ * @return the value at the unwind path, otherwise an empty pointer if no such value
+ * exists. The _unwindPathFieldIndexes attribute will be set as the field path is traversed
+ * to find the value to unwind.
+ */
+ intrusive_ptr<const Value> extractUnwindValue();
+ // Path to the array to unwind.
+ FieldPath _unwindPath;
+ // The souce document to unwind.
+ intrusive_ptr<Document> _document;
+ // Document indexes of the field path components.
+ vector<int> _unwindPathFieldIndexes;
+ // Iterator over the array within _document to unwind.
+ intrusive_ptr<ValueIterator> _unwindArrayIterator;
+ // The last value returned from _unwindArrayIterator.
+ intrusive_ptr<const Value> _unwindArrayIteratorCurrent;
+ };
+
+ DocumentSourceUnwind::Unwinder::Unwinder(const FieldPath& unwindPath):
+ _unwindPath(unwindPath) {
+ }
+
+ void DocumentSourceUnwind::Unwinder::resetDocument(const intrusive_ptr<Document>& document) {
+ verify( document );
+
+ // Reset document specific attributes.
+ _document = document;
+ _unwindPathFieldIndexes.clear();
+ _unwindArrayIterator.reset();
+ _unwindArrayIteratorCurrent.reset();
+
+ intrusive_ptr<const Value> pathValue = extractUnwindValue(); // sets _unwindPathFieldIndexes
+ if (!pathValue) {
+ // The path does not exist.
+ return;
+ }
+
+ bool nothingToEmit =
+ (pathValue->getType() == jstNULL) ||
+ (pathValue->getType() == Undefined) ||
+ ((pathValue->getType() == Array) && (pathValue->getArrayLength() == 0));
+
+ if (nothingToEmit) {
+ // The target field exists, but there are no values to unwind.
+ return;
+ }
+
+ // The target field must be an array to unwind.
+ uassert(15978, str::stream() << (string)DocumentSourceUnwind::unwindName
+ << ": value at end of field path must be an array",
+ pathValue->getType() == Array);
+
+ // Start the iterator used to unwind the array.
+ _unwindArrayIterator = pathValue->getArray();
+ verify(_unwindArrayIterator->more()); // Checked above that the array is nonempty.
+ // Pull the first value out of the iterator.
+ _unwindArrayIteratorCurrent = _unwindArrayIterator->next();
+ }
+
+ bool DocumentSourceUnwind::Unwinder::eof() const {
+ return !_unwindArrayIteratorCurrent;
+ }
+
+ void DocumentSourceUnwind::Unwinder::advance() {
+ if (!_unwindArrayIterator) {
+ // resetDocument() has not been called or the supplied document had no results to
+ // unwind.
+ _unwindArrayIteratorCurrent = NULL;
+ }
+ else if (!_unwindArrayIterator->more()) {
+ // There are no more results to unwind.
+ _unwindArrayIteratorCurrent = NULL;
+ }
+ else {
+ _unwindArrayIteratorCurrent = _unwindArrayIterator->next();
+ }
+ }
+
+ intrusive_ptr<Document> DocumentSourceUnwind::Unwinder::getCurrent() const {
+ if (!_unwindArrayIteratorCurrent) {
+ return NULL;
+ }
+
+ // Clone all the documents along the field path so that the end values are not shared across
+ // documents that have come out of this pipeline operator. This is a partial deep clone.
+ // Because the value at the end will be replaced, everything along the path leading to that
+ // will be replaced in order not to share that change with any other clones (or the
+ // original).
+
+ intrusive_ptr<Document> clone(_document->clone());
+ intrusive_ptr<Document> current(clone);
+ const size_t n = _unwindPathFieldIndexes.size();
+ verify(n);
+ for(size_t i = 0; i < n; ++i) {
+ const size_t fi = _unwindPathFieldIndexes[i];
+ Document::FieldPair fp(current->getField(fi));
+ if (i + 1 < n) {
+ // For every object in the path but the last, clone it and continue on down.
+ intrusive_ptr<Document> next = fp.second->getDocument()->clone();
+ current->setField(fi, fp.first, Value::createDocument(next));
+ current = next;
+ }
+ else {
+ // In the last nested document, subsitute the current unwound value.
+ current->setField(fi, fp.first, _unwindArrayIteratorCurrent);
+ }
+ }
+
+ return clone;
+ }
+
+ intrusive_ptr<const Value> DocumentSourceUnwind::Unwinder::extractUnwindValue() {
+
+ intrusive_ptr<Document> current = _document;
+ intrusive_ptr<const Value> pathValue;
+ const size_t pathLength = _unwindPath.getPathLength();
+ for(size_t i = 0; i < pathLength; ++i) {
+
+ size_t idx = current->getFieldIndex(_unwindPath.getFieldName(i));
+
+ if (idx == current->getFieldCount()) {
+ // The target field is missing.
+ return NULL;
+ }
+
+ // Record the indexes of the fields down the field path in order to quickly replace them
+ // as the documents along the field path are cloned.
+ _unwindPathFieldIndexes.push_back(idx);
+
+ pathValue = current->getField(idx).second;
+
+ if (i < pathLength - 1) {
+
+ if (pathValue->getType() != Object) {
+ // The next field in the path cannot exist (inside a non object).
+ return NULL;
+ }
+
+ // Move down the object tree.
+ current = pathValue->getDocument();
+ }
+ }
+
+ return pathValue;
+ }
+
+ const char DocumentSourceUnwind::unwindName[] = "$unwind";
+
+ DocumentSourceUnwind::~DocumentSourceUnwind() {
+ }
+
+ DocumentSourceUnwind::DocumentSourceUnwind(
+ const intrusive_ptr<ExpressionContext> &pExpCtx):
+ DocumentSource(pExpCtx) {
+ }
+
+ void DocumentSourceUnwind::lazyInit() {
+ if (!_unwinder) {
+ verify(_unwindPath);
+ _unwinder.reset(new Unwinder(*_unwindPath));
+ if (!pSource->eof()) {
+ // Set up the first source document for unwinding.
+ _unwinder->resetDocument(pSource->getCurrent());
+ }
+ mayAdvanceSource();
+ }
+ }
+
+ void DocumentSourceUnwind::mayAdvanceSource() {
+ while(_unwinder->eof()) {
+ // The _unwinder is exhausted.
+
+ if (pSource->eof()) {
+ // The source is exhausted.
+ return;
+ }
+ if (!pSource->advance()) {
+ // The source is exhausted.
+ return;
+ }
+ // Reset the _unwinder with pSource's next document.
+ _unwinder->resetDocument(pSource->getCurrent());
+ }
+ }
+
+ const char *DocumentSourceUnwind::getSourceName() const {
+ return unwindName;
+ }
+
+ bool DocumentSourceUnwind::eof() {
+ lazyInit();
+ return _unwinder->eof();
+ }
+
+ bool DocumentSourceUnwind::advance() {
+ DocumentSource::advance(); // check for interrupts
+ lazyInit();
+ _unwinder->advance();
+ mayAdvanceSource();
+ return !_unwinder->eof();
+ }
+
+ intrusive_ptr<Document> DocumentSourceUnwind::getCurrent() {
+ lazyInit();
+ return _unwinder->getCurrent();
+ }
+
+ void DocumentSourceUnwind::sourceToBson(
+ BSONObjBuilder *pBuilder, bool explain) const {
+ verify(_unwindPath);
+ pBuilder->append(unwindName, _unwindPath->getPath(true));
+ }
+
+ DocumentSource::GetDepsReturn DocumentSourceUnwind::getDependencies(set<string>& deps) const {
+ verify(_unwindPath);
+ deps.insert(_unwindPath->getPath(false));
+ return SEE_NEXT;
+ }
+
+ void DocumentSourceUnwind::unwindPath(const FieldPath &fieldPath) {
+ // Can't set more than one unwind path.
+ uassert(15979, str::stream() << unwindName << "can't unwind more than one path",
+ !_unwindPath);
+ // Record the unwind path.
+ _unwindPath.reset(new FieldPath(fieldPath));
+ }
+
+ intrusive_ptr<DocumentSource> DocumentSourceUnwind::createFromBson(
+ BSONElement *pBsonElement,
+ const intrusive_ptr<ExpressionContext> &pExpCtx) {
+ /*
+ The value of $unwind should just be a field path.
+ */
+ uassert(15981, str::stream() << "the " << unwindName <<
+ " field path must be specified as a string",
+ pBsonElement->type() == String);
+
+ string prefixedPathString(pBsonElement->str());
+ string pathString(Expression::removeFieldPrefix(prefixedPathString));
+ intrusive_ptr<DocumentSourceUnwind> pUnwind(new DocumentSourceUnwind(pExpCtx));
+ pUnwind->unwindPath(FieldPath(pathString));
+
+ return pUnwind;
+ }
+}
diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp
new file mode 100644
index 00000000000..a489ceeaa8f
--- /dev/null
+++ b/src/mongo/db/pipeline/expression.cpp
@@ -0,0 +1,2608 @@
+/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pch.h"
+#include "db/pipeline/expression.h"
+
+#include <cstdio>
+#include "db/jsobj.h"
+#include "db/pipeline/builder.h"
+#include "db/pipeline/document.h"
+#include "db/pipeline/expression_context.h"
+#include "db/pipeline/value.h"
+#include "util/mongoutils/str.h"
+
+namespace mongo {
+ using namespace mongoutils;
+
+ /* --------------------------- Expression ------------------------------ */
+
+ void Expression::toMatcherBson(BSONObjBuilder *pBuilder) const {
+ verify(false && "Expression::toMatcherBson()");
+ }
+
+ Expression::ObjectCtx::ObjectCtx(int theOptions)
+ : options(theOptions)
+ {}
+
+ bool Expression::ObjectCtx::documentOk() const {
+ return ((options & DOCUMENT_OK) != 0);
+ }
+
+ bool Expression::ObjectCtx::topLevel() const {
+ return ((options & TOP_LEVEL) != 0);
+ }
+
+ bool Expression::ObjectCtx::inclusionOk() const {
+ return ((options & INCLUSION_OK) != 0);
+ }
+
+ string Expression::removeFieldPrefix(const string &prefixedField) {
+ uassert(16419, str::stream()<<"field path must not contain embedded null characters" << prefixedField.find("\0") << "," ,
+ prefixedField.find('\0') == string::npos);
+
+ const char *pPrefixedField = prefixedField.c_str();
+ uassert(15982, str::stream() <<
+ "field path references must be prefixed with a '$' ('" <<
+ prefixedField << "'", pPrefixedField[0] == '$');
+
+ return string(pPrefixedField + 1);
+ }
+
+ intrusive_ptr<Expression> Expression::parseObject(
+ BSONElement *pBsonElement, ObjectCtx *pCtx) {
+ /*
+ An object expression can take any of the following forms:
+
+ f0: {f1: ..., f2: ..., f3: ...}
+ f0: {$operator:[operand1, operand2, ...]}
+ */
+
+ intrusive_ptr<Expression> pExpression; // the result
+ intrusive_ptr<ExpressionObject> pExpressionObject; // alt result
+ enum { UNKNOWN, NOTOPERATOR, OPERATOR } kind = UNKNOWN;
+
+ BSONObj obj(pBsonElement->Obj());
+ if (obj.isEmpty())
+ return ExpressionObject::create();
+ BSONObjIterator iter(obj);
+
+ for(size_t fieldCount = 0; iter.more(); ++fieldCount) {
+ BSONElement fieldElement(iter.next());
+ const char *pFieldName = fieldElement.fieldName();
+
+ if (pFieldName[0] == '$') {
+ uassert(15983, str::stream() <<
+ "the operator must be the only field in a pipeline object (at '"
+ << pFieldName << "'",
+ fieldCount == 0);
+
+ uassert(16404, "$expressions are not allowed at the top-level of $project",
+ !pCtx->topLevel());
+
+ /* we've determined this "object" is an operator expression */
+ kind = OPERATOR;
+
+ pExpression = parseExpression(pFieldName, &fieldElement);
+ }
+ else {
+ uassert(15990, str::stream() << "this object is already an operator expression, and can't be used as a document expression (at '" <<
+ pFieldName << "')",
+ kind != OPERATOR);
+
+ uassert(16405, "dotted field names are only allowed at the top level",
+ pCtx->topLevel() || !str::contains(pFieldName, '.'));
+
+ /* if it's our first time, create the document expression */
+ if (!pExpression.get()) {
+ verify(pCtx->documentOk());
+ // CW TODO error: document not allowed in this context
+
+ pExpressionObject = ExpressionObject::create();
+ pExpression = pExpressionObject;
+
+ /* this "object" is not an operator expression */
+ kind = NOTOPERATOR;
+ }
+
+ BSONType fieldType = fieldElement.type();
+ string fieldName(pFieldName);
+ switch (fieldType){
+ case Object: {
+ /* it's a nested document */
+ ObjectCtx oCtx(
+ (pCtx->documentOk() ? ObjectCtx::DOCUMENT_OK : 0)
+ | (pCtx->inclusionOk() ? ObjectCtx::INCLUSION_OK : 0));
+ intrusive_ptr<Expression> pNested(
+ parseObject(&fieldElement, &oCtx));
+ pExpressionObject->addField(fieldName, pNested);
+ break;
+ }
+ case String: {
+ /* it's a renamed field */
+ // CW TODO could also be a constant
+ intrusive_ptr<Expression> pPath(
+ ExpressionFieldPath::create(
+ removeFieldPrefix(fieldElement.str())));
+ pExpressionObject->addField(fieldName, pPath);
+ break;
+ }
+ case Bool:
+ case NumberDouble:
+ case NumberLong:
+ case NumberInt: {
+ /* it's an inclusion specification */
+ if (fieldElement.trueValue()) {
+ uassert(16420, "field inclusion is not allowed inside of $expressions",
+ pCtx->inclusionOk());
+ pExpressionObject->includePath(fieldName);
+ }
+ else {
+ uassert(16406,
+ "The top-level _id field is the only field currently supported for exclusion",
+ pCtx->topLevel() && fieldName == "_id");
+ pExpressionObject->excludeId(true);
+ }
+ break;
+ }
+ default:
+ uassert(15992, str::stream() <<
+ "disallowed field type " << typeName(fieldType) <<
+ " in object expression (at '" <<
+ fieldName << "')", false);
+ }
+ }
+ }
+
+ return pExpression;
+ }
+
+
+ struct OpDesc {
+ const char *pName;
+ intrusive_ptr<ExpressionNary> (*pFactory)(void);
+
+ unsigned flag;
+ static const unsigned FIXED_COUNT = 0x0001;
+ static const unsigned OBJECT_ARG = 0x0002;
+
+ unsigned argCount;
+ };
+
+ static int OpDescCmp(const void *pL, const void *pR) {
+ return strcmp(((const OpDesc *)pL)->pName, ((const OpDesc *)pR)->pName);
+ }
+
+ /*
+ Keep these sorted alphabetically so we can bsearch() them using
+ OpDescCmp() above.
+ */
+ static const OpDesc OpTable[] = {
+ {"$add", ExpressionAdd::create, 0},
+ {"$and", ExpressionAnd::create, 0},
+ {"$cmp", ExpressionCompare::createCmp, OpDesc::FIXED_COUNT, 2},
+ {"$cond", ExpressionCond::create, OpDesc::FIXED_COUNT, 3},
+ // $const handled specially in parseExpression
+ {"$dayOfMonth", ExpressionDayOfMonth::create, OpDesc::FIXED_COUNT, 1},
+ {"$dayOfWeek", ExpressionDayOfWeek::create, OpDesc::FIXED_COUNT, 1},
+ {"$dayOfYear", ExpressionDayOfYear::create, OpDesc::FIXED_COUNT, 1},
+ {"$divide", ExpressionDivide::create, OpDesc::FIXED_COUNT, 2},
+ {"$eq", ExpressionCompare::createEq, OpDesc::FIXED_COUNT, 2},
+ {"$gt", ExpressionCompare::createGt, OpDesc::FIXED_COUNT, 2},
+ {"$gte", ExpressionCompare::createGte, OpDesc::FIXED_COUNT, 2},
+ {"$hour", ExpressionHour::create, OpDesc::FIXED_COUNT, 1},
+ {"$ifNull", ExpressionIfNull::create, OpDesc::FIXED_COUNT, 2},
+ {"$lt", ExpressionCompare::createLt, OpDesc::FIXED_COUNT, 2},
+ {"$lte", ExpressionCompare::createLte, OpDesc::FIXED_COUNT, 2},
+ {"$minute", ExpressionMinute::create, OpDesc::FIXED_COUNT, 1},
+ {"$mod", ExpressionMod::create, OpDesc::FIXED_COUNT, 2},
+ {"$month", ExpressionMonth::create, OpDesc::FIXED_COUNT, 1},
+ {"$multiply", ExpressionMultiply::create, 0},
+ {"$ne", ExpressionCompare::createNe, OpDesc::FIXED_COUNT, 2},
+ {"$not", ExpressionNot::create, OpDesc::FIXED_COUNT, 1},
+ {"$or", ExpressionOr::create, 0},
+ {"$second", ExpressionSecond::create, OpDesc::FIXED_COUNT, 1},
+ {"$strcasecmp", ExpressionStrcasecmp::create, OpDesc::FIXED_COUNT, 2},
+ {"$substr", ExpressionSubstr::create, OpDesc::FIXED_COUNT, 3},
+ {"$subtract", ExpressionSubtract::create, OpDesc::FIXED_COUNT, 2},
+ {"$toLower", ExpressionToLower::create, OpDesc::FIXED_COUNT, 1},
+ {"$toUpper", ExpressionToUpper::create, OpDesc::FIXED_COUNT, 1},
+ {"$week", ExpressionWeek::create, OpDesc::FIXED_COUNT, 1},
+ {"$year", ExpressionYear::create, OpDesc::FIXED_COUNT, 1},
+ };
+
+ static const size_t NOp = sizeof(OpTable)/sizeof(OpTable[0]);
+
+ intrusive_ptr<Expression> Expression::parseExpression(
+ const char *pOpName, BSONElement *pBsonElement) {
+ /* look for the specified operator */
+
+ if (str::equals(pOpName, "$const")) {
+ return ExpressionConstant::createFromBsonElement(pBsonElement);
+ }
+
+ OpDesc key;
+ key.pName = pOpName;
+ const OpDesc *pOp = (const OpDesc *)bsearch(
+ &key, OpTable, NOp, sizeof(OpDesc), OpDescCmp);
+
+ uassert(15999, str::stream() << "invalid operator '" <<
+ pOpName << "'", pOp);
+
+ /* make the expression node */
+ intrusive_ptr<ExpressionNary> pExpression((*pOp->pFactory)());
+
+ /* add the operands to the expression node */
+ BSONType elementType = pBsonElement->type();
+
+ if (pOp->flag & OpDesc::FIXED_COUNT) {
+ if (pOp->argCount > 1)
+ uassert(16019, str::stream() << "the " << pOp->pName <<
+ " operator requires an array of " << pOp->argCount <<
+ " operands", elementType == Array);
+ }
+
+ if (elementType == Object) {
+ /* the operator must be unary and accept an object argument */
+ uassert(16021, str::stream() << "the " << pOp->pName <<
+ " operator does not accept an object as an operand",
+ pOp->flag & OpDesc::OBJECT_ARG);
+
+ BSONObj objOperand(pBsonElement->Obj());
+ ObjectCtx oCtx(ObjectCtx::DOCUMENT_OK);
+ intrusive_ptr<Expression> pOperand(
+ Expression::parseObject(pBsonElement, &oCtx));
+ pExpression->addOperand(pOperand);
+ }
+ else if (elementType == Array) {
+ /* multiple operands - an n-ary operator */
+ vector<BSONElement> bsonArray(pBsonElement->Array());
+ const size_t n = bsonArray.size();
+
+ if (pOp->flag & OpDesc::FIXED_COUNT)
+ uassert(16020, str::stream() << "the " << pOp->pName <<
+ " operator requires " << pOp->argCount <<
+ " operand(s)", pOp->argCount == n);
+
+ for(size_t i = 0; i < n; ++i) {
+ BSONElement *pBsonOperand = &bsonArray[i];
+ intrusive_ptr<Expression> pOperand(
+ Expression::parseOperand(pBsonOperand));
+ pExpression->addOperand(pOperand);
+ }
+ }
+ else {
+ /* assume it's an atomic operand */
+ if (pOp->flag & OpDesc::FIXED_COUNT)
+ uassert(16022, str::stream() << "the " << pOp->pName <<
+ " operator requires an array of " << pOp->argCount <<
+ " operands", pOp->argCount == 1);
+
+ intrusive_ptr<Expression> pOperand(
+ Expression::parseOperand(pBsonElement));
+ pExpression->addOperand(pOperand);
+ }
+
+ return pExpression;
+ }
+
+ intrusive_ptr<Expression> Expression::parseOperand(BSONElement *pBsonElement) {
+ BSONType type = pBsonElement->type();
+
+ if (type == String && pBsonElement->valuestr()[0] == '$') {
+ /* if we got here, this is a field path expression */
+ string fieldPath = removeFieldPrefix(pBsonElement->str());
+ return ExpressionFieldPath::create(fieldPath);
+ }
+ else if (type == Object) {
+ ObjectCtx oCtx(ObjectCtx::DOCUMENT_OK);
+ return Expression::parseObject(pBsonElement, &oCtx);
+ }
+ else {
+ return ExpressionConstant::createFromBsonElement(pBsonElement);
+ }
+ }
+
+ /* ------------------------- ExpressionAdd ----------------------------- */
+
+ ExpressionAdd::~ExpressionAdd() {
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionAdd::create() {
+ intrusive_ptr<ExpressionAdd> pExpression(new ExpressionAdd());
+ return pExpression;
+ }
+
+ intrusive_ptr<const Value> ExpressionAdd::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+
+ /*
+ We'll try to return the narrowest possible result value. To do that
+ without creating intermediate Values, do the arithmetic for double
+ and integral types in parallel, tracking the current narrowest
+ type.
+ */
+ double doubleTotal = 0;
+ long long longTotal = 0;
+ BSONType totalType = NumberInt;
+
+ const size_t n = vpOperand.size();
+ for (size_t i = 0; i < n; ++i) {
+ intrusive_ptr<const Value> pValue(vpOperand[i]->evaluate(pDocument));
+
+ BSONType valueType = pValue->getType();
+ uassert(16415, "$add does not support dates",
+ valueType != Date);
+ uassert(16416, "$add does not support strings",
+ valueType != String);
+
+ totalType = Value::getWidestNumeric(totalType, pValue->getType());
+ doubleTotal += pValue->coerceToDouble();
+ longTotal += pValue->coerceToLong();
+ }
+
+ if (totalType == NumberLong) {
+ return Value::createLong(longTotal);
+ }
+ else if (totalType == NumberDouble) {
+ return Value::createDouble(doubleTotal);
+ }
+ else if (totalType == NumberInt) {
+ return Value::createIntOrLong(longTotal);
+ }
+ else {
+ massert(16417, "$add resulted in a non-numeric type", false);
+ }
+ }
+
+ const char *ExpressionAdd::getOpName() const {
+ return "$add";
+ }
+
+ intrusive_ptr<ExpressionNary> (*ExpressionAdd::getFactory() const)() {
+ return ExpressionAdd::create;
+ }
+
+ /* ------------------------- ExpressionAnd ----------------------------- */
+
+ ExpressionAnd::~ExpressionAnd() {
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionAnd::create() {
+ intrusive_ptr<ExpressionNary> pExpression(new ExpressionAnd());
+ return pExpression;
+ }
+
+ ExpressionAnd::ExpressionAnd():
+ ExpressionNary() {
+ }
+
+ intrusive_ptr<Expression> ExpressionAnd::optimize() {
+ /* optimize the conjunction as much as possible */
+ intrusive_ptr<Expression> pE(ExpressionNary::optimize());
+
+ /* if the result isn't a conjunction, we can't do anything */
+ ExpressionAnd *pAnd = dynamic_cast<ExpressionAnd *>(pE.get());
+ if (!pAnd)
+ return pE;
+
+ /*
+ Check the last argument on the result; if it's not constant (as
+ promised by ExpressionNary::optimize(),) then there's nothing
+ we can do.
+ */
+ const size_t n = pAnd->vpOperand.size();
+ intrusive_ptr<Expression> pLast(pAnd->vpOperand[n - 1]);
+ const ExpressionConstant *pConst =
+ dynamic_cast<ExpressionConstant *>(pLast.get());
+ if (!pConst)
+ return pE;
+
+ /*
+ Evaluate and coerce the last argument to a boolean. If it's false,
+ then we can replace this entire expression.
+ */
+ bool last = pLast->evaluate(intrusive_ptr<Document>())->coerceToBool();
+ if (!last) {
+ intrusive_ptr<ExpressionConstant> pFinal(
+ ExpressionConstant::create(Value::getFalse()));
+ return pFinal;
+ }
+
+ /*
+ If we got here, the final operand was true, so we don't need it
+ anymore. If there was only one other operand, we don't need the
+ conjunction either. Note we still need to keep the promise that
+ the result will be a boolean.
+ */
+ if (n == 2) {
+ intrusive_ptr<Expression> pFinal(
+ ExpressionCoerceToBool::create(pAnd->vpOperand[0]));
+ return pFinal;
+ }
+
+ /*
+ Remove the final "true" value, and return the new expression.
+
+ CW TODO:
+ Note that because of any implicit conversions, we may need to
+ apply an implicit boolean conversion.
+ */
+ pAnd->vpOperand.resize(n - 1);
+ return pE;
+ }
+
+ intrusive_ptr<const Value> ExpressionAnd::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ const size_t n = vpOperand.size();
+ for(size_t i = 0; i < n; ++i) {
+ intrusive_ptr<const Value> pValue(vpOperand[i]->evaluate(pDocument));
+ if (!pValue->coerceToBool())
+ return Value::getFalse();
+ }
+
+ return Value::getTrue();
+ }
+
+ const char *ExpressionAnd::getOpName() const {
+ return "$and";
+ }
+
+ void ExpressionAnd::toMatcherBson(BSONObjBuilder *pBuilder) const {
+ /*
+ There are two patterns we can handle:
+ (1) one or two comparisons on the same field: { a:{$gte:3, $lt:7} }
+ (2) multiple field comparisons: {a:7, b:{$lte:6}, c:2}
+ This can be recognized as a conjunction of a set of range
+ expressions. Direct equality is a degenerate range expression;
+ range expressions can be open-ended.
+ */
+ verify(false && "unimplemented");
+ }
+
+ intrusive_ptr<ExpressionNary> (*ExpressionAnd::getFactory() const)() {
+ return ExpressionAnd::create;
+ }
+
+ /* -------------------- ExpressionCoerceToBool ------------------------- */
+
+ ExpressionCoerceToBool::~ExpressionCoerceToBool() {
+ }
+
+ intrusive_ptr<ExpressionCoerceToBool> ExpressionCoerceToBool::create(
+ const intrusive_ptr<Expression> &pExpression) {
+ intrusive_ptr<ExpressionCoerceToBool> pNew(
+ new ExpressionCoerceToBool(pExpression));
+ return pNew;
+ }
+
+ ExpressionCoerceToBool::ExpressionCoerceToBool(
+ const intrusive_ptr<Expression> &pTheExpression):
+ Expression(),
+ pExpression(pTheExpression) {
+ }
+
+ intrusive_ptr<Expression> ExpressionCoerceToBool::optimize() {
+ /* optimize the operand */
+ pExpression = pExpression->optimize();
+
+ /* if the operand already produces a boolean, then we don't need this */
+ /* LATER - Expression to support a "typeof" query? */
+ Expression *pE = pExpression.get();
+ if (dynamic_cast<ExpressionAnd *>(pE) ||
+ dynamic_cast<ExpressionOr *>(pE) ||
+ dynamic_cast<ExpressionNot *>(pE) ||
+ dynamic_cast<ExpressionCoerceToBool *>(pE))
+ return pExpression;
+
+ return intrusive_ptr<Expression>(this);
+ }
+
+ void ExpressionCoerceToBool::addDependencies(set<string>& deps, vector<string>* path) const {
+ pExpression->addDependencies(deps);
+ }
+
+ intrusive_ptr<const Value> ExpressionCoerceToBool::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+
+ intrusive_ptr<const Value> pResult(pExpression->evaluate(pDocument));
+ bool b = pResult->coerceToBool();
+ if (b)
+ return Value::getTrue();
+ return Value::getFalse();
+ }
+
+ void ExpressionCoerceToBool::addToBsonObj(
+ BSONObjBuilder *pBuilder, string fieldName,
+ bool requireExpression) const {
+ // Serializing as an $and expression which will become a CoerceToBool
+ BSONObjBuilder sub (pBuilder->subobjStart(fieldName));
+ BSONArrayBuilder arr (sub.subarrayStart("$and"));
+ pExpression->addToBsonArray(&arr);
+ arr.doneFast();
+ sub.doneFast();
+ }
+
+ void ExpressionCoerceToBool::addToBsonArray(
+ BSONArrayBuilder *pBuilder) const {
+ // Serializing as an $and expression which will become a CoerceToBool
+ BSONObjBuilder sub (pBuilder->subobjStart());
+ BSONArrayBuilder arr (sub.subarrayStart("$and"));
+ pExpression->addToBsonArray(&arr);
+ arr.doneFast();
+ sub.doneFast();
+ }
+
+ /* ----------------------- ExpressionCompare --------------------------- */
+
+ ExpressionCompare::~ExpressionCompare() {
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionCompare::createEq() {
+ intrusive_ptr<ExpressionCompare> pExpression(
+ new ExpressionCompare(EQ));
+ return pExpression;
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionCompare::createNe() {
+ intrusive_ptr<ExpressionCompare> pExpression(
+ new ExpressionCompare(NE));
+ return pExpression;
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionCompare::createGt() {
+ intrusive_ptr<ExpressionCompare> pExpression(
+ new ExpressionCompare(GT));
+ return pExpression;
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionCompare::createGte() {
+ intrusive_ptr<ExpressionCompare> pExpression(
+ new ExpressionCompare(GTE));
+ return pExpression;
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionCompare::createLt() {
+ intrusive_ptr<ExpressionCompare> pExpression(
+ new ExpressionCompare(LT));
+ return pExpression;
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionCompare::createLte() {
+ intrusive_ptr<ExpressionCompare> pExpression(
+ new ExpressionCompare(LTE));
+ return pExpression;
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionCompare::createCmp() {
+ intrusive_ptr<ExpressionCompare> pExpression(
+ new ExpressionCompare(CMP));
+ return pExpression;
+ }
+
+ ExpressionCompare::ExpressionCompare(CmpOp theCmpOp):
+ ExpressionNary(),
+ cmpOp(theCmpOp) {
+ }
+
+ void ExpressionCompare::addOperand(
+ const intrusive_ptr<Expression> &pExpression) {
+ checkArgLimit(2);
+ ExpressionNary::addOperand(pExpression);
+ }
+
+ /*
+ Lookup table for truth value returns
+ */
+ struct CmpLookup {
+ bool truthValue[3]; /* truth value for -1, 0, 1 */
+ Expression::CmpOp reverse; /* reverse comparison operator */
+ char name[5]; /* string name (w/trailing '\0') */
+ };
+ static const CmpLookup cmpLookup[7] = {
+ /* -1 0 1 reverse name */
+ /* EQ */ { { false, true, false }, Expression::EQ, "$eq" },
+ /* NE */ { { true, false, true }, Expression::NE, "$ne" },
+ /* GT */ { { false, false, true }, Expression::LT, "$gt" },
+ /* GTE */ { { false, true, true }, Expression::LTE, "$gte" },
+ /* LT */ { { true, false, false }, Expression::GT, "$lt" },
+ /* LTE */ { { true, true, false }, Expression::GTE, "$lte" },
+ /* CMP */ { { false, false, false }, Expression::CMP, "$cmp" },
+ };
+
+ intrusive_ptr<Expression> ExpressionCompare::optimize() {
+ /* first optimize the comparison operands */
+ intrusive_ptr<Expression> pE(ExpressionNary::optimize());
+
+ /*
+ If the result of optimization is no longer a comparison, there's
+ nothing more we can do.
+ */
+ ExpressionCompare *pCmp = dynamic_cast<ExpressionCompare *>(pE.get());
+ if (!pCmp)
+ return pE;
+
+ /* check to see if optimizing comparison operator is supported */
+ CmpOp newOp = pCmp->cmpOp;
+ // CMP and NE cannot use ExpressionFieldRange which is what this optimization uses
+ if (newOp == CMP || newOp == NE)
+ return pE;
+
+ /*
+ There's one localized optimization we recognize: a comparison
+ between a field and a constant. If we recognize that pattern,
+ replace it with an ExpressionFieldRange.
+
+ When looking for this pattern, note that the operands could appear
+ in any order. If we need to reverse the sense of the comparison to
+ put it into the required canonical form, do so.
+ */
+ intrusive_ptr<Expression> pLeft(pCmp->vpOperand[0]);
+ intrusive_ptr<Expression> pRight(pCmp->vpOperand[1]);
+ intrusive_ptr<ExpressionFieldPath> pFieldPath(
+ dynamic_pointer_cast<ExpressionFieldPath>(pLeft));
+ intrusive_ptr<ExpressionConstant> pConstant;
+ if (pFieldPath.get()) {
+ pConstant = dynamic_pointer_cast<ExpressionConstant>(pRight);
+ if (!pConstant.get())
+ return pE; // there's nothing more we can do
+ }
+ else {
+ /* if the first operand wasn't a path, see if it's a constant */
+ pConstant = dynamic_pointer_cast<ExpressionConstant>(pLeft);
+ if (!pConstant.get())
+ return pE; // there's nothing more we can do
+
+ /* the left operand was a constant; see if the right is a path */
+ pFieldPath = dynamic_pointer_cast<ExpressionFieldPath>(pRight);
+ if (!pFieldPath.get())
+ return pE; // there's nothing more we can do
+
+ /* these were not in canonical order, so reverse the sense */
+ newOp = cmpLookup[newOp].reverse;
+ }
+
+ return ExpressionFieldRange::create(
+ pFieldPath, newOp, pConstant->getValue());
+ }
+
+ intrusive_ptr<const Value> ExpressionCompare::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ checkArgCount(2);
+ intrusive_ptr<const Value> pLeft(vpOperand[0]->evaluate(pDocument));
+ intrusive_ptr<const Value> pRight(vpOperand[1]->evaluate(pDocument));
+
+ int cmp = signum(Value::compare(pLeft, pRight));
+
+ if (cmpOp == CMP) {
+ switch(cmp) {
+ case -1:
+ return Value::getMinusOne();
+ case 0:
+ return Value::getZero();
+ case 1:
+ return Value::getOne();
+
+ default:
+ verify(false); // CW TODO internal error
+ return Value::getNull();
+ }
+ }
+
+ bool returnValue = cmpLookup[cmpOp].truthValue[cmp + 1];
+ if (returnValue)
+ return Value::getTrue();
+ return Value::getFalse();
+ }
+
+ const char *ExpressionCompare::getOpName() const {
+ return cmpLookup[cmpOp].name;
+ }
+
+ /* ----------------------- ExpressionCond ------------------------------ */
+
+ ExpressionCond::~ExpressionCond() {
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionCond::create() {
+ intrusive_ptr<ExpressionCond> pExpression(new ExpressionCond());
+ return pExpression;
+ }
+
+ ExpressionCond::ExpressionCond():
+ ExpressionNary() {
+ }
+
+ void ExpressionCond::addOperand(
+ const intrusive_ptr<Expression> &pExpression) {
+ checkArgLimit(3);
+ ExpressionNary::addOperand(pExpression);
+ }
+
+ intrusive_ptr<const Value> ExpressionCond::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ checkArgCount(3);
+ intrusive_ptr<const Value> pCond(vpOperand[0]->evaluate(pDocument));
+ int idx = pCond->coerceToBool() ? 1 : 2;
+ return vpOperand[idx]->evaluate(pDocument);
+ }
+
+ const char *ExpressionCond::getOpName() const {
+ return "$cond";
+ }
+
+ /* ---------------------- ExpressionConstant --------------------------- */
+
+ ExpressionConstant::~ExpressionConstant() {
+ }
+
+ intrusive_ptr<ExpressionConstant> ExpressionConstant::createFromBsonElement(
+ BSONElement *pBsonElement) {
+ intrusive_ptr<ExpressionConstant> pEC(
+ new ExpressionConstant(pBsonElement));
+ return pEC;
+ }
+
+ ExpressionConstant::ExpressionConstant(BSONElement *pBsonElement):
+ pValue(Value::createFromBsonElement(pBsonElement)) {
+ }
+
+ intrusive_ptr<ExpressionConstant> ExpressionConstant::create(
+ const intrusive_ptr<const Value> &pValue) {
+ intrusive_ptr<ExpressionConstant> pEC(new ExpressionConstant(pValue));
+ return pEC;
+ }
+
+ ExpressionConstant::ExpressionConstant(
+ const intrusive_ptr<const Value> &pTheValue):
+ pValue(pTheValue) {
+ }
+
+
+ intrusive_ptr<Expression> ExpressionConstant::optimize() {
+ /* nothing to do */
+ return intrusive_ptr<Expression>(this);
+ }
+
+ void ExpressionConstant::addDependencies(set<string>& deps, vector<string>* path) const {
+ /* nothing to do */
+ }
+
+ intrusive_ptr<const Value> ExpressionConstant::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ return pValue;
+ }
+
+ void ExpressionConstant::addToBsonObj(
+ BSONObjBuilder *pBuilder, string fieldName,
+ bool requireExpression) const {
+ /*
+ If we don't need an expression, but can use a naked scalar,
+ do the regular thing.
+
+ This is geared to handle $project, which uses expressions as a cue
+ that the field is a new virtual field rather than just an
+ inclusion (or exclusion):
+ { $project : {
+ x : true, // include
+ y : { $const: true }
+ }}
+
+ This can happen as a result of optimizations. For example, the
+ above may have originally been
+ { $project : {
+ x : true, // include
+ y : { $eq:["foo", "foo"] }
+ }}
+ When this is optimized, the $eq will be replaced with true. However,
+ if the pipeline is rematerialized (as happens for a split for
+ sharding) and sent to another node, it will now have
+ y : true
+ which will look like an inclusion rather than a computed field.
+ */
+ if (!requireExpression) {
+ pValue->addToBsonObj(pBuilder, fieldName);
+ return;
+ }
+
+ // We require an expression, so build one here, and use that.
+ BSONObjBuilder constBuilder (pBuilder->subobjStart(fieldName));
+ pValue->addToBsonObj(&constBuilder, getOpName());
+ constBuilder.done();
+ }
+
+ void ExpressionConstant::addToBsonArray(
+ BSONArrayBuilder *pBuilder) const {
+ pValue->addToBsonArray(pBuilder);
+ }
+
+ const char *ExpressionConstant::getOpName() const {
+ return "$const";
+ }
+
+ /* ---------------------- ExpressionDayOfMonth ------------------------- */
+
+ ExpressionDayOfMonth::~ExpressionDayOfMonth() {
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionDayOfMonth::create() {
+ intrusive_ptr<ExpressionDayOfMonth> pExpression(new ExpressionDayOfMonth());
+ return pExpression;
+ }
+
+ ExpressionDayOfMonth::ExpressionDayOfMonth():
+ ExpressionNary() {
+ }
+
+ void ExpressionDayOfMonth::addOperand(const intrusive_ptr<Expression> &pExpression) {
+ checkArgLimit(1);
+
+ ExpressionNary::addOperand(pExpression);
+ }
+
+ intrusive_ptr<const Value> ExpressionDayOfMonth::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ checkArgCount(1);
+ intrusive_ptr<const Value> pDate(vpOperand[0]->evaluate(pDocument));
+ tm date = pDate->coerceToTm();
+ return Value::createInt(date.tm_mday);
+ }
+
+ const char *ExpressionDayOfMonth::getOpName() const {
+ return "$dayOfMonth";
+ }
+
+ /* ------------------------- ExpressionDayOfWeek ----------------------------- */
+
+ ExpressionDayOfWeek::~ExpressionDayOfWeek() {
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionDayOfWeek::create() {
+ intrusive_ptr<ExpressionDayOfWeek> pExpression(new ExpressionDayOfWeek());
+ return pExpression;
+ }
+
+ ExpressionDayOfWeek::ExpressionDayOfWeek():
+ ExpressionNary() {
+ }
+
+ void ExpressionDayOfWeek::addOperand(const intrusive_ptr<Expression> &pExpression) {
+ checkArgLimit(1);
+ ExpressionNary::addOperand(pExpression);
+ }
+
+ intrusive_ptr<const Value> ExpressionDayOfWeek::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ checkArgCount(1);
+ intrusive_ptr<const Value> pDate(vpOperand[0]->evaluate(pDocument));
+ tm date = pDate->coerceToTm();
+ return Value::createInt(date.tm_wday+1); // MySQL uses 1-7 tm uses 0-6
+ }
+
+ const char *ExpressionDayOfWeek::getOpName() const {
+ return "$dayOfWeek";
+ }
+
+ /* ------------------------- ExpressionDayOfYear ----------------------------- */
+
+ ExpressionDayOfYear::~ExpressionDayOfYear() {
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionDayOfYear::create() {
+ intrusive_ptr<ExpressionDayOfYear> pExpression(new ExpressionDayOfYear());
+ return pExpression;
+ }
+
+ ExpressionDayOfYear::ExpressionDayOfYear():
+ ExpressionNary() {
+ }
+
+ void ExpressionDayOfYear::addOperand(const intrusive_ptr<Expression> &pExpression) {
+ checkArgLimit(1);
+ ExpressionNary::addOperand(pExpression);
+ }
+
+ intrusive_ptr<const Value> ExpressionDayOfYear::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ checkArgCount(1);
+ intrusive_ptr<const Value> pDate(vpOperand[0]->evaluate(pDocument));
+ tm date = pDate->coerceToTm();
+ return Value::createInt(date.tm_yday+1); // MySQL uses 1-366 tm uses 0-365
+ }
+
+ const char *ExpressionDayOfYear::getOpName() const {
+ return "$dayOfYear";
+ }
+
+ /* ----------------------- ExpressionDivide ---------------------------- */
+
+ ExpressionDivide::~ExpressionDivide() {
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionDivide::create() {
+ intrusive_ptr<ExpressionDivide> pExpression(new ExpressionDivide());
+ return pExpression;
+ }
+
+ ExpressionDivide::ExpressionDivide():
+ ExpressionNary() {
+ }
+
+ void ExpressionDivide::addOperand(
+ const intrusive_ptr<Expression> &pExpression) {
+ checkArgLimit(2);
+ ExpressionNary::addOperand(pExpression);
+ }
+
+ intrusive_ptr<const Value> ExpressionDivide::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ checkArgCount(2);
+ intrusive_ptr<const Value> pLeft(vpOperand[0]->evaluate(pDocument));
+ intrusive_ptr<const Value> pRight(vpOperand[1]->evaluate(pDocument));
+
+ uassert(16373,
+ "$divide does not support dates",
+ pLeft->getType() != Date && pRight->getType() != Date);
+
+ double right = pRight->coerceToDouble();
+ if (right == 0)
+ return Value::getUndefined();
+
+ double left = pLeft->coerceToDouble();
+
+ return Value::createDouble(left / right);
+ }
+
+ const char *ExpressionDivide::getOpName() const {
+ return "$divide";
+ }
+
+ /* ---------------------- ExpressionObject --------------------------- */
+
+ ExpressionObject::~ExpressionObject() {
+ }
+
+ intrusive_ptr<ExpressionObject> ExpressionObject::create() {
+ intrusive_ptr<ExpressionObject> pExpression(new ExpressionObject());
+ return pExpression;
+ }
+
+ ExpressionObject::ExpressionObject(): _excludeId(false) {
+ }
+
+ intrusive_ptr<Expression> ExpressionObject::optimize() {
+ for (ExpressionMap::iterator it(_expressions.begin()); it!=_expressions.end(); ++it) {
+ if (it->second)
+ it->second = it->second->optimize();
+ }
+
+ return intrusive_ptr<Expression>(this);
+ }
+ bool ExpressionObject::isSimple() {
+ for (ExpressionMap::iterator it(_expressions.begin()); it!=_expressions.end(); ++it) {
+ if (it->second && !it->second->isSimple())
+ return false;
+ }
+ return true;
+ }
+
+ void ExpressionObject::addDependencies(set<string>& deps, vector<string>* path) const {
+ string pathStr;
+ if (path) {
+ if (path->empty()) {
+ // we are in the top-level so _id is implicit
+ if (!_excludeId)
+ deps.insert("_id");
+ }
+ else {
+ FieldPath f (*path);
+ pathStr = f.getPath(false);
+ pathStr += '.';
+ }
+ }
+
+ for (ExpressionMap::const_iterator it(_expressions.begin()); it!=_expressions.end(); ++it) {
+ if (it->second) {
+ if (path) path->push_back(it->first);
+ it->second->addDependencies(deps, path);
+ if (path) path->pop_back();
+ }
+ else { // inclusion
+ uassert(16407, "inclusion not supported in objects nested in $expressions",
+ path);
+
+ deps.insert(pathStr + it->first);
+ }
+ }
+ }
+
+ void ExpressionObject::addToDocument(
+ const intrusive_ptr<Document> &pResult,
+ const intrusive_ptr<Document> &pDocument,
+ const intrusive_ptr<Document> &rootDoc
+ ) const
+ {
+ const bool atRoot = (pDocument == rootDoc);
+
+ ExpressionMap::const_iterator end = _expressions.end();
+
+ // This is used to mark fields we've done so that we can add the ones we haven't
+ set<string> doneFields;
+
+ FieldIterator fields(pDocument);
+ while(fields.more()) {
+ Document::FieldPair field (fields.next());
+
+ ExpressionMap::const_iterator exprIter = _expressions.find(field.first);
+
+ // This field is not supposed to be in the output (unless it is _id)
+ if (exprIter == end) {
+ if (!_excludeId && atRoot && field.first == "_id") {
+ // _id from the root doc is always included (until exclusion is supported)
+ // not updating doneFields since "_id" isn't in _expressions
+ pResult->addField(field.first, field.second);
+ }
+ continue;
+ }
+
+ // make sure we don't add this field again
+ doneFields.insert(exprIter->first);
+
+ Expression* expr = exprIter->second.get();
+
+ if (!expr) {
+ // This means pull the matching field from the input document
+ pResult->addField(field.first, field.second);
+ continue;
+ }
+
+ ExpressionObject* exprObj = dynamic_cast<ExpressionObject*>(expr);
+ BSONType valueType = field.second->getType();
+ if ((valueType != Object && valueType != Array) || !exprObj ) {
+ // This expression replace the whole field
+
+ intrusive_ptr<const Value> pValue(expr->evaluate(rootDoc));
+
+ // don't add field if nothing was found in the subobject
+ if (exprObj && pValue->getDocument()->getFieldCount() == 0)
+ continue;
+
+ /*
+ 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.
+ */
+ // TODO make missing distinct from Undefined
+ if (pValue->getType() != Undefined)
+ pResult->addField(field.first, pValue);
+
+
+ continue;
+ }
+
+ /*
+ 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.
+ */
+ if (valueType == Object) {
+ intrusive_ptr<Document> doc = Document::create(exprObj->getSizeHint());
+ exprObj->addToDocument(doc,
+ field.second->getDocument(),
+ rootDoc);
+ pResult->addField(field.first, Value::createDocument(doc));
+ }
+ else if (valueType == Array) {
+ /*
+ 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.
+ */
+ vector<intrusive_ptr<const Value> > result;
+ intrusive_ptr<ValueIterator> pVI(field.second->getArray());
+ while(pVI->more()) {
+ intrusive_ptr<const Value> next = pVI->next();
+
+ // can't look for a subfield in a non-object value.
+ if (next->getType() != Object)
+ continue;
+
+ intrusive_ptr<Document> doc = Document::create(exprObj->getSizeHint());
+ exprObj->addToDocument(doc,
+ next->getDocument(),
+ rootDoc);
+ result.push_back(Value::createDocument(doc));
+ }
+
+ pResult->addField(field.first,
+ Value::createArray(result));
+ }
+ }
+
+ if (doneFields.size() == _expressions.size())
+ return;
+
+ /* add any remaining fields we haven't already taken care of */
+ for (vector<string>::const_iterator i(_order.begin()); i!=_order.end(); ++i) {
+ ExpressionMap::const_iterator it = _expressions.find(*i);
+ string fieldName(it->first);
+
+ /* if we've already dealt with this field, above, do nothing */
+ if (doneFields.count(fieldName))
+ continue;
+
+ // this is a missing inclusion field
+ if (!it->second)
+ continue;
+
+ intrusive_ptr<const Value> pValue(it->second->evaluate(rootDoc));
+
+ /*
+ 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.
+ */
+ if (pValue->getType() == Undefined)
+ continue;
+
+ // don't add field if nothing was found in the subobject
+ if (dynamic_cast<ExpressionObject*>(it->second.get())
+ && pValue->getDocument()->getFieldCount() == 0)
+ continue;
+
+
+ pResult->addField(fieldName, pValue);
+ }
+ }
+
+ size_t ExpressionObject::getSizeHint() const {
+ // Note: this can overestimate, but that is better than underestimating
+ return _expressions.size() + (_excludeId ? 0 : 1);
+ }
+
+ intrusive_ptr<Document> ExpressionObject::evaluateDocument(
+ const intrusive_ptr<Document> &pDocument) const {
+ /* create and populate the result */
+ intrusive_ptr<Document> pResult(
+ Document::create(getSizeHint()));
+ addToDocument(pResult, Document::create(), pDocument);
+ return pResult;
+ }
+
+ intrusive_ptr<const Value> ExpressionObject::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ return Value::createDocument(evaluateDocument(pDocument));
+ }
+
+ void ExpressionObject::addField(const FieldPath &fieldPath,
+ const intrusive_ptr<Expression> &pExpression) {
+ const string fieldPart = fieldPath.getFieldName(0);
+ const bool haveExpr = _expressions.count(fieldPart);
+
+ intrusive_ptr<Expression>& expr = _expressions[fieldPart]; // inserts if !haveExpr
+ intrusive_ptr<ExpressionObject> subObj = dynamic_cast<ExpressionObject*>(expr.get());
+
+ if (!haveExpr) {
+ _order.push_back(fieldPart);
+ }
+ else { // we already have an expression or inclusion for this field
+ if (fieldPath.getPathLength() == 1) {
+ // This expression is for right here
+
+ ExpressionObject* newSubObj = dynamic_cast<ExpressionObject*>(pExpression.get());
+ uassert(16400, str::stream()
+ << "can't add an expression for field " << fieldPart
+ << " because there is already an expression for that field"
+ << " or one of its sub-fields.",
+ subObj && newSubObj); // we can merge them
+
+ // Copy everything from the newSubObj to the existing subObj
+ // This is for cases like { $project:{ 'b.c':1, b:{ a:1 } } }
+ for (vector<string>::const_iterator it (newSubObj->_order.begin());
+ it != newSubObj->_order.end();
+ ++it) {
+ // asserts if any fields are dupes
+ subObj->addField(*it, newSubObj->_expressions[*it]);
+ }
+ return;
+ }
+ else {
+ // This expression is for a subfield
+ uassert(16401, str::stream()
+ << "can't add an expression for a subfield of " << fieldPart
+ << " because there is already an expression that applies to"
+ << " the whole field",
+ subObj);
+ }
+ }
+
+ if (fieldPath.getPathLength() == 1) {
+ expr = pExpression;
+ return;
+ }
+
+ if (!haveExpr)
+ expr = subObj = ExpressionObject::create();
+
+ subObj->addField(fieldPath.tail(), pExpression);
+ }
+
+ void ExpressionObject::includePath(const string &theFieldPath) {
+ addField(theFieldPath, NULL);
+ }
+
+ void ExpressionObject::documentToBson(BSONObjBuilder *pBuilder, bool requireExpression) const {
+ if (_excludeId)
+ pBuilder->appendBool("_id", false);
+
+ for (vector<string>::const_iterator it(_order.begin()); it!=_order.end(); ++it) {
+ string fieldName = *it;
+ verify(_expressions.find(fieldName) != _expressions.end());
+ intrusive_ptr<Expression> expr = _expressions.find(fieldName)->second;
+
+ if (!expr) {
+ // this is inclusion, not an expression
+ pBuilder->appendBool(fieldName, true);
+ }
+ else {
+ expr->addToBsonObj(pBuilder, fieldName, requireExpression);
+ }
+ }
+ }
+
+ void ExpressionObject::addToBsonObj(
+ BSONObjBuilder *pBuilder, string fieldName,
+ bool requireExpression) const {
+
+ BSONObjBuilder objBuilder (pBuilder->subobjStart(fieldName));
+ documentToBson(&objBuilder, requireExpression);
+ objBuilder.done();
+ }
+
+ void ExpressionObject::addToBsonArray(
+ BSONArrayBuilder *pBuilder) const {
+
+ BSONObjBuilder objBuilder (pBuilder->subobjStart());
+ documentToBson(&objBuilder, false);
+ objBuilder.done();
+ }
+
+ void ExpressionObject::BuilderPathSink::path(
+ const string &path, bool include) {
+ pBuilder->append(path, include);
+ }
+
+ /* --------------------- ExpressionFieldPath --------------------------- */
+
+ ExpressionFieldPath::~ExpressionFieldPath() {
+ }
+
+ intrusive_ptr<ExpressionFieldPath> ExpressionFieldPath::create(
+ const string &fieldPath) {
+ intrusive_ptr<ExpressionFieldPath> pExpression(
+ new ExpressionFieldPath(fieldPath));
+ return pExpression;
+ }
+
+ ExpressionFieldPath::ExpressionFieldPath(
+ const string &theFieldPath):
+ fieldPath(theFieldPath) {
+ }
+
+ intrusive_ptr<Expression> ExpressionFieldPath::optimize() {
+ /* nothing can be done for these */
+ return intrusive_ptr<Expression>(this);
+ }
+
+ void ExpressionFieldPath::addDependencies(set<string>& deps, vector<string>* path) const {
+ deps.insert(fieldPath.getPath(false));
+ }
+
+ intrusive_ptr<const Value> ExpressionFieldPath::evaluatePath(
+ size_t index, const size_t pathLength,
+ intrusive_ptr<Document> pDocument) const {
+ intrusive_ptr<const Value> pValue; /* the return value */
+
+ pValue = pDocument->getValue(fieldPath.getFieldName(index));
+
+ /* if the field doesn't exist, quit with an undefined value */
+ if (!pValue.get())
+ return Value::getUndefined();
+
+ /* if we've hit the end of the path, stop */
+ ++index;
+ if (index >= pathLength)
+ return pValue;
+
+ /*
+ We're diving deeper. If the value was null, return null.
+ */
+ BSONType type = pValue->getType();
+ if ((type == Undefined) || (type == jstNULL))
+ return Value::getUndefined();
+
+ if (type == Object) {
+ /* extract from the next level down */
+ return evaluatePath(index, pathLength, pValue->getDocument());
+ }
+
+ if (type == Array) {
+ /*
+ We're going to repeat this for each member of the array,
+ building up a new array as we go.
+ */
+ vector<intrusive_ptr<const Value> > result;
+ intrusive_ptr<ValueIterator> pIter(pValue->getArray());
+ while(pIter->more()) {
+ intrusive_ptr<const Value> pItem(pIter->next());
+ BSONType iType = pItem->getType();
+ if ((iType == Undefined) || (iType == jstNULL)) {
+ result.push_back(pItem);
+ continue;
+ }
+
+ uassert(16014, str::stream() <<
+ "the element '" << fieldPath.getFieldName(index) <<
+ "' along the dotted path '" <<
+ fieldPath.getPath(false) <<
+ "' is not an object, and cannot be navigated",
+ iType == Object);
+ intrusive_ptr<const Value> itemResult(
+ evaluatePath(index, pathLength, pItem->getDocument()));
+ result.push_back(itemResult);
+ }
+
+ return Value::createArray(result);
+ }
+ // subdocument field does not exist, return undefined
+ return Value::getUndefined();
+ }
+
+ intrusive_ptr<const Value> ExpressionFieldPath::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ return evaluatePath(0, fieldPath.getPathLength(), pDocument);
+ }
+
+ void ExpressionFieldPath::addToBsonObj(
+ BSONObjBuilder *pBuilder, string fieldName,
+ bool requireExpression) const {
+ pBuilder->append(fieldName, fieldPath.getPath(true));
+ }
+
+ void ExpressionFieldPath::addToBsonArray(
+ BSONArrayBuilder *pBuilder) const {
+ pBuilder->append(getFieldPath(true));
+ }
+
+ /* --------------------- ExpressionFieldRange -------------------------- */
+
+ ExpressionFieldRange::~ExpressionFieldRange() {
+ }
+
+ intrusive_ptr<Expression> ExpressionFieldRange::optimize() {
+ /* if there is no range to match, this will never evaluate true */
+ if (!pRange.get())
+ return ExpressionConstant::create(Value::getFalse());
+
+ /*
+ If we ended up with a double un-ended range, anything matches. I
+ don't know how that can happen, given intersect()'s interface, but
+ here it is, just in case.
+ */
+ if (!pRange->pBottom.get() && !pRange->pTop.get())
+ return ExpressionConstant::create(Value::getTrue());
+
+ /*
+ In all other cases, we have to test candidate values. The
+ intersect() method has already optimized those tests, so there
+ aren't any more optimizations to look for here.
+ */
+ return intrusive_ptr<Expression>(this);
+ }
+
+ void ExpressionFieldRange::addDependencies(set<string>& deps, vector<string>* path) const {
+ pFieldPath->addDependencies(deps);
+ }
+
+ intrusive_ptr<const Value> ExpressionFieldRange::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ /* if there's no range, there can't be a match */
+ if (!pRange.get())
+ return Value::getFalse();
+
+ /* get the value of the specified field */
+ intrusive_ptr<const Value> pValue(pFieldPath->evaluate(pDocument));
+
+ /* see if it fits within any of the ranges */
+ if (pRange->contains(pValue))
+ return Value::getTrue();
+
+ return Value::getFalse();
+ }
+
+ void ExpressionFieldRange::addToBson(Builder *pBuilder) const {
+ if (!pRange.get()) {
+ /* nothing will satisfy this predicate */
+ pBuilder->append(false);
+ return;
+ }
+
+ if (!pRange->pTop.get() && !pRange->pBottom.get()) {
+ /* any value will satisfy this predicate */
+ pBuilder->append(true);
+ return;
+ }
+
+ if (pRange->pTop.get() == pRange->pBottom.get()) {
+ BSONArrayBuilder operands;
+ pFieldPath->addToBsonArray(&operands);
+ pRange->pTop->addToBsonArray(&operands);
+
+ BSONObjBuilder equals;
+ equals.append("$eq", operands.arr());
+ pBuilder->append(&equals);
+ return;
+ }
+
+ BSONObjBuilder leftOperator;
+ if (pRange->pBottom.get()) {
+ BSONArrayBuilder leftOperands;
+ pFieldPath->addToBsonArray(&leftOperands);
+ pRange->pBottom->addToBsonArray(&leftOperands);
+ leftOperator.append(
+ (pRange->bottomOpen ? "$gt" : "$gte"),
+ leftOperands.arr());
+
+ if (!pRange->pTop.get()) {
+ pBuilder->append(&leftOperator);
+ return;
+ }
+ }
+
+ BSONObjBuilder rightOperator;
+ if (pRange->pTop.get()) {
+ BSONArrayBuilder rightOperands;
+ pFieldPath->addToBsonArray(&rightOperands);
+ pRange->pTop->addToBsonArray(&rightOperands);
+ rightOperator.append(
+ (pRange->topOpen ? "$lt" : "$lte"),
+ rightOperands.arr());
+
+ if (!pRange->pBottom.get()) {
+ pBuilder->append(&rightOperator);
+ return;
+ }
+ }
+
+ BSONArrayBuilder andOperands;
+ andOperands.append(leftOperator.done());
+ andOperands.append(rightOperator.done());
+ BSONObjBuilder andOperator;
+ andOperator.append("$and", andOperands.arr());
+ pBuilder->append(&andOperator);
+ }
+
+ void ExpressionFieldRange::addToBsonObj(
+ BSONObjBuilder *pBuilder, string fieldName,
+ bool requireExpression) const {
+ BuilderObj builder(pBuilder, fieldName);
+ addToBson(&builder);
+ }
+
+ void ExpressionFieldRange::addToBsonArray(
+ BSONArrayBuilder *pBuilder) const {
+ BuilderArray builder(pBuilder);
+ addToBson(&builder);
+ }
+
+ void ExpressionFieldRange::toMatcherBson(
+ BSONObjBuilder *pBuilder) const {
+ verify(pRange.get()); // otherwise, we can't do anything
+
+ /* if there are no endpoints, then every value is accepted */
+ if (!pRange->pBottom.get() && !pRange->pTop.get())
+ return; // nothing to add to the predicate
+
+ /* we're going to need the field path */
+ string fieldPath(pFieldPath->getFieldPath(false));
+
+ BSONObjBuilder range;
+ if (pRange->pBottom.get()) {
+ /* the test for equality doesn't generate a subobject */
+ if (pRange->pBottom.get() == pRange->pTop.get()) {
+ pRange->pBottom->addToBsonObj(pBuilder, fieldPath);
+ return;
+ }
+
+ pRange->pBottom->addToBsonObj(
+ pBuilder, (pRange->bottomOpen ? "$gt" : "$gte"));
+ }
+
+ if (pRange->pTop.get()) {
+ pRange->pTop->addToBsonObj(
+ pBuilder, (pRange->topOpen ? "$lt" : "$lte"));
+ }
+
+ pBuilder->append(fieldPath, range.done());
+ }
+
+ intrusive_ptr<ExpressionFieldRange> ExpressionFieldRange::create(
+ const intrusive_ptr<ExpressionFieldPath> &pFieldPath, CmpOp cmpOp,
+ const intrusive_ptr<const Value> &pValue) {
+ intrusive_ptr<ExpressionFieldRange> pE(
+ new ExpressionFieldRange(pFieldPath, cmpOp, pValue));
+ return pE;
+ }
+
+ ExpressionFieldRange::ExpressionFieldRange(
+ const intrusive_ptr<ExpressionFieldPath> &pTheFieldPath, CmpOp cmpOp,
+ const intrusive_ptr<const Value> &pValue):
+ pFieldPath(pTheFieldPath),
+ pRange(new Range(cmpOp, pValue)) {
+ }
+
+ void ExpressionFieldRange::intersect(
+ CmpOp cmpOp, const intrusive_ptr<const Value> &pValue) {
+
+ /* create the new range */
+ scoped_ptr<Range> pNew(new Range(cmpOp, pValue));
+
+ /*
+ Go through the range list. For every range, either add the
+ intersection of that to the range list, or if there is none, the
+ original range. This has the effect of restricting overlapping
+ ranges, but leaving non-overlapping ones as-is.
+ */
+ pRange.reset(pRange->intersect(pNew.get()));
+ }
+
+ ExpressionFieldRange::Range::Range(
+ CmpOp cmpOp, const intrusive_ptr<const Value> &pValue):
+ bottomOpen(false),
+ topOpen(false),
+ pBottom(),
+ pTop() {
+ switch(cmpOp) {
+ case NE:
+ bottomOpen = topOpen = true;
+ /* FALLTHROUGH */
+ case EQ:
+ pBottom = pTop = pValue;
+ break;
+
+ case GT:
+ bottomOpen = true;
+ /* FALLTHROUGH */
+ case GTE:
+ topOpen = true;
+ pBottom = pValue;
+ break;
+
+ case LT:
+ topOpen = true;
+ /* FALLTHROUGH */
+ case LTE:
+ bottomOpen = true;
+ pTop = pValue;
+ break;
+
+ case CMP:
+ verify(false); // not allowed
+ break;
+ }
+ }
+
+ ExpressionFieldRange::Range::Range(const Range &rRange):
+ bottomOpen(rRange.bottomOpen),
+ topOpen(rRange.topOpen),
+ pBottom(rRange.pBottom),
+ pTop(rRange.pTop) {
+ }
+
+ ExpressionFieldRange::Range::Range(
+ const intrusive_ptr<const Value> &pTheBottom, bool theBottomOpen,
+ const intrusive_ptr<const Value> &pTheTop, bool theTopOpen):
+ bottomOpen(theBottomOpen),
+ topOpen(theTopOpen),
+ pBottom(pTheBottom),
+ pTop(pTheTop) {
+ }
+
+ ExpressionFieldRange::Range *ExpressionFieldRange::Range::intersect(
+ const Range *pRange) const {
+ /*
+ Find the max of the bottom end of the ranges.
+
+ Start by assuming the maximum is from pRange. Then, if we have
+ values of our own, see if they're greater.
+ */
+ intrusive_ptr<const Value> pMaxBottom(pRange->pBottom);
+ bool maxBottomOpen = pRange->bottomOpen;
+ if (pBottom.get()) {
+ if (!pRange->pBottom.get()) {
+ pMaxBottom = pBottom;
+ maxBottomOpen = bottomOpen;
+ }
+ else {
+ const int cmp = Value::compare(pBottom, pRange->pBottom);
+ if (cmp == 0)
+ maxBottomOpen = bottomOpen || pRange->bottomOpen;
+ else if (cmp > 0) {
+ pMaxBottom = pBottom;
+ maxBottomOpen = bottomOpen;
+ }
+ }
+ }
+
+ /*
+ Find the minimum of the tops of the ranges.
+
+ Start by assuming the minimum is from pRange. Then, if we have
+ values of our own, see if they are less.
+ */
+ intrusive_ptr<const Value> pMinTop(pRange->pTop);
+ bool minTopOpen = pRange->topOpen;
+ if (pTop.get()) {
+ if (!pRange->pTop.get()) {
+ pMinTop = pTop;
+ minTopOpen = topOpen;
+ }
+ else {
+ const int cmp = Value::compare(pTop, pRange->pTop);
+ if (cmp == 0)
+ minTopOpen = topOpen || pRange->topOpen;
+ else if (cmp < 0) {
+ pMinTop = pTop;
+ minTopOpen = topOpen;
+ }
+ }
+ }
+
+ /*
+ If the intersections didn't create a disjoint set, create the
+ new range.
+ */
+ if (Value::compare(pMaxBottom, pMinTop) <= 0)
+ return new Range(pMaxBottom, maxBottomOpen, pMinTop, minTopOpen);
+
+ /* if we got here, the intersection is empty */
+ return NULL;
+ }
+
+ bool ExpressionFieldRange::Range::contains(
+ const intrusive_ptr<const Value> &pValue) const {
+ if (pBottom.get()) {
+ const int cmp = Value::compare(pValue, pBottom);
+ if (cmp < 0)
+ return false;
+ if (bottomOpen && (cmp == 0))
+ return false;
+ }
+
+ if (pTop.get()) {
+ const int cmp = Value::compare(pValue, pTop);
+ if (cmp > 0)
+ return false;
+ if (topOpen && (cmp == 0))
+ return false;
+ }
+
+ return true;
+ }
+
+ /* ------------------------- ExpressionMinute -------------------------- */
+
+ ExpressionMinute::~ExpressionMinute() {
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionMinute::create() {
+ intrusive_ptr<ExpressionMinute> pExpression(new ExpressionMinute());
+ return pExpression;
+ }
+
+ ExpressionMinute::ExpressionMinute():
+ ExpressionNary() {
+ }
+
+ void ExpressionMinute::addOperand(
+ const intrusive_ptr<Expression> &pExpression) {
+ checkArgLimit(1);
+ ExpressionNary::addOperand(pExpression);
+ }
+
+ intrusive_ptr<const Value> ExpressionMinute::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ checkArgCount(1);
+ intrusive_ptr<const Value> pDate(vpOperand[0]->evaluate(pDocument));
+ tm date = pDate->coerceToTm();
+ return Value::createInt(date.tm_min);
+ }
+
+ const char *ExpressionMinute::getOpName() const {
+ return "$minute";
+ }
+
+ /* ----------------------- ExpressionMod ---------------------------- */
+
+ ExpressionMod::~ExpressionMod() {
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionMod::create() {
+ intrusive_ptr<ExpressionMod> pExpression(new ExpressionMod());
+ return pExpression;
+ }
+
+ ExpressionMod::ExpressionMod():
+ ExpressionNary() {
+ }
+
+ void ExpressionMod::addOperand(
+ const intrusive_ptr<Expression> &pExpression) {
+ checkArgLimit(2);
+ ExpressionNary::addOperand(pExpression);
+ }
+
+ intrusive_ptr<const Value> ExpressionMod::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ checkArgCount(2);
+ intrusive_ptr<const Value> pLeft(vpOperand[0]->evaluate(pDocument));
+ intrusive_ptr<const Value> pRight(vpOperand[1]->evaluate(pDocument));
+
+ BSONType leftType = pLeft->getType();
+ BSONType rightType = pRight->getType();
+
+ uassert(16374, "$mod does not support dates", leftType != Date && rightType != Date);
+
+ // pass along jstNULLs and Undefineds
+ if (leftType == jstNULL || leftType == Undefined)
+ return pLeft;
+ if (rightType == jstNULL || rightType == Undefined)
+ return pRight;
+ // ensure we aren't modding by 0
+ double right = pRight->coerceToDouble();
+ if (right == 0)
+ return Value::getUndefined();
+
+ if (leftType == NumberDouble) {
+ // left is a double, return a double
+ double left = pLeft->coerceToDouble();
+ return Value::createDouble(fmod(left, right));
+ }
+ else if (rightType == NumberDouble && pRight->coerceToInt() != right) {
+ // the shell converts ints to doubles so if right is larger than int max or
+ // if right truncates to something other than itself, it is a real double.
+ // Integer-valued double case is handled below
+ double left = pLeft->coerceToDouble();
+ return Value::createDouble(fmod(left, right));
+ }
+ if (leftType == NumberLong || rightType == NumberLong) {
+ // if either is long, return long
+ long long left = pLeft->coerceToLong();
+ long long rightLong = pRight->coerceToLong();
+ return Value::createLong(left % rightLong);
+ }
+ // lastly they must both be ints, return int
+ int left = pLeft->coerceToInt();
+ int rightInt = pRight->coerceToInt();
+ return Value::createInt(left % rightInt);
+ }
+
+ const char *ExpressionMod::getOpName() const {
+ return "$mod";
+ }
+
+ /* ------------------------ ExpressionMonth ----------------------------- */
+
+ ExpressionMonth::~ExpressionMonth() {
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionMonth::create() {
+ intrusive_ptr<ExpressionMonth> pExpression(new ExpressionMonth());
+ return pExpression;
+ }
+
+ ExpressionMonth::ExpressionMonth():
+ ExpressionNary() {
+ }
+
+ void ExpressionMonth::addOperand(const intrusive_ptr<Expression> &pExpression) {
+ checkArgLimit(1);
+ ExpressionNary::addOperand(pExpression);
+ }
+
+ intrusive_ptr<const Value> ExpressionMonth::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ checkArgCount(1);
+ intrusive_ptr<const Value> pDate(vpOperand[0]->evaluate(pDocument));
+ tm date = pDate->coerceToTm();
+ return Value::createInt(date.tm_mon + 1); // MySQL uses 1-12 tm uses 0-11
+ }
+
+ const char *ExpressionMonth::getOpName() const {
+ return "$month";
+ }
+
+ /* ------------------------- ExpressionMultiply ----------------------------- */
+
+ ExpressionMultiply::~ExpressionMultiply() {
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionMultiply::create() {
+ intrusive_ptr<ExpressionMultiply> pExpression(new ExpressionMultiply());
+ return pExpression;
+ }
+
+ ExpressionMultiply::ExpressionMultiply():
+ ExpressionNary() {
+ }
+
+ intrusive_ptr<const Value> ExpressionMultiply::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ /*
+ We'll try to return the narrowest possible result value. To do that
+ without creating intermediate Values, do the arithmetic for double
+ and integral types in parallel, tracking the current narrowest
+ type.
+ */
+ double doubleProduct = 1;
+ long long longProduct = 1;
+ BSONType productType = NumberInt;
+
+ const size_t n = vpOperand.size();
+ for(size_t i = 0; i < n; ++i) {
+ intrusive_ptr<const Value> pValue(vpOperand[i]->evaluate(pDocument));
+
+ uassert(16375, "$multiply does not support dates", pValue->getType() != Date);
+
+ productType = Value::getWidestNumeric(productType, pValue->getType());
+ doubleProduct *= pValue->coerceToDouble();
+ longProduct *= pValue->coerceToLong();
+ }
+
+ if (productType == NumberDouble)
+ return Value::createDouble(doubleProduct);
+ else if (productType == NumberLong)
+ return Value::createLong(longProduct);
+ else if (productType == NumberInt)
+ return Value::createIntOrLong(longProduct);
+ else
+ massert(16418, "$multiply resulted in a non-numeric type", false);
+ }
+
+ const char *ExpressionMultiply::getOpName() const {
+ return "$multiply";
+ }
+
+ intrusive_ptr<ExpressionNary> (*ExpressionMultiply::getFactory() const)() {
+ return ExpressionMultiply::create;
+ }
+
+ /* ------------------------- ExpressionHour ----------------------------- */
+
+ ExpressionHour::~ExpressionHour() {
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionHour::create() {
+ intrusive_ptr<ExpressionHour> pExpression(new ExpressionHour());
+ return pExpression;
+ }
+
+ ExpressionHour::ExpressionHour():
+ ExpressionNary() {
+ }
+
+ void ExpressionHour::addOperand(const intrusive_ptr<Expression> &pExpression) {
+ checkArgLimit(1);
+ ExpressionNary::addOperand(pExpression);
+ }
+
+ intrusive_ptr<const Value> ExpressionHour::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ checkArgCount(1);
+ intrusive_ptr<const Value> pDate(vpOperand[0]->evaluate(pDocument));
+ tm date = pDate->coerceToTm();
+ return Value::createInt(date.tm_hour);
+ }
+
+ const char *ExpressionHour::getOpName() const {
+ return "$hour";
+ }
+
+ /* ----------------------- ExpressionIfNull ---------------------------- */
+
+ ExpressionIfNull::~ExpressionIfNull() {
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionIfNull::create() {
+ intrusive_ptr<ExpressionIfNull> pExpression(new ExpressionIfNull());
+ return pExpression;
+ }
+
+ ExpressionIfNull::ExpressionIfNull():
+ ExpressionNary() {
+ }
+
+ void ExpressionIfNull::addOperand(
+ const intrusive_ptr<Expression> &pExpression) {
+ checkArgLimit(2);
+ ExpressionNary::addOperand(pExpression);
+ }
+
+ intrusive_ptr<const Value> ExpressionIfNull::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ checkArgCount(2);
+ intrusive_ptr<const Value> pLeft(vpOperand[0]->evaluate(pDocument));
+ BSONType leftType = pLeft->getType();
+
+ if ((leftType != Undefined) && (leftType != jstNULL))
+ return pLeft;
+
+ intrusive_ptr<const Value> pRight(vpOperand[1]->evaluate(pDocument));
+ return pRight;
+ }
+
+ const char *ExpressionIfNull::getOpName() const {
+ return "$ifNull";
+ }
+
+ /* ------------------------ ExpressionNary ----------------------------- */
+
+ ExpressionNary::ExpressionNary():
+ vpOperand() {
+ }
+
+ intrusive_ptr<Expression> ExpressionNary::optimize() {
+ unsigned constCount = 0; // count of constant operands
+ unsigned stringCount = 0; // count of constant string operands
+ const size_t n = vpOperand.size();
+ for(size_t i = 0; i < n; ++i) {
+ intrusive_ptr<Expression> pNew(vpOperand[i]->optimize());
+
+ /* subsitute the optimized expression */
+ vpOperand[i] = pNew;
+
+ /* check to see if the result was a constant */
+ const ExpressionConstant *pConst =
+ dynamic_cast<ExpressionConstant *>(pNew.get());
+ if (pConst) {
+ ++constCount;
+ if (pConst->getValue()->getType() == String)
+ ++stringCount;
+ }
+ }
+
+ /*
+ If all the operands are constant, we can replace this expression
+ with a constant. We can find the value by evaluating this
+ expression over a NULL Document because evaluating the
+ ExpressionConstant never refers to the argument Document.
+ */
+ if (constCount == n) {
+ intrusive_ptr<const Value> pResult(
+ evaluate(intrusive_ptr<Document>()));
+ intrusive_ptr<Expression> pReplacement(
+ ExpressionConstant::create(pResult));
+ return pReplacement;
+ }
+
+ /*
+ If there are any strings, we can't re-arrange anything, so stop
+ now.
+
+ LATER: we could concatenate adjacent strings as a special case.
+ */
+ if (stringCount)
+ return intrusive_ptr<Expression>(this);
+
+ /*
+ If there's no more than one constant, then we can't do any
+ constant folding, so don't bother going any further.
+ */
+ if (constCount <= 1)
+ return intrusive_ptr<Expression>(this);
+
+ /*
+ If the operator isn't commutative or associative, there's nothing
+ more we can do. We test that by seeing if we can get a factory;
+ if we can, we can use it to construct a temporary expression which
+ we'll evaluate to collapse as many constants as we can down to
+ a single one.
+ */
+ intrusive_ptr<ExpressionNary> (*const pFactory)() = getFactory();
+ if (!pFactory)
+ return intrusive_ptr<Expression>(this);
+
+ /*
+ Create a new Expression that will be the replacement for this one.
+ We actually create two: one to hold constant expressions, and
+ one to hold non-constants. Once we've got these, we evaluate
+ the constant expression to produce a single value, as above.
+ We then add this operand to the end of the non-constant expression,
+ and return that.
+ */
+ intrusive_ptr<ExpressionNary> pNew((*pFactory)());
+ intrusive_ptr<ExpressionNary> pConst((*pFactory)());
+ for(size_t i = 0; i < n; ++i) {
+ intrusive_ptr<Expression> pE(vpOperand[i]);
+ if (dynamic_cast<ExpressionConstant *>(pE.get()))
+ pConst->addOperand(pE);
+ else {
+ /*
+ If the child operand is the same type as this, then we can
+ extract its operands and inline them here because we already
+ know this is commutative and associative because it has a
+ factory. We can detect sameness of the child operator by
+ checking for equality of the factory
+
+ Note we don't have to do this recursively, because we
+ called optimize() on all the children first thing in
+ this call to optimize().
+ */
+ ExpressionNary *pNary =
+ dynamic_cast<ExpressionNary *>(pE.get());
+ if (!pNary)
+ pNew->addOperand(pE);
+ else {
+ intrusive_ptr<ExpressionNary> (*const pChildFactory)() =
+ pNary->getFactory();
+ if (pChildFactory != pFactory)
+ pNew->addOperand(pE);
+ else {
+ /* same factory, so flatten */
+ size_t nChild = pNary->vpOperand.size();
+ for(size_t iChild = 0; iChild < nChild; ++iChild) {
+ intrusive_ptr<Expression> pCE(
+ pNary->vpOperand[iChild]);
+ if (dynamic_cast<ExpressionConstant *>(pCE.get()))
+ pConst->addOperand(pCE);
+ else
+ pNew->addOperand(pCE);
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ If there was only one constant, add it to the end of the expression
+ operand vector.
+ */
+ if (pConst->vpOperand.size() == 1)
+ pNew->addOperand(pConst->vpOperand[0]);
+ else if (pConst->vpOperand.size() > 1) {
+ /*
+ If there was more than one constant, collapse all the constants
+ together before adding the result to the end of the expression
+ operand vector.
+ */
+ intrusive_ptr<const Value> pResult(
+ pConst->evaluate(intrusive_ptr<Document>()));
+ pNew->addOperand(ExpressionConstant::create(pResult));
+ }
+
+ return pNew;
+ }
+
+ void ExpressionNary::addDependencies(set<string>& deps, vector<string>* path) const {
+ for(ExpressionVector::const_iterator i(vpOperand.begin());
+ i != vpOperand.end(); ++i) {
+ (*i)->addDependencies(deps);
+ }
+ }
+
+ void ExpressionNary::addOperand(
+ const intrusive_ptr<Expression> &pExpression) {
+ vpOperand.push_back(pExpression);
+ }
+
+ intrusive_ptr<ExpressionNary> (*ExpressionNary::getFactory() const)() {
+ return NULL;
+ }
+
+ void ExpressionNary::toBson(BSONObjBuilder *pBuilder, const char *pOpName) const {
+ const size_t nOperand = vpOperand.size();
+
+ /* build up the array */
+ BSONArrayBuilder arrBuilder (pBuilder->subarrayStart(pOpName));
+ for(size_t i = 0; i < nOperand; ++i)
+ vpOperand[i]->addToBsonArray(&arrBuilder);
+ arrBuilder.doneFast();
+ }
+
+ void ExpressionNary::addToBsonObj(
+ BSONObjBuilder *pBuilder, string fieldName,
+ bool requireExpression) const {
+ BSONObjBuilder exprBuilder;
+ toBson(&exprBuilder, getOpName());
+ pBuilder->append(fieldName, exprBuilder.done());
+ }
+
+ void ExpressionNary::addToBsonArray(
+ BSONArrayBuilder *pBuilder) const {
+ BSONObjBuilder exprBuilder;
+ toBson(&exprBuilder, getOpName());
+ pBuilder->append(exprBuilder.done());
+ }
+
+ void ExpressionNary::checkArgLimit(unsigned maxArgs) const {
+ uassert(15993, str::stream() << getOpName() <<
+ " only takes " << maxArgs <<
+ " operand" << (maxArgs == 1 ? "" : "s"),
+ vpOperand.size() < maxArgs);
+ }
+
+ void ExpressionNary::checkArgCount(unsigned reqArgs) const {
+ uassert(15997, str::stream() << getOpName() <<
+ ": insufficient operands; " << reqArgs <<
+ " required, only got " << vpOperand.size(),
+ vpOperand.size() == reqArgs);
+ }
+
+ /* ------------------------- ExpressionNot ----------------------------- */
+
+ ExpressionNot::~ExpressionNot() {
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionNot::create() {
+ intrusive_ptr<ExpressionNot> pExpression(new ExpressionNot());
+ return pExpression;
+ }
+
+ ExpressionNot::ExpressionNot():
+ ExpressionNary() {
+ }
+
+ void ExpressionNot::addOperand(const intrusive_ptr<Expression> &pExpression) {
+ checkArgLimit(1);
+ ExpressionNary::addOperand(pExpression);
+ }
+
+ intrusive_ptr<const Value> ExpressionNot::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ checkArgCount(1);
+ intrusive_ptr<const Value> pOp(vpOperand[0]->evaluate(pDocument));
+
+ bool b = pOp->coerceToBool();
+ if (b)
+ return Value::getFalse();
+ return Value::getTrue();
+ }
+
+ const char *ExpressionNot::getOpName() const {
+ return "$not";
+ }
+
+ /* -------------------------- ExpressionOr ----------------------------- */
+
+ ExpressionOr::~ExpressionOr() {
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionOr::create() {
+ intrusive_ptr<ExpressionNary> pExpression(new ExpressionOr());
+ return pExpression;
+ }
+
+ ExpressionOr::ExpressionOr():
+ ExpressionNary() {
+ }
+
+ intrusive_ptr<const Value> ExpressionOr::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ const size_t n = vpOperand.size();
+ for(size_t i = 0; i < n; ++i) {
+ intrusive_ptr<const Value> pValue(vpOperand[i]->evaluate(pDocument));
+ if (pValue->coerceToBool())
+ return Value::getTrue();
+ }
+
+ return Value::getFalse();
+ }
+
+ void ExpressionOr::toMatcherBson(
+ BSONObjBuilder *pBuilder) const {
+ BSONObjBuilder opArray;
+ const size_t n = vpOperand.size();
+ for(size_t i = 0; i < n; ++i)
+ vpOperand[i]->toMatcherBson(&opArray);
+
+ pBuilder->append("$or", opArray.done());
+ }
+
+ intrusive_ptr<ExpressionNary> (*ExpressionOr::getFactory() const)() {
+ return ExpressionOr::create;
+ }
+
+ intrusive_ptr<Expression> ExpressionOr::optimize() {
+ /* optimize the disjunction as much as possible */
+ intrusive_ptr<Expression> pE(ExpressionNary::optimize());
+
+ /* if the result isn't a conjunction, we can't do anything */
+ ExpressionOr *pOr = dynamic_cast<ExpressionOr *>(pE.get());
+ if (!pOr)
+ return pE;
+
+ /*
+ Check the last argument on the result; if it's not constant (as
+ promised by ExpressionNary::optimize(),) then there's nothing
+ we can do.
+ */
+ const size_t n = pOr->vpOperand.size();
+ intrusive_ptr<Expression> pLast(pOr->vpOperand[n - 1]);
+ const ExpressionConstant *pConst =
+ dynamic_cast<ExpressionConstant *>(pLast.get());
+ if (!pConst)
+ return pE;
+
+ /*
+ Evaluate and coerce the last argument to a boolean. If it's true,
+ then we can replace this entire expression.
+ */
+ bool last = pLast->evaluate(intrusive_ptr<Document>())->coerceToBool();
+ if (last) {
+ intrusive_ptr<ExpressionConstant> pFinal(
+ ExpressionConstant::create(Value::getTrue()));
+ return pFinal;
+ }
+
+ /*
+ If we got here, the final operand was false, so we don't need it
+ anymore. If there was only one other operand, we don't need the
+ conjunction either. Note we still need to keep the promise that
+ the result will be a boolean.
+ */
+ if (n == 2) {
+ intrusive_ptr<Expression> pFinal(
+ ExpressionCoerceToBool::create(pOr->vpOperand[0]));
+ return pFinal;
+ }
+
+ /*
+ Remove the final "false" value, and return the new expression.
+ */
+ pOr->vpOperand.resize(n - 1);
+ return pE;
+ }
+
+ const char *ExpressionOr::getOpName() const {
+ return "$or";
+ }
+
+ /* ------------------------- ExpressionSecond ----------------------------- */
+
+ ExpressionSecond::~ExpressionSecond() {
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionSecond::create() {
+ intrusive_ptr<ExpressionSecond> pExpression(new ExpressionSecond());
+ return pExpression;
+ }
+
+ ExpressionSecond::ExpressionSecond():
+ ExpressionNary() {
+ }
+
+ void ExpressionSecond::addOperand(const intrusive_ptr<Expression> &pExpression) {
+ checkArgLimit(1);
+ ExpressionNary::addOperand(pExpression);
+ }
+
+ intrusive_ptr<const Value> ExpressionSecond::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ checkArgCount(1);
+ intrusive_ptr<const Value> pDate(vpOperand[0]->evaluate(pDocument));
+ tm date = pDate->coerceToTm();
+ return Value::createInt(date.tm_sec);
+ }
+
+ const char *ExpressionSecond::getOpName() const {
+ return "$second";
+ }
+
+ /* ----------------------- ExpressionStrcasecmp ---------------------------- */
+
+ ExpressionStrcasecmp::~ExpressionStrcasecmp() {
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionStrcasecmp::create() {
+ intrusive_ptr<ExpressionStrcasecmp> pExpression(new ExpressionStrcasecmp());
+ return pExpression;
+ }
+
+ ExpressionStrcasecmp::ExpressionStrcasecmp():
+ ExpressionNary() {
+ }
+
+ void ExpressionStrcasecmp::addOperand(
+ const intrusive_ptr<Expression> &pExpression) {
+ checkArgLimit(2);
+ ExpressionNary::addOperand(pExpression);
+ }
+
+ intrusive_ptr<const Value> ExpressionStrcasecmp::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ checkArgCount(2);
+ intrusive_ptr<const Value> pString1(vpOperand[0]->evaluate(pDocument));
+ intrusive_ptr<const Value> pString2(vpOperand[1]->evaluate(pDocument));
+
+ /* boost::iequals returns a bool not an int so strings must actually be allocated */
+ string str1 = boost::to_upper_copy( pString1->coerceToString() );
+ string str2 = boost::to_upper_copy( pString2->coerceToString() );
+ int result = str1.compare(str2);
+
+ if (result == 0)
+ return Value::getZero();
+ if (result > 0)
+ return Value::getOne();
+ return Value::getMinusOne();
+ }
+
+ const char *ExpressionStrcasecmp::getOpName() const {
+ return "$strcasecmp";
+ }
+
+ /* ----------------------- ExpressionSubstr ---------------------------- */
+
+ ExpressionSubstr::~ExpressionSubstr() {
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionSubstr::create() {
+ intrusive_ptr<ExpressionSubstr> pExpression(new ExpressionSubstr());
+ return pExpression;
+ }
+
+ ExpressionSubstr::ExpressionSubstr():
+ ExpressionNary() {
+ }
+
+ void ExpressionSubstr::addOperand(
+ const intrusive_ptr<Expression> &pExpression) {
+ checkArgLimit(3);
+ ExpressionNary::addOperand(pExpression);
+ }
+
+ intrusive_ptr<const Value> ExpressionSubstr::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ checkArgCount(3);
+ intrusive_ptr<const Value> pString(vpOperand[0]->evaluate(pDocument));
+ intrusive_ptr<const Value> pLower(vpOperand[1]->evaluate(pDocument));
+ intrusive_ptr<const Value> pLength(vpOperand[2]->evaluate(pDocument));
+
+ string str = pString->coerceToString();
+ uassert(16034, str::stream() << getOpName() <<
+ ": starting index must be a numeric type (is BSON type " <<
+ typeName(pLower->getType()) << ")",
+ (pLower->getType() == NumberInt
+ || pLower->getType() == NumberLong
+ || pLower->getType() == NumberDouble));
+ uassert(16035, str::stream() << getOpName() <<
+ ": length must be a numeric type (is BSON type " <<
+ typeName(pLength->getType() )<< ")",
+ (pLength->getType() == NumberInt
+ || pLength->getType() == NumberLong
+ || pLength->getType() == NumberDouble));
+ string::size_type lower = static_cast< string::size_type >( pLower->coerceToLong() );
+ string::size_type length = static_cast< string::size_type >( pLength->coerceToLong() );
+ if ( lower >= str.length() ) {
+ // If lower > str.length() then string::substr() will throw out_of_range, so return an
+ // empty string if lower is not a valid string index.
+ return Value::createString( "" );
+ }
+ return Value::createString( str.substr(lower, length) );
+ }
+
+ const char *ExpressionSubstr::getOpName() const {
+ return "$substr";
+ }
+
+ /* ----------------------- ExpressionSubtract ---------------------------- */
+
+ ExpressionSubtract::~ExpressionSubtract() {
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionSubtract::create() {
+ intrusive_ptr<ExpressionSubtract> pExpression(new ExpressionSubtract());
+ return pExpression;
+ }
+
+ ExpressionSubtract::ExpressionSubtract():
+ ExpressionNary() {
+ }
+
+ void ExpressionSubtract::addOperand(
+ const intrusive_ptr<Expression> &pExpression) {
+ checkArgLimit(2);
+ ExpressionNary::addOperand(pExpression);
+ }
+
+ intrusive_ptr<const Value> ExpressionSubtract::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ BSONType productType;
+ checkArgCount(2);
+ intrusive_ptr<const Value> pLeft(vpOperand[0]->evaluate(pDocument));
+ intrusive_ptr<const Value> pRight(vpOperand[1]->evaluate(pDocument));
+
+ productType = Value::getWidestNumeric(pRight->getType(), pLeft->getType());
+
+ uassert(16376,
+ "$subtract does not support dates",
+ pLeft->getType() != Date && pRight->getType() != Date);
+
+ if (productType == NumberDouble) {
+ double right = pRight->coerceToDouble();
+ double left = pLeft->coerceToDouble();
+ return Value::createDouble(left - right);
+ }
+
+ long long right = pRight->coerceToLong();
+ long long left = pLeft->coerceToLong();
+ if (productType == NumberLong)
+ return Value::createLong(left - right);
+ else if (productType == NumberInt)
+ return Value::createIntOrLong(left - right);
+ else
+ massert(16413, "$subtract resulted in a non-numeric type", false);
+
+ }
+
+ const char *ExpressionSubtract::getOpName() const {
+ return "$subtract";
+ }
+
+ /* ------------------------- ExpressionToLower ----------------------------- */
+
+ ExpressionToLower::~ExpressionToLower() {
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionToLower::create() {
+ intrusive_ptr<ExpressionToLower> pExpression(new ExpressionToLower());
+ return pExpression;
+ }
+
+ ExpressionToLower::ExpressionToLower():
+ ExpressionNary() {
+ }
+
+ void ExpressionToLower::addOperand(const intrusive_ptr<Expression> &pExpression) {
+ checkArgLimit(1);
+ ExpressionNary::addOperand(pExpression);
+ }
+
+ intrusive_ptr<const Value> ExpressionToLower::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ checkArgCount(1);
+ intrusive_ptr<const Value> pString(vpOperand[0]->evaluate(pDocument));
+ string str = pString->coerceToString();
+ boost::to_lower(str);
+ return Value::createString(str);
+ }
+
+ const char *ExpressionToLower::getOpName() const {
+ return "$toLower";
+ }
+
+ /* ------------------------- ExpressionToUpper -------------------------- */
+
+ ExpressionToUpper::~ExpressionToUpper() {
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionToUpper::create() {
+ intrusive_ptr<ExpressionToUpper> pExpression(new ExpressionToUpper());
+ return pExpression;
+ }
+
+ ExpressionToUpper::ExpressionToUpper():
+ ExpressionNary() {
+ }
+
+ void ExpressionToUpper::addOperand(
+ const intrusive_ptr<Expression> &pExpression) {
+ checkArgLimit(1);
+ ExpressionNary::addOperand(pExpression);
+ }
+
+ intrusive_ptr<const Value> ExpressionToUpper::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ checkArgCount(1);
+ intrusive_ptr<const Value> pString(vpOperand[0]->evaluate(pDocument));
+ string str(pString->coerceToString());
+ boost::to_upper(str);
+ return Value::createString(str);
+ }
+
+ const char *ExpressionToUpper::getOpName() const {
+ return "$toUpper";
+ }
+
+ /* ------------------------- ExpressionWeek ----------------------------- */
+
+ ExpressionWeek::~ExpressionWeek() {
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionWeek::create() {
+ intrusive_ptr<ExpressionWeek> pExpression(new ExpressionWeek());
+ return pExpression;
+ }
+
+ ExpressionWeek::ExpressionWeek():
+ ExpressionNary() {
+ }
+
+ void ExpressionWeek::addOperand(const intrusive_ptr<Expression> &pExpression) {
+ checkArgLimit(1);
+ ExpressionNary::addOperand(pExpression);
+ }
+
+ intrusive_ptr<const Value> ExpressionWeek::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ checkArgCount(1);
+ intrusive_ptr<const Value> pDate(vpOperand[0]->evaluate(pDocument));
+ tm date = pDate->coerceToTm();
+ int dayOfWeek = date.tm_wday;
+ int dayOfYear = date.tm_yday;
+ int prevSundayDayOfYear = dayOfYear - dayOfWeek; // may be negative
+ int nextSundayDayOfYear = prevSundayDayOfYear + 7; // must be positive
+
+ // Return the zero based index of the week of the next sunday, equal to the one based index
+ // of the week of the previous sunday, which is to be returned.
+ int nextSundayWeek = nextSundayDayOfYear / 7;
+
+ // Verify that the week calculation is consistent with strftime "%U".
+ DEV{
+ char buf[3];
+ verify(strftime(buf,3,"%U",&date));
+ verify(int(str::toUnsigned(buf))==nextSundayWeek);
+ }
+
+ return Value::createInt(nextSundayWeek);
+ }
+
+ const char *ExpressionWeek::getOpName() const {
+ return "$week";
+ }
+
+ /* ------------------------- ExpressionYear ----------------------------- */
+
+ ExpressionYear::~ExpressionYear() {
+ }
+
+ intrusive_ptr<ExpressionNary> ExpressionYear::create() {
+ intrusive_ptr<ExpressionYear> pExpression(new ExpressionYear());
+ return pExpression;
+ }
+
+ ExpressionYear::ExpressionYear():
+ ExpressionNary() {
+ }
+
+ void ExpressionYear::addOperand(
+ const intrusive_ptr<Expression> &pExpression) {
+ checkArgLimit(1);
+ ExpressionNary::addOperand(pExpression);
+ }
+
+ intrusive_ptr<const Value> ExpressionYear::evaluate(
+ const intrusive_ptr<Document> &pDocument) const {
+ checkArgCount(1);
+ intrusive_ptr<const Value> pDate(vpOperand[0]->evaluate(pDocument));
+ tm date = pDate->coerceToTm();
+ return Value::createInt(date.tm_year + 1900); // tm_year is years since 1900
+ }
+
+ const char *ExpressionYear::getOpName() const {
+ return "$year";
+ }
+
+}
diff --git a/src/mongo/db/pipeline/expression.h b/src/mongo/db/pipeline/expression.h
new file mode 100755
index 00000000000..3eb0f7326fb
--- /dev/null
+++ b/src/mongo/db/pipeline/expression.h
@@ -0,0 +1,1199 @@
+/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "mongo/pch.h"
+
+#include "db/pipeline/field_path.h"
+#include "db/pipeline/value.h"
+#include "util/intrusive_counter.h"
+
+namespace mongo {
+
+ class BSONArrayBuilder;
+ class BSONElement;
+ class BSONObjBuilder;
+ class Builder;
+ class Document;
+ class DocumentSource;
+ class ExpressionContext;
+ class Value;
+
+
+ class Expression :
+ public IntrusiveCounterUnsigned {
+ public:
+ virtual ~Expression() {};
+
+ /*
+ Optimize the Expression.
+
+ This provides an opportunity to do constant folding, or to
+ collapse nested operators that have the same precedence, such as
+ $add, $and, or $or.
+
+ The Expression should be replaced with the return value, which may
+ or may not be the same object. In the case of constant folding,
+ a computed expression may be replaced by a constant.
+
+ @returns the optimized Expression
+ */
+ virtual intrusive_ptr<Expression> optimize() = 0;
+
+ /**
+ Add this expression's field dependencies to the set
+
+ Expressions are trees, so this is often recursive.
+
+ @param deps output parameter
+ @param path path to self if all ancestors are ExpressionObjects.
+ Top-level ExpressionObject gets pointer to empty vector.
+ If any other Expression is an ancestor {a:1} object
+ aren't allowed, so they get NULL
+
+
+ */
+ virtual void addDependencies(set<string>& deps, vector<string>* path=NULL) const = 0;
+
+ /** simple expressions are just inclusion exclusion as supported by ExpressionObject */
+ virtual bool isSimple() { return false; }
+
+ /*
+ Evaluate the Expression using the given document as input.
+
+ @returns the computed value
+ */
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const = 0;
+
+ /*
+ Add the Expression (and any descendant Expressions) into a BSON
+ object that is under construction.
+
+ Unevaluated Expressions always materialize as objects. Evaluation
+ may produce a scalar or another object, either of which will be
+ substituted inline.
+
+ @param pBuilder the builder to add the expression to
+ @param fieldName the name the object should be given
+ @param requireExpression specify true if the value must appear
+ as an expression; this is used by DocumentSources like
+ $project which distinguish between field inclusion and virtual
+ field specification; See ExpressionConstant.
+ */
+ virtual void addToBsonObj(
+ BSONObjBuilder *pBuilder, string fieldName,
+ bool requireExpression) const = 0;
+
+ /*
+ Add the Expression (and any descendant Expressions) into a BSON
+ array that is under construction.
+
+ Unevaluated Expressions always materialize as objects. Evaluation
+ may produce a scalar or another object, either of which will be
+ substituted inline.
+
+ @param pBuilder the builder to add the expression to
+ */
+ virtual void addToBsonArray(BSONArrayBuilder *pBuilder) const = 0;
+
+ /*
+ Convert the expression into a BSONObj that corresponds to the
+ db.collection.find() predicate language. This is intended for
+ use by DocumentSourceFilter.
+
+ This is more limited than the full expression language supported
+ by all available expressions in a DocumentSource processing
+ pipeline, and will fail with an assertion if an attempt is made
+ to go outside the bounds of the recognized patterns, which don't
+ include full computed expressions. There are other methods available
+ on DocumentSourceFilter which can be used to analyze a filter
+ predicate and break it up into appropriate expressions which can
+ be translated within these constraints. As a result, the default
+ implementation is to fail with an assertion; only a subset of
+ operators will be able to fulfill this request.
+
+ @param pBuilder the builder to add the expression to.
+ */
+ virtual void toMatcherBson(BSONObjBuilder *pBuilder) const;
+
+ /*
+ Utility class for parseObject() below.
+
+ DOCUMENT_OK indicates that it is OK to use a Document in the current
+ context.
+ */
+ class ObjectCtx {
+ public:
+ ObjectCtx(int options);
+ static const int DOCUMENT_OK = 0x0001;
+ static const int TOP_LEVEL = 0x0002;
+ static const int INCLUSION_OK = 0x0004;
+
+ bool documentOk() const;
+ bool topLevel() const;
+ bool inclusionOk() const;
+
+ private:
+ int options;
+ };
+
+ /*
+ Parse a BSONElement Object. The object could represent a functional
+ expression or a Document expression.
+
+ @param pBsonElement the element representing the object
+ @param pCtx a MiniCtx representing the options above
+ @returns the parsed Expression
+ */
+ static intrusive_ptr<Expression> parseObject(
+ BSONElement *pBsonElement, ObjectCtx *pCtx);
+
+ /*
+ Parse a BSONElement Object which has already been determined to be
+ functional expression.
+
+ @param pOpName the name of the (prefix) operator
+ @param pBsonElement the BSONElement to parse
+ @returns the parsed Expression
+ */
+ static intrusive_ptr<Expression> parseExpression(
+ const char *pOpName, BSONElement *pBsonElement);
+
+
+ /*
+ Parse a BSONElement which is an operand in an Expression.
+
+ @param pBsonElement the expected operand's BSONElement
+ @returns the parsed operand, as an Expression
+ */
+ static intrusive_ptr<Expression> parseOperand(
+ BSONElement *pBsonElement);
+
+ /*
+ Produce a field path string with the field prefix removed.
+
+ Throws an error if the field prefix is not present.
+
+ @param prefixedField the prefixed field
+ @returns the field path with the prefix removed
+ */
+ static string removeFieldPrefix(const string &prefixedField);
+
+ /*
+ Enumeration of comparison operators. These are shared between a
+ few expression implementations, so they are factored out here.
+
+ Any changes to these values require adjustment of the lookup
+ table in the implementation.
+ */
+ enum CmpOp {
+ EQ = 0, // return true for a == b, false otherwise
+ NE = 1, // return true for a != b, false otherwise
+ GT = 2, // return true for a > b, false otherwise
+ GTE = 3, // return true for a >= b, false otherwise
+ LT = 4, // return true for a < b, false otherwise
+ LTE = 5, // return true for a <= b, false otherwise
+ CMP = 6, // return -1, 0, 1 for a < b, a == b, a > b
+ };
+
+ static int signum(int i);
+
+ protected:
+ typedef vector<intrusive_ptr<Expression> > ExpressionVector;
+
+ };
+
+
+ class ExpressionNary :
+ public Expression {
+ public:
+ // virtuals from Expression
+ virtual intrusive_ptr<Expression> optimize();
+ virtual void addToBsonObj(
+ BSONObjBuilder *pBuilder, string fieldName,
+ bool requireExpression) const;
+ virtual void addToBsonArray(BSONArrayBuilder *pBuilder) const;
+ virtual void addDependencies(set<string>& deps, vector<string>* path=NULL) const;
+
+ /*
+ Add an operand to the n-ary expression.
+
+ @param pExpression the expression to add
+ */
+ virtual void addOperand(const intrusive_ptr<Expression> &pExpression);
+
+ /*
+ Return a factory function that will make Expression nodes of
+ the same type as this. This will be used to create constant
+ expressions for constant folding for optimize(). Only return
+ a factory function if this operator is both associative and
+ commutative. The default implementation returns NULL; optimize()
+ will recognize that and stop.
+
+ Note that ExpressionNary::optimize() promises that if it uses this
+ to fold constants, then if optimize() returns an ExpressionNary,
+ any remaining constant will be the last one in vpOperand. Derived
+ classes may take advantage of this to do further optimizations in
+ their optimize().
+
+ @returns pointer to a factory function or NULL
+ */
+ virtual intrusive_ptr<ExpressionNary> (*getFactory() const)();
+
+ /*
+ Get the name of the operator.
+
+ @returns the name of the operator; this string belongs to the class
+ implementation, and should not be deleted
+ and should not
+ */
+ virtual const char *getOpName() const = 0;
+
+ protected:
+ ExpressionNary();
+
+ ExpressionVector vpOperand;
+
+ /*
+ Add the expression to the builder.
+
+ If there is only one operand (a unary operator), then the operand
+ is added directly, without an array. For more than one operand,
+ a named array is created. In both cases, the result is an object.
+
+ @param pBuilder the (blank) builder to add the expression to
+ @param pOpName the name of the operator
+ */
+ virtual void toBson(BSONObjBuilder *pBuilder,
+ const char *pOpName) const;
+
+ /*
+ Checks the current size of vpOperand; if the size equal to or
+ greater than maxArgs, fires a user assertion indicating that this
+ operator cannot have this many arguments.
+
+ The equal is there because this is intended to be used in
+ addOperand() to check for the limit *before* adding the requested
+ argument.
+
+ @param maxArgs the maximum number of arguments the operator accepts
+ */
+ void checkArgLimit(unsigned maxArgs) const;
+
+ /*
+ Checks the current size of vpOperand; if the size is not equal to
+ reqArgs, fires a user assertion indicating that this must have
+ exactly reqArgs arguments.
+
+ This is meant to be used in evaluate(), *before* the evaluation
+ takes place.
+
+ @param reqArgs the number of arguments this operator requires
+ */
+ void checkArgCount(unsigned reqArgs) const;
+ };
+
+
+ class ExpressionAdd :
+ public ExpressionNary {
+ public:
+ // virtuals from Expression
+ virtual ~ExpressionAdd();
+ virtual intrusive_ptr<const Value> evaluate(const intrusive_ptr<Document> &pDocument) const;
+ virtual const char *getOpName() const;
+
+ // virtuals from ExpressionNary
+ virtual intrusive_ptr<ExpressionNary> (*getFactory() const)();
+
+ /*
+ Create an expression that finds the sum of n operands.
+
+ @returns addition expression
+ */
+ static intrusive_ptr<ExpressionNary> create();
+ };
+
+
+ class ExpressionAnd :
+ public ExpressionNary {
+ public:
+ // virtuals from Expression
+ virtual ~ExpressionAnd();
+ virtual intrusive_ptr<Expression> optimize();
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual const char *getOpName() const;
+ virtual void toMatcherBson(BSONObjBuilder *pBuilder) const;
+
+ // virtuals from ExpressionNary
+ virtual intrusive_ptr<ExpressionNary> (*getFactory() const)();
+
+ /*
+ Create an expression that finds the conjunction of n operands.
+ The conjunction uses short-circuit logic; the expressions are
+ evaluated in the order they were added to the conjunction, and
+ the evaluation stops and returns false on the first operand that
+ evaluates to false.
+
+ @returns conjunction expression
+ */
+ static intrusive_ptr<ExpressionNary> create();
+
+ private:
+ ExpressionAnd();
+ };
+
+
+ class ExpressionCoerceToBool :
+ public Expression {
+ public:
+ // virtuals from ExpressionNary
+ virtual ~ExpressionCoerceToBool();
+ virtual intrusive_ptr<Expression> optimize();
+ virtual void addDependencies(set<string>& deps, vector<string>* path=NULL) const;
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual void addToBsonObj(
+ BSONObjBuilder *pBuilder, string fieldName,
+ bool requireExpression) const;
+ virtual void addToBsonArray(BSONArrayBuilder *pBuilder) const;
+
+ static intrusive_ptr<ExpressionCoerceToBool> create(
+ const intrusive_ptr<Expression> &pExpression);
+
+ private:
+ ExpressionCoerceToBool(const intrusive_ptr<Expression> &pExpression);
+
+ intrusive_ptr<Expression> pExpression;
+ };
+
+
+ class ExpressionCompare :
+ public ExpressionNary {
+ public:
+ // virtuals from ExpressionNary
+ virtual ~ExpressionCompare();
+ virtual intrusive_ptr<Expression> optimize();
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual const char *getOpName() const;
+ virtual void addOperand(const intrusive_ptr<Expression> &pExpression);
+
+ /*
+ Shorthands for creating various comparisons expressions.
+ Provide for conformance with the uniform function pointer signature
+ required for parsing.
+
+ These create a particular comparision operand, without any
+ operands. Those must be added via ExpressionNary::addOperand().
+ */
+ static intrusive_ptr<ExpressionNary> createCmp();
+ static intrusive_ptr<ExpressionNary> createEq();
+ static intrusive_ptr<ExpressionNary> createNe();
+ static intrusive_ptr<ExpressionNary> createGt();
+ static intrusive_ptr<ExpressionNary> createGte();
+ static intrusive_ptr<ExpressionNary> createLt();
+ static intrusive_ptr<ExpressionNary> createLte();
+
+ private:
+ friend class ExpressionFieldRange;
+ ExpressionCompare(CmpOp cmpOp);
+
+ CmpOp cmpOp;
+ };
+
+
+ class ExpressionCond :
+ public ExpressionNary {
+ public:
+ // virtuals from ExpressionNary
+ virtual ~ExpressionCond();
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual const char *getOpName() const;
+ virtual void addOperand(const intrusive_ptr<Expression> &pExpression);
+
+ static intrusive_ptr<ExpressionNary> create();
+
+ private:
+ ExpressionCond();
+ };
+
+
+ class ExpressionConstant :
+ public Expression {
+ public:
+ // virtuals from Expression
+ virtual ~ExpressionConstant();
+ virtual intrusive_ptr<Expression> optimize();
+ virtual void addDependencies(set<string>& deps, vector<string>* path=NULL) const;
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual const char *getOpName() const;
+ virtual void addToBsonObj(
+ BSONObjBuilder *pBuilder, string fieldName,
+ bool requireExpression) const;
+ virtual void addToBsonArray(BSONArrayBuilder *pBuilder) const;
+
+ static intrusive_ptr<ExpressionConstant> createFromBsonElement(
+ BSONElement *pBsonElement);
+ static intrusive_ptr<ExpressionConstant> create(
+ const intrusive_ptr<const Value> &pValue);
+
+ /*
+ Get the constant value represented by this Expression.
+
+ @returns the value
+ */
+ intrusive_ptr<const Value> getValue() const;
+
+ private:
+ ExpressionConstant(BSONElement *pBsonElement);
+ ExpressionConstant(const intrusive_ptr<const Value> &pValue);
+
+ intrusive_ptr<const Value> pValue;
+ };
+
+
+ class ExpressionDayOfMonth :
+ public ExpressionNary {
+ public:
+ // virtuals from ExpressionNary
+ virtual ~ExpressionDayOfMonth();
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual const char *getOpName() const;
+ virtual void addOperand(const intrusive_ptr<Expression> &pExpression);
+
+ static intrusive_ptr<ExpressionNary> create();
+
+ private:
+ ExpressionDayOfMonth();
+ };
+
+
+ class ExpressionDayOfWeek :
+ public ExpressionNary {
+ public:
+ // virtuals from ExpressionNary
+ virtual ~ExpressionDayOfWeek();
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual const char *getOpName() const;
+ virtual void addOperand(const intrusive_ptr<Expression> &pExpression);
+
+ static intrusive_ptr<ExpressionNary> create();
+
+ private:
+ ExpressionDayOfWeek();
+ };
+
+
+ class ExpressionDayOfYear :
+ public ExpressionNary {
+ public:
+ // virtuals from ExpressionNary
+ virtual ~ExpressionDayOfYear();
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual const char *getOpName() const;
+ virtual void addOperand(const intrusive_ptr<Expression> &pExpression);
+
+ static intrusive_ptr<ExpressionNary> create();
+
+ private:
+ ExpressionDayOfYear();
+ };
+
+
+ class ExpressionDivide :
+ public ExpressionNary {
+ public:
+ // virtuals from ExpressionNary
+ virtual ~ExpressionDivide();
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual const char *getOpName() const;
+ virtual void addOperand(const intrusive_ptr<Expression> &pExpression);
+
+ static intrusive_ptr<ExpressionNary> create();
+
+ private:
+ ExpressionDivide();
+ };
+
+
+ class ExpressionFieldPath :
+ public Expression {
+ public:
+ // virtuals from Expression
+ virtual ~ExpressionFieldPath();
+ virtual intrusive_ptr<Expression> optimize();
+ virtual void addDependencies(set<string>& deps, vector<string>* path=NULL) const;
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual void addToBsonObj(
+ BSONObjBuilder *pBuilder, string fieldName,
+ bool requireExpression) const;
+ virtual void addToBsonArray(BSONArrayBuilder *pBuilder) const;
+
+ /*
+ Create a field path expression.
+
+ Evaluation will extract the value associated with the given field
+ path from the source document.
+
+ @param fieldPath the field path string, without any leading document
+ indicator
+ @returns the newly created field path expression
+ */
+ static intrusive_ptr<ExpressionFieldPath> create(
+ const string &fieldPath);
+
+ /*
+ Return a string representation of the field path.
+
+ @param fieldPrefix whether or not to include the document field
+ indicator prefix
+ @returns the dot-delimited field path
+ */
+ string getFieldPath(bool fieldPrefix) const;
+
+ /*
+ Write a string representation of the field path to a stream.
+
+ @param the stream to write to
+ @param fieldPrefix whether or not to include the document field
+ indicator prefix
+ */
+ void writeFieldPath(ostream &outStream, bool fieldPrefix) const;
+
+ private:
+ ExpressionFieldPath(const string &fieldPath);
+
+ /*
+ Internal implementation of evaluate(), used recursively.
+
+ The internal implementation doesn't just use a loop because of
+ the possibility that we need to skip over an array. If the path
+ is "a.b.c", and a is an array, then we fan out from there, and
+ traverse "b.c" for each element of a:[...]. This requires that
+ a be an array of objects in order to navigate more deeply.
+
+ @param index current path field index to extract
+ @param pathLength maximum number of fields on field path
+ @param pDocument current document traversed to (not the top-level one)
+ @returns the field found; could be an array
+ */
+ intrusive_ptr<const Value> evaluatePath(
+ size_t index, const size_t pathLength,
+ intrusive_ptr<Document> pDocument) const;
+
+ FieldPath fieldPath;
+ };
+
+
+ class ExpressionFieldRange :
+ public Expression {
+ public:
+ // virtuals from expression
+ virtual ~ExpressionFieldRange();
+ virtual intrusive_ptr<Expression> optimize();
+ virtual void addDependencies(set<string>& deps, vector<string>* path=NULL) const;
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual void addToBsonObj(
+ BSONObjBuilder *pBuilder, string fieldName,
+ bool requireExpression) const;
+ virtual void addToBsonArray(BSONArrayBuilder *pBuilder) const;
+ virtual void toMatcherBson(BSONObjBuilder *pBuilder) const;
+
+ /*
+ Create a field range expression.
+
+ Field ranges are meant to match up with classic Matcher semantics,
+ and therefore are conjunctions. For example, these appear in
+ mongo shell predicates in one of these forms:
+ { a : C } -> (a == C) // degenerate "point" range
+ { a : { $lt : C } } -> (a < C) // open range
+ { a : { $gt : C1, $lte : C2 } } -> ((a > C1) && (a <= C2)) // closed
+
+ When initially created, a field range only includes one end of
+ the range. Additional points may be added via intersect().
+
+ Note that NE and CMP are not supported.
+
+ @param pFieldPath the field path for extracting the field value
+ @param cmpOp the comparison operator
+ @param pValue the value to compare against
+ @returns the newly created field range expression
+ */
+ static intrusive_ptr<ExpressionFieldRange> create(
+ const intrusive_ptr<ExpressionFieldPath> &pFieldPath,
+ CmpOp cmpOp, const intrusive_ptr<const Value> &pValue);
+
+ /*
+ Add an intersecting range.
+
+ This can be done any number of times after creation. The
+ range is internally optimized for each new addition. If the new
+ intersection extends or reduces the values within the range, the
+ internal representation is adjusted to reflect that.
+
+ Note that NE and CMP are not supported.
+
+ @param cmpOp the comparison operator
+ @param pValue the value to compare against
+ */
+ void intersect(CmpOp cmpOp, const intrusive_ptr<const Value> &pValue);
+
+ private:
+ ExpressionFieldRange(const intrusive_ptr<ExpressionFieldPath> &pFieldPath,
+ CmpOp cmpOp,
+ const intrusive_ptr<const Value> &pValue);
+
+ intrusive_ptr<ExpressionFieldPath> pFieldPath;
+
+ class Range {
+ public:
+ Range(CmpOp cmpOp, const intrusive_ptr<const Value> &pValue);
+ Range(const Range &rRange);
+
+ Range *intersect(const Range *pRange) const;
+ bool contains(const intrusive_ptr<const Value> &pValue) const;
+
+ Range(const intrusive_ptr<const Value> &pBottom, bool bottomOpen,
+ const intrusive_ptr<const Value> &pTop, bool topOpen);
+
+ bool bottomOpen;
+ bool topOpen;
+ intrusive_ptr<const Value> pBottom;
+ intrusive_ptr<const Value> pTop;
+ };
+
+ scoped_ptr<Range> pRange;
+
+ /*
+ Add to a generic Builder.
+
+ The methods to append items to an object and an array differ by
+ their inclusion of a field name. For more complicated objects,
+ it makes sense to abstract that out and use a generic builder that
+ always looks the same, and then implement addToBsonObj() and
+ addToBsonArray() by using the common method.
+ */
+ void addToBson(Builder *pBuilder) const;
+ };
+
+
+ class ExpressionHour :
+ public ExpressionNary {
+ public:
+ // virtuals from ExpressionNary
+ virtual ~ExpressionHour();
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual const char *getOpName() const;
+ virtual void addOperand(const intrusive_ptr<Expression> &pExpression);
+
+ static intrusive_ptr<ExpressionNary> create();
+
+ private:
+ ExpressionHour();
+ };
+
+
+ class ExpressionIfNull :
+ public ExpressionNary {
+ public:
+ // virtuals from ExpressionNary
+ virtual ~ExpressionIfNull();
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual const char *getOpName() const;
+ virtual void addOperand(const intrusive_ptr<Expression> &pExpression);
+
+ static intrusive_ptr<ExpressionNary> create();
+
+ private:
+ ExpressionIfNull();
+ };
+
+
+ class ExpressionMinute :
+ public ExpressionNary {
+ public:
+ // virtuals from ExpressionNary
+ virtual ~ExpressionMinute();
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual const char *getOpName() const;
+ virtual void addOperand(const intrusive_ptr<Expression> &pExpression);
+
+ static intrusive_ptr<ExpressionNary> create();
+
+ private:
+ ExpressionMinute();
+ };
+
+
+ class ExpressionMod :
+ public ExpressionNary {
+ public:
+ // virtuals from ExpressionNary
+ virtual ~ExpressionMod();
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual const char *getOpName() const;
+ virtual void addOperand(const intrusive_ptr<Expression> &pExpression);
+
+ static intrusive_ptr<ExpressionNary> create();
+
+ private:
+ ExpressionMod();
+ };
+
+
+ class ExpressionMultiply :
+ public ExpressionNary {
+ public:
+ // virtuals from Expression
+ virtual ~ExpressionMultiply();
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual const char *getOpName() const;
+
+ // virtuals from ExpressionNary
+ virtual intrusive_ptr<ExpressionNary> (*getFactory() const)();
+
+ /*
+ Create an expression that finds the product of n operands.
+
+ @returns multiplication expression
+ */
+ static intrusive_ptr<ExpressionNary> create();
+
+ private:
+ ExpressionMultiply();
+ };
+
+
+ class ExpressionMonth :
+ public ExpressionNary {
+ public:
+ // virtuals from ExpressionNary
+ virtual ~ExpressionMonth();
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual const char *getOpName() const;
+ virtual void addOperand(const intrusive_ptr<Expression> &pExpression);
+
+ static intrusive_ptr<ExpressionNary> create();
+
+ private:
+ ExpressionMonth();
+ };
+
+
+ class ExpressionNot :
+ public ExpressionNary {
+ public:
+ // virtuals from ExpressionNary
+ virtual ~ExpressionNot();
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual const char *getOpName() const;
+ virtual void addOperand(const intrusive_ptr<Expression> &pExpression);
+
+ static intrusive_ptr<ExpressionNary> create();
+
+ private:
+ ExpressionNot();
+ };
+
+
+ class ExpressionObject :
+ public Expression {
+ public:
+ // virtuals from Expression
+ virtual ~ExpressionObject();
+ virtual intrusive_ptr<Expression> optimize();
+ virtual bool isSimple();
+ virtual void addDependencies(set<string>& deps, vector<string>* path=NULL) const;
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual void addToBsonObj(
+ BSONObjBuilder *pBuilder, string fieldName,
+ bool requireExpression) const;
+ virtual void addToBsonArray(BSONArrayBuilder *pBuilder) const;
+
+ /*
+ evaluate(), but return a Document instead of a Value-wrapped
+ Document.
+
+ @param pDocument the input Document
+ @returns the result document
+ */
+ intrusive_ptr<Document> evaluateDocument(
+ const intrusive_ptr<Document> &pDocument) const;
+
+ /*
+ evaluate(), but add the evaluated fields to a given document
+ instead of creating a new one.
+
+ @param pResult the Document to add the evaluated expressions to
+ @param pDocument the input Document for this level
+ @param rootDoc the root of the whole input document
+ */
+ void addToDocument(const intrusive_ptr<Document>& pResult,
+ const intrusive_ptr<Document>& pDocument,
+ const intrusive_ptr<Document>& rootDoc
+ ) const;
+
+ // estimated number of fields that will be output
+ size_t getSizeHint() const;
+
+ /*
+ Create an empty expression. Until fields are added, this
+ will evaluate to an empty document (object).
+ */
+ static intrusive_ptr<ExpressionObject> create();
+
+ /*
+ Add a field to the document expression.
+
+ @param fieldPath the path the evaluated expression will have in the
+ result Document
+ @param pExpression the expression to evaluate obtain this field's
+ Value in the result Document
+ */
+ void addField(const FieldPath &fieldPath,
+ const intrusive_ptr<Expression> &pExpression);
+
+ /*
+ Add a field path to the set of those to be included.
+
+ Note that including a nested field implies including everything on
+ the path leading down to it.
+
+ @param fieldPath the name of the field to be included
+ */
+ void includePath(const string &fieldPath);
+
+ /*
+ Get a count of the added fields.
+
+ @returns how many fields have been added
+ */
+ size_t getFieldCount() const;
+
+ /*
+ Specialized BSON conversion that allows for writing out a
+ $project specification. This creates a standalone object, which must
+ be added to a containing object with a name
+
+ @param pBuilder where to write the object to
+ @param requireExpression see Expression::addToBsonObj
+ */
+ void documentToBson(BSONObjBuilder *pBuilder,
+ bool requireExpression) const;
+
+ /*
+ Visitor abstraction used by emitPaths(). Each path is recorded by
+ calling path().
+ */
+ class PathSink {
+ public:
+ virtual ~PathSink() {};
+
+ /**
+ Record a path.
+
+ @param path the dotted path string
+ @param include if true, the path is included; if false, the path
+ is excluded
+ */
+ virtual void path(const string &path, bool include) = 0;
+ };
+
+ void excludeId(bool b) { _excludeId = b; }
+
+ private:
+ ExpressionObject();
+
+ // mapping from fieldname to Expression to generate the value
+ // NULL expression means include from source document
+ typedef map<string, intrusive_ptr<Expression> > ExpressionMap;
+ ExpressionMap _expressions;
+
+ // this is used to maintain order for generated fields not in the source document
+ vector<string> _order;
+
+ bool _excludeId;
+
+ /*
+ Utility object for collecting emitPaths() results in a BSON
+ object.
+ */
+ class BuilderPathSink :
+ public PathSink {
+ public:
+ // virtuals from PathSink
+ virtual void path(const string &path, bool include);
+
+ /*
+ Create a PathSink that writes paths to a BSONObjBuilder,
+ to create an object in the form of { path:is_included,...}
+
+ 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.
+
+ @param pBuilder to the builder to write paths to
+ */
+ BuilderPathSink(BSONObjBuilder *pBuilder);
+
+ private:
+ BSONObjBuilder *pBuilder;
+ };
+
+ /* utility class used by emitPaths() */
+ class PathPusher :
+ boost::noncopyable {
+ public:
+ PathPusher(vector<string> *pvPath, const string &s);
+ ~PathPusher();
+
+ private:
+ vector<string> *pvPath;
+ };
+ };
+
+
+ class ExpressionOr :
+ public ExpressionNary {
+ public:
+ // virtuals from Expression
+ virtual ~ExpressionOr();
+ virtual intrusive_ptr<Expression> optimize();
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual const char *getOpName() const;
+ virtual void toMatcherBson(BSONObjBuilder *pBuilder) const;
+
+ // virtuals from ExpressionNary
+ virtual intrusive_ptr<ExpressionNary> (*getFactory() const)();
+
+ /*
+ Create an expression that finds the conjunction of n operands.
+ The conjunction uses short-circuit logic; the expressions are
+ evaluated in the order they were added to the conjunction, and
+ the evaluation stops and returns false on the first operand that
+ evaluates to false.
+
+ @returns conjunction expression
+ */
+ static intrusive_ptr<ExpressionNary> create();
+
+ private:
+ ExpressionOr();
+ };
+
+
+ class ExpressionSecond :
+ public ExpressionNary {
+ public:
+ // virtuals from ExpressionNary
+ virtual ~ExpressionSecond();
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual const char *getOpName() const;
+ virtual void addOperand(const intrusive_ptr<Expression> &pExpression);
+
+ static intrusive_ptr<ExpressionNary> create();
+
+ private:
+ ExpressionSecond();
+ };
+
+
+ class ExpressionStrcasecmp :
+ public ExpressionNary {
+ public:
+ // virtuals from ExpressionNary
+ virtual ~ExpressionStrcasecmp();
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual const char *getOpName() const;
+ virtual void addOperand(const intrusive_ptr<Expression> &pExpression);
+
+ static intrusive_ptr<ExpressionNary> create();
+
+ private:
+ ExpressionStrcasecmp();
+ };
+
+
+ class ExpressionSubstr :
+ public ExpressionNary {
+ public:
+ // virtuals from ExpressionNary
+ virtual ~ExpressionSubstr();
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual const char *getOpName() const;
+ virtual void addOperand(const intrusive_ptr<Expression> &pExpression);
+
+ static intrusive_ptr<ExpressionNary> create();
+
+ private:
+ ExpressionSubstr();
+ };
+
+
+ class ExpressionSubtract :
+ public ExpressionNary {
+ public:
+ // virtuals from ExpressionNary
+ virtual ~ExpressionSubtract();
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual const char *getOpName() const;
+ virtual void addOperand(const intrusive_ptr<Expression> &pExpression);
+
+ static intrusive_ptr<ExpressionNary> create();
+
+ private:
+ ExpressionSubtract();
+ };
+
+
+ class ExpressionToLower :
+ public ExpressionNary {
+ public:
+ // virtuals from ExpressionNary
+ virtual ~ExpressionToLower();
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual const char *getOpName() const;
+ virtual void addOperand(const intrusive_ptr<Expression> &pExpression);
+
+ static intrusive_ptr<ExpressionNary> create();
+
+ private:
+ ExpressionToLower();
+ };
+
+
+ class ExpressionToUpper :
+ public ExpressionNary {
+ public:
+ // virtuals from ExpressionNary
+ virtual ~ExpressionToUpper();
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual const char *getOpName() const;
+ virtual void addOperand(const intrusive_ptr<Expression> &pExpression);
+
+ static intrusive_ptr<ExpressionNary> create();
+
+ private:
+ ExpressionToUpper();
+ };
+
+
+ class ExpressionWeek :
+ public ExpressionNary {
+ public:
+ // virtuals from ExpressionNary
+ virtual ~ExpressionWeek();
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual const char *getOpName() const;
+ virtual void addOperand(const intrusive_ptr<Expression> &pExpression);
+
+ static intrusive_ptr<ExpressionNary> create();
+
+ private:
+ ExpressionWeek();
+ };
+
+
+ class ExpressionYear :
+ public ExpressionNary {
+ public:
+ // virtuals from ExpressionNary
+ virtual ~ExpressionYear();
+ virtual intrusive_ptr<const Value> evaluate(
+ const intrusive_ptr<Document> &pDocument) const;
+ virtual const char *getOpName() const;
+ virtual void addOperand(const intrusive_ptr<Expression> &pExpression);
+
+ static intrusive_ptr<ExpressionNary> create();
+
+ private:
+ ExpressionYear();
+ };
+}
+
+
+/* ======================= INLINED IMPLEMENTATIONS ========================== */
+
+namespace mongo {
+
+ inline int Expression::signum(int i) {
+ if (i < 0)
+ return -1;
+ if (i > 0)
+ return 1;
+ return 0;
+ }
+
+ inline intrusive_ptr<const Value> ExpressionConstant::getValue() const {
+ return pValue;
+ }
+
+ inline string ExpressionFieldPath::getFieldPath(bool fieldPrefix) const {
+ return fieldPath.getPath(fieldPrefix);
+ }
+
+ inline void ExpressionFieldPath::writeFieldPath(
+ ostream &outStream, bool fieldPrefix) const {
+ return fieldPath.writePath(outStream, fieldPrefix);
+ }
+
+ inline size_t ExpressionObject::getFieldCount() const {
+ return _expressions.size();
+ }
+
+ inline ExpressionObject::BuilderPathSink::BuilderPathSink(
+ BSONObjBuilder *pB):
+ pBuilder(pB) {
+ }
+
+ inline ExpressionObject::PathPusher::PathPusher(
+ vector<string> *pTheVPath, const string &s):
+ pvPath(pTheVPath) {
+ pvPath->push_back(s);
+ }
+
+ inline ExpressionObject::PathPusher::~PathPusher() {
+ pvPath->pop_back();
+ }
+
+}
diff --git a/src/mongo/db/pipeline/expression_context.cpp b/src/mongo/db/pipeline/expression_context.cpp
new file mode 100755
index 00000000000..0533fc63e7d
--- /dev/null
+++ b/src/mongo/db/pipeline/expression_context.cpp
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pch.h"
+
+#include "db/interrupt_status.h"
+#include "db/pipeline/expression_context.h"
+
+namespace mongo {
+
+ ExpressionContext::~ExpressionContext() {
+ }
+
+ inline ExpressionContext::ExpressionContext(InterruptStatus *pS):
+ doingMerge(false),
+ inShard(false),
+ inRouter(false),
+ intCheckCounter(1),
+ pStatus(pS) {
+ }
+
+ void ExpressionContext::checkForInterrupt() {
+ /*
+ Only really check periodically; the check gets a mutex, and could
+ be expensive, at least in relative terms.
+ */
+ if ((++intCheckCounter % 128) == 0) {
+ pStatus->checkForInterrupt();
+ }
+ }
+
+ ExpressionContext* ExpressionContext::clone() {
+ ExpressionContext* newContext = create(pStatus);
+ newContext->setDoingMerge(getDoingMerge());
+ newContext->setInShard(getInShard());
+ newContext->setInRouter(getInRouter());
+ return newContext;
+ }
+
+ ExpressionContext *ExpressionContext::create(InterruptStatus *pStatus) {
+ return new ExpressionContext(pStatus);
+ }
+
+}
diff --git a/src/mongo/db/pipeline/expression_context.h b/src/mongo/db/pipeline/expression_context.h
new file mode 100755
index 00000000000..b0a1260b22c
--- /dev/null
+++ b/src/mongo/db/pipeline/expression_context.h
@@ -0,0 +1,91 @@
+/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "mongo/pch.h"
+
+#include "util/intrusive_counter.h"
+
+namespace mongo {
+
+ class InterruptStatus;
+
+ class ExpressionContext :
+ public IntrusiveCounterUnsigned {
+ public:
+ virtual ~ExpressionContext();
+
+ void setDoingMerge(bool b);
+ void setInShard(bool b);
+ void setInRouter(bool b);
+
+ bool getDoingMerge() const;
+ bool getInShard() const;
+ bool getInRouter() const;
+
+ /**
+ Used by a pipeline to check for interrupts so that killOp() works.
+
+ @throws if the operation has been interrupted
+ */
+ void checkForInterrupt();
+
+ ExpressionContext* clone();
+
+ static ExpressionContext *create(InterruptStatus *pStatus);
+
+ private:
+ ExpressionContext(InterruptStatus *pStatus);
+
+ bool doingMerge;
+ bool inShard;
+ bool inRouter;
+ unsigned intCheckCounter; // interrupt check counter
+ InterruptStatus *const pStatus;
+ };
+}
+
+
+/* ======================= INLINED IMPLEMENTATIONS ========================== */
+
+namespace mongo {
+
+ inline void ExpressionContext::setDoingMerge(bool b) {
+ doingMerge = b;
+ }
+
+ inline void ExpressionContext::setInShard(bool b) {
+ inShard = b;
+ }
+
+ inline void ExpressionContext::setInRouter(bool b) {
+ inRouter = b;
+ }
+
+ inline bool ExpressionContext::getDoingMerge() const {
+ return doingMerge;
+ }
+
+ inline bool ExpressionContext::getInShard() const {
+ return inShard;
+ }
+
+ inline bool ExpressionContext::getInRouter() const {
+ return inRouter;
+ }
+
+};
diff --git a/src/mongo/db/pipeline/field_path.cpp b/src/mongo/db/pipeline/field_path.cpp
new file mode 100644
index 00000000000..a21010bb95c
--- /dev/null
+++ b/src/mongo/db/pipeline/field_path.cpp
@@ -0,0 +1,101 @@
+/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pch.h"
+#include "db/pipeline/field_path.h"
+#include "util/mongoutils/str.h"
+
+namespace mongo {
+
+ using namespace mongoutils;
+
+ const char FieldPath::prefix[] = "$";
+
+ FieldPath::FieldPath(const vector<string>& fieldPath) {
+ massert(16409, "FieldPath cannot be constructed from an empty vector.", !fieldPath.empty());
+ vFieldName.reserve(fieldPath.size());
+ for(vector<string>::const_iterator i = fieldPath.begin(); i != fieldPath.end(); ++i) {
+ pushFieldName(*i);
+ }
+ verify(getPathLength() > 0);
+ }
+
+ FieldPath::FieldPath(const string& fieldPath) {
+ /*
+ The field path could be using dot notation.
+ Break the field path up by peeling off successive pieces.
+ */
+ size_t startpos = 0;
+ while(true) {
+ /* find the next dot */
+ const size_t dotpos = fieldPath.find('.', startpos);
+
+ /* if there are no more dots, use the remainder of the string */
+ if (dotpos == fieldPath.npos) {
+ string lastFieldName = fieldPath.substr(startpos, dotpos);
+ pushFieldName(lastFieldName);
+ break;
+ }
+
+ /* use the string up to the dot */
+ const size_t length = dotpos - startpos;
+ string nextFieldName = fieldPath.substr(startpos, length);
+ pushFieldName(nextFieldName);
+
+ /* next time, search starting one spot after that */
+ startpos = dotpos + 1;
+ }
+ verify(getPathLength() > 0);
+ }
+
+ string FieldPath::getPath(bool fieldPrefix) const {
+ stringstream ss;
+ writePath(ss, fieldPrefix);
+ return ss.str();
+ }
+
+ void FieldPath::writePath(ostream &outStream, bool fieldPrefix) const {
+ if (fieldPrefix)
+ outStream << prefix;
+
+ const size_t n = vFieldName.size();
+
+ verify(n > 0);
+ outStream << vFieldName[0];
+ for(size_t i = 1; i < n; ++i)
+ outStream << '.' << vFieldName[i];
+ }
+
+ FieldPath FieldPath::tail() const {
+ vector<string> allButFirst(vFieldName.begin()+1, vFieldName.end());
+ return FieldPath(allButFirst);
+ }
+
+ void FieldPath::uassertValidFieldName(const string& fieldName) {
+ uassert(15998, "FieldPath field names may not be empty strings.", fieldName.length() > 0);
+ uassert(16410, "FieldPath field names may not start with '$'.", fieldName[0] != '$');
+ uassert(16411, "FieldPath field names may not contain '\0'.",
+ fieldName.find('\0') == string::npos);
+ uassert(16412, "FieldPath field names may not contain '.'.",
+ !str::contains(fieldName, '.'));
+ }
+
+ void FieldPath::pushFieldName(const string& fieldName) {
+ uassertValidFieldName(fieldName);
+ vFieldName.push_back(fieldName);
+ }
+
+}
diff --git a/src/mongo/db/pipeline/field_path.h b/src/mongo/db/pipeline/field_path.h
new file mode 100755
index 00000000000..c3b9aa2a22c
--- /dev/null
+++ b/src/mongo/db/pipeline/field_path.h
@@ -0,0 +1,115 @@
+/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "mongo/pch.h"
+
+namespace mongo {
+
+ class FieldPath {
+ public:
+
+ /**
+ * Constructor.
+ *
+ * @param fieldPath the dotted field path string or non empty pre-split vector.
+ * The constructed object will have getPathLength() > 0.
+ * Uassert if any component field names do not pass validation.
+ */
+ FieldPath(const string& fieldPath);
+ FieldPath(const vector<string>& fieldPath);
+
+ /**
+ Get the number of path elements in the field path.
+
+ @returns the number of path elements
+ */
+ size_t getPathLength() const;
+
+ /**
+ Get a particular path element from the path.
+
+ @param i the zero based index of the path element.
+ @returns the path element
+ */
+ string getFieldName(size_t i) const;
+
+ /**
+ Get the full path.
+
+ @param fieldPrefix whether or not to include the field prefix
+ @returns the complete field path
+ */
+ string getPath(bool fieldPrefix) const;
+
+ /**
+ Write the full path.
+
+ @param outStream where to write the path to
+ @param fieldPrefix whether or not to include the field prefix
+ */
+ void writePath(ostream &outStream, bool fieldPrefix) const;
+
+ /**
+ Get the prefix string.
+
+ @returns the prefix string
+ */
+ static const char *getPrefix();
+
+ static const char prefix[];
+
+ /**
+ * A FieldPath like this but missing the first element (useful for recursion).
+ * Precondition getPathLength() > 1.
+ */
+ FieldPath tail() const;
+
+ private:
+ /** Uassert if a field name does not pass validation. */
+ static void uassertValidFieldName(const string& fieldName);
+
+ /**
+ * Push a new field name to the back of the vector of names comprising the field path.
+ * Uassert if 'fieldName' does not pass validation.
+ */
+ void pushFieldName(const string& fieldName);
+
+ vector<string> vFieldName;
+ };
+}
+
+
+/* ======================= INLINED IMPLEMENTATIONS ========================== */
+
+namespace mongo {
+
+ inline size_t FieldPath::getPathLength() const {
+ return vFieldName.size();
+ }
+
+ inline string FieldPath::getFieldName(size_t i) const {
+ verify(i < getPathLength());
+ return vFieldName[i];
+ }
+
+ inline const char *FieldPath::getPrefix() {
+ return prefix;
+ }
+
+}
+
diff --git a/src/mongo/db/pipeline/pipeline.cpp b/src/mongo/db/pipeline/pipeline.cpp
new file mode 100644
index 00000000000..0bd4096fe78
--- /dev/null
+++ b/src/mongo/db/pipeline/pipeline.cpp
@@ -0,0 +1,477 @@
+/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pch.h"
+#include "db/pipeline/pipeline.h"
+
+#include "mongo/client/authentication_table.h"
+#include "db/jsobj.h"
+#include "db/pipeline/accumulator.h"
+#include "db/pipeline/document.h"
+#include "db/pipeline/document_source.h"
+#include "db/pipeline/expression.h"
+#include "db/pipeline/expression_context.h"
+#include "util/mongoutils/str.h"
+
+namespace mongo {
+
+ const char Pipeline::commandName[] = "aggregate";
+ const char Pipeline::pipelineName[] = "pipeline";
+ const char Pipeline::explainName[] = "explain";
+ const char Pipeline::fromRouterName[] = "fromRouter";
+ const char Pipeline::splitMongodPipelineName[] = "splitMongodPipeline";
+ const char Pipeline::serverPipelineName[] = "serverPipeline";
+ const char Pipeline::mongosPipelineName[] = "mongosPipeline";
+
+ Pipeline::~Pipeline() {
+ }
+
+ Pipeline::Pipeline(const intrusive_ptr<ExpressionContext> &pTheCtx):
+ collectionName(),
+ sourceVector(),
+ explain(false),
+ splitMongodPipeline(false),
+ pCtx(pTheCtx) {
+ }
+
+
+ /* this structure is used to make a lookup table of operators */
+ struct StageDesc {
+ const char *pName;
+ intrusive_ptr<DocumentSource> (*pFactory)(
+ BSONElement *, const intrusive_ptr<ExpressionContext> &);
+ };
+
+ /* this table must be in alphabetical order by name for bsearch() */
+ static const StageDesc stageDesc[] = {
+#ifdef NEVER /* disabled for now in favor of $match */
+ {DocumentSourceFilter::filterName,
+ DocumentSourceFilter::createFromBson},
+#endif
+ {DocumentSourceGroup::groupName,
+ DocumentSourceGroup::createFromBson},
+ {DocumentSourceLimit::limitName,
+ DocumentSourceLimit::createFromBson},
+ {DocumentSourceMatch::matchName,
+ DocumentSourceMatch::createFromBson},
+#ifdef LATER /* https://jira.mongodb.org/browse/SERVER-3253 */
+ {DocumentSourceOut::outName,
+ DocumentSourceOut::createFromBson},
+#endif
+ {DocumentSourceProject::projectName,
+ DocumentSourceProject::createFromBson},
+ {DocumentSourceSkip::skipName,
+ DocumentSourceSkip::createFromBson},
+ {DocumentSourceSort::sortName,
+ DocumentSourceSort::createFromBson},
+ {DocumentSourceUnwind::unwindName,
+ DocumentSourceUnwind::createFromBson},
+ };
+ static const size_t nStageDesc = sizeof(stageDesc) / sizeof(StageDesc);
+
+ static int stageDescCmp(const void *pL, const void *pR) {
+ return strcmp(((const StageDesc *)pL)->pName,
+ ((const StageDesc *)pR)->pName);
+ }
+
+ intrusive_ptr<Pipeline> Pipeline::parseCommand(
+ string &errmsg, BSONObj &cmdObj,
+ const intrusive_ptr<ExpressionContext> &pCtx) {
+ intrusive_ptr<Pipeline> pPipeline(new Pipeline(pCtx));
+ vector<BSONElement> pipeline;
+
+ /* gather the specification for the aggregation */
+ for(BSONObj::iterator cmdIterator = cmdObj.begin();
+ cmdIterator.more(); ) {
+ BSONElement cmdElement(cmdIterator.next());
+ const char *pFieldName = cmdElement.fieldName();
+
+ /* look for the aggregation command */
+ if (!strcmp(pFieldName, commandName)) {
+ pPipeline->collectionName = cmdElement.String();
+ continue;
+ }
+
+ /* check for the collection name */
+ if (!strcmp(pFieldName, pipelineName)) {
+ pipeline = cmdElement.Array();
+ continue;
+ }
+
+ /* check for explain option */
+ if (!strcmp(pFieldName, explainName)) {
+ pPipeline->explain = cmdElement.Bool();
+ continue;
+ }
+
+ /* if the request came from the router, we're in a shard */
+ if (!strcmp(pFieldName, fromRouterName)) {
+ pCtx->setInShard(cmdElement.Bool());
+ continue;
+ }
+
+ /* check for debug options */
+ if (!strcmp(pFieldName, splitMongodPipelineName)) {
+ pPipeline->splitMongodPipeline = true;
+ continue;
+ }
+
+ /* Ignore $auth information sent along with the command. The authentication system will
+ * use it, it's not a part of the pipeline.
+ */
+ if (!strcmp(pFieldName, AuthenticationTable::fieldName.c_str())) {
+ continue;
+ }
+
+ /* we didn't recognize a field in the command */
+ ostringstream sb;
+ sb <<
+ "unrecognized field \"" <<
+ cmdElement.fieldName();
+ errmsg = sb.str();
+ return intrusive_ptr<Pipeline>();
+ }
+
+ /*
+ If we get here, we've harvested the fields we expect for a pipeline.
+
+ Set up the specified document source pipeline.
+ */
+ SourceVector *pSourceVector = &pPipeline->sourceVector; // shorthand
+
+ /* iterate over the steps in the pipeline */
+ const size_t nSteps = pipeline.size();
+ for(size_t iStep = 0; iStep < nSteps; ++iStep) {
+ /* pull out the pipeline element as an object */
+ BSONElement pipeElement(pipeline[iStep]);
+ uassert(15942, str::stream() << "pipeline element " <<
+ iStep << " is not an object",
+ pipeElement.type() == Object);
+ BSONObj bsonObj(pipeElement.Obj());
+
+ intrusive_ptr<DocumentSource> pSource;
+
+ /* use the object to add a DocumentSource to the processing chain */
+ BSONObjIterator bsonIterator(bsonObj);
+ while(bsonIterator.more()) {
+ BSONElement bsonElement(bsonIterator.next());
+ const char *pFieldName = bsonElement.fieldName();
+
+ /* select the appropriate operation and instantiate */
+ StageDesc key;
+ key.pName = pFieldName;
+ const StageDesc *pDesc = (const StageDesc *)
+ bsearch(&key, stageDesc, nStageDesc, sizeof(StageDesc),
+ stageDescCmp);
+ if (pDesc) {
+ pSource = (*pDesc->pFactory)(&bsonElement, pCtx);
+ pSource->setPipelineStep(iStep);
+ }
+ else {
+ ostringstream sb;
+ sb <<
+ "Pipeline::run(): unrecognized pipeline op \"" <<
+ pFieldName;
+ errmsg = sb.str();
+ return intrusive_ptr<Pipeline>();
+ }
+ }
+
+ pSourceVector->push_back(pSource);
+ }
+
+ /* if there aren't any pipeline stages, there's nothing more to do */
+ if (!pSourceVector->size())
+ return pPipeline;
+
+ /*
+ Move filters up where possible.
+
+ CW TODO -- move filter past projections where possible, and noting
+ corresponding field renaming.
+ */
+
+ /*
+ Wherever there is a match immediately following a sort, swap them.
+ This means we sort fewer items. Neither changes the documents in
+ the stream, so this transformation shouldn't affect the result.
+
+ We do this first, because then when we coalesce operators below,
+ any adjacent matches will be combined.
+ */
+ for(size_t srcn = pSourceVector->size(), srci = 1;
+ srci < srcn; ++srci) {
+ intrusive_ptr<DocumentSource> &pSource = pSourceVector->at(srci);
+ if (dynamic_cast<DocumentSourceMatch *>(pSource.get())) {
+ intrusive_ptr<DocumentSource> &pPrevious =
+ pSourceVector->at(srci - 1);
+ if (dynamic_cast<DocumentSourceSort *>(pPrevious.get())) {
+ /* swap this item with the previous */
+ intrusive_ptr<DocumentSource> pTemp(pPrevious);
+ pPrevious = pSource;
+ pSource = pTemp;
+ }
+ }
+ }
+
+ /*
+ Coalesce adjacent filters where possible. Two adjacent filters
+ are equivalent to one filter whose predicate is the conjunction of
+ the two original filters' predicates. For now, capture this by
+ giving any DocumentSource the option to absorb it's successor; this
+ will also allow adjacent projections to coalesce when possible.
+
+ Run through the DocumentSources, and give each one the opportunity
+ to coalesce with its successor. If successful, remove the
+ successor.
+
+ Move all document sources to a temporary list.
+ */
+ SourceVector tempVector(*pSourceVector);
+ pSourceVector->clear();
+
+ /* move the first one to the final list */
+ pSourceVector->push_back(tempVector[0]);
+
+ /* run through the sources, coalescing them or keeping them */
+ for(size_t tempn = tempVector.size(), tempi = 1;
+ tempi < tempn; ++tempi) {
+ /*
+ If we can't coalesce the source with the last, then move it
+ to the final list, and make it the new last. (If we succeeded,
+ then we're still on the same last, and there's no need to move
+ or do anything with the source -- the destruction of tempVector
+ will take care of the rest.)
+ */
+ intrusive_ptr<DocumentSource> &pLastSource = pSourceVector->back();
+ intrusive_ptr<DocumentSource> &pTemp = tempVector.at(tempi);
+ if (!pTemp || !pLastSource) {
+ errmsg = "Pipeline received empty document as argument";
+ return intrusive_ptr<Pipeline>();
+ }
+ if (!pLastSource->coalesce(pTemp))
+ pSourceVector->push_back(pTemp);
+ }
+
+ /* optimize the elements in the pipeline */
+ for(SourceVector::iterator iter(pSourceVector->begin()),
+ listEnd(pSourceVector->end()); iter != listEnd; ++iter) {
+ if (!*iter) {
+ errmsg = "Pipeline received empty document as argument";
+ return intrusive_ptr<Pipeline>();
+ }
+
+ (*iter)->optimize();
+ }
+
+ return pPipeline;
+ }
+
+ intrusive_ptr<Pipeline> Pipeline::splitForSharded() {
+ /* create an initialize the shard spec we'll return */
+ intrusive_ptr<Pipeline> pShardPipeline(new Pipeline(pCtx));
+ pShardPipeline->collectionName = collectionName;
+ pShardPipeline->explain = explain;
+
+ // We will be removing from the front so reverse for now. undone later
+ // TODO: maybe sourceVector should be a deque
+ reverse(sourceVector.begin(), sourceVector.end());
+
+ /*
+ Run through the pipeline, looking for points to split it into
+ shard pipelines, and the rest.
+ */
+ while(!sourceVector.empty()) {
+ // pop the first source
+ intrusive_ptr<DocumentSource> pSource = sourceVector.back();
+ sourceVector.pop_back();
+
+ // Check if this source is splittable
+ SplittableDocumentSource* splittable=
+ dynamic_cast<SplittableDocumentSource *>(pSource.get());
+
+ if (!splittable){
+ // move the source from the router sourceVector to the shard sourceVector
+ pShardPipeline->sourceVector.push_back(pSource);
+ }
+ else {
+ // split into Router and Shard sources
+ intrusive_ptr<DocumentSource> shardSource = splittable->getShardSource();
+ intrusive_ptr<DocumentSource> routerSource = splittable->getRouterSource();
+ if (shardSource) pShardPipeline->sourceVector.push_back(shardSource);
+ if (routerSource) this->sourceVector.push_back(routerSource);
+
+ // put the sourceVector back in the correct order and exit the loop
+ reverse(sourceVector.begin(), sourceVector.end());
+ break;
+ }
+ }
+
+ return pShardPipeline;
+ }
+
+ bool Pipeline::getInitialQuery(BSONObjBuilder *pQueryBuilder) const
+ {
+ if (!sourceVector.size())
+ return false;
+
+ /* look for an initial $match */
+ const intrusive_ptr<DocumentSource> &pMC = sourceVector.front();
+ const DocumentSourceMatch *pMatch =
+ dynamic_cast<DocumentSourceMatch *>(pMC.get());
+
+ if (!pMatch)
+ return false;
+
+ /* build the query */
+ pMatch->toMatcherBson(pQueryBuilder);
+
+ return true;
+ }
+
+ void Pipeline::toBson(BSONObjBuilder *pBuilder) const {
+ /* create an array out of the pipeline operations */
+ BSONArrayBuilder arrayBuilder;
+ for(SourceVector::const_iterator iter(sourceVector.begin()),
+ listEnd(sourceVector.end()); iter != listEnd; ++iter) {
+ intrusive_ptr<DocumentSource> pSource(*iter);
+ pSource->addToBsonArray(&arrayBuilder);
+ }
+
+ /* add the top-level items to the command */
+ pBuilder->append(commandName, getCollectionName());
+ pBuilder->append(pipelineName, arrayBuilder.arr());
+
+ if (explain) {
+ pBuilder->append(explainName, explain);
+ }
+
+ bool btemp;
+ if ((btemp = getSplitMongodPipeline())) {
+ pBuilder->append(splitMongodPipelineName, btemp);
+ }
+
+ if ((btemp = pCtx->getInRouter())) {
+ pBuilder->append(fromRouterName, btemp);
+ }
+ }
+
+ bool Pipeline::run(BSONObjBuilder &result, string &errmsg,
+ const intrusive_ptr<DocumentSource> &pInputSource) {
+ /* chain together the sources we found */
+ DocumentSource *pSource = pInputSource.get();
+ for(SourceVector::iterator iter(sourceVector.begin()),
+ listEnd(sourceVector.end()); iter != listEnd; ++iter) {
+ intrusive_ptr<DocumentSource> pTemp(*iter);
+ pTemp->setSource(pSource);
+ pSource = pTemp.get();
+ }
+ /* pSource is left pointing at the last source in the chain */
+
+ /*
+ Iterate through the resulting documents, and add them to the result.
+ We do this even if we're doing an explain, in order to capture
+ the document counts and other stats. However, we don't capture
+ the result documents for explain.
+ */
+ if (explain) {
+ if (!pCtx->getInRouter())
+ writeExplainShard(result, pInputSource);
+ else {
+ writeExplainMongos(result, pInputSource);
+ }
+ }
+ else {
+ // the array in which the aggregation results reside
+ // cant use subArrayStart() due to error handling
+ BSONArrayBuilder resultArray;
+ for(bool hasDoc = !pSource->eof(); hasDoc; hasDoc = pSource->advance()) {
+ intrusive_ptr<Document> pDocument(pSource->getCurrent());
+
+ /* add the document to the result set */
+ BSONObjBuilder documentBuilder (resultArray.subobjStart());
+ pDocument->toBson(&documentBuilder);
+ documentBuilder.doneFast();
+ // object will be too large, assert. the extra 1KB is for headers
+ uassert(16389,
+ str::stream() << "aggregation result exceeds maximum document size ("
+ << BSONObjMaxUserSize / (1024 * 1024) << "MB)",
+ resultArray.len() < BSONObjMaxUserSize - 1024);
+ }
+
+ resultArray.done();
+ result.appendArray("result", resultArray.arr());
+ }
+
+ return true;
+ }
+
+ void Pipeline::writeExplainOps(BSONArrayBuilder *pArrayBuilder) const {
+ for(SourceVector::const_iterator iter(sourceVector.begin()),
+ listEnd(sourceVector.end()); iter != listEnd; ++iter) {
+ intrusive_ptr<DocumentSource> pSource(*iter);
+
+ pSource->addToBsonArray(pArrayBuilder, true);
+ }
+ }
+
+ void Pipeline::writeExplainShard(
+ BSONObjBuilder &result,
+ const intrusive_ptr<DocumentSource> &pInputSource) const {
+ BSONArrayBuilder opArray; // where we'll put the pipeline ops
+
+ // first the cursor, which isn't in the opArray
+ pInputSource->addToBsonArray(&opArray, true);
+
+ // next, add the pipeline operators
+ writeExplainOps(&opArray);
+
+ result.appendArray(serverPipelineName, opArray.arr());
+ }
+
+ void Pipeline::writeExplainMongos(
+ BSONObjBuilder &result,
+ const intrusive_ptr<DocumentSource> &pInputSource) const {
+
+ /*
+ For now, this should be a BSON source array.
+ In future, we might have a more clever way of getting this, when
+ we have more interleaved fetching between shards. The DocumentSource
+ interface will have to change to accomodate that.
+ */
+ DocumentSourceBsonArray *pSourceBsonArray =
+ dynamic_cast<DocumentSourceBsonArray *>(pInputSource.get());
+ verify(pSourceBsonArray);
+
+ BSONArrayBuilder shardOpArray; // where we'll put the pipeline ops
+ for(bool hasDocument = !pSourceBsonArray->eof(); hasDocument;
+ hasDocument = pSourceBsonArray->advance()) {
+ intrusive_ptr<Document> pDocument(
+ pSourceBsonArray->getCurrent());
+ BSONObjBuilder opBuilder;
+ pDocument->toBson(&opBuilder);
+ shardOpArray.append(opBuilder.obj());
+ }
+
+ BSONArrayBuilder mongosOpArray; // where we'll put the pipeline ops
+ writeExplainOps(&mongosOpArray);
+
+ // now we combine the shard pipelines with the one here
+ result.append(serverPipelineName, shardOpArray.arr());
+ result.append(mongosPipelineName, mongosOpArray.arr());
+ }
+
+} // namespace mongo
diff --git a/src/mongo/db/pipeline/pipeline.h b/src/mongo/db/pipeline/pipeline.h
new file mode 100755
index 00000000000..9f3a545c5d7
--- /dev/null
+++ b/src/mongo/db/pipeline/pipeline.h
@@ -0,0 +1,225 @@
+/**
+ * Copyright 2011 (c) 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "mongo/pch.h"
+
+#include "util/intrusive_counter.h"
+#include "util/timer.h"
+
+namespace mongo {
+ class BSONObj;
+ class BSONObjBuilder;
+ class BSONArrayBuilder;
+ class DocumentSource;
+ class DocumentSourceProject;
+ class Expression;
+ class ExpressionContext;
+ class ExpressionNary;
+ struct OpDesc; // local private struct
+
+ /** mongodb "commands" (sent via db.$cmd.findOne(...))
+ subclass to make a command. define a singleton object for it.
+ */
+ class Pipeline :
+ public IntrusiveCounterUnsigned {
+ public:
+ virtual ~Pipeline();
+
+ /**
+ Create a pipeline from the command.
+
+ @param errmsg where to write errors, if there are any
+ @param cmdObj the command object sent from the client
+ @returns the pipeline, if created, otherwise a NULL reference
+ */
+ static intrusive_ptr<Pipeline> parseCommand(
+ string &errmsg, BSONObj &cmdObj,
+ const intrusive_ptr<ExpressionContext> &pCtx);
+
+ /**
+ Get the collection name from the command.
+
+ @returns the collection name
+ */
+ string getCollectionName() const;
+
+ /**
+ Split the current Pipeline into a Pipeline for each shard, and
+ a Pipeline that combines the results within mongos.
+
+ This permanently alters this pipeline for the merging operation.
+
+ @returns the Spec for the pipeline command that should be sent
+ to the shards
+ */
+ intrusive_ptr<Pipeline> splitForSharded();
+
+ /**
+ If the pipeline starts with a $match, dump its BSON predicate
+ specification to the supplied builder and return true.
+
+ @param pQueryBuilder the builder to put the match BSON into
+ @returns true if a match was found and dumped to pQueryBuilder,
+ false otherwise
+ */
+ bool getInitialQuery(BSONObjBuilder *pQueryBuilder) const;
+
+ /**
+ Write the Pipeline as a BSONObj command. This should be the
+ inverse of parseCommand().
+
+ This is only intended to be used by the shard command obtained
+ from splitForSharded(). Some pipeline operations in the merge
+ process do not have equivalent command forms, and using this on
+ the mongos Pipeline will cause assertions.
+
+ @param the builder to write the command to
+ */
+ void toBson(BSONObjBuilder *pBuilder) const;
+
+ /**
+ Run the Pipeline on the given source.
+
+ @param result builder to write the result to
+ @param errmsg place to put error messages, if any
+ @param pSource the document source to use at the head of the chain
+ @returns true on success, false if an error occurs
+ */
+ bool run(BSONObjBuilder &result, string &errmsg,
+ const intrusive_ptr<DocumentSource> &pSource);
+
+ /**
+ Debugging: should the processing pipeline be split within
+ mongod, simulating the real mongos/mongod split? This is determined
+ by setting the splitMongodPipeline field in an "aggregate"
+ command.
+
+ The split itself is handled by the caller, which is currently
+ pipeline_command.cpp.
+
+ @returns true if the pipeline is to be split
+ */
+ bool getSplitMongodPipeline() const;
+
+ /**
+ Ask if this is for an explain request.
+
+ @returns true if this is an explain
+ */
+ bool isExplain() const;
+
+ /**
+ The aggregation command name.
+ */
+ static const char commandName[];
+
+ /*
+ PipelineD is a "sister" class that has additional functionality
+ for the Pipeline. It exists because of linkage requirements.
+ Pipeline needs to function in mongod and mongos. PipelineD
+ contains extra functionality required in mongod, and which can't
+ appear in mongos because the required symbols are unavailable
+ for linking there. Consider PipelineD to be an extension of this
+ class for mongod only.
+ */
+ friend class PipelineD;
+
+ private:
+ static const char pipelineName[];
+ static const char explainName[];
+ static const char fromRouterName[];
+ static const char splitMongodPipelineName[];
+ static const char serverPipelineName[];
+ static const char mongosPipelineName[];
+
+ Pipeline(const intrusive_ptr<ExpressionContext> &pCtx);
+
+ /*
+ Write the pipeline's operators to the given array, with the
+ explain flag true (for DocumentSource::addToBsonArray()).
+
+ @param pArrayBuilder where to write the ops to
+ */
+ void writeExplainOps(BSONArrayBuilder *pArrayBuilder) const;
+
+ /*
+ Write the pipeline's operators to the given result document,
+ for a shard server (or regular server, in an unsharded setup).
+
+ This uses writeExplainOps() and adds that array to the result
+ with the serverPipelineName. That will be preceded by explain
+ information for the input source.
+
+ @param result the object to add the explain information to
+ @param pInputSource source for the pipeline
+ */
+ void writeExplainShard(BSONObjBuilder &result,
+ const intrusive_ptr<DocumentSource> &pInputSource) const;
+
+ /*
+ Write the pipeline's operators to the given result document,
+ for a mongos instance.
+
+ This first adds the serverPipeline obtained from the input
+ source.
+
+ Then this uses writeExplainOps() and adds that array to the result
+ with the serverPipelineName. That will be preceded by explain
+ information for the input source.
+
+ @param result the object to add the explain information to
+ @param pInputSource source for the pipeline; expected to be the
+ output of a shard
+ */
+ void writeExplainMongos(BSONObjBuilder &result,
+ const intrusive_ptr<DocumentSource> &pInputSource) const;
+
+ string collectionName;
+ typedef vector<intrusive_ptr<DocumentSource> > SourceVector;
+ SourceVector sourceVector;
+ bool explain;
+
+ bool splitMongodPipeline;
+ intrusive_ptr<ExpressionContext> pCtx;
+ };
+
+} // namespace mongo
+
+
+/* ======================= INLINED IMPLEMENTATIONS ========================== */
+
+namespace mongo {
+
+ inline string Pipeline::getCollectionName() const {
+ return collectionName;
+ }
+
+ inline bool Pipeline::getSplitMongodPipeline() const {
+ if (!DEBUG_BUILD)
+ return false;
+
+ return splitMongodPipeline;
+ }
+
+ inline bool Pipeline::isExplain() const {
+ return explain;
+ }
+
+} // namespace mongo
+
+
diff --git a/src/mongo/db/pipeline/pipeline_d.cpp b/src/mongo/db/pipeline/pipeline_d.cpp
new file mode 100755
index 00000000000..ed075f727fa
--- /dev/null
+++ b/src/mongo/db/pipeline/pipeline_d.cpp
@@ -0,0 +1,200 @@
+/**
+ * Copyright (c) 2012 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pch.h"
+#include "db/pipeline/pipeline.h"
+#include "db/pipeline/pipeline_d.h"
+
+#include "db/cursor.h"
+#include "db/queryutil.h"
+#include "db/pipeline/document_source.h"
+#include "mongo/client/dbclientinterface.h"
+
+
+namespace mongo {
+
+ intrusive_ptr<DocumentSourceCursor> PipelineD::prepareCursorSource(
+ const intrusive_ptr<Pipeline> &pPipeline,
+ const string &dbName,
+ const intrusive_ptr<ExpressionContext> &pExpCtx) {
+
+ Pipeline::SourceVector *pSources = &pPipeline->sourceVector;
+
+ /* look for an initial match */
+ BSONObjBuilder queryBuilder;
+ bool initQuery = pPipeline->getInitialQuery(&queryBuilder);
+ if (initQuery) {
+ /*
+ This will get built in to the Cursor we'll create, so
+ remove the match from the pipeline
+ */
+ pSources->erase(pSources->begin());
+ }
+
+ /*
+ Create a query object.
+
+ This works whether we got an initial query above or not; if not,
+ it results in a "{}" query, which will be what we want in that case.
+
+ We create a pointer to a shared object instead of a local
+ object so that we can preserve it for the Cursor we're going to
+ create below.
+ */
+ shared_ptr<BSONObj> pQueryObj(new BSONObj(queryBuilder.obj()));
+
+ /* Look for an initial simple project; we'll avoid constructing Values
+ * for fields that won't make it through the projection.
+ */
+
+ BSONObj projection;
+ {
+ set<string> deps;
+ DocumentSource::GetDepsReturn status = DocumentSource::SEE_NEXT;
+ for (size_t i=0; i < pSources->size() && status == DocumentSource::SEE_NEXT; i++) {
+ status = (*pSources)[i]->getDependencies(deps);
+ }
+
+ if (status == DocumentSource::EXHAUSTIVE) {
+ projection = DocumentSource::depsToProjection(deps);
+ }
+ }
+
+ /*
+ Look for an initial sort; we'll try to add this to the
+ Cursor we create. If we're successful in doing that (further down),
+ we'll remove the $sort from the pipeline, because the documents
+ will already come sorted in the specified order as a result of the
+ index scan.
+ */
+ const DocumentSourceSort *pSort = NULL;
+ BSONObjBuilder sortBuilder;
+ if (pSources->size()) {
+ const intrusive_ptr<DocumentSource> &pSC = pSources->front();
+ pSort = dynamic_cast<DocumentSourceSort *>(pSC.get());
+
+ if (pSort) {
+ /* build the sort key */
+ pSort->sortKeyToBson(&sortBuilder, false);
+ }
+ }
+
+ /* Create the sort object; see comments on the query object above */
+ shared_ptr<BSONObj> pSortObj(new BSONObj(sortBuilder.obj()));
+
+ /* get the full "namespace" name */
+ string fullName(dbName + "." + pPipeline->getCollectionName());
+
+ /* for debugging purposes, show what the query and sort are */
+ DEV {
+ (log() << "\n---- query BSON\n" <<
+ pQueryObj->jsonString(Strict, 1) << "\n----\n").flush();
+ (log() << "\n---- sort BSON\n" <<
+ pSortObj->jsonString(Strict, 1) << "\n----\n").flush();
+ (log() << "\n---- fullName\n" <<
+ fullName << "\n----\n").flush();
+ }
+
+ // Create the necessary context to use a Cursor, including taking a namespace read lock,
+ // see SERVER-6123.
+ // Note: this may throw if the sharding version for this connection is out of date.
+ shared_ptr<DocumentSourceCursor::CursorWithContext> cursorWithContext
+ ( new DocumentSourceCursor::CursorWithContext( fullName ) );
+
+ /*
+ Create the cursor.
+
+ If we try to create a cursor that includes both the match and the
+ sort, and the two are incompatible wrt the available indexes, then
+ we don't get a cursor back.
+
+ So we try to use both first. If that fails, try again, without the
+ sort.
+
+ If we don't have a sort, jump straight to just creating a cursor
+ without the sort.
+
+ If we are able to incorporate the sort into the cursor, remove it
+ from the head of the pipeline.
+
+ LATER - we should be able to find this out before we create the
+ cursor. Either way, we can then apply other optimizations there
+ are tickets for, such as SERVER-4507.
+ */
+
+ shared_ptr<Cursor> pCursor;
+ bool initSort = false;
+ if (pSort) {
+ const BSONObj queryAndSort = BSON("$query" << *pQueryObj << "$orderby" << *pSortObj);
+ shared_ptr<ParsedQuery> pq (new ParsedQuery(
+ fullName.c_str(), 0, 0, QueryOption_NoCursorTimeout, queryAndSort, projection));
+
+ /* try to create the cursor with the query and the sort */
+ shared_ptr<Cursor> pSortedCursor(
+ pCursor = NamespaceDetailsTransient::getCursor(
+ fullName.c_str(), *pQueryObj, *pSortObj,
+ QueryPlanSelectionPolicy::any(), NULL, pq));
+
+ if (pSortedCursor.get()) {
+ /* success: remove the sort from the pipeline */
+ pSources->erase(pSources->begin());
+
+ pCursor = pSortedCursor;
+ initSort = true;
+ }
+ }
+
+ if (!pCursor.get()) {
+ shared_ptr<ParsedQuery> pq (new ParsedQuery(
+ fullName.c_str(), 0, 0, QueryOption_NoCursorTimeout, *pQueryObj, projection));
+
+ /* try to create the cursor without the sort */
+ shared_ptr<Cursor> pUnsortedCursor(
+ pCursor = NamespaceDetailsTransient::getCursor(
+ fullName.c_str(), *pQueryObj, BSONObj(),
+ QueryPlanSelectionPolicy::any(), NULL, pq));
+
+ pCursor = pUnsortedCursor;
+ }
+
+ // Now add the Cursor to cursorWithContext.
+ cursorWithContext->_cursor.reset
+ ( new ClientCursor( QueryOption_NoCursorTimeout, pCursor, fullName ) );
+
+ /* wrap the cursor with a DocumentSource and return that */
+ intrusive_ptr<DocumentSourceCursor> pSource(
+ DocumentSourceCursor::create( cursorWithContext, pExpCtx ) );
+
+ pSource->setNamespace(fullName);
+
+ /*
+ Note the query and sort
+
+ This records them for explain, and keeps them alive; they are
+ referenced (by reference) by the cursor, which doesn't make its
+ own copies of them.
+ */
+ pSource->setQuery(pQueryObj);
+ if (initSort)
+ pSource->setSort(pSortObj);
+
+ if (!projection.isEmpty())
+ pSource->setProjection(projection);
+
+ return pSource;
+ }
+
+} // namespace mongo
diff --git a/src/mongo/db/pipeline/pipeline_d.h b/src/mongo/db/pipeline/pipeline_d.h
new file mode 100755
index 00000000000..83f9d78fff0
--- /dev/null
+++ b/src/mongo/db/pipeline/pipeline_d.h
@@ -0,0 +1,62 @@
+/**
+ * Copyright 2012 (c) 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "mongo/pch.h"
+
+namespace mongo {
+ class DocumentSourceCursor;
+ class Pipeline;
+
+ /*
+ PipelineD is an extension of the Pipeline class, but with additional
+ material that references symbols that are not available in mongos,
+ where the remainder of the Pipeline class also functions. PipelineD
+ is a friend of Pipeline so that it can have equal access to Pipeline's
+ members.
+
+ See the friend declaration in Pipeline.
+ */
+ class PipelineD {
+ public:
+
+ /**
+ Create a Cursor wrapped in a DocumentSourceCursor, which is suitable
+ to be the first source for a pipeline to begin with. This source
+ will feed the execution of the pipeline.
+
+ This method looks for early pipeline stages that can be folded into
+ the underlying cursor, and when a cursor can absorb those, they
+ are removed from the head of the pipeline. For example, an
+ early match can be removed and replaced with a Cursor that will
+ do an index scan.
+
+ @param pPipeline the logical "this" for this operation
+ @param dbName the name of the database
+ @param pExpCtx the expression context for this pipeline
+ @returns the cursor that was created
+ */
+ static intrusive_ptr<DocumentSourceCursor> prepareCursorSource(
+ const intrusive_ptr<Pipeline> &pPipeline,
+ const string &dbName,
+ const intrusive_ptr<ExpressionContext> &pExpCtx);
+
+ private:
+ PipelineD(); // does not exist: prevent instantiation
+ };
+
+} // namespace mongo
diff --git a/src/mongo/db/pipeline/value.cpp b/src/mongo/db/pipeline/value.cpp
new file mode 100644
index 00000000000..ff75022a4fb
--- /dev/null
+++ b/src/mongo/db/pipeline/value.cpp
@@ -0,0 +1,1129 @@
+/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "pch.h"
+#include "db/pipeline/value.h"
+
+#include <boost/functional/hash.hpp>
+#include "db/jsobj.h"
+#include "db/pipeline/builder.h"
+#include "db/pipeline/document.h"
+#include "util/mongoutils/str.h"
+
+namespace mongo {
+ using namespace mongoutils;
+
+ const intrusive_ptr<const Value> Value::pFieldUndefined(
+ new ValueStatic(Undefined));
+ const intrusive_ptr<const Value> Value::pFieldNull(new ValueStatic());
+ const intrusive_ptr<const Value> Value::pFieldTrue(new ValueStatic(true));
+ const intrusive_ptr<const Value> Value::pFieldFalse(new ValueStatic(false));
+ const intrusive_ptr<const Value> Value::pFieldMinusOne(new ValueStatic(-1));
+ const intrusive_ptr<const Value> Value::pFieldZero(new ValueStatic(0));
+ const intrusive_ptr<const Value> Value::pFieldOne(new ValueStatic(1));
+
+ Value::~Value() {
+ }
+
+ Value::Value(): type(jstNULL) {}
+
+ Value::Value(BSONType theType): type(theType) {
+ switch(type) {
+ case Undefined:
+ case jstNULL:
+ case Object: // empty
+ case Array: // empty
+ break;
+
+ case Bool:
+ boolValue = false;
+ break;
+
+ case NumberDouble:
+ doubleValue = 0;
+ break;
+
+ case NumberInt:
+ intValue = 0;
+ break;
+
+ case NumberLong:
+ longValue = 0;
+ break;
+
+ case Date:
+ dateValue = 0;
+ break;
+
+ case Timestamp:
+ timestampValue = 0;
+ break;
+
+ default:
+ // nothing else is allowed
+ uassert(16001, str::stream() <<
+ "can't create empty Value of type " << typeName(type), false);
+ break;
+ }
+ }
+
+ Value::Value(bool value)
+ : type(Bool)
+ , boolValue(value)
+ {}
+
+ intrusive_ptr<const Value> Value::createFromBsonElement(
+ BSONElement *pBsonElement) {
+ switch (pBsonElement->type()) {
+ case Undefined:
+ return getUndefined();
+ case jstNULL:
+ return getNull();
+ case Bool:
+ if (pBsonElement->boolean())
+ return getTrue();
+ else
+ return getFalse();
+ default:
+ intrusive_ptr<const Value> pValue(new Value(pBsonElement));
+ return pValue;
+ }
+ }
+
+ Value::Value(BSONElement *pBsonElement):
+ type(pBsonElement->type()),
+ pDocumentValue(),
+ vpValue() {
+ switch(type) {
+ case NumberDouble:
+ doubleValue = pBsonElement->Double();
+ break;
+
+ case String:
+ stringValue = pBsonElement->str();
+ break;
+
+ case Object: {
+ BSONObj document(pBsonElement->embeddedObject());
+ pDocumentValue = Document::createFromBsonObj(&document);
+ break;
+ }
+
+ case Array: {
+ vector<BSONElement> vElement(pBsonElement->Array());
+ const size_t n = vElement.size();
+
+ vpValue.reserve(n); // save on realloc()ing
+
+ for(size_t i = 0; i < n; ++i) {
+ vpValue.push_back(
+ Value::createFromBsonElement(&vElement[i]));
+ }
+ break;
+ }
+
+ case jstOID:
+ BOOST_STATIC_ASSERT(sizeof(oidValue) == sizeof(OID));
+ memcpy(oidValue, pBsonElement->OID().getData(), sizeof(oidValue));
+ break;
+
+ case Bool:
+ boolValue = pBsonElement->Bool();
+ break;
+
+ case Date:
+ // this is really signed but typed as unsigned for historical reasons
+ dateValue = static_cast<long long>(pBsonElement->Date().millis);
+ break;
+
+ case RegEx:
+ stringValue = pBsonElement->regex();
+ // TODO pBsonElement->regexFlags();
+ break;
+
+ case NumberInt:
+ intValue = pBsonElement->numberInt();
+ break;
+
+ case Timestamp:
+ // asDate is a poorly named function that returns a ReplTime
+ timestampValue = pBsonElement->_opTime().asDate();
+ break;
+
+ case NumberLong:
+ longValue = pBsonElement->numberLong();
+ break;
+
+ case Undefined:
+ case jstNULL:
+ break;
+
+ case BinData:
+ case Symbol:
+ case CodeWScope:
+
+ /* these shouldn't happen in this context */
+ case MinKey:
+ case EOO:
+ case DBRef:
+ case Code:
+ case MaxKey:
+ uassert(16002, str::stream() <<
+ "can't create Value of BSON type " << typeName(type), false);
+ break;
+ }
+ }
+
+ Value::Value(int value)
+ : type(NumberInt)
+ , intValue(value)
+ {}
+
+ intrusive_ptr<const Value> Value::createInt(int value) {
+ intrusive_ptr<const Value> pValue(new Value(value));
+ return pValue;
+ }
+
+ intrusive_ptr<const Value> Value::createIntOrLong(long long value) {
+ if (value > numeric_limits<int>::max() || value < numeric_limits<int>::min()) {
+ // it is too large to be an int and should remain a long
+ return new Value(value);
+ }
+
+ // should be an int since all arguments were int and it fits
+ return createInt(value);
+ }
+
+ Value::Value(long long value)
+ : type(NumberLong)
+ , longValue(value)
+ {}
+
+ intrusive_ptr<const Value> Value::createLong(long long value) {
+ intrusive_ptr<const Value> pValue(new Value(value));
+ return pValue;
+ }
+
+ Value::Value(double value)
+ : type(NumberDouble)
+ , doubleValue(value)
+ {}
+
+ intrusive_ptr<const Value> Value::createDouble(double value) {
+ intrusive_ptr<const Value> pValue(new Value(value));
+ return pValue;
+ }
+
+ intrusive_ptr<const Value> Value::createDate(const long long &value) {
+ // Can't directly construct because constructor would clash with createLong
+ intrusive_ptr<Value> pValue(new Value(Date));
+ pValue->dateValue = value;
+ return pValue;
+ }
+
+ Value::Value(const OpTime& value)
+ : type(Timestamp)
+ , timestampValue(value.asDate())
+ {}
+
+ intrusive_ptr<const Value> Value::createTimestamp(const OpTime& value) {
+ intrusive_ptr<const Value> pValue(new Value(value));
+ return pValue;
+ }
+
+ Value::Value(const string &value):
+ type(String),
+ pDocumentValue(),
+ vpValue() {
+ stringValue = value;
+ }
+
+ intrusive_ptr<const Value> Value::createString(const string &value) {
+ intrusive_ptr<const Value> pValue(new Value(value));
+ return pValue;
+ }
+
+ Value::Value(const intrusive_ptr<Document> &pDocument):
+ type(Object),
+ pDocumentValue(pDocument),
+ vpValue() {
+ }
+
+ intrusive_ptr<const Value> Value::createDocument(
+ const intrusive_ptr<Document> &pDocument) {
+ intrusive_ptr<const Value> pValue(new Value(pDocument));
+ return pValue;
+ }
+
+ Value::Value(const vector<intrusive_ptr<const Value> > &thevpValue):
+ type(Array),
+ pDocumentValue(),
+ vpValue(thevpValue) {
+ }
+
+ intrusive_ptr<const Value> Value::createArray(
+ const vector<intrusive_ptr<const Value> > &vpValue) {
+ intrusive_ptr<const Value> pValue(new Value(vpValue));
+ return pValue;
+ }
+
+ double Value::getDouble() const {
+ BSONType type = getType();
+ if (type == NumberInt)
+ return intValue;
+ if (type == NumberLong)
+ return static_cast< double >( longValue );
+
+ verify(type == NumberDouble);
+ return doubleValue;
+ }
+
+ string Value::getString() const {
+ verify(getType() == String);
+ return stringValue;
+ }
+
+ intrusive_ptr<Document> Value::getDocument() const {
+ verify(getType() == Object);
+ return pDocumentValue;
+ }
+
+ ValueIterator::~ValueIterator() {
+ }
+
+ Value::vi::~vi() {
+ }
+
+ bool Value::vi::more() const {
+ return (nextIndex < size);
+ }
+
+ intrusive_ptr<const Value> Value::vi::next() {
+ verify(more());
+ return (*pvpValue)[nextIndex++];
+ }
+
+ Value::vi::vi(const intrusive_ptr<const Value> &pValue,
+ const vector<intrusive_ptr<const Value> > *thepvpValue):
+ size(thepvpValue->size()),
+ nextIndex(0),
+ pvpValue(thepvpValue) {
+ }
+
+ intrusive_ptr<ValueIterator> Value::getArray() const {
+ verify(getType() == Array);
+ intrusive_ptr<ValueIterator> pVI(
+ new vi(intrusive_ptr<const Value>(this), &vpValue));
+ return pVI;
+ }
+
+ OID Value::getOid() const {
+ verify(getType() == jstOID);
+ return OID(oidValue);
+ }
+
+ bool Value::getBool() const {
+ verify(getType() == Bool);
+ return boolValue;
+ }
+
+ long long Value::getDate() const {
+ verify(getType() == Date);
+ return dateValue;
+ }
+
+ OpTime Value::getTimestamp() const {
+ verify(getType() == Timestamp);
+ return timestampValue;
+ }
+
+ string Value::getRegex() const {
+ verify(getType() == RegEx);
+ return stringValue;
+ }
+
+ string Value::getSymbol() const {
+ verify(getType() == Symbol);
+ return stringValue;
+ }
+
+ int Value::getInt() const {
+ verify(getType() == NumberInt);
+ return intValue;
+ }
+
+ long long Value::getLong() const {
+ BSONType type = getType();
+ if (type == NumberInt)
+ return intValue;
+
+ verify(type == NumberLong);
+ return longValue;
+ }
+
+ void Value::addToBson(Builder *pBuilder) const {
+ switch(getType()) {
+ case NumberDouble:
+ pBuilder->append(getDouble());
+ break;
+
+ case String:
+ pBuilder->append(getString());
+ break;
+
+ case Object: {
+ intrusive_ptr<Document> pDocument(getDocument());
+ BSONObjBuilder subBuilder;
+ pDocument->toBson(&subBuilder);
+ subBuilder.done();
+ pBuilder->append(&subBuilder);
+ break;
+ }
+
+ case Array: {
+ const size_t n = vpValue.size();
+ BSONArrayBuilder arrayBuilder(n);
+ for(size_t i = 0; i < n; ++i) {
+ vpValue[i]->addToBsonArray(&arrayBuilder);
+ }
+
+ pBuilder->append(&arrayBuilder);
+ break;
+ }
+
+ case BinData:
+ // pBuilder->appendBinData(fieldName, ...);
+ verify(false); // CW TODO unimplemented
+ break;
+
+ case jstOID:
+ pBuilder->append(getOid());
+ break;
+
+ case Bool:
+ pBuilder->append(getBool());
+ break;
+
+ case Date:
+ pBuilder->append(Date_t(getDate()));
+ break;
+
+ case RegEx:
+ pBuilder->append(getRegex());
+ break;
+
+ case Symbol:
+ pBuilder->append(getSymbol());
+ break;
+
+ case CodeWScope:
+ verify(false); // CW TODO unimplemented
+ break;
+
+ case NumberInt:
+ pBuilder->append(getInt());
+ break;
+
+ case Timestamp:
+ pBuilder->append(getTimestamp());
+ break;
+
+ case NumberLong:
+ pBuilder->append(getLong());
+ break;
+
+ case Undefined:
+ pBuilder->appendUndefined();
+ break;
+
+ case jstNULL:
+ pBuilder->append();
+ break;
+
+ /* these shouldn't appear in this context */
+ case MinKey:
+ case EOO:
+ case DBRef:
+ case Code:
+ case MaxKey:
+ verify(false); // CW TODO better message
+ break;
+ }
+ }
+
+ void Value::addToBsonObj(BSONObjBuilder *pBuilder, string fieldName) const {
+ BuilderObj objBuilder(pBuilder, fieldName);
+ addToBson(&objBuilder);
+ }
+
+ void Value::addToBsonArray(BSONArrayBuilder *pBuilder) const {
+ BuilderArray arrBuilder(pBuilder);
+ addToBson(&arrBuilder);
+ }
+
+ bool Value::coerceToBool() const {
+ // TODO Unify the implementation with BSONElement::trueValue().
+ BSONType type = getType();
+ switch(type) {
+ case NumberDouble:
+ if (doubleValue != 0)
+ return true;
+ break;
+
+ case String:
+ case Object:
+ case Array:
+ case BinData:
+ case jstOID:
+ case Date:
+ case RegEx:
+ case Symbol:
+ case Timestamp:
+ return true;
+
+ case Bool:
+ if (boolValue)
+ return true;
+ break;
+
+ case CodeWScope:
+ verify(false); // CW TODO unimplemented
+ break;
+
+ case NumberInt:
+ if (intValue != 0)
+ return true;
+ break;
+
+ case NumberLong:
+ if (longValue != 0)
+ return true;
+ break;
+
+ case jstNULL:
+ case Undefined:
+ /* nothing to do */
+ break;
+
+ /* these shouldn't happen in this context */
+ case MinKey:
+ case EOO:
+ case DBRef:
+ case Code:
+ case MaxKey:
+ verify(false); // CW TODO better message
+ break;
+ }
+
+ return false;
+ }
+
+ int Value::coerceToInt() const {
+ switch(type) {
+ case NumberDouble:
+ return (int)doubleValue;
+
+ case NumberInt:
+ return intValue;
+
+ case NumberLong:
+ return (int)longValue;
+
+ case jstNULL:
+ case Undefined:
+ break;
+
+ case String:
+ default:
+ uassert(16003, str::stream() <<
+ "can't convert from BSON type " << typeName(type) <<
+ " to int",
+ false);
+ } // switch(type)
+
+ return (int)0;
+ }
+
+ long long Value::coerceToLong() const {
+ switch(type) {
+ case NumberDouble:
+ return (long long)doubleValue;
+
+ case NumberInt:
+ return intValue;
+
+ case NumberLong:
+ return longValue;
+
+ case jstNULL:
+ case Undefined:
+ break;
+
+ case String:
+ default:
+ uassert(16004, str::stream() <<
+ "can't convert from BSON type " << typeName(type) <<
+ " to long",
+ false);
+ } // switch(type)
+
+ return (long long)0;
+ }
+
+ double Value::coerceToDouble() const {
+ switch(type) {
+ case NumberDouble:
+ return doubleValue;
+
+ case NumberInt:
+ return (double)intValue;
+
+ case NumberLong:
+ return (double)longValue;
+
+ case jstNULL:
+ case Undefined:
+ break;
+
+ case String:
+ default:
+ uassert(16005, str::stream() <<
+ "can't convert from BSON type " << typeName(type) <<
+ " to double",
+ false);
+ } // switch(type)
+
+ return (double)0;
+ }
+
+ long long Value::coerceToDate() const {
+ switch(type) {
+
+ case Date:
+ return dateValue;
+
+ case Timestamp:
+ return getTimestamp().getSecs() * 1000LL;
+
+ default:
+ uassert(16006, str::stream() <<
+ "can't convert from BSON type " << typeName(type) << " to Date",
+ false);
+ } // switch(type)
+ }
+
+ time_t Value::coerceToTimeT() const {
+ long long millis = coerceToDate();
+ if (millis < 0) {
+ // We want the division below to truncate toward -inf rather than 0
+ // eg Dec 31, 1969 23:59:58.001 should be -2 seconds rather than -1
+ // This is needed to get the correct values from coerceToTM
+ if ( -1999 / 1000 != -2) { // this is implementation defined
+ millis -= 1000-1;
+ }
+ }
+ const long long seconds = millis / 1000;
+
+ uassert(16421, "Can't handle date values outside of time_t range",
+ seconds >= std::numeric_limits<time_t>::min() &&
+ seconds <= std::numeric_limits<time_t>::max());
+
+ return static_cast<time_t>(seconds);
+ }
+ tm Value::coerceToTm() const {
+ // See implementation in Date_t.
+ // Can't reuse that here because it doesn't support times before 1970
+ time_t dtime = coerceToTimeT();
+ tm out;
+
+#if defined(_WIN32) // Both the argument order and the return values differ
+ bool itWorked = gmtime_s(&out, &dtime) == 0;
+#else
+ bool itWorked = gmtime_r(&dtime, &out) != NULL;
+#endif
+
+ if (!itWorked) {
+ if (dtime < 0) {
+ // Windows docs say it doesn't support these, but empirically it seems to work
+ uasserted(16422, "gmtime failed - your system doesn't support dates before 1970");
+ }
+ else {
+ uasserted(16423, str::stream() << "gmtime failed to convert time_t of " << dtime);
+ }
+ }
+
+ return out;
+ }
+
+ static string tmToISODateString(const tm& time) {
+ char buf[128];
+ size_t len = strftime(buf, 128, "%Y-%m-%dT%H:%M:%S", &time);
+ verify(len > 0);
+ verify(len < 128);
+ return buf;
+ }
+
+ string Value::coerceToString() const {
+ stringstream ss;
+ switch(type) {
+ case NumberDouble:
+ ss << doubleValue;
+ return ss.str();
+
+ case NumberInt:
+ ss << intValue;
+ return ss.str();
+
+ case NumberLong:
+ ss << longValue;
+ return ss.str();
+
+ case String:
+ return stringValue;
+
+ case Timestamp:
+ ss << getTimestamp().toStringPretty();
+ return ss.str();
+
+ case Date:
+ return tmToISODateString(coerceToTm());
+
+ case jstNULL:
+ case Undefined:
+ break;
+
+ default:
+ uassert(16007, str::stream() <<
+ "can't convert from BSON type " << typeName(type) <<
+ " to String",
+ false);
+ } // switch(type)
+
+ return "";
+ }
+
+ OpTime Value::coerceToTimestamp() const {
+ switch(type) {
+
+ case Timestamp:
+ return timestampValue;
+
+ default:
+ uassert(16378, str::stream() <<
+ "can't convert from BSON type " << typeName(type) <<
+ " to timestamp",
+ false);
+ } // switch(type)
+ }
+
+ int Value::compare(const intrusive_ptr<const Value> &rL,
+ const intrusive_ptr<const Value> &rR) {
+ BSONType lType = rL->getType();
+ BSONType rType = rR->getType();
+
+ /*
+ Special handling for Undefined and NULL values; these are types,
+ so it's easier to handle them here before we go below to handle
+ values of the same types. This allows us to compare Undefined and
+ NULL values with everything else. As coded now:
+ (*) Undefined is less than everything except itself (which is equal)
+ (*) NULL is less than everything except Undefined and itself
+ */
+ if (lType == Undefined) {
+ if (rType == Undefined)
+ return 0;
+
+ /* if rType is anything else, the left value is less */
+ return -1;
+ }
+
+ if (lType == jstNULL) {
+ if (rType == Undefined)
+ return 1;
+ if (rType == jstNULL)
+ return 0;
+
+ return -1;
+ }
+
+ if ((rType == Undefined) || (rType == jstNULL)) {
+ /*
+ We know the left value isn't Undefined, because of the above.
+ Count a NULL value as greater than an undefined one.
+ */
+ return 1;
+ }
+
+ /* if the comparisons are numeric, prepare to promote the values */
+ if (((lType == NumberDouble) || (lType == NumberLong) ||
+ (lType == NumberInt)) &&
+ ((rType == NumberDouble) || (rType == NumberLong) ||
+ (rType == NumberInt))) {
+
+ /* if the biggest type of either is a double, compare as doubles */
+ if ((lType == NumberDouble) || (rType == NumberDouble)) {
+ const double left = rL->getDouble();
+ const double right = rR->getDouble();
+ if (left < right)
+ return -1;
+ if (left > right)
+ return 1;
+ return 0;
+ }
+
+ /* if the biggest type of either is a long, compare as longs */
+ if ((lType == NumberLong) || (rType == NumberLong)) {
+ const long long left = rL->getLong();
+ const long long right = rR->getLong();
+ if (left < right)
+ return -1;
+ if (left > right)
+ return 1;
+ return 0;
+ }
+
+ /* if we got here, they must both be ints; compare as ints */
+ {
+ const int left = rL->getInt();
+ const int right = rR->getInt();
+ if (left < right)
+ return -1;
+ if (left > right)
+ return 1;
+ return 0;
+ }
+ }
+
+ // CW TODO for now, only compare like values
+ uassert(16016, str::stream() <<
+ "can't compare values of BSON types " << typeName(lType) <<
+ " and " << typeName(rType),
+ lType == rType);
+
+ switch(lType) {
+ case NumberDouble:
+ case NumberInt:
+ case NumberLong:
+ /* these types were handled above */
+ verify(false);
+
+ case String:
+ return rL->stringValue.compare(rR->stringValue);
+
+ case Object:
+ return Document::compare(rL->getDocument(), rR->getDocument());
+
+ case Array: {
+ intrusive_ptr<ValueIterator> pli(rL->getArray());
+ intrusive_ptr<ValueIterator> pri(rR->getArray());
+
+ while(true) {
+ /* have we run out of left array? */
+ if (!pli->more()) {
+ if (!pri->more())
+ return 0; // the arrays are the same length
+
+ return -1; // the left array is shorter
+ }
+
+ /* have we run out of right array? */
+ if (!pri->more())
+ return 1; // the right array is shorter
+
+ /* compare the two corresponding elements */
+ intrusive_ptr<const Value> plv(pli->next());
+ intrusive_ptr<const Value> prv(pri->next());
+ const int cmp = Value::compare(plv, prv);
+ if (cmp)
+ return cmp; // values are unequal
+ }
+
+ /* NOTREACHED */
+ verify(false);
+ break;
+ }
+
+ case BinData:
+ case Symbol:
+ case CodeWScope:
+ uassert(16017, str::stream() <<
+ "comparisons of values of BSON type " << typeName(lType) <<
+ " are not supported", false);
+ // pBuilder->appendBinData(fieldName, ...);
+ break;
+
+ case jstOID:
+ if (rL->getOid() < rR->getOid())
+ return -1;
+ if (rL->getOid() == rR->getOid())
+ return 0;
+ return 1;
+
+ case Bool:
+ if (rL->boolValue == rR->boolValue)
+ return 0;
+ if (rL->boolValue)
+ return 1;
+ return -1;
+
+ case Date: {
+ long long l = rL->dateValue;
+ long long r = rR->dateValue;
+ if (l < r)
+ return -1;
+ if (l > r)
+ return 1;
+ return 0;
+ }
+
+ case RegEx:
+ return rL->stringValue.compare(rR->stringValue);
+
+ case Timestamp:
+ if (rL->timestampValue < rR->timestampValue)
+ return -1;
+ if (rL->timestampValue > rR->timestampValue)
+ return 1;
+ return 0;
+
+ case Undefined:
+ case jstNULL:
+ return 0; // treat two Undefined or NULL values as equal
+
+ /* these shouldn't happen in this context */
+ case MinKey:
+ case EOO:
+ case DBRef:
+ case Code:
+ case MaxKey:
+ verify(false);
+ break;
+ } // switch(lType)
+
+ /* NOTREACHED */
+ return 0;
+ }
+
+ void Value::hash_combine(size_t &seed) const {
+ BSONType type = getType();
+
+ switch(type) {
+ /*
+ Numbers whose values are equal need to hash to the same thing
+ as well. Note that Value::compare() promotes numeric values to
+ their largest common form in order for comparisons to work.
+ We must hash all numeric values as if they are doubles so that
+ things like grouping work. We don't know what values will come
+ down the pipe later, but if we start out with int representations
+ of a value, and later see double representations of it, they need
+ to end up in the same buckets.
+ */
+ case NumberDouble:
+ case NumberLong:
+ case NumberInt:
+ {
+ const double d = getDouble();
+ boost::hash_combine(seed, d);
+ break;
+ }
+
+ case String:
+ boost::hash_combine(seed, stringValue);
+ break;
+
+ case Object:
+ getDocument()->hash_combine(seed);
+ break;
+
+ case Array: {
+ intrusive_ptr<ValueIterator> pIter(getArray());
+ while(pIter->more()) {
+ intrusive_ptr<const Value> pValue(pIter->next());
+ pValue->hash_combine(seed);
+ };
+ break;
+ }
+
+ case BinData:
+ case Symbol:
+ case CodeWScope:
+ uassert(16018, str::stream() <<
+ "hashes of values of BSON type " << typeName(type) <<
+ " are not supported", false);
+ break;
+
+ case jstOID:
+ getOid().hash_combine(seed);
+ break;
+
+ case Bool:
+ boost::hash_combine(seed, boolValue);
+ break;
+
+ case Date:
+ boost::hash_combine(seed, dateValue);
+ break;
+
+ case RegEx:
+ boost::hash_combine(seed, stringValue);
+ break;
+
+ case Timestamp:
+ boost::hash_combine(seed, timestampValue);
+ break;
+
+ case Undefined:
+ case jstNULL:
+ break;
+
+ /* these shouldn't happen in this context */
+ case MinKey:
+ case EOO:
+ case DBRef:
+ case Code:
+ case MaxKey:
+ verify(false); // CW TODO better message
+ break;
+ } // switch(type)
+ }
+
+ BSONType Value::getWidestNumeric(BSONType lType, BSONType rType) {
+ if (lType == NumberDouble) {
+ switch(rType) {
+ case NumberDouble:
+ case NumberLong:
+ case NumberInt:
+ case jstNULL:
+ case Undefined:
+ return NumberDouble;
+
+ default:
+ break;
+ }
+ }
+ else if (lType == NumberLong) {
+ switch(rType) {
+ case NumberDouble:
+ return NumberDouble;
+
+ case NumberLong:
+ case NumberInt:
+ case jstNULL:
+ case Undefined:
+ return NumberLong;
+
+ default:
+ break;
+ }
+ }
+ else if (lType == NumberInt) {
+ switch(rType) {
+ case NumberDouble:
+ return NumberDouble;
+
+ case NumberLong:
+ return NumberLong;
+
+ case NumberInt:
+ case jstNULL:
+ case Undefined:
+ return NumberInt;
+
+ default:
+ break;
+ }
+ }
+ else if ((lType == jstNULL) || (lType == Undefined)) {
+ switch(rType) {
+ case NumberDouble:
+ return NumberDouble;
+
+ case NumberLong:
+ return NumberLong;
+
+ case NumberInt:
+ return NumberInt;
+
+ default:
+ break;
+ }
+ }
+
+ // Reachable, but callers must subsequently err out in this case.
+ return Undefined;
+ }
+
+ size_t Value::getApproximateSize() const {
+ switch(type) {
+ case String:
+ return sizeof(Value) + stringValue.length();
+
+ case Object:
+ return sizeof(Value) + pDocumentValue->getApproximateSize();
+
+ case Array: {
+ size_t size = sizeof(Value);
+ const size_t n = vpValue.size();
+ for(size_t i = 0; i < n; ++i) {
+ size += vpValue[i]->getApproximateSize();
+ }
+ return size;
+ }
+
+ case NumberDouble:
+ case BinData:
+ case jstOID:
+ case Bool:
+ case Date:
+ case RegEx:
+ case Symbol:
+ case CodeWScope:
+ case NumberInt:
+ case Timestamp:
+ case NumberLong:
+ case jstNULL:
+ case Undefined:
+ return sizeof(Value);
+
+ /* these shouldn't happen in this context */
+ case MinKey:
+ case EOO:
+ case DBRef:
+ case Code:
+ case MaxKey:
+ verify(false); // CW TODO better message
+ return sizeof(Value);
+ }
+
+ /*
+ We shouldn't get here. In order to make the implementor think about
+ these cases, they are all listed explicitly, above. The compiler
+ should complain if they aren't all listed, because there's no
+ default. However, not all the compilers seem to do that. Therefore,
+ this final catch-all is here.
+ */
+ verify(false);
+ return sizeof(Value);
+ }
+
+
+ void ValueStatic::addRef() const {
+ }
+
+ void ValueStatic::release() const {
+ }
+
+}
diff --git a/src/mongo/db/pipeline/value.h b/src/mongo/db/pipeline/value.h
new file mode 100755
index 00000000000..293ba065fc1
--- /dev/null
+++ b/src/mongo/db/pipeline/value.h
@@ -0,0 +1,480 @@
+/**
+ * Copyright (c) 2011 10gen Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License, version 3,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include "mongo/pch.h"
+#include "bson/bsontypes.h"
+#include "bson/oid.h"
+#include "util/intrusive_counter.h"
+#include "util/optime.h"
+
+namespace mongo {
+ class BSONElement;
+ class Builder;
+ class Document;
+ class Value;
+
+ class ValueIterator :
+ public IntrusiveCounterUnsigned {
+ public:
+ virtual ~ValueIterator();
+
+ /*
+ Ask if there are more fields to return.
+
+ @returns true if there are more fields, false otherwise
+ */
+ virtual bool more() const = 0;
+
+ /*
+ Move the iterator to point to the next field and return it.
+
+ @returns the next field's <name, Value>
+ */
+ virtual intrusive_ptr<const Value> next() = 0;
+ };
+
+
+ /*
+ Values are immutable, so these are passed around as
+ intrusive_ptr<const Value>.
+ */
+ class Value :
+ public IntrusiveCounterUnsigned {
+ public:
+ ~Value();
+
+ /*
+ Construct a Value from a BSONElement.
+
+ This ignores the name of the element, and only uses the value,
+ whatever type it is.
+
+ @returns a new Value initialized from the bsonElement
+ */
+ static intrusive_ptr<const Value> createFromBsonElement(
+ BSONElement *pBsonElement);
+
+ /*
+ Construct an integer-valued Value.
+
+ For commonly used values, consider using one of the singleton
+ instances defined below.
+
+ @param value the value
+ @returns a Value with the given value
+ */
+ static intrusive_ptr<const Value> createInt(int value);
+
+ /*
+ Construct a long or interger-valued Value.
+ Used when preforming arithmetic operations with int where the result may be too large
+ and need to be stored as long. The Value will be an int if value fits, otherwise it
+ will be a long.
+
+ @param value the value
+ @returns a Value with the given value
+ */
+ static intrusive_ptr<const Value> createIntOrLong(long long value);
+
+ /*
+ Construct an long(long)-valued Value.
+
+ For commonly used values, consider using one of the singleton
+ instances defined below.
+
+ @param value the value
+ @returns a Value with the given value
+ */
+ static intrusive_ptr<const Value> createLong(long long value);
+
+ /*
+ Construct a double-valued Value.
+
+ @param value the value
+ @returns a Value with the given value
+ */
+ static intrusive_ptr<const Value> createDouble(double value);
+
+ /*
+ Construct a string-valued Value.
+
+ @param value the value
+ @returns a Value with the given value
+ */
+ static intrusive_ptr<const Value> createString(const string &value);
+
+ /*
+ Construct a date-valued Value.
+
+ @param value the value
+ @returns a Value with the given value
+ */
+ static intrusive_ptr<const Value> createDate(const long long &value);
+
+ static intrusive_ptr<const Value> createTimestamp(const OpTime& value);
+
+ /*
+ Construct a document-valued Value.
+
+ @param value the value
+ @returns a Value with the given value
+ */
+ static intrusive_ptr<const Value> createDocument(
+ const intrusive_ptr<Document> &pDocument);
+
+ /*
+ Construct an array-valued Value.
+
+ @param value the value
+ @returns a Value with the given value
+ */
+ static intrusive_ptr<const Value> createArray(
+ const vector<intrusive_ptr<const Value> > &vpValue);
+
+ /*
+ Get the BSON type of the field.
+
+ If the type is jstNULL, no value getter will work.
+
+ @return the BSON type of the field.
+ */
+ BSONType getType() const;
+
+ /*
+ Getters.
+
+ @returns the Value's value; asserts if the requested value type is
+ incorrect.
+ */
+ double getDouble() const;
+ string getString() const;
+ intrusive_ptr<Document> getDocument() const;
+ intrusive_ptr<ValueIterator> getArray() const;
+ OID getOid() const;
+ bool getBool() const;
+ long long getDate() const;
+ OpTime getTimestamp() const;
+ string getRegex() const;
+ string getSymbol() const;
+ int getInt() const;
+ long long getLong() const;
+
+ /*
+ Get the length of an array value.
+
+ @returns the length of the array, if this is array-valued; otherwise
+ throws an error
+ */
+ size_t getArrayLength() const;
+
+ /*
+ Add this value to the BSON object under construction.
+ */
+ void addToBsonObj(BSONObjBuilder *pBuilder, string fieldName) const;
+
+ /*
+ Add this field to the BSON array under construction.
+
+ As part of an array, the Value's name will be ignored.
+ */
+ void addToBsonArray(BSONArrayBuilder *pBuilder) const;
+
+ /*
+ Get references to singleton instances of commonly used field values.
+ */
+ static intrusive_ptr<const Value> getUndefined();
+ static intrusive_ptr<const Value> getNull();
+ static intrusive_ptr<const Value> getTrue();
+ static intrusive_ptr<const Value> getFalse();
+ static intrusive_ptr<const Value> getMinusOne();
+ static intrusive_ptr<const Value> getZero();
+ static intrusive_ptr<const Value> getOne();
+
+ /**
+ * Coerce (cast) a value to a native bool using BSONElement::trueValue() rules, but with
+ * some types unsupported. SERVER-6120
+ * @return the bool value
+ */
+ bool coerceToBool() const;
+
+ /*
+ Coerce (cast) a value to an int, using JSON rules.
+
+ @returns the int value
+ */
+ int coerceToInt() const;
+
+ /*
+ Coerce (cast) a value to a long long, using JSON rules.
+
+ @returns the long value
+ */
+ long long coerceToLong() const;
+
+ /*
+ Coerce (cast) a value to a double, using JSON rules.
+
+ @returns the double value
+ */
+ double coerceToDouble() const;
+
+ /*
+ Coerce (cast) a value to a date, using JSON rules.
+
+ @returns the date value
+ */
+ long long coerceToDate() const;
+ time_t coerceToTimeT() const;
+ tm coerceToTm() const; // broken-out time struct (see man gmtime)
+
+ OpTime coerceToTimestamp() const;
+
+ /*
+ Coerce (cast) a value to a string, using JSON rules.
+
+ @returns the date value
+ */
+ string coerceToString() const;
+
+ /*
+ Compare two Values.
+
+ @param rL left value
+ @param rR right value
+ @returns an integer less than zero, zero, or an integer greater than
+ zero, depending on whether rL < rR, rL == rR, or rL > rR
+ */
+ static int compare(const intrusive_ptr<const Value> &rL,
+ const intrusive_ptr<const Value> &rR);
+
+
+ /*
+ Figure out what the widest of two numeric types is.
+
+ Widest can be thought of as "most capable," or "able to hold the
+ largest or most precise value." The progression is Int, Long, Double.
+
+ @param rL left value
+ @param rR right value
+ @returns a BSONType of NumberInt, NumberLong, or NumberDouble
+ */
+ static BSONType getWidestNumeric(BSONType lType, BSONType rType);
+
+ /*
+ Get the approximate storage size of the value, in bytes.
+
+ @returns approximate storage size of the value.
+ */
+ size_t getApproximateSize() const;
+
+ /*
+ Calculate a hash value.
+
+ Meant to be used to create composite hashes suitable for
+ boost classes such as unordered_map<>.
+
+ @param seed value to augment with this' hash
+ */
+ void hash_combine(size_t &seed) const;
+
+ /*
+ struct Hash is defined to enable the use of Values as
+ keys in boost::unordered_map<>.
+
+ Values are always referenced as immutables in the form
+ intrusive_ptr<const Value>, so these operate on that construction.
+ */
+ struct Hash :
+ unary_function<intrusive_ptr<const Value>, size_t> {
+ size_t operator()(const intrusive_ptr<const Value> &rV) const;
+ };
+
+ protected:
+ Value(); // creates null value
+ Value(BSONType type); // creates an empty (unitialized value) of type
+ // mostly useful for Undefined
+ Value(bool boolValue);
+ Value(int intValue);
+
+ private:
+ Value(BSONElement *pBsonElement);
+
+ Value(long long longValue);
+ Value(double doubleValue);
+ Value(const OpTime& timestampValue);
+ Value(const string &stringValue);
+ Value(const intrusive_ptr<Document> &pDocument);
+ Value(const vector<intrusive_ptr<const Value> > &vpValue);
+
+ void addToBson(Builder *pBuilder) const;
+
+ BSONType type;
+
+ // store values that don't need a ctor/dtor in one of these
+ union {
+ double doubleValue;
+ bool boolValue;
+ int intValue;
+ long long longValue;
+ ReplTime timestampValue;
+ unsigned char oidValue[12];
+ // The member below is redundant, but useful for clarity and searchability.
+ long long dateValue;
+ };
+
+ string stringValue; // String, Regex, Symbol
+ intrusive_ptr<Document> pDocumentValue;
+ vector<intrusive_ptr<const Value> > vpValue; // for arrays
+
+ /*
+ These are often used as the result of boolean or comparison
+ expressions.
+
+ These are obtained via public static getters defined above.
+ */
+ static const intrusive_ptr<const Value> pFieldUndefined;
+ static const intrusive_ptr<const Value> pFieldNull;
+ static const intrusive_ptr<const Value> pFieldTrue;
+ static const intrusive_ptr<const Value> pFieldFalse;
+ static const intrusive_ptr<const Value> pFieldMinusOne;
+ static const intrusive_ptr<const Value> pFieldZero;
+ static const intrusive_ptr<const Value> pFieldOne;
+
+ /* this implementation is used for getArray() */
+ class vi :
+ public ValueIterator {
+ public:
+ // virtuals from ValueIterator
+ virtual ~vi();
+ virtual bool more() const;
+ virtual intrusive_ptr<const Value> next();
+
+ private:
+ friend class Value;
+ vi(const intrusive_ptr<const Value> &pSource,
+ const vector<intrusive_ptr<const Value> > *pvpValue);
+
+ size_t size;
+ size_t nextIndex;
+ const vector<intrusive_ptr<const Value> > *pvpValue;
+ }; /* class vi */
+
+ };
+
+ /*
+ Equality operator for values.
+
+ Useful for unordered_map<>, etc.
+ */
+ inline bool operator==(const intrusive_ptr<const Value> &v1,
+ const intrusive_ptr<const Value> &v2) {
+ return (Value::compare(v1, v2) == 0);
+ }
+
+ /*
+ For performance reasons, there are various sharable static values
+ defined in class Value, obtainable by methods such as getUndefined(),
+ getTrue(), getOne(), etc. We don't want these to go away as they are
+ used by a multitude of threads evaluating pipelines. In order to avoid
+ having to use atomic integers in the intrusive reference counter, this
+ class overrides the reference counting methods to do nothing, making it
+ safe to use for static Values.
+
+ At this point, only the constructors necessary for the static Values in
+ common use have been defined. The remainder can be defined if necessary.
+ */
+ class ValueStatic :
+ public Value {
+ public:
+ // virtuals from IntrusiveCounterUnsigned
+ virtual void addRef() const;
+ virtual void release() const;
+
+ // constructors
+ ValueStatic();
+ ValueStatic(BSONType type);
+ ValueStatic(bool boolValue);
+ ValueStatic(int intValue);
+ };
+}
+
+/* ======================= INLINED IMPLEMENTATIONS ========================== */
+
+namespace mongo {
+
+ inline BSONType Value::getType() const {
+ return type;
+ }
+
+ inline size_t Value::getArrayLength() const {
+ verify(getType() == Array);
+ return vpValue.size();
+ }
+
+ inline intrusive_ptr<const Value> Value::getUndefined() {
+ return pFieldUndefined;
+ }
+
+ inline intrusive_ptr<const Value> Value::getNull() {
+ return pFieldNull;
+ }
+
+ inline intrusive_ptr<const Value> Value::getTrue() {
+ return pFieldTrue;
+ }
+
+ inline intrusive_ptr<const Value> Value::getFalse() {
+ return pFieldFalse;
+ }
+
+ inline intrusive_ptr<const Value> Value::getMinusOne() {
+ return pFieldMinusOne;
+ }
+
+ inline intrusive_ptr<const Value> Value::getZero() {
+ return pFieldZero;
+ }
+
+ inline intrusive_ptr<const Value> Value::getOne() {
+ return pFieldOne;
+ }
+
+ inline size_t Value::Hash::operator()(
+ const intrusive_ptr<const Value> &rV) const {
+ size_t seed = 0xf0afbeef;
+ rV->hash_combine(seed);
+ return seed;
+ }
+
+ inline ValueStatic::ValueStatic():
+ Value() {
+ }
+
+ inline ValueStatic::ValueStatic(BSONType type):
+ Value(type) {
+ }
+
+ inline ValueStatic::ValueStatic(bool boolValue):
+ Value(boolValue) {
+ }
+
+ inline ValueStatic::ValueStatic(int intValue):
+ Value(intValue) {
+ }
+
+};