~ubuntu-branches/ubuntu/utopic/mongodb/utopic

« back to all changes in this revision

Viewing changes to src/mongo/db/query/canonical_query_test.cpp

  • Committer: Package Import Robot
  • Author(s): James Page
  • Date: 2014-07-03 09:23:46 UTC
  • mfrom: (1.3.10) (44.1.14 sid)
  • Revision ID: package-import@ubuntu.com-20140703092346-c5bvt46wnzougyly
Tags: 1:2.6.3-0ubuntu1
* New upstream stable release:
  - Dropped patches, included upstream:
    + 0003-All-platforms-but-Windows-find-hash-in-std-tr1.patch
    + 0008-Use-system-libstemmer.patch
    + 0011-Use-a-signed-char-to-store-BSONType-enumerations.patch
    + 0001-SERVER-12064-Atomic-operations-for-gcc-non-Intel-arc.patch
    + 0002-SERVER-12065-Support-ARM-and-AArch64-builds.patch
  - d/p/*: Refreshed/rebased remaining patches.
  - Use system provided libyaml-cpp:
    + d/control: Add libyaml-cpp-dev to BD's.
    + d/rules: Enable --with-system-yaml option.
    + d/p/fix-yaml-detection.patch: Fix detection of libyaml-cpp library.
  - d/mongodb-server.mongodb.upstart: Sync changes from upstream.
  - d/control,mongodb-dev.*: Drop mongodb-dev package; it has no reverse
    dependencies and upstream no longer install header files.
  - d/NEWS: Point users to upstream upgrade documentation for upgrades
    from 2.4 to 2.6.
* Merge from Debian unstable.
* d/control: BD on libv8-3.14-dev to ensure that transitioning to new v8
  versions is a explicit action due to changes in behaviour in >= 3.25
  (LP: #1295723).
* d/mongodb-server.prerm: Dropped debug echo call from maintainer script
  (LP: #1294455).

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/**
 
2
 *    Copyright (C) 2014 MongoDB Inc.
 
3
 *
 
4
 *    This program is free software: you can redistribute it and/or  modify
 
5
 *    it under the terms of the GNU Affero General Public License, version 3,
 
6
 *    as published by the Free Software Foundation.
 
7
 *
 
8
 *    This program is distributed in the hope that it will be useful,
 
9
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
 *    GNU Affero General Public License for more details.
 
12
 *
 
13
 *    You should have received a copy of the GNU Affero General Public License
 
14
 *    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
15
 *
 
16
 *    As a special exception, the copyright holders give permission to link the
 
17
 *    code of portions of this program with the OpenSSL library under certain
 
18
 *    conditions as described in each individual source file and distribute
 
19
 *    linked combinations including the program with the OpenSSL library. You
 
20
 *    must comply with the GNU Affero General Public License in all respects for
 
21
 *    all of the code used other than as permitted herein. If you modify file(s)
 
22
 *    with this exception, you may extend this exception to your version of the
 
23
 *    file(s), but you are not obligated to do so. If you do not wish to do so,
 
24
 *    delete this exception statement from your version. If you delete this
 
25
 *    exception statement from all source files in the program, then also delete
 
26
 *    it in the license file.
 
27
 */
 
28
 
 
29
#include "mongo/db/query/canonical_query.h"
 
30
 
 
31
#include "mongo/db/json.h"
 
32
#include "mongo/unittest/unittest.h"
 
33
 
 
34
using namespace mongo;
 
35
 
 
36
namespace {
 
37
 
 
38
    static const char* ns = "somebogusns";
 
39
 
 
40
    /**
 
41
     * Utility function to parse the given JSON as a MatchExpression and normalize the expression
 
42
     * tree.  Returns the resulting tree, or an error Status.
 
43
     */
 
44
    StatusWithMatchExpression parseNormalize(const std::string& queryStr) {
 
45
        StatusWithMatchExpression swme = MatchExpressionParser::parse(fromjson(queryStr));
 
46
        if (!swme.getStatus().isOK()) {
 
47
            return swme;
 
48
        }
 
49
        return StatusWithMatchExpression(CanonicalQuery::normalizeTree(swme.getValue()));
 
50
    }
 
51
 
 
52
    TEST(CanonicalQueryTest, IsValidText) {
 
53
        // Passes in default values for LiteParsedQuery.
 
54
        // Filter inside LiteParsedQuery is not used.
 
55
        LiteParsedQuery* lpqRaw;
 
56
        ASSERT_OK(LiteParsedQuery::make(ns, 0, 0, 0, fromjson("{}"), fromjson("{}"),
 
57
                                        fromjson("{}"), fromjson("{}"), fromjson("{}"),
 
58
                                        fromjson("{}"),
 
59
                                        false, // snapshot
 
60
                                        false, // explain
 
61
                                        &lpqRaw));
 
62
        auto_ptr<LiteParsedQuery> lpq(lpqRaw);
 
63
 
 
64
        auto_ptr<MatchExpression> me;
 
65
        StatusWithMatchExpression swme(Status::OK());
 
66
 
 
67
        // Valid: regular TEXT.
 
68
        swme = parseNormalize("{$text: {$search: 's'}}");
 
69
        ASSERT_OK(swme.getStatus());
 
70
        me.reset(swme.getValue());
 
71
        ASSERT_OK(CanonicalQuery::isValid(me.get(), *lpq));
 
72
 
 
73
        // Valid: TEXT inside OR.
 
74
        swme = parseNormalize(
 
75
            "{$or: ["
 
76
            "    {$text: {$search: 's'}},"
 
77
            "    {a: 1}"
 
78
            "]}"
 
79
        );
 
80
        ASSERT_OK(swme.getStatus());
 
81
        me.reset(swme.getValue());
 
82
        ASSERT_OK(CanonicalQuery::isValid(me.get(), *lpq));
 
83
 
 
84
        // Valid: TEXT outside NOR.
 
85
        swme = parseNormalize("{$text: {$search: 's'}, $nor: [{a: 1}, {b: 1}]}");
 
86
        ASSERT_OK(swme.getStatus());
 
87
        me.reset(swme.getValue());
 
88
        ASSERT_OK(CanonicalQuery::isValid(me.get(), *lpq));
 
89
 
 
90
        // Invalid: TEXT inside NOR.
 
91
        swme = parseNormalize("{$nor: [{$text: {$search: 's'}}, {a: 1}]}");
 
92
        ASSERT_OK(swme.getStatus());
 
93
        me.reset(swme.getValue());
 
94
        ASSERT_NOT_OK(CanonicalQuery::isValid(me.get(), *lpq));
 
95
 
 
96
        // Invalid: TEXT inside NOR.
 
97
        swme = parseNormalize(
 
98
            "{$nor: ["
 
99
            "    {$or: ["
 
100
            "        {$text: {$search: 's'}},"
 
101
            "        {a: 1}"
 
102
            "    ]},"
 
103
            "    {a: 2}"
 
104
            "]}"
 
105
        );
 
106
        ASSERT_OK(swme.getStatus());
 
107
        me.reset(swme.getValue());
 
108
        ASSERT_NOT_OK(CanonicalQuery::isValid(me.get(), *lpq));
 
109
 
 
110
        // Invalid: >1 TEXT.
 
111
        swme = parseNormalize(
 
112
            "{$and: ["
 
113
            "    {$text: {$search: 's'}},"
 
114
            "    {$text: {$search: 't'}}"
 
115
            "]}"
 
116
        );
 
117
        ASSERT_OK(swme.getStatus());
 
118
        me.reset(swme.getValue());
 
119
        ASSERT_NOT_OK(CanonicalQuery::isValid(me.get(), *lpq));
 
120
 
 
121
        // Invalid: >1 TEXT.
 
122
        swme = parseNormalize(
 
123
            "{$and: ["
 
124
            "    {$or: ["
 
125
            "        {$text: {$search: 's'}},"
 
126
            "        {a: 1}"
 
127
            "    ]},"
 
128
            "    {$or: ["
 
129
            "        {$text: {$search: 't'}},"
 
130
            "        {b: 1}"
 
131
            "    ]}"
 
132
            "]}"
 
133
        );
 
134
        ASSERT_OK(swme.getStatus());
 
135
        me.reset(swme.getValue());
 
136
        ASSERT_NOT_OK(CanonicalQuery::isValid(me.get(), *lpq));
 
137
    }
 
138
 
 
139
    TEST(CanonicalQueryTest, IsValidGeo) {
 
140
        // Passes in default values for LiteParsedQuery.
 
141
        // Filter inside LiteParsedQuery is not used.
 
142
        LiteParsedQuery* lpqRaw;
 
143
        ASSERT_OK(LiteParsedQuery::make(ns, 0, 0, 0, fromjson("{}"), fromjson("{}"),
 
144
                                        fromjson("{}"), fromjson("{}"), fromjson("{}"),
 
145
                                        fromjson("{}"),
 
146
                                        false, // snapshot
 
147
                                        false, // explain
 
148
                                        &lpqRaw));
 
149
        auto_ptr<LiteParsedQuery> lpq(lpqRaw);
 
150
 
 
151
        auto_ptr<MatchExpression> me;
 
152
        StatusWithMatchExpression swme(Status::OK());
 
153
 
 
154
        // Valid: regular GEO_NEAR.
 
155
        swme = parseNormalize("{a: {$near: [0, 0]}}");
 
156
        ASSERT_OK(swme.getStatus());
 
157
        me.reset(swme.getValue());
 
158
        ASSERT_OK(CanonicalQuery::isValid(me.get(), *lpq));
 
159
 
 
160
        // Valid: GEO_NEAR inside nested AND.
 
161
        swme = parseNormalize(
 
162
            "{$and: ["
 
163
            "    {$and: ["
 
164
            "        {a: {$near: [0, 0]}},"
 
165
            "        {b: 1}"
 
166
            "    ]},"
 
167
            "    {c: 1}"
 
168
            "]}"
 
169
        );
 
170
        ASSERT_OK(swme.getStatus());
 
171
        me.reset(swme.getValue());
 
172
        ASSERT_OK(CanonicalQuery::isValid(me.get(), *lpq));
 
173
 
 
174
        // Invalid: >1 GEO_NEAR.
 
175
        swme = parseNormalize(
 
176
            "{$and: ["
 
177
            "    {a: {$near: [0, 0]}},"
 
178
            "    {b: {$near: [0, 0]}}"
 
179
            "]}"
 
180
        );
 
181
        ASSERT_OK(swme.getStatus());
 
182
        me.reset(swme.getValue());
 
183
        ASSERT_NOT_OK(CanonicalQuery::isValid(me.get(), *lpq));
 
184
 
 
185
        // Invalid: >1 GEO_NEAR.
 
186
        swme = parseNormalize(
 
187
            "{$and: ["
 
188
            "    {a: {$geoNear: [0, 0]}},"
 
189
            "    {b: {$near: [0, 0]}}"
 
190
            "]}"
 
191
        );
 
192
        ASSERT_OK(swme.getStatus());
 
193
        me.reset(swme.getValue());
 
194
        ASSERT_NOT_OK(CanonicalQuery::isValid(me.get(), *lpq));
 
195
 
 
196
        // Invalid: >1 GEO_NEAR.
 
197
        swme = parseNormalize(
 
198
            "{$and: ["
 
199
            "    {$and: ["
 
200
            "        {a: {$near: [0, 0]}},"
 
201
            "        {b: 1}"
 
202
            "    ]},"
 
203
            "    {$and: ["
 
204
            "        {c: {$near: [0, 0]}},"
 
205
            "        {d: 1}"
 
206
            "    ]}"
 
207
            "]}"
 
208
        );
 
209
        ASSERT_OK(swme.getStatus());
 
210
        me.reset(swme.getValue());
 
211
        ASSERT_NOT_OK(CanonicalQuery::isValid(me.get(), *lpq));
 
212
 
 
213
        // Invalid: GEO_NEAR inside NOR.
 
214
        swme = parseNormalize(
 
215
            "{$nor: ["
 
216
            "    {a: {$near: [0, 0]}},"
 
217
            "    {b: 1}"
 
218
            "]}"
 
219
        );
 
220
        ASSERT_OK(swme.getStatus());
 
221
        me.reset(swme.getValue());
 
222
        ASSERT_NOT_OK(CanonicalQuery::isValid(me.get(), *lpq));
 
223
 
 
224
        // Invalid: GEO_NEAR inside OR.
 
225
        swme = parseNormalize(
 
226
            "{$or: ["
 
227
            "    {a: {$near: [0, 0]}},"
 
228
            "    {b: 1}"
 
229
            "]}"
 
230
        );
 
231
        ASSERT_OK(swme.getStatus());
 
232
        me.reset(swme.getValue());
 
233
        ASSERT_NOT_OK(CanonicalQuery::isValid(me.get(), *lpq));
 
234
    }
 
235
 
 
236
    TEST(CanonicalQueryTest, IsValidTextAndGeo) {
 
237
        // Passes in default values for LiteParsedQuery.
 
238
        // Filter inside LiteParsedQuery is not used.
 
239
        LiteParsedQuery* lpqRaw;
 
240
        ASSERT_OK(LiteParsedQuery::make(ns, 0, 0, 0, fromjson("{}"), fromjson("{}"),
 
241
                                        fromjson("{}"), fromjson("{}"), fromjson("{}"),
 
242
                                        fromjson("{}"),
 
243
                                        false, // snapshot
 
244
                                        false, // explain
 
245
                                        &lpqRaw));
 
246
        auto_ptr<LiteParsedQuery> lpq(lpqRaw);
 
247
 
 
248
        auto_ptr<MatchExpression> me;
 
249
        StatusWithMatchExpression swme(Status::OK());
 
250
 
 
251
        // Invalid: TEXT and GEO_NEAR.
 
252
        swme = parseNormalize("{$text: {$search: 's'}, a: {$near: [0, 0]}}");
 
253
        ASSERT_OK(swme.getStatus());
 
254
        me.reset(swme.getValue());
 
255
        ASSERT_NOT_OK(CanonicalQuery::isValid(me.get(), *lpq));
 
256
 
 
257
        // Invalid: TEXT and GEO_NEAR.
 
258
        swme = parseNormalize("{$text: {$search: 's'}, a: {$geoNear: [0, 0]}}");
 
259
        ASSERT_OK(swme.getStatus());
 
260
        me.reset(swme.getValue());
 
261
        ASSERT_NOT_OK(CanonicalQuery::isValid(me.get(), *lpq));
 
262
 
 
263
        // Invalid: TEXT and GEO_NEAR.
 
264
        swme = parseNormalize(
 
265
            "{$or: ["
 
266
            "    {$text: {$search: 's'}},"
 
267
            "    {a: 1}"
 
268
            " ],"
 
269
            " b: {$near: [0, 0]}}"
 
270
        );
 
271
        ASSERT_OK(swme.getStatus());
 
272
        me.reset(swme.getValue());
 
273
        ASSERT_NOT_OK(CanonicalQuery::isValid(me.get(), *lpq));
 
274
    }
 
275
 
 
276
    TEST(CanonicalQueryTest, IsValidTextAndNaturalAscending) {
 
277
        // Passes in default values for LiteParsedQuery except for sort order.
 
278
        // Filter inside LiteParsedQuery is not used.
 
279
        LiteParsedQuery* lpqRaw;
 
280
        BSONObj sort = fromjson("{$natural: 1}");
 
281
        ASSERT_OK(LiteParsedQuery::make(ns, 0, 0, 0, fromjson("{}"), fromjson("{}"),
 
282
                                        sort, fromjson("{}"), fromjson("{}"),
 
283
                                        fromjson("{}"),
 
284
                                        false, // snapshot
 
285
                                        false, // explain
 
286
                                        &lpqRaw));
 
287
        auto_ptr<LiteParsedQuery> lpq(lpqRaw);
 
288
 
 
289
        auto_ptr<MatchExpression> me;
 
290
        StatusWithMatchExpression swme(Status::OK());
 
291
 
 
292
        // Invalid: TEXT and {$natural: 1} sort order.
 
293
        swme = parseNormalize("{$text: {$search: 's'}}");
 
294
        ASSERT_OK(swme.getStatus());
 
295
        me.reset(swme.getValue());
 
296
        ASSERT_NOT_OK(CanonicalQuery::isValid(me.get(), *lpq));
 
297
    }
 
298
 
 
299
    TEST(CanonicalQueryTest, IsValidTextAndNaturalDescending) {
 
300
        // Passes in default values for LiteParsedQuery except for sort order.
 
301
        // Filter inside LiteParsedQuery is not used.
 
302
        LiteParsedQuery* lpqRaw;
 
303
        BSONObj sort = fromjson("{$natural: -1}");
 
304
        ASSERT_OK(LiteParsedQuery::make(ns, 0, 0, 0, fromjson("{}"), fromjson("{}"),
 
305
                                        sort, fromjson("{}"), fromjson("{}"),
 
306
                                        fromjson("{}"),
 
307
                                        false, // snapshot
 
308
                                        false, // explain
 
309
                                        &lpqRaw));
 
310
        auto_ptr<LiteParsedQuery> lpq(lpqRaw);
 
311
 
 
312
        auto_ptr<MatchExpression> me;
 
313
        StatusWithMatchExpression swme(Status::OK());
 
314
 
 
315
        // Invalid: TEXT and {$natural: -1} sort order.
 
316
        swme = parseNormalize("{$text: {$search: 's'}}");
 
317
        ASSERT_OK(swme.getStatus());
 
318
        me.reset(swme.getValue());
 
319
        ASSERT_NOT_OK(CanonicalQuery::isValid(me.get(), *lpq));
 
320
    }
 
321
 
 
322
    TEST(CanonicalQueryTest, IsValidTextAndHint) {
 
323
        // Passes in default values for LiteParsedQuery except for hint.
 
324
        // Filter inside LiteParsedQuery is not used.
 
325
        LiteParsedQuery* lpqRaw;
 
326
        BSONObj hint = fromjson("{a: 1}");
 
327
        ASSERT_OK(LiteParsedQuery::make(ns, 0, 0, 0, fromjson("{}"), fromjson("{}"),
 
328
                                        fromjson("{}"), hint, fromjson("{}"),
 
329
                                        fromjson("{}"),
 
330
                                        false, // snapshot
 
331
                                        false, // explain
 
332
                                        &lpqRaw));
 
333
        auto_ptr<LiteParsedQuery> lpq(lpqRaw);
 
334
 
 
335
        auto_ptr<MatchExpression> me;
 
336
        StatusWithMatchExpression swme(Status::OK());
 
337
 
 
338
        // Invalid: TEXT and {$natural: -1} sort order.
 
339
        swme = parseNormalize("{$text: {$search: 's'}}");
 
340
        ASSERT_OK(swme.getStatus());
 
341
        me.reset(swme.getValue());
 
342
        ASSERT_NOT_OK(CanonicalQuery::isValid(me.get(), *lpq));
 
343
    }
 
344
 
 
345
    TEST(CanonicalQueryTest, IsValidTextAndSnapshot) {
 
346
        // Passes in default values for LiteParsedQuery except for snapshot.
 
347
        // Filter inside LiteParsedQuery is not used.
 
348
        LiteParsedQuery* lpqRaw;
 
349
        bool snapshot = true;
 
350
        ASSERT_OK(LiteParsedQuery::make(ns, 0, 0, 0, fromjson("{}"), fromjson("{}"),
 
351
                                        fromjson("{}"), fromjson("{}"), fromjson("{}"),
 
352
                                        fromjson("{}"),
 
353
                                        snapshot,
 
354
                                        false, // explain
 
355
                                        &lpqRaw));
 
356
        auto_ptr<LiteParsedQuery> lpq(lpqRaw);
 
357
 
 
358
        auto_ptr<MatchExpression> me;
 
359
        StatusWithMatchExpression swme(Status::OK());
 
360
 
 
361
        // Invalid: TEXT and snapshot.
 
362
        swme = parseNormalize("{$text: {$search: 's'}}");
 
363
        ASSERT_OK(swme.getStatus());
 
364
        me.reset(swme.getValue());
 
365
        ASSERT_NOT_OK(CanonicalQuery::isValid(me.get(), *lpq));
 
366
    }
 
367
 
 
368
    /**
 
369
     * Utility function to create a CanonicalQuery
 
370
     */
 
371
    CanonicalQuery* canonicalize(const char* queryStr) {
 
372
        BSONObj queryObj = fromjson(queryStr);
 
373
        CanonicalQuery* cq;
 
374
        Status result = CanonicalQuery::canonicalize(ns, queryObj, &cq);
 
375
        ASSERT_OK(result);
 
376
        return cq;
 
377
    }
 
378
 
 
379
    CanonicalQuery* canonicalize(const char* queryStr, const char* sortStr,
 
380
                                 const char* projStr) {
 
381
        BSONObj queryObj = fromjson(queryStr);
 
382
        BSONObj sortObj = fromjson(sortStr);
 
383
        BSONObj projObj = fromjson(projStr);
 
384
        CanonicalQuery* cq;
 
385
        Status result = CanonicalQuery::canonicalize(ns, queryObj, sortObj,
 
386
                                                     projObj,
 
387
                                                     &cq);
 
388
        ASSERT_OK(result);
 
389
        return cq;
 
390
    }
 
391
 
 
392
   /**
 
393
    * Utility function to create MatchExpression
 
394
    */
 
395
    MatchExpression* parseMatchExpression(const BSONObj& obj) {
 
396
        StatusWithMatchExpression status = MatchExpressionParser::parse(obj);
 
397
        if (!status.isOK()) {
 
398
            mongoutils::str::stream ss;
 
399
            ss << "failed to parse query: " << obj.toString()
 
400
               << ". Reason: " << status.toString();
 
401
            FAIL(ss);
 
402
        }
 
403
        MatchExpression* expr(status.getValue());
 
404
        return expr;
 
405
    }
 
406
 
 
407
    void assertEquivalent(const char* queryStr,
 
408
                          const MatchExpression* expected,
 
409
                          const MatchExpression* actual) {
 
410
        if (actual->equivalent(expected)) {
 
411
            return;
 
412
        }
 
413
        mongoutils::str::stream ss;
 
414
        ss << "Match expressions are not equivalent."
 
415
           << "\nOriginal query: " << queryStr
 
416
           << "\nExpected: " << expected->toString()
 
417
           << "\nActual: " << actual->toString();
 
418
        FAIL(ss);
 
419
    }
 
420
 
 
421
    //
 
422
    // Tests for CanonicalQuery::logicalRewrite
 
423
    //
 
424
 
 
425
    // Don't do anything with a double OR.
 
426
    TEST(CanonicalQueryTest, RewriteNoDoubleOr) {
 
427
        string queryStr = "{$or:[{a:1}, {b:1}], $or:[{c:1}, {d:1}], e:1}";
 
428
        BSONObj queryObj = fromjson(queryStr);
 
429
        auto_ptr<MatchExpression> base(parseMatchExpression(queryObj));
 
430
        auto_ptr<MatchExpression> rewrite(CanonicalQuery::logicalRewrite(base->shallowClone()));
 
431
        assertEquivalent(queryStr.c_str(), base.get(), rewrite.get());
 
432
    }
 
433
 
 
434
    // Do something with a single or.
 
435
    TEST(CanonicalQueryTest, RewriteSingleOr) {
 
436
        // Rewrite of this...
 
437
        string queryStr = "{$or:[{a:1}, {b:1}], e:1}";
 
438
        BSONObj queryObj = fromjson(queryStr);
 
439
        auto_ptr<MatchExpression> rewrite(CanonicalQuery::logicalRewrite(parseMatchExpression(queryObj)));
 
440
 
 
441
        // Should look like this.
 
442
        string rewriteStr = "{$or:[{a:1, e:1}, {b:1, e:1}]}";
 
443
        BSONObj rewriteObj = fromjson(rewriteStr);
 
444
        auto_ptr<MatchExpression> base(parseMatchExpression(rewriteObj));
 
445
        assertEquivalent(queryStr.c_str(), base.get(), rewrite.get());
 
446
    }
 
447
 
 
448
    /**
 
449
     * Test function for CanonicalQuery::normalize.
 
450
     */
 
451
    void testNormalizeQuery(const char* queryStr, const char* expectedExprStr) {
 
452
        auto_ptr<CanonicalQuery> cq(canonicalize(queryStr));
 
453
        MatchExpression* me = cq->root();
 
454
        BSONObj expectedExprObj = fromjson(expectedExprStr);
 
455
        auto_ptr<MatchExpression> expectedExpr(parseMatchExpression(expectedExprObj));
 
456
        assertEquivalent(queryStr, expectedExpr.get(), me);
 
457
    }
 
458
 
 
459
    TEST(CanonicalQueryTest, NormalizeQuerySort) {
 
460
        // Field names
 
461
        testNormalizeQuery("{b: 1, a: 1}", "{a: 1, b: 1}");
 
462
        // Operator types
 
463
        testNormalizeQuery("{a: {$gt: 5}, a: {$lt: 10}}}", "{a: {$lt: 10}, a: {$gt: 5}}");
 
464
        // Nested queries
 
465
        testNormalizeQuery("{a: {$elemMatch: {c: 1, b:1}}}",
 
466
                           "{a: {$elemMatch: {b: 1, c:1}}}");
 
467
    }
 
468
 
 
469
    TEST(CanonicalQueryTest, NormalizeQueryTree) {
 
470
        // Single-child $or elimination.
 
471
        testNormalizeQuery("{$or: [{b: 1}]}", "{b: 1}");
 
472
        // Single-child $and elimination.
 
473
        testNormalizeQuery("{$or: [{$and: [{a: 1}]}, {b: 1}]}", "{$or: [{a: 1}, {b: 1}]}");
 
474
        // $or absorbs $or children.
 
475
        testNormalizeQuery("{$or: [{a: 1}, {$or: [{b: 1}, {$or: [{c: 1}]}]}, {d: 1}]}",
 
476
                           "{$or: [{a: 1}, {b: 1}, {c: 1}, {d: 1}]}");
 
477
        // $and absorbs $and children.
 
478
        testNormalizeQuery("{$and: [{$and: [{a: 1}, {b: 1}]}, {c: 1}]}",
 
479
                           "{$and: [{a: 1}, {b: 1}, {c: 1}]}");
 
480
    }
 
481
 
 
482
    /**
 
483
     * Test functions for getPlanCacheKey.
 
484
     * Cache keys are intentionally obfuscated and are meaningful only
 
485
     * within the current lifetime of the server process. Users should treat
 
486
     * plan cache keys as opaque.
 
487
     */
 
488
    void testGetPlanCacheKey(const char* queryStr, const char* sortStr,
 
489
                             const char* projStr,
 
490
                             const char *expectedStr) {
 
491
        auto_ptr<CanonicalQuery> cq(canonicalize(queryStr, sortStr, projStr));
 
492
        const PlanCacheKey& key = cq->getPlanCacheKey();
 
493
        PlanCacheKey expectedKey(expectedStr);
 
494
        if (key == expectedKey) {
 
495
            return;
 
496
        }
 
497
        mongoutils::str::stream ss;
 
498
        ss << "Unexpected plan cache key. Expected: " << expectedKey << ". Actual: " << key
 
499
           << ". Query: " << cq->toString();
 
500
        FAIL(ss);
 
501
    }
 
502
 
 
503
    TEST(PlanCacheTest, GetPlanCacheKey) {
 
504
        // Generated cache keys should be treated as opaque to the user.
 
505
 
 
506
        // No sorts
 
507
        testGetPlanCacheKey("{}", "{}", "{}", "an");
 
508
        testGetPlanCacheKey("{$or: [{a: 1}, {b: 2}]}", "{}", "{}", "or[eqa,eqb]");
 
509
        testGetPlanCacheKey("{$or: [{a: 1}, {b: 1}, {c: 1}], d: 1}", "{}", "{}",
 
510
                            "an[or[eqa,eqb,eqc],eqd]");
 
511
        testGetPlanCacheKey("{$or: [{a: 1}, {b: 1}], c: 1, d: 1}", "{}", "{}",
 
512
                            "an[or[eqa,eqb],eqc,eqd]");
 
513
        testGetPlanCacheKey("{a: 1, b: 1, c: 1}", "{}", "{}", "an[eqa,eqb,eqc]");
 
514
        testGetPlanCacheKey("{a: 1, beqc: 1}", "{}", "{}", "an[eqa,eqbeqc]");
 
515
        testGetPlanCacheKey("{ap1a: 1}", "{}", "{}", "eqap1a");
 
516
        testGetPlanCacheKey("{aab: 1}", "{}", "{}", "eqaab");
 
517
 
 
518
        // With sort
 
519
        testGetPlanCacheKey("{}", "{a: 1}", "{}", "an~aa");
 
520
        testGetPlanCacheKey("{}", "{a: -1}", "{}", "an~da");
 
521
        testGetPlanCacheKey("{}", "{a: {$meta: 'textScore'}}", "{a: {$meta: 'textScore'}}",
 
522
                            "an~ta|{ $meta: \"textScore\" }a");
 
523
        testGetPlanCacheKey("{a: 1}", "{b: 1}", "{}", "eqa~ab");
 
524
 
 
525
        // With projection
 
526
        testGetPlanCacheKey("{}", "{}", "{a: 1}", "an|1a");
 
527
        testGetPlanCacheKey("{}", "{}", "{a: 0}", "an|0a");
 
528
        testGetPlanCacheKey("{}", "{}", "{a: 99}", "an|99a");
 
529
        testGetPlanCacheKey("{}", "{}", "{a: 'foo'}", "an|\"foo\"a");
 
530
        testGetPlanCacheKey("{}", "{}", "{a: {$slice: [3, 5]}}", "an|{ $slice: \\[ 3\\, 5 \\] }a");
 
531
        testGetPlanCacheKey("{}", "{}", "{a: {$elemMatch: {x: 2}}}",
 
532
                            "an|{ $elemMatch: { x: 2 } }a");
 
533
        testGetPlanCacheKey("{a: 1}", "{}", "{'a.$': 1}", "eqa|1a.$");
 
534
        testGetPlanCacheKey("{a: 1}", "{}", "{a: 1}", "eqa|1a");
 
535
 
 
536
        // Projection should be order-insensitive
 
537
        testGetPlanCacheKey("{}", "{}", "{a: 1, b: 1}", "an|1a1b");
 
538
        testGetPlanCacheKey("{}", "{}", "{b: 1, a: 1}", "an|1a1b");
 
539
 
 
540
        // With or-elimination and projection
 
541
        testGetPlanCacheKey("{$or: [{a: 1}]}", "{}", "{_id: 0, a: 1}", "eqa|0_id1a");
 
542
        testGetPlanCacheKey("{$or: [{a: 1}]}", "{}", "{'a.$': 1}", "eqa|1a.$");
 
543
    }
 
544
 
 
545
    // Delimiters found in user field names or non-standard projection field values
 
546
    // must be escaped.
 
547
    TEST(PlanCacheTest, GetPlanCacheKeyEscaped) {
 
548
        // Field name in query.
 
549
        testGetPlanCacheKey("{'a,[]~|': 1}", "{}", "{}", "eqa\\,\\[\\]\\~\\|");
 
550
 
 
551
        // Field name in sort.
 
552
        testGetPlanCacheKey("{}", "{'a,[]~|': 1}", "{}", "an~aa\\,\\[\\]\\~\\|");
 
553
 
 
554
        // Field name in projection.
 
555
        testGetPlanCacheKey("{}", "{}", "{'a,[]~|': 1}", "an|1a\\,\\[\\]\\~\\|");
 
556
 
 
557
        // Value in projection.
 
558
        testGetPlanCacheKey("{}", "{}", "{a: 'foo,[]~|'}", "an|\"foo\\,\\[\\]\\~\\|\"a");
 
559
    }
 
560
 
 
561
    // Cache keys for $geoWithin queries with legacy and GeoJSON coordinates should
 
562
    // not be the same.
 
563
    TEST(PlanCacheTest, GetPlanCacheKeyGeoWithin) {
 
564
        // Legacy coordinates.
 
565
        auto_ptr<CanonicalQuery> cqLegacy(canonicalize("{a: {$geoWithin: "
 
566
                                                       "{$box: [[-180, -90], [180, 90]]}}}"));
 
567
        // GeoJSON coordinates.
 
568
        auto_ptr<CanonicalQuery> cqNew(canonicalize("{a: {$geoWithin: "
 
569
                                                    "{$geometry: {type: 'Polygon', coordinates: "
 
570
                                                    "[[[0, 0], [0, 90], [90, 0], [0, 0]]]}}}}"));
 
571
        ASSERT_NOT_EQUALS(cqLegacy->getPlanCacheKey(), cqNew->getPlanCacheKey());
 
572
    }
 
573
 
 
574
    // GEO_NEAR cache keys should include information on geometry and CRS in addition
 
575
    // to the match type and field name.
 
576
    TEST(PlanCacheTest, GetPlanCacheKeyGeoNear) {
 
577
        testGetPlanCacheKey("{a: {$near: [0,0], $maxDistance:0.3 }}", "{}", "{}",
 
578
                            "gnanrfl");
 
579
        testGetPlanCacheKey("{a: {$nearSphere: [0,0], $maxDistance: 0.31 }}", "{}", "{}",
 
580
                            "gnansfl");
 
581
        testGetPlanCacheKey("{a: {$geoNear: {$geometry: {type: 'Point', coordinates: [0,0]},"
 
582
                            "$maxDistance:100}}}", "{}", "{}",
 
583
                            "gnanrsp");
 
584
    }
 
585
 
 
586
}