summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorCharlie Swanson <charlie.swanson@mongodb.com>2015-05-28 16:25:55 -0400
committerCharlie Swanson <charlie.swanson@mongodb.com>2015-06-18 14:50:53 -0400
commitc317c2b9416ffeb9a79ac8bec1eb79ae10448a4a (patch)
treef9ece61a2eba4d1e0e808be027c3250018b22e29 /src/mongo
parentd9a33681a85c160cb8367b04da9c539614d6b26a (diff)
downloadmongo-c317c2b9416ffeb9a79ac8bec1eb79ae10448a4a.tar.gz
SERVER-4589: Add $arrayElemAt aggregation expression
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/db/pipeline/expression.cpp45
-rw-r--r--src/mongo/db/pipeline/expression.h7
-rw-r--r--src/mongo/db/pipeline/value.cpp17
-rw-r--r--src/mongo/db/pipeline/value.h6
4 files changed, 70 insertions, 5 deletions
diff --git a/src/mongo/db/pipeline/expression.cpp b/src/mongo/db/pipeline/expression.cpp
index 8c266d22158..34cbda702a3 100644
--- a/src/mongo/db/pipeline/expression.cpp
+++ b/src/mongo/db/pipeline/expression.cpp
@@ -593,6 +593,44 @@ namespace {
return "$anyElementTrue";
}
+ /* ------------------------- ExpressionArrayElemAt -------------------------- */
+
+ Value ExpressionArrayElemAt::evaluateInternal(Variables* vars) const {
+ const Value array = vpOperand[0]->evaluateInternal(vars);
+ const Value indexArg = vpOperand[1]->evaluateInternal(vars);
+
+ if (array.nullish() || indexArg.nullish()) {
+ return Value(BSONNULL);
+ }
+
+ uassert(28689, str::stream() << getOpName() << "'s first argument must be an array, but is "
+ << typeName(array.getType()),
+ array.getType() == Array);
+ uassert(28690, str::stream() << getOpName() << "'s second argument must be a numeric value,"
+ << " but is " << typeName(indexArg.getType()),
+ indexArg.numeric());
+ uassert(28691, str::stream() << getOpName() << "'s second argument must be representable as"
+ << " a 32-bit integer: " << indexArg.coerceToDouble(),
+ indexArg.integral());
+
+ long long i = indexArg.coerceToLong();
+ if (i < 0 && static_cast<size_t>(std::abs(i)) > array.getArrayLength()) {
+ // Positive indices that are too large are handled automatically by Value.
+ return Value();
+ }
+ else if (i < 0) {
+ // Index from the back of the array.
+ i = array.getArrayLength() + i;
+ }
+ const size_t index = static_cast<size_t>(i);
+ return array[index];
+ }
+
+ REGISTER_EXPRESSION("$arrayElemAt", ExpressionArrayElemAt::parse);
+ const char* ExpressionArrayElemAt::getOpName() const {
+ return "$arrayElemAt";
+ }
+
/* -------------------- ExpressionCoerceToBool ------------------------- */
intrusive_ptr<ExpressionCoerceToBool> ExpressionCoerceToBool::create(
@@ -1931,11 +1969,8 @@ namespace {
uassert(16610, "can't $mod by 0",
right != 0);
- if (leftType == NumberDouble
- || (rightType == NumberDouble && rhs.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
+ if (leftType == NumberDouble || (rightType == NumberDouble && !rhs.integral())) {
+ // Need to do fmod. Integer-valued double case is handled below.
double left = lhs.coerceToDouble();
return Value(fmod(left, right));
diff --git a/src/mongo/db/pipeline/expression.h b/src/mongo/db/pipeline/expression.h
index dfdadf0cccb..258e0d6c034 100644
--- a/src/mongo/db/pipeline/expression.h
+++ b/src/mongo/db/pipeline/expression.h
@@ -403,6 +403,13 @@ namespace mongo {
};
+ class ExpressionArrayElemAt final : public ExpressionFixedArity<ExpressionArrayElemAt, 2> {
+ public:
+ Value evaluateInternal(Variables* vars) const final;
+ const char* getOpName() const final;
+ };
+
+
class ExpressionCoerceToBool : public Expression {
public:
// virtuals from ExpressionNary
diff --git a/src/mongo/db/pipeline/value.cpp b/src/mongo/db/pipeline/value.cpp
index 833d7ac23de..0d55a309002 100644
--- a/src/mongo/db/pipeline/value.cpp
+++ b/src/mongo/db/pipeline/value.cpp
@@ -31,6 +31,7 @@
#include "mongo/db/pipeline/value.h"
#include <cmath>
+#include <limits>
#include <boost/functional/hash.hpp>
#include "mongo/base/compare_numbers.h"
@@ -822,6 +823,22 @@ namespace mongo {
return Undefined;
}
+ bool Value::integral() const {
+ switch (getType()) {
+ case NumberInt:
+ return true;
+ case NumberLong:
+ return (_storage.longValue <= numeric_limits<int>::max()
+ && _storage.longValue >= numeric_limits<int>::min());
+ case NumberDouble:
+ return (_storage.doubleValue <= numeric_limits<int>::max()
+ && _storage.doubleValue >= numeric_limits<int>::min()
+ && _storage.doubleValue == static_cast<int>(_storage.doubleValue));
+ default:
+ return false;
+ }
+ }
+
size_t Value::getApproximateSize() const {
switch(getType()) {
case Code:
diff --git a/src/mongo/db/pipeline/value.h b/src/mongo/db/pipeline/value.h
index f388b82be7b..41201f64873 100644
--- a/src/mongo/db/pipeline/value.h
+++ b/src/mongo/db/pipeline/value.h
@@ -124,6 +124,12 @@ namespace mongo {
|| _storage.type == NumberInt;
}
+ /**
+ * Returns true if this value is a numeric type that can be represented as a 32-bit integer,
+ * and false otherwise.
+ */
+ bool integral() const;
+
/// Get the BSON type of the field.
BSONType getType() const { return _storage.bsonType(); }