diff options
author | Benjamin Murphy <benjamin_murphy@me.com> | 2016-03-14 17:29:41 -0400 |
---|---|---|
committer | Benjamin Murphy <benjamin_murphy@me.com> | 2016-03-25 11:29:43 -0400 |
commit | 60f636afea30271a7393e05838378e9eeffc6806 (patch) | |
tree | 6017c9f17ff18a15dcee5c966a81b3d3dbd97efa /src/mongo/db | |
parent | 5afa97da4ce5049ef7eb8bf4717ce37bd6777754 (diff) | |
download | mongo-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.cpp | 170 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression.h | 17 |
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; +}; } |