Error using $text in Mongo find()

Trying to do a full text search from a single collection, was hoping I wouldn’t need a package for this.

I tried, just for example: Products.find({ $text: { $search: “Silver” } }).count();

I get “Error: Unrecognized logical operator: $text”

Am I using this wrong, or is this not supported?

Meteor 1.0.4.1, Mongo 2.6.7

Thanks

Have you created a text index? This may be helpful: http://www.tutorialspoint.com/mongodb/mongodb_text_search.htm

That definitely did just help, I can do it inside the Mongo shell no problem now. Still no luck through minimongo, or on the client side. I put it into a Meteor method and it does seem to work there. I am sort of a novice, is there an easier way to do a full text partial string search, or will a meteor method be ok for this?

Thanks for the help!

Minimongo does not support all the MongoDB operators. I checked for $text as an exclusion, but it wasn’t mentioned (but as it was only supported in MongoDB 2.6, that’s not surprising). Short of looking through the source, it’s probably safe to assume from your experiments that it’s not in minimongo.

So, I guess you’ll just have to use a method, unless you wnat to “do it yourself” on the client collection. If the number of documents is large, it’s definitely better on the server, though.

I spent a while fighting with this, figuring out how to get it to work properly, so I wrote up a guide for anyone else who tries to implement it: http://www.okgrow.com/posts/2015/04/27/guide-to-full-text-search-in-meteor/

2 Likes

Have there been any developments / updates for using full text search? Or is this write up still the best way to incorporate it?

I encountered same problem while using MeteorObservable with Angular Meteor. My solution bellow

On client

this.manifestSub = MeteorObservable.subscribe('manifestSearch', searchText).subscribe(() => {
                MeteorObservable.autorun().subscribe(() => {
                    this.zone.run(() => {
                        this.manifestList = Manifests.find({}).fetch();
                    });
                });
            });

On server

Meteor.publish('manifestSearch', (searchText: string) => {
    return Manifests.find({ $text: { $search: searchText } }, { fields: { _id: 1, hash: 1, title: 1, description: 1, tags: 1, time: 1 } });
})

It did full text search for me! :heart_eyes:

@0mkar and @u2622, you are both talking aboujt something entirely else. What we talk about is support for doing text searches on the client. You are doing the actual searching exclusively on the server.

The reason one would do it on the client as well is to avoid errors. For instance, if you have another publication pushing elements to the same collection, then you would get a lot more objects with your non-exclusive find on the client side - essentially showing too much.

You can also see how not filtering will bring weird results by checking out this gist.

There isn’t any $text support in Minimongo, which means you either have to use Meteor.methods() or be creative :smile: I managed to hack around the problem by using a second publication that I store the ids in:

Server side

export const AuditLog = new Mongo.Collection('audit_log');
AuditLog.attachSchema(AuditLogSchema);
AuditLog.ResultIds = new Mongo.Collection('audit_log_results'); // stores results from $text queries

Meteor.publish('auditLog', function(findQuery, options){
    const logResults = AuditLog.find(findQuery, options);

    // This is a hack to work around the lack of $text support on the client side Minimongo lib
    // without which we would have trouble recreating the search results on the client
    const key = userId + JSON.stringify(queryOptions); // key that is unique per user and query
    const resultIds = logResults.map(e => e._id);
    AuditLog.ResultIds.upsert(key, {results: resultIds});


    // publish the results to the client side
    return [
        logResults,
        AuditLog.ResultIds.find(key) // ensures the client side can get the indices in the right sort order
    ];
}

Client side - in the subscription for ‘auditLog’

// We cannot re-use the query as MiniMongo does not support the $text operator!
// Instead of resorting to a Meteor method we can hack around it by relying on an extra
// ad-hoc collection containing the sorted ids ...
const key = Meteor.userId() + JSON.stringify(queryArgs);
const result = AuditLog.ResultIds.findOne(key);
const idsInSortOrder = result.results;
const logsInSortedOrder = idsInSortOrder.map(id => AuditLog.findOne(id));

While there is no $text search in minimongo, you could work around it with a regex hack:

var getDocs = function (search) {
     search = search.replace(/\W/g, "");
     var searchRegex = new RegExp(Search, "i");
     return Docs.find({field: searchRegex}).fetch()
}

getDocs('mete')

What about this RegEx @msavin ?

function ( term ) {
  term = term.replace( /[-\/\\^$*+?.()|[\]{}]/g, '\\$&' ); // escape string
  var reg = new RegExp( '/.*' + term + '.*/', 'i' );
  ...
}

Do they do the same thing ?