summaryrefslogtreecommitdiff
path: root/src/mongo/db/matcher/expression_parser.h
blob: 6091d3f14262f81bc8947badd1a23af4832c4147 (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
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
// expression_parser.h

/**
 *    Copyright (C) 2013 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/>.
 *
 *    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 GNU Affero General 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 "mongo/base/status.h"
#include "mongo/base/status_with.h"
#include "mongo/db/matcher/expression.h"
#include "mongo/db/matcher/expression_leaf.h"
#include "mongo/db/matcher/expression_tree.h"
#include "mongo/db/matcher/expression_type.h"
#include "mongo/db/matcher/expression_with_placeholder.h"
#include "mongo/db/matcher/extensions_callback.h"
#include "mongo/db/matcher/extensions_callback_noop.h"
#include "mongo/db/matcher/schema/expression_internal_schema_allowed_properties.h"
#include "mongo/db/pipeline/expression.h"
#include "mongo/db/pipeline/expression_context.h"
#include "mongo/stdx/functional.h"

namespace mongo {

class OperationContext;

enum class PathAcceptingKeyword {
    ALL,
    BITS_ALL_CLEAR,
    BITS_ALL_SET,
    BITS_ANY_CLEAR,
    BITS_ANY_SET,
    ELEM_MATCH,
    EQUALITY,
    EXISTS,
    GEO_INTERSECTS,
    GEO_NEAR,
    GREATER_THAN,
    GREATER_THAN_OR_EQUAL,
    INTERNAL_SCHEMA_ALL_ELEM_MATCH_FROM_INDEX,
    INTERNAL_SCHEMA_EQ,
    INTERNAL_SCHEMA_FMOD,
    INTERNAL_SCHEMA_MATCH_ARRAY_INDEX,
    INTERNAL_SCHEMA_MAX_ITEMS,
    INTERNAL_SCHEMA_MAX_LENGTH,
    INTERNAL_SCHEMA_MIN_ITEMS,
    INTERNAL_SCHEMA_MIN_LENGTH,
    INTERNAL_SCHEMA_OBJECT_MATCH,
    INTERNAL_SCHEMA_TYPE,
    INTERNAL_SCHEMA_UNIQUE_ITEMS,
    IN_EXPR,
    LESS_THAN,
    LESS_THAN_OR_EQUAL,
    MOD,
    NOT_EQUAL,
    NOT_IN,
    OPTIONS,
    REGEX,
    SIZE,
    TYPE,
    WITHIN,
};

class MatchExpressionParser {
public:
    /**
     * Features allowed in match expression parsing.
     */
    enum AllowedFeatures {
        kText = 1,
        kGeoNear = 1 << 1,
        kJavascript = 1 << 2,
        kExpr = 1 << 3,
        kJSONSchema = 1 << 4,
    };
    using AllowedFeatureSet = unsigned long long;
    static constexpr AllowedFeatureSet kBanAllSpecialFeatures = 0;
    static constexpr AllowedFeatureSet kAllowAllSpecialFeatures =
        std::numeric_limits<unsigned long long>::max();
    static constexpr AllowedFeatureSet kDefaultSpecialFeatures =
        AllowedFeatures::kExpr | AllowedFeatures::kJSONSchema;

    /**
     * Constant double representation of 2^63.
     */
    static const double kLongLongMaxPlusOneAsDouble;

    static constexpr StringData kAggExpression = "$expr"_sd;

    /**
     * Parses PathAcceptingKeyword from 'typeElem'. Returns 'defaultKeyword' if 'typeElem'
     * doesn't represent a known type, or represents PathAcceptingKeyword::EQUALITY which is not
     * handled by this parser (see SERVER-19565).
     */
    static boost::optional<PathAcceptingKeyword> parsePathAcceptingKeyword(
        BSONElement typeElem, boost::optional<PathAcceptingKeyword> defaultKeyword = boost::none);

    /**
     * caller has to maintain ownership obj
     * the tree has views (BSONElement) into obj
     */
    static StatusWithMatchExpression parse(
        const BSONObj& obj,
        const boost::intrusive_ptr<ExpressionContext>& expCtx,
        const ExtensionsCallback& extensionsCallback = ExtensionsCallbackNoop(),
        AllowedFeatureSet allowedFeatures = kDefaultSpecialFeatures) {
        invariant(expCtx.get());
        return MatchExpressionParser(&extensionsCallback)
            ._parse(obj, expCtx, allowedFeatures, DocumentParseLevel::kPredicateTopLevel);
    }

    /**
     * Parses a BSONElement of any numeric type into a positive long long, failing if the value
     * is any of the following:
     *
     * - NaN.
     * - Negative.
     * - A floating point number which is not integral.
     * - Too large to fit within a 64-bit signed integer.
     */
    static StatusWith<long long> parseIntegerElementToNonNegativeLong(BSONElement elem);

    /**
     * Parses a BSONElement of any numeric type into a long long, failing if the value
     * is any of the following:
     *
     * - NaN.
     * - A floating point number which is not integral.
     * - Too large in the positive or negative direction to fit within a 64-bit signed integer.
     */
    static StatusWith<long long> parseIntegerElementToLong(BSONElement elem);

private:
    /**
     * 'DocumentParseLevel' refers to the current position of the parser as it descends a
     *  MatchExpression tree.
     */
    enum class DocumentParseLevel {
        // Indicates that the parser is looking at the root level of the BSON object containing the
        // user's query predicate.
        kPredicateTopLevel,
        // Indicates that match expression nodes in this position will match against the complete
        // user document, as opposed to matching against a nested document or a subdocument inside
        // an array.
        kUserDocumentTopLevel,
        // Indicates that match expression nodes in this position will match against a nested
        // document or a subdocument inside an array.
        kUserSubDocument,
    };

    MatchExpressionParser(const ExtensionsCallback* extensionsCallback)
        : _extensionsCallback(extensionsCallback) {}

    /**
     * 5 = false
     * { a : 5 } = false
     * { $lt : 5 } = true
     * { $ref: "s", $id: "x" } = false
     * { $ref: "s", $id: "x", $db: "mydb" } = false
     * { $ref : "s" } = false (if incomplete DBRef is allowed)
     * { $id : "x" } = false (if incomplete DBRef is allowed)
     * { $db : "mydb" } = false (if incomplete DBRef is allowed)
     */
    bool _isExpressionDocument(const BSONElement& e, bool allowIncompleteDBRef);

    /**
     * { $ref: "s", $id: "x" } = true
     * { $ref : "s" } = true (if incomplete DBRef is allowed)
     * { $id : "x" } = true (if incomplete DBRef is allowed)
     * { $db : "x" } = true (if incomplete DBRef is allowed)
     */
    bool _isDBRefDocument(const BSONObj& obj, bool allowIncompleteDBRef);

    /**
     * Parse 'obj' and return either a MatchExpression or an error.
     */
    StatusWithMatchExpression _parse(const BSONObj& obj,
                                     const boost::intrusive_ptr<ExpressionContext>& expCtx,
                                     AllowedFeatureSet allowedFeatures,
                                     DocumentParseLevel currentLevel);

    /**
     * parses a field in a sub expression
     * if the query is { x : { $gt : 5, $lt : 8 } }
     * obj is { $gt : 5, $lt : 8 }
     */
    Status _parseSub(const char* name,
                     const BSONObj& obj,
                     AndMatchExpression* root,
                     const boost::intrusive_ptr<ExpressionContext>& expCtx,
                     AllowedFeatureSet allowedFeatures,
                     DocumentParseLevel currentLevel);

    /**
     * parses a single field in a sub expression
     * if the query is { x : { $gt : 5, $lt : 8 } }
     * e is $gt : 5
     */
    StatusWithMatchExpression _parseSubField(const BSONObj& context,
                                             const AndMatchExpression* andSoFar,
                                             const char* name,
                                             const BSONElement& e,
                                             const boost::intrusive_ptr<ExpressionContext>& expCtx,
                                             AllowedFeatureSet allowedFeatures,
                                             DocumentParseLevel currentLevel);

    StatusWithMatchExpression _parseComparison(
        const char* name,
        ComparisonMatchExpression* cmp,
        const BSONElement& e,
        const boost::intrusive_ptr<ExpressionContext>& expCtx,
        AllowedFeatureSet allowedFeatures);

    StatusWithMatchExpression _parseMOD(const char* name, const BSONElement& e);

    StatusWithMatchExpression _parseRegexElement(const char* name, const BSONElement& e);

    StatusWithMatchExpression _parseRegexDocument(const char* name, const BSONObj& doc);

    Status _parseInExpression(InMatchExpression* entries,
                              const BSONObj& theArray,
                              const boost::intrusive_ptr<ExpressionContext>& expCtx);

    template <class T>
    StatusWithMatchExpression _parseType(const char* name, const BSONElement& elt);

    StatusWithMatchExpression _parseGeo(const char* name,
                                        PathAcceptingKeyword type,
                                        const BSONObj& section,
                                        AllowedFeatureSet allowedFeatures);

    StatusWithMatchExpression _parseExpr(BSONElement elem,
                                         AllowedFeatureSet allowedFeatures,
                                         const boost::intrusive_ptr<ExpressionContext>& expCtx);

    // arrays

    StatusWithMatchExpression _parseElemMatch(const char* name,
                                              const BSONElement& e,
                                              const boost::intrusive_ptr<ExpressionContext>& expCtx,
                                              AllowedFeatureSet allowedFeatures);

    StatusWithMatchExpression _parseAll(const char* name,
                                        const BSONElement& e,
                                        const boost::intrusive_ptr<ExpressionContext>& expCtx,
                                        AllowedFeatureSet allowedFeatures);

    // tree

    Status _parseTreeList(const BSONObj& arr,
                          ListOfMatchExpression* out,
                          const boost::intrusive_ptr<ExpressionContext>& expCtx,
                          AllowedFeatureSet allowedFeatures,
                          DocumentParseLevel currentLevel);

    StatusWithMatchExpression _parseNot(const char* name,
                                        const BSONElement& e,
                                        const boost::intrusive_ptr<ExpressionContext>& expCtx,
                                        AllowedFeatureSet allowedFeatures,
                                        DocumentParseLevel currentLevel);

    /**
     * Parses 'e' into a BitTestMatchExpression.
     */
    template <class T>
    StatusWithMatchExpression _parseBitTest(const char* name, const BSONElement& e);

    /**
     * Converts 'theArray', a BSONArray of integers, into a std::vector of integers.
     */
    StatusWith<std::vector<uint32_t>> _parseBitPositionsArray(const BSONObj& theArray);

    StatusWithMatchExpression _parseInternalSchemaFmod(const char* name, const BSONElement& e);

    /**
     * Looks at the field named 'exprWithPlaceholderFieldName' within 'containingObject' and parses
     * an ExpressionWithPlaceholder from that element. Fails if an error occurs during parsing, or
     * if the ExpressionWithPlaceholder has a different name placeholder than 'expectedPlaceholder'.
     * 'expressionName' is the name of the expression that requires the ExpressionWithPlaceholder
     * and is used to generate helpful error messages.
     */
    StatusWith<std::unique_ptr<ExpressionWithPlaceholder>> _parseExprWithPlaceholder(
        const BSONObj& containingObject,
        StringData exprWithPlaceholderFieldName,
        StringData expressionName,
        StringData expectedPlaceholder,
        const boost::intrusive_ptr<ExpressionContext>& expCtx,
        AllowedFeatureSet allowedFeatures,
        DocumentParseLevel currentLevel);

    StatusWith<std::vector<InternalSchemaAllowedPropertiesMatchExpression::PatternSchema>>
    _parsePatternProperties(BSONElement patternPropertiesElem,
                            StringData expectedPlaceholder,
                            const boost::intrusive_ptr<ExpressionContext>& expCtx,
                            AllowedFeatureSet allowedFeatures,
                            DocumentParseLevel currentLevel);

    /**
     * Parses a MatchExpression which takes a fixed-size array of MatchExpressions as arguments.
     */
    template <class T>
    StatusWithMatchExpression _parseInternalSchemaFixedArityArgument(
        StringData name,
        const BSONElement& elem,
        const boost::intrusive_ptr<ExpressionContext>& expCtx,
        AllowedFeatureSet allowedFeatures,
        DocumentParseLevel currentLevel);

    /**
     * Parses the given BSONElement into a single integer argument and creates a MatchExpression
     * of type 'T' that gets initialized with the resulting integer.
     */
    template <class T>
    StatusWithMatchExpression _parseInternalSchemaSingleIntegerArgument(
        const char* name, const BSONElement& elem) const;

    /**
     * Same as the  _parseInternalSchemaSingleIntegerArgument function, but for top-level
     * operators which don't have paths.
     */
    template <class T>
    StatusWithMatchExpression _parseTopLevelInternalSchemaSingleIntegerArgument(
        const BSONElement& elem) const;

    /**
     * Parses 'elem' into an InternalSchemaMatchArrayIndexMatchExpression.
     */
    StatusWithMatchExpression _parseInternalSchemaMatchArrayIndex(
        const char* path,
        const BSONElement& elem,
        const boost::intrusive_ptr<ExpressionContext>& expCtx,
        AllowedFeatureSet allowedFeatures,
        DocumentParseLevel currentLevel);

    StatusWithMatchExpression _parseInternalSchemaAllowedProperties(
        const BSONElement& elem,
        const boost::intrusive_ptr<ExpressionContext>& expCtx,
        AllowedFeatureSet allowedFeatures,
        DocumentParseLevel currentLevel);

    // Performs parsing for the match extensions. We do not own this pointer - it has to live
    // as long as the parser is active.
    const ExtensionsCallback* _extensionsCallback;
};
}  // namespace mongo