summaryrefslogtreecommitdiff
path: root/src/mongo/db/geo/s2cursor.cpp
blob: f94971d8e0817726102b1c9c1ff6901a5c1ca445 (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
/**
*    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/geo/s2cursor.h"

#include "mongo/db/btreecursor.h"
#include "mongo/db/index.h"
#include "mongo/db/matcher.h"
#include "mongo/db/pdfile.h"
#include "mongo/db/geo/s2common.h"

namespace mongo {
    S2Cursor::S2Cursor(const BSONObj &keyPattern, const IndexDetails *details,
                       const BSONObj &query, const vector<GeoQuery> &fields,
                       const S2IndexingParams &params)
        : _details(details), _fields(fields), _params(params), _keyPattern(keyPattern),
          _nscanned(0), _matchTested(0), _geoTested(0) {

        BSONObjBuilder geoFieldsToNuke;
        for (size_t i = 0; i < _fields.size(); ++i) {
            geoFieldsToNuke.append(_fields[i].getField(), "");
        }
        // false means we want to filter OUT geoFieldsToNuke, not filter to include only that.
        _filteredQuery = query.filterFieldsUndotted(geoFieldsToNuke.obj(), false);
        _matcher.reset(new CoveredIndexMatcher(_filteredQuery, keyPattern));
    }

    S2Cursor::~S2Cursor() { }

    CoveredIndexMatcher* S2Cursor::matcher() const { return _matcher.get(); }

    bool S2Cursor::ok() {
        if (NULL == _btreeCursor.get()) {
            // FieldRangeVector needs an IndexSpec so we make it one.
            BSONObjBuilder specBuilder;
            BSONObjIterator i(_keyPattern);
            while (i.more()) {
                BSONElement e = i.next();
                specBuilder.append(e.fieldName(), 1);
            }
            BSONObj spec = specBuilder.obj();
            IndexSpec specForFRV(spec);
            // All the magic is in makeUnifiedFRS.  See below.
            // A lot of these arguments are opaque.
            BSONObj frsObj;
            if (!makeFRSObject(&frsObj)) { return false; }
            FieldRangeSet frs(_details->parentNS().c_str(), frsObj, false, false);
            shared_ptr<FieldRangeVector> frv(new FieldRangeVector(frs, specForFRV, 1));
            _btreeCursor.reset(BtreeCursor::make(nsdetails(_details->parentNS()),
                                                 *_details, frv, 0, 1));
            return advance();
        }
        return _btreeCursor->ok();
    }

    // Make the FieldRangeSet of keys we look for.  Uses coverAsBSON to go from
    // a region to a covering to a set of keys for that covering.
    // Returns false if the FRS object would be empty.
    bool S2Cursor::makeFRSObject(BSONObj *out) {
        BSONObjBuilder frsObjBuilder;
        frsObjBuilder.appendElements(_filteredQuery);

        S2RegionCoverer coverer;

        for (size_t i = 0; i < _fields.size(); ++i) {
            vector<S2CellId> cover;
            double area = _fields[i].getRegion().GetRectBound().Area();
            S2SearchUtil::setCoverLimitsBasedOnArea(area, &coverer, _params.coarsestIndexedLevel);
            coverer.GetCovering(_fields[i].getRegion(), &cover);
            if (0 == cover.size()) { return false; }
            _cellsInCover = cover.size();
            BSONObj fieldRange = S2SearchUtil::coverAsBSON(cover, _fields[i].getField(),
                _params.coarsestIndexedLevel);
            frsObjBuilder.appendElements(fieldRange);
        }

        *out = frsObjBuilder.obj();
        return true;
    }

    Record* S2Cursor::_current() { return _btreeCursor->currLoc().rec(); }
    BSONObj S2Cursor::current() { return _btreeCursor->currLoc().obj(); }
    DiskLoc S2Cursor::currLoc() { return _btreeCursor->currLoc(); }
    BSONObj S2Cursor::currKey() const { return _btreeCursor->currKey(); }
    DiskLoc S2Cursor::refLoc() { return DiskLoc(); }
    long long S2Cursor::nscanned() { return _nscanned; }

    // This is the actual search.
    bool S2Cursor::advance() {
        for (; _btreeCursor->ok(); _btreeCursor->advance()) {
            ++_nscanned;
            if (_seen.end() != _seen.find(_btreeCursor->currLoc())) { continue; }
            _seen.insert(_btreeCursor->currLoc());

            ++_matchTested;
            MatchDetails details;
            bool matched = _matcher->matchesCurrent(_btreeCursor.get(), &details);
            if (!matched) { continue; }

            const BSONObj &indexedObj = _btreeCursor->currLoc().obj();

            ++_geoTested;
            size_t geoFieldsMatched = 0;
            // OK, cool, non-geo match satisfied.  See if the object actually overlaps w/the geo
            // query fields.
            for (size_t i = 0; i < _fields.size(); ++i) {
                BSONElementSet geoFieldElements;
                indexedObj.getFieldsDotted(_fields[i].getField(), geoFieldElements, false);
                if (geoFieldElements.empty()) { continue; }

                bool match = false;

                for (BSONElementSet::iterator oi = geoFieldElements.begin();
                     !match && (oi != geoFieldElements.end()); ++oi) {
                    if (!oi->isABSONObj()) { continue; }
                    const BSONObj &geoObj = oi->Obj();
                    GeometryContainer geoContainer;
                    uassert(16698, "malformed geometry: " + geoObj.toString(),
                            geoContainer.parseFrom(geoObj));
                    match = _fields[i].satisfiesPredicate(geoContainer);
                }

                if (match) { ++geoFieldsMatched; }
            }

            if (geoFieldsMatched == _fields.size()) {
                // We have a winner!  And we point at it.
                return true;
            }
        }
        return false;
    }

    // This is called when we're supposed to yield.
    void S2Cursor::noteLocation() {
        _btreeCursor->noteLocation();
        _seen.clear();
    }

    // Called when we're un-yielding.
    void S2Cursor::checkLocation() {
        _btreeCursor->checkLocation();
        // We are pointing at a valid btree location now, but it may not be a valid result.
        // This ensures that we're pointing at a valid result that satisfies the query.

        // There is something subtle here: Say we point at something valid, and note the location
        // (yield), then checkLocation (unyield), when we call advance, we don't go past the object
        // that we were/are pointing at since we only do that if we've seen it before (that is, it's
        // in _seen, which we clear when we yield).
        advance();
    }

    void S2Cursor::explainDetails(BSONObjBuilder& b) {
        b << "nscanned" << _nscanned;
        b << "matchTested" << _matchTested;
        b << "geoTested" << _geoTested;
        b << "cellsInCover" << _cellsInCover;
    }
}  // namespace mongo