1

Previously, I used to write

MyCollection.find({ $text: { $search: 'foo' } }, {
  fields: {
    score: { $meta: "textScore" }
  },
  sort: {
    score: { $meta: "textScore" }
  }
});

But, now I get an error

I20180926-18:26:08.708(-4)? Exception from sub mysubscription id ZeSWJcoghED3t6Eq6 Error: Exception while polling query {"collectionName":"my-collection","selector":{"$text":{"$search":"foo"}},"options":{"transform":null,"limit":25,"sort":{"score":{"$meta":"textScore"}}}}: must have $meta projection for all $meta sort keys
I20180926-18:26:08.709(-4)?     at PollingObserveDriver._pollMongo (packages/mongo/polling_observe_driver.js:165:11)
I20180926-18:26:08.709(-4)?     at Object.task (packages/mongo/polling_observe_driver.js:93:12)
I20180926-18:26:08.710(-4)?     at Meteor._SynchronousQueue.SQp._run (packages/meteor.js:987:16)
I20180926-18:26:08.710(-4)?     at packages/meteor.js:964:12

As I try to find more information, this answer mentions that the fields parameter needs to be included in a projection, such as

collection.find({
    $text:
      {
        $search: filter,
        $caseSensitive: false,
        $diacriticSensitive: true
      }
    })
    .project({ score: { $meta: "textScore" } })
    .sort({score:{$meta:"textScore"}})

However, Meteor does not have a .project method.

What's the solution?

Yanick Rochon
  • 51,409
  • 25
  • 133
  • 214
  • If you can't get it run with the Meteor Mongo.Collection you can still use [rawCollection](https://docs.meteor.com/api/collections.html#Mongo-Collection-rawCollection) to operate on the native mongo driver level collections. Does that bring you any further? – Jankapunkt Sep 27 '18 at 05:11
  • Another question here: which Meteor version did you use before 1.7? Maybe you need to re-index with a new [index version](https://docs.mongodb.com/manual/core/index-text/)? – Jankapunkt Sep 27 '18 at 06:55
  • I did not upgrade. I had enough wisdom to keep the other project as is, even if many modules are outdated (~2 years). It's an internal project anyway, and it works with extremely minimal supervision. No, this question is about a brand new blank slate project, starting with Meteor 1.7.0.1. I simply opened the old source files from the other project and "borrowed" a few files to realize that many were incompatible with the new Mongo driver. – Yanick Rochon Sep 27 '18 at 17:35

1 Answers1

1

There is a created mini-repro guide below. It shows how to execute a (indexed-)text search as the one you have initially reported to throw an error.

Therefore one can assume the sources of error for example in the migration to Meteor 1.7+ / Mongo 3.6+ or in the code. The migration has high chances of including the cause, as there were recently many posts on issues with upgrading to 1.7 in the forums as well as on SO.

So here is a short checklist on what could have gone wrong:

  • A new Mongo version and node-Mongo driver version is in use for 1.7+ - did the update also update these correct? If on production - did you update to the respective Mongo Version on your db Server?
  • Since Mongo 3.2 there is a new version 3 of text indexing in use. Maybe your previous Meteor version used an older Mongo with former text indexing when executing db.createIndex. I have not found any info that it breaks backwards compatibility but it could be a possible cause. If you are on dev you can easily verify by executing db.collection.getIndexes(). The below created repro project for example has the following output:

Get current indexes:

meteor:PRIMARY> db.texts.getIndexes()
[
    {
        "v" : 2,
        "key" : {
            "_id" : 1
        },
        "name" : "_id_",
        "ns" : "meteor.texts"
    },
    {
        "v" : 2,
        "key" : {
            "_fts" : "text",
            "_ftsx" : 1
        },
        "name" : "text_text",
        "ns" : "meteor.texts",
        "weights" : {
            "text" : 1
        },
        "default_language" : "english",
        "language_override" : "language",
        "textIndexVersion" : 3
    }
]
  • If the cause is due to an index version mismatch, you can drop the index and re-create it so that the versions match.
  • If there is still a problem and you can't figure it out, you can still rely on the Mongo.Collection.rawCollection, that allows native operations on collections. Note this SO answer on extended requirements to integrate native Mongo operations into your Meteor environment

Reproduction of working text search including $meta "textScore" scores:

In order to verify, that it is working with a new project (Release METEOR@1.7.0.5) you can reproduce the following steps:

  1. Create a new project and install faker (to create some texts fast):
$ meteor create metasearch
$ cd metasearch
$ meteor npm install --save faker
$ meteor
  1. Create a collection named Texts in /imports/Texts.js:
import { Mongo } from "meteor/mongo"
export const Texts = new Mongo.Collection('texts')
  1. Past the following server code to /server/main.js:
import { Meteor } from 'meteor/meteor'
import { Texts } from '../imports/Texts'

Meteor.startup(() => {
  import { lorem } from 'faker'
  for (let i = 0; i < 5; i++) {
    Texts.insert({
      title: lorem.words(),
      text: lorem.text()
    })
  }
})

Meteor.publish('search', function searchpub (filter) {
  const cursor = Texts.find({
    $text: {
      $search: filter,
      $caseSensitive: false,
      $diacriticSensitive: false,
    }
  }, {
    fields: {
      score: {$meta: 'textScore'},
    },
    sort: {
      score: {$meta: 'textScore'},
    },
  })

  // fallback if cursor is undefined
  if (cursor && cursor.count && cursor.count() >= 0) {
    return cursor
  } else {
    this.ready()
  }
})

As you can see it uses the default "query, projection and sort in one document" structure, as you initially posted.

  1. Extend the /client/main.js code by the following:
import { Template } from 'meteor/templating';
import { ReactiveVar } from 'meteor/reactive-var';

import './main.html';

import {Texts} from '../imports/Texts'

Template.hello.onCreated(function helloOnCreated() {
  // counter starts at 0
  this.counter = new ReactiveVar(0);

  const instance = this
  instance.autorun(() => {
    const cursor = instance.subscribe('search', 'dolor') // use a word, that often occurs
    if (cursor.ready()) {
      console.log(Texts.find().fetch())
    }
  })
});

// ... rest of the file
  1. Open a new terminal tab and open the mongo shell and create a new text index:
$ meteor mongo
$ db.texts.createIndex({text:"text"})

Output should be similar to:

{
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 1,
    "numIndexesAfter" : 2,
    "ok" : 1,
    "operationTime" : Timestamp(1538031016, 1),
    "$clusterTime" : {
        "clusterTime" : Timestamp(1538031016, 1),
        "signature" : {
            "hash" : BinData(0,"AAAAAAAAAAAAAAAAAAAAAAAAAAA="),
            "keyId" : NumberLong(0)
        }
    }
}
  1. Clean, cancel the running meteor and restart.

In the meantime the Meteor.startup induced inserts should have create a neat amount of documents to search but they may not be added to the index, yet.

You can cancel the running instance and restart again a few times (or increase the number of documents to be inserted on startup) to get a good amount of matches.

  1. Running the client and look at the subs

When running on localhost:3000 per default you should get a similar output like the following:

Array (51) […]
​
0: {…}
_id: "n2WhMskCXBm7ziZea"
score: 1.0416666666666667
text: "Dolor at sed et dolorem tenetur a dolore voluptate incidunt. Rerum corrupti officia aut tenetur nisi officiis voluptas soluta. Fugiat eos sed expedita inventore. Esse cupiditate qui. Facere dolor quisquam ipsa a facere praesentium. Aut sunt mollitia dolore tenetur."
title: "quia est fuga"
<prototype>: Object { … }
​
1: {…}
_id: "QjAcZQLTH8Mc3jDzS"
score: 1.0110294117647058
text: "Sequi dolores omnis sequi consequatur laborum et asperiores. Accusantium repellat magnam est aut suscipit enim iure. Qui qui aut cupiditate necessitatibus commodi qui quia. Ut tempore autem provident maiores cumque necessitatibus dolores accusantium. Nostrum ut ut sunt adipisci qui consequuntur explicabo voluptas. Minima praesentium sunt facere doloribus non at dolor dolore est."
title: "est explicabo omnis"
<prototype>: Object { … }
Jankapunkt
  • 8,128
  • 4
  • 30
  • 59