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
|
// Test $text with $textScore projection.
// @tags: [
// sbe_incompatible,
// ]
(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");
})();
|