summaryrefslogtreecommitdiff
path: root/src/mongo/db/exec/projection_exec.h
blob: e330184f3a77b000997439c8ade69cc39a09d59b (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
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283

/**
 *    Copyright (C) 2018-present MongoDB, Inc.
 *
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the Server Side Public License, version 1,
 *    as published by MongoDB, Inc.
 *
 *    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
 *    Server Side Public License for more details.
 *
 *    You should have received a copy of the Server Side Public License
 *    along with this program. If not, see
 *    <http://www.mongodb.com/licensing/server-side-public-license>.
 *
 *    As a special exception, the copyright holders give permission to link the
 *    code of portions of this program with the OpenSSL library under certain
 *    conditions as described in each individual source file and distribute
 *    linked combinations including the program with the OpenSSL library. You
 *    must comply with the Server Side Public License in all respects for
 *    all of the code used other than as permitted herein. If you modify file(s)
 *    with this exception, you may extend this exception to your version of the
 *    file(s), but you are not obligated to do so. If you do not wish to do so,
 *    delete this exception statement from your version. If you delete this
 *    exception statement from all source files in the program, then also delete
 *    it in the license file.
 */

#pragma once

#include "boost/optional.hpp"

#include "mongo/db/exec/working_set.h"
#include "mongo/db/jsobj.h"
#include "mongo/db/matcher/expression.h"
#include "mongo/db/matcher/expression_parser.h"
#include "mongo/util/string_map.h"

namespace mongo {

class CollatorInterface;

/**
 * A fully-featured executor for find projection.
 */
class ProjectionExec {
public:
    /**
     * A .find() projection can have an array operation, either an elemMatch or positional (or
     * neither).
     */
    enum ArrayOpType { ARRAY_OP_NORMAL = 0, ARRAY_OP_ELEM_MATCH, ARRAY_OP_POSITIONAL };

    /**
     * Projections based on data computed while answering a query, or other metadata about a
     * document / query.
     */
    enum MetaProjection {
        META_GEONEAR_DIST,
        META_GEONEAR_POINT,
        META_RECORDID,
        META_SORT_KEY,
        META_TEXT_SCORE,
    };

    /**
     * TODO: document why we like StringMap so much here
     */
    typedef StringMap<ProjectionExec*> FieldMap;
    typedef StringMap<MatchExpression*> Matchers;
    typedef StringMap<MetaProjection> MetaMap;

    ProjectionExec(OperationContext* opCtx,
                   const BSONObj& spec,
                   const MatchExpression* queryExpression,
                   const CollatorInterface* collator);

    ~ProjectionExec();

    /**
     * Indicates whether this is a returnKey projection which should be performed via
     * 'computeReturnKeyProjection()'.
     */
    bool returnKey() const {
        return _hasReturnKey;
    }

    /**
     * Indicates whether 'sortKey' must be provided for 'computeReturnKeyProjection()' or
     * 'project()'.
     */
    bool needsSortKey() const {
        return _needsSortKey;
    }

    /**
     * Indicates whether 'geoDistance' must be provided for 'project()'.
     */
    bool needsGeoNearDistance() const {
        return _needsGeoNearDistance;
    }

    /**
     * Indicates whether 'geoNearPoint' must be provided for 'project()'.
     */
    bool needsGeoNearPoint() const {
        return _needsGeoNearPoint;
    }

    /**
     * Indicates whether 'textScore' is going to be used in 'project()'.
     */
    bool needsTextScore() const {
        return _needsTextScore;
    }

    /**
     * Returns false if there are no meta fields to project.
     */
    bool hasMetaFields() const {
        return !_meta.empty();
    }

    /**
     * Performs a returnKey projection and provides index keys rather than projection results.
     */
    StatusWith<BSONObj> computeReturnKeyProjection(const BSONObj& indexKey,
                                                   const BSONObj& sortKey) const;

    /**
     * Performs a projection given a BSONObj source. Meta fields must be provided if necessary.
     * Their necessity can be queried via the 'needs*' functions.
     */
    StatusWith<BSONObj> project(const BSONObj& in,
                                const boost::optional<const double> geoDistance = boost::none,
                                const BSONObj& geoNearPoint = BSONObj(),
                                const BSONObj& sortKey = BSONObj(),
                                const boost::optional<const double> textScore = boost::none,
                                const int64_t recordId = 0) const;

    /**
     * Performs a projection given index 'KeyData' to directly retrieve results. This function
     * handles projections which do not qualify for the ProjectionNodeCovered fast-path but are
     * still covered by indices.
     */
    StatusWith<BSONObj> projectCovered(
        const std::vector<IndexKeyDatum>& keyData,
        const boost::optional<const double> geoDistance = boost::none,
        const BSONObj& geoNearPoint = BSONObj(),
        const BSONObj& sortKey = BSONObj(),
        const boost::optional<const double> textScore = boost::none,
        const int64_t recordId = 0) const;

    /**
     * Determines if calls to the project method require that this object was created with the full
     * query expression. We may need it for MatchDetails.
     */
    bool projectRequiresQueryExpression() const {
        return ARRAY_OP_POSITIONAL == _arrayOpType;
    }


private:
    /**
     * Adds meta fields to the end of a projection.
     */
    BSONObj addMeta(BSONObjBuilder bob,
                    const boost::optional<const double> geoDistance,
                    const BSONObj& geoNearPoint,
                    const BSONObj& sortKey,
                    const boost::optional<const double> textScore,
                    const int64_t recordId) const;

    //
    // Initialization
    //

    ProjectionExec() = default;

    /**
     * Add 'field' as a field name that is included or excluded as part of the projection.
     */
    void add(const std::string& field, bool include);

    /**
     * Add 'field' as a field name that is sliced as part of the projection.
     */
    void add(const std::string& field, int skip, int limit);

    //
    // Execution
    //

    /**
     * Apply the projection that 'this' represents to the object 'in'.  'details' is the result
     * of a match evaluation of the full query on the object 'in'.  This is only required
     * if the projection is positional.
     *
     * If the projection is successfully computed, returns Status::OK() and stuff the result in
     * 'bob'.
     * Otherwise, returns error.
     */
    Status projectHelper(const BSONObj& in,
                         BSONObjBuilder* bob,
                         const MatchDetails* details = nullptr) const;

    /**
     * Appends the element 'e' to the builder 'bob', possibly descending into sub-fields of 'e'
     * if needed.
     */
    Status append(BSONObjBuilder* bob,
                  const BSONElement& elt,
                  const MatchDetails* details = NULL,
                  const ArrayOpType arrayOpType = ARRAY_OP_NORMAL) const;

    /**
     * Like append, but for arrays.
     * Deals with slice and calls appendArray to preserve the array-ness.
     */
    void appendArray(BSONObjBuilder* bob, const BSONObj& array, bool nested = false) const;

    // True if default at this level is to include.
    bool _include = true;

    // True if this level can't be skipped or included without recursing.
    bool _special = false;

    // We must group projections with common prefixes together.
    // TODO: benchmark std::vector<pair> vs map
    //
    // Projection is a rooted tree.  If we have {a.b: 1, a.c: 1} we don't want to
    // double-traverse the document when we're projecting it.  Instead, we have an entry in
    // _fields for 'a' with two sub projections: b:1 and c:1.
    FieldMap _fields;

    // The raw projection spec. that is passed into init(...)
    BSONObj _source;

    // Should we include the _id field?
    bool _includeID = true;

    // Arguments from the $slice operator.
    int _skip = 0;
    int _limit = -1;

    // Used for $elemMatch and positional operator ($)
    Matchers _matchers;

    // The matchers above point into BSONObjs and this is where those objs live.
    std::vector<BSONObj> _elemMatchObjs;

    ArrayOpType _arrayOpType = ARRAY_OP_NORMAL;

    // The full query expression. Used when we need MatchDetails.
    const MatchExpression* _queryExpression = nullptr;

    // Projections that aren't sourced from the document or index keys.
    MetaMap _meta;

    // Do we have a returnKey projection?  If so we *only* output the index key metadata, and
    // possibly the sort key for mongos to use.  If it's not found we output nothing.
    bool _hasReturnKey = false;

    // After parsing in the constructor, these fields will indicate the neccesity of metadata
    // for $meta projection.
    bool _needsSortKey = false;
    bool _needsGeoNearDistance = false;
    bool _needsGeoNearPoint = false;
    bool _needsTextScore = false;

    // The field names associated with any sortKey meta-projection(s). Empty if there is no sortKey
    // meta-projection.
    std::vector<StringData> _sortKeyMetaFields;

    // The collator this projection should use to compare strings. Needed for projection operators
    // that perform matching (e.g. elemMatch projection). If null, the collation is a simple binary
    // compare.
    const CollatorInterface* _collator = nullptr;
};

}  // namespace mongo