Skip to content

Commit 1e974de

Browse files
committed
CXX-186 Add group command helper
1 parent 9828159 commit 1e974de

File tree

3 files changed

+206
-0
lines changed

3 files changed

+206
-0
lines changed

src/mongo/client/dbclient.cpp

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,78 @@ namespace mongo {
791791
return info;
792792
}
793793

794+
void DBClientWithCommands::group(
795+
const std::string& ns,
796+
const std::string& jsreduce,
797+
std::vector<BSONObj>* output,
798+
const BSONObj& initial,
799+
const BSONObj& cond,
800+
const BSONObj& key,
801+
const std::string& finalize
802+
) {
803+
BSONObjBuilder groupObjBuilder;
804+
_buildGroupObj(ns, jsreduce, initial, cond, finalize, &groupObjBuilder);
805+
806+
if (!key.isEmpty())
807+
groupObjBuilder.append("key", key);
808+
809+
_runGroup(ns, groupObjBuilder.obj(), output);
810+
}
811+
812+
void DBClientWithCommands::groupWithKeyFunction(
813+
const std::string& ns,
814+
const std::string& jsreduce,
815+
std::vector<BSONObj>* output,
816+
const BSONObj& initial,
817+
const BSONObj& cond,
818+
const std::string& jskey,
819+
const std::string& finalize
820+
) {
821+
BSONObjBuilder groupBuilder;
822+
_buildGroupObj(ns, jsreduce, initial, cond, finalize, &groupBuilder);
823+
824+
if (!jskey.empty())
825+
groupBuilder.append("$keyf", jskey);
826+
827+
_runGroup(ns, groupBuilder.obj(), output);
828+
}
829+
830+
void DBClientWithCommands::_buildGroupObj(
831+
const std::string& ns,
832+
const std::string& jsreduce,
833+
const BSONObj& initial,
834+
const BSONObj& cond,
835+
const std::string& finalize,
836+
BSONObjBuilder* groupObj
837+
) {
838+
groupObj->append("ns", nsGetCollection(ns));
839+
groupObj->appendCode("$reduce", jsreduce);
840+
groupObj->append("initial", initial);
841+
842+
if (!cond.isEmpty())
843+
groupObj->append("cond", cond);
844+
if (!finalize.empty())
845+
groupObj->append("finalize", finalize);
846+
}
847+
848+
void DBClientWithCommands::_runGroup(const std::string ns, const BSONObj& group, std::vector<BSONObj>* output) {
849+
BSONObjBuilder commandBuilder;
850+
commandBuilder.append("group", group);
851+
852+
BSONObj result;
853+
bool ok = runCommand(nsGetDB(ns), commandBuilder.obj(), result);
854+
855+
if (!ok)
856+
throw OperationException(result);
857+
858+
BSONElement resultArray = result.getField("retval");
859+
BSONObjIterator resultIterator(resultArray.Obj());
860+
861+
while (resultIterator.more()) {
862+
output->push_back(resultIterator.next().Obj().getOwned());
863+
}
864+
}
865+
794866
bool DBClientWithCommands::eval(const string &dbname, const string &jscode, BSONObj& info, BSONElement& retValue, BSONObj *args) {
795867
BSONObjBuilder b;
796868
b.appendCode("$eval", jscode);

src/mongo/client/dbclientinterface.h

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -821,6 +821,50 @@ namespace mongo {
821821
*/
822822
BSONObj mapreduce(const std::string &ns, const std::string &jsmapf, const std::string &jsreducef, BSONObj query = BSONObj(), MROutput output = MRInline);
823823

824+
/**
825+
* Groups documents in a collection by the specified keys and performs simple aggregation
826+
* functions such as computing counts and sums.
827+
*
828+
* See: http://docs.mongodb.org/manual/reference/method/db.collection.group
829+
*
830+
* @param ns The namespace to group
831+
* @param key The field or fields to group specified as a projection document: { field: 1 }
832+
* @param jsreduce An aggregation function that operates on the documents during the group
833+
* ing operation. The function should take two arguments: the current document and an
834+
* aggregation result for that group.
835+
* @param output The output vector.
836+
* @param initial Initial aggregation result document.
837+
* @param cond Optional selection criteria to determine which documents to process.
838+
* @param finalize Optional function that runs for each item in the result set before
839+
* returning the final values in the output vector.
840+
*/
841+
void group(
842+
const std::string& ns,
843+
const std::string& jsreduce,
844+
std::vector<BSONObj>* output,
845+
const BSONObj& initial = BSONObj(),
846+
const BSONObj& cond = BSONObj(),
847+
const BSONObj& key = BSONObj(),
848+
const std::string& finalize = ""
849+
);
850+
851+
/**
852+
* Does the same thing as group but accepts a key function that can be used to create the
853+
* object representing the key. This allows for grouping on calculated fields rather than
854+
* existing fields alone.
855+
*
856+
* @see DBClientWithCommands::group
857+
*/
858+
void groupWithKeyFunction(
859+
const std::string& ns,
860+
const std::string& jsreduce,
861+
std::vector<BSONObj>* output,
862+
const BSONObj& initial = BSONObj(),
863+
const BSONObj& cond = BSONObj(),
864+
const std::string& jskey = "",
865+
const std::string& finalize = ""
866+
);
867+
824868
/** Run javascript code on the database server.
825869
dbname database SavedContext in which the code runs. The javascript variable 'db' will be assigned
826870
to this database when the function is invoked.
@@ -1012,6 +1056,17 @@ namespace mongo {
10121056
private:
10131057
enum QueryOptions _cachedAvailableOptions;
10141058
bool _haveCachedAvailableOptions;
1059+
1060+
void _buildGroupObj(
1061+
const std::string& ns,
1062+
const std::string& jsreduce,
1063+
const BSONObj& initial,
1064+
const BSONObj& cond,
1065+
const std::string& finalize,
1066+
BSONObjBuilder* groupObj
1067+
);
1068+
1069+
void _runGroup(const std::string ns, const BSONObj& group, std::vector<BSONObj>* output);
10151070
};
10161071

10171072
class DBClientWriter;

src/mongo/unittest/dbclient_test.cpp

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,85 @@ namespace {
281281
ASSERT_EQUALS(c.count(TEST_NS), 2U);
282282
}
283283

284+
TEST_F(DBClientTest, SimpleGroup) {
285+
c.insert(TEST_NS, BSON("a" << 1 << "color" << "green"));
286+
c.insert(TEST_NS, BSON("a" << 2 << "color" << "green"));
287+
c.insert(TEST_NS, BSON("a" << 3 << "color" << "green"));
288+
c.insert(TEST_NS, BSON("a" << 1 << "color" << "blue"));
289+
290+
std::string reduce = "function(current, aggregate) {"
291+
"if (current.color === 'green') { aggregate.green++; }"
292+
"if (current.a) { aggregate.a += current.a; }"
293+
"}";
294+
295+
BSONObj initial = BSON("a" << 0 << "green" << 0);
296+
BSONObj cond = BSON("a" << LT << 3);
297+
BSONObj key = BSON("color" << 1);
298+
299+
std::string finalize = "function(result) {"
300+
"result.combined = result.green + result.a;"
301+
"}";
302+
303+
vector<BSONObj> results;
304+
c.group(TEST_NS, reduce, &results, initial, cond, key, finalize);
305+
306+
vector<BSONObj>::const_iterator it = results.begin();
307+
while (it != results.end()) {
308+
BSONObj current = *it;
309+
if (current.getStringField("color") == std::string("green")) {
310+
ASSERT_EQUALS(current.getField("a").Double(), 3.0);
311+
ASSERT_EQUALS(current.getField("green").Double(), 2.0);
312+
ASSERT_EQUALS(current.getField("combined").Double(), 5.0);
313+
} else {
314+
ASSERT_EQUALS(current.getField("a").Double(), 1.0);
315+
ASSERT_EQUALS(current.getField("green").Double(), 0.0);
316+
ASSERT_EQUALS(current.getField("combined").Double(), 1.0);
317+
}
318+
++it;
319+
}
320+
}
321+
322+
TEST_F(DBClientTest, GroupWithKeyFunction) {
323+
c.insert(TEST_NS, BSON("a" << 1 << "color" << "green"));
324+
c.insert(TEST_NS, BSON("a" << 2 << "color" << "green"));
325+
c.insert(TEST_NS, BSON("a" << 3 << "color" << "green"));
326+
c.insert(TEST_NS, BSON("a" << 1 << "color" << "blue"));
327+
328+
std::string reduce = "function(current, aggregate) {"
329+
"if (current.color === 'green') { aggregate.green++; }"
330+
"if (current.a) { aggregate.a += current.a; }"
331+
"}";
332+
333+
BSONObj initial = BSON("a" << 0 << "green" << 0);
334+
BSONObj cond = BSON("a" << LT << 3);
335+
336+
std::string key = "function(doc) {"
337+
"return { 'color': doc.color }"
338+
"}";
339+
340+
std::string finalize = "function(result) {"
341+
"result.combined = result.green + result.a;"
342+
"}";
343+
344+
vector<BSONObj> results;
345+
c.groupWithKeyFunction(TEST_NS, reduce, &results, initial, cond, key, finalize);
346+
347+
vector<BSONObj>::const_iterator it = results.begin();
348+
while (it != results.end()) {
349+
BSONObj current = *it;
350+
if (current.getStringField("color") == std::string("green")) {
351+
ASSERT_EQUALS(current.getField("a").Double(), 3.0);
352+
ASSERT_EQUALS(current.getField("green").Double(), 2.0);
353+
ASSERT_EQUALS(current.getField("combined").Double(), 5.0);
354+
} else {
355+
ASSERT_EQUALS(current.getField("a").Double(), 1.0);
356+
ASSERT_EQUALS(current.getField("green").Double(), 0.0);
357+
ASSERT_EQUALS(current.getField("combined").Double(), 1.0);
358+
}
359+
++it;
360+
}
361+
}
362+
284363
TEST_F(DBClientTest, InsertVectorContinueOnError) {
285364
vector<BSONObj> v;
286365
v.push_back(BSON("_id" << 1));

0 commit comments

Comments
 (0)