summaryrefslogtreecommitdiff
path: root/src/mongo/db
diff options
context:
space:
mode:
authorBenjamin Murphy <benjamin_murphy@me.com>2016-03-14 17:29:41 -0400
committerBenjamin Murphy <benjamin_murphy@me.com>2016-03-25 11:29:43 -0400
commit60f636afea30271a7393e05838378e9eeffc6806 (patch)
tree6017c9f17ff18a15dcee5c966a81b3d3dbd97efa /src/mongo/db
parent5afa97da4ce5049ef7eb8bf4717ce37bd6777754 (diff)
downloadmongo-60f636afea30271a7393e05838378e9eeffc6806.tar.gz
SERVER-20163 Aggregation now supports the zip expression.
Diffstat (limited to 'src/mongo/db')
-rw-r--r--src/mongo/db/pipeline/expression.cpp170
-rw-r--r--src/mongo/db/pipeline/expression.h17
2 files changed, 187 insertions, 0 deletions
diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp
index a7ffd9f86b0..4c733b8bf8d 100644
--- a/src/mongo/db/pipeline/expression.cpp
+++ b/src/mongo/db/pipeline/expression.cpp
@@ -3297,4 +3297,174 @@ REGISTER_EXPRESSION(year, ExpressionYear::parse);
const char* ExpressionYear::getOpName() const {
return "$year";
}
+
+/* -------------------------- ExpressionZip ------------------------------ */
+
+REGISTER_EXPRESSION(zip, ExpressionZip::parse);
+intrusive_ptr<Expression> ExpressionZip::parse(BSONElement expr, const VariablesParseState& vps) {
+ uassert(34460,
+ str::stream() << "$zip only supports an object as an argument, found "
+ << typeName(expr.type()),
+ expr.type() == Object);
+
+ intrusive_ptr<ExpressionZip> newZip(new ExpressionZip());
+
+ for (auto&& elem : expr.Obj()) {
+ const auto field = elem.fieldNameStringData();
+ if (field == "inputs") {
+ uassert(34461,
+ str::stream() << "inputs must be an array of expressions, found "
+ << typeName(elem.type()),
+ elem.type() == Array);
+ for (auto&& subExpr : elem.Array()) {
+ newZip->_inputs.push_back(parseOperand(subExpr, vps));
+ }
+ } else if (field == "defaults") {
+ uassert(34462,
+ str::stream() << "defaults must be an array of expressions, found "
+ << typeName(elem.type()),
+ elem.type() == Array);
+ for (auto&& subExpr : elem.Array()) {
+ newZip->_defaults.push_back(parseOperand(subExpr, vps));
+ }
+ } else if (field == "useLongestLength") {
+ uassert(34463,
+ str::stream() << "useLongestLength must be a bool, found "
+ << typeName(expr.type()),
+ elem.type() == Bool);
+ newZip->_useLongestLength = elem.Bool();
+ } else {
+ uasserted(34464,
+ str::stream() << "$zip found an unknown argument: " << elem.fieldName());
+ }
+ }
+
+ uassert(34465, "$zip requires at least one input array", !newZip->_inputs.empty());
+ uassert(34466,
+ "cannot specify defaults unless useLongestLength is true",
+ (newZip->_useLongestLength || newZip->_defaults.empty()));
+ uassert(34467,
+ "defaults and inputs must have the same length",
+ (newZip->_defaults.empty() || newZip->_defaults.size() == newZip->_inputs.size()));
+
+ return std::move(newZip);
+}
+
+Value ExpressionZip::evaluateInternal(Variables* vars) const {
+ // Evaluate input values.
+ vector<vector<Value>> inputValues;
+ inputValues.reserve(_inputs.size());
+
+ size_t minArraySize = 0;
+ size_t maxArraySize = 0;
+ for (size_t i = 0; i < _inputs.size(); i++) {
+ Value evalExpr = _inputs[i]->evaluateInternal(vars);
+ if (evalExpr.nullish()) {
+ return Value(BSONNULL);
+ }
+
+ uassert(
+ 34468,
+ str::stream() << "$zip found a non-array expression in input: " << evalExpr.toString(),
+ evalExpr.isArray());
+
+ inputValues.push_back(evalExpr.getArray());
+
+ size_t arraySize = evalExpr.getArrayLength();
+
+ if (i == 0) {
+ minArraySize = arraySize;
+ maxArraySize = arraySize;
+ } else {
+ auto arraySizes = std::minmax({minArraySize, arraySize, maxArraySize});
+ minArraySize = arraySizes.first;
+ maxArraySize = arraySizes.second;
+ }
+ }
+
+ vector<Value> evaluatedDefaults(_inputs.size(), Value(BSONNULL));
+
+ // If we need default values, evaluate each expression.
+ if (minArraySize != maxArraySize) {
+ for (size_t i = 0; i < _defaults.size(); i++) {
+ evaluatedDefaults[i] = _defaults[i]->evaluateInternal(vars);
+ }
+ }
+
+ size_t outputLength = _useLongestLength ? maxArraySize : minArraySize;
+
+ // The final output array, e.g. [[1, 2, 3], [2, 3, 4]].
+ vector<Value> output;
+
+ // Used to construct each array in the output, e.g. [1, 2, 3].
+ vector<Value> outputChild;
+
+ output.reserve(outputLength);
+ outputChild.reserve(_inputs.size());
+
+ for (size_t row = 0; row < outputLength; row++) {
+ outputChild.clear();
+ for (size_t col = 0; col < _inputs.size(); col++) {
+ if (inputValues[col].size() > row) {
+ // Add the value from the appropriate input array.
+ outputChild.push_back(inputValues[col][row]);
+ } else {
+ // Add the corresponding default value.
+ outputChild.push_back(evaluatedDefaults[col]);
+ }
+ }
+ output.push_back(Value(outputChild));
+ }
+
+ return Value(output);
+}
+
+boost::intrusive_ptr<Expression> ExpressionZip::optimize() {
+ std::transform(_inputs.begin(),
+ _inputs.end(),
+ _inputs.begin(),
+ [](intrusive_ptr<Expression> inputExpression)
+ -> intrusive_ptr<Expression> { return inputExpression->optimize(); });
+
+ std::transform(_defaults.begin(),
+ _defaults.end(),
+ _defaults.begin(),
+ [](intrusive_ptr<Expression> defaultExpression)
+ -> intrusive_ptr<Expression> { return defaultExpression->optimize(); });
+
+ return this;
+}
+
+Value ExpressionZip::serialize(bool explain) const {
+ vector<Value> serializedInput;
+ vector<Value> serializedDefaults;
+ Value serializedUseLongestLength = Value(_useLongestLength);
+
+ for (auto&& expr : _inputs) {
+ serializedInput.push_back(expr->serialize(explain));
+ }
+
+ for (auto&& expr : _defaults) {
+ serializedDefaults.push_back(expr->serialize(explain));
+ }
+
+ return Value(DOC("$zip" << DOC("inputs" << Value(serializedInput) << "defaults"
+ << Value(serializedDefaults) << "useLongestLength"
+ << serializedUseLongestLength)));
+}
+
+void ExpressionZip::addDependencies(DepsTracker* deps, std::vector<std::string>* path) const {
+ std::for_each(_inputs.begin(),
+ _inputs.end(),
+ [&deps](intrusive_ptr<Expression> inputExpression)
+ -> void { inputExpression->addDependencies(deps); });
+ std::for_each(_defaults.begin(),
+ _defaults.end(),
+ [&deps](intrusive_ptr<Expression> defaultExpression)
+ -> void { defaultExpression->addDependencies(deps); });
+}
+
+const char* ExpressionZip::getOpName() const {
+ return "$zip";
+}
}
diff --git a/src/mongo/db/pipeline/expression.h b/src/mongo/db/pipeline/expression.h
index 71fcfec44ff..ae6adb7e7a6 100644
--- a/src/mongo/db/pipeline/expression.h
+++ b/src/mongo/db/pipeline/expression.h
@@ -1313,4 +1313,21 @@ public:
return tm.tm_year + 1900;
}
};
+
+
+class ExpressionZip final : public ExpressionFixedArity<ExpressionZip, 1> {
+public:
+ void addDependencies(DepsTracker* deps, std::vector<std::string>* path = nullptr) const final;
+ Value evaluateInternal(Variables* vars) const final;
+ boost::intrusive_ptr<Expression> optimize() final;
+ static boost::intrusive_ptr<Expression> parse(BSONElement expr,
+ const VariablesParseState& vpsIn);
+ Value serialize(bool explain) const final;
+ const char* getOpName() const final;
+
+private:
+ bool _useLongestLength = false;
+ ExpressionVector _inputs;
+ ExpressionVector _defaults;
+};
}