How to do text search from mongodb in meteor?

In mongodb I can do this :

db.getCollection('keywords').find(
   { $text: { $search: "I have headache" } },
   { score: { $meta: "textScore" } }
).sort( { score: { $meta: "textScore" } } )

result

{
    "_id" : "ux2dpeYKQCQRgdbzB",
    "keyword" : "Headache",
    "createdAt" : ISODate("2020-02-10T01:45:07.670Z"),
    "createdBy" : "a5oNybXwHKB8DkxdB",
    "score" : 1.33333333333333
}

and I want do that in meteor :

Keywords.find(
                { $text: { $search: keyword } },
                { score: { $meta: "textScore" }, sort: { score: { $meta: "textScore" } } }
            ).fetch()

but it get error :

MongoError: must have $meta projection for all $meta sort keys

I tried like this without sort

Keywords.find(
                { $text: { $search: keyword } },
                { score: { $meta: "textScore" } }
            ).fetch()

but I dont get score field in this document :

{
    "_id" : "ux2dpeYKQCQRgdbzB",
    "keyword" : "Headache",
    "createdAt" : ISODate("2020-02-10T01:45:07.670Z"),
    "createdBy" : "a5oNybXwHKB8DkxdB"
}

how to solve this text search in meteor and sort by score?

thanks

I would try using the raw Mongo collection with:

Keywords.rawCollection().find(

Note that this will be properly async and will only work on the server side

I do it on server side. and got error :

Keywords.rawCollection(...).find(...).fetch is not a function

I tried

Keywords.rawCollection(...).find(...)

still without score

{
    "_id" : "ux2dpeYKQCQRgdbzB",
    "keyword" : "Headache",
    "createdAt" : ISODate("2020-02-10T01:45:07.670Z"),
    "createdBy" : "a5oNybXwHKB8DkxdB"
}

I checked quickly with meteor shell in my own project and it looks like the Node driver for Mongodb
doesn’t support the simplified projection syntax anymore, so you have to provide a projection key to options:

> Promise.await(Pages.rawCollection().find({ $text: {$search: "test"} }, {projection: {score: {$meta: "textScore"}}}).toArray())
[
  {
    _id: 'RAdodezebKj6jZmsH',
    modules: [ 'AXTDHndWaLQjxsCpd' ],
    name: 'test page name',
    navbar: { enabled: true },
    createdAt: 2020-01-09T02:59:30.688Z,
    createdBy: '0',
    updatedAt: 2020-01-09T02:59:30.764Z,
    updatedBy: '0',
    score: 0.6666666666666666
  },
  {
    _id: 'ESEESN7KfuezwNKBK',
    modules: [],
    name: 'test page name',
    navbar: { enabled: true },
    createdAt: 2020-01-09T02:59:30.689Z,
    createdBy: '0',
    score: 0.6666666666666666
  }
]

Note the use of await and toArray instead of fetch because we’re talking to the raw mongodb driver, so the syntax is different.

It also seems to work with Meteor collections, but they don’t understand projection, so we use the depreciated fields key:

> Pages.find({ $text: {$search: "test"} }, {fields: {score: {$meta: "textScore"}}, sort: {score:{$meta:"textScore"}}}).fetch()
[
  {
    _id: 'RAdodezebKj6jZmsH',
    modules: [ 'AXTDHndWaLQjxsCpd' ],
    name: 'test page name',
    navbar: { enabled: true },
    createdAt: 2020-01-09T02:59:30.688Z,
    createdBy: '0',
    updatedAt: 2020-01-09T02:59:30.764Z,
    updatedBy: '0',
    score: 0.6666666666666666
  },
  {
    _id: 'ESEESN7KfuezwNKBK',
    modules: [],
    name: 'test page name',
    navbar: { enabled: true },
    createdAt: 2020-01-09T02:59:30.689Z,
    createdBy: '0',
    score: 0.6666666666666666
  }
]
3 Likes

This is what I did:

I believe the only way to do this is with sever-side methods.

Meteor.methods({
  'my.search'({ term }) {
    const user = Meteor.user()
    const userFilter = user ? { ownerId: user._id } : {}
    if (!user) {
      return []
    }

    return new Promise((resolve, reject) => {
      MyCollection.rawCollection().find(
        {
          ...userFilter,
          $text: { $search: term },
        },
        (err, cursor) => {
          if (err) {
            resolve([err])
          } else {
            cursor.toArray().then(
              (items) => {
                resolve([null, items])
              },
              () => {
                reject()
              }
            )
          }
        }
      )
    })
  },
})

Also be sure to have a text index.

MyCollection._ensureIndex({ '<text-field-name>': 'text' }) // in Meteor.startup

Also there’s the text search aggregation pipeline

const a = MyCollection.rawCollection().aggregate([

  {

    $match: { $text: { $search: 'sup' } },

  },

])

a.toArray((err, docs) => {

  console.log('it is', err, docs)

})

Regular text search is possible without the use of rawCollection. A text index is required though and the necessary $ operators are not able to be used on the client so you can either use a method to retrieve data, or you only use these operators in the publish function, and leave them out of the query on the client.

2 Likes

Check the post above by @coagmano .

Pages.find(
  { $text: { $search: "test" } },
  {
    fields: { score: { $meta: "textScore" } },
    sort: { score:{ $meta:"textScore" } }
  }
).fetch()