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 gs...@apache.org on 2009/12/24 14:03:24 UTC

svn commit: r893746 [1/2] - in /lucene/solr/trunk: ./ example/exampledocs/ example/solr/conf/ src/common/org/apache/solr/common/ src/java/org/apache/solr/schema/ src/java/org/apache/solr/search/ src/java/org/apache/solr/search/function/ src/java/org/ap...

Author: gsingers
Date: Thu Dec 24 13:03:22 2009
New Revision: 893746

URL: http://svn.apache.org/viewvc?rev=893746&view=rev
Log:
SOLR-1132: Added support for poly fields

Added:
    lucene/solr/trunk/src/java/org/apache/solr/schema/CoordinateFieldType.java   (with props)
    lucene/solr/trunk/src/java/org/apache/solr/schema/PointType.java   (with props)
    lucene/solr/trunk/src/java/org/apache/solr/schema/SchemaAware.java   (with props)
    lucene/solr/trunk/src/java/org/apache/solr/search/MultiValueSource.java   (with props)
    lucene/solr/trunk/src/java/org/apache/solr/search/ToMultiValueSource.java   (with props)
    lucene/solr/trunk/src/test/org/apache/solr/schema/PolyFieldTest.java   (with props)
Modified:
    lucene/solr/trunk/CHANGES.txt
    lucene/solr/trunk/example/exampledocs/hd.xml
    lucene/solr/trunk/example/exampledocs/ipod_other.xml
    lucene/solr/trunk/example/exampledocs/ipod_video.xml
    lucene/solr/trunk/example/exampledocs/mem.xml
    lucene/solr/trunk/example/exampledocs/monitor.xml
    lucene/solr/trunk/example/exampledocs/monitor2.xml
    lucene/solr/trunk/example/exampledocs/mp500.xml
    lucene/solr/trunk/example/exampledocs/sd500.xml
    lucene/solr/trunk/example/exampledocs/vidcard.xml
    lucene/solr/trunk/example/solr/conf/schema.xml
    lucene/solr/trunk/src/common/org/apache/solr/common/SolrException.java
    lucene/solr/trunk/src/java/org/apache/solr/schema/FieldType.java
    lucene/solr/trunk/src/java/org/apache/solr/schema/IndexSchema.java
    lucene/solr/trunk/src/java/org/apache/solr/schema/SchemaField.java
    lucene/solr/trunk/src/java/org/apache/solr/schema/TextField.java
    lucene/solr/trunk/src/java/org/apache/solr/search/FieldQParserPlugin.java
    lucene/solr/trunk/src/java/org/apache/solr/search/SolrQueryParser.java
    lucene/solr/trunk/src/java/org/apache/solr/search/ValueSourceParser.java
    lucene/solr/trunk/src/java/org/apache/solr/search/function/DocValues.java
    lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/DistanceUtils.java
    lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/HaversineFunction.java
    lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/SquaredEuclideanFunction.java
    lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/VectorDistanceFunction.java
    lucene/solr/trunk/src/java/org/apache/solr/update/DocumentBuilder.java
    lucene/solr/trunk/src/java/org/apache/solr/util/AbstractSolrTestCase.java
    lucene/solr/trunk/src/test/org/apache/solr/search/function/distance/DistanceFunctionTest.java
    lucene/solr/trunk/src/test/org/apache/solr/update/DocumentBuilderTest.java
    lucene/solr/trunk/src/test/test-files/solr/conf/schema.xml
    lucene/solr/trunk/src/test/test-files/solr/conf/schema11.xml

Modified: lucene/solr/trunk/CHANGES.txt
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/CHANGES.txt?rev=893746&r1=893745&r2=893746&view=diff
==============================================================================
--- lucene/solr/trunk/CHANGES.txt (original)
+++ lucene/solr/trunk/CHANGES.txt Thu Dec 24 13:03:22 2009
@@ -74,6 +74,9 @@
 
 * SOLR-1653: Add PatternReplaceCharFilter (koji)
 
+* SOLR-1131: FieldTypes can now output multiple Fields per Type and still be searched.  This can be handy for hiding the details of a particular
+  implementation such as in the spatial case. (Chris Mattmann, shalin, noble, gsingers, yonik)
+
 Optimizations
 ----------------------
 

Modified: lucene/solr/trunk/example/exampledocs/hd.xml
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/example/exampledocs/hd.xml?rev=893746&r1=893745&r2=893746&view=diff
==============================================================================
--- lucene/solr/trunk/example/exampledocs/hd.xml (original)
+++ lucene/solr/trunk/example/exampledocs/hd.xml Thu Dec 24 13:03:22 2009
@@ -28,6 +28,7 @@
   <field name="popularity">6</field>
   <field name="inStock">true</field>
   <field name="manufacturedate_dt">2006-02-13T15:26:37Z</field>
+  <field name="store">45.17614,-93.87341</field>
 </doc>
 
 <doc>
@@ -42,6 +43,8 @@
   <field name="price">350</field>
   <field name="popularity">6</field>
   <field name="inStock">true</field>
+  <!-- Buffalo store -->
+  <field name="store">45.17614,-93.87341</field>
   <field name="manufacturedate_dt">2006-02-13T15:26:37Z</field>
 </doc>
 </add>

Modified: lucene/solr/trunk/example/exampledocs/ipod_other.xml
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/example/exampledocs/ipod_other.xml?rev=893746&r1=893745&r2=893746&view=diff
==============================================================================
--- lucene/solr/trunk/example/exampledocs/ipod_other.xml (original)
+++ lucene/solr/trunk/example/exampledocs/ipod_other.xml Thu Dec 24 13:03:22 2009
@@ -28,6 +28,8 @@
   <field name="price">19.95</field>
   <field name="popularity">1</field>
   <field name="inStock">false</field>
+  <!-- Buffalo store -->
+  <field name="store">45.17614,-93.87341</field>
   <field name="manufacturedate_dt">2005-08-01T16:30:25Z</field>
 </doc>
 
@@ -42,6 +44,8 @@
   <field name="price">11.50</field>
   <field name="popularity">1</field>
   <field name="inStock">false</field>
+  <!-- San Francisco store -->
+  <field name="store">37.7752,-122.4232</field>
   <field name="manufacturedate_dt">2006-02-14T23:55:59Z</field>
 </doc>
 

Modified: lucene/solr/trunk/example/exampledocs/ipod_video.xml
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/example/exampledocs/ipod_video.xml?rev=893746&r1=893745&r2=893746&view=diff
==============================================================================
--- lucene/solr/trunk/example/exampledocs/ipod_video.xml (original)
+++ lucene/solr/trunk/example/exampledocs/ipod_video.xml Thu Dec 24 13:03:22 2009
@@ -32,5 +32,7 @@
   <field name="price">399.00</field>
   <field name="popularity">10</field>
   <field name="inStock">true</field>
+  <!-- San Francisco store -->
+  <field name="store">37.7752,-122.4232</field>
   <field name="manufacturedate_dt">2005-10-12T08:00:00Z</field>
 </doc></add>

Modified: lucene/solr/trunk/example/exampledocs/mem.xml
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/example/exampledocs/mem.xml?rev=893746&r1=893745&r2=893746&view=diff
==============================================================================
--- lucene/solr/trunk/example/exampledocs/mem.xml (original)
+++ lucene/solr/trunk/example/exampledocs/mem.xml Thu Dec 24 13:03:22 2009
@@ -26,6 +26,8 @@
   <field name="price">185</field>
   <field name="popularity">5</field>
   <field name="inStock">true</field>
+  <!-- San Francisco store -->
+  <field name="store">37.7752,-122.4232</field>
   <field name="manufacturedate_dt">2006-02-13T15:26:37Z</field>
 </doc>
 
@@ -38,6 +40,8 @@
   <field name="price">74.99</field>
   <field name="popularity">7</field>
   <field name="inStock">true</field>
+  <!-- San Francisco store -->
+  <field name="store">37.7752,-122.4232</field>
   <field name="manufacturedate_dt">2006-02-13T15:26:37Z</field>
 </doc>
 
@@ -51,6 +55,8 @@
   <!-- note: price & popularity is missing on this one -->
   <field name="popularity">0</field>
   <field name="inStock">true</field>
+  <!-- Buffalo store -->
+  <field name="store">45.17614,-93.87341</field>
   <field name="manufacturedate_dt">2006-02-13T15:26:37Z</field>
 </doc>
 

Modified: lucene/solr/trunk/example/exampledocs/monitor.xml
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/example/exampledocs/monitor.xml?rev=893746&r1=893745&r2=893746&view=diff
==============================================================================
--- lucene/solr/trunk/example/exampledocs/monitor.xml (original)
+++ lucene/solr/trunk/example/exampledocs/monitor.xml Thu Dec 24 13:03:22 2009
@@ -27,5 +27,7 @@
   <field name="price">2199</field>
   <field name="popularity">6</field>
   <field name="inStock">true</field>
+  <!-- Buffalo store -->
+  <field name="store">45.17614,-93.87341</field>
 </doc></add>
 

Modified: lucene/solr/trunk/example/exampledocs/monitor2.xml
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/example/exampledocs/monitor2.xml?rev=893746&r1=893745&r2=893746&view=diff
==============================================================================
--- lucene/solr/trunk/example/exampledocs/monitor2.xml (original)
+++ lucene/solr/trunk/example/exampledocs/monitor2.xml Thu Dec 24 13:03:22 2009
@@ -26,5 +26,7 @@
   <field name="price">279.95</field>
   <field name="popularity">6</field>
   <field name="inStock">true</field>
+  <!-- Buffalo store -->
+  <field name="store">45.17614,-93.87341</field>
 </doc></add>
 

Modified: lucene/solr/trunk/example/exampledocs/mp500.xml
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/example/exampledocs/mp500.xml?rev=893746&r1=893745&r2=893746&view=diff
==============================================================================
--- lucene/solr/trunk/example/exampledocs/mp500.xml (original)
+++ lucene/solr/trunk/example/exampledocs/mp500.xml Thu Dec 24 13:03:22 2009
@@ -35,5 +35,7 @@
   <field name="price">179.99</field>
   <field name="popularity">6</field>
   <field name="inStock">true</field>
+  <!-- Buffalo store -->
+  <field name="store">45.17614,-93.87341</field>
 </doc></add>
 

Modified: lucene/solr/trunk/example/exampledocs/sd500.xml
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/example/exampledocs/sd500.xml?rev=893746&r1=893745&r2=893746&view=diff
==============================================================================
--- lucene/solr/trunk/example/exampledocs/sd500.xml (original)
+++ lucene/solr/trunk/example/exampledocs/sd500.xml Thu Dec 24 13:03:22 2009
@@ -31,4 +31,6 @@
   <field name="popularity">7</field>
   <field name="inStock">true</field>
   <field name="manufacturedate_dt">2006-02-13T15:26:37Z</field>
+  <!-- Buffalo store -->
+  <field name="store">45.17614,-93.87341</field>
 </doc></add>

Modified: lucene/solr/trunk/example/exampledocs/vidcard.xml
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/example/exampledocs/vidcard.xml?rev=893746&r1=893745&r2=893746&view=diff
==============================================================================
--- lucene/solr/trunk/example/exampledocs/vidcard.xml (original)
+++ lucene/solr/trunk/example/exampledocs/vidcard.xml Thu Dec 24 13:03:22 2009
@@ -30,6 +30,7 @@
   <field name="weight">16</field>
   <field name="price">479.95</field>
   <field name="popularity">7</field>
+  <field name="store">40.7143,-74.006</field>
   <field name="inStock">false</field>
   <field name="manufacturedate_dt">2006-02-13T15:26:37Z/DAY</field>
 </doc>
@@ -50,5 +51,7 @@
   <field name="popularity">7</field>
   <field name="inStock">false</field>
   <field name="manufacturedate_dt">2006-02-13T15:26:37Z/DAY</field>
+  <!-- NYC store -->
+  <field name="store">40.7143,-74.006</field>
 </doc>
 </add>

Modified: lucene/solr/trunk/example/solr/conf/schema.xml
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/example/solr/conf/schema.xml?rev=893746&r1=893745&r2=893746&view=diff
==============================================================================
--- lucene/solr/trunk/example/solr/conf/schema.xml (original)
+++ lucene/solr/trunk/example/solr/conf/schema.xml Thu Dec 24 13:03:22 2009
@@ -394,7 +394,12 @@
 
     <!-- since fields of this type are by default not stored or indexed,
          any data added to them will be ignored outright.  --> 
-    <fieldtype name="ignored" stored="false" indexed="false" multiValued="true" class="solr.StrField" /> 
+    <fieldtype name="ignored" stored="false" indexed="false" multiValued="true" class="solr.StrField" />
+
+    <!--
+     A PointType is a Poly Field.  It can either declare a subFieldType or a subFieldSuffix
+     -->
+    <fieldType name="location" class="solr.PointType" dimension="2" subFieldType="double"/>
 
  </types>
 
@@ -440,6 +445,8 @@
    <field name="popularity" type="int" indexed="true" stored="true" />
    <field name="inStock" type="boolean" indexed="true" stored="true" />
 
+   <field name="store" type="location" indexed="true" stored="true"/>
+
 
    <!-- Common metadata fields, named specifically to match up with
      SolrCell metadata when parsing rich documents such as Word, PDF.

Modified: lucene/solr/trunk/src/common/org/apache/solr/common/SolrException.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/common/org/apache/solr/common/SolrException.java?rev=893746&r1=893745&r2=893746&view=diff
==============================================================================
--- lucene/solr/trunk/src/common/org/apache/solr/common/SolrException.java (original)
+++ lucene/solr/trunk/src/common/org/apache/solr/common/SolrException.java Thu Dec 24 13:03:22 2009
@@ -38,7 +38,7 @@
     SERVER_ERROR( 500 ),
     SERVICE_UNAVAILABLE( 503 ),
     UNKNOWN(0);
-    final int code;
+    public final int code;
     
     private ErrorCode( int c )
     {

Added: lucene/solr/trunk/src/java/org/apache/solr/schema/CoordinateFieldType.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/schema/CoordinateFieldType.java?rev=893746&view=auto
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/schema/CoordinateFieldType.java (added)
+++ lucene/solr/trunk/src/java/org/apache/solr/schema/CoordinateFieldType.java Thu Dec 24 13:03:22 2009
@@ -0,0 +1,138 @@
+/**
+ * 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.schema;
+
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.index.Term;
+import org.apache.solr.search.QParser;
+import org.apache.solr.search.function.ValueSource;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.params.MapSolrParams;
+
+import java.util.Map;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+
+/**
+ * A CoordinateFieldType is the base class for {@link org.apache.solr.schema.FieldType}s that have semantics
+ * related to items in a coordinate system.
+ * <br/>
+ * Implementations depend on a delegating work to a sub {@link org.apache.solr.schema.FieldType}, specified by
+ * either the {@link #SUB_FIELD_SUFFIX} or the {@link #SUB_FIELD_TYPE} (the latter is used if both are defined.
+ * <br/>
+ * Example:
+ * <pre>&lt;fieldType name="xy" class="solr.PointType" dimension="2" subFieldType="double"/&gt;
+ * </pre>
+ * In theory, classes deriving from this should be able to do things like represent a point, a polygon, a line, etc.
+ * <br/>
+ * NOTE: There can only be one sub Field Type.
+ *
+ */
+public abstract class CoordinateFieldType extends FieldType implements SchemaAware  {
+  /**
+   * The dimension of the coordinate system
+   */
+  protected int dimension;
+  protected FieldType subType;
+  public static final String SUB_FIELD_SUFFIX = "subFieldSuffix";
+  public static final String SUB_FIELD_TYPE = "subFieldType";
+  private String suffix;//need to keep this around between init and inform, since dynamic fields aren't created until before inform
+  protected int dynFieldProps;
+
+  public int getDimension() {
+    return dimension;
+  }
+
+  public FieldType getSubType() {
+    return subType;
+  }
+
+  @Override
+  protected void init(IndexSchema schema, Map<String, String> args) {
+
+    //it's not a first class citizen for the IndexSchema
+    SolrParams p = new MapSolrParams(args);
+    String subFT = p.get(SUB_FIELD_TYPE);
+    String subSuffix = p.get(SUB_FIELD_SUFFIX);
+    if (subFT != null) {
+      args.remove(SUB_FIELD_TYPE);
+      subType = schema.getFieldTypeByName(subFT.trim());
+    } else if (subSuffix != null) {
+      args.remove(SUB_FIELD_SUFFIX);
+      suffix = subSuffix;
+    }else {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "The field type: " + typeName
+              + " must specify the " +
+      SUB_FIELD_TYPE + " attribute or the " + SUB_FIELD_SUFFIX + " attribute.");
+    }
+
+    super.init(schema, args);
+  }
+
+  public void inform(IndexSchema schema) {
+    //Can't do this until here b/c the Dynamic Fields are not initialized until here.
+    if (suffix != null){
+      SchemaField sf = schema.getField(suffix);
+      subType = sf.getType();//this means it is already registered
+      dynFieldProps = sf.getProperties(); 
+    }
+    else if (subType != null) {
+      SchemaField proto = registerPolyFieldDynamicPrototype(schema, subType);
+      dynFieldProps = proto.getProperties();
+    } else {
+      throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "The field type: " + typeName
+              + " must specify the " +
+      SUB_FIELD_TYPE + " attribute or the " + SUB_FIELD_SUFFIX + " attribute.");
+    }
+  }
+
+  /**
+   * Helper method for creating a dynamic field SchemaField prototype.  Returns a {@link org.apache.solr.schema.SchemaField} with
+   * the {@link org.apache.solr.schema.FieldType} given and a name of "*" + {@link org.apache.solr.schema.FieldType#POLY_FIELD_SEPARATOR} + {@link org.apache.solr.schema.FieldType#typeName}
+   * and props of indexed=true, stored=false.
+   * @param schema the IndexSchema
+   * @param type The {@link org.apache.solr.schema.FieldType} of the prototype.
+   * @return The {@link org.apache.solr.schema.SchemaField}
+   */
+
+  static SchemaField registerPolyFieldDynamicPrototype(IndexSchema schema, FieldType type){
+    String name = "*" + FieldType.POLY_FIELD_SEPARATOR + type.typeName;
+    Map<String, String> props = new HashMap<String, String>();
+    //Just set these, delegate everything else to the field type
+    props.put("indexed", "true");
+    props.put("stored", "false");
+    int p = SchemaField.calcProps(name, type, props);
+    SchemaField proto = SchemaField.create(name,
+            type, p, null);
+    schema.registerDynamicField(proto);
+    return proto;
+  }
+
+
+  /**
+   * Throws UnsupportedOperationException()
+   */
+  public Query getFieldQuery(QParser parser, SchemaField field, String externalVal) {
+    throw new UnsupportedOperationException();
+  }
+
+}

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

Modified: lucene/solr/trunk/src/java/org/apache/solr/schema/FieldType.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/schema/FieldType.java?rev=893746&r1=893745&r2=893746&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/schema/FieldType.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/schema/FieldType.java Thu Dec 24 13:03:22 2009
@@ -20,14 +20,14 @@
 import org.apache.lucene.document.Field;
 import org.apache.lucene.document.Fieldable;
 import org.apache.lucene.analysis.Analyzer;
-import org.apache.lucene.analysis.TokenStream;
 import org.apache.lucene.analysis.Tokenizer;
-import org.apache.lucene.analysis.Token;
 import org.apache.lucene.analysis.tokenattributes.TermAttribute;
 import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
 import org.apache.lucene.search.SortField;
 import org.apache.lucene.search.Query;
 import org.apache.lucene.search.TermRangeQuery;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.index.Term;
 import org.apache.solr.search.function.ValueSource;
 import org.apache.solr.search.function.OrdFieldSource;
 import org.apache.solr.search.Sorting;
@@ -36,11 +36,17 @@
 import org.apache.solr.request.TextResponseWriter;
 import org.apache.solr.analysis.SolrAnalyzer;
 import org.apache.solr.common.SolrException;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.common.params.MapSolrParams;
 
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import java.util.Map;
 import java.util.HashMap;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.ArrayList;
 import java.io.Reader;
 import java.io.IOException;
 
@@ -52,6 +58,14 @@
 public abstract class FieldType extends FieldProperties {
   public static final Logger log = LoggerFactory.getLogger(FieldType.class);
 
+  /**
+   * The default poly field separator.
+   *
+   * @see #createFields(SchemaField, String, float)
+   * @see #isPolyField()
+   */
+  public static final String POLY_FIELD_SEPARATOR = "___";
+
   /** The name of the type (not the name of the field) */
   protected String typeName;
   /** additional arguments specified in the field type declaration */
@@ -59,9 +73,10 @@
   /** properties explicitly set to true */
   protected int trueProperties;
   /** properties explicitly set to false */
-  protected int falseProperties;  
+  protected int falseProperties;
   int properties;
 
+
   /** Returns true if fields of this type should be tokenized */
   public boolean isTokenized() {
     return (properties & TOKENIZED) != 0;
@@ -72,6 +87,18 @@
     return (properties & MULTIVALUED) != 0;
   }
 
+  /**
+   * A "polyField" is a FieldType that can produce more than one Field per FieldType, via the {@link #createFields(org.apache.solr.schema.SchemaField, String, float)} method.  This is useful
+   * when hiding the implementation details of a field from the Solr end user.  For instance, a spatial point may be represented by three different field types, all of which may produce 1 or more
+   * fields.
+   * @return true if the {@link #createFields(org.apache.solr.schema.SchemaField, String, float)} method may return more than one field
+   */
+  public boolean isPolyField(){
+    return false;
+  }
+
+
+
   /** Returns true if a single field value of this type has multiple logical values
    *  for the purposes of faceting, sorting, etc.  Text fields normally return
    *  true since each token/word is a logical value.
@@ -85,7 +112,8 @@
    * Common boolean properties have already been handled.
    *
    */
-  protected void init(IndexSchema schema, Map<String,String> args) {
+  protected void init(IndexSchema schema, Map<String, String> args) {
+
   }
 
   protected String getArg(String n, Map<String,String> args) {
@@ -191,8 +219,15 @@
    * :TODO: clean up and clarify this explanation.
    *
    * @see #toInternal
+   *
+   *
    */
   public Field createField(SchemaField field, String externalVal, float boost) {
+    if (!field.indexed() && !field.stored()) {
+      if (log.isTraceEnabled())
+        log.trace("Ignoring unindexed/unstored field: " + field);
+      return null;
+    }
     String val;
     try {
       val = toInternal(externalVal);
@@ -200,23 +235,123 @@
       throw new SolrException( SolrException.ErrorCode.SERVER_ERROR, "Error while creating field '" + field + "' from value '" + externalVal + "'", e, false);
     }
     if (val==null) return null;
-    if (!field.indexed() && !field.stored()) {
-      if (log.isTraceEnabled())
-        log.trace("Ignoring unindexed/unstored field: " + field);
-      return null;
+
+    return createField(field.getName(), val, getFieldStore(field, val),
+            getFieldIndex(field, val), getFieldTermVec(field, val), field.omitNorms(),
+            field.omitTf(), boost);
+  }
+
+
+
+  /**
+   * Create multiple fields from a single field and multiple values.  Fields are named as SchemaField.getName() + {@link #POLY_FIELD_SEPARATOR} + i, where
+   * i starts at 0.
+   * <p/>
+   * If the field is stored, then an extra field gets created that contains the storageVal.  It is this field that also
+   *
+   * @param field The {@link org.apache.solr.schema.SchemaField}
+   * @param props The properties to use
+   * @param delegatedType An optional type to use.  If null, then field.getType() is used.  Useful for poly fields.
+   * @param storageVal If the field stores, then this value will be used for the stored field
+   * @param boost The boost to apply to all fields
+   * @param externalVals The values to use
+   * @return The fields
+   */
+  protected Fieldable[] createFields(SchemaField field, int props,
+                                 FieldType delegatedType, String storageVal,
+                                 float boost, String ... externalVals) {
+    int n = field.indexed() ? externalVals.length : 0;
+    n += field.stored() ? 1 : 0;
+    if (delegatedType == null) { //if the type isn't being overriden, then just use the base one
+      delegatedType = field.getType();
     }
+    Field[] results = new Field[n];
+    //Field.Store.NO,Field.Index.NOT_ANALYZED_NO_NORMS, Field.TermVector.NO, true, true
 
+    if (externalVals.length > 0) {
+      if (field.indexed()) {
+        String name = field.getName() + "_";
+        String suffix = POLY_FIELD_SEPARATOR + delegatedType.typeName;
+
+        int len = name.length();
+        StringBuilder bldr = new StringBuilder(len + 3 + suffix.length());//should be enough buffer to handle most values of j.
+        bldr.append(name);
+        for (int j = 0; j < externalVals.length; j++) {
+          //SchemaField is final, as is name, so we need to recreate each time
+          //put the counter before the separator, b/c dynamic fields can't be asterisks on both the front and the end of the String
+          bldr.append(j).append(suffix);
+          SchemaField sf = SchemaField.create(bldr.toString(),
+                  delegatedType, props, null);
+                  //schema.getDynamicField(name  + "_" + j + POLY_FIELD_SEPARATOR + delegatedType.typeName);
+                  /**/
+          //new SchemaField(name, ft, p, defaultValue )
+          //QUESTION: should we allow for vectors, etc?  Not sure that it makes sense
+          results[j] = delegatedType.createField(sf, externalVals[j], boost);
+          bldr.setLength(len);//cut the builder back to just the length of the prefix, but keep the capacity
+        }
+      }
+      Field.TermVector fieldTermVec = getFieldTermVec(field, storageVal);
+      if (field.stored() || fieldTermVec.equals(Field.TermVector.YES)
+              || fieldTermVec.equals(Field.TermVector.WITH_OFFSETS)
+              || fieldTermVec.equals(Field.TermVector.WITH_POSITIONS)
+              || fieldTermVec.equals(Field.TermVector.WITH_POSITIONS_OFFSETS)
+      ) {
+
+          //QUESTION: should we allow for vectors, etc?  Not sure that it makes sense
+        results[results.length - 1] = createField(field.getName(), storageVal, getFieldStore(field, storageVal),
+                Field.Index.NO,
+                fieldTermVec, field.omitNorms(), field.omitTf(), boost);
+         
+      }
+
+    }
+    return results;
+  }
 
-    Field f = new Field(field.getName(),
+  /**
+   * Create the field from native Lucene parts.  Mostly intended for use by FieldTypes outputing multiple
+   * Fields per SchemaField
+   * @param name The name of the field
+   * @param val The _internal_ value to index
+   * @param storage {@link org.apache.lucene.document.Field.Store}
+   * @param index {@link org.apache.lucene.document.Field.Index}
+   * @param vec {@link org.apache.lucene.document.Field.TermVector}
+   * @param omitNorms true if norms should be omitted
+   * @param omitTFPos true if term freq and position should be omitted.
+   * @param boost The boost value
+   * @return the {@link org.apache.lucene.document.Field}.
+   */
+  protected Field createField(String name, String val, Field.Store storage, Field.Index index,
+                                    Field.TermVector vec, boolean omitNorms, boolean omitTFPos, float boost){
+    Field f = new Field(name,
                         val,
-                        getFieldStore(field, val),
-                        getFieldIndex(field, val),
-                        getFieldTermVec(field, val));
-    f.setOmitNorms(field.omitNorms());
-    f.setOmitTermFreqAndPositions(field.omitTf());
+                        storage,
+                        index,
+                        vec);
+    f.setOmitNorms(omitNorms);
+    f.setOmitTermFreqAndPositions(omitTFPos);
     f.setBoost(boost);
     return f;
   }
+
+  /**
+   * Given a {@link org.apache.solr.schema.SchemaField}, create one or more {@link org.apache.lucene.document.Field} instances
+   * @param field the {@link org.apache.solr.schema.SchemaField}
+   * @param externalVal The value to add to the field
+   * @param boost The boost to apply
+   * @return The {@link org.apache.lucene.document.Field} instances
+   *
+   * @see #createField(SchemaField, String, float)
+   * @see #isPolyField()
+   */
+  public Fieldable[] createFields(SchemaField field, String externalVal, float boost) {
+    Field f = createField( field, externalVal, boost);
+    if( f != null ) {
+      return new Field[] { f };
+    }
+    return null;
+  }
+
   /* Helpers for field construction */
   protected Field.TermVector getFieldTermVec(SchemaField field,
                                              String internalVal) {
@@ -226,7 +361,7 @@
     else if (field.storeTermPositions())
       ftv = Field.TermVector.WITH_POSITIONS;
     else if (field.storeTermOffsets())
-      ftv = Field.TermVector.WITH_OFFSETS;            
+      ftv = Field.TermVector.WITH_OFFSETS;
     else if (field.storeTermVector())
       ftv = Field.TermVector.YES;
     return ftv;
@@ -237,7 +372,7 @@
   }
   protected Field.Index getFieldIndex(SchemaField field,
                                       String internalVal) {
-    return field.indexed() ? (isTokenized() ? Field.Index.TOKENIZED : 
+    return field.indexed() ? (isTokenized() ? Field.Index.TOKENIZED :
                               Field.Index.UN_TOKENIZED) : Field.Index.NO;
   }
 
@@ -265,7 +400,7 @@
   }
 
   /**
-   * Convert the stored-field format to an external object.  
+   * Convert the stored-field format to an external object.
    * @see #toInternal
    * @since solr 1.3
    */
@@ -290,7 +425,7 @@
     // that the indexed form is the same as the stored field form.
     return f.stringValue();
   }
-  
+
   /** Given the readable value, return the term value that will match it. */
   public String readableToIndexed(String val) {
     return toInternal(val);
@@ -321,7 +456,7 @@
           termAtt.setTermBuffer(s);
           offsetAtt.setOffset(correctOffset(0),correctOffset(n));
           return true;
-        }       
+        }
       };
 
       return new TokenStreamInfo(ts, ts);
@@ -335,7 +470,7 @@
    * @see #getAnalyzer
    */
   protected Analyzer analyzer=new DefaultAnalyzer(256);
-  
+
   /**
    * Analyzer set by schema for text types to use when searching fields
    * of this type, subclasses can set analyzer themselves or override
@@ -394,7 +529,7 @@
    */
   public abstract void write(TextResponseWriter writer, String name, Fieldable f) throws IOException;
 
-  
+
   /**
    * Returns the SortField instance that should be used to sort fields
    * of this type.
@@ -452,4 +587,36 @@
             minInclusive, maxInclusive);
   }
 
+  /**
+   * Returns a Query instance for doing searches against a field.
+   * @param parser The {@link org.apache.solr.search.QParser} calling the method
+   * @param field The {@link org.apache.solr.schema.SchemaField} of the field to search
+   * @param externalVal The String representation of the value to search
+   * @return The {@link org.apache.lucene.search.Query} instance.  This implementation returns a {@link org.apache.lucene.search.TermQuery} but overriding queries may not
+   * 
+   */
+  public Query getFieldQuery(QParser parser, SchemaField field, String externalVal) {
+    return new TermQuery(new Term(field.getName(), toInternal(externalVal)));
+  }
+
+
+  /**
+   * Return a collection of all the Fields in the index where the {@link org.apache.solr.schema.SchemaField}
+   * @param polyField The instance of the {@link org.apache.solr.schema.SchemaField} to find the actual field names from
+   * @return The {@link java.util.Collection} of names of the actual fields that are a poly field.
+   *
+   *
+   */
+  /*protected Collection<String> getPolyFieldNames(SchemaField polyField){
+    if (polyField.isPolyField()) {
+      if (polyField != null) {
+        //we need the names of all the fields.  Do this lazily and then cache?
+
+
+      }
+    } //TODO: Should we throw an exception here in an else clause?
+    return Collections.emptyList();
+  }*/
+
+
 }

Modified: lucene/solr/trunk/src/java/org/apache/solr/schema/IndexSchema.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/schema/IndexSchema.java?rev=893746&r1=893745&r2=893746&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/schema/IndexSchema.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/schema/IndexSchema.java Thu Dec 24 13:03:22 2009
@@ -65,6 +65,30 @@
   private float version;
   private final SolrResourceLoader loader;
 
+  private final HashMap<String, SchemaField> fields = new HashMap<String,SchemaField>();
+
+
+  private final HashMap<String, FieldType> fieldTypes = new HashMap<String,FieldType>();
+
+  private final List<SchemaField> fieldsWithDefaultValue = new ArrayList<SchemaField>();
+  private final Collection<SchemaField> requiredFields = new HashSet<SchemaField>();
+  private DynamicField[] dynamicFields;
+
+  private Analyzer analyzer;
+  private Analyzer queryAnalyzer;
+
+  private String defaultSearchFieldName=null;
+  private String queryParserDefaultOperator = "OR";
+
+
+  private final Map<String, List<CopyField>> copyFieldsMap = new HashMap<String, List<CopyField>>();
+  private DynamicCopy[] dynamicCopyFields;
+  /**
+   * keys are all fields copied to, count is num of copyField
+   * directives that target them.
+   */
+  private Map<SchemaField, Integer> copyFieldTargetCounts
+    = new HashMap<SchemaField, Integer>();
   /**
    * Constructs a schema using the specified file name using the normal
    * Config path directory searching rules.
@@ -156,10 +180,7 @@
   @Deprecated
   public String getName() { return name; }
 
-  private final HashMap<String, SchemaField> fields = new HashMap<String,SchemaField>();
-  private final HashMap<String, FieldType> fieldTypes = new HashMap<String,FieldType>();
-  private final List<SchemaField> fieldsWithDefaultValue = new ArrayList<SchemaField>();
-  private final Collection<SchemaField> requiredFields = new HashSet<SchemaField>();
+  ;
 
   /**
    * Provides direct access to the Map containing all explicit
@@ -218,7 +239,7 @@
    */
   public SimilarityFactory getSimilarityFactory() { return similarityFactory; }
 
-  private Analyzer analyzer;
+
 
   /**
    * Returns the Analyzer used when indexing documents for this index
@@ -230,7 +251,7 @@
    */
   public Analyzer getAnalyzer() { return analyzer; }
 
-  private Analyzer queryAnalyzer;
+
 
   /**
    * Returns the Analyzer used when searching this index
@@ -242,8 +263,7 @@
    */
   public Analyzer getQueryAnalyzer() { return queryAnalyzer; }
 
-  private String defaultSearchFieldName=null;
-  private String queryParserDefaultOperator = "OR";
+
 
   /**
    * A SolrQueryParser linked to this IndexSchema for field datatype
@@ -399,7 +419,7 @@
       Config schemaConf = new Config(loader, "schema", is, "/schema/");
       Document document = schemaConf.getDocument();
       final XPath xpath = schemaConf.getXPath();
-
+      final List<SchemaAware> schemaAware = new ArrayList<SchemaAware>();
       Node nd = (Node) xpath.evaluate("/schema/@name", document, XPathConstants.NODE);
       if (nd==null) {
         log.warn("schema has no name!");
@@ -434,6 +454,9 @@
             ft.setAnalyzer(analyzer);
             ft.setQueryAnalyzer(queryAnalyzer);
           }
+          if (ft instanceof SchemaAware){
+            schemaAware.add((SchemaAware) ft);
+          }
           return ft;
         }
         
@@ -494,7 +517,6 @@
             SolrException.logOnce(log,null,t);
             SolrConfig.severeErrors.add( t );
           }
-          
           log.debug("field defined: " + f);
           if( f.getDefaultValue() != null ) {
             log.debug(name+" contains default value: " + f.getDefaultValue());
@@ -506,23 +528,7 @@
           }
         } else if (node.getNodeName().equals("dynamicField")) {
           // make sure nothing else has the same path
-          boolean dup = false;
-          for( DynamicField df : dFields ) {
-            if( df.regex.equals( f.name ) ) {
-              String msg = "[schema.xml] Duplicate DynamicField definition for '"
-                + f.getName() + "' ignoring: "+f.toString();
-              
-              Throwable t = new SolrException( SolrException.ErrorCode.SERVER_ERROR, msg );
-              SolrException.logOnce(log,null,t);
-              SolrConfig.severeErrors.add( t );
-              dup = true;
-              break;
-            }
-          }
-          if( !dup ) {
-            dFields.add(new DynamicField(f));
-            log.debug("dynamic field defined: " + f);
-          }
+          addDynamicField(dFields, f);
         } else {
           // we should never get here
           throw new RuntimeException("Unknown field type");
@@ -534,6 +540,7 @@
     // in DocumentBuilder.getDoc()
     requiredFields.addAll(getFieldsWithDefaultValue());
 
+
     // OK, now sort the dynamic fields largest to smallest size so we don't get
     // any false matches.  We want to act like a compiler tool and try and match
     // the largest string possible.
@@ -568,6 +575,9 @@
           }
         };
       }
+      if (similarityFactory instanceof SchemaAware){
+        schemaAware.add((SchemaAware) similarityFactory);
+      }
       log.debug("using similarity factory" + similarityFactory.getClass().getName());
     }
 
@@ -652,7 +662,10 @@
                       entry.getValue()+")");
         }
       }
-
+      //Run the callbacks on SchemaAware now that everything else is done
+      for (SchemaAware aware : schemaAware) {
+        aware.inform(this);
+      }
     } catch (SolrException e) {
       SolrConfig.severeErrors.add( e );
       throw e;
@@ -664,6 +677,56 @@
 
     // create the field analyzers
     refreshAnalyzers();
+
+  }
+
+  private void addDynamicField(List<DynamicField> dFields, SchemaField f) {
+    boolean dup = isDuplicateDynField(dFields, f);
+    if( !dup ) {
+      addDynamicFieldNoDupCheck(dFields, f);
+    } else {
+      String msg = "[schema.xml] Duplicate DynamicField definition for '"
+              + f.getName() + "' ignoring: " + f.toString();
+
+      Throwable t = new SolrException(SolrException.ErrorCode.SERVER_ERROR, msg);
+      SolrException.logOnce(log, null, t);
+      SolrConfig.severeErrors.add(t);
+    }
+  }
+
+  /**
+   * Register one or more new Dynamic Field with the Schema.
+   * @param f The {@link org.apache.solr.schema.SchemaField}
+   */
+  public void registerDynamicField(SchemaField ... f) {
+    List<DynamicField> dynFields = new ArrayList<DynamicField>(Arrays.asList(dynamicFields));
+    for (SchemaField field : f) {
+      if (isDuplicateDynField(dynFields, field) == false) {
+        log.debug("dynamic field creation for schema field: " + field.getName());
+        addDynamicFieldNoDupCheck(dynFields, field);
+      } else {
+        log.debug("dynamic field creation avoided: dynamic field: [" + field.getName() + "] " +
+                "already defined in the schema!");
+      }
+    }
+    Collections.sort(dynFields);
+    dynamicFields = (DynamicField[]) dynFields.toArray(new DynamicField[dynFields.size()]);
+  }
+
+  private void addDynamicFieldNoDupCheck(List<DynamicField> dFields, SchemaField f) {
+    dFields.add(new DynamicField(f));
+    log.debug("dynamic field defined: " + f);
+  }
+
+  private boolean isDuplicateDynField(List<DynamicField> dFields, SchemaField f) {
+    boolean dup = false;
+    for( DynamicField df : dFields ) {
+      if( df.regex.equals( f.name ) ) {
+        dup = true;
+        break;
+      }
+    }
+    return dup;
   }
 
   public void registerCopyField( String source, String dest )
@@ -987,7 +1050,7 @@
     }
   }
 
-  private DynamicField[] dynamicFields;
+
   public SchemaField[] getDynamicFieldPrototypes() {
     SchemaField[] df = new SchemaField[dynamicFields.length];
     for (int i=0;i<dynamicFields.length;i++) {
@@ -1038,42 +1101,43 @@
     }
 
     return false;
-  }
-  
+  }   
+
   /**
    * Returns the SchemaField that should be used for the specified field name, or
    * null if none exists.
    *
-   * @param fieldName may be an explicitly defined field, or a name that
+   * @param fieldName may be an explicitly defined field, a PolyField, or a name that
    * matches a dynamic field.
    * @see #getFieldType
+   * @see #getField(String)
+   * @return The {@link org.apache.solr.schema.SchemaField}
    */
   public SchemaField getFieldOrNull(String fieldName) {
-     SchemaField f = fields.get(fieldName);
+    SchemaField f = fields.get(fieldName);
     if (f != null) return f;
 
     for (DynamicField df : dynamicFields) {
       if (df.matches(fieldName)) return df.makeSchemaField(fieldName);
     }
-    
+
     return f;
   }
 
   /**
    * Returns the SchemaField that should be used for the specified field name
    *
-   * @param fieldName may be an explicitly defined field, or a name that
+   * @param fieldName may be an explicitly defined field, a PolyField type, or a name that
    * matches a dynamic field.
    * @throws SolrException if no such field exists
    * @see #getFieldType
+   * @see #getFieldOrNull(String)
+   * @return The {@link SchemaField}
    */
   public SchemaField getField(String fieldName) {
-     SchemaField f = fields.get(fieldName);
+    SchemaField f = getFieldOrNull(fieldName);
     if (f != null) return f;
 
-    for (DynamicField df : dynamicFields) {
-      if (df.matches(fieldName)) return df.makeSchemaField(fieldName);
-    }
 
     // Hmmm, default field could also be implemented with a dynamic field of "*".
     // It would have to be special-cased and only used if nothing else matched.
@@ -1105,6 +1169,16 @@
   }
 
   /**
+   * Given the name of a {@link org.apache.solr.schema.FieldType} (not to be confused with {@link #getFieldType(String)} which
+   * takes in the name of a field), return the {@link org.apache.solr.schema.FieldType}.
+   * @param fieldTypeName The name of the {@link org.apache.solr.schema.FieldType}
+   * @return The {@link org.apache.solr.schema.FieldType} or null.
+   */
+  public FieldType getFieldTypeByName(String fieldTypeName){
+    return fieldTypes.get(fieldTypeName);
+  }
+
+  /**
    * Returns the FieldType for the specified field name.
    *
    * <p>
@@ -1149,15 +1223,22 @@
     return null;
   };
 
-
-  private final Map<String, List<CopyField>> copyFieldsMap = new HashMap<String, List<CopyField>>();
-  private DynamicCopy[] dynamicCopyFields;
   /**
-   * keys are all fields copied to, count is num of copyField
-   * directives that target them.
+   *
+   * @param fieldName The name of the field
+   * @return the {@link FieldType} or a {@link org.apache.solr.common.SolrException} if the field is not a poly field.
    */
-  private Map<SchemaField, Integer> copyFieldTargetCounts
-    = new HashMap<SchemaField, Integer>();
+  public FieldType getPolyFieldType(String fieldName){
+    SchemaField f = fields.get(fieldName);
+    if (f != null && f.isPolyField()) return f.getType();
+    throw new SolrException( SolrException.ErrorCode.BAD_REQUEST,"undefined field or not a poly field "+fieldName);
+  }
+
+  public FieldType getPolyFieldTypeNoEx(String fieldName){
+    SchemaField f = fields.get(fieldName);
+    if (f != null && f.isPolyField()) return f.getType();
+    return null;
+  }
 
   /**
    * Get all copy fields, both the static and the dynamic ones.

Added: lucene/solr/trunk/src/java/org/apache/solr/schema/PointType.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/schema/PointType.java?rev=893746&view=auto
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/schema/PointType.java (added)
+++ lucene/solr/trunk/src/java/org/apache/solr/schema/PointType.java Thu Dec 24 13:03:22 2009
@@ -0,0 +1,276 @@
+/**
+ * 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.schema;
+
+import org.apache.lucene.document.Field;
+import org.apache.lucene.document.Fieldable;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.Searcher;
+import org.apache.lucene.search.SortField;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.params.MapSolrParams;
+import org.apache.solr.common.params.SolrParams;
+import org.apache.solr.request.TextResponseWriter;
+import org.apache.solr.request.XMLWriter;
+import org.apache.solr.search.MultiValueSource;
+import org.apache.solr.search.QParser;
+import org.apache.solr.search.function.DocValues;
+import org.apache.solr.search.function.ValueSource;
+import org.apache.solr.search.function.distance.DistanceUtils;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * A point type that indexes a point in an n-dimensional space as separate fields and uses
+ * range queries for bounding box calculations.
+ * <p/>
+ * <p/>
+ * NOTE: There can only be one sub type
+ */
+public class PointType extends CoordinateFieldType {
+  /**
+   * 2 dimensional by default
+   */
+  public static final int DEFAULT_DIMENSION = 2;
+  public static final String DIMENSION = "dimension";
+
+  protected IndexSchema schema;   // needed for retrieving SchemaFields
+
+
+  @Override
+  protected void init(IndexSchema schema, Map<String, String> args) {
+    SolrParams p = new MapSolrParams(args);
+    dimension = p.getInt(DIMENSION, DEFAULT_DIMENSION);
+    if (dimension < 1) {
+      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
+              "The dimension must be > 0: " + dimension);
+    }
+    args.remove(DIMENSION);
+    this.schema = schema;
+    super.init(schema, args);
+
+  }
+
+
+  @Override
+  public boolean isPolyField() {
+    return true;   // really only true if the field is indexed
+  }
+
+  @Override
+  public Fieldable[] createFields(SchemaField field, String externalVal, float boost) {
+    String[] point = DistanceUtils.parsePoint(null, externalVal, dimension);
+    return createFields(field, dynFieldProps, subType, externalVal, boost, point);
+  }
+
+  @Override
+  public ValueSource getValueSource(SchemaField field, QParser parser) {
+    return new PointTypeValueSource(field, dimension, subType, parser);
+  }
+
+
+  //It never makes sense to create a single field, so make it impossible to happen
+  @Override
+  public Field createField(SchemaField field, String externalVal, float boost) {
+    throw new UnsupportedOperationException("PointType uses multiple fields.  field=" + field.getName());
+  }
+
+  @Override
+  public void write(XMLWriter xmlWriter, String name, Fieldable f) throws IOException {
+    xmlWriter.writeStr(name, f.stringValue());
+  }
+
+  @Override
+  public void write(TextResponseWriter writer, String name, Fieldable f) throws IOException {
+    writer.writeStr(name, f.stringValue(), false);
+  }
+
+  @Override
+  public SortField getSortField(SchemaField field, boolean top) {
+    throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Sorting not suported on DualPointType " + field.getName());
+  }
+
+  @Override
+  /**
+   * Care should be taken in calling this with higher order dimensions for performance reasons.
+   */
+  public Query getRangeQuery(QParser parser, SchemaField field, String part1, String part2, boolean minInclusive, boolean maxInclusive) {
+    //Query could look like: [x1,y1 TO x2,y2] for 2 dimension, but could look like: [x1,y1,z1 TO x2,y2,z2], and can be extrapolated to n-dimensions
+    //thus, this query essentially creates a box, cube, etc.
+    String[] p1 = DistanceUtils.parsePoint(null, part1, dimension);
+    String[] p2 = DistanceUtils.parsePoint(null, part2, dimension);
+    BooleanQuery result = new BooleanQuery(true);
+    String name = field.getName() + "_";
+    String suffix = POLY_FIELD_SEPARATOR + subType.typeName;
+    int len = name.length();
+    StringBuilder bldr = new StringBuilder(len + 3 + suffix.length());//should be enough buffer to handle most values of j.
+    bldr.append(name);
+    for (int i = 0; i < dimension; i++) {
+      bldr.append(i).append(suffix);
+      SchemaField subSF = schema.getField(bldr.toString());
+      // points must currently be ordered... should we support specifying any two opposite corner points?
+
+      /*new TermRangeQuery(
+     field.getName() + i + POLY_FIELD_SEPARATOR + subType.typeName,
+     subType.toInternal(p1[i]),
+     subType.toInternal(p2[i]),
+     minInclusive, maxInclusive);*/
+      result.add(subType.getRangeQuery(parser, subSF, p1[i], p2[i], minInclusive, maxInclusive), BooleanClause.Occur.MUST);
+      bldr.setLength(len);
+    }
+    return result;
+  }
+
+  @Override
+  public Query getFieldQuery(QParser parser, SchemaField field, String externalVal) {
+    Query result = null;
+
+    String[] p1 = DistanceUtils.parsePoint(null, externalVal, dimension);
+    //TODO: should we assert that p1.length == dimension?
+    BooleanQuery bq = new BooleanQuery(true);
+    String name = field.getName() + "_";
+    String suffix = POLY_FIELD_SEPARATOR + subType.typeName;
+    int len = name.length();
+    StringBuilder bldr = new StringBuilder(len + 3 + suffix.length());//should be enough buffer to handle most values of j.
+    bldr.append(name);
+    for (int i = 0; i < dimension; i++) {
+      bldr.append(i).append(suffix);
+      SchemaField sf1 = schema.getField(bldr.toString());
+      Query tq = subType.getFieldQuery(parser, sf1, p1[i]);
+      //new TermQuery(new Term(bldr.toString(), subType.toInternal(p1[i])));
+      bq.add(tq, BooleanClause.Occur.MUST);
+      bldr.setLength(len);
+    }
+    result = bq;
+
+    return result;
+  }
+
+  class PointTypeValueSource extends MultiValueSource {
+  protected SchemaField field;
+  protected FieldType subType;
+  protected int dimension;
+  private QParser parser;
+
+  public PointTypeValueSource(SchemaField field, int dimension, FieldType subType, QParser parser) {
+    this.field = field;
+    this.dimension = dimension;
+    this.subType = subType;
+    this.parser = parser;
+  }
+
+  @Override
+  public void createWeight(Map context, Searcher searcher) throws IOException {
+    String name = field.getName();
+    String suffix = FieldType.POLY_FIELD_SEPARATOR + subType.typeName;
+    int len = name.length();
+    StringBuilder bldr = new StringBuilder(len + 3 + suffix.length());//should be enough buffer to handle most values of j.
+    bldr.append(name);
+    for (int i = 0; i < dimension; i++) {
+      bldr.append(i).append(suffix);
+      SchemaField sf = schema.getField(bldr.toString());
+      subType.getValueSource(sf, parser).createWeight(context, searcher);
+      bldr.setLength(len);
+    }
+  }
+
+  public int dimension() {
+    return dimension;
+  }
+
+  @Override
+  public DocValues getValues(Map context, IndexReader reader) throws IOException {
+    final DocValues[] valsArr1 = new DocValues[dimension];
+    String name = field.getName();
+    String suffix = FieldType.POLY_FIELD_SEPARATOR + subType.typeName;
+    int len = name.length();
+    StringBuilder bldr = new StringBuilder(len + 3 + suffix.length());//should be enough buffer to handle most values of j.
+    bldr.append(name);
+    for (int i = 0; i < dimension; i++) {
+      bldr.append(i).append(suffix);
+      SchemaField sf = schema.getField(bldr.toString());
+      valsArr1[i] = subType.getValueSource(sf, parser).getValues(context, reader);
+      bldr.setLength(len);
+    }
+    return new DocValues() {
+      //TODO: not sure how to handle the other types at this moment
+      @Override
+      public void doubleVal(int doc, double[] vals) {
+        //TODO: check whether vals.length == dimension or assume its handled elsewhere?
+        for (int i = 0; i < dimension; i++) {
+          vals[i] = valsArr1[i].doubleVal(doc);
+        }
+      }
+
+
+      @Override
+      public String toString(int doc) {
+        StringBuilder sb = new StringBuilder("point(");
+        boolean firstTime = true;
+        for (DocValues docValues : valsArr1) {
+          if (firstTime == false) {
+            sb.append(",");
+          } else {
+            firstTime = true;
+          }
+          sb.append(docValues.toString(doc));
+        }
+        sb.append(")");
+        return sb.toString();
+      }
+    };
+  }
+
+  public String description() {
+    StringBuilder sb = new StringBuilder();
+    sb.append("point(");
+    sb.append("fld=").append(field.name).append(", subType=").append(subType.typeName)
+            .append(", dimension=").append(dimension).append(')');
+    return sb.toString();
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (!(o instanceof PointTypeValueSource)) return false;
+
+    PointTypeValueSource that = (PointTypeValueSource) o;
+
+    if (dimension != that.dimension) return false;
+    if (!field.equals(that.field)) return false;
+    if (!subType.equals(that.subType)) return false;
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = field.hashCode();
+    result = 31 * result + subType.hashCode();
+    result = 31 * result + dimension;
+    return result;
+  }
+}
+
+}
+
+

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

Added: lucene/solr/trunk/src/java/org/apache/solr/schema/SchemaAware.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/schema/SchemaAware.java?rev=893746&view=auto
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/schema/SchemaAware.java (added)
+++ lucene/solr/trunk/src/java/org/apache/solr/schema/SchemaAware.java Thu Dec 24 13:03:22 2009
@@ -0,0 +1,39 @@
+package org.apache.solr.schema;
+/**
+ * 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.
+ */
+
+
+/**
+* An interface that can be extended to provide a callback mechanism for
+* informing an {@link IndexSchema} instance of changes to it, dynamically
+* performed at runtime.
+*
+* @since SOLR-1131
+ *
+ **/
+public interface SchemaAware {
+  /**
+   * Informs the {@link IndexSchema} provided by the <code>schema</code>
+   * parameter of an event (e.g., a new {@link FieldType} was added, etc.
+   *
+   * @param schema
+   *          The {@link IndexSchema} instance that inform of the update to.
+   *
+   * @since SOLR-1131
+   */
+  public void inform(IndexSchema schema);
+}

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

Modified: lucene/solr/trunk/src/java/org/apache/solr/schema/SchemaField.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/schema/SchemaField.java?rev=893746&r1=893745&r2=893746&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/schema/SchemaField.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/schema/SchemaField.java Thu Dec 24 13:03:22 2009
@@ -90,9 +90,23 @@
   boolean isTokenized() { return (properties & TOKENIZED)!=0; }
   boolean isBinary() { return (properties & BINARY)!=0; }
 
+
   public Field createField(String val, float boost) {
     return type.createField(this,val,boost);
   }
+  
+  public Fieldable[] createFields(String val, float boost) {
+    return type.createFields(this,val,boost);
+  }
+
+  /**
+   * If true, then use {@link #createFields(String, float)}, else use {@link #createField} to save an extra allocation
+   * @return true if this field is a poly field
+   */
+  public boolean isPolyField(){
+    return type.isPolyField();
+  }
+
 
   @Override
   public String toString() {
@@ -119,6 +133,29 @@
 
 
   static SchemaField create(String name, FieldType ft, Map<String,String> props) {
+
+    String defaultValue = null;
+    if( props.containsKey( "default" ) ) {
+    	defaultValue = (String)props.get( "default" );
+    }
+    return new SchemaField(name, ft, calcProps(name, ft, props), defaultValue );
+  }
+
+  /**
+   * Create a SchemaField w/ the props specified.  Does not support a default value.
+   * @param name The name of the SchemaField
+   * @param ft The {@link org.apache.solr.schema.FieldType} of the field
+   * @param props The props.  See {@link #calcProps(String, org.apache.solr.schema.FieldType, java.util.Map)}
+   * @param defValue The default Value for the field
+   * @return The SchemaField
+   *
+   * @see #create(String, FieldType, java.util.Map)
+   */
+  static SchemaField create(String name, FieldType ft, int props, String defValue){
+    return new SchemaField(name, ft, props, defValue);
+  }
+
+  static int calcProps(String name, FieldType ft, Map<String, String> props) {
     int trueProps = parseProperties(props,true);
     int falseProps = parseProperties(props,false);
 
@@ -166,12 +203,7 @@
 
     p &= ~falseProps;
     p |= trueProps;
-
-    String defaultValue = null;
-    if( props.containsKey( "default" ) ) {
-    	defaultValue = (String)props.get( "default" );
-    }
-    return new SchemaField(name, ft, p, defaultValue );
+    return p;
   }
 
   public String getDefaultValue() {

Modified: lucene/solr/trunk/src/java/org/apache/solr/schema/TextField.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/schema/TextField.java?rev=893746&r1=893745&r2=893746&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/schema/TextField.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/schema/TextField.java Thu Dec 24 13:03:22 2009
@@ -18,12 +18,28 @@
 package org.apache.solr.schema;
 
 import org.apache.lucene.search.SortField;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.PhraseQuery;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.MultiPhraseQuery;
 import org.apache.lucene.document.Fieldable;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
+import org.apache.lucene.analysis.tokenattributes.TermAttribute;
+import org.apache.lucene.analysis.CachingTokenFilter;
+import org.apache.lucene.analysis.TokenStream;
+import org.apache.lucene.analysis.Analyzer;
 import org.apache.solr.request.XMLWriter;
 import org.apache.solr.request.TextResponseWriter;
+import org.apache.solr.search.QParser;
 
 import java.util.Map;
+import java.util.List;
+import java.util.ArrayList;
 import java.io.IOException;
+import java.io.StringReader;
 
 /** <code>TextField</code> is the basic type for configurable text analysis.
  * Analyzers for field types using this implementation should be defined in the schema.
@@ -48,4 +64,190 @@
   public void write(TextResponseWriter writer, String name, Fieldable f) throws IOException {
     writer.writeStr(name, f.stringValue(), true);
   }
+
+  @Override
+  public Query getFieldQuery(QParser parser, SchemaField field, String externalVal) {
+    return parseFieldQuery(parser, getQueryAnalyzer(), field.getName(), externalVal);
+  }
+
+
+  static Query parseFieldQuery(QParser parser, Analyzer analyzer, String field, String queryText) {
+    int phraseSlop = 0;
+    boolean enablePositionIncrements = true;
+
+    // most of the following code is taken from the Lucene QueryParser
+
+    // Use the analyzer to get all the tokens, and then build a TermQuery,
+    // PhraseQuery, or nothing based on the term count
+
+    TokenStream source;
+    try {
+      source = analyzer.reusableTokenStream(field, new StringReader(queryText));
+      source.reset();
+    } catch (IOException e) {
+      source = analyzer.tokenStream(field, new StringReader(queryText));
+    }
+    CachingTokenFilter buffer = new CachingTokenFilter(source);
+    TermAttribute termAtt = null;
+    PositionIncrementAttribute posIncrAtt = null;
+    int numTokens = 0;
+
+    boolean success = false;
+    try {
+      buffer.reset();
+      success = true;
+    } catch (IOException e) {
+      // success==false if we hit an exception
+    }
+    if (success) {
+      if (buffer.hasAttribute(TermAttribute.class)) {
+        termAtt = (TermAttribute) buffer.getAttribute(TermAttribute.class);
+      }
+      if (buffer.hasAttribute(PositionIncrementAttribute.class)) {
+        posIncrAtt = (PositionIncrementAttribute) buffer.getAttribute(PositionIncrementAttribute.class);
+      }
+    }
+
+    int positionCount = 0;
+    boolean severalTokensAtSamePosition = false;
+
+    boolean hasMoreTokens = false;
+    if (termAtt != null) {
+      try {
+        hasMoreTokens = buffer.incrementToken();
+        while (hasMoreTokens) {
+          numTokens++;
+          int positionIncrement = (posIncrAtt != null) ? posIncrAtt.getPositionIncrement() : 1;
+          if (positionIncrement != 0) {
+            positionCount += positionIncrement;
+          } else {
+            severalTokensAtSamePosition = true;
+          }
+          hasMoreTokens = buffer.incrementToken();
+        }
+      } catch (IOException e) {
+        // ignore
+      }
+    }
+    try {
+      // rewind the buffer stream
+      buffer.reset();
+
+      // close original stream - all tokens buffered
+      source.close();
+    }
+    catch (IOException e) {
+      // ignore
+    }
+
+    if (numTokens == 0)
+      return null;
+    else if (numTokens == 1) {
+      String term = null;
+      try {
+        boolean hasNext = buffer.incrementToken();
+        assert hasNext == true;
+        term = termAtt.term();
+      } catch (IOException e) {
+        // safe to ignore, because we know the number of tokens
+      }
+      // return newTermQuery(new Term(field, term));
+      return new TermQuery(new Term(field, term));
+    } else {
+      if (severalTokensAtSamePosition) {
+        if (positionCount == 1) {
+          // no phrase query:
+          // BooleanQuery q = newBooleanQuery(true);
+          BooleanQuery q = new BooleanQuery(true);
+          for (int i = 0; i < numTokens; i++) {
+            String term = null;
+            try {
+              boolean hasNext = buffer.incrementToken();
+              assert hasNext == true;
+              term = termAtt.term();
+            } catch (IOException e) {
+              // safe to ignore, because we know the number of tokens
+            }
+
+            // Query currentQuery = newTermQuery(new Term(field, term));
+            Query currentQuery = new TermQuery(new Term(field, term));
+            q.add(currentQuery, BooleanClause.Occur.SHOULD);
+          }
+          return q;
+        }
+        else {
+          // phrase query:
+          // MultiPhraseQuery mpq = newMultiPhraseQuery();
+          MultiPhraseQuery mpq = new MultiPhraseQuery();
+          mpq.setSlop(phraseSlop);
+          List multiTerms = new ArrayList();
+          int position = -1;
+          for (int i = 0; i < numTokens; i++) {
+            String term = null;
+            int positionIncrement = 1;
+            try {
+              boolean hasNext = buffer.incrementToken();
+              assert hasNext == true;
+              term = termAtt.term();
+              if (posIncrAtt != null) {
+                positionIncrement = posIncrAtt.getPositionIncrement();
+              }
+            } catch (IOException e) {
+              // safe to ignore, because we know the number of tokens
+            }
+
+            if (positionIncrement > 0 && multiTerms.size() > 0) {
+              if (enablePositionIncrements) {
+                mpq.add((Term[])multiTerms.toArray(new Term[0]),position);
+              } else {
+                mpq.add((Term[])multiTerms.toArray(new Term[0]));
+              }
+              multiTerms.clear();
+            }
+            position += positionIncrement;
+            multiTerms.add(new Term(field, term));
+          }
+          if (enablePositionIncrements) {
+            mpq.add((Term[])multiTerms.toArray(new Term[0]),position);
+          } else {
+            mpq.add((Term[])multiTerms.toArray(new Term[0]));
+          }
+          return mpq;
+        }
+      }
+      else {
+        // PhraseQuery pq = newPhraseQuery();
+        PhraseQuery pq = new PhraseQuery();
+        pq.setSlop(phraseSlop);
+        int position = -1;
+
+
+        for (int i = 0; i < numTokens; i++) {
+          String term = null;
+          int positionIncrement = 1;
+
+          try {
+            boolean hasNext = buffer.incrementToken();
+            assert hasNext == true;
+            term = termAtt.term();
+            if (posIncrAtt != null) {
+              positionIncrement = posIncrAtt.getPositionIncrement();
+            }
+          } catch (IOException e) {
+            // safe to ignore, because we know the number of tokens
+          }
+
+          if (enablePositionIncrements) {
+            position += positionIncrement;
+            pq.add(new Term(field, term),position);
+          } else {
+            pq.add(new Term(field, term));
+          }
+        }
+        return pq;
+      }
+    }
+
+  }
+
 }

Modified: lucene/solr/trunk/src/java/org/apache/solr/search/FieldQParserPlugin.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/search/FieldQParserPlugin.java?rev=893746&r1=893745&r2=893746&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/search/FieldQParserPlugin.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/search/FieldQParserPlugin.java Thu Dec 24 13:03:22 2009
@@ -28,6 +28,7 @@
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.schema.FieldType;
 import org.apache.solr.schema.TextField;
+import org.apache.solr.schema.SchemaField;
 
 import java.io.IOException;
 import java.io.StringReader;
@@ -52,99 +53,9 @@
       public Query parse() throws ParseException {
         String field = localParams.get(QueryParsing.F);
         String queryText = localParams.get(QueryParsing.V);
-        FieldType ft = req.getSchema().getFieldType(field);
-        if (!(ft instanceof TextField)) {
-          String internal = ft.toInternal(queryText);
-          return new TermQuery(new Term(field, internal));
-        }
-
-        int phraseSlop = 0;
-        Analyzer analyzer = req.getSchema().getQueryAnalyzer();
-
-        // most of the following code is taken from the Lucene QueryParser
-
-        // Use the analyzer to get all the tokens, and then build a TermQuery,
-        // PhraseQuery, or nothing based on the term count
-
-        TokenStream source = null;
-        try {
-          source = analyzer.reusableTokenStream(field, new StringReader(queryText));
-          source.reset();
-        } catch (IOException e) {
-          throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);  
-        }
-        ArrayList<Token> lst = new ArrayList<Token>();
-        Token t;
-        int positionCount = 0;
-        boolean severalTokensAtSamePosition = false;
-
-        while (true) {
-          try {
-            t = source.next();
-          }
-          catch (IOException e) {
-            t = null;
-          }
-          if (t == null)
-            break;
-          lst.add(t);
-          if (t.getPositionIncrement() != 0)
-            positionCount += t.getPositionIncrement();
-          else
-            severalTokensAtSamePosition = true;
-        }
-        try {
-          source.close();
-        }
-        catch (IOException e) {
-          // ignore
-        }
-
-        if (lst.size() == 0)
-          return null;
-        else if (lst.size() == 1) {
-          t = lst.get(0);
-          return new TermQuery(new Term(field, new String(t.termBuffer(), 0, t.termLength())));
-        } else {
-          if (severalTokensAtSamePosition) {
-            if (positionCount == 1) {
-              // no phrase query:
-              BooleanQuery q = new BooleanQuery(true);
-              for (int i = 0; i < lst.size(); i++) {
-                t = (org.apache.lucene.analysis.Token) lst.get(i);
-                TermQuery currentQuery = new TermQuery(
-                        new Term(field, new String(t.termBuffer(), 0, t.termLength())));
-                q.add(currentQuery, BooleanClause.Occur.SHOULD);
-              }
-              return q;
-            }
-            else {
-              // phrase query:
-              MultiPhraseQuery mpq = new MultiPhraseQuery();
-              mpq.setSlop(phraseSlop);
-              ArrayList multiTerms = new ArrayList();
-              for (int i = 0; i < lst.size(); i++) {
-                t = (org.apache.lucene.analysis.Token) lst.get(i);
-                if (t.getPositionIncrement() == 1 && multiTerms.size() > 0) {
-                  mpq.add((Term[])multiTerms.toArray(new Term[0]));
-                  multiTerms.clear();
-                }
-                multiTerms.add(new Term(field, new String(t.termBuffer(), 0, t.termLength())));
-              }
-              mpq.add((Term[])multiTerms.toArray(new Term[0]));
-              return mpq;
-            }
-          }
-          else {
-            PhraseQuery q = new PhraseQuery();
-            q.setSlop(phraseSlop);
-            for (int i = 0; i < lst.size(); i++) {
-              Token token = lst.get(i);
-              q.add(new Term(field, new String(token.termBuffer(), 0, token.termLength())));
-            }
-            return q;
-          }
-        }
+        SchemaField sf = req.getSchema().getField(field);
+        FieldType ft = sf.getType();
+        return ft.getFieldQuery(this, sf, queryText);
       }
     };
   }

Added: lucene/solr/trunk/src/java/org/apache/solr/search/MultiValueSource.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/search/MultiValueSource.java?rev=893746&view=auto
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/search/MultiValueSource.java (added)
+++ lucene/solr/trunk/src/java/org/apache/solr/search/MultiValueSource.java Thu Dec 24 13:03:22 2009
@@ -0,0 +1,29 @@
+package org.apache.solr.search;
+/**
+ * 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.
+ */
+
+import org.apache.solr.search.function.ValueSource;
+
+
+/**
+ * A {@link ValueSource} that abstractly represents {@link ValueSource}s for
+ * poly fields, and other things.
+ **/
+public abstract class MultiValueSource extends ValueSource {
+
+  public abstract int dimension();
+}

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

Modified: lucene/solr/trunk/src/java/org/apache/solr/search/SolrQueryParser.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/search/SolrQueryParser.java?rev=893746&r1=893745&r2=893746&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/search/SolrQueryParser.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/search/SolrQueryParser.java Thu Dec 24 13:03:22 2009
@@ -34,6 +34,7 @@
 import org.apache.solr.schema.SchemaField;
 import org.apache.solr.schema.TrieField;
 import org.apache.solr.schema.SchemaField;
+import org.apache.solr.schema.TextField;
 
 // TODO: implement the analysis of simple fields with
 // FieldType.toInternal() instead of going through the
@@ -145,6 +146,12 @@
         return parser.subQuery(queryText, null).getQuery();
       }
     }
+    //Intercept poly fields, as they get expanded by default to an OR clause of
+    SchemaField sf = schema.getField(field);
+    //TODO: is there anyway to avoid this instance of check?
+    if (sf != null&& !(sf.getType() instanceof TextField)){//we have a poly field, deal with it specially by delegating to the FieldType
+      return sf.getType().getFieldQuery(parser, sf, queryText); 
+    }
 
     // default to a normal field query
     return super.getFieldQuery(field, queryText);

Added: lucene/solr/trunk/src/java/org/apache/solr/search/ToMultiValueSource.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/search/ToMultiValueSource.java?rev=893746&view=auto
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/search/ToMultiValueSource.java (added)
+++ lucene/solr/trunk/src/java/org/apache/solr/search/ToMultiValueSource.java Thu Dec 24 13:03:22 2009
@@ -0,0 +1,164 @@
+package org.apache.solr.search;
+/**
+ * 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.
+ */
+
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.Searcher;
+import org.apache.solr.search.function.DocValues;
+import org.apache.solr.search.function.ValueSource;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+
+/**
+ * Converts individual ValueSource instances to leverage the DocValues *Val functions that work with multiple values,
+ * i.e. {@link org.apache.solr.search.function.DocValues#doubleVal(int, double[])}
+ */
+//Not crazy about the name, but...
+public class ToMultiValueSource extends MultiValueSource {
+  protected List<ValueSource> sources;
+
+
+  public ToMultiValueSource(List<ValueSource> sources) {
+    this.sources = sources;
+  }
+
+  public List<ValueSource> getSources() {
+    return sources;
+  }
+
+  public int dimension() {
+    return sources.size();
+  }
+
+  @Override
+  public DocValues getValues(Map context, IndexReader reader) throws IOException {
+    int size = sources.size();
+    final DocValues[] valsArr = new DocValues[size];
+    for (int i = 0; i < size; i++) {
+      valsArr[i] = sources.get(i).getValues(context, reader);
+    }
+    return new DocValues() {
+      @Override
+      public void byteVal(int doc, byte[] vals) {
+        for (int i = 0; i < valsArr.length; i++) {
+          vals[i] = valsArr[i].byteVal(doc);
+        }
+      }
+
+      @Override
+      public void shortVal(int doc, short[] vals) {
+        for (int i = 0; i < valsArr.length; i++) {
+          vals[i] = valsArr[i].shortVal(doc);
+        }
+      }
+
+      @Override
+      public void floatVal(int doc, float[] vals) {
+        for (int i = 0; i < valsArr.length; i++) {
+          vals[i] = valsArr[i].floatVal(doc);
+        }
+      }
+
+      @Override
+      public void intVal(int doc, int[] vals) {
+        for (int i = 0; i < valsArr.length; i++) {
+          vals[i] = valsArr[i].intVal(doc);
+        }
+      }
+
+      @Override
+      public void longVal(int doc, long[] vals) {
+        for (int i = 0; i < valsArr.length; i++) {
+          vals[i] = valsArr[i].longVal(doc);
+        }
+      }
+
+      @Override
+      public void doubleVal(int doc, double[] vals) {
+        for (int i = 0; i < valsArr.length; i++) {
+          vals[i] = valsArr[i].doubleVal(doc);
+        }
+      }
+
+      @Override
+      public void strVal(int doc, String[] vals) {
+        for (int i = 0; i < valsArr.length; i++) {
+          vals[i] = valsArr[i].strVal(doc);
+        }
+      }
+
+      @Override
+      public String toString(int doc) {
+        StringBuilder sb = new StringBuilder();
+        sb.append("toMultiVS(");
+        boolean firstTime = true;
+        for (DocValues vals : valsArr) {
+          if (firstTime) {
+            firstTime = false;
+          } else {
+            sb.append(',');
+          }
+          sb.append(vals.toString(doc));
+        }
+        sb.append(')');
+        return sb.toString();
+      }
+    };
+  }
+
+  public void createWeight(Map context, Searcher searcher) throws IOException {
+    for (ValueSource source : sources)
+      source.createWeight(context, searcher);
+  }
+
+
+  public String description() {
+    StringBuilder sb = new StringBuilder();
+    sb.append("toMultiVS(");
+    boolean firstTime = true;
+    for (ValueSource source : sources) {
+      if (firstTime) {
+        firstTime = false;
+      } else {
+        sb.append(',');
+      }
+      sb.append(source);
+    }
+    sb.append(")");
+    return sb.toString();
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (!(o instanceof ToMultiValueSource)) return false;
+
+    ToMultiValueSource that = (ToMultiValueSource) o;
+
+    if (!sources.equals(that.sources)) return false;
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    return sources.hashCode();
+  }
+}

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

Modified: lucene/solr/trunk/src/java/org/apache/solr/search/ValueSourceParser.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/search/ValueSourceParser.java?rev=893746&r1=893745&r2=893746&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/search/ValueSourceParser.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/search/ValueSourceParser.java Thu Dec 24 13:03:22 2009
@@ -41,6 +41,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Collections;
 
 /**
  * A factory that parses user queries to generate ValueSource instances.
@@ -202,6 +203,11 @@
         };
       }
     });
+    addParser("toMultiVS", new ValueSourceParser(){
+      public ValueSource parse(FunctionQParser fp) throws ParseException{
+        return new ToMultiValueSource(fp.parseValueSourceList());
+      }
+    });
     addParser("query", new ValueSourceParser() {
       // boost(query($q),rating)
       public ValueSource parse(FunctionQParser fp) throws ParseException {
@@ -224,22 +230,47 @@
     addParser("hsin", new ValueSourceParser() {
       public ValueSource parse(FunctionQParser fp) throws ParseException {
 
-        ValueSource x1 = fp.parseValueSource();
-        ValueSource y1 = fp.parseValueSource();
-        ValueSource x2 = fp.parseValueSource();
-        ValueSource y2 = fp.parseValueSource();
         double radius = fp.parseDouble();
+        MultiValueSource pv1;
+        MultiValueSource pv2;
 
-        return new HaversineFunction(x1, y1, x2, y2, radius);
+        ValueSource one = fp.parseValueSource();
+        ValueSource two = fp.parseValueSource();
+        if (fp.hasMoreArguments()) {
+          List<ValueSource> s1 = new ArrayList<ValueSource>();
+          s1.add(one);
+          s1.add(two);
+          pv1 = new ToMultiValueSource(s1);
+          ValueSource x2 = fp.parseValueSource();
+          ValueSource y2 = fp.parseValueSource();
+          List<ValueSource> s2 = new ArrayList<ValueSource>();
+          s2.add(x2);
+          s2.add(y2);
+          pv2 = new ToMultiValueSource(s2);
+        } else {
+          //check to see if we have multiValue source
+          if (one instanceof MultiValueSource && two instanceof MultiValueSource){
+            pv1 = (MultiValueSource) one;
+            pv2 = (MultiValueSource) two;
+          } else {
+            throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
+                    "Input must either be 2 MultiValueSources, or there must be 4 ValueSources");
+          }
+        }
+        boolean convert = false;
+        if (fp.hasMoreArguments()){
+          convert = Boolean.parseBoolean(fp.parseArg());
+        }
+        return new HaversineFunction(pv1, pv2, radius, convert);
       }
     });
 
     addParser("ghhsin", new ValueSourceParser() {
       public ValueSource parse(FunctionQParser fp) throws ParseException {
+        double radius = fp.parseDouble();
 
         ValueSource gh1 = fp.parseValueSource();
         ValueSource gh2 = fp.parseValueSource();
-        double radius = fp.parseDouble();
 
         return new GeohashHaversineFunction(gh1, gh2, radius);
       }
@@ -393,15 +424,9 @@
     addParser("sqedist", new ValueSourceParser() {
       public ValueSource parse(FunctionQParser fp) throws ParseException {
         List<ValueSource> sources = fp.parseValueSourceList();
-        if (sources.size() % 2 != 0) {
-          throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Illegal number of sources.  There must be an even number of sources");
-        }
-        int dim = sources.size() / 2;
-        List<ValueSource> sources1 = new ArrayList<ValueSource>(dim);
-        List<ValueSource> sources2 = new ArrayList<ValueSource>(dim);
-        //Get dim value sources for the first vector
-        splitSources(dim, sources, sources1, sources2);
-        return new SquaredEuclideanFunction(sources1, sources2);
+        MVResult mvr = getMultiValueSources(sources);
+
+        return new SquaredEuclideanFunction(mvr.mv1, mvr.mv2);
       }
     });
 
@@ -409,14 +434,8 @@
       public ValueSource parse(FunctionQParser fp) throws ParseException {
         float power = fp.parseFloat();
         List<ValueSource> sources = fp.parseValueSourceList();
-        if (sources.size() % 2 != 0) {
-          throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Illegal number of sources.  There must be an even number of sources");
-        }
-        int dim = sources.size() / 2;
-        List<ValueSource> sources1 = new ArrayList<ValueSource>(dim);
-        List<ValueSource> sources2 = new ArrayList<ValueSource>(dim);
-        splitSources(dim, sources, sources1, sources2);
-        return new VectorDistanceFunction(power, sources1, sources2);
+        MVResult mvr = getMultiValueSources(sources);
+        return new VectorDistanceFunction(power, mvr.mv1, mvr.mv2);
       }
     });
     addParser("ms", new DateValueSourceParser());
@@ -445,6 +464,44 @@
     }
   }
 
+  private static MVResult getMultiValueSources(List<ValueSource> sources) {
+    MVResult mvr = new MVResult();
+    if (sources.size() % 2 != 0) {
+      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Illegal number of sources.  There must be an even number of sources");
+    }
+    if (sources.size() == 2) {
+
+      //check to see if these are MultiValueSource
+      boolean s1MV = sources.get(0) instanceof MultiValueSource;
+      boolean s2MV = sources.get(1) instanceof MultiValueSource;
+      if (s1MV && s2MV) {
+        mvr.mv1 = (MultiValueSource) sources.get(0);
+        mvr.mv2 = (MultiValueSource) sources.get(1);
+      } else if (s1MV ||
+              s2MV) {
+        //if one is a MultiValueSource, than the other one needs to be too.
+        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Illegal number of sources.  There must be an even number of sources");
+      } else {
+        mvr.mv1 = new ToMultiValueSource(Collections.singletonList(sources.get(0)));
+        mvr.mv2 = new ToMultiValueSource(Collections.singletonList(sources.get(1)));
+      }
+    } else {
+      int dim = sources.size() / 2;
+      List<ValueSource> sources1 = new ArrayList<ValueSource>(dim);
+      List<ValueSource> sources2 = new ArrayList<ValueSource>(dim);
+      //Get dim value sources for the first vector
+      splitSources(dim, sources, sources1, sources2);
+      mvr.mv1 = new ToMultiValueSource(sources1);
+      mvr.mv2 = new ToMultiValueSource(sources2);
+    }
+
+    return mvr;
+  }
+
+  private static class MVResult {
+    MultiValueSource mv1;
+    MultiValueSource mv2;
+  }
 }
 
 

Modified: lucene/solr/trunk/src/java/org/apache/solr/search/function/DocValues.java
URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/search/function/DocValues.java?rev=893746&r1=893745&r2=893746&view=diff
==============================================================================
--- lucene/solr/trunk/src/java/org/apache/solr/search/function/DocValues.java (original)
+++ lucene/solr/trunk/src/java/org/apache/solr/search/function/DocValues.java Thu Dec 24 13:03:22 2009
@@ -48,6 +48,15 @@
   public String strVal(int doc) { throw new UnsupportedOperationException(); }
   public abstract String toString(int doc);
 
+  //For Functions that can work with multiple values from the same document.  This does not apply to all functions
+  public void byteVal(int doc, byte [] vals) { throw new UnsupportedOperationException(); }
+  public void shortVal(int doc, short [] vals) { throw new UnsupportedOperationException(); }
+
+  public void floatVal(int doc, float [] vals) { throw new UnsupportedOperationException(); }
+  public void intVal(int doc, int [] vals) { throw new UnsupportedOperationException(); }
+  public void longVal(int doc, long [] vals) { throw new UnsupportedOperationException(); }
+  public void doubleVal(int doc, double [] vals) { throw new UnsupportedOperationException(); }
+  public void strVal(int doc, String [] vals) { throw new UnsupportedOperationException(); }
 
   public Explanation explain(int doc) {
     return new Explanation(floatVal(doc), toString(doc));