SOLR-10830: Solr now correctly enforces that the '_root_' field has the same fieldType as the uniqueKey field

This commit is contained in:
Chris Hostetter 2017-06-13 10:17:32 -07:00
parent e50332507a
commit 6396cb759f
29 changed files with 467 additions and 86 deletions

View File

@ -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
----------------------

View File

@ -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

View File

@ -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

View File

@ -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 <code>true</code> 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()));
}
}

View File

@ -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

View File

@ -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

View File

@ -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);
}
}
}

View File

@ -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();

View File

@ -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,19 +412,18 @@ 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);
LegacyNumericUtils.intToPrefixCoded(parseIntFromUser(null, s), 0, result);
break;
case FLOAT:
LegacyNumericUtils.intToPrefixCoded(NumericUtils.floatToSortableInt(Float.parseFloat(s)), 0, result);
LegacyNumericUtils.intToPrefixCoded(NumericUtils.floatToSortableInt(parseFloatFromUser(null, s)), 0, result);
break;
case LONG:
LegacyNumericUtils.longToPrefixCoded(Long.parseLong(s), 0, result);
LegacyNumericUtils.longToPrefixCoded(parseLongFromUser(null, s), 0, result);
break;
case DOUBLE:
LegacyNumericUtils.longToPrefixCoded(NumericUtils.doubleToSortableLong(Double.parseDouble(s)), 0, result);
LegacyNumericUtils.longToPrefixCoded(NumericUtils.doubleToSortableLong(parseDoubleFromUser(null, s)), 0, result);
break;
case DATE:
LegacyNumericUtils.longToPrefixCoded(DateMathParser.parseMath(null, s).getTime(), 0, result);
@ -433,10 +431,6 @@ public class TrieField extends NumericFieldType {
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);
}
}
@Override

View File

@ -191,7 +191,7 @@ public class AddUpdateCommand extends UpdateCommand implements Iterable<Document
boolean isVersion = version != 0;
for (SolrInputDocument sdoc : all) {
sdoc.setField("_root_", idField); // should this be a string or the same type as the ID?
sdoc.setField(IndexSchema.ROOT_FIELD_NAME, idField);
if(isVersion) sdoc.setField(CommonParams.VERSION_FIELD, version);
// TODO: if possible concurrent modification exception (if SolrInputDocument not cloned and is being forwarded to replicas)
// then we could add this field to the generated lucene document instead.
@ -220,6 +220,12 @@ public class AddUpdateCommand extends UpdateCommand implements Iterable<Document
private List<SolrInputDocument> flatten(SolrInputDocument root) {
List<SolrInputDocument> 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;
}

View File

@ -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) {

View File

@ -176,7 +176,7 @@ 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 " +
"Multiple values encountered for non multiValued copy field " +
destinationField.getName() + ": " + v);
}
@ -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,

View File

@ -0,0 +1,36 @@
<?xml version="1.0" ?>
<!--
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.
-->
<schema name="bad-schema-uniquekey-diff-type-dynamic-root" version="1.4">
<!-- NOTE: these fieldTypes are defined with same class, and same props, but schema should
still error because the type *names* used not identical.
this is risky, because it means the types could diverge over time, and we wnat to
protect the user fromthat.
-->
<fieldType name="string1" class="solr.StrField"/>
<fieldType name="string2" class="solr.StrField"/>
<uniqueKey>id</uniqueKey>
<field name="id" type="string1" indexed="true" stored="true" />
<!-- BEGIN BAD STUFF -->
<!-- matches '_root_' -->
<dynamicField name="*" type="string2" indexed="true" stored="true" />
<!-- END BAD STUFF -->
</schema>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" ?>
<!--
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.
-->
<schema name="bad-schema-uniquekey-diff-type-root" version="1.4">
<!-- NOTE: these fieldTypes are defined with same class, and same props, but schema should
still error because the type *names* used not identical.
this is risky, because it means the types could diverge over time, and we wnat to
protect the user fromthat.
-->
<fieldType name="string1" class="solr.StrField"/>
<fieldType name="string2" class="solr.StrField"/>
<uniqueKey>id</uniqueKey>
<field name="id" type="string1" indexed="true" stored="true" />
<!-- BEGIN BAD STUFF -->
<field name="_root_" type="string2" indexed="true" stored="true" />
<!-- END BAD STUFF -->
</schema>

View File

@ -734,12 +734,26 @@
useDocValuesAsStored="true"/>
<dynamicField name="*_s_dvo" multiValued="false" type="string" docValues="true" indexed="false" stored="false"
useDocValuesAsStored="true"/>
<dynamicField name="*_l_dvo" multiValued="false" type="${solr.tests.longClass:plong}" docValues="true" indexed="false" stored="false"
useDocValuesAsStored="true"/>
<dynamicField name="*_f_dvo" multiValued="false" type="${solr.tests.floatClass:pfloat}" docValues="true" indexed="false" stored="false"
useDocValuesAsStored="true"/>
<dynamicField name="*_dt_dvo" multiValued="false" type="${solr.tests.dateClass:pdate}" docValues="true" indexed="false" stored="false"
useDocValuesAsStored="true"/>
<dynamicField name="*_ii_dvo" multiValued="true" type="${solr.tests.intClass:pint}" docValues="true" indexed="false" stored="false"
useDocValuesAsStored="true"/>
<dynamicField name="*_dd_dvo" multiValued="true" type="${solr.tests.doubleClass:pdouble}" docValues="true" indexed="false" stored="false"
useDocValuesAsStored="true"/>
<!-- Only Stored numerics -->
<dynamicField name="*_i_os" type="${solr.tests.intClass:pint}" indexed="false" stored="true" docValues="false"/>
<dynamicField name="*_l_os" type="${solr.tests.longClass:plong}" indexed="false" stored="true" docValues="false"/>
<dynamicField name="*_f_os" type="${solr.tests.floatClass:pfloat}" indexed="false" stored="true" docValues="false"/>
<dynamicField name="*_d_os" type="${solr.tests.doubleClass:pdouble}" indexed="false" stored="true" docValues="false"/>
<dynamicField name="*_dt_os" type="${solr.tests.dateClass:pdate}" indexed="false" stored="true" docValues="false"/>
<!-- Non-stored, DocValues=true, useDocValuesAsStored=false -->
<field name="single_i_dvn" multiValued="false" type="${solr.tests.intClass:pint}" indexed="true" stored="true"/>
<field name="single_d_dvn" multiValued="false" type="${solr.tests.doubleClass:pdouble}" indexed="true" stored="true"/>

View File

@ -413,6 +413,10 @@ valued. -->
<dynamicField name="*_ds_p" type="pdouble" indexed="true" stored="true" docValues="true" multiValued="true"/>
<dynamicField name="*_d_ni_p" type="pdouble" indexed="false" stored="true" docValues="true"/>
<dynamicField name="*_ds_ni_p" type="pdouble" indexed="false" stored="true" docValues="true" multiValued="true"/>
<dynamicField name="*_dt_p" type="pdate" indexed="true" stored="true" docValues="true"/>
<dynamicField name="*_dts_p" type="pdate" indexed="true" stored="true" docValues="true" multiValued="true"/>
<dynamicField name="*_dt_ni_p" type="pdate" indexed="false" stored="true" docValues="true"/>
<dynamicField name="*_dts_ni_p" type="pdate" indexed="false" stored="true" docValues="true" multiValued="true"/>
<dynamicField name="*_t" type="text" indexed="true" stored="true"/>
<dynamicField name="*_b" type="boolean" indexed="true" stored="true"/>

View File

@ -604,16 +604,33 @@
<dynamicField name="*_is" type="${solr.tests.intClass:pint}" indexed="true" stored="true" multiValued="true"/>
<dynamicField name="*_i_dv" type="${solr.tests.intClass:pint}" indexed="true" stored="true" docValues="true" multiValued="false"/>
<dynamicField name="*_is_dv" type="${solr.tests.intClass:pint}" indexed="true" stored="true" docValues="true" multiValued="true"/>
<dynamicField name="*_i_dvo" type="${solr.tests.intClass:pint}" indexed="false" stored="true" docValues="true"/>
<dynamicField name="*_f" type="${solr.tests.floatClass:pfloat}" indexed="true" stored="true"/>
<dynamicField name="*_fs" type="${solr.tests.floatClass:pfloat}" indexed="true" stored="true" multiValued="true"/>
<dynamicField name="*_f_dv" type="${solr.tests.floatClass:pfloat}" indexed="true" stored="true" docValues="true" multiValued="false"/>
<dynamicField name="*_fs_dv" type="${solr.tests.floatClass:pfloat}" indexed="true" stored="true" docValues="true" multiValued="true"/>
<dynamicField name="*_f_dvo" type="${solr.tests.floatClass:pfloat}" indexed="false" stored="true" docValues="true"/>
<dynamicField name="*_l" type="${solr.tests.longClass:plong}" indexed="true" stored="true"/>
<dynamicField name="*_ls" type="${solr.tests.longClass:plong}" indexed="true" stored="true" multiValued="true"/>
<dynamicField name="*_l_dv" type="${solr.tests.longClass:plong}" indexed="true" stored="true" docValues="true" multiValued="false"/>
<dynamicField name="*_ls_dv" type="${solr.tests.longClass:plong}" indexed="true" stored="true" docValues="true" multiValued="true"/>
<dynamicField name="*_l_dvo" type="${solr.tests.longClass:plong}" indexed="false" stored="true" docValues="true"/>
<dynamicField name="*_d" type="${solr.tests.doubleClass:pdouble}" indexed="true" stored="true"/>
<dynamicField name="*_ds" type="${solr.tests.doubleClass:pdouble}" indexed="true" stored="true" multiValued="true"/>
<dynamicField name="*_d_dv" type="${solr.tests.doubleClass:pdouble}" indexed="true" stored="true" docValues="true" multiValued="false"/>
<dynamicField name="*_ds_dv" type="${solr.tests.doubleClass:pdouble}" indexed="true" stored="true" docValues="true" multiValued="true"/>
<dynamicField name="*_d_dvo" type="${solr.tests.doubleClass:pdouble}" indexed="false" stored="true" docValues="true"/>
<dynamicField name="*_s1" type="string" indexed="true" stored="true" multiValued="false"/>
<!-- :TODO: why are these identical?!?!?! -->
<dynamicField name="*_s" type="string" indexed="true" stored="true" multiValued="true"/>
<dynamicField name="*_ss" type="string" indexed="true" stored="true" multiValued="true"/>
<dynamicField name="*_l" type="${solr.tests.longClass:plong}" indexed="true" stored="true"/>
<dynamicField name="*_t" type="text" indexed="true" stored="true"/>
<dynamicField name="*_tt" type="text" indexed="true" stored="true"/>
<dynamicField name="*_b" type="boolean" indexed="true" stored="true"/>
<dynamicField name="*_f" type="${solr.tests.floatClass:pfloat}" indexed="true" stored="true"/>
<dynamicField name="*_d" type="${solr.tests.doubleClass:pdouble}" indexed="true" stored="true"/>
<dynamicField name="*_dt" type="${solr.tests.dateClass:pdate}" indexed="true" stored="true"/>
<dynamicField name="*_pi" type="pint" indexed="true" stored="true" docValues="false" multiValued="false"/>

View File

@ -22,7 +22,7 @@
<dynamicField name="*" type="string" indexed="true" stored="true"/>
<!-- for versioning -->
<field name="_version_" type="long" indexed="true" stored="true"/>
<field name="_root_" type="int" indexed="true" stored="true" multiValued="false" required="false"/>
<field name="_root_" type="string" indexed="true" stored="true" multiValued="false" required="false"/>
<field name="id" type="string" indexed="true" stored="true"/>
<uniqueKey>id</uniqueKey>
</schema>

View File

@ -35,7 +35,7 @@
<!-- for versioning -->
<field name="_version_" type="long" indexed="true" stored="true"/>
<field name="_root_" type="int" indexed="true" stored="true" multiValued="false" required="false"/>
<field name="_root_" type="string" indexed="true" stored="true" multiValued="false" required="false"/>
<field name="id" type="string" indexed="true" stored="true"/>
<uniqueKey>id</uniqueKey>
</schema>

View File

@ -21,7 +21,7 @@
<fieldType name="long" class="solr.TrieLongField" precisionStep="0" omitNorms="true" positionIncrementGap="0"/>
<!-- for versioning -->
<field name="_version_" type="long" indexed="true" stored="true"/>
<field name="_root_" type="int" indexed="true" stored="true" multiValued="false" required="false"/>
<field name="_root_" type="string" indexed="true" stored="true" multiValued="false" required="false"/>
<field name="id" type="string" indexed="true" stored="true"/>
<uniqueKey>id</uniqueKey>
</schema>

View File

@ -21,7 +21,7 @@
<fieldType name="long" class="solr.TrieLongField" precisionStep="0" omitNorms="true" positionIncrementGap="0"/>
<!-- for versioning -->
<field name="_version_" type="long" indexed="true" stored="true"/>
<field name="_root_" type="int" indexed="true" stored="true" multiValued="false" required="false"/>
<field name="_root_" type="string" indexed="true" stored="true" multiValued="false" required="false"/>
<field name="id" type="string" indexed="true" stored="true"/>
<uniqueKey>id</uniqueKey>
</schema>

View File

@ -22,7 +22,7 @@
<dynamicField name="*" type="string" indexed="true" stored="true"/>
<!-- for versioning -->
<field name="_version_" type="long" indexed="true" stored="true"/>
<field name="_root_" type="int" indexed="true" stored="true" multiValued="false" required="false"/>
<field name="_root_" type="string" indexed="true" stored="true" multiValued="false" required="false"/>
<field name="id" type="string" indexed="true" stored="true"/>
<uniqueKey>id</uniqueKey>
</schema>

View File

@ -22,7 +22,7 @@
<dynamicField name="*" type="string" indexed="true" stored="true"/>
<!-- for versioning -->
<field name="_version_" type="long" indexed="true" stored="true"/>
<field name="_root_" type="int" indexed="true" stored="true" multiValued="false" required="false"/>
<field name="_root_" type="string" indexed="true" stored="true" multiValued="false" required="false"/>
<field name="id" type="string" indexed="true" stored="true"/>
<uniqueKey>id</uniqueKey>
</schema>

View File

@ -22,7 +22,7 @@
<dynamicField name="*" type="string" indexed="true" stored="true"/>
<!-- for versioning -->
<field name="_version_" type="long" indexed="true" stored="true"/>
<field name="_root_" type="int" indexed="true" stored="true" multiValued="false" required="false"/>
<field name="_root_" type="string" indexed="true" stored="true" multiValued="false" required="false"/>
<field name="id" type="string" indexed="true" stored="true"/>
<uniqueKey>id</uniqueKey>
</schema>

View File

@ -22,7 +22,7 @@
<dynamicField name="*" type="string" indexed="true" stored="true"/>
<!-- for versioning -->
<field name="_version_" type="long" indexed="true" stored="true"/>
<field name="_root_" type="int" indexed="true" stored="true" multiValued="false" required="false"/>
<field name="_root_" type="string" indexed="true" stored="true" multiValued="false" required="false"/>
<field name="id" type="string" indexed="true" stored="true"/>
<uniqueKey>id</uniqueKey>
</schema>

View File

@ -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<String> 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<String> 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));
}
}
}

View File

@ -102,6 +102,21 @@ public class BadIndexSchemaTest extends AbstractBadConfigTestBase {
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");
doTest("bad-schema-sweetspot-partial-baseline.xml",

View File

@ -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}<PointRangeQuery fieldName=\"foo_"+ suffix + "\" lowerTerm=\"1\" upperTerm=\"" + badNumber + "\"/>"), 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
}
}
}

View File

@ -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"));
}
}
}
}