Index: modules/queryparser/src/test/org/apache/lucene/queryparser/util/QueryParserTestBase.java =================================================================== --- modules/queryparser/src/test/org/apache/lucene/queryparser/util/QueryParserTestBase.java (revision 1305474) +++ modules/queryparser/src/test/org/apache/lucene/queryparser/util/QueryParserTestBase.java (working copy) @@ -404,6 +404,19 @@ assertQueryEquals("\" \"~2 germ", null, "germ"); assertQueryEquals("\"term germ\"~2^2", null, "\"term germ\"~2^2.0"); } + + public void testMinMatch() throws Exception { + assertQueryEquals("(term germ)~0", null, "term germ"); + assertQueryEquals("(term germ)~2", null, "(term germ)~2"); + assertQueryEquals("(term germ)~10", null, "(term germ)~10"); + assertQueryEquals("term (uerm verm)~1 (werm xerm)~2", null, "term ((uerm verm)~1) ((werm xerm)~2)"); + assertQueryEquals("term -(uerm verm)~1 (werm xerm)~2", null, "term -((uerm verm)~1) ((werm xerm)~2)"); + assertQueryEquals("(+term -germ rerm merm)~1", null, "(+term -germ rerm merm)~1"); + assertQueryEquals("(term germ)~2^2.0", null, "(term germ)~2^2.0"); + assertQueryEquals("(term germ)~2.0", null, "term germ"); + assertQueryEquals("(term)~0", null, "term"); + assertQueryEquals("(term)~2", null, "term"); + } public void testNumber() throws Exception { // The numbers go away because SimpleAnalzyer ignores them Index: modules/queryparser/src/java/org/apache/lucene/queryparser/classic/QueryParser.java =================================================================== --- modules/queryparser/src/java/org/apache/lucene/queryparser/classic/QueryParser.java (revision 1305474) +++ modules/queryparser/src/java/org/apache/lucene/queryparser/classic/QueryParser.java (working copy) @@ -222,7 +222,7 @@ final public Query Clause(String field) throws ParseException { Query q; - Token fieldToken=null, boost=null; + Token fieldToken=null, boost=null, minMatch=null; if (jj_2_1(2)) { switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { case TERM: @@ -261,21 +261,29 @@ q = Query(field); jj_consume_token(RPAREN); switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { + case FUZZY_SLOP: + minMatch = jj_consume_token(FUZZY_SLOP); + break; + default: + jj_la1[6] = jj_gen; + ; + } + switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { case CARAT: jj_consume_token(CARAT); boost = jj_consume_token(NUMBER); break; default: - jj_la1[6] = jj_gen; + jj_la1[7] = jj_gen; ; } break; default: - jj_la1[7] = jj_gen; + jj_la1[8] = jj_gen; jj_consume_token(-1); throw new ParseException(); } - {if (true) return handleBoost(q, boost);} + {if (true) return handleBoostAndMinMatch(q, boost, minMatch);} throw new Error("Missing return statement in function"); } @@ -324,7 +332,7 @@ term.image = term.image.substring(0,1); break; default: - jj_la1[8] = jj_gen; + jj_la1[9] = jj_gen; jj_consume_token(-1); throw new ParseException(); } @@ -334,7 +342,7 @@ fuzzy=true; break; default: - jj_la1[9] = jj_gen; + jj_la1[10] = jj_gen; ; } switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { @@ -347,12 +355,12 @@ fuzzy=true; break; default: - jj_la1[10] = jj_gen; + jj_la1[11] = jj_gen; ; } break; default: - jj_la1[11] = jj_gen; + jj_la1[12] = jj_gen; ; } q = handleBareTokenQuery(field, term, fuzzySlop, prefix, wildcard, fuzzy, regexp); @@ -368,7 +376,7 @@ jj_consume_token(RANGEEX_START); break; default: - jj_la1[12] = jj_gen; + jj_la1[13] = jj_gen; jj_consume_token(-1); throw new ParseException(); } @@ -380,7 +388,7 @@ goop1 = jj_consume_token(RANGE_QUOTED); break; default: - jj_la1[13] = jj_gen; + jj_la1[14] = jj_gen; jj_consume_token(-1); throw new ParseException(); } @@ -389,7 +397,7 @@ jj_consume_token(RANGE_TO); break; default: - jj_la1[14] = jj_gen; + jj_la1[15] = jj_gen; ; } switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { @@ -400,7 +408,7 @@ goop2 = jj_consume_token(RANGE_QUOTED); break; default: - jj_la1[15] = jj_gen; + jj_la1[16] = jj_gen; jj_consume_token(-1); throw new ParseException(); } @@ -413,7 +421,7 @@ jj_consume_token(RANGEEX_END); break; default: - jj_la1[16] = jj_gen; + jj_la1[17] = jj_gen; jj_consume_token(-1); throw new ParseException(); } @@ -423,7 +431,7 @@ boost = jj_consume_token(NUMBER); break; default: - jj_la1[17] = jj_gen; + jj_la1[18] = jj_gen; ; } boolean startOpen=false; @@ -447,7 +455,7 @@ fuzzySlop = jj_consume_token(FUZZY_SLOP); break; default: - jj_la1[18] = jj_gen; + jj_la1[19] = jj_gen; ; } switch ((jj_ntk==-1)?jj_ntk():jj_ntk) { @@ -456,13 +464,13 @@ boost = jj_consume_token(NUMBER); break; default: - jj_la1[19] = jj_gen; + jj_la1[20] = jj_gen; ; } q = handleQuotedTerm(field, term, fuzzySlop); break; default: - jj_la1[20] = jj_gen; + jj_la1[21] = jj_gen; jj_consume_token(-1); throw new ParseException(); } @@ -509,7 +517,7 @@ private Token jj_scanpos, jj_lastpos; private int jj_la; private int jj_gen; - final private int[] jj_la1 = new int[21]; + final private int[] jj_la1 = new int[22]; static private int[] jj_la1_0; static private int[] jj_la1_1; static { @@ -517,10 +525,10 @@ jj_la1_init_1(); } private static void jj_la1_init_0() { - jj_la1_0 = new int[] {0x300,0x300,0x1c00,0x1c00,0xfda7f00,0x120000,0x40000,0xfda6000,0x9d22000,0x200000,0x200000,0x40000,0x6000000,0x80000000,0x10000000,0x80000000,0x60000000,0x40000,0x200000,0x40000,0xfda2000,}; + jj_la1_0 = new int[] {0x300,0x300,0x1c00,0x1c00,0xfda7f00,0x120000,0x200000,0x40000,0xfda6000,0x9d22000,0x200000,0x200000,0x40000,0x6000000,0x80000000,0x10000000,0x80000000,0x60000000,0x40000,0x200000,0x40000,0xfda2000,}; } private static void jj_la1_init_1() { - jj_la1_1 = new int[] {0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x1,0x0,0x0,0x0,0x0,0x0,}; + jj_la1_1 = new int[] {0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x1,0x0,0x0,0x0,0x0,0x0,}; } final private JJCalls[] jj_2_rtns = new JJCalls[1]; private boolean jj_rescan = false; @@ -532,7 +540,7 @@ token = new Token(); jj_ntk = -1; jj_gen = 0; - for (int i = 0; i < 21; i++) jj_la1[i] = -1; + for (int i = 0; i < 22; i++) jj_la1[i] = -1; for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls(); } @@ -542,7 +550,7 @@ token = new Token(); jj_ntk = -1; jj_gen = 0; - for (int i = 0; i < 21; i++) jj_la1[i] = -1; + for (int i = 0; i < 22; i++) jj_la1[i] = -1; for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls(); } @@ -552,7 +560,7 @@ token = new Token(); jj_ntk = -1; jj_gen = 0; - for (int i = 0; i < 21; i++) jj_la1[i] = -1; + for (int i = 0; i < 22; i++) jj_la1[i] = -1; for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls(); } @@ -562,7 +570,7 @@ token = new Token(); jj_ntk = -1; jj_gen = 0; - for (int i = 0; i < 21; i++) jj_la1[i] = -1; + for (int i = 0; i < 22; i++) jj_la1[i] = -1; for (int i = 0; i < jj_2_rtns.length; i++) jj_2_rtns[i] = new JJCalls(); } @@ -679,7 +687,7 @@ la1tokens[jj_kind] = true; jj_kind = -1; } - for (int i = 0; i < 21; i++) { + for (int i = 0; i < 22; i++) { if (jj_la1[i] == jj_gen) { for (int j = 0; j < 32; j++) { if ((jj_la1_0[i] & (1< q=Query(field) ( boost=)? + | q=Query(field) (minMatch=)? ( boost=)? ) - { return handleBoost(q, boost); } + { return handleBoostAndMinMatch(q, boost, minMatch); } } Index: modules/queryparser/src/java/org/apache/lucene/queryparser/classic/ParseException.java =================================================================== --- modules/queryparser/src/java/org/apache/lucene/queryparser/classic/ParseException.java (revision 1305474) +++ modules/queryparser/src/java/org/apache/lucene/queryparser/classic/ParseException.java (working copy) @@ -59,18 +59,6 @@ } /** - * Creates a new ParseException which is wrapping another Throwable with an - * additional message - * - * @param message Message for the Exception - * @param throwable Wrapped Throwable - */ - public ParseException(String message, Throwable throwable) { - super(message, throwable); - specialConstructor = false; - } - - /** * This variable determines which constructor was used to create * this object and thereby affects the semantics of the * "getMessage" method (see below). Index: modules/queryparser/src/java/org/apache/lucene/queryparser/classic/QueryParserBase.java =================================================================== --- modules/queryparser/src/java/org/apache/lucene/queryparser/classic/QueryParserBase.java (revision 1305474) +++ modules/queryparser/src/java/org/apache/lucene/queryparser/classic/QueryParserBase.java (working copy) @@ -474,8 +474,10 @@ try { source = analyzer.tokenStream(field, new StringReader(queryText)); source.reset(); - } catch (IOException e) { - throw new ParseException("Unable to initialize TokenStream to analyze query text", e); + } catch (IOException tme) { + ParseException e = new ParseException("Unable to initialize TokenStream to analyze query text"); + e.initCause(tme); + throw e; } CachingTokenFilter buffer = new CachingTokenFilter(source); TermToBytesRefAttribute termAtt = null; @@ -484,8 +486,10 @@ try { buffer.reset(); - } catch (IOException e) { - throw new ParseException("Unable to initialize TokenStream to analyze query text", e); + } catch (IOException tme) { + ParseException e = new ParseException("Unable to initialize TokenStream to analyze query text"); + e.initCause(tme); + throw e; } if (buffer.hasAttribute(TermToBytesRefAttribute.class)) { @@ -523,8 +527,10 @@ // close original stream - all tokens buffered source.close(); } - catch (IOException e) { - throw new ParseException("Cannot close TokenStream analyzing query text", e); + catch (IOException tme) { + ParseException e = new ParseException("Cannot close TokenStream analyzing query text"); + e.initCause(tme); + throw e; } BytesRef bytes = termAtt == null ? null : termAtt.getBytesRef(); @@ -1071,9 +1077,13 @@ } return getFieldQuery(qfield, discardEscapeChar(term.image.substring(1, term.image.length()-1)), s); } + + Query handleBoost(Query q, Token boost) throws ParseException { + return handleBoostAndMinMatch(q, boost, null); + } // extracted from the .jj grammar - Query handleBoost(Query q, Token boost) throws ParseException { + Query handleBoostAndMinMatch(Query q, Token boost, Token minMatch) throws ParseException { if (boost != null) { float f = (float) 1.0; try { @@ -1090,6 +1100,22 @@ q.setBoost(f); } } + if (minMatch != null) { + int mm = 0; + try { + mm = Integer.valueOf(minMatch.image.substring(1)).intValue(); + } + catch (Exception ignored) { + /* Should this be handled somehow? (defaults to 0, if + * mm number is invalid) + */ + } + + if (q instanceof BooleanQuery) { + BooleanQuery bq = (BooleanQuery)q; + bq.setMinimumNumberShouldMatch(mm); + } // should we do something if it isn't a boolean query? + } return q; }