summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBenjamin Murphy <benjamin_murphy@me.com>2016-03-17 11:36:16 -0400
committerBenjamin Murphy <benjamin_murphy@me.com>2016-03-29 15:21:06 -0400
commit6bd6589e806c6545906475510e1b385774676489 (patch)
treeb6bbf7bbc1c25a75a8f8afbec3f7e4f3aa3121b5
parentf23e588c180cc1a2ae79b9cbf6a4ba2a196ee215 (diff)
downloadmongo-6bd6589e806c6545906475510e1b385774676489.tar.gz
SERVER-14670 Aggregation supports strLenCP and strLenBytes.
-rw-r--r--jstests/aggregation/bugs/server14670.js23
-rw-r--r--src/mongo/db/pipeline/expression.cpp52
-rw-r--r--src/mongo/db/pipeline/expression.h12
-rw-r--r--src/mongo/db/pipeline/expression_test.cpp53
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 {