是否可以在 Meteor 1.7+ 中订阅文本搜索光标

Is it possible to subscribe to a text search cursor in Meteor 1.7+

以前,我曾经写过

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

但是,现在我得到一个错误

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

当我尝试查找更多信息时, 提到 fields 参数需要包含在投影中,例如

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

但是,Meteor 没有 .project 方法。

有什么解决办法?

下面有一个创建的迷你复制指南。它显示了如何执行(索引)文本搜索作为您最初报告抛出错误的搜索。

因此可以假设错误的来源,例如在迁移到 Meteor 1.7+ / Mongo 3.6+ 或代码中。迁移很有可能包括原因,因为最近在论坛和 SO 上有很多关于升级到 1.7 的问题的帖子。

所以这里有一个简短的清单,说明可能出了什么问题:

  • 新的 Mongo 版本和 node-Mongo 驱动程序版本正在用于 1.7+ - 更新是否也正确地更新了这些?如果在生产环境中 - 您是否在数据库服务器上更新到相应的 Mongo 版本?
  • 自 Mongo 3.2 开始使用新的 version 3 of text indexing。也许您以前的 Meteor 版本在执行 db.createIndex 时使用了旧的 Mongo 和以前的文本索引。我没有发现任何信息表明它破坏了向后兼容性,但这可能是一个可能的原因。如果你在开发中,你可以通过执行 db.collection.getIndexes() 轻松验证。例如,下面创建的重现项目具有以下输出:

获取当前索引:

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
    }
]
  • 如果原因是索引版本不匹配,您可以 drop the index 并重新创建它以使版本匹配。
  • 如果仍然存在问题而您无法解决,您仍然可以依靠 Mongo.Collection.rawCollection,它允许对集合进行本机操作。请注意关于 integrate native Mongo operations into your Meteor environment
  • 扩展要求的 SO 回答

复制工作文本搜索,包括 $meta "textScore" 分数:

为了验证它是否正在使用新项目(发布 METEOR@1.7.0.5),您可以重现以下步骤:

  1. 创建一个新项目并安装faker(快速创建一些文本):
$ meteor create metasearch
$ cd metasearch
$ meteor npm install --save faker
$ meteor
  1. /imports/Texts.js 中创建名为 Texts 的集合:
import { Mongo } from "meteor/mongo"
export const Texts = new Mongo.Collection('texts')
  1. 将以下服务器代码传递到 /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()
  }
})

如您所见,它使用您最初发布的默认 "query, projection and sort in one document" 结构。

  1. 通过以下方式扩展 /client/main.js 代码:
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. 打开一个新的终端选项卡并打开 mongo shell 并创建一个新的文本索引:
$ meteor mongo
$ db.texts.createIndex({text:"text"})

输出应类似于:

{
    "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. 清除,取消运行流星并重新启动。

与此同时,Meteor.startup 引入的插入应该会创建大量要搜索的文档,但它们可能还没有添加到索引中。

您可以取消 运行 实例并重新启动几次(或增加启动时要插入的文档数)以获得大量匹配项。

  1. 运行客户端并查看订阅

当 运行 默认为 localhost:3000 时,您应该得到类似如下的输出:

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 { … }