diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt index d2a26c09f88..c1c7293f969 100644 --- a/solr/CHANGES.txt +++ b/solr/CHANGES.txt @@ -156,6 +156,10 @@ Bug Fixes * SOLR-10223: Allow running examples as root on Linux with -force option (janhoy) +* SOLR-10830: Solr now correctly enforces that the '_root_' field has the same fieldType as the + uniqueKey field. With out this enforcement, child document updating was unreliable. (hossman) + + Optimizations ---------------------- diff --git a/solr/core/src/java/org/apache/solr/schema/DoublePointField.java b/solr/core/src/java/org/apache/solr/schema/DoublePointField.java index 05a1ce7d124..d2cf6ed36db 100644 --- a/solr/core/src/java/org/apache/solr/schema/DoublePointField.java +++ b/solr/core/src/java/org/apache/solr/schema/DoublePointField.java @@ -61,7 +61,7 @@ public class DoublePointField extends PointField implements DoubleValueFieldType if (min == null) { actualMin = Double.NEGATIVE_INFINITY; } else { - actualMin = Double.parseDouble(min); + actualMin = parseDoubleFromUser(field.getName(), min); if (!minInclusive) { actualMin = DoublePoint.nextUp(actualMin); } @@ -69,7 +69,7 @@ public class DoublePointField extends PointField implements DoubleValueFieldType if (max == null) { actualMax = Double.POSITIVE_INFINITY; } else { - actualMax = Double.parseDouble(max); + actualMax = parseDoubleFromUser(field.getName(), max); if (!maxInclusive) { actualMax = DoublePoint.nextDown(actualMax); } @@ -100,7 +100,7 @@ public class DoublePointField extends PointField implements DoubleValueFieldType @Override protected Query getExactQuery(SchemaField field, String externalVal) { - return DoublePoint.newExactQuery(field.getName(), Double.parseDouble(externalVal)); + return DoublePoint.newExactQuery(field.getName(), parseDoubleFromUser(field.getName(), externalVal)); } @Override @@ -112,7 +112,7 @@ public class DoublePointField extends PointField implements DoubleValueFieldType double[] values = new double[externalVal.size()]; int i = 0; for (String val:externalVal) { - values[i] = Double.parseDouble(val); + values[i] = parseDoubleFromUser(field.getName(), val); i++; } return DoublePoint.newSetQuery(field.getName(), values); @@ -127,7 +127,7 @@ public class DoublePointField extends PointField implements DoubleValueFieldType public void readableToIndexed(CharSequence val, BytesRefBuilder result) { result.grow(Double.BYTES); result.setLength(Double.BYTES); - DoublePoint.encodeDimension(Double.parseDouble(val.toString()), result.bytes(), 0); + DoublePoint.encodeDimension(parseDoubleFromUser(null, val.toString()), result.bytes(), 0); } @Override diff --git a/solr/core/src/java/org/apache/solr/schema/FloatPointField.java b/solr/core/src/java/org/apache/solr/schema/FloatPointField.java index fe9c7533802..e1a97410323 100644 --- a/solr/core/src/java/org/apache/solr/schema/FloatPointField.java +++ b/solr/core/src/java/org/apache/solr/schema/FloatPointField.java @@ -61,7 +61,7 @@ public class FloatPointField extends PointField implements FloatValueFieldType { if (min == null) { actualMin = Float.NEGATIVE_INFINITY; } else { - actualMin = Float.parseFloat(min); + actualMin = parseFloatFromUser(field.getName(), min); if (!minInclusive) { actualMin = FloatPoint.nextUp(actualMin); } @@ -69,7 +69,7 @@ public class FloatPointField extends PointField implements FloatValueFieldType { if (max == null) { actualMax = Float.POSITIVE_INFINITY; } else { - actualMax = Float.parseFloat(max); + actualMax = parseFloatFromUser(field.getName(), max); if (!maxInclusive) { actualMax = FloatPoint.nextDown(actualMax); } @@ -100,7 +100,7 @@ public class FloatPointField extends PointField implements FloatValueFieldType { @Override protected Query getExactQuery(SchemaField field, String externalVal) { - return FloatPoint.newExactQuery(field.getName(), Float.parseFloat(externalVal)); + return FloatPoint.newExactQuery(field.getName(), parseFloatFromUser(field.getName(), externalVal)); } @Override @@ -112,7 +112,7 @@ public class FloatPointField extends PointField implements FloatValueFieldType { float[] values = new float[externalVal.size()]; int i = 0; for (String val:externalVal) { - values[i] = Float.parseFloat(val); + values[i] = parseFloatFromUser(field.getName(), val); i++; } return FloatPoint.newSetQuery(field.getName(), values); @@ -127,7 +127,7 @@ public class FloatPointField extends PointField implements FloatValueFieldType { public void readableToIndexed(CharSequence val, BytesRefBuilder result) { result.grow(Float.BYTES); result.setLength(Float.BYTES); - FloatPoint.encodeDimension(Float.parseFloat(val.toString()), result.bytes(), 0); + FloatPoint.encodeDimension(parseFloatFromUser(null, val.toString()), result.bytes(), 0); } @Override diff --git a/solr/core/src/java/org/apache/solr/schema/IndexSchema.java b/solr/core/src/java/org/apache/solr/schema/IndexSchema.java index 463df3ee681..36efbcf8a25 100644 --- a/solr/core/src/java/org/apache/solr/schema/IndexSchema.java +++ b/solr/core/src/java/org/apache/solr/schema/IndexSchema.java @@ -113,6 +113,7 @@ public class IndexSchema { public static final String SOURCE = "source"; public static final String TYPE = "type"; public static final String TYPES = "types"; + public static final String ROOT_FIELD_NAME = "_root_"; public static final String UNIQUE_KEY = "uniqueKey"; public static final String VERSION = "version"; @@ -517,6 +518,20 @@ public class IndexSchema { log.warn("no " + UNIQUE_KEY + " specified in schema."); } else { uniqueKeyField=getIndexedField(node.getNodeValue().trim()); + uniqueKeyFieldName=uniqueKeyField.getName(); + uniqueKeyFieldType=uniqueKeyField.getType(); + + // we fail on init if the ROOT field is *explicitly* defined as incompatible with uniqueKey + // we don't want ot fail if there happens to be a dynamicField matching ROOT, (ie: "*") + // because the user may not care about child docs at all. The run time code + // related to child docs can catch that if it happens + if (fields.containsKey(ROOT_FIELD_NAME) && ! isUsableForChildDocs()) { + String msg = ROOT_FIELD_NAME + " field must be defined using the exact same fieldType as the " + + UNIQUE_KEY + " field ("+uniqueKeyFieldName+") uses: " + uniqueKeyFieldType.getTypeName(); + log.error(msg); + throw new SolrException(ErrorCode.SERVER_ERROR, msg); + } + if (null != uniqueKeyField.getDefaultValue()) { String msg = UNIQUE_KEY + " field ("+uniqueKeyFieldName+ ") can not be configured with a default value ("+ @@ -542,9 +557,6 @@ public class IndexSchema { throw new SolrException(ErrorCode.SERVER_ERROR, msg); } - uniqueKeyFieldName=uniqueKeyField.getName(); - uniqueKeyFieldType=uniqueKeyField.getType(); - // Unless the uniqueKeyField is marked 'required=false' then make sure it exists if( Boolean.FALSE != explicitRequiredProp.get( uniqueKeyFieldName ) ) { uniqueKeyField.required = true; @@ -1914,4 +1926,17 @@ public class IndexSchema { + XPATH_OR + stepsToPath(SCHEMA, TYPES, FIELD_TYPE); return expression; } + + /** + * Helper method that returns true if the {@link #ROOT_FIELD_NAME} uses the exact + * same 'type' as the {@link #getUniqueKeyField()} + * + * @lucene.internal + */ + public boolean isUsableForChildDocs() { + FieldType rootType = getFieldType(ROOT_FIELD_NAME); + return (null != uniqueKeyFieldType && + null != rootType && + rootType.getTypeName().equals(uniqueKeyFieldType.getTypeName())); + } } diff --git a/solr/core/src/java/org/apache/solr/schema/IntPointField.java b/solr/core/src/java/org/apache/solr/schema/IntPointField.java index f47f45079e9..7d366127c0c 100644 --- a/solr/core/src/java/org/apache/solr/schema/IntPointField.java +++ b/solr/core/src/java/org/apache/solr/schema/IntPointField.java @@ -64,7 +64,7 @@ public class IntPointField extends PointField implements IntValueFieldType { if (min == null) { actualMin = Integer.MIN_VALUE; } else { - actualMin = Integer.parseInt(min); + actualMin = parseIntFromUser(field.getName(), min); if (!minInclusive) { actualMin++; } @@ -72,7 +72,7 @@ public class IntPointField extends PointField implements IntValueFieldType { if (max == null) { actualMax = Integer.MAX_VALUE; } else { - actualMax = Integer.parseInt(max); + actualMax = parseIntFromUser(field.getName(), max); if (!maxInclusive) { actualMax--; } @@ -97,7 +97,7 @@ public class IntPointField extends PointField implements IntValueFieldType { @Override protected Query getExactQuery(SchemaField field, String externalVal) { - return IntPoint.newExactQuery(field.getName(), Integer.parseInt(externalVal)); + return IntPoint.newExactQuery(field.getName(), parseIntFromUser(field.getName(), externalVal)); } @Override @@ -109,7 +109,7 @@ public class IntPointField extends PointField implements IntValueFieldType { int[] values = new int[externalVal.size()]; int i = 0; for (String val:externalVal) { - values[i] = Integer.parseInt(val); + values[i] = parseIntFromUser(field.getName(), val); i++; } return IntPoint.newSetQuery(field.getName(), values); @@ -124,7 +124,7 @@ public class IntPointField extends PointField implements IntValueFieldType { public void readableToIndexed(CharSequence val, BytesRefBuilder result) { result.grow(Integer.BYTES); result.setLength(Integer.BYTES); - IntPoint.encodeDimension(Integer.parseInt(val.toString()), result.bytes(), 0); + IntPoint.encodeDimension(parseIntFromUser(null, val.toString()), result.bytes(), 0); } @Override diff --git a/solr/core/src/java/org/apache/solr/schema/LongPointField.java b/solr/core/src/java/org/apache/solr/schema/LongPointField.java index bef6c472346..b2d8a3a03b4 100644 --- a/solr/core/src/java/org/apache/solr/schema/LongPointField.java +++ b/solr/core/src/java/org/apache/solr/schema/LongPointField.java @@ -63,7 +63,7 @@ public class LongPointField extends PointField implements LongValueFieldType { if (min == null) { actualMin = Long.MIN_VALUE; } else { - actualMin = Long.parseLong(min); + actualMin = parseLongFromUser(field.getName(), min); if (!minInclusive) { actualMin++; } @@ -71,7 +71,7 @@ public class LongPointField extends PointField implements LongValueFieldType { if (max == null) { actualMax = Long.MAX_VALUE; } else { - actualMax = Long.parseLong(max); + actualMax = parseLongFromUser(field.getName(), max); if (!maxInclusive) { actualMax--; } @@ -96,7 +96,7 @@ public class LongPointField extends PointField implements LongValueFieldType { @Override protected Query getExactQuery(SchemaField field, String externalVal) { - return LongPoint.newExactQuery(field.getName(), Long.parseLong(externalVal)); + return LongPoint.newExactQuery(field.getName(), parseLongFromUser(field.getName(), externalVal)); } @Override @@ -108,7 +108,7 @@ public class LongPointField extends PointField implements LongValueFieldType { long[] values = new long[externalVal.size()]; int i = 0; for (String val:externalVal) { - values[i] = Long.parseLong(val); + values[i] = parseLongFromUser(field.getName(), val); i++; } return LongPoint.newSetQuery(field.getName(), values); @@ -123,7 +123,7 @@ public class LongPointField extends PointField implements LongValueFieldType { public void readableToIndexed(CharSequence val, BytesRefBuilder result) { result.grow(Long.BYTES); result.setLength(Long.BYTES); - LongPoint.encodeDimension(Long.parseLong(val.toString()), result.bytes(), 0); + LongPoint.encodeDimension(parseLongFromUser(null, val.toString()), result.bytes(), 0); } @Override diff --git a/solr/core/src/java/org/apache/solr/schema/NumericFieldType.java b/solr/core/src/java/org/apache/solr/schema/NumericFieldType.java index 6cda9ca6438..cf17aaf7480 100644 --- a/solr/core/src/java/org/apache/solr/schema/NumericFieldType.java +++ b/solr/core/src/java/org/apache/solr/schema/NumericFieldType.java @@ -56,8 +56,8 @@ public abstract class NumericFieldType extends PrimitiveFieldType { switch (getNumberType()) { case INTEGER: return numericDocValuesRangeQuery(field.getName(), - min == null ? null : (long) Integer.parseInt(min), - max == null ? null : (long) Integer.parseInt(max), + min == null ? null : (long) parseIntFromUser(field.getName(), min), + max == null ? null : (long) parseIntFromUser(field.getName(), max), minInclusive, maxInclusive, field.multiValued()); case FLOAT: if (field.multiValued()) { @@ -67,8 +67,8 @@ public abstract class NumericFieldType extends PrimitiveFieldType { } case LONG: return numericDocValuesRangeQuery(field.getName(), - min == null ? null : Long.parseLong(min), - max == null ? null : Long.parseLong(max), + min == null ? null : parseLongFromUser(field.getName(), min), + max == null ? null : parseLongFromUser(field.getName(),max), minInclusive, maxInclusive, field.multiValued()); case DOUBLE: if (field.multiValued()) { @@ -90,8 +90,8 @@ public abstract class NumericFieldType extends PrimitiveFieldType { Query query; String fieldName = sf.getName(); - Number minVal = min == null ? null : getNumberType() == NumberType.FLOAT ? Float.parseFloat(min): Double.parseDouble(min); - Number maxVal = max == null ? null : getNumberType() == NumberType.FLOAT ? Float.parseFloat(max): Double.parseDouble(max); + Number minVal = min == null ? null : getNumberType() == NumberType.FLOAT ? parseFloatFromUser(sf.getName(), min): parseDoubleFromUser(sf.getName(), min); + Number maxVal = max == null ? null : getNumberType() == NumberType.FLOAT ? parseFloatFromUser(sf.getName(), max): parseDoubleFromUser(sf.getName(), max); Long minBits = min == null ? null : getNumberType() == NumberType.FLOAT ? (long) Float.floatToIntBits(minVal.floatValue()): Double.doubleToLongBits(minVal.doubleValue()); @@ -124,14 +124,14 @@ public abstract class NumericFieldType extends PrimitiveFieldType { } protected Query getRangeQueryForMultiValuedDoubleDocValues(SchemaField sf, String min, String max, boolean minInclusive, boolean maxInclusive) { - Long minBits = min == null ? NumericUtils.doubleToSortableLong(Double.NEGATIVE_INFINITY): NumericUtils.doubleToSortableLong(Double.parseDouble(min)); - Long maxBits = max == null ? NumericUtils.doubleToSortableLong(Double.POSITIVE_INFINITY): NumericUtils.doubleToSortableLong(Double.parseDouble(max)); + Long minBits = min == null ? NumericUtils.doubleToSortableLong(Double.NEGATIVE_INFINITY): NumericUtils.doubleToSortableLong(parseDoubleFromUser(sf.getName(), min)); + Long maxBits = max == null ? NumericUtils.doubleToSortableLong(Double.POSITIVE_INFINITY): NumericUtils.doubleToSortableLong(parseDoubleFromUser(sf.getName(), max)); return numericDocValuesRangeQuery(sf.getName(), minBits, maxBits, minInclusive, maxInclusive, true); } protected Query getRangeQueryForMultiValuedFloatDocValues(SchemaField sf, String min, String max, boolean minInclusive, boolean maxInclusive) { - Long minBits = (long)(min == null ? NumericUtils.floatToSortableInt(Float.NEGATIVE_INFINITY): NumericUtils.floatToSortableInt(Float.parseFloat(min))); - Long maxBits = (long)(max == null ? NumericUtils.floatToSortableInt(Float.POSITIVE_INFINITY): NumericUtils.floatToSortableInt(Float.parseFloat(max))); + Long minBits = (long)(min == null ? NumericUtils.floatToSortableInt(Float.NEGATIVE_INFINITY): NumericUtils.floatToSortableInt(parseFloatFromUser(sf.getName(), min))); + Long maxBits = (long)(max == null ? NumericUtils.floatToSortableInt(Float.POSITIVE_INFINITY): NumericUtils.floatToSortableInt(parseFloatFromUser(sf.getName(), max))); return numericDocValuesRangeQuery(sf.getName(), minBits, maxBits, minInclusive, maxInclusive, true); } @@ -169,4 +169,72 @@ public abstract class NumericFieldType extends PrimitiveFieldType { return NumericDocValuesField.newRangeQuery(field, actualLowerValue, actualUpperValue); } } + + /** + * Wrapper for {@link Long#parseLong(String)} that throws a BAD_REQUEST error if the input is not valid + * @param fieldName used in any exception, may be null + * @param val string to parse, NPE if null + */ + static long parseLongFromUser(String fieldName, String val) { + if (val == null) { + throw new NullPointerException("Invalid input" + (null == fieldName ? "" : " for field " + fieldName)); + } + try { + return Long.parseLong(val); + } catch (NumberFormatException e) { + String msg = "Invalid Number: " + val + (null == fieldName ? "" : " for field " + fieldName); + throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, msg); + } + } + + /** + * Wrapper for {@link Integer#parseInt(String)} that throws a BAD_REQUEST error if the input is not valid + * @param fieldName used in any exception, may be null + * @param val string to parse, NPE if null + */ + static int parseIntFromUser(String fieldName, String val) { + if (val == null) { + throw new NullPointerException("Invalid input" + (null == fieldName ? "" : " for field " + fieldName)); + } + try { + return Integer.parseInt(val); + } catch (NumberFormatException e) { + String msg = "Invalid Number: " + val + (null == fieldName ? "" : " for field " + fieldName); + throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, msg); + } + } + + /** + * Wrapper for {@link Double#parseDouble(String)} that throws a BAD_REQUEST error if the input is not valid + * @param fieldName used in any exception, may be null + * @param val string to parse, NPE if null + */ + static double parseDoubleFromUser(String fieldName, String val) { + if (val == null) { + throw new NullPointerException("Invalid input" + (null == fieldName ? "" : " for field " + fieldName)); + } + try { + return Double.parseDouble(val); + } catch (NumberFormatException e) { + String msg = "Invalid Number: " + val + (null == fieldName ? "" : " for field " + fieldName); + throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, msg); + } + } + + /** + * Wrapper for {@link Float#parseFloat(String)} that throws a BAD_REQUEST error if the input is not valid + * @param fieldName used in any exception, may be null + * @param val string to parse, NPE if null + */ + static float parseFloatFromUser(String fieldName, String val) { + if (val == null) { + throw new NullPointerException("Invalid input" + (null == fieldName ? "" : " for field " + fieldName)); + } + try { + return Float.parseFloat(val); + } catch (NumberFormatException e) { + String msg = "Invalid Number: " + val + (null == fieldName ? "" : " for field " + fieldName); + throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, msg); + } + } } diff --git a/solr/core/src/java/org/apache/solr/schema/PointField.java b/solr/core/src/java/org/apache/solr/schema/PointField.java index cad3c7e9706..98105af2ec8 100644 --- a/solr/core/src/java/org/apache/solr/schema/PointField.java +++ b/solr/core/src/java/org/apache/solr/schema/PointField.java @@ -127,7 +127,7 @@ public abstract class PointField extends NumericFieldType { return new IndexOrDocValuesQuery(pointsQuery, dvQuery); } else { return getExactQuery(field, externalVal); - } + } } protected abstract Query getExactQuery(SchemaField field, String externalVal); @@ -191,6 +191,11 @@ public abstract class PointField extends NumericFieldType { protected abstract String indexedToReadable(BytesRef indexedForm); + @Override + public Query getPrefixQuery(QParser parser, SchemaField sf, String termStr) { + throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Can't run prefix queries on numeric fields"); + } + protected boolean isFieldUsed(SchemaField field) { boolean indexed = field.indexed(); boolean stored = field.stored(); diff --git a/solr/core/src/java/org/apache/solr/schema/TrieField.java b/solr/core/src/java/org/apache/solr/schema/TrieField.java index f90877cbfdd..998583b449e 100644 --- a/solr/core/src/java/org/apache/solr/schema/TrieField.java +++ b/solr/core/src/java/org/apache/solr/schema/TrieField.java @@ -344,7 +344,7 @@ public class TrieField extends NumericFieldType { } int ps = precisionStep; Query query; - + if (field.hasDocValues() && !field.indexed()) { return getDocValuesRangeQuery(parser, field, min, max, minInclusive, maxInclusive); } @@ -352,26 +352,26 @@ public class TrieField extends NumericFieldType { switch (type) { case INTEGER: query = LegacyNumericRangeQuery.newIntRange(field.getName(), ps, - min == null ? null : Integer.parseInt(min), - max == null ? null : Integer.parseInt(max), + min == null ? null : parseIntFromUser(field.getName(), min), + max == null ? null : parseIntFromUser(field.getName(), max), minInclusive, maxInclusive); break; case FLOAT: query = LegacyNumericRangeQuery.newFloatRange(field.getName(), ps, - min == null ? null : Float.parseFloat(min), - max == null ? null : Float.parseFloat(max), + min == null ? null : parseFloatFromUser(field.getName(), min), + max == null ? null : parseFloatFromUser(field.getName(), max), minInclusive, maxInclusive); break; case LONG: query = LegacyNumericRangeQuery.newLongRange(field.getName(), ps, - min == null ? null : Long.parseLong(min), - max == null ? null : Long.parseLong(max), + min == null ? null : parseLongFromUser(field.getName(), min), + max == null ? null : parseLongFromUser(field.getName(), max), minInclusive, maxInclusive); break; case DOUBLE: query = LegacyNumericRangeQuery.newDoubleRange(field.getName(), ps, - min == null ? null : Double.parseDouble(min), - max == null ? null : Double.parseDouble(max), + min == null ? null : parseDoubleFromUser(field.getName(), min), + max == null ? null : parseDoubleFromUser(field.getName(), max), minInclusive, maxInclusive); break; case DATE: @@ -383,7 +383,6 @@ public class TrieField extends NumericFieldType { default: throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unknown type for trie field"); } - return query; } @@ -413,29 +412,24 @@ public class TrieField extends NumericFieldType { @Override public void readableToIndexed(CharSequence val, BytesRefBuilder result) { String s = val.toString(); - try { - switch (type) { - case INTEGER: - LegacyNumericUtils.intToPrefixCoded(Integer.parseInt(s), 0, result); - break; - case FLOAT: - LegacyNumericUtils.intToPrefixCoded(NumericUtils.floatToSortableInt(Float.parseFloat(s)), 0, result); - break; - case LONG: - LegacyNumericUtils.longToPrefixCoded(Long.parseLong(s), 0, result); - break; - case DOUBLE: - LegacyNumericUtils.longToPrefixCoded(NumericUtils.doubleToSortableLong(Double.parseDouble(s)), 0, result); - break; - case DATE: - LegacyNumericUtils.longToPrefixCoded(DateMathParser.parseMath(null, s).getTime(), 0, result); - break; - default: - throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unknown type for trie field: " + type); - } - } catch (NumberFormatException nfe) { - throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, - "Invalid Number: " + val); + switch (type) { + case INTEGER: + LegacyNumericUtils.intToPrefixCoded(parseIntFromUser(null, s), 0, result); + break; + case FLOAT: + LegacyNumericUtils.intToPrefixCoded(NumericUtils.floatToSortableInt(parseFloatFromUser(null, s)), 0, result); + break; + case LONG: + LegacyNumericUtils.longToPrefixCoded(parseLongFromUser(null, s), 0, result); + break; + case DOUBLE: + LegacyNumericUtils.longToPrefixCoded(NumericUtils.doubleToSortableLong(parseDoubleFromUser(null, s)), 0, result); + break; + case DATE: + LegacyNumericUtils.longToPrefixCoded(DateMathParser.parseMath(null, s).getTime(), 0, result); + break; + default: + throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unknown type for trie field: " + type); } } diff --git a/solr/core/src/java/org/apache/solr/update/AddUpdateCommand.java b/solr/core/src/java/org/apache/solr/update/AddUpdateCommand.java index 596ddd3d418..cb1af9a29f2 100644 --- a/solr/core/src/java/org/apache/solr/update/AddUpdateCommand.java +++ b/solr/core/src/java/org/apache/solr/update/AddUpdateCommand.java @@ -191,7 +191,7 @@ public class AddUpdateCommand extends UpdateCommand implements Iterable flatten(SolrInputDocument root) { List unwrappedDocs = new ArrayList<>(); recUnwrapp(unwrappedDocs, root); + if (1 < unwrappedDocs.size() && ! req.getSchema().isUsableForChildDocs()) { + throw new SolrException + (SolrException.ErrorCode.BAD_REQUEST, "Unable to index docs with children: the schema must " + + "include definitions for both a uniqueKey field and the '" + IndexSchema.ROOT_FIELD_NAME + + "' field, using the exact same fieldType"); + } return unwrappedDocs; } diff --git a/solr/core/src/java/org/apache/solr/update/DirectUpdateHandler2.java b/solr/core/src/java/org/apache/solr/update/DirectUpdateHandler2.java index e4811091c73..3efb748fd02 100644 --- a/solr/core/src/java/org/apache/solr/update/DirectUpdateHandler2.java +++ b/solr/core/src/java/org/apache/solr/update/DirectUpdateHandler2.java @@ -55,6 +55,7 @@ import org.apache.solr.request.LocalSolrQueryRequest; import org.apache.solr.request.SolrQueryRequest; import org.apache.solr.request.SolrRequestInfo; import org.apache.solr.response.SolrQueryResponse; +import org.apache.solr.schema.IndexSchema; import org.apache.solr.schema.SchemaField; import org.apache.solr.search.FunctionRangeQuery; import org.apache.solr.search.QParser; @@ -394,7 +395,7 @@ public class DirectUpdateHandler2 extends UpdateHandler implements SolrCoreState } private Term getIdTerm(AddUpdateCommand cmd) { - return new Term(cmd.isBlock() ? "_root_" : idField.getName(), cmd.getIndexedId()); + return new Term(cmd.isBlock() ? IndexSchema.ROOT_FIELD_NAME : idField.getName(), cmd.getIndexedId()); } private void updateDeleteTrackers(DeleteUpdateCommand cmd) { diff --git a/solr/core/src/java/org/apache/solr/update/DocumentBuilder.java b/solr/core/src/java/org/apache/solr/update/DocumentBuilder.java index b97af3bcb05..58638ae63c4 100644 --- a/solr/core/src/java/org/apache/solr/update/DocumentBuilder.java +++ b/solr/core/src/java/org/apache/solr/update/DocumentBuilder.java @@ -176,8 +176,8 @@ public class DocumentBuilder { // check if the copy field is a multivalued or not if (!destinationField.multiValued() && destHasValues) { throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, - "ERROR: "+getID(doc, schema)+"multiple values encountered for non multiValued copy field " + - destinationField.getName() + ": " + v); + "Multiple values encountered for non multiValued copy field " + + destinationField.getName() + ": " + v); } used = true; @@ -198,7 +198,9 @@ public class DocumentBuilder { } } catch( SolrException ex ) { - throw ex; + throw new SolrException(SolrException.ErrorCode.getErrorCode(ex.code()), + "ERROR: "+getID(doc, schema)+"Error adding field '" + + field.getName() + "'='" +field.getValue()+"' msg=" + ex.getMessage(), ex ); } catch( Exception ex ) { throw new SolrException( SolrException.ErrorCode.BAD_REQUEST, diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-uniquekey-diff-type-dynamic-root.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-uniquekey-diff-type-dynamic-root.xml new file mode 100644 index 00000000000..4f06bd1270c --- /dev/null +++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-uniquekey-diff-type-dynamic-root.xml @@ -0,0 +1,36 @@ + + + + + + + + + id + + + + + + + + diff --git a/solr/core/src/test-files/solr/collection1/conf/bad-schema-uniquekey-diff-type-root.xml b/solr/core/src/test-files/solr/collection1/conf/bad-schema-uniquekey-diff-type-root.xml new file mode 100644 index 00000000000..377762d0443 --- /dev/null +++ b/solr/core/src/test-files/solr/collection1/conf/bad-schema-uniquekey-diff-type-root.xml @@ -0,0 +1,35 @@ + + + + + + + + + id + + + + + + + diff --git a/solr/core/src/test-files/solr/collection1/conf/schema.xml b/solr/core/src/test-files/solr/collection1/conf/schema.xml index 05145f9464f..f0ddae44c46 100644 --- a/solr/core/src/test-files/solr/collection1/conf/schema.xml +++ b/solr/core/src/test-files/solr/collection1/conf/schema.xml @@ -734,11 +734,25 @@ useDocValuesAsStored="true"/> - + + + + + + + + + + + + diff --git a/solr/core/src/test-files/solr/collection1/conf/schema11.xml b/solr/core/src/test-files/solr/collection1/conf/schema11.xml index caa24cc2f25..819b6d1b9eb 100644 --- a/solr/core/src/test-files/solr/collection1/conf/schema11.xml +++ b/solr/core/src/test-files/solr/collection1/conf/schema11.xml @@ -413,6 +413,10 @@ valued. --> + + + + diff --git a/solr/core/src/test-files/solr/collection1/conf/schema12.xml b/solr/core/src/test-files/solr/collection1/conf/schema12.xml index 214fc26c65b..22363952b1a 100644 --- a/solr/core/src/test-files/solr/collection1/conf/schema12.xml +++ b/solr/core/src/test-files/solr/collection1/conf/schema12.xml @@ -604,16 +604,33 @@ + + + + + + + + + + + + + + + + + + + + - - - diff --git a/solr/core/src/test-files/solr/configsets/cloud-hdfs/conf/schema.xml b/solr/core/src/test-files/solr/configsets/cloud-hdfs/conf/schema.xml index aab5e811110..7b8b690d395 100644 --- a/solr/core/src/test-files/solr/configsets/cloud-hdfs/conf/schema.xml +++ b/solr/core/src/test-files/solr/configsets/cloud-hdfs/conf/schema.xml @@ -22,7 +22,7 @@ - + id diff --git a/solr/core/src/test-files/solr/configsets/cloud-managed-preanalyzed/conf/managed-schema b/solr/core/src/test-files/solr/configsets/cloud-managed-preanalyzed/conf/managed-schema index e70e02b36f8..39e142e2417 100644 --- a/solr/core/src/test-files/solr/configsets/cloud-managed-preanalyzed/conf/managed-schema +++ b/solr/core/src/test-files/solr/configsets/cloud-managed-preanalyzed/conf/managed-schema @@ -35,7 +35,7 @@ - + id diff --git a/solr/core/src/test-files/solr/configsets/cloud-managed-upgrade/conf/schema.xml b/solr/core/src/test-files/solr/configsets/cloud-managed-upgrade/conf/schema.xml index b9f09f9a50b..1d97a2ac5eb 100644 --- a/solr/core/src/test-files/solr/configsets/cloud-managed-upgrade/conf/schema.xml +++ b/solr/core/src/test-files/solr/configsets/cloud-managed-upgrade/conf/schema.xml @@ -21,7 +21,7 @@ - + id diff --git a/solr/core/src/test-files/solr/configsets/cloud-managed/conf/managed-schema b/solr/core/src/test-files/solr/configsets/cloud-managed/conf/managed-schema index b9f09f9a50b..1d97a2ac5eb 100644 --- a/solr/core/src/test-files/solr/configsets/cloud-managed/conf/managed-schema +++ b/solr/core/src/test-files/solr/configsets/cloud-managed/conf/managed-schema @@ -21,7 +21,7 @@ - + id diff --git a/solr/core/src/test-files/solr/configsets/cloud-minimal-jmx/conf/schema.xml b/solr/core/src/test-files/solr/configsets/cloud-minimal-jmx/conf/schema.xml index aab5e811110..7b8b690d395 100644 --- a/solr/core/src/test-files/solr/configsets/cloud-minimal-jmx/conf/schema.xml +++ b/solr/core/src/test-files/solr/configsets/cloud-minimal-jmx/conf/schema.xml @@ -22,7 +22,7 @@ - + id diff --git a/solr/core/src/test-files/solr/configsets/cloud-minimal/conf/schema.xml b/solr/core/src/test-files/solr/configsets/cloud-minimal/conf/schema.xml index aab5e811110..7b8b690d395 100644 --- a/solr/core/src/test-files/solr/configsets/cloud-minimal/conf/schema.xml +++ b/solr/core/src/test-files/solr/configsets/cloud-minimal/conf/schema.xml @@ -22,7 +22,7 @@ - + id diff --git a/solr/core/src/test-files/solr/configsets/cloud-subdirs/conf/schema.xml b/solr/core/src/test-files/solr/configsets/cloud-subdirs/conf/schema.xml index aab5e811110..7b8b690d395 100644 --- a/solr/core/src/test-files/solr/configsets/cloud-subdirs/conf/schema.xml +++ b/solr/core/src/test-files/solr/configsets/cloud-subdirs/conf/schema.xml @@ -22,7 +22,7 @@ - + id diff --git a/solr/core/src/test-files/solr/configsets/exitable-directory/conf/schema.xml b/solr/core/src/test-files/solr/configsets/exitable-directory/conf/schema.xml index aab5e811110..7b8b690d395 100644 --- a/solr/core/src/test-files/solr/configsets/exitable-directory/conf/schema.xml +++ b/solr/core/src/test-files/solr/configsets/exitable-directory/conf/schema.xml @@ -22,7 +22,7 @@ - + id diff --git a/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java b/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java index 02ae888bd7d..b1747fd9afb 100644 --- a/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java +++ b/solr/core/src/test/org/apache/solr/BasicFunctionalityTest.java @@ -341,6 +341,59 @@ public class BasicFunctionalityTest extends SolrTestCaseJ4 { ); } + @Test + public void testClientErrorOnMalformedDate() throws Exception { + final String BAD_VALUE = "NOT_A_DATE"; + ignoreException(BAD_VALUE); + + final List FIELDS = new LinkedList<>(); + for (String type : new String[] { + "tdt", "tdt1", "tdtdv", "tdtdv1", + "dt_dv", "dt_dvo", "dt", "dt1", "dt_os" + }) { + FIELDS.add("malformed_" + type); + } + + // test that malformed numerics cause client error not server error + for (String field : FIELDS) { + try { + h.update(add( doc("id","100", field, BAD_VALUE))); + fail("Didn't encounter an error trying to add a bad date: " + field); + } catch (SolrException e) { + String msg = e.toString(); + assertTrue("not an (update) client error on field: " + field +" : "+ msg, + 400 <= e.code() && e.code() < 500); + assertTrue("(update) client error does not mention bad value: " + msg, + msg.contains(BAD_VALUE)); + assertTrue("client error does not mention document id: " + msg, + msg.contains("[doc=100]")); + } + SchemaField sf = h.getCore().getLatestSchema().getField(field); + if (!sf.hasDocValues() && !sf.indexed()) { + continue; + } + try { + h.query(req("q",field + ":" + BAD_VALUE)); + fail("Didn't encounter an error trying to query a bad date: " + field); + } catch (SolrException e) { + String msg = e.toString(); + assertTrue("not a (search) client error on field: " + field +" : "+ msg, + 400 <= e.code() && e.code() < 500); + assertTrue("(search) client error does not mention bad value: " + msg, + msg.contains(BAD_VALUE)); + } + try { + h.query(req("q",field + ":[NOW TO " + BAD_VALUE + "]")); + fail("Didn't encounter an error trying to query a bad date: " + field); + } catch (SolrException e) { + String msg = e.toString(); + assertTrue("not a (search) client error on field: " + field +" : "+ msg, + 400 <= e.code() && e.code() < 500); + assertTrue("(search) client error does not mention bad value: " + msg, + msg.contains(BAD_VALUE)); + } + } + } @Test public void testClientErrorOnMalformedNumbers() throws Exception { @@ -349,7 +402,13 @@ public class BasicFunctionalityTest extends SolrTestCaseJ4 { ignoreException(BAD_VALUE); final List FIELDS = new LinkedList<>(); - for (String type : new String[] { "ti", "tf", "td", "tl" }) { + for (String type : new String[] { + "ti", "tf", "td", "tl", + "i", "f", "d", "l", + "i_dv", "f_dv", "d_dv", "l_dv", + "i_dvo", "f_dvo", "d_dvo", "l_dvo", + "i_os", "f_os", "d_os", "l_os" + }) { FIELDS.add("malformed_" + type); } @@ -364,6 +423,12 @@ public class BasicFunctionalityTest extends SolrTestCaseJ4 { 400 <= e.code() && e.code() < 500); assertTrue("(update) client error does not mention bad value: " + msg, msg.contains(BAD_VALUE)); + assertTrue("client error does not mention document id", + msg.contains("[doc=100]")); + } + SchemaField sf = h.getCore().getLatestSchema().getField(field); + if (!sf.hasDocValues() && !sf.indexed()) { + continue; } try { h.query(req("q",field + ":" + BAD_VALUE)); @@ -375,6 +440,16 @@ public class BasicFunctionalityTest extends SolrTestCaseJ4 { assertTrue("(search) client error does not mention bad value: " + msg, msg.contains(BAD_VALUE)); } + try { + h.query(req("q",field + ":[10 TO " + BAD_VALUE + "]")); + fail("Didn't encounter an error trying to query a non-number: " + field); + } catch (SolrException e) { + String msg = e.toString(); + assertTrue("not a (search) client error on field: " + field +" : "+ msg, + 400 <= e.code() && e.code() < 500); + assertTrue("(search) client error does not mention bad value: " + msg, + msg.contains(BAD_VALUE)); + } } } diff --git a/solr/core/src/test/org/apache/solr/schema/BadIndexSchemaTest.java b/solr/core/src/test/org/apache/solr/schema/BadIndexSchemaTest.java index 5545d959026..f635436fa83 100644 --- a/solr/core/src/test/org/apache/solr/schema/BadIndexSchemaTest.java +++ b/solr/core/src/test/org/apache/solr/schema/BadIndexSchemaTest.java @@ -101,6 +101,21 @@ public class BadIndexSchemaTest extends AbstractBadConfigTestBase { public void testDocValuesUnsupported() throws Exception { doTest("bad-schema-unsupported-docValues.xml", "does not support doc values"); } + + public void testRootTypeMissmatchWithUniqueKey() throws Exception { + doTest("bad-schema-uniquekey-diff-type-root.xml", + "using the exact same fieldType as the uniqueKey field (id) uses: string1"); + } + + public void testRootTypeDynamicMissmatchWithUniqueKey() throws Exception { + // in this case, the core should load fine -- but we should get an error adding docs + try { + initCore("solrconfig.xml","bad-schema-uniquekey-diff-type-dynamic-root.xml"); + assertFailedU("Unable to index docs with children", adoc(sdocWithChildren("1","-1"))); + } finally { + deleteCore(); + } + } public void testSweetSpotSimBadConfig() throws Exception { doTest("bad-schema-sweetspot-both-tf.xml", "Can not mix"); diff --git a/solr/core/src/test/org/apache/solr/search/TestQueryTypes.java b/solr/core/src/test/org/apache/solr/search/TestQueryTypes.java index 3f57ee5da0c..f282c3f2171 100644 --- a/solr/core/src/test/org/apache/solr/search/TestQueryTypes.java +++ b/solr/core/src/test/org/apache/solr/search/TestQueryTypes.java @@ -20,6 +20,7 @@ import org.apache.solr.common.SolrException; import org.apache.solr.common.params.CommonParams; import org.apache.solr.util.AbstractSolrTestCase; import org.junit.BeforeClass; +import org.junit.Test; public class TestQueryTypes extends AbstractSolrTestCase { @@ -209,7 +210,6 @@ public class TestQueryTypes extends AbstractSolrTestCase { ,"//result[@numFound='2']" ); - // // test escapes in quoted strings // @@ -436,5 +436,49 @@ public class TestQueryTypes extends AbstractSolrTestCase { assertQ("Test text field with no analysis doesn't NPE with wildcards (SOLR-4318)", req("q", "text_no_analyzer:should*"), "//result[@numFound='1']"); + + } + + @Test + public void testNumericBadRequests() { + String[] suffixes = new String[50]; + int fieldNum = 0; + for (String type:new String[]{"i", "l", "f", "d", "dt"}) { + for (String s:new String[]{"", "s"}) { + //Trie + suffixes[fieldNum++] = "t" + type + s; + suffixes[fieldNum++] = "t" + type + s + "_dv"; + suffixes[fieldNum++] = "t" + type + s + "_ni_dv"; + + //Points + suffixes[fieldNum++] = type + s + "_p"; + suffixes[fieldNum++] = type + s + "_ni_p"; + } + } + assertEquals(fieldNum,suffixes.length); + + String badNumber = "NOT_A_NUMBER"; + for (String suffix:suffixes) { + // Numeric bad requests + assertQEx("Expecting exception for suffix: " + suffix, badNumber, req("q","{!term f=foo_" + suffix + "}" + badNumber), SolrException.ErrorCode.BAD_REQUEST); + assertQEx("Expecting exception for suffix: " + suffix, badNumber, req("q","{!terms f=foo_" + suffix + "}1 2 3 4 5 " + badNumber), SolrException.ErrorCode.BAD_REQUEST); + assertQEx("Expecting exception for suffix: " + suffix, badNumber, req("q","{!lucene}foo_" + suffix + ":" + badNumber), SolrException.ErrorCode.BAD_REQUEST); + assertQEx("Expecting exception for suffix: " + suffix, badNumber, req("q","{!field f=foo_" + suffix + "}" + badNumber), SolrException.ErrorCode.BAD_REQUEST); + assertQEx("Expecting exception for suffix: " + suffix, badNumber, req("q","{!maxscore}foo_" + suffix + ":" + badNumber), SolrException.ErrorCode.BAD_REQUEST); + assertQEx("Expecting exception for suffix: " + suffix, badNumber, + req("q","{!xmlparser}"), SolrException.ErrorCode.BAD_REQUEST); + if (suffix.contains("_p")) { + // prefix queries work in Trie fields + assertQEx("Expecting exception for suffix: " + suffix, "Can't run prefix queries on numeric fields", + req("q","{!prefix f=foo_" + suffix + "}NOT_A_NUMBER"), SolrException.ErrorCode.BAD_REQUEST); + assertQEx("Expecting exception for suffix: " + suffix, "Can't run prefix queries on numeric fields", + req("q","{!lucene}foo_" + suffix + ":123*"), SolrException.ErrorCode.BAD_REQUEST); + } + + // Skipping: func, boost, raw, nested, frange, spatial*, join, surround, switch, parent, child, collapsing, + // complexphrase, rerank, export, mlt, hash, graph, graphTerms, igain, tlogit, sigificantTerms, payload* + // Maybe add: raw, join, parent, child, collapsing, graphTerms, igain, sigificantTerms, simple + } + } } diff --git a/solr/core/src/test/org/apache/solr/search/TestSolrQueryParser.java b/solr/core/src/test/org/apache/solr/search/TestSolrQueryParser.java index 1a2e5720822..4b82e6243d3 100644 --- a/solr/core/src/test/org/apache/solr/search/TestSolrQueryParser.java +++ b/solr/core/src/test/org/apache/solr/search/TestSolrQueryParser.java @@ -33,6 +33,7 @@ import org.apache.lucene.search.Query; import org.apache.lucene.search.TermInSetQuery; import org.apache.lucene.search.TermQuery; import org.apache.solr.SolrTestCaseJ4; +import org.apache.solr.common.SolrException; import org.apache.solr.common.params.MapSolrParams; import org.apache.solr.metrics.MetricsMap; import org.apache.solr.common.params.ModifiableSolrParams; @@ -1038,4 +1039,39 @@ public class TestSolrQueryParser extends SolrTestCaseJ4 { , "/response/numFound==1" ); } + + @Test + public void testBadRequestInSetQuery() throws SyntaxError { + SolrQueryRequest req = req(); + QParser qParser; + String[] fieldSuffix = new String[] { + "ti", "tf", "td", "tl", + "i", "f", "d", "l", + "is", "fs", "ds", "ls", + "i_dv", "f_dv", "d_dv", "l_dv", + "is_dv", "fs_dv", "ds_dv", "ls_dv", + "i_dvo", "f_dvo", "d_dvo", "l_dvo", + }; + + for (String suffix:fieldSuffix) { + //Good queries + qParser = QParser.getParser("foo_" + suffix + ":(1 2 3 4 5 6 7 8 9 10 20 19 18 17 16 15 14 13 12 25)", req); + qParser.setIsFilter(true); + qParser.getQuery(); + } + + for (String suffix:fieldSuffix) { + qParser = QParser.getParser("foo_" + suffix + ":(1 2 3 4 5 6 7 8 9 10 20 19 18 17 16 15 14 13 12 NOT_A_NUMBER)", req); + qParser.setIsFilter(true); // this may change in the future + try { + qParser.getQuery(); + fail("Expecting exception"); + } catch (SolrException e) { + assertEquals(SolrException.ErrorCode.BAD_REQUEST.code, e.code()); + assertTrue("Unexpected exception: " + e.getMessage(), e.getMessage().contains("Invalid Number: NOT_A_NUMBER")); + } + } + + + } } \ No newline at end of file