mirror of https://github.com/apache/lucene.git
SOLR-5010: add copy field support to the REST API
git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1500737 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
6e638c8460
commit
33d014f3aa
|
@ -157,6 +157,8 @@ New Features
|
|||
* SOLR-5003: CSV Update Handler supports optionally adding the line number/row id to
|
||||
a document (gsingers)
|
||||
|
||||
* SOLR-5010: Add support for creating copy fields to the Fields REST API (gsingers)
|
||||
|
||||
Bug Fixes
|
||||
----------------------
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.SortedSet;
|
||||
|
@ -126,6 +127,7 @@ public class FieldCollectionResource extends BaseFieldResource implements GETabl
|
|||
throw new SolrException(ErrorCode.BAD_REQUEST, message);
|
||||
} else {
|
||||
Object object = ObjectBuilder.fromJSON(entity.getText());
|
||||
Map<String, String> copyFields = new HashMap<>();
|
||||
if ( ! (object instanceof List)) {
|
||||
String message = "Invalid JSON type " + object.getClass().getName() + ", expected List of the form"
|
||||
+ " (ignore the backslashes): [{\"name\":\"foo\",\"type\":\"text_general\", ...}, {...}, ...]";
|
||||
|
@ -148,9 +150,24 @@ public class FieldCollectionResource extends BaseFieldResource implements GETabl
|
|||
log.error(message);
|
||||
throw new SolrException(ErrorCode.BAD_REQUEST, message);
|
||||
}
|
||||
// copyFields:"comma separated list of destination fields"
|
||||
String copyTo = (String)map.get(IndexSchema.COPY_FIELDS);
|
||||
if (copyTo != null){
|
||||
map.remove(IndexSchema.COPY_FIELDS);
|
||||
copyFields.put(fieldName, copyTo);
|
||||
}
|
||||
newFields.add(oldSchema.newField(fieldName, fieldType, map));
|
||||
}
|
||||
IndexSchema newSchema = oldSchema.addFields(newFields);
|
||||
for (Map.Entry<String, String> entry : copyFields.entrySet()) {
|
||||
//key is the source, value is a comma separated list of targets
|
||||
String [] splits = entry.getValue().split(",");
|
||||
if (splits != null && splits.length > 0){
|
||||
for (int i = 0; i < splits.length; i++) {
|
||||
newSchema.registerCopyField(entry.getKey(), splits[i].trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
getSolrCore().setLatestSchema(newSchema);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,6 +31,9 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
|
@ -44,9 +47,9 @@ import java.util.Map;
|
|||
* <p/>
|
||||
* The PUT method accepts field addition requests in JSON format.
|
||||
*/
|
||||
public class FieldResource extends BaseFieldResource implements GETable,PUTable {
|
||||
public class FieldResource extends BaseFieldResource implements GETable, PUTable {
|
||||
private static final Logger log = LoggerFactory.getLogger(FieldResource.class);
|
||||
|
||||
|
||||
private boolean includeDynamic;
|
||||
private String fieldName;
|
||||
|
||||
|
@ -59,7 +62,7 @@ public class FieldResource extends BaseFieldResource implements GETable,PUTable
|
|||
super.doInit();
|
||||
if (isExisting()) {
|
||||
includeDynamic = getSolrRequest().getParams().getBool(INCLUDE_DYNAMIC_PARAM, false);
|
||||
fieldName = (String)getRequestAttributes().get(IndexSchema.NAME);
|
||||
fieldName = (String) getRequestAttributes().get(IndexSchema.NAME);
|
||||
try {
|
||||
fieldName = null == fieldName ? "" : urlDecode(fieldName.trim()).trim();
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
|
@ -97,53 +100,61 @@ public class FieldResource extends BaseFieldResource implements GETable,PUTable
|
|||
}
|
||||
|
||||
/**
|
||||
* Accepts JSON add field request, to URL
|
||||
* Accepts JSON add field request, to URL
|
||||
*/
|
||||
@Override
|
||||
public Representation put(Representation entity) {
|
||||
try {
|
||||
if ( ! getSchema().isMutable()) {
|
||||
if (!getSchema().isMutable()) {
|
||||
final String message = "This IndexSchema is not mutable.";
|
||||
throw new SolrException(ErrorCode.BAD_REQUEST, message);
|
||||
} else {
|
||||
if (null == entity.getMediaType()) {
|
||||
entity.setMediaType(MediaType.APPLICATION_JSON);
|
||||
}
|
||||
if ( ! entity.getMediaType().equals(MediaType.APPLICATION_JSON, true)) {
|
||||
if (!entity.getMediaType().equals(MediaType.APPLICATION_JSON, true)) {
|
||||
String message = "Only media type " + MediaType.APPLICATION_JSON.toString() + " is accepted."
|
||||
+ " Request has media type " + entity.getMediaType().toString() + ".";
|
||||
+ " Request has media type " + entity.getMediaType().toString() + ".";
|
||||
log.error(message);
|
||||
throw new SolrException(ErrorCode.BAD_REQUEST, message);
|
||||
} else {
|
||||
Object object = ObjectBuilder.fromJSON(entity.getText());
|
||||
if ( ! (object instanceof Map)) {
|
||||
if (!(object instanceof Map)) {
|
||||
String message = "Invalid JSON type " + object.getClass().getName() + ", expected Map of the form"
|
||||
+ " (ignore the backslashes): {\"type\":\"text_general\", ...}, either with or"
|
||||
+ " without a \"name\" mapping. If the \"name\" is specified, it must match the"
|
||||
+ " name given in the request URL: /schema/fields/(name)";
|
||||
+ " (ignore the backslashes): {\"type\":\"text_general\", ...}, either with or"
|
||||
+ " without a \"name\" mapping. If the \"name\" is specified, it must match the"
|
||||
+ " name given in the request URL: /schema/fields/(name)";
|
||||
log.error(message);
|
||||
throw new SolrException(ErrorCode.BAD_REQUEST, message);
|
||||
} else {
|
||||
Map<String,Object> map = (Map<String,Object>)object;
|
||||
Map<String, Object> map = (Map<String, Object>) object;
|
||||
if (1 == map.size() && map.containsKey(IndexSchema.FIELD)) {
|
||||
map = (Map<String,Object>)map.get(IndexSchema.FIELD);
|
||||
map = (Map<String, Object>) map.get(IndexSchema.FIELD);
|
||||
}
|
||||
String bodyFieldName;
|
||||
if (null != (bodyFieldName = (String)map.remove(IndexSchema.NAME)) && ! fieldName.equals(bodyFieldName)) {
|
||||
String message = "Field name in the request body '" + bodyFieldName
|
||||
+ "' doesn't match field name in the request URL '" + fieldName + "'";
|
||||
if (null != (bodyFieldName = (String) map.remove(IndexSchema.NAME)) && !fieldName.equals(bodyFieldName)) {
|
||||
String message = "Field name in the request body '" + bodyFieldName
|
||||
+ "' doesn't match field name in the request URL '" + fieldName + "'";
|
||||
log.error(message);
|
||||
throw new SolrException(ErrorCode.BAD_REQUEST, message);
|
||||
} else {
|
||||
String fieldType;
|
||||
if (null == (fieldType = (String)map.remove(IndexSchema.TYPE))) {
|
||||
if (null == (fieldType = (String) map.remove(IndexSchema.TYPE))) {
|
||||
String message = "Missing '" + IndexSchema.TYPE + "' mapping.";
|
||||
log.error(message);
|
||||
throw new SolrException(ErrorCode.BAD_REQUEST, message);
|
||||
} else {
|
||||
ManagedIndexSchema oldSchema = (ManagedIndexSchema)getSchema();
|
||||
ManagedIndexSchema oldSchema = (ManagedIndexSchema) getSchema();
|
||||
String copyTo = (String) map.get(IndexSchema.COPY_FIELDS);
|
||||
Collection<String> copyFieldNames = Collections.emptySet();
|
||||
if (copyTo != null) {
|
||||
map.remove(IndexSchema.COPY_FIELDS);
|
||||
String [] tmp = copyTo.split(",");
|
||||
copyFieldNames = new HashSet<>(tmp.length);
|
||||
Collections.addAll(copyFieldNames, tmp);
|
||||
}
|
||||
SchemaField newField = oldSchema.newField(fieldName, fieldType, map);
|
||||
ManagedIndexSchema newSchema = oldSchema.addField(newField);
|
||||
IndexSchema newSchema = oldSchema.addField(newField, copyFieldNames);
|
||||
getSolrCore().setLatestSchema(newSchema);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1436,10 +1436,24 @@ public class IndexSchema {
|
|||
throw new SolrException(ErrorCode.SERVER_ERROR, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies this schema, adds the given field to the copy, then persists the new schema.
|
||||
*
|
||||
* @param newField the SchemaField to add
|
||||
* @param copyFieldNames 0 or more names of targets to copy this field to
|
||||
* @return a new IndexSchema based on this schema with newField added
|
||||
* @see #newField(String, String, Map)
|
||||
*/
|
||||
public IndexSchema addField(SchemaField newField, Collection<String> copyFieldNames) {
|
||||
String msg = "This IndexSchema is not mutable.";
|
||||
log.error(msg);
|
||||
throw new SolrException(ErrorCode.SERVER_ERROR, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies this schema, adds the given fields to the copy, then persists the new schema.
|
||||
*
|
||||
* @param newFields the SchemaFields to add
|
||||
* @param newFields the SchemaFields to add
|
||||
* @return a new IndexSchema based on this schema with newFields added
|
||||
* @see #newField(String, String, Map)
|
||||
*/
|
||||
|
@ -1449,6 +1463,20 @@ public class IndexSchema {
|
|||
throw new SolrException(ErrorCode.SERVER_ERROR, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies this schema, adds the given fields to the copy, then persists the new schema.
|
||||
*
|
||||
* @param newFields the SchemaFields to add
|
||||
* @param copyFieldNames 0 or more names of targets to copy this field to
|
||||
* @return a new IndexSchema based on this schema with newFields added
|
||||
* @see #newField(String, String, Map)
|
||||
*/
|
||||
public IndexSchema addFields(Collection<SchemaField> newFields, Map<String, Collection<String>> copyFieldNames) {
|
||||
String msg = "This IndexSchema is not mutable.";
|
||||
log.error(msg);
|
||||
throw new SolrException(ErrorCode.SERVER_ERROR, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a SchemaField if the given fieldName does not already
|
||||
* exist in this schema, and does not match any dynamic fields
|
||||
|
|
|
@ -40,6 +40,7 @@ import java.io.OutputStreamWriter;
|
|||
import java.io.StringWriter;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
/** Solr-managed schema - non-user-editable, but can be mutable via internal and external REST API requests. */
|
||||
|
@ -165,27 +166,40 @@ public final class ManagedIndexSchema extends IndexSchema {
|
|||
}
|
||||
|
||||
@Override
|
||||
public ManagedIndexSchema addField(SchemaField newField) {
|
||||
public IndexSchema addField(SchemaField newField) {
|
||||
return addFields(Arrays.asList(newField));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexSchema addField(SchemaField newField, Collection<String> copyFieldNames) {
|
||||
return addFields(Arrays.asList(newField), Collections.singletonMap(newField.getName(), copyFieldNames));
|
||||
}
|
||||
|
||||
public class FieldExistsException extends SolrException {
|
||||
public FieldExistsException(ErrorCode code, String msg) {
|
||||
super(code, msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ManagedIndexSchema addFields(Collection<SchemaField> newFields) {
|
||||
public IndexSchema addFields(Collection<SchemaField> newFields) {
|
||||
return addFields(newFields, Collections.<String, Collection<String>>emptyMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexSchema addFields(Collection<SchemaField> newFields, Map<String, Collection<String>> copyFieldNames) {
|
||||
ManagedIndexSchema newSchema = null;
|
||||
if (isMutable) {
|
||||
boolean success = false;
|
||||
if (copyFieldNames == null){
|
||||
copyFieldNames = Collections.emptyMap();
|
||||
}
|
||||
while ( ! success) { // optimistic concurrency
|
||||
// even though fields is volatile, we need to synchronize to avoid two addFields
|
||||
// happening concurrently (and ending up missing one of them)
|
||||
synchronized (getSchemaUpdateLock()) {
|
||||
newSchema = shallowCopy(true);
|
||||
|
||||
|
||||
for (SchemaField newField : newFields) {
|
||||
if (null != newSchema.getFieldOrNull(newField.getName())) {
|
||||
String msg = "Field '" + newField.getName() + "' already exists.";
|
||||
|
@ -201,6 +215,12 @@ public final class ManagedIndexSchema extends IndexSchema {
|
|||
log.debug("{} is required in this schema", newField.getName());
|
||||
newSchema.requiredFields.add(newField);
|
||||
}
|
||||
Collection<String> copyFields = copyFieldNames.get(newField.getName());
|
||||
if (copyFields != null) {
|
||||
for (String copyField : copyFields) {
|
||||
newSchema.registerCopyField(newField.getName(), copyField);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Run the callbacks on SchemaAware now that everything else is done
|
||||
for (SchemaAware aware : newSchema.schemaAware) {
|
||||
|
|
|
@ -102,6 +102,29 @@ public class TestManagedSchemaFieldResource extends RestTestBase {
|
|||
"/response/result[@name='response']/doc/str[@name='id'][.='123']");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddCopyField() throws Exception {
|
||||
assertQ("/schema/fields/newfield2?indent=on&wt=xml",
|
||||
"count(/response/lst[@name='field']) = 0",
|
||||
"/response/lst[@name='responseHeader']/int[@name='status'] = '404'",
|
||||
"/response/lst[@name='error']/int[@name='code'] = '404'");
|
||||
|
||||
assertJPut("/schema/fields/fieldA",
|
||||
"{\"type\":\"text\",\"stored\":\"false\"}",
|
||||
"/responseHeader/status==0");
|
||||
assertJPut("/schema/fields/fieldB",
|
||||
"{\"type\":\"text\",\"stored\":\"false\", \"copyFields\":\"fieldA\"}",
|
||||
"/responseHeader/status==0");
|
||||
|
||||
assertQ("/schema/fields/fieldB?indent=on&wt=xml",
|
||||
"count(/response/lst[@name='field']) = 1",
|
||||
"/response/lst[@name='responseHeader']/int[@name='status'] = '0'");
|
||||
assertQ("/schema/copyfields/?indent=on&wt=xml&source.fl=fieldB",
|
||||
"count(/response/arr[@name='copyFields']/lst) = 1"
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPostMultipleFields() throws Exception {
|
||||
assertQ("/schema/fields/newfield1?indent=on&wt=xml",
|
||||
|
@ -142,5 +165,35 @@ public class TestManagedSchemaFieldResource extends RestTestBase {
|
|||
"count(/response/result[@name='response']/doc/*) = 1",
|
||||
"/response/result[@name='response']/doc/str[@name='id'][.='456']");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPostCopy() throws Exception {
|
||||
assertJPost("/schema/fields",
|
||||
"[{\"name\":\"fieldA\",\"type\":\"text\",\"stored\":\"false\"},"
|
||||
+ "{\"name\":\"fieldB\",\"type\":\"text\",\"stored\":\"false\"},"
|
||||
+ " {\"name\":\"fieldC\",\"type\":\"text\",\"stored\":\"false\", \"copyFields\":\"fieldB\"}]",
|
||||
"/responseHeader/status==0");
|
||||
assertQ("/schema/copyfields/?indent=on&wt=xml&source.fl=fieldC",
|
||||
"count(/response/arr[@name='copyFields']/lst) = 1"
|
||||
);
|
||||
assertJPost("/schema/fields",
|
||||
"[{\"name\":\"fieldD\",\"type\":\"text\",\"stored\":\"false\"},"
|
||||
+ "{\"name\":\"fieldE\",\"type\":\"text\",\"stored\":\"false\"},"
|
||||
+ " {\"name\":\"fieldF\",\"type\":\"text\",\"stored\":\"false\", \"copyFields\":\"fieldD,fieldE\"}]",
|
||||
"/responseHeader/status==0");
|
||||
assertQ("/schema/copyfields/?indent=on&wt=xml&source.fl=fieldF",
|
||||
"count(/response/arr[@name='copyFields']/lst) = 2"
|
||||
);
|
||||
assertJPost("/schema/fields",
|
||||
"[{\"name\":\"fieldG\",\"type\":\"text\",\"stored\":\"false\"},"
|
||||
+ "{\"name\":\"fieldH\",\"type\":\"text\",\"stored\":\"false\"},"
|
||||
+ " {\"name\":\"fieldI\",\"type\":\"text\",\"stored\":\"false\", \"copyFields\":\"fieldG, fieldH \"}]",
|
||||
"/responseHeader/status==0");
|
||||
assertQ("/schema/copyfields/?indent=on&wt=xml&source.fl=fieldF",
|
||||
"count(/response/arr[@name='copyFields']/lst) = 2"
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue