diff options
author | Benjamin Murphy <benjamin_murphy@me.com> | 2016-03-17 11:36:16 -0400 |
---|---|---|
committer | Benjamin Murphy <benjamin_murphy@me.com> | 2016-03-29 15:21:06 -0400 |
commit | 6bd6589e806c6545906475510e1b385774676489 (patch) | |
tree | b6bbf7bbc1c25a75a8f8afbec3f7e4f3aa3121b5 | |
parent | f23e588c180cc1a2ae79b9cbf6a4ba2a196ee215 (diff) | |
download | mongo-6bd6589e806c6545906475510e1b385774676489.tar.gz |
SERVER-14670 Aggregation supports strLenCP and strLenBytes.
-rw-r--r-- | jstests/aggregation/bugs/server14670.js | 23 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression.cpp | 52 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression.h | 12 | ||||
-rw-r--r-- | src/mongo/db/pipeline/expression_test.cpp | 53 |
4 files changed, 140 insertions, 0 deletions
diff --git a/jstests/aggregation/bugs/server14670.js b/jstests/aggregation/bugs/server14670.js new file mode 100644 index 00000000000..92c6e98e8e1 --- /dev/null +++ b/jstests/aggregation/bugs/server14670.js @@ -0,0 +1,23 @@ +// SERVER-14670 introduced the $strLenBytes and $strLenCP aggregation expressions. In this file, we +// test the error cases for these expressions. +load("jstests/aggregation/extras/utils.js"); // For assertErrorCode. + +(function() { + "use strict"; + + var coll = db.substr; + coll.drop(); + + // Need an empty document for the pipeline. + coll.insert({}); + + assertErrorCode(coll, + [{$project: {strLen: {$strLenBytes: 1}}}], + 34473, + "$strLenBytes requires a string argument."); + + assertErrorCode(coll, + [{$project: {strLen: {$strLenCP: 1}}}], + 34471, + "$strLenCP requires a string argument."); +}()); diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp index 7dc91bdd4fe..6b4fd5ac6ee 100644 --- a/src/mongo/db/pipeline/expression.cpp +++ b/src/mongo/db/pipeline/expression.cpp @@ -3166,6 +3166,58 @@ const char* ExpressionSubstrCP::getOpName() const { return "$substrCP"; } +/* ----------------------- ExpressionStrLenBytes ------------------------- */ + +Value ExpressionStrLenBytes::evaluateInternal(Variables* vars) const { + Value str(vpOperand[0]->evaluateInternal(vars)); + + uassert(34473, + str::stream() << "$strLenBytes requires a string argument, found: " + << typeName(str.getType()), + str.getType() == String); + + size_t strLen = str.getString().size(); + + uassert(34470, + "string length could not be represented as an int.", + strLen <= std::numeric_limits<int>::max()); + return Value(static_cast<int>(strLen)); +} + +REGISTER_EXPRESSION(strLenBytes, ExpressionStrLenBytes::parse); +const char* ExpressionStrLenBytes::getOpName() const { + return "$strLenBytes"; +} + +/* ----------------------- ExpressionStrLenCP ------------------------- */ + +Value ExpressionStrLenCP::evaluateInternal(Variables* vars) const { + Value val(vpOperand[0]->evaluateInternal(vars)); + + uassert( + 34471, + str::stream() << "$strLenCP requires a string argument, found: " << typeName(val.getType()), + val.getType() == String); + + std::string stringVal = val.getString(); + + size_t strLen = 0; + for (char byte : stringVal) { + strLen += !isContinuationByte(byte); + } + + uassert(34472, + "string length could not be represented as an int.", + strLen <= std::numeric_limits<int>::max()); + + return Value(static_cast<int>(strLen)); +} + +REGISTER_EXPRESSION(strLenCP, ExpressionStrLenCP::parse); +const char* ExpressionStrLenCP::getOpName() const { + return "$strLenCP"; +} + /* ----------------------- ExpressionSubtract ---------------------------- */ Value ExpressionSubtract::evaluateInternal(Variables* vars) const { diff --git a/src/mongo/db/pipeline/expression.h b/src/mongo/db/pipeline/expression.h index fe3e57b0d4a..0939bcbcdc7 100644 --- a/src/mongo/db/pipeline/expression.h +++ b/src/mongo/db/pipeline/expression.h @@ -1259,6 +1259,18 @@ public: }; +class ExpressionStrLenBytes final : public ExpressionFixedArity<ExpressionStrLenBytes, 1> { + Value evaluateInternal(Variables* vars) const final; + const char* getOpName() const final; +}; + + +class ExpressionStrLenCP final : public ExpressionFixedArity<ExpressionStrLenCP, 1> { + Value evaluateInternal(Variables* vars) const final; + const char* getOpName() const final; +}; + + class ExpressionSubtract final : public ExpressionFixedArity<ExpressionSubtract, 2> { public: Value evaluateInternal(Variables* vars) const final; diff --git a/src/mongo/db/pipeline/expression_test.cpp b/src/mongo/db/pipeline/expression_test.cpp index ed6625cc594..457cc1e4d8c 100644 --- a/src/mongo/db/pipeline/expression_test.cpp +++ b/src/mongo/db/pipeline/expression_test.cpp @@ -4294,6 +4294,59 @@ class NullMiddleGt : public ExpectedResultBase { } // namespace Strcasecmp +namespace StrLenBytes { + +TEST(ExpressionStrLenBytes, ComputesLengthOfString) { + assertExpectedResults("$strLenBytes", {{{Value("abc")}, Value(3)}}); +} + +TEST(ExpressionStrLenBytes, ComputesLengthOfEmptyString) { + assertExpectedResults("$strLenBytes", {{{Value("")}, Value(0)}}); +} + +TEST(ExpressionStrLenBytes, ComputesLengthOfStringWithNull) { + assertExpectedResults("$strLenBytes", + {{{Value(StringData("ab\0c", StringData::LiteralTag()))}, Value(4)}}); +} + +TEST(ExpressionStrLenCP, ComputesLengthOfStringWithNullAtEnd) { + assertExpectedResults("$strLenBytes", + {{{Value(StringData("abc\0", StringData::LiteralTag()))}, Value(4)}}); +} + +} // namespace StrLenBytes + +namespace StrLenCP { + +TEST(ExpressionStrLenCP, ComputesLengthOfASCIIString) { + assertExpectedResults("$strLenCP", {{{Value("abc")}, Value(3)}}); +} + +TEST(ExpressionStrLenCP, ComputesLengthOfEmptyString) { + assertExpectedResults("$strLenCP", {{{Value("")}, Value(0)}}); +} + +TEST(ExpressionStrLenCP, ComputesLengthOfStringWithNull) { + assertExpectedResults("$strLenCP", + {{{Value(StringData("ab\0c", StringData::LiteralTag()))}, Value(4)}}); +} + +TEST(ExpressionStrLenCP, ComputesLengthOfStringWithNullAtEnd) { + assertExpectedResults("$strLenCP", + {{{Value(StringData("abc\0", StringData::LiteralTag()))}, Value(4)}}); +} + +TEST(ExpressionStrLenCP, ComputesLengthOfStringWithAccent) { + assertExpectedResults("$strLenCP", + {{{Value(StringData("a\0bâ", StringData::LiteralTag()))}, Value(4)}}); +} + +TEST(ExpressionStrLenCP, ComputesLengthOfStringWithSpecialCharacters) { + assertExpectedResults("$strLenCP", {{{Value("ºabøåß")}, Value(6)}}); +} + +} // namespace StrLenCP + namespace SubstrBytes { class ExpectedResultBase { |