summaryrefslogtreecommitdiff
path: root/src/mongo/db/projection.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/mongo/db/projection.cpp')
-rw-r--r--src/mongo/db/projection.cpp301
1 files changed, 301 insertions, 0 deletions
diff --git a/src/mongo/db/projection.cpp b/src/mongo/db/projection.cpp
new file mode 100644
index 00000000000..d07e56527af
--- /dev/null
+++ b/src/mongo/db/projection.cpp
@@ -0,0 +1,301 @@
+// 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.isNumber() )
+ _hasNonSimple = true;
+
+ 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.firstElementFieldName(), 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());
+ }
+ }
+ }
+
+ Projection::KeyOnly* Projection::checkKey( const BSONObj& keyPattern ) const {
+ if ( _include ) {
+ // if we default to including then we can't
+ // use an index because we don't know what we're missing
+ return 0;
+ }
+
+ if ( _hasNonSimple )
+ return 0;
+
+ if ( _includeID && keyPattern["_id"].eoo() )
+ return 0;
+
+ // at this point we know its all { x : 1 } style
+
+ auto_ptr<KeyOnly> p( new KeyOnly() );
+
+ int got = 0;
+ BSONObjIterator i( keyPattern );
+ while ( i.more() ) {
+ BSONElement k = i.next();
+
+ if ( _source[k.fieldName()].type() ) {
+
+ if ( strchr( k.fieldName() , '.' ) ) {
+ // TODO we currently don't support dotted fields
+ // SERVER-2104
+ return 0;
+ }
+
+ if ( ! _includeID && mongoutils::str::equals( k.fieldName() , "_id" ) ) {
+ p->addNo();
+ }
+ else {
+ p->addYes( k.fieldName() );
+ got++;
+ }
+ }
+ else if ( mongoutils::str::equals( "_id" , k.fieldName() ) && _includeID ) {
+ p->addYes( "_id" );
+ }
+ else {
+ p->addNo();
+ }
+
+ }
+
+ int need = _source.nFields();
+ if ( ! _includeID )
+ need--;
+
+ if ( got == need )
+ return p.release();
+
+ return 0;
+ }
+
+ BSONObj Projection::KeyOnly::hydrate( const BSONObj& key ) const {
+ assert( _include.size() == _names.size() );
+
+ BSONObjBuilder b( key.objsize() + _stringSize + 16 );
+
+ BSONObjIterator i(key);
+ unsigned n=0;
+ while ( i.more() ) {
+ assert( n < _include.size() );
+ BSONElement e = i.next();
+ if ( _include[n] ) {
+ b.appendAs( e , _names[n] );
+ }
+ n++;
+ }
+
+ return b.obj();
+ }
+}