Modified: lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/DistanceUtils.java URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/DistanceUtils.java?rev=893746&r1=893745&r2=893746&view=diff ============================================================================== --- lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/DistanceUtils.java (original) +++ lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/DistanceUtils.java Thu Dec 24 13:03:22 2009 @@ -1,4 +1,6 @@ package org.apache.solr.search.function.distance; + +import org.apache.solr.common.SolrException; /** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with @@ -50,5 +52,44 @@ return result; } - + /** + * Given a string containing <i>dimension</i> values encoded in it, separated by commas, return a String array of length <i>dimension</i> + * containing the values. + * @param out A preallocated array. Must be size dimension. If it is not it will be resized. + * @param externalVal The value to parse + * @param dimension The expected number of values for the point + * @return An array of the values that make up the point (aka vector) + * + * @throws {...@link SolrException} if the dimension specified does not match the number of values in the externalValue. + */ + public static String[] parsePoint(String[] out, String externalVal, int dimension) { + //TODO: Should we support sparse vectors? + if (out==null || out.length != dimension) out=new String[dimension]; + int idx = externalVal.indexOf(','); + int end = idx; + int start = 0; + int i = 0; + if (idx == -1 && dimension == 1 && externalVal.length() > 0){//we have a single point, dimension better be 1 + out[0] = externalVal.trim(); + i = 1; + } + else if (idx > 0) {//if it is zero, that is an error + //Parse out a comma separated list of point values, as in: 73.5,89.2,7773.4 + for (; i < dimension; i++){ + while (start<end && externalVal.charAt(start)==' ') start++; + while (end>start && externalVal.charAt(end-1)==' ') end--; + out[i] = externalVal.substring(start, end); + start = idx+1; + end = externalVal.indexOf(',', start); + if (end == -1){ + end = externalVal.length(); + } + } + } + if (i != dimension){ + throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "incompatible dimension (" + dimension + + ") and values (" + externalVal + "). Only " + i + " values specified"); + } + return out; + } }
Modified: lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/HaversineFunction.java URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/HaversineFunction.java?rev=893746&r1=893745&r2=893746&view=diff ============================================================================== --- lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/HaversineFunction.java (original) +++ lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/HaversineFunction.java Thu Dec 24 13:03:22 2009 @@ -18,6 +18,8 @@ import org.apache.lucene.index.IndexReader; import org.apache.lucene.search.Searcher; +import org.apache.solr.common.SolrException; +import org.apache.solr.search.MultiValueSource; import org.apache.solr.search.function.DocValues; import org.apache.solr.search.function.ValueSource; @@ -29,28 +31,31 @@ * Calculate the Haversine formula (distance) between any two points on a sphere * Takes in four value sources: (latA, lonA); (latB, lonB). * <p/> - * Assumes the value sources are in radians + * Assumes the value sources are in radians unless * <p/> * See http://en.wikipedia.org/wiki/Great-circle_distance and * http://en.wikipedia.org/wiki/Haversine_formula for the actual formula and * also http://www.movable-type.co.uk/scripts/latlong.html - * - * @see org.apache.solr.search.function.RadianFunction */ public class HaversineFunction extends ValueSource { - private ValueSource x1; - private ValueSource y1; - private ValueSource x2; - private ValueSource y2; + private MultiValueSource p1; + private MultiValueSource p2; + private boolean convertToRadians = false; private double radius; - public HaversineFunction(ValueSource x1, ValueSource y1, ValueSource x2, ValueSource y2, double radius) { - this.x1 = x1; - this.y1 = y1; - this.x2 = x2; - this.y2 = y2; + public HaversineFunction(MultiValueSource p1, MultiValueSource p2, double radius) { + this(p1, p2, radius, false); + } + + public HaversineFunction(MultiValueSource p1, MultiValueSource p2, double radius, boolean convertToRads){ + this.p1 = p1; + this.p2 = p2; + if (p1.dimension() != 2 || p2.dimension() != 2) { + throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Illegal dimension for value sources"); + } this.radius = radius; + this.convertToRadians = convertToRads; } protected String name() { @@ -59,28 +64,40 @@ /** * @param doc The doc to score - * @param x1DV - * @param y1DV - * @param x2DV - * @param y2DV + * @param p1DV + * @param p2DV * @return The haversine distance formula */ - protected double distance(int doc, DocValues x1DV, DocValues y1DV, DocValues x2DV, DocValues y2DV) { - double x1 = x1DV.doubleVal(doc); //in radians - double y1 = y1DV.doubleVal(doc); - double x2 = x2DV.doubleVal(doc); - double y2 = y2DV.doubleVal(doc); + protected double distance(int doc, DocValues p1DV, DocValues p2DV) { + double[] p1D = new double[2]; + double[] p2D = new double[2]; + p1DV.doubleVal(doc, p1D); + p2DV.doubleVal(doc, p2D); + double x1; + double y1; + double x2; + double y2; + if (convertToRadians) { + x1 = p1D[0] * DistanceUtils.DEGREES_TO_RADIANS; + y1 = p1D[1] * DistanceUtils.DEGREES_TO_RADIANS; + x2 = p2D[0] * DistanceUtils.DEGREES_TO_RADIANS; + y2 = p2D[1] * DistanceUtils.DEGREES_TO_RADIANS; + } else { + x1 = p1D[0]; + y1 = p1D[1]; + x2 = p2D[0]; + y2 = p2D[1]; + } return DistanceUtils.haversine(x1, y1, x2, y2, radius); } @Override public DocValues getValues(Map context, IndexReader reader) throws IOException { - final DocValues x1DV = x1.getValues(context, reader); - final DocValues y1DV = y1.getValues(context, reader); - final DocValues x2DV = x2.getValues(context, reader); - final DocValues y2DV = y2.getValues(context, reader); + final DocValues vals1 = p1.getValues(context, reader); + + final DocValues vals2 = p2.getValues(context, reader); return new DocValues() { public float floatVal(int doc) { return (float) doubleVal(doc); @@ -95,7 +112,7 @@ } public double doubleVal(int doc) { - return (double) distance(doc, x1DV, y1DV, x2DV, y2DV); + return (double) distance(doc, vals1, vals2); } public String strVal(int doc) { @@ -106,8 +123,7 @@ public String toString(int doc) { StringBuilder sb = new StringBuilder(); sb.append(name()).append('('); - sb.append(x1DV.toString(doc)).append(',').append(y1DV.toString(doc)).append(',') - .append(x2DV.toString(doc)).append(',').append(y2DV.toString(doc)); + sb.append(vals1.toString(doc)).append(',').append(vals2.toString(doc)); sb.append(')'); return sb.toString(); } @@ -116,10 +132,9 @@ @Override public void createWeight(Map context, Searcher searcher) throws IOException { - x1.createWeight(context, searcher); - x2.createWeight(context, searcher); - y1.createWeight(context, searcher); - y2.createWeight(context, searcher); + p1.createWeight(context, searcher); + p2.createWeight(context, searcher); + } @Override @@ -127,20 +142,16 @@ if (this.getClass() != o.getClass()) return false; HaversineFunction other = (HaversineFunction) o; return this.name().equals(other.name()) - && x1.equals(other.x1) && - y1.equals(other.y1) && - x2.equals(other.x2) && - y2.equals(other.y2) && radius == other.radius; + && p1.equals(other.p1) && + p2.equals(other.p2) && radius == other.radius; } @Override public int hashCode() { int result; long temp; - result = x1.hashCode(); - result = 31 * result + y1.hashCode(); - result = 31 * result + x2.hashCode(); - result = 31 * result + y2.hashCode(); + result = p1.hashCode(); + result = 31 * result + p2.hashCode(); result = 31 * result + name().hashCode(); temp = Double.doubleToRawLongBits(radius); result = 31 * result + (int) (temp ^ (temp >>> 32)); @@ -150,7 +161,7 @@ public String description() { StringBuilder sb = new StringBuilder(); sb.append(name()).append('('); - sb.append(x1).append(',').append(y1).append(',').append(x2).append(',').append(y2); + sb.append(p1).append(',').append(p2); sb.append(')'); return sb.toString(); } Modified: lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/SquaredEuclideanFunction.java URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/SquaredEuclideanFunction.java?rev=893746&r1=893745&r2=893746&view=diff ============================================================================== --- lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/SquaredEuclideanFunction.java (original) +++ lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/SquaredEuclideanFunction.java Thu Dec 24 13:03:22 2009 @@ -18,6 +18,7 @@ import org.apache.solr.search.function.DocValues; import org.apache.solr.search.function.ValueSource; +import org.apache.solr.search.MultiValueSource; import java.util.List; @@ -30,8 +31,8 @@ public class SquaredEuclideanFunction extends VectorDistanceFunction { protected String name = "sqedist"; - public SquaredEuclideanFunction(List<ValueSource> sources1, List<ValueSource> sources2) { - super(-1, sources1, sources2);//overriding distance, so power doesn't matter here + public SquaredEuclideanFunction(MultiValueSource source1, MultiValueSource source2) { + super(-1, source1, source2);//overriding distance, so power doesn't matter here } @@ -43,11 +44,16 @@ /** * @param doc The doc to score */ - protected double distance(int doc, DocValues[] docValues1, DocValues[] docValues2) { + protected double distance(int doc, DocValues dv1, DocValues dv2) { double result = 0; - for (int i = 0; i < docValues1.length; i++) { - result += Math.pow(docValues1[i].doubleVal(doc) - docValues2[i].doubleVal(doc), 2); - } + double [] vals1 = new double[source1.dimension()]; + double [] vals2 = new double[source1.dimension()]; + dv1.doubleVal(doc, vals1); + dv2.doubleVal(doc, vals2); + for (int i = 0; i < vals1.length; i++) { + double v = vals1[i] - vals2[i]; + result += v * v; + } return result; } Modified: lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/VectorDistanceFunction.java URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/VectorDistanceFunction.java?rev=893746&r1=893745&r2=893746&view=diff ============================================================================== --- lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/VectorDistanceFunction.java (original) +++ lucene/solr/trunk/src/java/org/apache/solr/search/function/distance/VectorDistanceFunction.java Thu Dec 24 13:03:22 2009 @@ -21,9 +21,9 @@ import org.apache.solr.common.SolrException; import org.apache.solr.search.function.DocValues; import org.apache.solr.search.function.ValueSource; +import org.apache.solr.search.MultiValueSource; import java.io.IOException; -import java.util.List; import java.util.Map; @@ -41,18 +41,18 @@ * @see SquaredEuclideanFunction for the special case */ public class VectorDistanceFunction extends ValueSource { - protected List<ValueSource> sources1, sources2; + protected MultiValueSource source1, source2; protected float power; protected float oneOverPower; - public VectorDistanceFunction(float power, List<ValueSource> sources1, List<ValueSource> sources2) { - this.power = power; - this.oneOverPower = 1 / power; - this.sources1 = sources1; - this.sources2 = sources2; - if ((sources1.size() != sources2.size())) { + public VectorDistanceFunction(float power, MultiValueSource source1, MultiValueSource source2) { + if ((source1.dimension() != source2.dimension())) { throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Illegal number of sources"); } + this.power = power; + this.oneOverPower = 1 / power; + this.source1 = source1; + this.source2 = source2; } protected String name() { @@ -63,37 +63,39 @@ * Calculate the distance * * @param doc The current doc - * @param docValues1 The values from the first set of value sources - * @param docValues2 The values from the second set of value sources + * @param dv1 The values from the first MultiValueSource + * @param dv2 The values from the second MultiValueSource * @return The distance */ - protected double distance(int doc, DocValues[] docValues1, DocValues[] docValues2) { + protected double distance(int doc, DocValues dv1, DocValues dv2) { double result = 0; //Handle some special cases: + double [] vals1 = new double[source1.dimension()]; + double [] vals2 = new double[source1.dimension()]; + dv1.doubleVal(doc, vals1); + dv2.doubleVal(doc, vals2); if (power == 0) { - for (int i = 0; i < docValues1.length; i++) { - //sparseness measure - result += docValues1[i].doubleVal(doc) - docValues2[i].doubleVal(doc) == 0 ? 0 : 1; + for (int i = 0; i < vals1.length; i++) { + result += vals1[i] - vals2[i] == 0 ? 0 :1; } + } else if (power == 1.0) { - for (int i = 0; i < docValues1.length; i++) { - result += docValues1[i].doubleVal(doc) - docValues2[i].doubleVal(doc); + for (int i = 0; i < vals1.length; i++) { + result += vals1[i] - vals2[i]; } } else if (power == 2.0) { - for (int i = 0; i < docValues1.length; i++) { - double v = docValues1[i].doubleVal(doc) - docValues2[i].doubleVal(doc); + for (int i = 0; i < vals1.length; i++) { + double v = vals1[i] - vals2[i]; result += v * v; } result = Math.sqrt(result); } else if (power == Integer.MAX_VALUE || Double.isInfinite(power)) {//infininte norm? - for (int i = 0; i < docValues1.length; i++) { - //TODO: is this the correct infinite norm? - result = Math.max(docValues1[i].doubleVal(doc) - docValues2[i].doubleVal(doc), result); + for (int i = 0; i < vals1.length; i++) { + result = Math.max(vals1[i], vals2[i]); } - } else { - for (int i = 0; i < docValues1.length; i++) { - result += Math.pow(docValues1[i].doubleVal(doc) - docValues2[i].doubleVal(doc), power); + for (int i = 0; i < vals1.length; i++) { + result += Math.pow(vals1[i] - vals2[i], power); } result = Math.pow(result, oneOverPower); } @@ -103,19 +105,24 @@ @Override public DocValues getValues(Map context, IndexReader reader) throws IOException { - final DocValues[] valsArr1 = new DocValues[sources1.size()]; - int i = 0; - for (ValueSource source : sources1) { - valsArr1[i++] = source.getValues(context, reader); - } - final DocValues[] valsArr2 = new DocValues[sources2.size()]; - i = 0; - for (ValueSource source : sources2) { - valsArr2[i++] = source.getValues(context, reader); - } + + final DocValues vals1 = source1.getValues(context, reader); + + final DocValues vals2 = source2.getValues(context, reader); + return new DocValues() { + @Override + public byte byteVal(int doc) { + return (byte) doubleVal(doc); + } + + @Override + public short shortVal(int doc) { + return (short)doubleVal(doc); + } + public float floatVal(int doc) { return (float) doubleVal(doc); } @@ -129,7 +136,7 @@ } public double doubleVal(int doc) { - return distance(doc, valsArr1, valsArr2); + return distance(doc, vals1, vals2); } public String strVal(int doc) { @@ -141,18 +148,8 @@ StringBuilder sb = new StringBuilder(); sb.append(name()).append('(').append(power).append(','); boolean firstTime = true; - for (DocValues vals : valsArr1) { - if (firstTime) { - firstTime = false; - } else { - sb.append(','); - } - sb.append(vals.toString(doc)); - } - for (DocValues vals : valsArr2) { - sb.append(',');//we will always have valsArr1, else there is an error - sb.append(vals.toString(doc)); - } + sb.append(vals1.toString(doc)).append(','); + sb.append(vals2.toString(doc)); sb.append(')'); return sb.toString(); } @@ -161,12 +158,8 @@ @Override public void createWeight(Map context, Searcher searcher) throws IOException { - for (ValueSource source : sources1) { - source.createWeight(context, searcher); - } - for (ValueSource source : sources2) { - source.createWeight(context, searcher); - } + source1.createWeight(context, searcher); + source2.createWeight(context, searcher); } @Override @@ -177,16 +170,16 @@ VectorDistanceFunction that = (VectorDistanceFunction) o; if (Float.compare(that.power, power) != 0) return false; - if (!sources1.equals(that.sources1)) return false; - if (!sources2.equals(that.sources2)) return false; + if (!source1.equals(that.source1)) return false; + if (!source2.equals(that.source2)) return false; return true; } @Override public int hashCode() { - int result = sources1.hashCode(); - result = 31 * result + sources2.hashCode(); + int result = source1.hashCode(); + result = 31 * result + source2.hashCode(); result = 31 * result + Float.floatToRawIntBits(power); return result; } @@ -195,19 +188,8 @@ public String description() { StringBuilder sb = new StringBuilder(); sb.append(name()).append('(').append(power).append(','); - boolean firstTime = true; - for (ValueSource source : sources1) { - if (firstTime) { - firstTime = false; - } else { - sb.append(','); - } - sb.append(source); - } - for (ValueSource source : sources2) { - sb.append(',');//we will always have sources1, else there is an error - sb.append(source); - } + sb.append(source1).append(','); + sb.append(source2); sb.append(')'); return sb.toString(); } Modified: lucene/solr/trunk/src/java/org/apache/solr/update/DocumentBuilder.java URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/update/DocumentBuilder.java?rev=893746&r1=893745&r2=893746&view=diff ============================================================================== --- lucene/solr/trunk/src/java/org/apache/solr/update/DocumentBuilder.java (original) +++ lucene/solr/trunk/src/java/org/apache/solr/update/DocumentBuilder.java Thu Dec 24 13:03:22 2009 @@ -57,19 +57,35 @@ // we don't check for a null val ourselves because a solr.FieldType // might actually want to map it to something. If createField() // returns null, then we don't store the field. - Field field = sfield.createField(val, boost); - if (field != null) { - if (!sfield.multiValued()) { - String oldValue = map.put(sfield.getName(), val); - if (oldValue != null) { - throw new SolrException( SolrException.ErrorCode.BAD_REQUEST,"ERROR: multiple values encountered for non multiValued field " + sfield.getName() - + ": first='" + oldValue + "' second='" + val + "'"); + if (sfield.isPolyField()) { + Fieldable[] fields = sfield.createFields(val, boost); + if (fields != null && fields.length > 0) { + if (!sfield.multiValued()) { + String oldValue = map.put(sfield.getName(), val); + if (oldValue != null) { + throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "ERROR: multiple values encountered for non multiValued field " + sfield.getName() + + ": first='" + oldValue + "' second='" + val + "'"); + } + } + // Add each field + for (Fieldable field : fields) { + doc.add(field); + } + } + } else { + Field field = sfield.createField(val, boost); + if (field != null) { + if (!sfield.multiValued()) { + String oldValue = map.put(sfield.getName(), val); + if (oldValue != null) { + throw new SolrException( SolrException.ErrorCode.BAD_REQUEST,"ERROR: multiple values encountered for non multiValued field " + sfield.getName() + + ": first='" + oldValue + "' second='" + val + "'"); + } } } - - // field.setBoost(boost); doc.add(field); } + } /** @@ -147,7 +163,7 @@ for (SchemaField field : schema.getRequiredFields()) { if (doc.getField(field.getName() ) == null) { if (field.getDefaultValue() != null) { - doc.add( field.createField( field.getDefaultValue(), 1.0f ) ); + addField(doc, field, field.getDefaultValue(), 1.0f); } else { if (missingFields==null) { missingFields = new ArrayList<String>(1); @@ -176,6 +192,19 @@ Document ret = doc; doc=null; return ret; } + + + private static void addField(Document doc, SchemaField field, String val, float boost) { + if (field.isPolyField()) { + Fieldable[] farr = field.getType().createFields(field, val, boost); + for (Fieldable f : farr) { + if (f != null) doc.add(f); // null fields are not added + } + } else { + Field f = field.createField(val, boost); + if (f != null) doc.add(f); // null fields are not added + } + } /** @@ -230,7 +259,9 @@ isBinaryField = true; BinaryField binaryField = (BinaryField) sfield.getType(); Field f = binaryField.createField(sfield,v,boost); - if(f != null) out.add(f); + if(f != null){ + out.add(f); + } used = true; } else { // TODO!!! HACK -- date conversion @@ -243,10 +274,7 @@ if (sfield != null) { used = true; - Field f = sfield.createField(val, boost); - if (f != null) { // null fields are not added - out.add(f); - } + addField(out, sfield, val, boost); } } @@ -263,17 +291,21 @@ } used = true; - Field f = null; + //Don't worry about poly fields here + Fieldable [] fields = null; if (isBinaryField) { if (destinationField.getType() instanceof BinaryField) { BinaryField binaryField = (BinaryField) destinationField.getType(); - f = binaryField.createField(destinationField, v, boost); + //TODO: safe to assume that binary fields only create one? + fields = new Field[]{binaryField.createField(destinationField, v, boost)}; } } else { - f = destinationField.createField(cf.getLimitedValue(val), boost); + fields = destinationField.createFields(cf.getLimitedValue(val), boost); } - if (f != null) { // null fields are not added - out.add(f); + if (fields != null) { // null fields are not added + for (Fieldable f : fields) { + out.add(f); + } } } @@ -297,7 +329,7 @@ for (SchemaField field : schema.getRequiredFields()) { if (out.getField(field.getName() ) == null) { if (field.getDefaultValue() != null) { - out.add( field.createField( field.getDefaultValue(), 1.0f ) ); + addField(out, field, field.getDefaultValue(), 1.0f); } else { String id = schema.printableUniqueKey( out ); Modified: lucene/solr/trunk/src/java/org/apache/solr/util/AbstractSolrTestCase.java URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/java/org/apache/solr/util/AbstractSolrTestCase.java?rev=893746&r1=893745&r2=893746&view=diff ============================================================================== --- lucene/solr/trunk/src/java/org/apache/solr/util/AbstractSolrTestCase.java (original) +++ lucene/solr/trunk/src/java/org/apache/solr/util/AbstractSolrTestCase.java Thu Dec 24 13:03:22 2009 @@ -231,6 +231,17 @@ } } + public void assertQEx(String message, SolrQueryRequest req, SolrException.ErrorCode code ) { + try { + h.query(req); + fail( message ); + } catch (SolrException e) { + assertEquals( code.code, e.code() ); + } catch (Exception e2) { + throw new RuntimeException("Exception during query", e2); + } + } + /** * @see TestHarness#optimize Added: lucene/solr/trunk/src/test/org/apache/solr/schema/PolyFieldTest.java URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/test/org/apache/solr/schema/PolyFieldTest.java?rev=893746&view=auto ============================================================================== --- lucene/solr/trunk/src/test/org/apache/solr/schema/PolyFieldTest.java (added) +++ lucene/solr/trunk/src/test/org/apache/solr/schema/PolyFieldTest.java Thu Dec 24 13:03:22 2009 @@ -0,0 +1,196 @@ +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. + */ + +import org.apache.lucene.document.Field; +import org.apache.lucene.document.Fieldable; +import org.apache.lucene.document.Document; +import org.apache.lucene.search.BooleanClause; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.TopDocs; +import org.apache.lucene.index.IndexReader; +import org.apache.solr.core.SolrCore; +import org.apache.solr.util.AbstractSolrTestCase; +import org.apache.solr.common.SolrException; + +import java.util.Map; +import java.util.Random; + + +/** + * Test a whole slew of things related to PolyFields + */ +public class PolyFieldTest extends AbstractSolrTestCase { + + @Override + public String getSchemaFile() { + return "schema.xml"; + } + + @Override + public String getSolrConfigFile() { + return "solrconfig.xml"; + } + + public void testSchemaBasics() throws Exception { + IndexSchema schema = h.getCore().getSchema(); + + + SchemaField home = schema.getField("home"); + assertNotNull(home); + assertTrue(home.isPolyField()); + + SchemaField[] dynFields = schema.getDynamicFieldPrototypes(); + boolean seen = false; + for (SchemaField dynField : dynFields) { + if (dynField.getName().equals("*" + FieldType.POLY_FIELD_SEPARATOR + "double")) { + seen = true; + } + } + assertTrue("Didn't find the expected dynamic field", seen); + FieldType homeFT = schema.getFieldType("home"); + assertEquals(home.getType(), homeFT); + FieldType xy = schema.getFieldTypeByName("xy"); + assertNotNull(xy); + assertTrue(xy instanceof PointType); + assertTrue(xy.isPolyField()); + home = schema.getFieldOrNull("home_0" + FieldType.POLY_FIELD_SEPARATOR + "double"); + assertNotNull(home); + home = schema.getField("home"); + assertNotNull(home); + homeFT = schema.getPolyFieldType("home"); + assertNotNull(homeFT); + + home = schema.getField("homed");//sub field suffix + assertNotNull(home); + assertTrue(home.isPolyField()); + + try { + FieldType bad = schema.getPolyFieldType("foo"); + assertTrue(false); + } catch (Exception e) { + } + try { + FieldType bad = schema.getPolyFieldTypeNoEx("foo"); + assertNull(bad); + } catch (Exception e) { + assertTrue(false); + } + } + + public void testPointFieldType() throws Exception { + SolrCore core = h.getCore(); + IndexSchema schema = core.getSchema(); + SchemaField home = schema.getField("home"); + assertNotNull(home); + assertTrue("home is not a poly field", home.isPolyField()); + FieldType tmp = home.getType(); + assertTrue(tmp instanceof PointType); + PointType pt = (PointType) tmp; + assertEquals(pt.getDimension(), 2); + double[] xy = new double[]{35.0, -79.34}; + String point = xy[0] + "," + xy[1]; + Fieldable[] fields = home.createFields(point, 2); + assertEquals(fields.length, 3);//should be 3, we have a stored field + //first two fields contain the values, third is just stored and contains the original + for (int i = 0; i < 3; i++) { + boolean hasValue = fields[1].tokenStreamValue() != null + || fields[1].getBinaryValue() != null + || fields[1].stringValue() != null; + assertTrue("Doesn't have a value: " + fields[1], hasValue); + } + /*assertTrue("first field " + fields[0].tokenStreamValue() + " is not 35.0", pt.getSubType().toExternal(fields[0]).equals(String.valueOf(xy[0]))); + assertTrue("second field is not -79.34", pt.getSubType().toExternal(fields[1]).equals(String.valueOf(xy[1]))); + assertTrue("third field is not '35.0,-79.34'", pt.getSubType().toExternal(fields[2]).equals(point));*/ + + + home = schema.getField("home_ns"); + assertNotNull(home); + fields = home.createFields(point, 2); + assertEquals(fields.length, 2);//should be 2, since we aren't storing + + home = schema.getField("home_ns"); + assertNotNull(home); + try { + fields = home.createFields("35.0,foo", 2); + assertTrue(false); + } catch (Exception e) { + // + } + } + + public void testSearching() throws Exception { + for (int i = 0; i < 50; i++) { + assertU(adoc("id", "" + i, "home", i + "," + (i * 100), "homed", (i * 1000) + "," + (i * 10000))); + } + assertU(commit()); + IndexReader reader = h.getCore().getSearcher().get().getReader(); + /*for (int i = 0; i < 50; i++){ + Document doc = reader.document(i); + System.out.println("Doc: " + doc.get("homed_0___double")); + }*/ + assertQ(req("fl", "*,score", "q", "*:*"), "//*...@numfound='50']"); + assertQ(req("fl", "*,score", "q", "home:1,100"), + "//*...@numfound='1']", + "//s...@name='home'][.='1,100']"); + assertQ(req("fl", "*,score", "q", "homed:1000,10000"), + "//*...@numfound='1']", + "//s...@name='homed'][.='1000,10000']"); + assertQ(req("fl", "*,score", "q", + "{!func}sqedist(home, toMultiVS(0, 0))"), + "\"//*...@numfound='50']\""); + assertQ(req("fl", "*,score", "q", + "{!func}dist(2, home, toMultiVS(0, 0))"), + "\"//*...@numfound='50']\""); + + assertQ(req("fl", "*,score", "q", + "home:[10,10000 TO 30,30000]"), + "\"//*...@numfound='3']\""); + assertQ(req("fl", "*,score", "q", + "homed:[1,1000 TO 2000,35000]"), + "\"//*...@numfound='2']\""); + //bad + + assertQEx("Query should throw an exception due to incorrect dimensions", req("fl", "*,score", "q", + "homed:[1 TO 2000]"), SolrException.ErrorCode.BAD_REQUEST); + } + + + + public void testSearchDetails() throws Exception { + SolrCore core = h.getCore(); + IndexSchema schema = core.getSchema(); + double[] xy = new double[]{35.0, -79.34}; + String point = xy[0] + "," + xy[1]; + //How about some queries? + //don't need a parser for this path currently. This may change + assertU(adoc("id", "0", "home_ns", point)); + assertU(commit()); + SchemaField home = schema.getField("home_ns"); + PointType pt = (PointType) home.getType(); + assertEquals(pt.getDimension(), 2); + Query q = pt.getFieldQuery(null, home, point); + assertNotNull(q); + assertTrue(q instanceof BooleanQuery); + //should have two clauses, one for 35.0 and the other for -79.34 + BooleanQuery bq = (BooleanQuery) q; + BooleanClause[] clauses = bq.getClauses(); + assertEquals(clauses.length, 2); + + } +} \ No newline at end of file Propchange: lucene/solr/trunk/src/test/org/apache/solr/schema/PolyFieldTest.java ------------------------------------------------------------------------------ svn:eol-style = native Modified: lucene/solr/trunk/src/test/org/apache/solr/search/function/distance/DistanceFunctionTest.java URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/test/org/apache/solr/search/function/distance/DistanceFunctionTest.java?rev=893746&r1=893745&r2=893746&view=diff ============================================================================== --- lucene/solr/trunk/src/test/org/apache/solr/search/function/distance/DistanceFunctionTest.java (original) +++ lucene/solr/trunk/src/test/org/apache/solr/search/function/distance/DistanceFunctionTest.java Thu Dec 24 13:03:22 2009 @@ -16,9 +16,9 @@ * limitations under the License. */ +import org.apache.lucene.spatial.geohash.GeoHashUtils; import org.apache.solr.common.SolrException; import org.apache.solr.util.AbstractSolrTestCase; -import org.apache.lucene.spatial.geohash.GeoHashUtils; /** @@ -44,20 +44,21 @@ assertU(adoc("id", "2", "x_td", "0", "y_td", String.valueOf(Math.PI / 2), "gh_s", GeoHashUtils.encode(32.7693246, -78.9289094))); assertU(adoc("id", "3", "x_td", String.valueOf(Math.PI / 2), "y_td", String.valueOf(Math.PI / 2), "gh_s", GeoHashUtils.encode(32.7693246, -80.9289094))); assertU(adoc("id", "4", "x_td", String.valueOf(Math.PI / 4), "y_td", String.valueOf(Math.PI / 4), "gh_s", GeoHashUtils.encode(32.7693246, -81.9289094))); + assertU(adoc("id", "5", "x_td", "45.0", "y_td", "45.0", + "gh_s", GeoHashUtils.encode(32.7693246, -81.9289094))); assertU(commit()); //Get the haversine distance between the point 0,0 and the docs above assuming a radius of 1 - assertQ(req("fl", "*,score", "q", "{!func}hsin(x_td, y_td, 0, 0, 1)", "fq", "id:1"), "//flo...@name='score']='0.0'"); - assertQ(req("fl", "*,score", "q", "{!func}hsin(x_td, y_td, 0, 0, 1)", "fq", "id:2"), "//flo...@name='score']='" + (float) (Math.PI / 2) + "'"); - assertQ(req("fl", "*,score", "q", "{!func}hsin(x_td, y_td, 0, 0, 1)", "fq", "id:3"), "//flo...@name='score']='" + (float) (Math.PI / 2) + "'"); - assertQ(req("fl", "*,score", "q", "{!func}hsin(x_td, y_td, 0, 0, 1)", "fq", "id:4"), "//flo...@name='score']='1.0471976'"); + assertQ(req("fl", "*,score", "q", "{!func}hsin(1, x_td, y_td, 0, 0)", "fq", "id:1"), "//flo...@name='score']='0.0'"); + assertQ(req("fl", "*,score", "q", "{!func}hsin(1, x_td, y_td, 0, 0)", "fq", "id:2"), "//flo...@name='score']='" + (float) (Math.PI / 2) + "'"); + assertQ(req("fl", "*,score", "q", "{!func}hsin(1, x_td, y_td, 0, 0)", "fq", "id:3"), "//flo...@name='score']='" + (float) (Math.PI / 2) + "'"); + assertQ(req("fl", "*,score", "q", "{!func}hsin(1, x_td, y_td, 0, 0)", "fq", "id:4"), "//flo...@name='score']='1.0471976'"); + assertQ(req("fl", "*,score", "q", "{!func}hsin(1, x_td, y_td, 0, 0, true)", "fq", "id:5"), "//flo...@name='score']='1.0471976'"); //Geo Hash Haversine //Can verify here: http://www.movable-type.co.uk/scripts/latlong.html, but they use a slightly different radius for the earth, so just be close - assertQ(req("fl", "*,score", "q", "{!func}ghhsin(gh_s, \"" + GeoHashUtils.encode(32, -79) + - "\"," + Constants.EARTH_RADIUS_KM + - ")", "fq", "id:1"), "//flo...@name='score']='122.30894'"); - assertQ(req("fl", "*,score", "q", "{!func}ghhsin(gh_s, geohash(32, -79)," + Constants.EARTH_RADIUS_KM + - ")", "fq", "id:1"), "//flo...@name='score']='122.30894'"); + assertQ(req("fl", "*,score", "q", "{!func}ghhsin(" + Constants.EARTH_RADIUS_KM + ", gh_s, \"" + GeoHashUtils.encode(32, -79) + + "\",)", "fq", "id:1"), "//flo...@name='score']='122.30894'"); + assertQ(req("fl", "*,score", "q", "{!func}ghhsin(" + Constants.EARTH_RADIUS_KM + ", gh_s, geohash(32, -79))", "fq", "id:1"), "//flo...@name='score']='122.30894'"); } public void testVector() throws Exception { @@ -66,6 +67,8 @@ assertU(adoc("id", "3", "x_td", "1", "y_td", "1", "z_td", "1", "w_td", "1")); assertU(adoc("id", "4", "x_td", "1", "y_td", "0", "z_td", "0", "w_td", "0")); assertU(adoc("id", "5", "x_td", "2.3", "y_td", "5.5", "z_td", "7.9", "w_td", "-2.4")); + assertU(adoc("id", "6", "point", "1.0,0.0")); + assertU(adoc("id", "7", "point", "5.5,10.9")); assertU(commit()); //two dimensions, notice how we only pass in 4 value sources assertQ(req("fl", "*,score", "q", "{!func}sqedist(x_td, y_td, 0, 0)", "fq", "id:1"), "//flo...@name='score']='0.0'"); @@ -111,6 +114,15 @@ assertQ(req("fl", "*,score", "q", "{!func}dist(1, x_td, y_td, 0, 0)", "fq", "id:3"), "//flo...@name='score']='" + (float) 2.0 + "'"); assertQ(req("fl", "*,score", "q", "{!func}dist(1, x_td, y_td, 0, 0)", "fq", "id:4"), "//flo...@name='score']='1.0'"); assertQ(req("fl", "*,score", "q", "{!func}dist(1, x_td, y_td, 0, 0)", "fq", "id:5"), "//flo...@name='score']='" + (float) (2.3 + 5.5) + "'"); + + + //Do point tests: + assertQ(req("fl", "*,score", "q", "{!func}dist(1, toMultiVS(x_td, y_td), toMultiVS(0, 0))", "fq", "id:5"), + "//flo...@name='score']='" + (float) (2.3 + 5.5) + "'"); + + assertQ(req("fl", "*,score", "q", "{!func}dist(1, point, toMultiVS(0, 0))", "fq", "id:6"), + "//flo...@name='score']='" + 0.0f + "'"); + } } Modified: lucene/solr/trunk/src/test/org/apache/solr/update/DocumentBuilderTest.java URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/test/org/apache/solr/update/DocumentBuilderTest.java?rev=893746&r1=893745&r2=893746&view=diff ============================================================================== --- lucene/solr/trunk/src/test/org/apache/solr/update/DocumentBuilderTest.java (original) +++ lucene/solr/trunk/src/test/org/apache/solr/update/DocumentBuilderTest.java Thu Dec 24 13:03:22 2009 @@ -22,6 +22,7 @@ import org.apache.solr.common.SolrInputDocument; import org.apache.solr.core.SolrCore; import org.apache.solr.util.AbstractSolrTestCase; +import org.apache.solr.schema.FieldType; /** * @@ -59,4 +60,17 @@ Document out = DocumentBuilder.toDocument( doc, core.getSchema() ); assertNull( out.get( "name" ) ); } + + public void testMultiField() throws Exception { + SolrCore core = h.getCore(); + + // make sure a null value is not indexed + SolrInputDocument doc = new SolrInputDocument(); + doc.addField( "home", "2.2,3.3", 1.0f ); + Document out = DocumentBuilder.toDocument( doc, core.getSchema() ); + assertNotNull( out.get( "home" ) );//contains the stored value and term vector, if there is one + assertNotNull( out.getField( "home_0" + FieldType.POLY_FIELD_SEPARATOR + "double" ) ); + assertNotNull( out.getField( "home_1" + FieldType.POLY_FIELD_SEPARATOR + "double" ) ); + } + } Modified: lucene/solr/trunk/src/test/test-files/solr/conf/schema.xml URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/test/test-files/solr/conf/schema.xml?rev=893746&r1=893745&r2=893746&view=diff ============================================================================== --- lucene/solr/trunk/src/test/test-files/solr/conf/schema.xml (original) +++ lucene/solr/trunk/src/test/test-files/solr/conf/schema.xml Thu Dec 24 13:03:22 2009 @@ -367,6 +367,12 @@ </fieldtype> <fieldType name="uuid" class="solr.UUIDField" /> + + <!-- Try out some point types --> + <fieldType name="xy" class="solr.PointType" dimension="2" subFieldType="double"/> + <fieldType name="tenD" class="solr.PointType" dimension="10" subFieldType="double"/> + <!-- Use the sub field suffix --> + <fieldType name="xyd" class="solr.PointType" dimension="2" subFieldSuffix="*_d"/> </types> @@ -392,6 +398,15 @@ <field name="shouldbestored" type="unstored" stored="true"/> <field name="shouldbeunindexed" type="unstored" indexed="false" stored="true"/> + <!-- Test points --> + <!-- Test points --> + <field name="home" type="xy" indexed="true" stored="true" multiValued="false"/> + <field name="homed" type="xyd" indexed="true" stored="true" multiValued="false"/> + <field name="home_ns" type="xy" indexed="true" stored="false" multiValued="false"/> + <field name="work" type="xy" indexed="true" stored="true" multiValued="false"/> + + <field name="point10" type="tenD" indexed="true" stored="true" multiValued="false"/> + <!-- test different combinations of indexed and stored --> <field name="bind" type="boolean" indexed="true" stored="false"/> Modified: lucene/solr/trunk/src/test/test-files/solr/conf/schema11.xml URL: http://svn.apache.org/viewvc/lucene/solr/trunk/src/test/test-files/solr/conf/schema11.xml?rev=893746&r1=893745&r2=893746&view=diff ============================================================================== --- lucene/solr/trunk/src/test/test-files/solr/conf/schema11.xml (original) +++ lucene/solr/trunk/src/test/test-files/solr/conf/schema11.xml Thu Dec 24 13:03:22 2009 @@ -251,6 +251,10 @@ <fieldType name="tdoubles" class="solr.TrieDoubleField" omitNorms="true" positionIncrementGap="0" precisionStep="0" multiValued="true" /> <fieldType name="tdates" class="solr.TrieDateField" omitNorms="true" positionIncrementGap="0" precisionStep="0" multiValued="true" /> + <!-- Poly field --> + <fieldType name="xy" class="solr.PointType" dimension="2" subFieldType="double"/> + <fieldType name="xyd" class="solr.PointType" dimension="2" subFieldSuffix="*_d"/> + </types> @@ -275,7 +279,11 @@ <!-- for testing, a type that does a transform to see if it's correctly done everywhere --> <field name="id" type="sfloat" indexed="true" stored="true" required="true" /> - <field name="text" type="text" indexed="true" stored="false" /> + <field name="text" type="text" indexed="true" stored="false" /> + + <!-- Test a point field for distances --> + <field name="point" type="xy" indexed="true" stored="true" multiValued="false"/> + <field name="pointD" type="xyd" indexed="true" stored="true" multiValued="false"/> <!-- Dynamic field definitions. If a field name is not found, dynamicFields will be used if the name matches any of the patterns.
