summaryrefslogtreecommitdiff
path: root/src/mongo/db/keypattern.cpp
blob: 96229b70fd8b1e6ba84865469b2a3c88e90691ac (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
// @file keypattern.cpp

/**
*    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 <http://www.gnu.org/licenses/>.
*/

#include "mongo/db/keypattern.h"
#include "mongo/util/mongoutils/str.h"
#include "mongo/db/hasher.h"
#include "mongo/db/queryutil.h"

namespace mongo {

    BSONObj KeyPattern::extractSingleKey(const BSONObj& doc ) const {
        if ( _pattern.isEmpty() )
            return BSONObj();

        if ( mongoutils::str::equals( _pattern.firstElement().valuestrsafe() , "hashed" ) ){
            BSONElement fieldVal = doc.getFieldDotted( _pattern.firstElementFieldName() );
            return BSON( _pattern.firstElementFieldName() <<
                         BSONElementHasher::hash64( fieldVal ,
                                                    BSONElementHasher::DEFAULT_HASH_SEED ) );
        }

        return doc.extractFields( _pattern );
    }

    bool KeyPattern::isSpecial() const {
        BSONForEach(e, _pattern) {
            int fieldVal = e.numberInt();
            if ( fieldVal != 1 && fieldVal != -1 ){
                return true;
            }
        }
        return false;
    }

    bool KeyPattern::isCoveredBy( const KeyPattern& other ) const {
        BSONForEach( e, _pattern ) {
            BSONElement otherfield = other.getField( e.fieldName() );
            if ( otherfield.eoo() ){
                return false;
            }

            if ( otherfield.numberInt() != 1 && otherfield.numberInt() != -1 && otherfield != e ){
                return false;
            }
        }
        return true;
    }

    typedef vector<pair<BSONObj,BSONObj> >::const_iterator BoundListIter;

    BoundList KeyPattern::keyBounds( const FieldRangeSet& queryConstraints ) const {
        // To construct our bounds we will generate intervals based on constraints for
        // the first field, then compound intervals based on constraints for the first
        // 2 fields, then compound intervals for the first 3 fields, etc.
        // As we loop through the fields, we start generating new intervals that will later
        // get extended in another iteration of the loop.  We define these partially constructed
        // intervals using pairs of BSONObjBuilders (shared_ptrs, since after one iteration of the
        // loop they still must exist outside their scope).
        typedef vector< pair< shared_ptr<BSONObjBuilder> ,
                              shared_ptr<BSONObjBuilder> > > BoundBuilders;
        BoundBuilders builders;
        builders.push_back( make_pair( shared_ptr<BSONObjBuilder>( new BSONObjBuilder() ),
                                       shared_ptr<BSONObjBuilder>( new BSONObjBuilder() ) ) );
        BSONObjIterator i( _pattern );
        // until equalityOnly is false, we are just dealing with equality (no range or $in queries).
        bool equalityOnly = true;
        while( i.more() ) {
            BSONElement e = i.next();

            // get the relevant intervals for this field, but we may have to transform the
            // list of what's relevant according to the expression for this field
            const FieldRange &fr = queryConstraints.range( e.fieldName() );
            const vector<FieldInterval> &oldIntervals = fr.intervals();
            BoundList fieldBounds = _transformFieldBounds( oldIntervals , e );

            if ( equalityOnly ) {
                if ( fieldBounds.size() == 1 &&
                     ( fieldBounds.front().first == fieldBounds.front().second ) ){
                    // this field is only a single point-interval
                    BoundBuilders::const_iterator j;
                    for( j = builders.begin(); j != builders.end(); ++j ) {
                        j->first->appendElements( fieldBounds.front().first );
                        j->second->appendElements( fieldBounds.front().first );
                    }
                }
                else {
                    // This clause is the first to generate more than a single point.
                    // We only execute this clause once. After that, we simplify the bound
                    // extensions to prevent combinatorial explosion.
                    equalityOnly = false;

                    BoundBuilders newBuilders;
                    BoundBuilders::const_iterator i;
                    for( i = builders.begin(); i != builders.end(); ++i ) {
                        BSONObj first = i->first->obj();
                        BSONObj second = i->second->obj();

                        for(BoundListIter j = fieldBounds.begin(); j != fieldBounds.end(); ++j ) {
                            uassert( 16452,
                                     "combinatorial limit of $in partitioning of results exceeded" ,
                                     newBuilders.size() < MAX_IN_COMBINATIONS );
                            newBuilders.push_back(
                                     make_pair( shared_ptr<BSONObjBuilder>( new BSONObjBuilder() ),
                                                shared_ptr<BSONObjBuilder>( new BSONObjBuilder())));
                            newBuilders.back().first->appendElements( first );
                            newBuilders.back().second->appendElements( second );
                            newBuilders.back().first->appendElements( j->first );
                            newBuilders.back().second->appendElements( j->second );
                        }
                    }
                    builders = newBuilders;
                }
            }
            else {
                // if we've already generated a range or multiple point-intervals
                // just extend what we've generated with min/max bounds for this field
                BoundBuilders::const_iterator j;
                for( j = builders.begin(); j != builders.end(); ++j ) {
                    j->first->appendElements( fieldBounds.front().first );
                    j->second->appendElements( fieldBounds.back().second );
                }
            }
        }
        BoundList ret;
        for( BoundBuilders::const_iterator i = builders.begin(); i != builders.end(); ++i )
            ret.push_back( make_pair( i->first->obj(), i->second->obj() ) );
        return ret;
    }

    BoundList KeyPattern::_transformFieldBounds( const vector<FieldInterval>& oldIntervals ,
                                                 const BSONElement& field  ) const {

        BoundList ret;
        vector<FieldInterval>::const_iterator i;
        for( i = oldIntervals.begin(); i != oldIntervals.end(); ++i ) {
            if ( isAscending( field ) ){
                // straightforward map [a,b] --> [a,b]
                ret.push_back( make_pair( BSON( field.fieldName() << i->_lower._bound ) ,
                                          BSON( field.fieldName() << i->_upper._bound ) ) );
            } else if ( isDescending( field ) ) {
                // reverse [a,b] --> [b,a]
                ret.push_back( make_pair( BSON( field.fieldName() << i->_upper._bound ) ,
                                          BSON( field.fieldName() << i->_lower._bound ) ) );
            } else if ( isHashed( field ) ){
                if ( i->equality() ) {
                    // hash [a,a] --> [hash(a),hash(a)]
                    long long int h = BSONElementHasher::hash64( i->_lower._bound ,
                                                             BSONElementHasher::DEFAULT_HASH_SEED );
                    ret.push_back( make_pair( BSON( field.fieldName() << h ) ,
                                              BSON( field.fieldName() << h ) ) );
                } else {
                    // if it's a range interval and this field is hashed, just generate one
                    // big interval from MinKey to MaxKey, since these vals could lie anywhere
                    ret.clear();
                    ret.push_back( make_pair( BSON( field.fieldName() << MINKEY ) ,
                                              BSON( field.fieldName() << MAXKEY ) ) );
                    break;
                }
            }
        }

        if ( isDescending( field ) ) {
            // now order is [ [2,1], [4,3] , [6,5]....[n,n-1] ].  Reverse to get decreasing order.
            reverse( ret.begin() , ret.end() );
        } else if ( isHashed( field ) ){
            // [ hash(a) , hash(b) , hash(c) ...] no longer in order, so sort before returning
            sort( ret.begin() , ret.end() );
        }

        return ret;
    }

} // namespace mongo