TEST(FTSQuery, Phrase2) { FTSQuery q; ASSERT( q.parse("doing a \"phrase-test\" for fun", "english", false, TEXT_INDEX_VERSION_2).isOK()); ASSERT_EQUALS(1U, q.getPositivePhr().size()); ASSERT_EQUALS("phrase-test", q.getPositivePhr()[0]); }
TEST( FTSMatcher, Phrase2 ) { FTSQuery q; q.parse( "foo \"table top\"", "english" ); FTSMatcher m( q, FTSSpec( FTSSpec::fixSpec( BSON( "key" << BSON( "x" << "text" ) ) ) ) ); ASSERT( m.phraseMatch( "table top", BSON( "x" << BSON_ARRAY( "table top" ) ) ) ); }
TEST(FTSQuery, CaseSensitiveNegativePhrases) { FTSQuery q; ASSERT( q.parse("doing a -\"Phrase Test\" for fun", "english", true, TEXT_INDEX_VERSION_2).isOK()); ASSERT_EQUALS(0U, q.getPositivePhr().size()); ASSERT_EQUALS(1U, q.getNegatedPhr().size()); ASSERT_EQUALS("Phrase Test", q.getNegatedPhr()[0]); }
// Regression test for SERVER-11994. TEST( FTSMatcher, NegWild2 ) { FTSQuery q; ASSERT_OK( q.parse( "pizza -restaurant", "english", TEXT_INDEX_VERSION_2 ) ); FTSMatcher m( q, FTSSpec( FTSSpec::fixSpec( BSON( "key" << BSON( "$**" << "text" ) ) ) ) ); ASSERT( m.hasNegativeTerm( BSON( "x" << BSON( "y" << "pizza restaurant" ) ) ) ); ASSERT( m.hasNegativeTerm( BSON( "x" << BSON( "y" << "PIZZA RESTAURANT" ) ) ) ); }
TEST( FTSMatcher, NegWild1 ) { FTSQuery q; q.parse( "foo -bar", "english" ); FTSMatcher m( q, FTSSpec( FTSSpec::fixSpec( BSON( "key" << BSON( "$**" << "text" ) ) ) ) ); ASSERT( m.hasNegativeTerm( BSON( "x" << BSON( "y" << "bar" ) ) ) ); ASSERT( m.hasNegativeTerm( BSON( "x" << BSON( "y" << "bar" ) ) ) ); }
// Test that the matcher parses the document with the document language, not the search // language. TEST( FTSMatcher, ParsesUsingDocLanguage ) { FTSQuery q; ASSERT_OK( q.parse( "-glad", "none", TEXT_INDEX_VERSION_2 ) ); FTSMatcher m( q, FTSSpec( FTSSpec::fixSpec( BSON( "key" << BSON( "x" << "text" ) ) ) ) ); // Even though the search language is "none", the document {x: "gladly"} should be // parsed using the English stemmer, and as such should match the negated term "glad". ASSERT( m.hasNegativeTerm( BSON( "x" << "gladly" ) ) ); }
// Test textIndexVersion:1 query with language "invalid". No stemming will be performed, // and no stopword list will be used. TEST(FTSQuery, TextIndexVersion1LanguageInvalid) { FTSQuery q; ASSERT(q.parse("the running", "invalid", false, TEXT_INDEX_VERSION_1).isOK()); ASSERT_EQUALS(2U, q.getPositiveTerms().size()); ASSERT_EQUALS(1, std::count(q.getPositiveTerms().begin(), q.getPositiveTerms().end(), "the")); ASSERT_EQUALS(1, std::count(q.getPositiveTerms().begin(), q.getPositiveTerms().end(), "running")); ASSERT_EQUALS(0U, q.getNegatedTerms().size()); ASSERT_EQUALS(0U, q.getPositivePhr().size()); ASSERT_EQUALS(0U, q.getNegatedPhr().size()); }
TEST(FTSQuery, Basic1) { FTSQuery q; ASSERT(q.parse("this is fun", "english", false, TEXT_INDEX_VERSION_2).isOK()); ASSERT_EQUALS(false, q.getCaseSensitive()); ASSERT_EQUALS(1U, q.getPositiveTerms().size()); ASSERT_EQUALS("fun", *q.getPositiveTerms().begin()); ASSERT_EQUALS(0U, q.getNegatedTerms().size()); ASSERT_EQUALS(0U, q.getPositivePhr().size()); ASSERT_EQUALS(0U, q.getNegatedPhr().size()); ASSERT_TRUE(q.getTermsForBounds() == q.getPositiveTerms()); }
TEST(FTSQuery, Phrase1) { FTSQuery q; ASSERT( q.parse("doing a \"phrase test\" for fun", "english", false, TEXT_INDEX_VERSION_2).isOK()); ASSERT_EQUALS(3U, q.getPositiveTerms().size()); ASSERT_EQUALS(0U, q.getNegatedTerms().size()); ASSERT_EQUALS(1U, q.getPositivePhr().size()); ASSERT_EQUALS(0U, q.getNegatedPhr().size()); ASSERT_TRUE(q.getTermsForBounds() == q.getPositiveTerms()); ASSERT_EQUALS("phrase test", q.getPositivePhr()[0]); ASSERT_EQUALS("fun|phrase|test||||phrase test||", q.debugString()); }
TEST( FTSMatcher, Phrase1 ) { FTSQuery q; q.parse( "foo \"table top\"", "english" ); FTSMatcher m( q, FTSSpec( FTSSpec::fixSpec( BSON( "key" << BSON( "$**" << "text" ) ) ) ) ); ASSERT( m.phraseMatch( "table top", BSON( "x" << "table top" ) ) ); ASSERT( m.phraseMatch( "table top", BSON( "x" << " asd table top asd" ) ) ); ASSERT( !m.phraseMatch( "table top", BSON( "x" << "tablz top" ) ) ); ASSERT( !m.phraseMatch( "table top", BSON( "x" << " asd tablz top asd" ) ) ); ASSERT( m.phrasesMatch( BSON( "x" << "table top" ) ) ); ASSERT( !m.phrasesMatch( BSON( "x" << "table a top" ) ) ); }
TEST(FTSQuery, CaseSensitiveNegativeTerms) { FTSQuery q; ASSERT( q.parse("-This -is -Negatively -miserable", "english", true, TEXT_INDEX_VERSION_2).isOK()); ASSERT_EQUALS(0U, q.getPositiveTerms().size()); ASSERT_EQUALS(0U, q.getTermsForBounds().size()); ASSERT_EQUALS(2U, q.getNegatedTerms().size()); ASSERT_EQUALS(1, std::count(q.getNegatedTerms().begin(), q.getNegatedTerms().end(), "Negat")); ASSERT_EQUALS(1, std::count(q.getNegatedTerms().begin(), q.getNegatedTerms().end(), "miser")); ASSERT_EQUALS(0U, q.getPositivePhr().size()); ASSERT_EQUALS(0U, q.getNegatedPhr().size()); }
// Test textIndexVersion:1 query with language "eng". "eng" uses the English stemmer, and // no stopword list. TEST(FTSQuery, TextIndexVersion1LanguageEng) { FTSQuery q; ASSERT(q.parse("the running", "eng", TEXT_INDEX_VERSION_1).isOK()); ASSERT_EQUALS(2U, q.getTerms().size()); ASSERT_EQUALS(1, std::count(q.getTerms().begin(), q.getTerms().end(), "the")); ASSERT_EQUALS(1, std::count(q.getTerms().begin(), q.getTerms().end(), "run")); ASSERT_EQUALS(0U, q.getNegatedTerms().size()); ASSERT_EQUALS(0U, q.getPhr().size()); ASSERT_EQUALS(0U, q.getNegatedPhr().size()); }
TEST(FTSQuery, Neg1) { FTSQuery q; ASSERT(q.parse("this is -really fun", "english", false, TEXT_INDEX_VERSION_2).isOK()); ASSERT_EQUALS(1U, q.getPositiveTerms().size()); ASSERT_EQUALS("fun", *q.getPositiveTerms().begin()); ASSERT_EQUALS(1U, q.getNegatedTerms().size()); ASSERT_EQUALS("realli", *q.getNegatedTerms().begin()); ASSERT_TRUE(q.getTermsForBounds() == q.getPositiveTerms()); }
TEST( FTSQuery, Phrase1 ) { FTSQuery q; ASSERT( q.parse( "doing a \"phrase test\" for fun", "english" ).isOK() ); ASSERT_EQUALS( 3U, q.getTerms().size() ); ASSERT_EQUALS( 0U, q.getNegatedTerms().size() ); ASSERT_EQUALS( 1U, q.getPhr().size() ); ASSERT_EQUALS( 0U, q.getNegatedPhr().size() ); ASSERT_EQUALS( "phrase test", q.getPhr()[0] ); ASSERT_EQUALS( "fun|phrase|test||||phrase test||", q.debugString() ); }
// Test textIndexVersion:1 query with language "english". This invokes the standard English // stemmer and stopword list. TEST(FTSQuery, TextIndexVersion1LanguageEnglish) { FTSQuery q; ASSERT(q.parse("the running", "english", false, TEXT_INDEX_VERSION_1).isOK()); ASSERT_EQUALS(1U, q.getPositiveTerms().size()); ASSERT_EQUALS("run", *q.getPositiveTerms().begin()); ASSERT_EQUALS(0U, q.getNegatedTerms().size()); ASSERT_EQUALS(0U, q.getPositivePhr().size()); ASSERT_EQUALS(0U, q.getNegatedPhr().size()); }
TEST( FTSQuery, Basic1 ) { FTSQuery q; ASSERT( q.parse( "this is fun", "english" ).isOK() ); ASSERT_EQUALS( 1U, q.getTerms().size() ); ASSERT_EQUALS( "fun", q.getTerms()[0] ); ASSERT_EQUALS( 0U, q.getNegatedTerms().size() ); ASSERT_EQUALS( 0U, q.getPhr().size() ); ASSERT_EQUALS( 0U, q.getNegatedPhr().size() ); }
TEST( FTSQuery, Neg1 ) { FTSQuery q; ASSERT( q.parse( "this is -really fun", "english" ).isOK() ); ASSERT_EQUALS( 1U, q.getTerms().size() ); ASSERT_EQUALS( "fun", q.getTerms()[0] ); ASSERT_EQUALS( 1U, q.getNegatedTerms().size() ); ASSERT_EQUALS( "realli", *q.getNegatedTerms().begin() ); }
TEST(FTSQuery, Mix1) { FTSQuery q; ASSERT( q.parse("\"industry\" -Melbourne -Physics", "english", false, TEXT_INDEX_VERSION_2).isOK()); ASSERT_EQUALS("industri||melbourn|physic||industry||", q.debugString()); }
TEST(FTSQuery, NegPhrase1) { FTSQuery q; ASSERT( q.parse("doing a -\"phrase test\" for fun", "english", false, TEXT_INDEX_VERSION_2).isOK()); ASSERT_EQUALS("fun||||||phrase test", q.debugString()); }
PlanStage* buildStages(const string& ns, const QuerySolutionNode* root, WorkingSet* ws) { if (STAGE_COLLSCAN == root->getType()) { const CollectionScanNode* csn = static_cast<const CollectionScanNode*>(root); CollectionScanParams params; params.ns = csn->name; params.tailable = csn->tailable; params.direction = (csn->direction == 1) ? CollectionScanParams::FORWARD : CollectionScanParams::BACKWARD; return new CollectionScan(params, ws, csn->filter.get()); } else if (STAGE_IXSCAN == root->getType()) { const IndexScanNode* ixn = static_cast<const IndexScanNode*>(root); // // XXX XXX // Given that this grabs data from the catalog, we must do this inside of a lock. // We should change this to take a (ns, index key pattern) pair so that the params // don't involve any on-disk data, just descriptions thereof. // XXX XXX // IndexScanParams params; NamespaceDetails* nsd = nsdetails(ns.c_str()); if (NULL == nsd) { warning() << "Can't ixscan null ns " << ns << endl; return NULL; } int idxNo = nsd->findIndexByKeyPattern(ixn->indexKeyPattern); if (-1 == idxNo) { warning() << "Can't find idx " << ixn->indexKeyPattern.toString() << "in ns " << ns << endl; return NULL; } params.descriptor = CatalogHack::getDescriptor(nsd, idxNo); params.bounds = ixn->bounds; params.direction = ixn->direction; params.limit = ixn->limit; return new IndexScan(params, ws, ixn->filter.get()); } else if (STAGE_FETCH == root->getType()) { const FetchNode* fn = static_cast<const FetchNode*>(root); PlanStage* childStage = buildStages(ns, fn->child.get(), ws); if (NULL == childStage) { return NULL; } return new FetchStage(ws, childStage, fn->filter.get()); } else if (STAGE_SORT == root->getType()) { const SortNode* sn = static_cast<const SortNode*>(root); PlanStage* childStage = buildStages(ns, sn->child.get(), ws); if (NULL == childStage) { return NULL; } SortStageParams params; params.pattern = sn->pattern; return new SortStage(params, ws, childStage); } else if (STAGE_PROJECTION == root->getType()) { const ProjectionNode* pn = static_cast<const ProjectionNode*>(root); PlanStage* childStage = buildStages(ns, pn->child.get(), ws); if (NULL == childStage) { return NULL; } return new ProjectionStage(pn->projection, ws, childStage, NULL); } else if (STAGE_LIMIT == root->getType()) { const LimitNode* ln = static_cast<const LimitNode*>(root); PlanStage* childStage = buildStages(ns, ln->child.get(), ws); if (NULL == childStage) { return NULL; } return new LimitStage(ln->limit, ws, childStage); } else if (STAGE_SKIP == root->getType()) { const SkipNode* sn = static_cast<const SkipNode*>(root); PlanStage* childStage = buildStages(ns, sn->child.get(), ws); if (NULL == childStage) { return NULL; } return new SkipStage(sn->skip, ws, childStage); } else if (STAGE_AND_HASH == root->getType()) { const AndHashNode* ahn = static_cast<const AndHashNode*>(root); auto_ptr<AndHashStage> ret(new AndHashStage(ws, ahn->filter.get())); for (size_t i = 0; i < ahn->children.size(); ++i) { PlanStage* childStage = buildStages(ns, ahn->children[i], ws); if (NULL == childStage) { return NULL; } ret->addChild(childStage); } return ret.release(); } else if (STAGE_OR == root->getType()) { const OrNode * orn = static_cast<const OrNode*>(root); auto_ptr<OrStage> ret(new OrStage(ws, orn->dedup, orn->filter.get())); for (size_t i = 0; i < orn->children.size(); ++i) { PlanStage* childStage = buildStages(ns, orn->children[i], ws); if (NULL == childStage) { return NULL; } ret->addChild(childStage); } return ret.release(); } else if (STAGE_AND_SORTED == root->getType()) { const AndSortedNode* asn = static_cast<const AndSortedNode*>(root); auto_ptr<AndSortedStage> ret(new AndSortedStage(ws, asn->filter.get())); for (size_t i = 0; i < asn->children.size(); ++i) { PlanStage* childStage = buildStages(ns, asn->children[i], ws); if (NULL == childStage) { return NULL; } ret->addChild(childStage); } return ret.release(); } else if (STAGE_SORT_MERGE == root->getType()) { const MergeSortNode* msn = static_cast<const MergeSortNode*>(root); MergeSortStageParams params; params.dedup = msn->dedup; params.pattern = msn->sort; auto_ptr<MergeSortStage> ret(new MergeSortStage(params, ws)); for (size_t i = 0; i < msn->children.size(); ++i) { PlanStage* childStage = buildStages(ns, msn->children[i], ws); if (NULL == childStage) { return NULL; } ret->addChild(childStage); } return ret.release(); } else if (STAGE_GEO_2D == root->getType()) { const Geo2DNode* node = static_cast<const Geo2DNode*>(root); TwoDParams params; params.gq = node->gq; params.filter = node->filter.get(); params.indexKeyPattern = node->indexKeyPattern; params.ns = ns; return new TwoD(params, ws); } else if (STAGE_GEO_NEAR_2D == root->getType()) { const GeoNear2DNode* node = static_cast<const GeoNear2DNode*>(root); TwoDNearParams params; params.nearQuery = node->nq; params.ns = ns; params.indexKeyPattern = node->indexKeyPattern; params.filter = node->filter.get(); params.numWanted = node->numWanted; // XXX XXX where do we grab this from?? the near query...modify geo parser... :( params.uniqueDocs = false; // XXX XXX where do we grab this from?? the near query...modify geo parser... :( return new TwoDNear(params, ws); } else if (STAGE_GEO_NEAR_2DSPHERE == root->getType()) { const GeoNear2DSphereNode* node = static_cast<const GeoNear2DSphereNode*>(root); return new S2NearStage(ns, node->indexKeyPattern, node->nq, node->baseBounds, node->filter.get(), ws); } else if (STAGE_TEXT == root->getType()) { const TextNode* node = static_cast<const TextNode*>(root); NamespaceDetails* nsd = nsdetails(ns.c_str()); if (NULL == nsd) { return NULL; } vector<int> idxMatches; nsd->findIndexByType("text", idxMatches); if (1 != idxMatches.size()) { return NULL; } IndexDescriptor* index = CatalogHack::getDescriptor(nsd, idxMatches[0]); auto_ptr<FTSAccessMethod> fam(new FTSAccessMethod(index)); TextStageParams params(fam->getSpec()); params.ns = ns; params.index = index; params.spec = fam->getSpec(); params.limit = node->_numWanted; Status s = fam->getSpec().getIndexPrefix(BSONObj(), ¶ms.indexPrefix); if (!s.isOK()) { return NULL; } string language = ("" == node->_language ? fam->getSpec().defaultLanguage() : node->_language); FTSQuery ftsq; Status parseStatus = ftsq.parse(node->_query, language); if (!parseStatus.isOK()) { return NULL; } params.query = ftsq; return new TextStage(params, ws, node->_filter.get()); } else { stringstream ss; root->appendToString(&ss, 0); warning() << "Could not build exec tree for node " << ss.str() << endl; return NULL; } }
TEST( FTSQuery, Phrase2 ) { FTSQuery q; ASSERT( q.parse( "doing a \"phrase-test\" for fun", "english" ).isOK() ); ASSERT_EQUALS( 1U, q.getPhr().size() ); ASSERT_EQUALS( "phrase-test", q.getPhr()[0] ); }
TEST(FTSQuery, ParsePunctuation) { FTSQuery q; ASSERT(q.parse("hello.world", "english", false, TEXT_INDEX_VERSION_2).isOK()); ASSERT_EQUALS(false, q.getCaseSensitive()); ASSERT_EQUALS(2U, q.getPositiveTerms().size()); ASSERT_EQUALS("hello", *q.getPositiveTerms().begin()); ASSERT_EQUALS("world", *(--q.getPositiveTerms().end())); ASSERT_EQUALS(0U, q.getNegatedTerms().size()); ASSERT_EQUALS(0U, q.getPositivePhr().size()); ASSERT_EQUALS(0U, q.getNegatedPhr().size()); ASSERT_TRUE(q.getTermsForBounds() == q.getPositiveTerms()); }
TEST( FTSQuery, NegPhrase1 ) { FTSQuery q; ASSERT( q.parse( "doing a -\"phrase test\" for fun", "english" ).isOK() ); ASSERT_EQUALS( "fun||||||phrase test", q.debugString() ); }
TEST(FTSQuery, CaseSensitiveOption) { FTSQuery q; ASSERT(q.parse("this is fun", "english", true, TEXT_INDEX_VERSION_2).isOK()); ASSERT_EQUALS(true, q.getCaseSensitive()); }
PlanStage* buildStages(const QuerySolution& qsol, const QuerySolutionNode* root, WorkingSet* ws, bool use_chronos = false) { if (STAGE_COLLSCAN == root->getType()) { const CollectionScanNode* csn = static_cast<const CollectionScanNode*>(root); CollectionScanParams params; params.ns = csn->name; params.tailable = csn->tailable; params.direction = (csn->direction == 1) ? CollectionScanParams::FORWARD : CollectionScanParams::BACKWARD; params.maxScan = csn->maxScan; return new CollectionScan(params, ws, csn->filter.get(), use_chronos); } else if (STAGE_IXSCAN == root->getType()) { const IndexScanNode* ixn = static_cast<const IndexScanNode*>(root); // // XXX XXX // Given that this grabs data from the catalog, we must do this inside of a lock. // We should change this to take a (ns, index key pattern) pair so that the params // don't involve any on-disk data, just descriptions thereof. // XXX XXX // Database* db = cc().database(); Collection* collection = db ? db->getCollection(qsol.ns) : NULL; if (NULL == collection) { warning() << "Can't ixscan null ns " << qsol.ns << endl; return NULL; } NamespaceDetails* nsd = collection->details(); int idxNo = nsd->findIndexByKeyPattern(ixn->indexKeyPattern); if (-1 == idxNo) { warning() << "Can't find idx " << ixn->indexKeyPattern.toString() << "in ns " << qsol.ns << endl; return NULL; } IndexScanParams params; params.descriptor = collection->getIndexCatalog()->getDescriptor( idxNo ); params.bounds = ixn->bounds; params.direction = ixn->direction; params.limit = ixn->limit; params.maxScan = ixn->maxScan; params.addKeyMetadata = ixn->addKeyMetadata; params.ns = qsol.ns; return new IndexScan(params, ws, ixn->filter.get(), use_chronos); } else if (STAGE_FETCH == root->getType()) { const FetchNode* fn = static_cast<const FetchNode*>(root); PlanStage* childStage = buildStages(qsol, fn->children[0], ws); if (NULL == childStage) { return NULL; } return new FetchStage(ws, childStage, fn->filter.get()); } else if (STAGE_SORT == root->getType()) { const SortNode* sn = static_cast<const SortNode*>(root); PlanStage* childStage = buildStages(qsol, sn->children[0], ws); if (NULL == childStage) { return NULL; } SortStageParams params; params.pattern = sn->pattern; params.query = sn->query; params.limit = sn->limit; return new SortStage(params, ws, childStage); } else if (STAGE_PROJECTION == root->getType()) { const ProjectionNode* pn = static_cast<const ProjectionNode*>(root); PlanStage* childStage = buildStages(qsol, pn->children[0], ws); if (NULL == childStage) { return NULL; } return new ProjectionStage(pn->projection, pn->fullExpression, ws, childStage); } else if (STAGE_LIMIT == root->getType()) { const LimitNode* ln = static_cast<const LimitNode*>(root); PlanStage* childStage = buildStages(qsol, ln->children[0], ws); if (NULL == childStage) { return NULL; } return new LimitStage(ln->limit, ws, childStage); } else if (STAGE_SKIP == root->getType()) { const SkipNode* sn = static_cast<const SkipNode*>(root); PlanStage* childStage = buildStages(qsol, sn->children[0], ws); if (NULL == childStage) { return NULL; } return new SkipStage(sn->skip, ws, childStage); } else if (STAGE_AND_HASH == root->getType()) { const AndHashNode* ahn = static_cast<const AndHashNode*>(root); auto_ptr<AndHashStage> ret(new AndHashStage(ws, ahn->filter.get())); for (size_t i = 0; i < ahn->children.size(); ++i) { PlanStage* childStage = buildStages(qsol, ahn->children[i], ws); if (NULL == childStage) { return NULL; } ret->addChild(childStage); } return ret.release(); } else if (STAGE_OR == root->getType()) { const OrNode * orn = static_cast<const OrNode*>(root); auto_ptr<OrStage> ret(new OrStage(ws, orn->dedup, orn->filter.get())); for (size_t i = 0; i < orn->children.size(); ++i) { PlanStage* childStage = buildStages(qsol, orn->children[i], ws); if (NULL == childStage) { return NULL; } ret->addChild(childStage); } return ret.release(); } else if (STAGE_AND_SORTED == root->getType()) { const AndSortedNode* asn = static_cast<const AndSortedNode*>(root); auto_ptr<AndSortedStage> ret(new AndSortedStage(ws, asn->filter.get())); for (size_t i = 0; i < asn->children.size(); ++i) { PlanStage* childStage = buildStages(qsol, asn->children[i], ws); if (NULL == childStage) { return NULL; } ret->addChild(childStage); } return ret.release(); } else if (STAGE_SORT_MERGE == root->getType()) { const MergeSortNode* msn = static_cast<const MergeSortNode*>(root); MergeSortStageParams params; params.dedup = msn->dedup; params.pattern = msn->sort; auto_ptr<MergeSortStage> ret(new MergeSortStage(params, ws)); for (size_t i = 0; i < msn->children.size(); ++i) { PlanStage* childStage = buildStages(qsol, msn->children[i], ws); if (NULL == childStage) { return NULL; } ret->addChild(childStage); } return ret.release(); } else if (STAGE_GEO_2D == root->getType()) { const Geo2DNode* node = static_cast<const Geo2DNode*>(root); TwoDParams params; params.gq = node->gq; params.filter = node->filter.get(); params.indexKeyPattern = node->indexKeyPattern; params.ns = qsol.ns; return new TwoD(params, ws); } else if (STAGE_GEO_NEAR_2D == root->getType()) { const GeoNear2DNode* node = static_cast<const GeoNear2DNode*>(root); TwoDNearParams params; params.nearQuery = node->nq; params.ns = qsol.ns; params.indexKeyPattern = node->indexKeyPattern; params.filter = node->filter.get(); params.numWanted = node->numWanted; params.addPointMeta = node->addPointMeta; params.addDistMeta = node->addDistMeta; return new TwoDNear(params, ws); } else if (STAGE_GEO_NEAR_2DSPHERE == root->getType()) { const GeoNear2DSphereNode* node = static_cast<const GeoNear2DSphereNode*>(root); S2NearParams params; params.ns = qsol.ns; params.indexKeyPattern = node->indexKeyPattern; params.nearQuery = node->nq; params.baseBounds = node->baseBounds; params.filter = node->filter.get(); params.addPointMeta = node->addPointMeta; params.addDistMeta = node->addDistMeta; return new S2NearStage(params, ws); } else if (STAGE_TEXT == root->getType()) { const TextNode* node = static_cast<const TextNode*>(root); Database* db = cc().database(); Collection* collection = db ? db->getCollection(qsol.ns) : NULL; if (NULL == collection) { warning() << "null collection for text?"; return NULL; } vector<int> idxMatches; collection->details()->findIndexByType("text", idxMatches); if (1 != idxMatches.size()) { warning() << "more than one text idx?"; return NULL; } IndexDescriptor* index = collection->getIndexCatalog()->getDescriptor(idxMatches[0]); const FTSAccessMethod* fam = static_cast<FTSAccessMethod*>( collection->getIndexCatalog()->getIndex( index ) ); TextStageParams params(fam->getSpec()); params.ns = qsol.ns; params.index = index; params.spec = fam->getSpec(); // XXX change getIndexPrefix to not look at BSONObj Status s = fam->getSpec().getIndexPrefix(qsol.filterData, ¶ms.indexPrefix); if (!s.isOK()) { warning() << "can't get text index prefix??"; return NULL; } string language = ("" == node->_language ? fam->getSpec().defaultLanguage().str() : node->_language); FTSQuery ftsq; Status parseStatus = ftsq.parse(node->_query, language); if (!parseStatus.isOK()) { warning() << "cant parse fts query"; return NULL; } params.query = ftsq; return new TextStage(params, ws, node->filter.get()); } else if (STAGE_SHARDING_FILTER == root->getType()) { const ShardingFilterNode* fn = static_cast<const ShardingFilterNode*>(root); PlanStage* childStage = buildStages(qsol, fn->children[0], ws); if (NULL == childStage) { return NULL; } return new ShardFilterStage(shardingState.getCollectionMetadata(qsol.ns), ws, childStage); } else { stringstream ss; root->appendToString(&ss, 0); warning() << "Could not build exec tree for node " << ss.str() << endl; return NULL; } }
TEST(FTSQuery, CaseSensitivePositiveTerms) { FTSQuery q; ASSERT(q.parse("This is Positively fun", "english", true, TEXT_INDEX_VERSION_2).isOK()); ASSERT_EQUALS(2U, q.getTermsForBounds().size()); ASSERT_EQUALS(1, std::count(q.getTermsForBounds().begin(), q.getTermsForBounds().end(), "posit")); ASSERT_EQUALS(1, std::count(q.getTermsForBounds().begin(), q.getTermsForBounds().end(), "fun")); ASSERT_EQUALS(2U, q.getPositiveTerms().size()); ASSERT_EQUALS(1, std::count(q.getPositiveTerms().begin(), q.getPositiveTerms().end(), "Posit")); ASSERT_EQUALS(1, std::count(q.getPositiveTerms().begin(), q.getPositiveTerms().end(), "fun")); ASSERT_EQUALS(0U, q.getNegatedTerms().size()); ASSERT_EQUALS(0U, q.getPositivePhr().size()); ASSERT_EQUALS(0U, q.getNegatedPhr().size()); }
FTSMatcher::FTSMatcher( const FTSQuery& query, const FTSSpec& spec ) : _query( query ), _spec( spec ), _stemmer( query.getLanguage() ){ }
/* * Runs the command object cmdobj on the db with name dbname and puts result in result. * @param dbname, name of db * @param cmdobj, object that contains entire command * @param options * @param errmsg, reference to error message * @param result, reference to builder for result * @param fromRepl * @return true if successful, false otherwise */ bool FTSCommand::_run(const string& dbname, BSONObj& cmdObj, int cmdOptions, const string& ns, const string& searchString, string language, // "" for not-set int limit, BSONObj& filter, BSONObj& projection, string& errmsg, BSONObjBuilder& result ) { Timer comm; scoped_ptr<Projection> pr; if ( !projection.isEmpty() ) { pr.reset( new Projection() ); pr->init( projection ); } // priority queue for results Results results; Database* db = cc().database(); Collection* collection = db->getCollection( ns ); if ( !collection ) { errmsg = "can't find ns"; return false; } vector<int> idxMatches; collection->details()->findIndexByType( INDEX_NAME, idxMatches ); if ( idxMatches.size() == 0 ) { errmsg = str::stream() << "no text index for: " << ns; return false; } if ( idxMatches.size() > 1 ) { errmsg = str::stream() << "too many text indexes for: " << ns; return false; } BSONObj indexPrefix; IndexDescriptor* descriptor = collection->getIndexCatalog()->getDescriptor(idxMatches[0]); auto_ptr<FTSAccessMethod> fam(new FTSAccessMethod(descriptor)); if ( language == "" ) { language = fam->getSpec().defaultLanguage().str(); } Status s = fam->getSpec().getIndexPrefix( filter, &indexPrefix ); if ( !s.isOK() ) { errmsg = s.toString(); return false; } FTSQuery query; if ( !query.parse( searchString, language ).isOK() ) { errmsg = "can't parse search"; return false; } result.append( "queryDebugString", query.debugString() ); result.append( "language", language ); FTSSearch search(descriptor, fam->getSpec(), indexPrefix, query, filter ); search.go( &results, limit ); // grab underlying container inside priority queue vector<ScoredLocation> r( results.dangerous() ); // sort results by score (not always in correct order, especially w.r.t. multiterm) sort( r.begin(), r.end() ); // build the results bson array shown to user BSONArrayBuilder a( result.subarrayStart( "results" ) ); int tempSize = 1024 * 1024; // leave a mb for other things long long numReturned = 0; for ( unsigned n = 0; n < r.size(); n++ ) { BSONObj obj = BSONObj::make(r[n].rec); BSONObj toSendBack = obj; if ( pr ) { toSendBack = pr->transform(obj); } if ( ( tempSize + toSendBack.objsize() ) >= BSONObjMaxUserSize ) { break; } BSONObjBuilder x( a.subobjStart() ); x.append( "score" , r[n].score ); x.append( "obj", toSendBack ); BSONObj xobj = x.done(); tempSize += xobj.objsize(); numReturned++; } a.done(); // returns some stats to the user BSONObjBuilder bb( result.subobjStart( "stats" ) ); bb.appendNumber( "nscanned" , search.getKeysLookedAt() ); bb.appendNumber( "nscannedObjects" , search.getObjLookedAt() ); bb.appendNumber( "n" , numReturned ); bb.appendNumber( "nfound" , r.size() ); bb.append( "timeMicros", (int)comm.micros() ); bb.done(); return true; }