You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucenenet.apache.org by sy...@apache.org on 2016/09/11 21:30:43 UTC
[12/50] [abbrv] lucenenet git commit: Ported
QueryParser.ComplexPhrase namespace + tests.
Ported QueryParser.ComplexPhrase namespace + tests.
Project: http://git-wip-us.apache.org/repos/asf/lucenenet/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucenenet/commit/071b60ce
Tree: http://git-wip-us.apache.org/repos/asf/lucenenet/tree/071b60ce
Diff: http://git-wip-us.apache.org/repos/asf/lucenenet/diff/071b60ce
Branch: refs/heads/master
Commit: 071b60ce871c174f356f65c1e6e96eb2f604b434
Parents: 11d7449
Author: Shad Storhaug <sh...@shadstorhaug.com>
Authored: Mon Aug 1 03:03:29 2016 +0700
Committer: Shad Storhaug <sh...@shadstorhaug.com>
Committed: Fri Sep 2 22:30:25 2016 +0700
----------------------------------------------------------------------
.../Classic/QueryParserBase.cs | 2 +-
.../ComplexPhrase/ComplexPhraseQueryParser.cs | 468 +++++++++++++++++++
.../Lucene.Net.QueryParser.csproj | 1 +
.../ComplexPhrase/TestComplexPhraseQuery.cs | 214 +++++++++
.../Lucene.Net.Tests.QueryParser.csproj | 1 +
5 files changed, 685 insertions(+), 1 deletion(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/071b60ce/Lucene.Net.QueryParser/Classic/QueryParserBase.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.QueryParser/Classic/QueryParserBase.cs b/Lucene.Net.QueryParser/Classic/QueryParserBase.cs
index 0449187..599110e 100644
--- a/Lucene.Net.QueryParser/Classic/QueryParserBase.cs
+++ b/Lucene.Net.QueryParser/Classic/QueryParserBase.cs
@@ -164,7 +164,7 @@ namespace Lucene.Net.QueryParser.Classic
/// </remarks>
/// <param name="query">the query string to be parsed.</param>
/// <returns></returns>
- public Query Parse(string query)
+ public virtual Query Parse(string query)
{
ReInit(new FastCharStream(new StringReader(query)));
try
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/071b60ce/Lucene.Net.QueryParser/ComplexPhrase/ComplexPhraseQueryParser.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.QueryParser/ComplexPhrase/ComplexPhraseQueryParser.cs b/Lucene.Net.QueryParser/ComplexPhrase/ComplexPhraseQueryParser.cs
new file mode 100644
index 0000000..0ac7c5b
--- /dev/null
+++ b/Lucene.Net.QueryParser/ComplexPhrase/ComplexPhraseQueryParser.cs
@@ -0,0 +1,468 @@
+\ufeffusing Lucene.Net.Analysis;
+using Lucene.Net.Index;
+using Lucene.Net.QueryParser.Classic;
+using Lucene.Net.Search;
+using Lucene.Net.Search.Spans;
+using Lucene.Net.Util;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Lucene.Net.QueryParser.ComplexPhrase
+{
+ /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ /// <summary>
+ /// QueryParser which permits complex phrase query syntax eg "(john jon
+ /// jonathan~) peters*".
+ /// <p>
+ /// Performs potentially multiple passes over Query text to parse any nested
+ /// logic in PhraseQueries. - First pass takes any PhraseQuery content between
+ /// quotes and stores for subsequent pass. All other query content is parsed as
+ /// normal - Second pass parses any stored PhraseQuery content, checking all
+ /// embedded clauses are referring to the same field and therefore can be
+ /// rewritten as Span queries. All PhraseQuery clauses are expressed as
+ /// ComplexPhraseQuery objects
+ /// </p>
+ /// <p>
+ /// This could arguably be done in one pass using a new QueryParser but here I am
+ /// working within the constraints of the existing parser as a base class. This
+ /// currently simply feeds all phrase content through an analyzer to select
+ /// phrase terms - any "special" syntax such as * ~ * etc are not given special
+ /// status
+ /// </p>
+ /// </summary>
+ public class ComplexPhraseQueryParser : Classic.QueryParser
+ {
+ private List<ComplexPhraseQuery> complexPhrases = null;
+
+ private bool isPass2ResolvingPhrases;
+
+ /// <summary>
+ /// When <code>inOrder</code> is true, the search terms must
+ /// exists in the documents as the same order as in query.
+ /// Choose between ordered (true) or un-ordered (false) proximity search.
+ /// </summary>
+ public bool InOrder { get; set; }
+
+ private ComplexPhraseQuery currentPhraseQuery = null;
+
+ public ComplexPhraseQueryParser(LuceneVersion matchVersion, string f, Analyzer a)
+ : base(matchVersion, f, a)
+ {
+ // set property defaults
+ this.InOrder = true;
+ }
+
+ protected internal override Query GetFieldQuery(string field, string queryText, int slop)
+ {
+ ComplexPhraseQuery cpq = new ComplexPhraseQuery(field, queryText, slop, InOrder);
+ complexPhrases.Add(cpq); // add to list of phrases to be parsed once
+ // we
+ // are through with this pass
+ return cpq;
+ }
+
+ public override Query Parse(string query)
+ {
+ if (isPass2ResolvingPhrases)
+ {
+ MultiTermQuery.RewriteMethod oldMethod = MultiTermRewriteMethod;
+ try
+ {
+ // Temporarily force BooleanQuery rewrite so that Parser will
+ // generate visible
+ // collection of terms which we can convert into SpanQueries.
+ // ConstantScoreRewrite mode produces an
+ // opaque ConstantScoreQuery object which cannot be interrogated for
+ // terms in the same way a BooleanQuery can.
+ // QueryParser is not guaranteed threadsafe anyway so this temporary
+ // state change should not
+ // present an issue
+ MultiTermRewriteMethod = MultiTermQuery.SCORING_BOOLEAN_QUERY_REWRITE;
+ return base.Parse(query);
+ }
+ finally
+ {
+ MultiTermRewriteMethod = oldMethod;
+ }
+ }
+
+ // First pass - parse the top-level query recording any PhraseQuerys
+ // which will need to be resolved
+ complexPhrases = new List<ComplexPhraseQuery>();
+ Query q = base.Parse(query);
+
+ // Perform second pass, using this QueryParser to parse any nested
+ // PhraseQueries with different
+ // set of syntax restrictions (i.e. all fields must be same)
+ isPass2ResolvingPhrases = true;
+ try
+ {
+ foreach (var currentPhraseQuery in complexPhrases)
+ {
+ this.currentPhraseQuery = currentPhraseQuery;
+ // in each phrase, now parse the contents between quotes as a
+ // separate parse operation
+ currentPhraseQuery.ParsePhraseElements(this);
+ }
+ }
+ finally
+ {
+ isPass2ResolvingPhrases = false;
+ }
+ return q;
+ }
+
+ // There is No "getTermQuery throws ParseException" method to override so
+ // unfortunately need
+ // to throw a runtime exception here if a term for another field is embedded
+ // in phrase query
+ protected override Query NewTermQuery(Term term)
+ {
+ if (isPass2ResolvingPhrases)
+ {
+ try
+ {
+ CheckPhraseClauseIsForSameField(term.Field);
+ }
+ catch (ParseException pe)
+ {
+ throw new Exception("Error parsing complex phrase", pe);
+ }
+ }
+ return base.NewTermQuery(term);
+ }
+
+ // Helper method used to report on any clauses that appear in query syntax
+ private void CheckPhraseClauseIsForSameField(string field)
+ {
+ if (!field.Equals(currentPhraseQuery.Field))
+ {
+ throw new ParseException("Cannot have clause for field \"" + field
+ + "\" nested in phrase " + " for field \"" + currentPhraseQuery.Field
+ + "\"");
+ }
+ }
+
+ protected internal override Query GetWildcardQuery(string field, string termStr)
+ {
+ if (isPass2ResolvingPhrases)
+ {
+ CheckPhraseClauseIsForSameField(field);
+ }
+ return base.GetWildcardQuery(field, termStr);
+ }
+
+ protected internal override Query GetRangeQuery(string field, string part1, string part2, bool startInclusive, bool endInclusive)
+ {
+ if (isPass2ResolvingPhrases)
+ {
+ CheckPhraseClauseIsForSameField(field);
+ }
+ return base.GetRangeQuery(field, part1, part2, startInclusive, endInclusive);
+ }
+
+ protected internal override Query NewRangeQuery(string field, string part1, string part2, bool startInclusive, bool endInclusive)
+ {
+ if (isPass2ResolvingPhrases)
+ {
+ // Must use old-style RangeQuery in order to produce a BooleanQuery
+ // that can be turned into SpanOr clause
+ TermRangeQuery rangeQuery = TermRangeQuery.NewStringRange(field, part1, part2, startInclusive, endInclusive);
+ rangeQuery.SetRewriteMethod(MultiTermQuery.SCORING_BOOLEAN_QUERY_REWRITE);
+ return rangeQuery;
+ }
+ return base.NewRangeQuery(field, part1, part2, startInclusive, endInclusive);
+ }
+
+ protected internal override Query GetFuzzyQuery(string field, string termStr, float minSimilarity)
+ {
+ if (isPass2ResolvingPhrases)
+ {
+ CheckPhraseClauseIsForSameField(field);
+ }
+ return base.GetFuzzyQuery(field, termStr, minSimilarity);
+ }
+
+ /// <summary>
+ /// Used to handle the query content in between quotes and produced Span-based
+ /// interpretations of the clauses.
+ /// </summary>
+ public class ComplexPhraseQuery : Query
+ {
+ private readonly string field;
+ private readonly string phrasedQueryStringContents;
+ private readonly int slopFactor;
+ private readonly bool inOrder;
+ private Query contents;
+
+ public ComplexPhraseQuery(string field, string phrasedQueryStringContents,
+ int slopFactor, bool inOrder)
+ {
+ this.field = field;
+ this.phrasedQueryStringContents = phrasedQueryStringContents;
+ this.slopFactor = slopFactor;
+ this.inOrder = inOrder;
+ }
+
+ public string Field
+ {
+ get { return field; }
+ }
+
+ // Called by ComplexPhraseQueryParser for each phrase after the main
+ // parse
+ // thread is through
+ protected internal void ParsePhraseElements(ComplexPhraseQueryParser qp)
+ {
+ // TODO ensure that field-sensitivity is preserved ie the query
+ // string below is parsed as
+ // field+":("+phrasedQueryStringContents+")"
+ // but this will need code in rewrite to unwrap the first layer of
+ // boolean query
+
+ string oldDefaultParserField = qp.Field;
+ try
+ {
+ //temporarily set the QueryParser to be parsing the default field for this phrase e.g author:"fred* smith"
+ qp.field = this.field;
+ contents = qp.Parse(phrasedQueryStringContents);
+ }
+ finally
+ {
+ qp.field = oldDefaultParserField;
+ }
+ }
+
+ public override Query Rewrite(IndexReader reader)
+ {
+ // ArrayList spanClauses = new ArrayList();
+ if (contents is TermQuery)
+ {
+ return contents;
+ }
+ // Build a sequence of Span clauses arranged in a SpanNear - child
+ // clauses can be complex
+ // Booleans e.g. nots and ors etc
+ int numNegatives = 0;
+ if (!(contents is BooleanQuery))
+ {
+ throw new ArgumentException("Unknown query type \""
+ + contents.GetType().Name
+ + "\" found in phrase query string \"" + phrasedQueryStringContents
+ + "\"");
+ }
+ BooleanQuery bq = (BooleanQuery)contents;
+ BooleanClause[] bclauses = bq.Clauses;
+ SpanQuery[] allSpanClauses = new SpanQuery[bclauses.Length];
+ // For all clauses e.g. one* two~
+ for (int i = 0; i < bclauses.Length; i++)
+ {
+ // HashSet bclauseterms=new HashSet();
+ Query qc = bclauses[i].Query;
+ // Rewrite this clause e.g one* becomes (one OR onerous)
+ qc = qc.Rewrite(reader);
+ if (bclauses[i].Occur_.Equals(BooleanClause.Occur.MUST_NOT))
+ {
+ numNegatives++;
+ }
+
+ if (qc is BooleanQuery)
+ {
+ List<SpanQuery> sc = new List<SpanQuery>();
+ AddComplexPhraseClause(sc, (BooleanQuery)qc);
+ if (sc.Count > 0)
+ {
+ allSpanClauses[i] = sc.ElementAt(0);
+ }
+ else
+ {
+ // Insert fake term e.g. phrase query was for "Fred Smithe*" and
+ // there were no "Smithe*" terms - need to
+ // prevent match on just "Fred".
+ allSpanClauses[i] = new SpanTermQuery(new Term(field,
+ "Dummy clause because no terms found - must match nothing"));
+ }
+ }
+ else
+ {
+ if (qc is TermQuery)
+ {
+ TermQuery tq = (TermQuery)qc;
+ allSpanClauses[i] = new SpanTermQuery(tq.Term);
+ }
+ else
+ {
+ throw new ArgumentException("Unknown query type \""
+ + qc.GetType().Name
+ + "\" found in phrase query string \""
+ + phrasedQueryStringContents + "\"");
+ }
+
+ }
+ }
+ if (numNegatives == 0)
+ {
+ // The simple case - no negative elements in phrase
+ return new SpanNearQuery(allSpanClauses, slopFactor, inOrder);
+ }
+ // Complex case - we have mixed positives and negatives in the
+ // sequence.
+ // Need to return a SpanNotQuery
+ List<SpanQuery> positiveClauses = new List<SpanQuery>();
+ for (int j = 0; j < allSpanClauses.Length; j++)
+ {
+ if (!bclauses[j].Occur_.Equals(BooleanClause.Occur.MUST_NOT))
+ {
+ positiveClauses.Add(allSpanClauses[j]);
+ }
+ }
+
+ SpanQuery[] includeClauses = positiveClauses
+ .ToArray();
+
+ SpanQuery include = null;
+ if (includeClauses.Length == 1)
+ {
+ include = includeClauses[0]; // only one positive clause
+ }
+ else
+ {
+ // need to increase slop factor based on gaps introduced by
+ // negatives
+ include = new SpanNearQuery(includeClauses, slopFactor + numNegatives,
+ inOrder);
+ }
+ // Use sequence of positive and negative values as the exclude.
+ SpanNearQuery exclude = new SpanNearQuery(allSpanClauses, slopFactor,
+ inOrder);
+ SpanNotQuery snot = new SpanNotQuery(include, exclude);
+ return snot;
+ }
+
+ private void AddComplexPhraseClause(List<SpanQuery> spanClauses, BooleanQuery qc)
+ {
+ List<SpanQuery> ors = new List<SpanQuery>();
+ List<SpanQuery> nots = new List<SpanQuery>();
+ BooleanClause[] bclauses = qc.Clauses;
+
+ // For all clauses e.g. one* two~
+ for (int i = 0; i < bclauses.Length; i++)
+ {
+ Query childQuery = bclauses[i].Query;
+
+ // select the list to which we will add these options
+ List<SpanQuery> chosenList = ors;
+ if (bclauses[i].Occur_ == BooleanClause.Occur.MUST_NOT)
+ {
+ chosenList = nots;
+ }
+
+ if (childQuery is TermQuery)
+ {
+ TermQuery tq = (TermQuery)childQuery;
+ SpanTermQuery stq = new SpanTermQuery(tq.Term);
+ stq.Boost = tq.Boost;
+ chosenList.Add(stq);
+ }
+ else if (childQuery is BooleanQuery)
+ {
+ BooleanQuery cbq = (BooleanQuery)childQuery;
+ AddComplexPhraseClause(chosenList, cbq);
+ }
+ else
+ {
+ // LUCENETODO alternatively could call extract terms here?
+ throw new ArgumentException("Unknown query type:"
+ + childQuery.GetType().Name);
+ }
+ }
+ if (ors.Count == 0)
+ {
+ return;
+ }
+ SpanOrQuery soq = new SpanOrQuery(ors
+ .ToArray());
+ if (nots.Count == 0)
+ {
+ spanClauses.Add(soq);
+ }
+ else
+ {
+ SpanOrQuery snqs = new SpanOrQuery(nots
+ .ToArray());
+ SpanNotQuery snq = new SpanNotQuery(soq, snqs);
+ spanClauses.Add(snq);
+ }
+ }
+
+ public override string ToString(string field)
+ {
+ return "\"" + phrasedQueryStringContents + "\"";
+ }
+
+ public override int GetHashCode()
+ {
+ int prime = 31;
+ int result = base.GetHashCode();
+ result = prime * result + ((field == null) ? 0 : field.GetHashCode());
+ result = prime
+ * result
+ + ((phrasedQueryStringContents == null) ? 0
+ : phrasedQueryStringContents.GetHashCode());
+ result = prime * result + slopFactor;
+ result = prime * result + (inOrder ? 1 : 0);
+ return result;
+ }
+
+ public override bool Equals(object obj)
+ {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (GetType() != obj.GetType())
+ return false;
+ if (!base.Equals(obj))
+ {
+ return false;
+ }
+ ComplexPhraseQuery other = (ComplexPhraseQuery)obj;
+ if (field == null)
+ {
+ if (other.field != null)
+ return false;
+ }
+ else if (!field.Equals(other.field))
+ return false;
+ if (phrasedQueryStringContents == null)
+ {
+ if (other.phrasedQueryStringContents != null)
+ return false;
+ }
+ else if (!phrasedQueryStringContents
+ .Equals(other.phrasedQueryStringContents))
+ return false;
+ if (slopFactor != other.slopFactor)
+ return false;
+ return inOrder == other.inOrder;
+ }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/071b60ce/Lucene.Net.QueryParser/Lucene.Net.QueryParser.csproj
----------------------------------------------------------------------
diff --git a/Lucene.Net.QueryParser/Lucene.Net.QueryParser.csproj b/Lucene.Net.QueryParser/Lucene.Net.QueryParser.csproj
index 2c0619c..0b18336 100644
--- a/Lucene.Net.QueryParser/Lucene.Net.QueryParser.csproj
+++ b/Lucene.Net.QueryParser/Lucene.Net.QueryParser.csproj
@@ -50,6 +50,7 @@
<Compile Include="Classic\QueryParserTokenManager.cs" />
<Compile Include="Classic\Token.cs" />
<Compile Include="Classic\TokenMgrError.cs" />
+ <Compile Include="ComplexPhrase\ComplexPhraseQueryParser.cs" />
<Compile Include="Flexible\Standard\CommonQueryParserConfiguration.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/071b60ce/Lucene.Net.Tests.QueryParser/ComplexPhrase/TestComplexPhraseQuery.cs
----------------------------------------------------------------------
diff --git a/Lucene.Net.Tests.QueryParser/ComplexPhrase/TestComplexPhraseQuery.cs b/Lucene.Net.Tests.QueryParser/ComplexPhrase/TestComplexPhraseQuery.cs
new file mode 100644
index 0000000..2c2d6e2
--- /dev/null
+++ b/Lucene.Net.Tests.QueryParser/ComplexPhrase/TestComplexPhraseQuery.cs
@@ -0,0 +1,214 @@
+\ufeffusing Lucene.Net.Analysis;
+using Lucene.Net.Documents;
+using Lucene.Net.Index;
+using Lucene.Net.Search;
+using Lucene.Net.Store;
+using Lucene.Net.Util;
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+
+namespace Lucene.Net.QueryParser.ComplexPhrase
+{
+ /*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ [TestFixture]
+ public class TestComplexPhraseQuery : LuceneTestCase
+ {
+ Directory rd;
+ Analyzer analyzer;
+ DocData[] docsContent = {
+ new DocData("john smith", "1", "developer"),
+ new DocData("johathon smith", "2", "developer"),
+ new DocData("john percival smith", "3", "designer"),
+ new DocData("jackson waits tom", "4", "project manager")
+ };
+
+ private IndexSearcher searcher;
+ private IndexReader reader;
+
+ string defaultFieldName = "name";
+
+ bool inOrder = true;
+
+ [Test]
+ public void TestComplexPhrases()
+ {
+ CheckMatches("\"john smith\"", "1"); // Simple multi-term still works
+ CheckMatches("\"j* smyth~\"", "1,2"); // wildcards and fuzzies are OK in
+ // phrases
+ CheckMatches("\"(jo* -john) smith\"", "2"); // boolean logic works
+ CheckMatches("\"jo* smith\"~2", "1,2,3"); // position logic works.
+ CheckMatches("\"jo* [sma TO smZ]\" ", "1,2"); // range queries supported
+ CheckMatches("\"john\"", "1,3"); // Simple single-term still works
+ CheckMatches("\"(john OR johathon) smith\"", "1,2"); // boolean logic with
+ // brackets works.
+ CheckMatches("\"(jo* -john) smyth~\"", "2"); // boolean logic with
+ // brackets works.
+
+ // CheckMatches("\"john -percival\"", "1"); // not logic doesn't work
+ // currently :(.
+
+ CheckMatches("\"john nosuchword*\"", ""); // phrases with clauses producing
+ // empty sets
+
+ CheckBadQuery("\"jo* id:1 smith\""); // mixing fields in a phrase is bad
+ CheckBadQuery("\"jo* \"smith\" \""); // phrases inside phrases is bad
+ }
+
+ [Test]
+ public void TestUnOrderedProximitySearches()
+ {
+ inOrder = true;
+ CheckMatches("\"smith jo*\"~2", ""); // ordered proximity produces empty set
+
+ inOrder = false;
+ CheckMatches("\"smith jo*\"~2", "1,2,3"); // un-ordered proximity
+ }
+
+ private void CheckBadQuery(String qString)
+ {
+ ComplexPhraseQueryParser qp = new ComplexPhraseQueryParser(TEST_VERSION_CURRENT, defaultFieldName, analyzer);
+ qp.InOrder = inOrder;
+ Exception expected = null;
+ try
+ {
+ qp.Parse(qString);
+ }
+ catch (Exception e)
+ {
+ expected = e;
+ }
+ assertNotNull("Expected parse error in " + qString, expected);
+ }
+
+ private void CheckMatches(string qString, string expectedVals)
+ {
+ ComplexPhraseQueryParser qp = new ComplexPhraseQueryParser(TEST_VERSION_CURRENT, defaultFieldName, analyzer);
+ qp.InOrder = inOrder;
+ qp.FuzzyPrefixLength = 1; // usually a good idea
+
+ Query q = qp.Parse(qString);
+
+ HashSet<string> expecteds = new HashSet<string>();
+ string[] vals = expectedVals.Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries);
+ for (int i = 0; i < vals.Length; i++)
+ {
+ if (vals[i].Length > 0)
+ expecteds.Add(vals[i]);
+ }
+
+ TopDocs td = searcher.Search(q, 10);
+ ScoreDoc[] sd = td.ScoreDocs;
+ for (int i = 0; i < sd.Length; i++)
+ {
+ Document doc = searcher.Doc(sd[i].Doc);
+ string id = doc.Get("id");
+ assertTrue(qString + "matched doc#" + id + " not expected", expecteds
+ .Contains(id));
+ expecteds.Remove(id);
+ }
+
+ assertEquals(qString + " missing some matches ", 0, expecteds.Count);
+ }
+
+ [Test]
+ public void TestFieldedQuery()
+ {
+ CheckMatches("name:\"john smith\"", "1");
+ CheckMatches("name:\"j* smyth~\"", "1,2");
+ CheckMatches("role:\"developer\"", "1,2");
+ CheckMatches("role:\"p* manager\"", "4");
+ CheckMatches("role:de*", "1,2,3");
+ CheckMatches("name:\"j* smyth~\"~5", "1,2,3");
+ CheckMatches("role:\"p* manager\" AND name:jack*", "4");
+ CheckMatches("+role:developer +name:jack*", "");
+ CheckMatches("name:\"john smith\"~2 AND role:designer AND id:3", "3");
+ }
+
+ [Test]
+ public void TestHashcodeEquals()
+ {
+ ComplexPhraseQueryParser qp = new ComplexPhraseQueryParser(TEST_VERSION_CURRENT, defaultFieldName, analyzer);
+ qp.InOrder = true;
+ qp.FuzzyPrefixLength = 1;
+
+ String qString = "\"aaa* bbb*\"";
+
+ Query q = qp.Parse(qString);
+ Query q2 = qp.Parse(qString);
+
+ assertEquals(q.GetHashCode(), q2.GetHashCode());
+ assertEquals(q, q2);
+
+ qp.InOrder = (false); // SOLR-6011
+
+ q2 = qp.Parse(qString);
+
+ // although the general contract of hashCode can't guarantee different values, if we only change one thing
+ // about a single query, it normally should result in a different value (and will with the current
+ // implementation in ComplexPhraseQuery)
+ assertTrue(q.GetHashCode() != q2.GetHashCode());
+ assertTrue(!q.equals(q2));
+ assertTrue(!q2.equals(q));
+ }
+
+ public override void SetUp()
+ {
+ base.SetUp();
+
+ analyzer = new MockAnalyzer(Random());
+ rd = NewDirectory();
+ using (IndexWriter w = new IndexWriter(rd, NewIndexWriterConfig(TEST_VERSION_CURRENT, analyzer)))
+ {
+ for (int i = 0; i < docsContent.Length; i++)
+ {
+ Document doc = new Document();
+ doc.Add(NewTextField("name", docsContent[i].Name, Field.Store.YES));
+ doc.Add(NewTextField("id", docsContent[i].Id, Field.Store.YES));
+ doc.Add(NewTextField("role", docsContent[i].Role, Field.Store.YES));
+ w.AddDocument(doc);
+ }
+ }
+ reader = DirectoryReader.Open(rd);
+ searcher = NewSearcher(reader);
+ }
+
+ public override void TearDown()
+ {
+ reader.Dispose();
+ rd.Dispose();
+ base.TearDown();
+ }
+
+
+ private class DocData
+ {
+ public DocData(string name, string id, string role)
+ {
+ this.Name = name;
+ this.Id = id;
+ this.Role = role;
+ }
+
+ public string Name { get; private set; }
+ public string Id { get; private set; }
+ public string Role { get; private set; }
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/lucenenet/blob/071b60ce/Lucene.Net.Tests.QueryParser/Lucene.Net.Tests.QueryParser.csproj
----------------------------------------------------------------------
diff --git a/Lucene.Net.Tests.QueryParser/Lucene.Net.Tests.QueryParser.csproj b/Lucene.Net.Tests.QueryParser/Lucene.Net.Tests.QueryParser.csproj
index 0f9e86c..b263dc8 100644
--- a/Lucene.Net.Tests.QueryParser/Lucene.Net.Tests.QueryParser.csproj
+++ b/Lucene.Net.Tests.QueryParser/Lucene.Net.Tests.QueryParser.csproj
@@ -47,6 +47,7 @@
<Compile Include="Classic\TestMultiFieldQueryParser.cs" />
<Compile Include="Classic\TestMultiPhraseQueryParsing.cs" />
<Compile Include="Classic\TestQueryParser.cs" />
+ <Compile Include="ComplexPhrase\TestComplexPhraseQuery.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Classic\TestMultiAnalyzer.cs" />
<Compile Include="Util\QueryParserTestBase.cs" />