diff options
Diffstat (limited to 'src/mongo/db/pipeline')
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) { + } + +}; |
