diff --git a/solr/core/src/java/org/apache/solr/handler/SchemaHandler.java b/solr/core/src/java/org/apache/solr/handler/SchemaHandler.java index da24c254def..4da557d85ef 100644 --- a/solr/core/src/java/org/apache/solr/handler/SchemaHandler.java +++ b/solr/core/src/java/org/apache/solr/handler/SchemaHandler.java @@ -185,7 +185,7 @@ public class SchemaHandler extends RequestHandlerBase implements SolrCoreAware, if (parts.get(0).isEmpty()) parts.remove(0); if (parts.size() > 1 && level2.containsKey(parts.get(1))) { String realName = level2.get(parts.get(1)); - SimpleOrderedMap propertyValues = req.getSchema().getNamedPropertyValues(req.getParams()); + Map propertyValues = req.getSchema().getNamedPropertyValues(realName, req.getParams()); Object o = propertyValues.get(realName); if(parts.size()> 2) { String name = parts.get(2); diff --git a/solr/core/src/java/org/apache/solr/response/SchemaXmlWriter.java b/solr/core/src/java/org/apache/solr/response/SchemaXmlWriter.java index 4ed4d8f06d3..a38fe364eaf 100644 --- a/solr/core/src/java/org/apache/solr/response/SchemaXmlWriter.java +++ b/solr/core/src/java/org/apache/solr/response/SchemaXmlWriter.java @@ -78,8 +78,8 @@ public class SchemaXmlWriter extends TextResponseWriter { writer.write(MANAGED_SCHEMA_DO_NOT_EDIT_WARNING); } - @SuppressWarnings("unchecked") SimpleOrderedMap schemaProperties - = (SimpleOrderedMap)rsp.getValues().get(IndexSchema.SCHEMA); + @SuppressWarnings("unchecked") Map schemaProperties + = (Map)rsp.getValues().get(IndexSchema.SCHEMA); openStartTag(IndexSchema.SCHEMA); writeAttr(IndexSchema.NAME, schemaProperties.get(IndexSchema.NAME).toString()); @@ -87,34 +87,33 @@ public class SchemaXmlWriter extends TextResponseWriter { closeStartTag(false); incLevel(); - for (int schemaPropNum = 0 ; schemaPropNum < schemaProperties.size() ; ++schemaPropNum) { - String schemaPropName = schemaProperties.getName(schemaPropNum); + for (Map.Entry entry : schemaProperties.entrySet()) { + String schemaPropName = entry.getKey(); + Object val = entry.getValue(); if (schemaPropName.equals(IndexSchema.NAME) || schemaPropName.equals(IndexSchema.VERSION)) { continue; } if (schemaPropName.equals(IndexSchema.UNIQUE_KEY)) { openStartTag(IndexSchema.UNIQUE_KEY); closeStartTag(false); - writer.write(schemaProperties.getVal(schemaPropNum).toString()); + writer.write(val.toString()); endTag(IndexSchema.UNIQUE_KEY, false); } else if (schemaPropName.equals(IndexSchema.DEFAULT_SEARCH_FIELD)) { openStartTag(IndexSchema.DEFAULT_SEARCH_FIELD); closeStartTag(false); - writer.write(schemaProperties.getVal(schemaPropNum).toString()); + writer.write(val.toString()); endTag(IndexSchema.DEFAULT_SEARCH_FIELD, false); } else if (schemaPropName.equals(IndexSchema.SOLR_QUERY_PARSER)) { openStartTag(IndexSchema.SOLR_QUERY_PARSER); - @SuppressWarnings("unchecked") SimpleOrderedMap solrQueryParserProperties - = (SimpleOrderedMap)schemaProperties.getVal(schemaPropNum); - writeAttr(IndexSchema.DEFAULT_OPERATOR, solrQueryParserProperties.get(IndexSchema.DEFAULT_OPERATOR).toString()); + writeAttr(IndexSchema.DEFAULT_OPERATOR, ((Map) val).get(IndexSchema.DEFAULT_OPERATOR).toString()); closeStartTag(true); } else if (schemaPropName.equals(IndexSchema.SIMILARITY)) { - writeSimilarity((SimpleOrderedMap) schemaProperties.getVal(schemaPropNum)); + writeSimilarity((SimpleOrderedMap) val); } else if (schemaPropName.equals(IndexSchema.FIELD_TYPES)) { - writeFieldTypes((List>)schemaProperties.getVal(schemaPropNum)); + writeFieldTypes((List>) val); } else if (schemaPropName.equals(IndexSchema.FIELDS)) { @SuppressWarnings("unchecked") List> fieldPropertiesList - = (List>)schemaProperties.getVal(schemaPropNum); + = (List>) val; for (SimpleOrderedMap fieldProperties : fieldPropertiesList) { openStartTag(IndexSchema.FIELD); for (int fieldPropNum = 0 ; fieldPropNum < fieldProperties.size() ; ++fieldPropNum) { @@ -124,7 +123,7 @@ public class SchemaXmlWriter extends TextResponseWriter { } } else if (schemaPropName.equals(IndexSchema.DYNAMIC_FIELDS)) { @SuppressWarnings("unchecked") List> dynamicFieldPropertiesList - = (List>)schemaProperties.getVal(schemaPropNum); + = (List>) val; for (SimpleOrderedMap dynamicFieldProperties : dynamicFieldPropertiesList) { openStartTag(IndexSchema.DYNAMIC_FIELD); for (int dynamicFieldPropNum = 0 ; dynamicFieldPropNum < dynamicFieldProperties.size() ; ++dynamicFieldPropNum) { @@ -135,7 +134,7 @@ public class SchemaXmlWriter extends TextResponseWriter { } } else if (schemaPropName.equals(IndexSchema.COPY_FIELDS)) { @SuppressWarnings("unchecked") List> copyFieldPropertiesList - = (List>)schemaProperties.getVal(schemaPropNum); + = (List>) val; for (SimpleOrderedMap copyFieldProperties : copyFieldPropertiesList) { openStartTag(IndexSchema.COPY_FIELD); for (int copyFieldPropNum = 0 ; copyFieldPropNum < copyFieldProperties.size() ; ++ copyFieldPropNum) { 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 7f7bd73b0ea..f71db0aafd2 100644 --- a/solr/core/src/java/org/apache/solr/schema/IndexSchema.java +++ b/solr/core/src/java/org/apache/solr/schema/IndexSchema.java @@ -20,11 +20,15 @@ import java.io.IOException; import java.io.Writer; import java.lang.invoke.MethodHandles; import java.util.*; +import java.util.function.Function; import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; +import com.google.common.base.Functions; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.analysis.DelegatingAnalyzerWrapper; import org.apache.lucene.index.DocValuesType; @@ -32,19 +36,21 @@ import org.apache.lucene.index.FieldInfo; import org.apache.lucene.index.IndexOptions; import org.apache.lucene.index.IndexReader; import org.apache.lucene.index.IndexableField; -import org.apache.lucene.index.IndexableField; import org.apache.lucene.index.MultiFields; import org.apache.lucene.search.similarities.Similarity; import org.apache.lucene.uninverting.UninvertingReader; import org.apache.lucene.util.Version; import org.apache.solr.common.SolrException.ErrorCode; import org.apache.solr.common.SolrException; +import org.apache.solr.common.params.CommonParams; import org.apache.solr.common.params.MapSolrParams; import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.params.SolrParams; import org.apache.solr.common.util.NamedList; +import org.apache.solr.common.util.Pair; import org.apache.solr.common.util.SimpleOrderedMap; import org.apache.solr.core.Config; +import org.apache.solr.core.MapSerializable; import org.apache.solr.core.SolrConfig; import org.apache.solr.core.SolrResourceLoader; import org.apache.solr.request.LocalSolrQueryRequest; @@ -63,6 +69,7 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; +import static java.util.Arrays.asList; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; @@ -780,7 +787,7 @@ public class IndexSchema { * @param fields The sequence of {@link org.apache.solr.schema.SchemaField} */ public void registerDynamicFields(SchemaField... fields) { - List dynFields = new ArrayList<>(Arrays.asList(dynamicFields)); + List dynFields = new ArrayList<>(asList(dynamicFields)); for (SchemaField field : fields) { if (isDuplicateDynField(dynFields, field)) { log.debug("dynamic field already exists: dynamic field: [" + field.getName() + "]"); @@ -1352,58 +1359,138 @@ public class IndexSchema { /** * Get a map of property name -> value for the whole schema. */ - public SimpleOrderedMap getNamedPropertyValues() { - return getNamedPropertyValues(new MapSolrParams(Collections.EMPTY_MAP)); - - } - public SimpleOrderedMap getNamedPropertyValues(SolrParams params) { - SimpleOrderedMap topLevel = new SimpleOrderedMap<>(); - topLevel.add(NAME, getSchemaName()); - topLevel.add(VERSION, getVersion()); - if (null != uniqueKeyFieldName) { - topLevel.add(UNIQUE_KEY, uniqueKeyFieldName); - } - if (null != defaultSearchFieldName) { - topLevel.add(DEFAULT_SEARCH_FIELD, defaultSearchFieldName); - } - if (isExplicitQueryParserDefaultOperator) { - SimpleOrderedMap solrQueryParserProperties = new SimpleOrderedMap<>(); - solrQueryParserProperties.add(DEFAULT_OPERATOR, queryParserDefaultOperator); - topLevel.add(SOLR_QUERY_PARSER, solrQueryParserProperties); - } - if (isExplicitSimilarity) { - topLevel.add(SIMILARITY, similarityFactory.getNamedPropertyValues()); - } - List> fieldTypeProperties = new ArrayList<>(); - SortedMap sortedFieldTypes = new TreeMap<>(fieldTypes); - for (FieldType fieldType : sortedFieldTypes.values()) { - fieldTypeProperties.add(fieldType.getNamedPropertyValues(params.getBool("showDefaults", false))); - } - topLevel.add(FIELD_TYPES, fieldTypeProperties); - List> fieldProperties = new ArrayList<>(); - SortedSet fieldNames = new TreeSet<>(fields.keySet()); - for (String fieldName : fieldNames) { - fieldProperties.add(fields.get(fieldName).getNamedPropertyValues(params.getBool("showDefaults", false))); - } - if (params.getBool("includeDynamic", false)) { - fieldProperties.addAll(getDynamicFields(params)); - } - topLevel.add(FIELDS, fieldProperties); - topLevel.add(DYNAMIC_FIELDS, getDynamicFields(params)); - topLevel.add(COPY_FIELDS, getCopyFieldProperties(false, null, null)); - return topLevel; + public Map getNamedPropertyValues() { + return getNamedPropertyValues(null, new MapSolrParams(Collections.EMPTY_MAP)); } - private List> getDynamicFields(SolrParams params) { - List> dynamicFieldProperties = new ArrayList<>(); - for (DynamicField dynamicField : dynamicFields) { - if ( ! dynamicField.getRegex().startsWith(INTERNAL_POLY_FIELD_PREFIX)) { // omit internal polyfields - dynamicFieldProperties.add(dynamicField.getPrototype().getNamedPropertyValues(params.getBool("showDefaults", false))); + static class SchemaProps implements MapSerializable { + private static final String SOURCE_FIELD_LIST = IndexSchema.SOURCE + "." + CommonParams.FL; + private static final String DESTINATION_FIELD_LIST = IndexSchema.DESTINATION + "." + CommonParams.FL; + private final String name; + private final SolrParams params; + private final IndexSchema schema; + boolean showDefaults, includeDynamic; + Set requestedFields; + private Set requestedSourceFields; + private Set requestedDestinationFields; + + + enum Handler { + NAME(IndexSchema.NAME, sp -> sp.schema.getSchemaName()), + VERSION(IndexSchema.VERSION, sp -> sp.schema.getVersion()), + UNIQUE_KEY(IndexSchema.UNIQUE_KEY, sp -> sp.schema.uniqueKeyFieldName), + DEFAULT_SEARCH_FIELD(IndexSchema.DEFAULT_SEARCH_FIELD, sp -> sp.schema.defaultSearchFieldName), + SOLR_QUERY_PARSER(IndexSchema.SOLR_QUERY_PARSER, sp -> sp.schema.isExplicitQueryParserDefaultOperator ? + singletonMap(DEFAULT_OPERATOR, sp.schema.queryParserDefaultOperator) : + null), + SIMILARITY(IndexSchema.SIMILARITY, sp -> sp.schema.isExplicitSimilarity ? + sp.schema.similarityFactory.getNamedPropertyValues() : + null), + FIELD_TYPES(IndexSchema.FIELD_TYPES, sp -> new TreeMap<>(sp.schema.fieldTypes) + .values().stream() + .map(it -> it.getNamedPropertyValues(sp.showDefaults)) + .collect(Collectors.toList())), + + DYNAMIC_FIELDS(IndexSchema.DYNAMIC_FIELDS, sp -> Stream.of(sp.schema.dynamicFields) + .filter(it -> !it.getRegex().startsWith(INTERNAL_POLY_FIELD_PREFIX)) + .filter(it -> sp.requestedFields == null || sp.requestedFields.contains(it.getPrototype().getName())) + .map(it -> sp.getProperties(it.getPrototype())) + .collect(Collectors.toList())), + FIELDS(IndexSchema.FIELDS, sp -> { + List result = (sp.requestedFields != null ? sp.requestedFields : new TreeSet<>(sp.schema.fields.keySet())) + .stream() + .map(sp.schema::getFieldOrNull) + .filter(it -> it != null) + .filter(it -> sp.includeDynamic || !sp.schema.isDynamicField(it.name)) + .map(sp::getProperties) + .collect(Collectors.toList()); + if (sp.includeDynamic && sp.requestedFields == null) { + result.addAll((Collection) Handler.DYNAMIC_FIELDS.fun.apply(sp)); + } + return result; + }), + + + COPY_FIELDS(IndexSchema.COPY_FIELDS, sp -> sp.schema.getCopyFieldProperties(false, + sp.requestedSourceFields, sp.requestedDestinationFields)); + + final Function fun; + final String name; + Handler(String name, Function fun) { + this.fun = fun; + this.name = name; } } - return dynamicFieldProperties; + + SchemaProps(String name, SolrParams params, IndexSchema schema) { + this.name = name; + this.params = params; + this.schema = schema; + showDefaults = params.getBool("showDefaults", false); + includeDynamic = params.getBool("includeDynamic", false); + + String sourceFieldListParam = params.get(SOURCE_FIELD_LIST); + if (null != sourceFieldListParam) { + String[] fields = sourceFieldListParam.trim().split("[,\\s]+"); + if (fields.length > 0) { + requestedSourceFields = new HashSet<>(Arrays.asList(fields)); + requestedSourceFields.remove(""); // Remove empty values, if any + } + } + String destinationFieldListParam = params.get(DESTINATION_FIELD_LIST); + if (null != destinationFieldListParam) { + String[] fields = destinationFieldListParam.trim().split("[,\\s]+"); + if (fields.length > 0) { + requestedDestinationFields = new HashSet<>(Arrays.asList(fields)); + requestedDestinationFields.remove(""); // Remove empty values, if any + } + } + + String flParam = params.get(CommonParams.FL); + if (null != flParam) { + String[] fields = flParam.trim().split("[,\\s]+"); + if (fields.length > 0) + requestedFields = new LinkedHashSet<>(Stream.of(fields) + .filter(it -> !it.trim().isEmpty()) + .collect(Collectors.toList())); + + } + + } + + + SimpleOrderedMap getProperties(SchemaField sf) { + SimpleOrderedMap result = sf.getNamedPropertyValues(showDefaults); + if (schema.isDynamicField(sf.name)) { + String dynamicBase = schema.getDynamicPattern(sf.getName()); + // Add dynamicBase property if it's different from the field name. + if (!sf.getName().equals(dynamicBase)) { + result.add("dynamicBase", dynamicBase); + } + } + return result; + } + + + @Override + public Map toMap() { + Map topLevel = new LinkedHashMap<>(); + Stream.of(Handler.values()) + .filter(it -> name == null || it.name.equals(name)) + .forEach(it -> { + Object val = it.fun.apply(this); + if (val != null) topLevel.put(it.name, val); + }); + return topLevel; + } } + public Map getNamedPropertyValues(String name, SolrParams params) { + return new SchemaProps(name, params, this).toMap(); + + } + + /** * Returns a list of copyField directives, with optional details and optionally restricting to those * directives that contain the requested source and/or destination field names. diff --git a/solr/core/src/test/org/apache/solr/rest/schema/TestCopyFieldCollectionResource.java b/solr/core/src/test/org/apache/solr/rest/schema/TestCopyFieldCollectionResource.java index c0f936d929e..7b39ab38685 100644 --- a/solr/core/src/test/org/apache/solr/rest/schema/TestCopyFieldCollectionResource.java +++ b/solr/core/src/test/org/apache/solr/rest/schema/TestCopyFieldCollectionResource.java @@ -100,4 +100,38 @@ public class TestCopyFieldCollectionResource extends SolrRestletTestBase { } + @Test + public void testRestrictSource() throws Exception { + assertQ("/schema/copyfields/?indent=on&wt=xml&source.fl=title,*_i,*_src_sub_i,src_sub_no_ast_i", + "count(/response/arr[@name='copyFields']/lst) = 16", // 4 + 4 + 4 + 4 + "count(/response/arr[@name='copyFields']/lst/str[@name='source'][.='title']) = 4", + "count(/response/arr[@name='copyFields']/lst/str[@name='source'][.='*_i']) = 4", + "count(/response/arr[@name='copyFields']/lst/str[@name='source'][.='*_src_sub_i']) = 4", + "count(/response/arr[@name='copyFields']/lst/str[@name='source'][.='src_sub_no_ast_i']) = 4"); + } + + @Test + public void testRestrictDest() throws Exception { + assertQ("/schema/copyfields/?indent=on&wt=xml&dest.fl=title,*_s,*_dest_sub_s,dest_sub_no_ast_s", + "count(/response/arr[@name='copyFields']/lst) = 16", // 3 + 4 + 4 + 5 + "count(/response/arr[@name='copyFields']/lst/str[@name='dest'][.='title']) = 3", + "count(/response/arr[@name='copyFields']/lst/str[@name='dest'][.='*_s']) = 4", + "count(/response/arr[@name='copyFields']/lst/str[@name='dest'][.='*_dest_sub_s']) = 4", + "count(/response/arr[@name='copyFields']/lst/str[@name='dest'][.='dest_sub_no_ast_s']) = 5"); + } + + @Test + public void testRestrictSourceAndDest() throws Exception { + assertQ("/schema/copyfields/?indent=on&wt=xml&source.fl=title,*_i&dest.fl=title,dest_sub_no_ast_s", + "count(/response/arr[@name='copyFields']/lst) = 3", + + "/response/arr[@name='copyFields']/lst[ str[@name='source'][.='title']" + + " and str[@name='dest'][.='dest_sub_no_ast_s']]", + + "/response/arr[@name='copyFields']/lst[ str[@name='source'][.='*_i']" + + " and str[@name='dest'][.='title']]", + + "/response/arr[@name='copyFields']/lst[ str[@name='source'][.='*_i']" + + " and str[@name='dest'][.='dest_sub_no_ast_s']]"); + } } diff --git a/solr/core/src/test/org/apache/solr/rest/schema/TestDynamicFieldCollectionResource.java b/solr/core/src/test/org/apache/solr/rest/schema/TestDynamicFieldCollectionResource.java index 032bbad08cc..6734ca2fb69 100644 --- a/solr/core/src/test/org/apache/solr/rest/schema/TestDynamicFieldCollectionResource.java +++ b/solr/core/src/test/org/apache/solr/rest/schema/TestDynamicFieldCollectionResource.java @@ -29,6 +29,21 @@ public class TestDynamicFieldCollectionResource extends SolrRestletTestBase { "(/response/arr[@name='dynamicFields']/lst/str[@name='name'])[3] = '*_mfacet'"); } + @Test + public void testGetTwoDynamicFields() throws IOException { + assertQ("/schema/dynamicfields?indent=on&wt=xml&fl=*_i,*_s", + "count(/response/arr[@name='dynamicFields']/lst/str[@name='name']) = 2", + "(/response/arr[@name='dynamicFields']/lst/str[@name='name'])[1] = '*_i'", + "(/response/arr[@name='dynamicFields']/lst/str[@name='name'])[2] = '*_s'"); + } + + @Test + public void testNotFoundDynamicFields() throws IOException { + assertQ("/schema/dynamicfields?indent=on&wt=xml&fl=*_not_in_there,this_one_isnt_either_*", + "count(/response/arr[@name='dynamicFields']) = 1", + "count(/response/arr[@name='dynamicfields']/lst/str[@name='name']) = 0"); + } + @Test public void testJsonGetAllDynamicFields() throws Exception { assertJQ("/schema/dynamicfields?indent=on", @@ -36,4 +51,13 @@ public class TestDynamicFieldCollectionResource extends SolrRestletTestBase { "/dynamicFields/[1]/name=='ignored_*'", "/dynamicFields/[2]/name=='*_mfacet'"); } + + @Test + public void testJsonGetTwoDynamicFields() throws Exception { + assertJQ("/schema/dynamicfields?indent=on&fl=*_i,*_s&wt=xml", // assertJQ will fix the wt param to be json + "/dynamicFields/[0]/name=='*_i'", + "/dynamicFields/[1]/name=='*_s'"); + } + + } diff --git a/solr/core/src/test/org/apache/solr/rest/schema/TestFieldCollectionResource.java b/solr/core/src/test/org/apache/solr/rest/schema/TestFieldCollectionResource.java index 6fdd29892bb..f3de92bee55 100644 --- a/solr/core/src/test/org/apache/solr/rest/schema/TestFieldCollectionResource.java +++ b/solr/core/src/test/org/apache/solr/rest/schema/TestFieldCollectionResource.java @@ -15,6 +15,8 @@ * limitations under the License. */ package org.apache.solr.rest.schema; +import java.io.IOException; + import org.apache.solr.rest.SolrRestletTestBase; import org.junit.Test; @@ -37,6 +39,37 @@ public class TestFieldCollectionResource extends SolrRestletTestBase { "/fields/[2]/name=='_version_'"); } + @Test + public void testGetThreeFieldsDontIncludeDynamic() throws IOException { + // + assertQ("/schema/fields?indent=on&wt=xml&fl=id,_version_,price_i", + "count(/response/arr[@name='fields']/lst/str[@name='name']) = 2", + "(/response/arr[@name='fields']/lst/str[@name='name'])[1] = 'id'", + "(/response/arr[@name='fields']/lst/str[@name='name'])[2] = '_version_'"); + } + + @Test + public void testGetThreeFieldsIncludeDynamic() throws IOException { + assertQ("/schema/fields?indent=on&wt=xml&fl=id,_version_,price_i&includeDynamic=on", + + "count(/response/arr[@name='fields']/lst/str[@name='name']) = 3", + + "(/response/arr[@name='fields']/lst/str[@name='name'])[1] = 'id'", + + "(/response/arr[@name='fields']/lst/str[@name='name'])[2] = '_version_'", + + "(/response/arr[@name='fields']/lst/str[@name='name'])[3] = 'price_i'", + + "/response/arr[@name='fields']/lst[ str[@name='name']='price_i' " + +" and str[@name='dynamicBase']='*_i']"); + } + @Test + public void testNotFoundFields() throws IOException { + assertQ("/schema/fields?indent=on&wt=xml&fl=not_in_there,this_one_either", + "count(/response/arr[@name='fields']) = 1", + "count(/response/arr[@name='fields']/lst/str[@name='name']) = 0"); + } + @Test public void testJsonGetAllFieldsIncludeDynamic() throws Exception {