Skip to content

Commit

Permalink
SERVER-5861 Support for template language-benchRun
Browse files Browse the repository at this point in the history
Support for #CONCAT and #RAND_STRING
  • Loading branch information
singhsiddharth committed Jun 12, 2012
1 parent 72e7302 commit 4af8516
Show file tree
Hide file tree
Showing 3 changed files with 277 additions and 4 deletions.
55 changes: 55 additions & 0 deletions src/mongo/scripting/bson_template_evaluator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

#include "mongo/scripting/bson_template_evaluator.h"

#include <cstddef>
#include <cstdlib>

#include "mongo/util/map_util.h"
Expand All @@ -26,6 +27,8 @@ namespace mongo {

void BsonTemplateEvaluator::initializeEvaluator() {
addOperator("RAND_INT", &BsonTemplateEvaluator::evalRandInt);
addOperator("RAND_STRING", &BsonTemplateEvaluator::evalRandString);
addOperator("CONCAT", &BsonTemplateEvaluator::evalConcat);
}

BsonTemplateEvaluator::BsonTemplateEvaluator() {
Expand Down Expand Up @@ -99,4 +102,56 @@ namespace mongo {
return StatusSuccess;
}

BsonTemplateEvaluator::Status BsonTemplateEvaluator::evalRandString(BsonTemplateEvaluator* btl,
const char* fieldName,
const BSONObj in,
BSONObjBuilder& out) {
// in = { #RAND_STRING: [10] }
BSONObj range = in.firstElement().embeddedObject();
if (range.nFields() != 1)
return StatusOpEvaluationError;
if (!range[0].isNumber())
return StatusOpEvaluationError;
const int length = range["0"].numberInt();
if (length <= 0)
return StatusOpEvaluationError;
static const char alphanum[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
static const size_t alphaNumLength = sizeof(alphanum) - 1;
BOOST_STATIC_ASSERT(alphaNumLength == 64);
unsigned currentRand = 0;
std::string str;
for (int i = 0; i < length; ++i, currentRand >>= 6) {
if (i % 5 == 0)
currentRand = rand();
str.push_back(alphanum[currentRand % alphaNumLength]);
}
out.append(fieldName, str);
return StatusSuccess;
}

BsonTemplateEvaluator::Status BsonTemplateEvaluator::evalConcat(BsonTemplateEvaluator* btl,
const char* fieldName,
const BSONObj in,
BSONObjBuilder& out) {
// in = { #CONCAT: ["hello", " ", "world"] }
BSONObjBuilder objectBuilder;
Status status = btl->evaluate(in.firstElement().embeddedObject(), objectBuilder);
if (status != StatusSuccess)
return status;
BSONObj parts = objectBuilder.obj();
if (parts.nFields() <= 1)
return StatusOpEvaluationError;
StringBuilder stringBuilder;
BSONForEach(part, parts) {
if (part.type() == String)
stringBuilder << part.String();
else
part.toString(stringBuilder,false);
}
out.append(fieldName, stringBuilder.str());
return StatusSuccess;
}

} // end namespace mongo
28 changes: 26 additions & 2 deletions src/mongo/scripting/bson_template_evaluator.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
/*
* This library supports a templating language that helps in generating BSON documents from a
* template. The language supports the following template:
* #RAND_INT #LITERAL, #CONCAT, #RAND_STRING.
* #RAND_INT, #RAND_STRING and #CONCAT.
*
* The language will help in quickly expressing richer documents for use in benchRun.
* Ex. : { key : { #RAND_INT: [10, 20] } } or { key : { #CONCAT: ["hello", " ", "world"] } }
Expand Down Expand Up @@ -119,9 +119,33 @@ namespace mongo {
// evaluates a BSON element. This is internally called by the top level evaluate method.
Status _evalElem(BSONElement in, BSONObjBuilder& out);

// operator methods
/*
* Operator method to support #RAND_INT : { key : { #RAND_INT: [10, 20] } }
* The array arguments to #RAND_INT are the min and mix range between which a random number
* will be chosen. The chosen random number is inclusive at the lower end but not at the
* upper end.
* This will evaluate to something like { key : 14 }
* #RAND_INT also supports a third optional argument which is a multiplier.
* Thus for an input { key : { #RAND_INT: [10, 20, 4] } }, the method will
* choose a random number between 10 and 20 and then multiple the chosen value with 4.
*/
static Status evalRandInt(BsonTemplateEvaluator* btl, const char* fieldName,
const BSONObj in, BSONObjBuilder& out);
/*
* Operator method to support #RAND_STRING : { key : { #RAND_STRING: [12] } }
* The array argument to RAND_STRING is the length of the string that is desired.
* This will evaluate to something like { key : "randomstring" }
*/
static Status evalRandString(BsonTemplateEvaluator* btl, const char* fieldName,
const BSONObj in, BSONObjBuilder& out);
/*
* Operator method to support #CONCAT : { key : { #CONCAT: ["hello", " ", "world", 2012] } }
* The array argument to CONCAT are the strings to be concatenated. If the argument is not
* a string it will be stringified and concatendated.
* This will evaluate to { key : "hello world2012" }
*/
static Status evalConcat(BsonTemplateEvaluator* btl, const char* fieldName,
const BSONObj in, BSONObjBuilder& out);

};

Expand Down
198 changes: 196 additions & 2 deletions src/mongo/scripting/bson_template_evaluator_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,13 @@ namespace mongo {
BSONObjBuilder builder2;
randObj = BSON( "#RAND_OP_NOT_EXISTS" << BSON_ARRAY( 5 << 0 ) );
ASSERT_EQUALS( BsonTemplateEvaluator::StatusBadOperator,
t->evaluate(BSON("randField" << randObj), builder2) );
t->evaluate(BSON("randField" << randObj), builder2) );

// Test failure when arguments to RAND_INT are not correct (max < min)
BSONObjBuilder builder3;
randObj = BSON( "#RAND_INT" << BSON_ARRAY( 5 << 0 ) );
ASSERT_EQUALS( BsonTemplateEvaluator::StatusOpEvaluationError,
t->evaluate(BSON("randField" << randObj), builder3) );
t->evaluate(BSON("randField" << randObj), builder3) );

// Test failure when operators are arbitrarily nested
// {id: { #RAND_INT: [ { #RAND_INT: [10, 20] }, 20] }
Expand Down Expand Up @@ -162,5 +162,199 @@ namespace mongo {
ASSERT_GREATER_THAN_OR_EQUALS(obj12.firstElement().numberInt(), 0);
ASSERT_LESS_THAN_OR_EQUALS(obj12.firstElement().numberInt(), 16);
}

TEST(BSONTemplateEvaluatorTest, RAND_STRING) {

BsonTemplateEvaluator *t = new BsonTemplateEvaluator();

// Test failure when the arguments to RAND_STRING is not an integer
BSONObjBuilder builder1;
BSONObj randObj = BSON( "#RAND_STRING" << BSON_ARRAY("hello") );
ASSERT_EQUALS( BsonTemplateEvaluator::StatusOpEvaluationError,
t->evaluate(BSON("randField" << randObj), builder1) );

// Test failure when there is more than 1 argument to RAND_STRING
BSONObjBuilder builder2;
randObj = BSON( "#RAND_STRING" << BSON_ARRAY( 2 << 8 ) );
ASSERT_EQUALS( BsonTemplateEvaluator::StatusOpEvaluationError,
t->evaluate(BSON("randField" << randObj), builder2) );

// Test failure when length argument to RAND_STRING is 0
BSONObjBuilder builder3;
randObj = BSON( "#RAND_STRING" << BSON_ARRAY(0 ) );
ASSERT_EQUALS( BsonTemplateEvaluator::StatusOpEvaluationError,
t->evaluate(BSON("randField" << randObj), builder3) );

// Test success with a single element
BSONObjBuilder builder4;
randObj = BSON( "#RAND_STRING" << BSON_ARRAY(5) );
ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
t->evaluate(BSON("randField" << randObj), builder4) );
BSONObj obj4 = builder4.obj();
ASSERT_EQUALS(obj4.nFields(), 1);
ASSERT_EQUALS(obj4.firstElement().str().length(), 5U);

// Test success with two #RAND_STRING elements
BSONObjBuilder builder5;
randObj = BSON( "#RAND_STRING" << BSON_ARRAY(5) );
ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
t->evaluate(BSON("randField1" << randObj <<
"randField2" << randObj), builder5) );
BSONObj obj5 = builder5.obj();
ASSERT_EQUALS(obj5.nFields(), 2);
BSONObjIterator iter5(obj5);
ASSERT_EQUALS(iter5.next().str().length(), 5U);
ASSERT_EQUALS(iter5.next().str().length(), 5U);

// Test success with #RAND_STRING as the last element
BSONObjBuilder builder6;
randObj = BSON( "#RAND_STRING" << BSON_ARRAY(5) );
ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
t->evaluate(BSON("id" << 1 << "hello" << "world" <<
"randField" << randObj), builder6) );
BSONObj obj6 = builder6.obj();
ASSERT_EQUALS(obj6.nFields(), 3);
BSONObjIterator iter6(obj6);
iter6++;
ASSERT_EQUALS(iter6.next().str().length(), 5U);

// Test success with #RAND_STRING as first element
BSONObjBuilder builder7;
randObj = BSON( "#RAND_STRING" << BSON_ARRAY(5) );
ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
t->evaluate(BSON("randField" << randObj << "hello" << "world" <<
"id" << 1), builder7) );
BSONObj obj7 = builder7.obj();
ASSERT_EQUALS(obj7.nFields(), 3);
ASSERT_EQUALS(obj7.firstElement().str().length(), 5U);

// Test success with #RAND_STRING as the middle element
BSONObjBuilder builder8;
randObj = BSON( "#RAND_STRING" << BSON_ARRAY(5) );
ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
t->evaluate(BSON("id" << 1 << "randField" << randObj << "hello" <<
"world"), builder8) );
BSONObj obj8 = builder8.obj();
ASSERT_EQUALS(obj8.nFields(), 3);
BSONObjIterator iter8(obj8);
iter8++;
ASSERT_EQUALS((*iter8).str().length(), 5U);

// Test success with #RAND_INT as the first and the last element
BSONObjBuilder builder10;
randObj = BSON( "#RAND_STRING" << BSON_ARRAY(5) );
ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
t->evaluate(BSON("randField1" << randObj << "hello" <<
"world" << "randField2" << randObj), builder10) );
BSONObj obj10 = builder10.obj();
ASSERT_EQUALS(obj10.nFields(), 3);

BSONObjIterator iter10(obj10);
ASSERT_EQUALS((*iter10).str().length(), 5U);
iter10++;
ASSERT_EQUALS(iter10.next().str().length(), 5U);

// Test success when one of the element is an array
BSONObjBuilder builder11;
randObj = BSON( "#RAND_STRING" << BSON_ARRAY(5) );
ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
t->evaluate(BSON("testArray" << BSON_ARRAY( 0 << 5 << 10 << 20 ) <<
"hello" << "world" <<
"randField" << randObj), builder11) );
BSONObj obj11 = builder11.obj();
ASSERT_EQUALS(obj11.nFields(), 3);
BSONObjIterator iter11(obj11);
iter11++;
ASSERT_EQUALS(iter11.next().str().length(), 5U);
}

TEST(BSONTemplateEvaluatorTest, CONCAT) {

BsonTemplateEvaluator *t = new BsonTemplateEvaluator();

// Test failure when the arguments to #CONCAT has only one argument
BSONObjBuilder builder1;
BSONObj concatObj = BSON( "#CONCAT" << BSON_ARRAY("hello") );
ASSERT_EQUALS( BsonTemplateEvaluator::StatusOpEvaluationError,
t->evaluate(BSON("concatField" << concatObj), builder1) );

// Test success when all arguments to #CONCAT are strings
BSONObjBuilder builder2;
concatObj = BSON( "#CONCAT" << BSON_ARRAY("hello" << " " << "world"));
ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
t->evaluate(BSON("concatField" << concatObj), builder2) );
BSONObj obj2 = builder2.obj();
ASSERT_EQUALS(obj2.nFields(), 1);
BSONObj expectedObj = BSON("concatField" << "hello world");
ASSERT_EQUALS(obj2.equal(expectedObj), true);

// Test success when some arguments to #CONCAT are integers
BSONObjBuilder builder3;
concatObj = BSON( "#CONCAT" << BSON_ARRAY("F" << 1 << "racing"));
ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
t->evaluate(BSON("concatField" << concatObj), builder3) );
BSONObj obj3 = builder3.obj();
ASSERT_EQUALS(obj3.nFields(), 1);
expectedObj = BSON("concatField" << "F1racing");
ASSERT_EQUALS(obj3.equal(expectedObj), true);

// Test success with #CONCAT as first element and last element
BSONObjBuilder builder4;
concatObj = BSON( "#CONCAT" << BSON_ARRAY("hello" << " " << "world"));
ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
t->evaluate(BSON("concatField1" << concatObj <<
"middleKey" << 1 <<
"concatField2" << concatObj), builder4) );
BSONObj obj4 = builder4.obj();
ASSERT_EQUALS(obj4.nFields(), 3);
expectedObj = BSON("concatField1" << "hello world" <<
"middleKey" << 1 <<
"concatField2" << "hello world");
ASSERT_EQUALS(obj4.equal(expectedObj), true);

// Test success when one of the arguments to #CONCAT is an array
BSONObjBuilder builder5;
concatObj = BSON( "#CONCAT" << BSON_ARRAY("hello" << BSON_ARRAY(1 << 10) << "world"));
ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
t->evaluate(BSON("concatField" << concatObj), builder5) );
BSONObj obj5 = builder5.obj();
ASSERT_EQUALS(obj5.nFields(), 1);
expectedObj = BSON("concatField" << "hello[ 1, 10 ]world");
ASSERT_EQUALS(obj5.equal(expectedObj), true);
}

TEST(BSONTemplateEvaluatorTest, COMBINED_OPERATORS) {

BsonTemplateEvaluator *t = new BsonTemplateEvaluator();
BSONObj randIntObj = BSON( "#RAND_INT" << BSON_ARRAY( 0 << 5 ) );
BSONObj randStrObj = BSON( "#RAND_STRING" << BSON_ARRAY(5) );

// Test success when #RAND_INT, and #RAND_STRING are combined
BSONObjBuilder builder1;
ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
t->evaluate(BSON("randInt" << randIntObj <<
"randStr" << randStrObj), builder1) );
BSONObj obj1 = builder1.obj();
ASSERT_EQUALS(obj1.nFields(), 2);
BSONObjIterator iter1(obj1);
int randInt = iter1.next().numberInt();
ASSERT_GREATER_THAN_OR_EQUALS(randInt, 0);
ASSERT_LESS_THAN(randInt, 5);
string randStr = iter1.next().str();
ASSERT_EQUALS(randStr.length(), 5U);

// Test success when the #CONCAT and #RAND_INT and #RAND_STRING are combined
BSONObjBuilder builder2;
BSONObj concatObj = BSON("#CONCAT" << BSON_ARRAY(randIntObj << " hello world " <<
randStrObj));
ASSERT_EQUALS( BsonTemplateEvaluator::StatusSuccess,
t->evaluate(BSON("concatField" << concatObj), builder2) );
BSONObj obj2 = builder2.obj();
ASSERT_EQUALS(obj2.nFields(), 1);
// check that the resulting string has a length of 19.
// randIntObj.length = 1, " hello world " = 13, randStrObj.length = 5
// so total string length should 1 + 13 + 5 = 19
ASSERT_EQUALS(obj2.firstElement().str().length(), 19U);
}
} // end anonymous namespace
} // end namespace mongo

0 comments on commit 4af8516

Please sign in to comment.