Index: CHANGES.txt
===================================================================
--- CHANGES.txt	(revision 727243)
+++ CHANGES.txt	(working copy)
@@ -182,6 +182,10 @@
 
 24. LUCENE-1131: Added numDeletedDocs method to IndexReader (Otis Gospodnetic)
 
+25. LUCENE-1494: Added MultiFieldSpanNearQuery, a variant of SpanNearQuery which
+    allows searching for terms in close proximity across different fields. 
+    (Paul Cowan)
+
 Bug fixes
     
  1. LUCENE-1134: Fixed BooleanQuery.rewrite to only optimize a single 
Index: src/java/org/apache/lucene/search/spans/MultiFieldSpanNearQuery.java
===================================================================
--- src/java/org/apache/lucene/search/spans/MultiFieldSpanNearQuery.java	(revision 0)
+++ src/java/org/apache/lucene/search/spans/MultiFieldSpanNearQuery.java	(revision 0)
@@ -0,0 +1,39 @@
+package org.apache.lucene.search.spans;
+
+/**
+ * <p>
+ * Variant of {@link SpanNearQuery} which does not require the clauses to refer
+ * to the same field. This can be useful for denormalized relational data: for
+ * example, when indexing a document with conceptually many 'children':
+ * </p>
+ * 
+ * <pre>
+ *  teacherid: 1
+ *  studentfirstname: james
+ *  studentsurname: jones
+ *  
+ *  teacherid: 2
+ *  studenfirstname: james
+ *  studentsurname: smith
+ *  studentfirstname: sally
+ *  studentsurname: jones
+ * </pre>
+ * 
+ * <p>
+ * a MultiFieldSpanNearQuery with a slop of 0 can be used to execute a search
+ * for 'studentfirstname:james studentsurname:jones' to find student James
+ * Jones, without matching teacherid 2 (which has a 'james' in position 0 and
+ * 'jones' in position 1.
+ * </p>
+ * <p>
+ * Note: as this class does not search a sngle field, it cannot meaningfully
+ * implement {@link #getField()}; the first field only will be returned. This
+ * affects the behaviour of, e.g.
+ * {@link #createWeight(org.apache.lucene.search.Searcher)} and explanations.
+ * </p>
+ */
+public class MultiFieldSpanNearQuery extends SpanNearQuery {
+  public MultiFieldSpanNearQuery(SpanQuery[] clauses, int slop, boolean inOrder) {
+    super(clauses, slop, inOrder, false);
+  }
+}

Property changes on: src/java/org/apache/lucene/search/spans/MultiFieldSpanNearQuery.java
___________________________________________________________________
Added: svn:mime-type
   + text/plain

Index: src/java/org/apache/lucene/search/spans/SpanNearQuery.java
===================================================================
--- src/java/org/apache/lucene/search/spans/SpanNearQuery.java	(revision 727243)
+++ src/java/org/apache/lucene/search/spans/SpanNearQuery.java	(working copy)
@@ -42,17 +42,25 @@
 
   /** Construct a SpanNearQuery.  Matches spans matching a span from each
    * clause, with up to <code>slop</code> total unmatched positions between
-   * them.  * When <code>inOrder</code> is true, the spans from each clause
-   * must be * ordered as in <code>clauses</code>. */
+   * them.  When <code>inOrder</code> is true, the spans from each clause
+   * must be ordered as in <code>clauses</code>. */
   public SpanNearQuery(SpanQuery[] clauses, int slop, boolean inOrder) {
-
+    this(clauses, slop, inOrder, true);
+  }
+  
+  /** Constructor for child classes to allow them to bypass the 'all clauses
+   * must refer to the same field' test.
+   * @see SpanNearQuery#SpanNearQuery(SpanQuery[], int, boolean)
+   */
+  protected SpanNearQuery(SpanQuery[] clauses, int slop, boolean inOrder,
+      boolean mustBeSameField) {
     // copy clauses array into an ArrayList
     this.clauses = new ArrayList(clauses.length);
     for (int i = 0; i < clauses.length; i++) {
       SpanQuery clause = clauses[i];
       if (i == 0) {                               // check field
         field = clause.getField();
-      } else if (!clause.getField().equals(field)) {
+      } else if (mustBeSameField && !clause.getField().equals(field)) {
         throw new IllegalArgumentException("Clauses must have same field.");
       }
       this.clauses.add(clause);
Index: src/test/org/apache/lucene/search/spans/TestBasics.java
===================================================================
--- src/test/org/apache/lucene/search/spans/TestBasics.java	(revision 727243)
+++ src/test/org/apache/lucene/search/spans/TestBasics.java	(working copy)
@@ -341,6 +341,18 @@
   }
 
 
+  public void testSpanAcrossMultipleFields() {
+    SpanTermQuery term1 = new SpanTermQuery(new Term("field1", "eight"));
+    SpanTermQuery term2 = new SpanTermQuery(new Term("field2", "one"));
+    try {
+      new SpanNearQuery(new SpanQuery[] {term1, term2},
+                                             0, true);
+      fail("Should not be able to create SpanNearQuery on 2 different fields");
+    } catch (IllegalArgumentException e) {
+      // Expected
+    }
+  }
+
   private void checkHits(Query query, int[] results) throws IOException {
     CheckHits.checkHits(query, "field", searcher, results);
   }
Index: src/test/org/apache/lucene/search/spans/TestMultiFieldSpanNearQuery.java
===================================================================
--- src/test/org/apache/lucene/search/spans/TestMultiFieldSpanNearQuery.java	(revision 0)
+++ src/test/org/apache/lucene/search/spans/TestMultiFieldSpanNearQuery.java	(revision 0)
@@ -0,0 +1,91 @@
+package org.apache.lucene.search.spans;
+
+import java.io.IOException;
+
+import org.apache.lucene.analysis.standard.StandardAnalyzer;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.CheckHits;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.store.FSDirectory;
+import org.apache.lucene.store.RAMDirectory;
+import org.apache.lucene.util.LuceneTestCase;
+
+public class TestMultiFieldSpanNearQuery extends LuceneTestCase {
+  private IndexSearcher searcher;
+  
+  private static final int INCREMENT_GAP = 100;
+
+  public void setUp() throws Exception {
+    super.setUp();
+    RAMDirectory directory = new RAMDirectory();
+    IndexWriter writer = new IndexWriter(directory, new StandardAnalyzer() {
+      @Override
+      public int getPositionIncrementGap(String fieldName) {
+        return INCREMENT_GAP;
+      }
+    }, true, IndexWriter.MaxFieldLength.LIMITED);
+
+    Document teacher1 = new Document();
+    addStudent(teacher1, "james", "jones");
+    addStudent(teacher1, "sally", "smith");
+    writer.addDocument(teacher1);
+
+    Document teacher2 = new Document();
+    addStudent(teacher2, "james", "smith");
+    addStudent(teacher2, "sally", "jones");
+    writer.addDocument(teacher2);
+
+    Document teacher3 = new Document();
+    addStudent(teacher3, "john jacob james", "jones");
+    writer.addDocument(teacher3);
+
+    addStudent(teacher1, "smith", "sally");
+
+    writer.close();
+
+    searcher = new IndexSearcher(directory);
+  }
+
+  public void testMultiFieldWithSpan0() throws IOException {
+    SpanNearQuery multiFieldSpan0 = new MultiFieldSpanNearQuery(
+        getSearchTerms(), 0, false);
+    // Only document 0 has 'james' and 'jones' in the same position
+    CheckHits.checkHits(multiFieldSpan0, "studentfirst", searcher,
+        new int[] { 0 });
+  }
+
+  public void testMultiFieldWithinFieldableBoundary() throws IOException {
+    SpanNearQuery multiFieldWithinFieldable = new MultiFieldSpanNearQuery(
+        getSearchTerms(), INCREMENT_GAP / 2, false);
+    // Only documents 0 & 2 have 'james' and 'jones' within the position
+    // increment gap
+    CheckHits.checkHits(multiFieldWithinFieldable, "studentfirst", searcher,
+        new int[] { 0, 2 });
+  }
+
+  public void testMultiFieldAcrossFieldableBoundary() throws IOException {
+    SpanNearQuery multiFieldAcrossFieldable = new MultiFieldSpanNearQuery(
+        getSearchTerms(), INCREMENT_GAP, false);
+    // All documents have both 'james' and 'jones' in adjacent terms
+    CheckHits.checkHits(multiFieldAcrossFieldable, "studentfirst", searcher,
+        new int[] { 0, 1, 2 });
+  }
+
+  private SpanQuery[] getSearchTerms() {
+    SpanTermQuery term1 = new SpanTermQuery(new Term("studentfirst", "james"));
+    SpanTermQuery term2 = new SpanTermQuery(new Term("studentsurname", "jones"));
+    return new SpanQuery[] { term1, term2 };
+  }
+
+  private void addStudent(Document teacher, String studentFirstName,
+      String studentSurname) {
+    teacher.add(new Field("studentfirst", studentFirstName, Field.Store.YES,
+        Field.Index.ANALYZED));
+    teacher.add(new Field("studentsurname", studentSurname, Field.Store.YES,
+        Field.Index.ANALYZED));
+  }
+
+}
\ No newline at end of file

Property changes on: src/test/org/apache/lucene/search/spans/TestMultiFieldSpanNearQuery.java
___________________________________________________________________
Added: svn:mime-type
   + text/plain

