summaryrefslogtreecommitdiff
path: root/jstests/core/fts_projection.js
blob: 3d08cb953f055628cb316cab157fa0ae7a1b1350 (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
// Test $text with $textScore projection.
(function() {
"use strict";

load("jstests/libs/analyze_plan.js");

var t = db.getSiblingDB("test").getCollection("fts_projection");
t.drop();

assert.commandWorked(t.insert({_id: 0, a: "textual content"}));
assert.commandWorked(t.insert({_id: 1, a: "additional content", b: -1}));
assert.commandWorked(t.insert({_id: 2, a: "irrelevant content"}));
assert.commandWorked(t.createIndex({a: "text"}));

// Project the text score.
var results = t.find({$text: {$search: "textual content -irrelevant"}}, {
                   score: {$meta: "textScore"}
               }).toArray();
// printjson(results);
// Scores should exist.
assert.eq(results.length, 2);
assert(results[0].score);
assert(results[1].score);

// indexed by _id.
var scores = [0, 0, 0];
scores[results[0]._id] = results[0].score;
scores[results[1]._id] = results[1].score;

//
// Edge/error cases:
//

// Project text score into 3 fields, one nested.
results = t.find({$text: {$search: "textual content -irrelevant"}}, {
               otherScore: {$meta: "textScore"},
               score: {$meta: "textScore"},
               "nestedObj.score": {$meta: "textScore"}
           }).toArray();
assert.eq(2, results.length);
for (var i = 0; i < results.length; ++i) {
    assert.close(scores[results[i]._id], results[i].score);
    assert.close(scores[results[i]._id], results[i].otherScore);
    assert.close(scores[results[i]._id], results[i].nestedObj.score);
}

// printjson(results);

// Project text score into "x.$" shouldn't crash
assert.throws(function() {
    t.find({$text: {$search: "textual content -irrelevant"}}, {
         'x.$': {$meta: "textScore"}
     }).toArray();
});

// TODO: We can't project 'x.y':1 and 'x':1 (yet).

// Clobber an existing field and behave nicely.
results =
    t.find({$text: {$search: "textual content -irrelevant"}}, {b: {$meta: "textScore"}}).toArray();
assert.eq(2, results.length);
for (var i = 0; i < results.length; ++i) {
    assert.close(
        scores[results[i]._id],
        results[i].b,
        i + ': existing field in ' + tojson(results[i], '', true) + ' is not clobbered with score');
}

assert.neq(-1, results[0].b);

// SERVER-12173
// When $text operator is in $or, should evaluate first
results = t.find({$or: [{$text: {$search: "textual content -irrelevant"}}, {_id: 1}]}, {
               score: {$meta: "textScore"}
           }).toArray();
printjson(results);
assert.eq(2, results.length);
for (var i = 0; i < results.length; ++i) {
    assert.close(scores[results[i]._id],
                 results[i].score,
                 i + ': TEXT under OR invalid score: ' + tojson(results[i], '', true));
}

// SERVER-12592
// When $text operator is in $or, all non-$text children must be indexed. Otherwise, we should
// produce
// a readable error.
var errorMessage = '';
assert.throws(function() {
    try {
        t.find({$or: [{$text: {$search: "textual content -irrelevant"}}, {b: 1}]}).itcount();
    } catch (e) {
        errorMessage = e;
        throw e;
    }
}, [], 'Expected error from failed TEXT under OR planning');
assert.neq(-1,
           errorMessage.message.indexOf('TEXT'),
           'message from failed text planning does not mention TEXT: ' + errorMessage);
assert.neq(-1,
           errorMessage.message.indexOf('OR'),
           'message from failed text planning does not mention OR: ' + errorMessage);

// SERVER-26833
// We should use the blocking "TEXT_OR" stage only if the projection calls for the "textScore"
// value.
let explainOutput = t.find({$text: {$search: "textual content -irrelevant"}}, {
                         score: {$meta: "textScore"}
                     }).explain();
assert(planHasStage(db, explainOutput.queryPlanner.winningPlan, "TEXT_OR"));

explainOutput = t.find({$text: {$search: "textual content -irrelevant"}}).explain();
assert(!planHasStage(db, explainOutput.queryPlanner.winningPlan, "TEXT_OR"));

// Scores should exist.
assert.eq(results.length, 2);
assert(results[0].score,
       "invalid text score for " + tojson(results[0], '', true) + " when $text is in $or");
assert(results[1].score,
       "invalid text score for " + tojson(results[0], '', true) + " when $text is in $or");
})();