summaryrefslogtreecommitdiff
path: root/jstests/aggregation/bugs/server11675.js
blob: 513714f98d746d8cecbb3eb3ab519c7be1dcb468 (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
// SERVER-11675 Text search integration with aggregation
load('jstests/aggregation/extras/utils.js');

var server11675 = function() {
    var t = db.server11675;
    t.drop();

    if (typeof(RUNNING_IN_SHARDED_AGG_TEST) != 'undefined') { // see end of testshard1.js
        db.adminCommand( { shardcollection : t.getFullName(), key : { "_id" : 1 } } );
    }

    t.insert({_id: 1, text: "apple", words: 1});
    t.insert({_id: 2, text: "banana", words: 1});
    t.insert({_id: 3, text: "apple banana", words: 2});
    t.insert({_id: 4, text: "cantaloupe", words: 1});

    t.ensureIndex({text: "text"});

    // query should have subfields query, project, sort, skip and limit. All but query are optional.
    var assertSameAsFind = function(query) {
        var cursor = t.find(query.query);
        var pipeline = [{$match: query.query}];

        if ('project' in query) {
            cursor = t.find(query.query, query.project); // no way to add to constructed cursor
            pipeline.push({$project: query.project});
        }

        if ('sort' in query) {
            cursor = cursor.sort(query.sort);
            pipeline.push({$sort: query.sort});
        }

        if ('skip' in query) {
            cursor = cursor.skip(query.skip);
            pipeline.push({$skip: query.skip});
        }

        if ('limit' in query) {
            cursor = cursor.limit(query.limit);
            pipeline.push({$limit: query.limit});
        }

        var findRes = cursor.toArray();
        var aggRes = t.aggregate(pipeline).toArray();
        assert.docEq(aggRes, findRes);
    };

    assertSameAsFind({query: {}}); // sanity check
    assertSameAsFind({query: {$text:{$search:"apple"}}});
    assertSameAsFind({query: {$and:[{$text:{$search:"apple"}}, {_id:1}]}});
    assertSameAsFind({query: {$text:{$search:"apple"}}
                     ,project: {_id:1, score: {$meta: "textScore"}}
                     });
    assertSameAsFind({query: {$text:{$search:"apple banana"}}
                     ,project: {_id:1, score: {$meta: "textScore"}}
                     });
    assertSameAsFind({query: {$text:{$search:"apple banana"}}
                     ,project: {_id:1, score: {$meta: "textScore"}}
                     ,sort: {score: {$meta: "textScore"}}
                     });
    assertSameAsFind({query: {$text:{$search:"apple banana"}}
                     ,project: {_id:1, score: {$meta: "textScore"}}
                     ,sort: {score: {$meta: "textScore"}}
                     ,limit: 1
                     });
    assertSameAsFind({query: {$text:{$search:"apple banana"}}
                     ,project: {_id:1, score: {$meta: "textScore"}}
                     ,sort: {score: {$meta: "textScore"}}
                     ,skip: 1
                     });
    assertSameAsFind({query: {$text:{$search:"apple banana"}}
                     ,project: {_id:1, score: {$meta: "textScore"}}
                     ,sort: {score: {$meta: "textScore"}}
                     ,skip: 1
                     ,limit: 1
                     });

    // sharded find requires projecting the score to sort, but sharded agg does not.
    var findRes = t.find({$text: {$search: "apple banana"}}, {textScore: {$meta: 'textScore'}})
                   .sort({textScore: {$meta: 'textScore'}})
                   .map(function(obj) {
                       delete obj.textScore; // remove it to match agg output
                       return obj;
                   });
    var res = t.aggregate([{$match: {$text: {$search: 'apple banana'}}}
                          ,{$sort: {textScore: {$meta: 'textScore'}}}
                          ]).toArray();
    assert.eq(res, findRes);

    // Make sure {$meta: 'textScore'} can be used as a sub-expression
    var res = t.aggregate([{$match: {_id:1, $text: {$search: 'apple'}}}
                          ,{$project: {words: 1
                                      ,score: {$meta: 'textScore'}
                                      ,wordsTimesScore: {$multiply: ['$words', {$meta:'textScore'}]}
                                      }}
                          ]).toArray();
    assert.eq(res[0].wordsTimesScore, res[0].words * res[0].score, tojson(res));

    // And can be used in $group
    var res = t.aggregate([{$match: {_id: 1, $text: {$search: 'apple banana'}}}
                          ,{$group: {_id: {$meta: 'textScore'}
                                    ,score: {$first: {$meta: 'textScore'}}
                                    }}
                          ]).toArray();
    assert.eq(res[0]._id, res[0].score, tojson(res));

    // Make sure metadata crosses shard -> merger boundary
    var res = t.aggregate([{$match: {_id:1, $text: {$search: 'apple'}}}
                          ,{$project: {scoreOnShard: {$meta: 'textScore'} }}
                          ,{$limit:1} // force a split. later stages run on merger
                          ,{$project: {scoreOnShard:1
                                      ,scoreOnMerger: {$meta: 'textScore'} }}
                          ]).toArray();
    assert.eq(res[0].scoreOnMerger, res[0].scoreOnShard);
    var score = res[0].scoreOnMerger; // save for later tests

    // Make sure metadata crosses shard -> merger boundary even if not used on shard
    var res = t.aggregate([{$match: {_id:1, $text: {$search: 'apple'}}}
                          ,{$limit:1} // force a split. later stages run on merger
                          ,{$project: {scoreOnShard:1
                                      ,scoreOnMerger: {$meta: 'textScore'} }}
                          ]).toArray();
    assert.eq(res[0].scoreOnMerger, score);

    // Make sure metadata works if first $project doesn't use it.
    var res = t.aggregate([{$match: {_id:1, $text: {$search: 'apple'}}}
                          ,{$project: {_id:1}}
                          ,{$project: {_id:1
                                      ,score: {$meta: 'textScore'} }}
                          ]).toArray();
    assert.eq(res[0].score, score);

    // Make sure the metadata is 'missing()' when it doesn't exist because it was never created
    var res = t.aggregate([{$project: {_id: 1, score: {$meta: 'textScore'}}}]).toArray();
    assert(!("score" in res[0]));

    // Make sure the metadata is 'missing()' when it doesn't exist because the document changed
    var res = t.aggregate([{$match: {_id: 1, $text: {$search: 'apple banana'}}},
                           {$group: {_id: 1, score: {$first: {$meta: 'textScore'}}}},
                           {$project: {_id: 1, scoreAgain: {$meta: 'textScore'}}},
                          ]).toArray();
    assert(!("scoreAgain" in res[0]));

    // Make sure metadata works after a $unwind
    t.insert({_id: 5, text: 'mango', words: [1, 2, 3]});
    var res = t.aggregate([{$match: {$text: {$search: 'mango'}}},
                           {$project: {score: {$meta: "textScore"}, _id:1, words: 1}},
                           {$unwind: '$words'},
                           {$project: {scoreAgain: {$meta: "textScore"}, score: 1}}
                       ]).toArray();
    assert.eq(res[0].scoreAgain, res[0].score);

    // Error checking
    // $match, but wrong position
    assertErrorCode(t, [{$sort: {text: 1}} ,{$match: {$text: {$search: 'apple banana'}}}], 17313);

    // wrong $stage, but correct position
    assertErrorCode(t, [{$project: {searchValue: {$text: {$search: 'apple banana'}}}}], 15999);
    assertErrorCode(t, [{$sort: {$text: {$search: 'apple banana'}}}], 17312);
};
server11675();