You are viewing a plain text version of this content. The canonical link for it is here.
Posted to solr-commits@lucene.apache.org by yo...@apache.org on 2008/02/26 20:47:10 UTC

svn commit: r631357 [2/2] - in /lucene/solr/trunk: ./ client/java/solrj/src/org/apache/solr/client/solrj/response/ src/java/org/apache/solr/common/util/ src/java/org/apache/solr/handler/ src/java/org/apache/solr/handler/component/ src/java/org/apache/s...

Modified: lucene/solr/trunk/src/java/org/apache/solr/handler/component/SearchHandler.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/handler/component/SearchHandler.java?rev=631357&r1=631356&r2=631357&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/handler/component/SearchHandler.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/handler/component/SearchHandler.java Tue Feb 26 11:47:07 2008
@@ -17,41 +17,45 @@
 
 package org.apache.solr.handler.component;
 
-import org.apache.lucene.queryParser.ParseException;
-import org.apache.solr.common.SolrException;
-import org.apache.solr.common.params.CommonParams;
+import org.apache.solr.handler.RequestHandlerBase;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.RTimer;
-import org.apache.solr.core.SolrCore;
-import org.apache.solr.handler.RequestHandlerBase;
+import org.apache.solr.common.util.SimpleOrderedMap;
+import org.apache.solr.common.params.CommonParams;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.SolrException;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrQueryResponse;
+import org.apache.solr.client.solrj.SolrServer;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.request.QueryRequest;
+import org.apache.solr.client.solrj.impl.CommonsHttpSolrServer;
 import org.apache.solr.util.plugin.SolrCoreAware;
+import org.apache.solr.core.SolrCore;
+import org.apache.lucene.queryParser.ParseException;
+import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
+import org.apache.commons.httpclient.HttpClient;
 
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.logging.Logger;
+import java.util.*;
+import java.util.concurrent.*;
 
 /**
  *
  * Refer SOLR-281
  *
- * @since solr 1.3
  */
 public class SearchHandler extends RequestHandlerBase implements SolrCoreAware
 {
-  static final String RESPONSE_BUILDER_CONTEXT_KEY = "ResponseBuilder";
-  
   static final String INIT_COMPONENTS = "components";
   static final String INIT_FIRST_COMPONENTS = "first-components";
   static final String INIT_LAST_COMPONENTS = "last-components";
-  
+
   protected static Logger log = Logger.getLogger(SearchHandler.class.getName());
-  
+
   protected List<SearchComponent> components = null;
   protected NamedList initArgs = null;
-  
+
   @Override
   public void init(NamedList args) {
     super.init( args );
@@ -73,7 +77,7 @@
    * Initialize the components based on name
    */
   @SuppressWarnings("unchecked")
-  public void inform(SolrCore core) 
+  public void inform(SolrCore core)
   {
     Object declaredComponents = initArgs.get(INIT_COMPONENTS);
     List<String> first = (List<String>) initArgs.get(INIT_FIRST_COMPONENTS);
@@ -83,13 +87,13 @@
     if( declaredComponents == null ) {
       // Use the default component list
       list = getDefaultComponets();
-      
+
       if( first != null ) {
         List<String> clist = first;
         clist.addAll( list );
         list = clist;
       }
-      
+
       if( last != null ) {
         list.addAll( last );
       }
@@ -101,7 +105,7 @@
             "First/Last components only valid if you do not declare 'components'");
       }
     }
-    
+
     // Build the component list
     components = new ArrayList<SearchComponent>( list.size() );
     for(String c : list){
@@ -115,71 +119,147 @@
     return components;
   }
   
-  public static ResponseBuilder getResponseBuilder(SolrQueryRequest req) 
-  {
-    return (ResponseBuilder) req.getContext().get( RESPONSE_BUILDER_CONTEXT_KEY );
-  }
-  
-  //---------------------------------------------------------------------------------------
-  // SolrRequestHandler
-  //---------------------------------------------------------------------------------------
-  
+
   @Override
-  public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws IOException, ParseException, InstantiationException, IllegalAccessException 
+  public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception, ParseException, InstantiationException, IllegalAccessException
   {
-    ResponseBuilder builder = new ResponseBuilder();
-    req.getContext().put( RESPONSE_BUILDER_CONTEXT_KEY, builder );
-    
-    if( components == null ) {
-      throw new SolrException( SolrException.ErrorCode.SERVER_ERROR,
-          "SearchHandler not initialized properly.  No components registered." );
-    }
-    
-    // The semantics of debugging vs not debugging are distinct enough 
-    // to justify two control loops
-    if( !req.getParams().getBool( CommonParams.DEBUG_QUERY, false ) ) {
-      // Prepare
-      for( SearchComponent c : components ) {
-        c.prepare( req, rsp );
-      }
-  
-      // Process
+    ResponseBuilder rb = new ResponseBuilder();
+    rb.req = req;
+    rb.rsp = rsp;
+    rb.components = components;
+    rb.setDebug(req.getParams().getBool(CommonParams.DEBUG_QUERY, false));
+
+    final RTimer timer = rb.isDebug() ? new RTimer() : null;
+
+    if (timer == null) {
+      // non-debugging prepare phase
       for( SearchComponent c : components ) {
-        c.process( req, rsp );
+        c.prepare(rb);
       }
-    }
-    else {
-      builder.setDebug( true );
-      RTimer timer = new RTimer();
-      
-      // Prepare
+    } else {
+      // debugging prepare phase
       RTimer subt = timer.sub( "prepare" );
       for( SearchComponent c : components ) {
-        builder.setTimer( subt.sub( c.getName() ) );
-        c.prepare( req, rsp );
-        builder.getTimer().stop();
+        rb.setTimer( subt.sub( c.getName() ) );
+        c.prepare(rb);
+        rb.getTimer().stop();
       }
       subt.stop();
-  
-      // Process
-      subt = timer.sub( "process" );
-      for( SearchComponent c : components ) {
-        builder.setTimer( subt.sub( c.getName() ) );
-        c.process( req, rsp );
-        builder.getTimer().stop();
+    }
+
+    if (rb.shards == null) {
+      // a normal non-distributed request
+
+      // The semantics of debugging vs not debugging are different enough that
+      // it makes sense to have two control loops
+      if(!rb.isDebug()) {
+        // Process
+        for( SearchComponent c : components ) {
+          c.process(rb);
+        }
       }
-      subt.stop();
-      timer.stop();
-      
-      // add the timing info
-      builder.addDebugInfo( "timing", timer.asNamedList() );
+      else {
+        // Process
+        RTimer subt = timer.sub( "process" );
+        for( SearchComponent c : components ) {
+          rb.setTimer( subt.sub( c.getName() ) );
+          c.process(rb);
+          rb.getTimer().stop();
+        }
+        subt.stop();
+        timer.stop();
+
+        // add the timing info
+        if( rb.getDebugInfo() == null ) {
+          rb.setDebugInfo( new SimpleOrderedMap<Object>() );
+        }
+        rb.getDebugInfo().add( "timing", timer.asNamedList() );
+      }
+
+    } else {
+      // a distributed request
+
+      HttpCommComponent comm = new HttpCommComponent();
+
+      if (rb.outgoing == null) {
+        rb.outgoing = new LinkedList<ShardRequest>();
+      }
+      rb.finished = new ArrayList<ShardRequest>();
+
+      int nextStage = 0;
+      do {
+        rb.stage = nextStage;
+        nextStage = ResponseBuilder.STAGE_DONE;
+
+        // call all components
+        for( SearchComponent c : components ) {
+          // the next stage is the minimum of what all components report
+          nextStage = Math.min(nextStage, c.distributedProcess(rb));
+        }
+
+
+        // check the outgoing queue and send requests
+        while (rb.outgoing.size() > 0) {
+
+          // submit all current request tasks at once
+          while (rb.outgoing.size() > 0) {
+            ShardRequest sreq = rb.outgoing.remove(0);
+            sreq.actualShards = sreq.shards;
+            if (sreq.actualShards==ShardRequest.ALL_SHARDS) {
+              sreq.actualShards = rb.shards;
+            }
+            sreq.responses = new ArrayList<ShardResponse>();
+
+            // TODO: map from shard to address[]
+            for (String shard : sreq.actualShards) {
+              ModifiableSolrParams params = sreq.params;
+              params.remove("shards");      // not a top-level request
+              params.remove("indent");
+              params.remove("echoParams");
+              params.set("isShard", true);  // a sub (shard) request
+              comm.submit(sreq, shard, params);
+            }
+          }
+
+
+          // now wait for replies, but if anyone puts more requests on
+          // the outgoing queue, send them out immediately (by exiting
+          // this loop)
+          while (rb.outgoing.size() == 0) {
+            ShardResponse srsp = comm.takeCompletedOrError();
+            if (srsp == null) break;  // no more requests to wait for
+
+            // Was there an exception?  If so, abort everything and
+            // rethrow
+            if (srsp.exception != null) {
+              comm.cancelAll();
+              if (srsp.exception instanceof SolrException) {
+                throw (SolrException)srsp.exception;
+              } else {
+                throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, srsp.exception);
+              }
+            }
+
+            rb.finished.add(srsp.req);
+
+            // let the components see the responses to the request
+            for(SearchComponent c : components) {
+              c.handleResponses(rb, srsp.req);
+            }
+          }
+        }
+
+        for(SearchComponent c : components) {
+            c.finishStage(rb);
+         }
+
+        // we are done when the next stage is MAX_VALUE
+      } while (nextStage != Integer.MAX_VALUE);
     }
   }
 
-  //---------------------------------------------------------------------------------------
-  // SolrInfoMBeans
-  //---------------------------------------------------------------------------------------
-  
+  //////////////////////// SolrInfoMBeans methods //////////////////////
+
   @Override
   public String getDescription() {
     StringBuilder sb = new StringBuilder();
@@ -205,4 +285,140 @@
   public String getSource() {
     return "$URL$";
   }
+}
+
+
+// TODO: generalize how a comm component can fit into search component framework
+// TODO: statics should be per-core singletons
+
+class HttpCommComponent {
+
+  // We want an executor that doesn't take up any resources if
+  // it's not used, so it could be created statically for
+  // the distributed search component if desired.
+  //
+  // Consider CallerRuns policy and a lower max threads to throttle
+  // requests at some point (or should we simply return failure?)
+  static Executor commExecutor = new ThreadPoolExecutor(
+          0,
+          Integer.MAX_VALUE,
+          5, TimeUnit.SECONDS, // terminate idle threads after 5 sec
+          new SynchronousQueue<Runnable>()  // directly hand off tasks
+  );
+
+
+  static HttpClient client;
+
+  static {
+    MultiThreadedHttpConnectionManager mgr = new MultiThreadedHttpConnectionManager();
+    mgr.getParams().setDefaultMaxConnectionsPerHost(20);
+    mgr.getParams().setMaxTotalConnections(10000);
+    // mgr.getParams().setStaleCheckingEnabled(false);
+    client = new HttpClient(mgr);    
+  }
+
+  CompletionService<ShardResponse> completionService = new ExecutorCompletionService<ShardResponse>(commExecutor);
+  Set<Future<ShardResponse>> pending = new HashSet<Future<ShardResponse>>();
+
+  HttpCommComponent() {
+  }
+
+  void submit(final ShardRequest sreq, final String shard, final ModifiableSolrParams params) {
+    Callable<ShardResponse> task = new Callable<ShardResponse>() {
+      public ShardResponse call() throws Exception {
+
+        ShardResponse srsp = new ShardResponse();
+        srsp.req = sreq;
+        srsp.shard = shard;
+
+        try {
+          // String url = "http://" + shard + "/select";
+          String url = "http://" + shard;
+
+          params.remove("wt"); // use default (or should we explicitly set it?)
+          params.remove("version");
+
+          SolrServer server = new CommonsHttpSolrServer(url, client);
+          // SolrRequest req = new SolrRequest(SolrRequest.METHOD.GET, "/select");
+          // use generic request to avoid extra processing of queries
+          // QueryRequest req = new QueryRequest(sreq.params);
+          // srsp.rsp = server.request(req);
+          srsp.rsp = server.query(sreq.params);
+        } catch (Throwable th) {
+          srsp.exception = th;
+          if (th instanceof SolrException) {
+            srsp.rspCode = ((SolrException)th).code();
+          } else {
+            srsp.rspCode = -1;
+          }
+        }
+
+        return srsp;
+      }
+    };
+
+    pending.add( completionService.submit(task) );
+  }
+
+  /** returns a ShardResponse of the last response correlated with a ShardRequest */
+  ShardResponse take() {
+    while (pending.size() > 0) {
+      try {
+        Future<ShardResponse> future = completionService.take();
+        pending.remove(future);
+        ShardResponse rsp = future.get();
+        rsp.req.responses.add(rsp);
+        if (rsp.req.responses.size() == rsp.req.actualShards.length) {
+          return rsp;
+        }
+      } catch (InterruptedException e) {
+        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
+      } catch (ExecutionException e) {
+        // should be impossible... the problem with catching the exception
+        // at this level is we don't know what ShardRequest it applied to
+        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Impossible Exception",e);
+      }
+    }
+    return null;
+  }
+
+
+  /** returns a ShardResponse of the last response correlated with a ShardRequest,
+   * or immediately returns a ShardResponse if there was an error detected
+   */
+  ShardResponse takeCompletedOrError() {
+    while (pending.size() > 0) {
+      try {
+        Future<ShardResponse> future = completionService.take();
+        pending.remove(future);
+        ShardResponse rsp = future.get();
+        if (rsp.exception != null) return rsp; // if exception, return immediately
+        // add response to the response list... we do this after the take() and
+        // not after the completion of "call" so we know when the last response
+        // for a request was received.  Otherwise we might return the same
+        // request more than once.
+        rsp.req.responses.add(rsp);
+        if (rsp.req.responses.size() == rsp.req.actualShards.length) {
+          return rsp;
+        }
+      } catch (InterruptedException e) {
+        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
+      } catch (ExecutionException e) {
+        // should be impossible... the problem with catching the exception
+        // at this level is we don't know what ShardRequest it applied to
+        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Impossible Exception",e);
+      }
+    }
+    return null;
+  }
+
+
+  void cancelAll() {
+    for (Future<ShardResponse> future : pending) {
+      // TODO: any issues with interrupting?  shouldn't be if
+      // there are finally blocks to release connections.
+      future.cancel(true);
+    }
+  }
+
 }

Added: lucene/solr/trunk/src/java/org/apache/solr/handler/component/ShardDoc.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/handler/component/ShardDoc.java?rev=631357&view=auto
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/handler/component/ShardDoc.java (added)
+++ lucene/solr/trunk/src/java/org/apache/solr/handler/component/ShardDoc.java Tue Feb 26 11:47:07 2008
@@ -0,0 +1,269 @@
+package org.apache.solr.handler.component;
+
+import org.apache.lucene.search.SortComparatorSource;
+import org.apache.lucene.search.SortField;
+import org.apache.lucene.util.PriorityQueue;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.search.MissingStringLastComparatorSource;
+
+import java.text.Collator;
+import java.util.Comparator;
+import java.util.Locale;
+import java.util.List;
+import java.util.ArrayList;
+
+public class ShardDoc {
+  public String shard;
+  public String shardAddress;  // TODO
+  
+  int orderInShard;
+    // the position of this doc within the shard... this can be used
+    // to short-circuit comparisons if the shard is equal, and can
+    // also be used to break ties within the same shard.
+
+  Object id;
+    // this is currently the uniqueKeyField but
+    // may be replaced with internal docid in a future release.
+
+  Float score;
+
+  NamedList sortFieldValues;
+  // sort field values for *all* docs in a particular shard.
+  // this doc's values are in position orderInShard
+
+  // TODO: store the SolrDocument here?
+  // Store the order in the merged list for lookup when getting stored fields?
+  // (other components need this ordering to store data in order, like highlighting)
+  // but we shouldn't expose uniqueKey (have a map by it) until the stored-field
+  // retrieval stage.
+
+  int positionInResponse;
+  // the ordinal position in the merged response arraylist  
+
+  public String toString(){
+    return "id="+id
+            +" ,score="+score
+            +" ,shard="+shard
+            +" ,orderInShard="+orderInShard
+            +" ,positionInResponse="+positionInResponse
+            +" ,sortFieldValues="+sortFieldValues;
+  }
+}
+
+
+
+// used by distributed search to merge results.
+class ShardFieldSortedHitQueue extends PriorityQueue {
+
+  /** Stores a comparator corresponding to each field being sorted by */
+  protected Comparator[] comparators;
+
+  /** Stores the sort criteria being used. */
+  protected SortField[] fields;
+
+  /** The order of these fieldNames should correspond to the order of sort field values retrieved from the shard */
+  protected List<String> fieldNames = new ArrayList<String>();
+
+  public ShardFieldSortedHitQueue(SortField[] fields, int size) {
+    final int n = fields.length;
+    comparators = new Comparator[n];
+    this.fields = new SortField[n];
+    for (int i = 0; i < n; ++i) {
+
+      // keep track of the named fields
+      int type = fields[i].getType();
+      if (type!=SortField.SCORE && type!=SortField.DOC) {
+        fieldNames.add(fields[i].getField());
+      }
+
+      String fieldname = fields[i].getField();
+      comparators[i] = getCachedComparator(fieldname, fields[i]
+          .getType(), fields[i].getLocale(), fields[i].getFactory());
+
+     if (fields[i].getType() == SortField.STRING) {
+        this.fields[i] = new SortField(fieldname, fields[i].getLocale(),
+            fields[i].getReverse());
+      } else {
+        this.fields[i] = new SortField(fieldname, fields[i].getType(),
+            fields[i].getReverse());
+      }
+
+      //System.out.println("%%%%%%%%%%%%%%%%%% got "+fields[i].getType() +"   for "+ fieldname +"  fields[i].getReverse(): "+fields[i].getReverse());
+    }
+
+    initialize(size);
+  }
+
+  @Override
+  protected boolean lessThan(Object objA, Object objB) {
+    ShardDoc docA = (ShardDoc)objA;
+    ShardDoc docB = (ShardDoc)objB;
+
+    // If these docs are from the same shard, then the relative order
+    // is how they appeared in the response from that shard.    
+    if (docA.shard == docB.shard) {
+      // if docA has a smaller position, it should be "larger" so it
+      // comes before docB.
+      // This will handle sorting by docid within the same shard
+
+      // comment this out to test comparators.
+      return !(docA.orderInShard < docB.orderInShard);
+    }
+
+
+    // run comparators
+    final int n = comparators.length;
+    int c = 0;
+    for (int i = 0; i < n && c == 0; i++) {
+      c = (fields[i].getReverse()) ? comparators[i].compare(docB, docA)
+          : comparators[i].compare(docA, docB);
+    }
+
+    // solve tiebreaks by comparing shards (similar to using docid)
+    // smaller docid's beat larger ids, so reverse the natural ordering
+    if (c == 0) {
+      c = -docA.shard.compareTo(docB.shard);
+    }
+
+    return c < 0;
+  }
+
+  Comparator getCachedComparator(String fieldname, int type, Locale locale, SortComparatorSource factory) {
+    Comparator comparator = null;
+    switch (type) {
+    case SortField.SCORE:
+      comparator = comparatorScore(fieldname);
+      break;
+    case SortField.STRING:
+      if (locale != null)
+        comparator = comparatorStringLocale(fieldname, locale);
+      else
+        comparator = comparatorNatural(fieldname);
+      break;
+    case SortField.CUSTOM:
+      if (factory instanceof MissingStringLastComparatorSource){
+        comparator = comparatorMissingStringLast(fieldname);
+      } else {
+        // TODO: support other types such as random... is there a way to
+        // support generically?  Perhaps just comparing Object
+        comparator = comparatorNatural(fieldname);
+        // throw new RuntimeException("Custom sort not supported factory is "+factory.getClass());
+      }
+      break;
+    case SortField.DOC:
+      // TODO: we can support this!
+      throw new RuntimeException("Doc sort not supported");
+    default:
+      comparator = comparatorNatural(fieldname);
+      break;
+    }
+    return comparator;
+  }
+
+  class ShardComparator implements Comparator {
+    String fieldName;
+    int fieldNum;
+    public ShardComparator(String fieldName) {
+      this.fieldName = fieldName;
+      this.fieldNum=0;
+      for (int i=0; i<fieldNames.size(); i++) {
+        if (fieldNames.get(i).equals(fieldName)) {
+          this.fieldNum = i;
+          break;
+        }
+      }
+    }
+
+    Object sortVal(ShardDoc shardDoc) {
+      assert(shardDoc.sortFieldValues.getName(fieldNum).equals(fieldName));
+      List lst = (List)shardDoc.sortFieldValues.getVal(fieldNum);
+      return lst.get(shardDoc.orderInShard);
+    }
+
+    public int compare(Object o1, Object o2) {
+      return 0;
+    }
+  }
+
+  static Comparator comparatorScore(final String fieldName) {
+    return new Comparator() {
+      public final int compare(final Object o1, final Object o2) {
+        ShardDoc e1 = (ShardDoc) o1;
+        ShardDoc e2 = (ShardDoc) o2;
+
+        final float f1 = e1.score;
+        final float f2 = e2.score;
+        if (f1 < f2)
+          return -1;
+        if (f1 > f2)
+          return 1;
+        return 0;
+      }
+    };
+  }
+
+  // The lucene natural sort ordering corresponds to numeric
+  // and string natural sort orderings (ascending).  Since
+  // the PriorityQueue keeps the biggest elements by default,
+  // we need to reverse the natural compare ordering so that the
+  // smallest elements are kept instead of the largest... hence
+  // the negative sign on the final compareTo().
+  Comparator comparatorNatural(String fieldName) {
+    return new ShardComparator(fieldName) {
+      public final int compare(final Object o1, final Object o2) {
+        ShardDoc sd1 = (ShardDoc) o1;
+        ShardDoc sd2 = (ShardDoc) o2;
+        Comparable v1 = (Comparable)sortVal(sd1);
+        Comparable v2 = (Comparable)sortVal(sd2);
+        if (v1==v2)
+          return 0;
+        if (v1==null)
+          return 1;
+        if(v2==null)
+          return -1;
+        return -v1.compareTo(v2);
+      }
+    };
+  }
+
+
+  Comparator comparatorStringLocale(final String fieldName,
+      Locale locale) {
+    final Collator collator = Collator.getInstance(locale);
+    return new ShardComparator(fieldName) {
+      public final int compare(final Object o1, final Object o2) {
+        ShardDoc sd1 = (ShardDoc) o1;
+        ShardDoc sd2 = (ShardDoc) o2;
+        Comparable v1 = (Comparable)sortVal(sd1);
+        Comparable v2 = (Comparable)sortVal(sd2);
+        if (v1==v2)
+          return 0;
+        if (v1==null)
+          return 1;
+        if(v2==null)
+          return -1;
+        return -collator.compare(v1,v2);
+      }
+    };
+  }
+
+
+  Comparator comparatorMissingStringLast(final String fieldName) {
+     return new ShardComparator(fieldName) {
+      public final int compare(final Object o1, final Object o2) {
+        ShardDoc sd1 = (ShardDoc) o1;
+        ShardDoc sd2 = (ShardDoc) o2;
+        Comparable v1 = (Comparable)sortVal(sd1);
+        Comparable v2 = (Comparable)sortVal(sd2);
+        if (v1==v2)
+          return 0;
+        if (v1==null)
+          return -1;
+        if(v2==null)
+          return 1;
+        return -v1.compareTo(v2);
+      }
+    };
+  }
+
+}
\ No newline at end of file

Propchange: lucene/solr/trunk/src/java/org/apache/solr/handler/component/ShardDoc.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: lucene/solr/trunk/src/java/org/apache/solr/handler/component/ShardDoc.java
------------------------------------------------------------------------------
    svn:executable = *

Propchange: lucene/solr/trunk/src/java/org/apache/solr/handler/component/ShardDoc.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Added: lucene/solr/trunk/src/java/org/apache/solr/handler/component/ShardRequest.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/handler/component/ShardRequest.java?rev=631357&view=auto
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/handler/component/ShardRequest.java (added)
+++ lucene/solr/trunk/src/java/org/apache/solr/handler/component/ShardRequest.java Tue Feb 26 11:47:07 2008
@@ -0,0 +1,70 @@
+package org.apache.solr.handler.component;
+
+import org.apache.solr.client.solrj.SolrResponse;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.util.NamedList;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+// todo... when finalized make accessors
+public class ShardRequest {
+  public final static String[] ALL_SHARDS = null;
+
+  public final static int PURPOSE_PRIVATE         = 0x01;
+  public final static int PURPOSE_GET_TERM_DFS    = 0x02;
+  public final static int PURPOSE_GET_TOP_IDS     = 0x04;
+  public final static int PURPOSE_REFINE_TOP_IDS  = 0x08;
+  public final static int PURPOSE_GET_FACETS      = 0x10;
+  public final static int PURPOSE_REFINE_FACETS   = 0x20;
+  public final static int PURPOSE_GET_FIELDS      = 0x40;
+  public final static int PURPOSE_GET_HIGHLIGHTS  = 0x80;
+  public final static int PURPOSE_GET_DEBUG       =0x100;
+
+  public int purpose;  // the purpose of this request
+
+  public String[] shards;  // the shards this request should be sent to, null for all
+// TODO: how to request a specific shard address?
+
+
+  public ModifiableSolrParams params;
+
+
+  /** list of responses... filled out by framework */
+  public List<ShardResponse> responses = new ArrayList<ShardResponse>();
+
+  /** actual shards to send the request to, filled out by framework */
+  public String[] actualShards;
+
+  // TODO: one could store a list of numbers to correlate where returned docs
+  // go in the top-level response rather than looking up by id...
+  // this would work well if we ever transitioned to using internal ids and
+  // didn't require a uniqueId
+
+  public String toString() {
+    return "ShardRequest:{params=" + params
+            + ", purpose=" + Integer.toHexString(purpose)
+            + ", nResponses =" + responses.size()
+            + "}";
+  }
+}
+
+
+class ShardResponse {
+  public ShardRequest req;
+  public String shard;
+  public String shardAddress;  // the specific shard that this response was received from  
+  public int rspCode;
+  public Throwable exception;
+  public SolrResponse rsp;
+
+  public String toString() {
+    return "ShardResponse:{shard="+shard+",shardAddress="+shardAddress
+            +"\n\trequest=" + req
+            +"\n\tresponse=" + rsp
+            + (exception==null ? "" : "\n\texception="+ SolrException.toStr(exception)) 
+            +"\n}";
+  }
+}
\ No newline at end of file

Propchange: lucene/solr/trunk/src/java/org/apache/solr/handler/component/ShardRequest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: lucene/solr/trunk/src/java/org/apache/solr/handler/component/ShardRequest.java
------------------------------------------------------------------------------
    svn:executable = *

Propchange: lucene/solr/trunk/src/java/org/apache/solr/handler/component/ShardRequest.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Modified: lucene/solr/trunk/src/java/org/apache/solr/request/SimpleFacets.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/request/SimpleFacets.java?rev=631357&r1=631356&r2=631357&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/request/SimpleFacets.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/request/SimpleFacets.java Tue Feb 26 11:47:07 2008
@@ -62,12 +62,13 @@
   protected SolrParams params;
   /** Searcher to use for all calculations */
   protected SolrIndexSearcher searcher;
+  protected SolrQueryRequest req;
 
-
-  public SimpleFacets(SolrIndexSearcher searcher,
+  public SimpleFacets(SolrQueryRequest req,
                       DocSet docs,
                       SolrParams params) {
-    this.searcher = searcher;
+    this.req = req;
+    this.searcher = req.getSearcher();
     this.docs = docs;
     this.params = params;
   }
@@ -117,12 +118,13 @@
      * If user doesn't want schema default for facet.query, they should be
      * explicit.
      */
-    SolrQueryParser qp = searcher.getSchema().getSolrQueryParser(null);
+    // SolrQueryParser qp = searcher.getSchema().getSolrQueryParser(null);
 
     String[] facetQs = params.getParams(FacetParams.FACET_QUERY);
     if (null != facetQs && 0 != facetQs.length) {
       for (String q : facetQs) {
-        res.add(q, searcher.numDocs(qp.parse(q), docs));
+        Query qobj = QParser.getParser(q, null, req).getQuery();
+        res.add(q, searcher.numDocs(qobj, docs));
       }
     }
 

Modified: lucene/solr/trunk/src/java/org/apache/solr/request/XMLWriter.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/request/XMLWriter.java?rev=631357&r1=631356&r2=631357&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/request/XMLWriter.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/request/XMLWriter.java Tue Feb 26 11:47:07 2008
@@ -473,6 +473,7 @@
         SolrIndexSearcher searcher = request.getSearcher();
         DocIterator iterator = ids.iterator();
         int sz = ids.size();
+        includeScore = includeScore && ids.hasScores();
         for (int i=0; i<sz; i++) {
           int id = iterator.nextDoc();
           Document doc = searcher.doc(id, fields);

Modified: lucene/solr/trunk/src/java/org/apache/solr/search/MissingStringLastComparatorSource.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/search/MissingStringLastComparatorSource.java?rev=631357&r1=631356&r2=631357&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/search/MissingStringLastComparatorSource.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/search/MissingStringLastComparatorSource.java Tue Feb 26 11:47:07 2008
@@ -33,7 +33,7 @@
  */
 
 // move to apache package and make public if it is accepted as a patch
-class MissingStringLastComparatorSource implements SortComparatorSource {
+public class MissingStringLastComparatorSource implements SortComparatorSource {
 
   public static final String bigString="\uffff\uffff\uffff\uffff\uffff\uffff\uffff\uffffNULL_VAL";
 

Modified: lucene/solr/trunk/src/java/org/apache/solr/search/QParser.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/search/QParser.java?rev=631357&r1=631356&r2=631357&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/search/QParser.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/search/QParser.java Tue Feb 26 11:47:07 2008
@@ -197,7 +197,7 @@
 
     QParserPlugin qplug = req.getCore().getQueryPlugin(type);
     return qplug.createParser(qstr, localParams, req.getParams(), req);
-  }
+  }                            
 
 }
 

Modified: lucene/solr/trunk/src/java/org/apache/solr/search/SolrIndexSearcher.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/search/SolrIndexSearcher.java?rev=631357&r1=631356&r2=631357&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/search/SolrIndexSearcher.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/search/SolrIndexSearcher.java Tue Feb 26 11:47:07 2008
@@ -519,8 +519,15 @@
 
   private static Query matchAllDocsQuery = new MatchAllDocsQuery();
 
-
-  protected DocSet getDocSet(List<Query> queries) throws IOException {
+  /**
+   * Returns the set of document ids matching all queries.
+   * This method is cache-aware and attempts to retrieve the answer from the cache if possible.
+   * If the answer was not cached, it may have been inserted into the cache as a result of this call.
+   * This method can handle negative queries.
+   * <p>
+   * The DocSet returned should <b>not</b> be modified.
+   */
+  public DocSet getDocSet(List<Query> queries) throws IOException {
     if (queries==null) return null;
     if (queries.size()==1) return getDocSet(queries.get(0));
     DocSet answer=null;

Modified: lucene/solr/trunk/src/java/org/apache/solr/search/SortSpec.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/search/SortSpec.java?rev=631357&r1=631356&r2=631357&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/search/SortSpec.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/search/SortSpec.java Tue Feb 26 11:47:07 2008
@@ -18,6 +18,7 @@
 package org.apache.solr.search;
 
 import org.apache.lucene.search.Sort;
+import org.apache.lucene.search.SortField;
 
 /***
  * SortSpec encapsulates a Lucene Sort and a count of the number of documents
@@ -42,6 +43,18 @@
   public void setSort( Sort s )
   {
     sort = s;
+  }
+
+  public static boolean includesScore(Sort sort) {
+    if (sort==null) return true;
+    for (SortField sf : sort.getSort()) {
+      if (sf.getType() == SortField.SCORE) return true;
+    }
+    return false;
+  }
+
+  public boolean includesScore() {
+    return includesScore(sort);
   }
 
   /**

Modified: lucene/solr/trunk/src/java/org/apache/solr/search/Sorting.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/search/Sorting.java?rev=631357&r1=631356&r2=631357&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/search/Sorting.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/search/Sorting.java Tue Feb 26 11:47:07 2008
@@ -52,6 +52,6 @@
   }
 
 
-  static final SortComparatorSource nullStringLastComparatorSource = new MissingStringLastComparatorSource();
+  static final SortComparatorSource nullStringLastComparatorSource = new MissingStringLastComparatorSource(null);
 }
 

Modified: lucene/solr/trunk/src/java/org/apache/solr/util/SolrPluginUtils.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/util/SolrPluginUtils.java?rev=631357&r1=631356&r2=631357&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/util/SolrPluginUtils.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/util/SolrPluginUtils.java Tue Feb 26 11:47:07 2008
@@ -437,11 +437,12 @@
 
       Document doc = searcher.doc(id);
       String strid = schema.printableUniqueKey(doc);
-      String docname = "";
-      if (strid != null) docname="id="+strid+",";
-      docname = docname + "internal_docid="+id;
 
-      explainList.add(docname, "\n" +explain.toString());
+      // String docname = "";
+      // if (strid != null) docname="id="+strid+",";
+      // docname = docname + "internal_docid="+id;
+
+      explainList.add(strid, "\n" +explain.toString());
     }
     return explainList;
   }

Modified: lucene/solr/trunk/src/test/org/apache/solr/BasicFunctionalityTest.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/test/org/apache/solr/BasicFunctionalityTest.java?rev=631357&r1=631356&r2=631357&view=diff
==============================================================================
--- lucene/solr/trunk/src/test/org/apache/solr/BasicFunctionalityTest.java (original)
+++ lucene/solr/trunk/src/test/org/apache/solr/BasicFunctionalityTest.java Tue Feb 26 11:47:07 2008
@@ -225,7 +225,7 @@
     String resp = h.query(lrf.makeRequest("q", "text:hello", "debugQuery", "true"));
     //System.out.println(resp);
     // second doc ranked first
-    assertTrue( resp.indexOf("id=2") < resp.indexOf("id=1") );
+    assertTrue( resp.indexOf("\"2\"") < resp.indexOf("\"1\"") );
   }
 
   public void testFieldBoost() throws Exception {
@@ -243,7 +243,7 @@
     String resp = h.query(lrf.makeRequest("q", "text:hello", "debugQuery", "true"));
     //System.out.println(resp);
     // second doc ranked first
-    assertTrue( resp.indexOf("id=2") < resp.indexOf("id=1") );
+    assertTrue( resp.indexOf("\"2\"") < resp.indexOf("\"1\"") );
   }
 
   public void testXMLWriter() throws Exception {

Added: lucene/solr/trunk/src/test/org/apache/solr/TestDistributedSearch.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/test/org/apache/solr/TestDistributedSearch.java?rev=631357&view=auto
==============================================================================
--- lucene/solr/trunk/src/test/org/apache/solr/TestDistributedSearch.java (added)
+++ lucene/solr/trunk/src/test/org/apache/solr/TestDistributedSearch.java Tue Feb 26 11:47:07 2008
@@ -0,0 +1,504 @@
+/**
+ * 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.
+ */
+
+package org.apache.solr;
+
+import org.apache.solr.client.solrj.SolrServer;
+import org.apache.solr.client.solrj.SolrRequest;
+import org.apache.solr.client.solrj.SolrResponse;
+import org.apache.solr.client.solrj.response.QueryResponse;
+import org.apache.solr.client.solrj.request.QueryRequest;
+import org.apache.solr.client.solrj.embedded.JettySolrRunner;
+import org.apache.solr.client.solrj.impl.CommonsHttpSolrServer;
+import org.apache.solr.common.SolrInputDocument;
+import org.apache.solr.common.SolrDocumentList;
+import org.apache.solr.common.SolrDocument;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.params.ModifiableSolrParams;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.util.AbstractSolrTestCase;
+
+import java.io.File;
+import java.util.*;
+
+import junit.framework.TestCase;
+
+/**
+ * TODO? perhaps use:
+ *  http://docs.codehaus.org/display/JETTY/ServletTester
+ * rather then open a real connection?
+ *
+ * @version $Id$
+ * @since solr 1.3
+ */
+public class TestDistributedSearch extends TestCase {
+
+  Random r = new Random(0);
+  File testDir;
+  
+  int controlPort = 8985;
+  SolrServer controlClient;
+  JettySolrRunner controlJetty;
+
+  int[] ports = new int[] {7574, 7576};
+  List<SolrServer> clients = new ArrayList<SolrServer>();
+  List<JettySolrRunner> jettys = new ArrayList<JettySolrRunner>();
+  String context = "/solr";
+  String shards;
+
+
+  String id="id";
+  String t1="a_t";
+  String i1="a_i";
+
+
+  @Override public void setUp() throws Exception
+  {
+    System.setProperty("solr.test.sys.prop1", "propone");
+    System.setProperty("solr.test.sys.prop2", "proptwo");
+    testDir = new File(System.getProperty("java.io.tmpdir")
+        + System.getProperty("file.separator")
+        + getClass().getName() + "-" + System.currentTimeMillis());
+    testDir.mkdirs();
+  }
+
+  @Override public void tearDown() throws Exception
+  {
+    destroyServers();
+    AbstractSolrTestCase.recurseDelete(testDir);
+  }
+
+
+  private void createServers() throws Exception {
+    controlJetty = createJetty(controlPort);
+    controlClient = createNewSolrServer(controlPort);
+
+    StringBuilder sb = new StringBuilder();
+    for (int port : ports) {
+      if (sb.length()>0) sb.append(',');
+      sb.append("localhost:"+port+context);
+      jettys.add(createJetty(port));
+      clients.add(createNewSolrServer(port));
+    }
+
+    shards = sb.toString();
+  }
+
+  private void destroyServers() throws Exception {
+    controlJetty.stop();
+    for (JettySolrRunner jetty : jettys) jetty.stop();
+    clients.clear();
+    jettys.clear();    
+  }
+
+  private JettySolrRunner createJetty(int port) throws Exception {
+    File subDir = new File(testDir, ""+port);
+    subDir.mkdirs();
+    System.setProperty("solr.data.dir", subDir.toString());
+
+    JettySolrRunner jetty = new JettySolrRunner("/solr", port);
+    jetty.start();
+    return jetty;
+  }
+
+  protected SolrServer createNewSolrServer(int port)
+  {
+    try {
+      // setup the server...
+      String url = "http://localhost:"+port+context;
+      CommonsHttpSolrServer s = new CommonsHttpSolrServer( url );
+      s.setConnectionTimeout(100); // 1/10th sec
+      s.setDefaultMaxConnectionsPerHost(100);
+      s.setMaxTotalConnections(100);
+      return s;
+    }
+    catch( Exception ex ) {
+      throw new RuntimeException( ex );
+    }
+  }
+
+
+  void index(Object... fields) throws Exception {
+    SolrInputDocument doc = new SolrInputDocument();
+    for (int i=0; i<fields.length; i+=2) {
+      doc.addField((String)(fields[i]), fields[i+1]);
+    }
+    controlClient.add(doc);
+
+    int which = (doc.getField(id).toString().hashCode() &0x7fffffff) % clients.size();
+    SolrServer client = clients.get(which);
+    client.add(doc);
+  }
+
+  void index_specific(int serverNumber, Object... fields) throws Exception {
+    SolrInputDocument doc = new SolrInputDocument();
+    for (int i=0; i<fields.length; i+=2) {
+      doc.addField((String)(fields[i]), fields[i+1]);
+    }
+    controlClient.add(doc);
+
+    int which = serverNumber;
+    SolrServer client = clients.get(which);
+    client.add(doc);
+  }
+
+  void del(String q) throws Exception {
+    controlClient.deleteByQuery(q);
+    for (SolrServer client : clients) {
+      client.deleteByQuery(q);
+    }
+  }
+
+
+  // serial commit...
+  void commit() throws Exception {
+    controlClient.commit();
+    for (SolrServer client : clients) client.commit();    
+  }
+
+  void query(Object... q) throws Exception {
+    ModifiableSolrParams params = new ModifiableSolrParams();
+
+    for (int i=0; i<q.length; i+=2) {
+      params.add(q[i].toString(), q[i+1].toString());
+    }
+
+    QueryResponse controlRsp = controlClient.query(params);
+
+    // query a random server
+    params.set("shards", shards);
+    int which = r.nextInt(clients.size());
+    SolrServer client = clients.get(which);
+    QueryResponse rsp = client.query(params);
+
+    compareResponses(rsp, controlRsp);
+  }
+
+
+  private static int ORDERED=1;
+  private static int SKIP=2;
+  private static int SKIPVAL=4;
+  private static int UNORDERED=8;
+
+
+  public static boolean eq(String a, String b) {
+    return a==b || (a != null && a.equals(b));
+  }
+
+  public static int flags(Map<String,Integer> handle, Object key) {
+    if (handle==null) return 0;
+    Integer f = handle.get(key);
+    return f == null ? 0 : f;
+  }
+
+  public static String compare(NamedList a, NamedList b, int flags, Map<String,Integer> handle) {
+    boolean ordered = (flags&UNORDERED) == 0;
+
+    int posa = 0, posb = 0;
+    int na = 0, nb = 0;
+
+    for(;;) {
+      if (posa >= a.size() || posb >= b.size()) {
+        break;
+      }
+
+      String namea=null, nameb=null;
+      Object vala=null, valb=null;
+
+      int flagsa, flagsb;
+      for (;;) {
+        namea = a.getName(posa);
+        vala = a.getVal(posa);
+        posa++;
+        flagsa = flags(handle, namea);
+        if ((flagsa & SKIP) != 0) continue;
+        na++;
+        break;
+      }
+
+      if (!ordered) posb=0;  // reset if not ordered
+
+      while (posb<b.size()) {
+        nameb = b.getName(posb);
+        valb = b.getVal(posb);
+        posb++;
+        flagsb = flags(handle, nameb);
+        if ((flagsb & SKIP) != 0) continue;
+        if (eq(namea, nameb)) {
+          nb++;
+          break;
+        }
+        if (ordered) {
+          return "."+namea+"!="+nameb+" (unordered or missing)";
+        }
+        // if unordered, continue until we find the right field.
+      }
+
+      // ok, namea and nameb should be equal here already.
+      if ((flagsa & SKIPVAL) != 0) continue;  // keys matching is enough
+
+      String cmp = compare(vala, valb, flagsa, handle);
+      if (cmp != null) return "."+namea+cmp;
+    }
+
+
+    if (na != nb) {
+      return ".size()=="+na+","+nb;
+    }
+
+    return null;
+  }
+
+  private static String compare1(Map a, Map b, int flags, Map<String,Integer> handle) {
+    String cmp;
+
+    for (Object keya : a.keySet()) {
+      Object vala = a.get(keya);
+      int flagsa = flags(handle, keya);
+      if ((flagsa & SKIP) != 0) continue;
+      if (!b.containsKey(keya)) {
+        return "[" + keya + "]==null";
+      }
+      if ((flagsa & SKIPVAL) != 0) continue;
+      Object valb = b.get(keya);
+      cmp = compare(vala, valb, flagsa, handle);
+      if (cmp != null) return "[" + keya + "]" + cmp;
+    }
+    return null;
+  }
+
+  public static String compare(Map a, Map b, int flags, Map<String,Integer> handle) {
+    String cmp;
+    cmp = compare1(a,b,flags,handle);
+    if (cmp != null) return cmp;
+    return compare1(b,a,flags,handle);
+  }
+
+  public static String compare(SolrDocument a, SolrDocument b, int flags, Map<String,Integer> handle) {
+    return compare(a.getFieldValuesMap(), b.getFieldValuesMap(), flags, handle);    
+  }
+
+  public static String compare(SolrDocumentList a, SolrDocumentList b, int flags, Map<String,Integer> handle) {
+    boolean ordered = (flags & UNORDERED) == 0;
+
+    String cmp;
+    int f = flags(handle, "maxScore");
+    if ((f & SKIPVAL) == 0) {
+      cmp = compare(a.getMaxScore(), b.getMaxScore(), 0, handle);
+      if (cmp != null) return ".maxScore" + cmp;
+    } else {
+      if (a.getMaxScore() != null) {
+        if (b.getMaxScore() == null) {
+          return ".maxScore missing";
+        }
+      }
+    }
+
+    cmp = compare(a.getNumFound(), b.getNumFound(), 0, handle);
+    if (cmp != null) return ".numFound" + cmp;
+
+    cmp = compare(a.getStart(), b.getStart(), 0, handle);
+    if (cmp != null) return ".start" + cmp;
+
+    cmp = compare(a.size(), b.size(), 0, handle);
+    if (cmp != null) return ".size()" + cmp;
+
+    // only for completely ordered results (ties might be in a different order)
+    if (ordered) {
+    for (int i=0; i<a.size(); i++) {
+      cmp = compare(a.get(i), b.get(i), 0, handle);
+      if (cmp != null) return "["+i+"]"+cmp;
+      }
+      return null;
+    }
+
+    // unordered case
+    for (int i=0; i<a.size(); i++) {
+      SolrDocument doc = a.get(i);
+      Object key = doc.getFirstValue("id");
+      SolrDocument docb=null;
+      if (key==null) {
+        // no id field to correlate... must compare ordered
+        docb = b.get(i);
+      } else {
+        for (int j=0; j<b.size(); j++) {
+          docb = b.get(j);
+          if (key.equals(docb.getFirstValue("id"))) break;
+        }
+      }
+      // if (docb == null) return "[id="+key+"]";
+      cmp = compare(doc, docb, 0, handle);
+      if (cmp != null) return "[id="+key+"]" + cmp;
+    }
+    return null;
+  }
+
+  public static String compare(Object[] a, Object[] b, int flags, Map<String,Integer> handle) {
+    if (a.length != b.length) {
+      return ".length:"+a.length+"!="+b.length;
+    }
+    for (int i=0; i<a.length; i++) {
+      String cmp = compare(a[i], b[i], flags, handle);
+      if (cmp != null) return "["+i+"]"+cmp;
+    }
+    return null;
+  }
+
+
+  static String compare(Object a, Object b, int flags, Map<String,Integer> handle) {
+    if (a==b) return null;
+    if (a==null || b==null) return ":" +a + "!=" + b;
+
+    if (a instanceof NamedList && b instanceof NamedList) {
+      return compare((NamedList)a, (NamedList)b, flags, handle);
+    }
+
+    if (a instanceof SolrDocumentList && b instanceof SolrDocumentList) {
+      return compare((SolrDocumentList)a, (SolrDocumentList)b, flags, handle);
+    }
+
+    if (a instanceof SolrDocument && b instanceof SolrDocument) {
+      return compare((SolrDocument)a, (SolrDocument)b, flags, handle);
+    }
+
+    if (a instanceof Map && b instanceof Map) {
+      return compare((Map)a, (Map)b, flags, handle);
+    }
+
+    if (a instanceof Object[] && b instanceof Object[]) {
+      return compare((Object[])a, (Object[])b, flags, handle);
+    }
+
+    if (!(a.equals(b))) {
+      return ":" + a + "!=" + b;
+    }
+
+    return null;
+  }
+
+
+  void compareResponses(QueryResponse a, QueryResponse b) {
+    String cmp;
+       System.out.println(a);
+      System.out.println(b);
+    
+    cmp = compare(a.getResponse(), b.getResponse(), flags, handle);
+    if (cmp != null) {
+      System.out.println(a);
+      System.out.println(b);
+      TestCase.fail(cmp);
+    }
+  }
+
+  int flags;
+  Map<String, Integer> handle = new HashMap<String,Integer>();
+
+
+  public void testDistribSearch() throws Exception {
+    for (int nServers=1; nServers<4; nServers++) {
+      ports = new int[nServers];
+      for (int i=0; i<nServers; i++) {
+        ports[i] = 7574 + i*2;
+      }
+      doTest();
+    }
+  }
+
+  public void doTest() throws Exception {
+    createServers();
+    del("*:*");
+    index(id,1, i1, 100,t1,"now is the time for all good men");
+    index(id,2, i1, 50 ,t1,"to come to the aid of their country.");
+    index(id,3, i1, 2 ,t1,"how now brown cow");
+    index(id,4, i1, -100 ,t1,"the quick fox jumped over the lazy dog");
+    index(id,5, i1, 500 ,t1,"the quick fox jumped way over the lazy dog");
+    index(id,6, i1, -600 ,t1,"humpty dumpy sat on a wall");
+    index(id,7, i1, 123 ,t1,"humpty dumpy had a great fall");
+    index(id,8, i1, 876 ,t1,"all the kings horses and all the kings men");
+    index(id,9, i1, 7 ,t1,"couldn't put humpty together again");
+    index(id,10, i1, 4321 ,t1,"this too shal pass");
+    index(id,11, i1, -987 ,t1,"An eye for eye only ends up making the whole world blind.");
+    index(id,12, i1, 379 ,t1,"Great works are performed, not by strength, but by perseverance.");
+
+    commit();
+
+    handle.clear();
+    handle.put("QTime", SKIPVAL);
+    handle.put("timestamp", SKIPVAL);
+
+    // these queries should be exactly ordered and scores should exactly match
+    query("q","*:*", "sort",i1+" desc");
+    query("q","<!func>"+i1);
+    query("q","<!func>"+i1, "fl","*,score");  // even scores should match exactly here
+
+    handle.put("highlighting", UNORDERED);
+    handle.put("response", UNORDERED);
+
+    query("q","quick");
+    query("q","all","fl","id","start","0");
+    query("q","all","fl","foofoofoo","start","0");  // no fields in returned docs
+    query("q","all","fl","id","start","100");
+
+    handle.put("maxScore", SKIPVAL);
+    handle.put("score", SKIPVAL);
+    query("q","quick","fl","*,score");
+    query("q","all","fl","*,score","start","1");
+    query("q","all","fl","*,score","start","100");
+
+    query("q","now their fox sat had put","fl","*,score",
+            "hl","true","hl.fl",t1);
+
+    query("q","now their fox sat had put","fl","foofoofoo",
+            "hl","true","hl.fl",t1);
+
+
+    handle.put("debug", UNORDERED);
+    handle.put("time", SKIPVAL);
+
+    query("q","now their fox sat had put","fl","*,score",
+            "debugQuery", "true");
+
+    query("q","*:*", "rows",100, "facet","true", "facet.field",t1);
+    query("q","*:*", "rows",100, "facet","true", "facet.query","quick", "facet.query","all", "facet.query","*:*");
+    query("q","*:*", "rows",100, "facet","true", "facet.field",t1, "facet.offset",1);
+    query("q","*:*", "rows",100, "facet","true", "facet.field",t1,"facet.mincount",2);
+
+
+    // index the same document to two servers and make sure things
+    // don't blow up.
+    if (clients.size()>=2) {
+      index(id,100, i1, 107 ,t1,"oh no, a duplicate!");
+      for (int i=0; i<clients.size(); i++) {
+        index_specific(i, id,100, i1, 107 ,t1,"oh no, a duplicate!");
+      }
+      commit();
+      query("q","duplicate", "hl","true", "hl.fl", t1);
+      query("q","fox duplicate horses", "hl","true", "hl.fl", t1);
+      query("q","*:*", "rows",100);
+    }
+
+    // Thread.sleep(10000000000L);
+
+    destroyServers();
+  }
+
+
+}
+
+
+

Propchange: lucene/solr/trunk/src/test/org/apache/solr/TestDistributedSearch.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: lucene/solr/trunk/src/test/org/apache/solr/TestDistributedSearch.java
------------------------------------------------------------------------------
    svn:executable = *

Propchange: lucene/solr/trunk/src/test/org/apache/solr/TestDistributedSearch.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Modified: lucene/solr/trunk/src/test/test-files/solr/conf/solrconfig.xml
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/test/test-files/solr/conf/solrconfig.xml?rev=631357&r1=631356&r2=631357&view=diff
==============================================================================
--- lucene/solr/trunk/src/test/test-files/solr/conf/solrconfig.xml (original)
+++ lucene/solr/trunk/src/test/test-files/solr/conf/solrconfig.xml Tue Feb 26 11:47:07 2008
@@ -27,9 +27,7 @@
   <!-- Used to specify an alternate directory to hold all index data.
        It defaults to "index" if not present, and should probably
        not be changed if replication is in use. -->
-  <!--
-  <indexDir>index</indexDir>
-  -->
+  <dataDir>${solr.data.dir:./solr/data}</dataDir>
 
   <indexDefaults>
    <!-- Values here affect all index writers and act as a default