Files
mongo/db/projection.cpp
2010-11-15 16:43:04 -05:00

217 lines
7.1 KiB
C++

// projection.cpp
/* Copyright 2009 10gen Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "pch.h"
#include "projection.h"
#include "../util/mongoutils/str.h"
namespace mongo {
void Projection::init( const BSONObj& o ){
massert( 10371 , "can only add to Projection once", _source.isEmpty());
_source = o;
BSONObjIterator i( o );
int true_false = -1;
while ( i.more() ){
BSONElement e = i.next();
if (e.type() == Object){
BSONObj obj = e.embeddedObject();
BSONElement e2 = obj.firstElement();
if ( strcmp(e2.fieldName(), "$slice") == 0 ){
if (e2.isNumber()){
int i = e2.numberInt();
if (i < 0)
add(e.fieldName(), i, -i); // limit is now positive
else
add(e.fieldName(), 0, i);
} else if (e2.type() == Array) {
BSONObj arr = e2.embeddedObject();
uassert(13099, "$slice array wrong size", arr.nFields() == 2 );
BSONObjIterator it(arr);
int skip = it.next().numberInt();
int limit = it.next().numberInt();
uassert(13100, "$slice limit must be positive", limit > 0 );
add(e.fieldName(), skip, limit);
} else {
uassert(13098, "$slice only supports numbers and [skip, limit] arrays", false);
}
} else {
uassert(13097, string("Unsupported projection option: ") + obj.firstElement().fieldName(), false);
}
} else if (!strcmp(e.fieldName(), "_id") && !e.trueValue()){
_includeID = false;
} else {
add (e.fieldName(), e.trueValue());
// validate input
if (true_false == -1){
true_false = e.trueValue();
_include = !e.trueValue();
}
else{
uassert( 10053 , "You cannot currently mix including and excluding fields. Contact us if this is an issue." ,
(bool)true_false == e.trueValue() );
}
}
}
}
void Projection::add(const string& field, bool include){
if (field.empty()){ // this is the field the user referred to
_include = include;
}
else {
_include = !include;
const size_t dot = field.find('.');
const string subfield = field.substr(0,dot);
const string rest = (dot == string::npos ? "" : field.substr(dot+1,string::npos));
boost::shared_ptr<Projection>& fm = _fields[subfield];
if (!fm)
fm.reset(new Projection());
fm->add(rest, include);
}
}
void Projection::add(const string& field, int skip, int limit){
_special = true; // can't include or exclude whole object
if (field.empty()){ // this is the field the user referred to
_skip = skip;
_limit = limit;
} else {
const size_t dot = field.find('.');
const string subfield = field.substr(0,dot);
const string rest = (dot == string::npos ? "" : field.substr(dot+1,string::npos));
boost::shared_ptr<Projection>& fm = _fields[subfield];
if (!fm)
fm.reset(new Projection());
fm->add(rest, skip, limit);
}
}
void Projection::transform( const BSONObj& in , BSONObjBuilder& b ) const {
BSONObjIterator i(in);
while ( i.more() ){
BSONElement e = i.next();
if ( mongoutils::str::equals( "_id" , e.fieldName() ) ){
if ( _includeID )
b.append( e );
}
else {
append( b , e );
}
}
}
BSONObj Projection::transform( const BSONObj& in ) const {
BSONObjBuilder b;
transform( in , b );
return b.obj();
}
//b will be the value part of an array-typed BSONElement
void Projection::appendArray( BSONObjBuilder& b , const BSONObj& a , bool nested) const {
int skip = nested ? 0 : _skip;
int limit = nested ? -1 : _limit;
if (skip < 0){
skip = max(0, skip + a.nFields());
}
int i=0;
BSONObjIterator it(a);
while (it.more()){
BSONElement e = it.next();
if (skip){
skip--;
continue;
}
if (limit != -1 && (limit-- == 0)){
break;
}
switch(e.type()){
case Array:{
BSONObjBuilder subb;
appendArray(subb , e.embeddedObject(), true);
b.appendArray(b.numStr(i++), subb.obj());
break;
}
case Object:{
BSONObjBuilder subb;
BSONObjIterator jt(e.embeddedObject());
while (jt.more()){
append(subb , jt.next());
}
b.append(b.numStr(i++), subb.obj());
break;
}
default:
if (_include)
b.appendAs(e, b.numStr(i++));
}
}
}
void Projection::append( BSONObjBuilder& b , const BSONElement& e ) const {
FieldMap::const_iterator field = _fields.find( e.fieldName() );
if (field == _fields.end()){
if (_include)
b.append(e);
}
else {
Projection& subfm = *field->second;
if ((subfm._fields.empty() && !subfm._special) || !(e.type()==Object || e.type()==Array) ){
if (subfm._include)
b.append(e);
}
else if (e.type() == Object){
BSONObjBuilder subb;
BSONObjIterator it(e.embeddedObject());
while (it.more()){
subfm.append(subb, it.next());
}
b.append(e.fieldName(), subb.obj());
}
else { //Array
BSONObjBuilder subb;
subfm.appendArray(subb, e.embeddedObject());
b.appendArray(e.fieldName(), subb.obj());
}
}
}
}