// pipelinetests.cpp : Unit tests for some classes within src/mongo/db/pipeline.
/**
* Copyright (C) 2012 10gen Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the GNU Affero General Public License in all respects
* for all of the code used other than as permitted herein. If you modify
* file(s) with this exception, you may extend this exception to your
* version of the file(s), but you are not obligated to do so. If you do not
* wish to do so, delete this exception statement from your version. If you
* delete this exception statement from all source files in the program,
* then also delete it in the license file.
*/
#include "mongo/platform/basic.h"
#include "mongo/db/pipeline/document.h"
#include "mongo/db/pipeline/expression_context.h"
#include "mongo/db/pipeline/field_path.h"
#include "mongo/db/pipeline/pipeline.h"
#include "mongo/db/operation_context_impl.h"
#include "mongo/dbtests/dbtests.h"
namespace PipelineTests {
using boost::intrusive_ptr;
using std::string;
namespace Optimizations {
using namespace mongo;
namespace Local {
class Base {
public:
// These both return json arrays of pipeline operators
virtual string inputPipeJson() = 0;
virtual string outputPipeJson() = 0;
BSONObj pipelineFromJsonArray(const string& array) {
return fromjson("{pipeline: " + array + "}");
}
virtual void run() {
const BSONObj inputBson = pipelineFromJsonArray(inputPipeJson());
const BSONObj outputPipeExpected = pipelineFromJsonArray(outputPipeJson());
intrusive_ptr ctx =
new ExpressionContext(&_opCtx, NamespaceString("a.collection"));
string errmsg;
intrusive_ptr outputPipe =
Pipeline::parseCommand(errmsg, inputBson, ctx);
ASSERT_EQUALS(errmsg, "");
ASSERT(outputPipe != NULL);
ASSERT_EQUALS(outputPipe->serialize()["pipeline"],
Value(outputPipeExpected["pipeline"]));
}
virtual ~Base() {}
private:
OperationContextImpl _opCtx;
};
class RemoveSkipZero : public Base {
string inputPipeJson() override {
return "[{$skip: 0}]";
}
string outputPipeJson() override {
return "[]";
}
};
class DoNotRemoveSkipOne : public Base {
string inputPipeJson() override {
return "[{$skip: 1}]";
}
string outputPipeJson() override {
return "[{$skip: 1}]";
}
};
class RemoveEmptyMatch : public Base {
string inputPipeJson() override {
return "[{$match: {}}]";
}
string outputPipeJson() override {
return "[]";
}
};
class RemoveMultipleEmptyMatches : public Base {
string inputPipeJson() override {
return "[{$match: {}}, {$match: {}}]";
}
string outputPipeJson() override {
// TODO: The desired behavior here is to end up with an empty array.
return "[{$match: {$and: [{}, {}]}}]";
}
};
class DoNotRemoveNonEmptyMatch : public Base {
string inputPipeJson() override {
return "[{$match: {_id: 1}}]";
}
string outputPipeJson() override {
return "[{$match: {_id: 1}}]";
}
};
} // namespace Local
namespace Sharded {
class Base {
public:
// These all return json arrays of pipeline operators
virtual string inputPipeJson() = 0;
virtual string shardPipeJson() = 0;
virtual string mergePipeJson() = 0;
BSONObj pipelineFromJsonArray(const string& array) {
return fromjson("{pipeline: " + array + "}");
}
virtual void run() {
const BSONObj inputBson = pipelineFromJsonArray(inputPipeJson());
const BSONObj shardPipeExpected = pipelineFromJsonArray(shardPipeJson());
const BSONObj mergePipeExpected = pipelineFromJsonArray(mergePipeJson());
intrusive_ptr ctx =
new ExpressionContext(&_opCtx, NamespaceString("a.collection"));
string errmsg;
intrusive_ptr mergePipe =
Pipeline::parseCommand(errmsg, inputBson, ctx);
ASSERT_EQUALS(errmsg, "");
ASSERT(mergePipe != NULL);
intrusive_ptr shardPipe = mergePipe->splitForSharded();
ASSERT(shardPipe != NULL);
ASSERT_EQUALS(shardPipe->serialize()["pipeline"],
Value(shardPipeExpected["pipeline"]));
ASSERT_EQUALS(mergePipe->serialize()["pipeline"],
Value(mergePipeExpected["pipeline"]));
}
virtual ~Base() {}
private:
OperationContextImpl _opCtx;
};
// General test to make sure all optimizations support empty pipelines
class Empty : public Base {
string inputPipeJson() { return "[]"; }
string shardPipeJson() { return "[]"; }
string mergePipeJson() { return "[]"; }
};
namespace moveFinalUnwindFromShardsToMerger {
class OneUnwind : public Base {
string inputPipeJson() { return "[{$unwind: '$a'}]}"; }
string shardPipeJson() { return "[]}"; }
string mergePipeJson() { return "[{$unwind: '$a'}]}"; }
};
class TwoUnwind : public Base {
string inputPipeJson() { return "[{$unwind: '$a'}, {$unwind: '$b'}]}"; }
string shardPipeJson() { return "[]}"; }
string mergePipeJson() { return "[{$unwind: '$a'}, {$unwind: '$b'}]}"; }
};
class UnwindNotFinal : public Base {
string inputPipeJson() { return "[{$unwind: '$a'}, {$match: {a:1}}]}"; }
string shardPipeJson() { return "[{$unwind: '$a'}, {$match: {a:1}}]}"; }
string mergePipeJson() { return "[]}"; }
};
class UnwindWithOther : public Base {
string inputPipeJson() { return "[{$match: {a:1}}, {$unwind: '$a'}]}"; }
string shardPipeJson() { return "[{$match: {a:1}}]}"; }
string mergePipeJson() { return "[{$unwind: '$a'}]}"; }
};
} // namespace moveFinalUnwindFromShardsToMerger
namespace limitFieldsSentFromShardsToMerger {
// These tests use $limit to split the pipelines between shards and merger as it is
// always a split point and neutral in terms of needed fields.
class NeedWholeDoc : public Base {
string inputPipeJson() { return "[{$limit:1}]"; }
string shardPipeJson() { return "[{$limit:1}]"; }
string mergePipeJson() { return "[{$limit:1}]"; }
};
class JustNeedsId : public Base {
string inputPipeJson() { return "[{$limit:1}, {$group: {_id: '$_id'}}]"; }
string shardPipeJson() { return "[{$limit:1}, {$project: {_id:true}}]"; }
string mergePipeJson() { return "[{$limit:1}, {$group: {_id: '$_id'}}]"; }
};
class JustNeedsNonId : public Base {
string inputPipeJson() {
return "[{$limit:1}, {$group: {_id: '$a.b'}}]";
}
string shardPipeJson() {
return "[{$limit:1}, {$project: {_id: false, a: {b: true}}}]";
}
string mergePipeJson() {
return "[{$limit:1}, {$group: {_id: '$a.b'}}]";
}
};
class NothingNeeded : public Base {
string inputPipeJson() {
return "[{$limit:1}"
",{$group: {_id: {$const: null}, count: {$sum: {$const: 1}}}}"
"]";
}
string shardPipeJson() {
return "[{$limit:1}"
",{$project: {_id: true}}"
"]";
}
string mergePipeJson() {
return "[{$limit:1}"
",{$group: {_id: {$const: null}, count: {$sum: {$const: 1}}}}"
"]";
}
};
class JustNeedsMetadata : public Base {
// Currently this optimization doesn't handle metadata and the shards assume it
// needs to be propagated implicitly. Therefore the $project produced should be
// the same as in NothingNeeded.
string inputPipeJson() {
return "[{$limit:1}, {$project: {_id: false, a: {$meta: 'textScore'}}}]";
}
string shardPipeJson() {
return "[{$limit:1}, {$project: {_id: true}}]";
}
string mergePipeJson() {
return "[{$limit:1}, {$project: {_id: false, a: {$meta: 'textScore'}}}]";
}
};
class ShardAlreadyExhaustive : public Base {
// No new project should be added. This test reflects current behavior where the
// 'a' field is still sent because it is explicitly asked for, even though it
// isn't actually needed. If this changes in the future, this test will need to
// change.
string inputPipeJson() {
return "[{$project: {_id:true, a:true}}"
",{$limit:1}"
",{$group: {_id: '$_id'}}"
"]";
}
string shardPipeJson() {
return "[{$project: {_id:true, a:true}}"
",{$limit:1}"
"]";
}
string mergePipeJson() {
return "[{$limit:1}"
",{$group: {_id: '$_id'}}"
"]";
}
};
} // namespace limitFieldsSentFromShardsToMerger
} // namespace Sharded
} // namespace Optimizations
class All : public Suite {
public:
All() : Suite( "pipeline" ) {
}
void setupTests() {
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
add();
}
};
SuiteInstance myall;
} // namespace PipelineTests