~evarlast/ubuntu/utopic/mongodb/upstart-workaround-debian-bug-718702

« back to all changes in this revision

Viewing changes to src/mongo/db/fts/fts_spec.cpp

  • Committer: Package Import Robot
  • Author(s): James Page, James Page, Robie Basak
  • Date: 2013-05-29 17:44:42 UTC
  • mfrom: (44.1.7 sid)
  • Revision ID: package-import@ubuntu.com-20130529174442-z0a4qmoww4y0t458
Tags: 1:2.4.3-1ubuntu1
[ James Page ]
* Merge from Debian unstable, remaining changes:
  - Enable SSL support:
    + d/control: Add libssl-dev to BD's.
    + d/rules: Enabled --ssl option.
    + d/mongodb.conf: Add example SSL configuration options.
  - d/mongodb-server.mongodb.upstart: Add upstart configuration.
  - d/rules: Don't strip binaries during scons build for Ubuntu.
  - d/control: Add armhf to target archs.
  - d/p/SConscript.client.patch: fixup install of client libraries.
  - d/p/0010-install-libs-to-usr-lib-not-usr-lib64-Closes-588557.patch:
    Install libraries to lib not lib64.
* Dropped changes:
  - d/p/arm-support.patch: Included in Debian.
  - d/p/double-alignment.patch: Included in Debian.
  - d/rules,control: Debian also builds with avaliable system libraries
    now.
* Fix FTBFS due to gcc and boost upgrades in saucy:
  - d/p/0008-ignore-unused-local-typedefs.patch: Add -Wno-unused-typedefs
    to unbreak building with g++-4.8.
  - d/p/0009-boost-1.53.patch: Fixup signed/unsigned casting issue.

[ Robie Basak ]
* d/p/0011-Use-a-signed-char-to-store-BSONType-enumerations.patch: Fixup
  build failure on ARM due to missing signed'ness of char cast.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// fts_spec.cpp
 
2
 
 
3
/**
 
4
*    Copyright (C) 2012 10gen Inc.
 
5
*
 
6
*    This program is free software: you can redistribute it and/or  modify
 
7
*    it under the terms of the GNU Affero General Public License, version 3,
 
8
*    as published by the Free Software Foundation.
 
9
*
 
10
*    This program is distributed in the hope that it will be useful,
 
11
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
*    GNU Affero General Public License for more details.
 
14
*
 
15
*    You should have received a copy of the GNU Affero General Public License
 
16
*    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
17
*/
 
18
 
 
19
#include "mongo/pch.h"
 
20
 
 
21
#include "mongo/db/fts/fts_spec.h"
 
22
#include "mongo/db/fts/fts_util.h"
 
23
#include "mongo/util/mongoutils/str.h"
 
24
 
 
25
namespace mongo {
 
26
 
 
27
    namespace fts {
 
28
 
 
29
        using namespace mongoutils;
 
30
 
 
31
        const double MAX_WEIGHT = 1000000000.0;
 
32
        const double MAX_WORD_WEIGHT = MAX_WEIGHT / 10000;
 
33
 
 
34
        FTSSpec::FTSSpec( const BSONObj& indexInfo ) {
 
35
            massert( 16739, "found invalid spec for text index",
 
36
                     indexInfo["weights"].isABSONObj() );
 
37
 
 
38
            _defaultLanguage = indexInfo["default_language"].valuestrsafe();
 
39
            _languageOverrideField = indexInfo["language_override"].valuestrsafe();
 
40
 
 
41
            if ( _defaultLanguage.size() == 0 )
 
42
                _defaultLanguage = "english";
 
43
            if ( _languageOverrideField.size() == 0 )
 
44
                _languageOverrideField = "language";
 
45
 
 
46
            _wildcard = false;
 
47
 
 
48
            // in this block we fill in the _weights map
 
49
            {
 
50
                BSONObjIterator i( indexInfo["weights"].Obj() );
 
51
                while ( i.more() ) {
 
52
                    BSONElement e = i.next();
 
53
                    verify( e.isNumber() );
 
54
 
 
55
                    if ( WILDCARD == e.fieldName() ) {
 
56
                        _wildcard = true;
 
57
                    }
 
58
                    else {
 
59
                        double num = e.number();
 
60
                        _weights[ e.fieldName() ] = num;
 
61
                        verify( num > 0 && num < MAX_WORD_WEIGHT );
 
62
                    }
 
63
                }
 
64
                verify( _wildcard || _weights.size() );
 
65
            }
 
66
 
 
67
            // extra information
 
68
            {
 
69
                BSONObj keyPattern = indexInfo["key"].Obj();
 
70
                verify( keyPattern.nFields() >= 2 );
 
71
                BSONObjIterator i( keyPattern );
 
72
 
 
73
                bool passedFTS = false;
 
74
 
 
75
                while ( i.more() ) {
 
76
                    BSONElement e = i.next();
 
77
                    if ( str::equals( e.fieldName(), "_fts" ) ||
 
78
                         str::equals( e.fieldName(), "_ftsx" ) ) {
 
79
                        passedFTS = true;
 
80
                        continue;
 
81
                    }
 
82
 
 
83
                    if ( passedFTS )
 
84
                        _extraAfter.push_back( e.fieldName() );
 
85
                    else
 
86
                        _extraBefore.push_back( e.fieldName() );
 
87
                }
 
88
 
 
89
            }
 
90
        }
 
91
 
 
92
        bool FTSSpec::weight( const StringData& field, double* out ) const {
 
93
            Weights::const_iterator i = _weights.find( field.toString() );
 
94
            if ( i == _weights.end() )
 
95
                return false;
 
96
            *out = i->second;
 
97
            return true;
 
98
        }
 
99
 
 
100
        string FTSSpec::getLanguageToUse( const BSONObj& userDoc ) const {
 
101
            BSONElement e = userDoc[_languageOverrideField];
 
102
            if ( e.type() == String ) {
 
103
                const char * x = e.valuestrsafe();
 
104
                if ( strlen( x ) > 0 )
 
105
                    return x;
 
106
            }
 
107
            return _defaultLanguage;
 
108
        }
 
109
 
 
110
 
 
111
        /*
 
112
         * Calculates the score for all terms in a document of a collection
 
113
         * @param obj, the document in the collection being parsed
 
114
         * @param term_freqs, map<string,double> to fill up
 
115
         */
 
116
        void FTSSpec::scoreDocument( const BSONObj& obj, TermFrequencyMap* term_freqs ) const {
 
117
 
 
118
            string language = getLanguageToUse( obj );
 
119
 
 
120
            Stemmer stemmer(language);
 
121
            Tools tools(language);
 
122
            tools.stemmer = &stemmer;
 
123
            tools.stopwords = StopWords::getStopWords( language );
 
124
 
 
125
            if ( wildcard() ) {
 
126
                // if * is specified for weight, we can recurse over all fields.
 
127
                _scoreRecurse(tools, obj, term_freqs);
 
128
                return;
 
129
            }
 
130
 
 
131
            // otherwise, we need to remember the different weights for each field
 
132
            // and act accordingly (in other words, call _score)
 
133
            for ( Weights::const_iterator i = _weights.begin(); i != _weights.end(); i++ ) {
 
134
                const char * leftOverName = i->first.c_str();
 
135
                // name of field
 
136
                BSONElement e = obj.getFieldDottedOrArray(leftOverName);
 
137
                // weight associated to name of field
 
138
                double weight = i->second;
 
139
 
 
140
                if ( e.eoo() ) {
 
141
                    // do nothing
 
142
                }
 
143
                else if ( e.type() == Array ) {
 
144
                    BSONObjIterator j( e.Obj() );
 
145
                    while ( j.more() ) {
 
146
                        BSONElement x = j.next();
 
147
                        if ( leftOverName[0] && x.isABSONObj() )
 
148
                            x = x.Obj().getFieldDotted( leftOverName );
 
149
                        if ( x.type() == String )
 
150
                            _scoreString( tools, x.valuestr(), term_freqs, weight );
 
151
                    }
 
152
                }
 
153
                else if ( e.type() == String ) {
 
154
                    _scoreString( tools, e.valuestr(), term_freqs, weight );
 
155
                }
 
156
 
 
157
            }
 
158
        }
 
159
 
 
160
 
 
161
        /*
 
162
         * Recurses over all fields of an obj (document in collection)
 
163
         *    and fills term,score map term_freqs
 
164
         * @param tokenizer, tokenizer to tokenize a string into terms
 
165
         * @param obj, object being parsed
 
166
         * term_freqs, map <term,score> to be filled up
 
167
         */
 
168
        void FTSSpec::_scoreRecurse(const Tools& tools,
 
169
                                    const BSONObj& obj,
 
170
                                    TermFrequencyMap* term_freqs ) const {
 
171
            BSONObjIterator j( obj );
 
172
            while ( j.more() ) {
 
173
                BSONElement x = j.next();
 
174
 
 
175
                if ( languageOverrideField() == x.fieldName() )
 
176
                    continue;
 
177
 
 
178
                if (x.type() == String) {
 
179
                    double w = 1;
 
180
                    weight( x.fieldName(), &w );
 
181
                    _scoreString(tools, x.valuestr(), term_freqs, w);
 
182
                }
 
183
                else if ( x.isABSONObj() ) {
 
184
                    _scoreRecurse( tools, x.Obj(), term_freqs);
 
185
                }
 
186
 
 
187
            }
 
188
        }
 
189
 
 
190
        namespace {
 
191
            struct ScoreHelperStruct {
 
192
                ScoreHelperStruct()
 
193
                    : freq(0), count(0), exp(0){
 
194
                }
 
195
                double freq;
 
196
                double count;
 
197
                double exp;
 
198
            };
 
199
            typedef unordered_map<string,ScoreHelperStruct> ScoreHelperMap;
 
200
        }
 
201
 
 
202
        void FTSSpec::_scoreString( const Tools& tools,
 
203
                                    const StringData& raw,
 
204
                                    TermFrequencyMap* docScores,
 
205
                                    double weight ) const {
 
206
 
 
207
            ScoreHelperMap terms;
 
208
 
 
209
            unsigned numTokens = 0;
 
210
 
 
211
            Tokenizer i( tools.language, raw );
 
212
            while ( i.more() ) {
 
213
                Token t = i.next();
 
214
                if ( t.type != Token::TEXT )
 
215
                    continue;
 
216
 
 
217
                string term = t.data.toString();
 
218
                makeLower( &term );
 
219
                if ( tools.stopwords->isStopWord( term ) )
 
220
                    continue;
 
221
                term = tools.stemmer->stem( term );
 
222
 
 
223
                ScoreHelperStruct& data = terms[term];
 
224
 
 
225
                if ( data.exp )
 
226
                    data.exp *= 2;
 
227
                else
 
228
                    data.exp = 1;
 
229
                data.count += 1;
 
230
                data.freq += ( 1 / data.exp );
 
231
 
 
232
                numTokens++;
 
233
            }
 
234
 
 
235
            for ( ScoreHelperMap::const_iterator i = terms.begin(); i != terms.end(); ++i ) {
 
236
 
 
237
                const string& term = i->first;
 
238
                const ScoreHelperStruct& data = i->second;
 
239
 
 
240
                // in order to adjust weights as a function of term count as it
 
241
                // relates to total field length. ie. is this the only word or
 
242
                // a frequently occuring term? or does it only show up once in
 
243
                // a long block of text?
 
244
 
 
245
                double coeff = ( 0.5 * data.count / numTokens ) + 0.5;
 
246
 
 
247
                // if term is identical to the raw form of the
 
248
                // field (untokenized) give it a small boost.
 
249
                double adjustment = 1;
 
250
                if ( raw.size() == term.length() && raw.equalCaseInsensitive( term ) )
 
251
                    adjustment += 0.1;
 
252
 
 
253
                double& score = (*docScores)[term];
 
254
                score += ( weight * data.freq * coeff * adjustment );
 
255
                verify( score <= MAX_WEIGHT );
 
256
            }
 
257
        }
 
258
 
 
259
        Status FTSSpec::getIndexPrefix( const BSONObj& query, BSONObj* out ) const {
 
260
            if ( numExtraBefore() == 0 ) {
 
261
                *out = BSONObj();
 
262
                return Status::OK();
 
263
            }
 
264
 
 
265
            BSONObjBuilder b;
 
266
            for ( unsigned i = 0; i < numExtraBefore(); i++ ) {
 
267
                BSONElement e = query.getFieldDotted(extraBefore(i));
 
268
                if ( e.eoo() )
 
269
                    return Status( ErrorCodes::BadValue,
 
270
                                   str::stream()
 
271
                                   << "need have an equality filter on: "
 
272
                                   << extraBefore(i) );
 
273
 
 
274
                if ( e.isABSONObj() && e.Obj().firstElement().getGtLtOp( -1 ) != -1 )
 
275
                    return Status( ErrorCodes::BadValue,
 
276
                                   str::stream()
 
277
                                   << "need have an equality filter on: "
 
278
                                   << extraBefore(i) );
 
279
 
 
280
                b.append( e );
 
281
            }
 
282
            *out = b.obj();
 
283
            return Status::OK();
 
284
        }
 
285
 
 
286
        void _addFTSStuff( BSONObjBuilder* b ) {
 
287
            b->append( "_fts", INDEX_NAME );
 
288
            b->append( "_ftsx", 1 );
 
289
        }
 
290
 
 
291
        BSONObj FTSSpec::fixSpec( const BSONObj& spec ) {
 
292
            map<string,int> m;
 
293
 
 
294
            BSONObj keyPattern;
 
295
            {
 
296
                BSONObjBuilder b;
 
297
                bool addedFtsStuff = false;
 
298
 
 
299
                BSONObjIterator i( spec["key"].Obj() );
 
300
                while ( i.more() ) {
 
301
                    BSONElement e = i.next();
 
302
                    if ( str::equals( e.fieldName(), "_fts" ) ||
 
303
                         str::equals( e.fieldName(), "_ftsx" ) ) {
 
304
                        continue;
 
305
                    }
 
306
                    else if ( e.type() == String &&
 
307
                              ( str::equals( "fts", e.valuestr() ) ||
 
308
                                str::equals( "text", e.valuestr() ) ) ) {
 
309
 
 
310
                        if ( !addedFtsStuff ) {
 
311
                            _addFTSStuff( &b );
 
312
                            addedFtsStuff = true;
 
313
                        }
 
314
 
 
315
                        m[e.fieldName()] = 1;
 
316
                    }
 
317
                    else {
 
318
                        b.append( e );
 
319
                    }
 
320
                }
 
321
 
 
322
                if ( !addedFtsStuff )
 
323
                    _addFTSStuff( &b );
 
324
 
 
325
                keyPattern = b.obj();
 
326
            }
 
327
 
 
328
            if ( spec["weights"].isABSONObj() ) {
 
329
                BSONObjIterator i( spec["weights"].Obj() );
 
330
                while ( i.more() ) {
 
331
                    BSONElement e = i.next();
 
332
                    m[e.fieldName()] = e.numberInt();
 
333
                }
 
334
            }
 
335
            else if ( spec["weights"].str() == WILDCARD ) {
 
336
                m[WILDCARD] = 1;
 
337
            }
 
338
 
 
339
            BSONObj weights;
 
340
            {
 
341
                BSONObjBuilder b;
 
342
                for ( map<string,int>::iterator i = m.begin(); i != m.end(); ++i ) {
 
343
                    uassert( 16674, "score for word too high",
 
344
                             i->second > 0 && i->second < MAX_WORD_WEIGHT );
 
345
                    b.append( i->first, i->second );
 
346
                }
 
347
                weights = b.obj();
 
348
            }
 
349
 
 
350
            string default_language(spec.getStringField("default_language"));
 
351
            if ( default_language.empty() )
 
352
                default_language = "english";
 
353
 
 
354
            string language_override(spec.getStringField("language_override"));
 
355
            if ( language_override.empty() )
 
356
                language_override = "language";
 
357
 
 
358
            int version = -1;
 
359
            int textIndexVersion = 1;
 
360
 
 
361
            BSONObjBuilder b;
 
362
            BSONObjIterator i( spec );
 
363
            while ( i.more() ) {
 
364
                BSONElement e = i.next();
 
365
                if ( str::equals( e.fieldName(), "key" ) ) {
 
366
                    b.append( "key", keyPattern );
 
367
                }
 
368
                else if ( str::equals( e.fieldName(), "weights" ) ) {
 
369
                    b.append( "weights", weights );
 
370
                    weights = BSONObj();
 
371
                }
 
372
                else if ( str::equals( e.fieldName(), "default_language" ) ) {
 
373
                    b.append( "default_language", default_language);
 
374
                    default_language = "";
 
375
                }
 
376
                else if ( str::equals( e.fieldName(), "language_override" ) ) {
 
377
                    b.append( "language_override", language_override);
 
378
                    language_override = "";
 
379
                }
 
380
                else if ( str::equals( e.fieldName(), "v" ) ) {
 
381
                    version = e.numberInt();
 
382
                }
 
383
                else if ( str::equals( e.fieldName(), "textIndexVersion" ) ) {
 
384
                    textIndexVersion = e.numberInt();
 
385
                    uassert( 16730,
 
386
                             str::stream() << "bad textIndexVersion: " << textIndexVersion,
 
387
                             textIndexVersion == 1 );
 
388
                }
 
389
                else {
 
390
                    b.append( e );
 
391
                }
 
392
            }
 
393
 
 
394
            if ( !weights.isEmpty() )
 
395
                b.append( "weights", weights );
 
396
            if ( !default_language.empty() )
 
397
                b.append( "default_language", default_language);
 
398
            if ( !language_override.empty() )
 
399
                b.append( "language_override", language_override);
 
400
 
 
401
            if ( version >= 0 )
 
402
                b.append( "v", version );
 
403
 
 
404
            b.append( "textIndexVersion", textIndexVersion );
 
405
 
 
406
            return b.obj();
 
407
 
 
408
        }
 
409
 
 
410
    }
 
411
}