mirror of https://github.com/apache/lucene.git
SOLR-6476
git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1628734 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
e3329e08a6
commit
041ea20853
|
@ -145,6 +145,8 @@ New Features
|
|||
|
||||
* SOLR-6565: SolrRequest support for query params (Gregory Chanan)
|
||||
|
||||
* SOLR-6476: Create a bulk mode for schema API (Noble Paul, Steve Rowe)
|
||||
|
||||
Bug Fixes
|
||||
----------------------
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ abstract class BaseFieldTypeResource extends BaseSolrResource {
|
|||
while (!success) {
|
||||
try {
|
||||
synchronized (oldSchema.getSchemaUpdateLock()) {
|
||||
newSchema = oldSchema.addFieldTypes(newFieldTypes);
|
||||
newSchema = oldSchema.addFieldTypes(newFieldTypes, true);
|
||||
getSolrCore().setLatestSchema(newSchema);
|
||||
success = true;
|
||||
}
|
||||
|
|
|
@ -173,7 +173,7 @@ public class CopyFieldCollectionResource extends BaseFieldResource implements GE
|
|||
while (!success) {
|
||||
try {
|
||||
synchronized (oldSchema.getSchemaUpdateLock()) {
|
||||
newSchema = oldSchema.addCopyFields(fieldsToCopy);
|
||||
newSchema = oldSchema.addCopyFields(fieldsToCopy,true);
|
||||
if (null != newSchema) {
|
||||
getSolrCore().setLatestSchema(newSchema);
|
||||
success = true;
|
||||
|
|
|
@ -178,7 +178,7 @@ public class DynamicFieldCollectionResource extends BaseFieldResource implements
|
|||
}
|
||||
firstAttempt = false;
|
||||
synchronized (oldSchema.getSchemaUpdateLock()) {
|
||||
newSchema = oldSchema.addDynamicFields(newDynamicFields, copyFields);
|
||||
newSchema = oldSchema.addDynamicFields(newDynamicFields, copyFields, true);
|
||||
if (null != newSchema) {
|
||||
getSolrCore().setLatestSchema(newSchema);
|
||||
success = true;
|
||||
|
|
|
@ -32,9 +32,14 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
|
||||
/**
|
||||
* This class responds to requests at /solr/(corename)/schema/dynamicfields/(pattern)
|
||||
|
@ -142,12 +147,12 @@ public class DynamicFieldResource extends BaseFieldResource implements GETable,
|
|||
} else {
|
||||
ManagedIndexSchema oldSchema = (ManagedIndexSchema)getSchema();
|
||||
Object copies = map.get(IndexSchema.COPY_FIELDS);
|
||||
List<String> copyFieldNames = null;
|
||||
Collection<String> copyFieldNames = null;
|
||||
if (copies != null) {
|
||||
if (copies instanceof List) {
|
||||
copyFieldNames = (List<String>)copies;
|
||||
} else if (copies instanceof String) {
|
||||
copyFieldNames = Collections.singletonList(copies.toString());
|
||||
copyFieldNames = singletonList(copies.toString());
|
||||
} else {
|
||||
String message = "Invalid '" + IndexSchema.COPY_FIELDS + "' type.";
|
||||
log.error(message);
|
||||
|
@ -163,7 +168,7 @@ public class DynamicFieldResource extends BaseFieldResource implements GETable,
|
|||
try {
|
||||
SchemaField newDynamicField = oldSchema.newDynamicField(fieldNamePattern, fieldType, map);
|
||||
synchronized (oldSchema.getSchemaUpdateLock()) {
|
||||
newSchema = oldSchema.addDynamicField(newDynamicField, copyFieldNames);
|
||||
newSchema = oldSchema.addDynamicFields(singletonList(newDynamicField), singletonMap(newDynamicField.getName(), copyFieldNames), true);
|
||||
if (null != newSchema) {
|
||||
getSolrCore().setLatestSchema(newSchema);
|
||||
success = true;
|
||||
|
|
|
@ -199,7 +199,7 @@ public class FieldCollectionResource extends BaseFieldResource implements GETabl
|
|||
}
|
||||
firstAttempt = false;
|
||||
synchronized (oldSchema.getSchemaUpdateLock()) {
|
||||
newSchema = oldSchema.addFields(newFields, copyFields);
|
||||
newSchema = oldSchema.addFields(newFields, copyFields, true);
|
||||
if (null != newSchema) {
|
||||
getSolrCore().setLatestSchema(newSchema);
|
||||
success = true;
|
||||
|
|
|
@ -16,18 +16,26 @@ package org.apache.solr.rest.schema;
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import org.apache.solr.request.SolrRequestInfo;
|
||||
import org.apache.solr.rest.BaseSolrResource;
|
||||
import org.apache.solr.rest.GETable;
|
||||
import org.apache.solr.rest.POSTable;
|
||||
import org.apache.solr.schema.IndexSchema;
|
||||
import org.apache.solr.schema.SchemaManager;
|
||||
import org.restlet.representation.Representation;
|
||||
import org.restlet.resource.ResourceException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class responds to requests at /solr/(corename)/schema
|
||||
*/
|
||||
public class SchemaResource extends BaseSolrResource implements GETable {
|
||||
public class SchemaResource extends BaseSolrResource implements GETable,POSTable {
|
||||
private static final Logger log = LoggerFactory.getLogger(SchemaResource.class);
|
||||
|
||||
public SchemaResource() {
|
||||
|
@ -50,4 +58,25 @@ public class SchemaResource extends BaseSolrResource implements GETable {
|
|||
|
||||
return new SolrOutputRepresentation();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Representation post(Representation representation) {
|
||||
SolrRequestInfo requestInfo = SolrRequestInfo.getRequestInfo();
|
||||
List<String> errs = null;
|
||||
try {
|
||||
String text = representation.getText();
|
||||
errs = new SchemaManager(requestInfo.getReq()).performOperations(new StringReader(text));
|
||||
} catch (IOException e) {
|
||||
requestInfo.getRsp().add("errors", Collections.singletonList("Error reading input String " + e.getMessage()));
|
||||
requestInfo.getRsp().setException(e);
|
||||
}
|
||||
if(!errs.isEmpty()){
|
||||
requestInfo.getRsp().add("errors", errs);
|
||||
}
|
||||
|
||||
|
||||
return new BaseSolrResource.SolrOutputRepresentation();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -74,6 +74,9 @@ import java.util.TreeMap;
|
|||
import java.util.TreeSet;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
|
||||
/**
|
||||
* <code>IndexSchema</code> contains information about the valid fields in an index
|
||||
* and the types of those fields.
|
||||
|
@ -1473,23 +1476,26 @@ public class IndexSchema {
|
|||
}
|
||||
|
||||
/**
|
||||
* Copies this schema, adds the given field to the copy, then persists the
|
||||
* new schema. Requires synchronizing on the object returned by
|
||||
* Copies this schema, adds the given field to the copy
|
||||
* Requires synchronizing on the object returned by
|
||||
* {@link #getSchemaUpdateLock()}.
|
||||
*
|
||||
* @param newField the SchemaField to add
|
||||
* @param persist to persist the schema or not or not
|
||||
* @return a new IndexSchema based on this schema with newField added
|
||||
* @see #newField(String, String, Map)
|
||||
*/
|
||||
public IndexSchema addField(SchemaField newField, boolean persist) {
|
||||
return addFields(Collections.singletonList(newField),Collections.EMPTY_MAP,persist );
|
||||
}
|
||||
|
||||
public IndexSchema addField(SchemaField newField) {
|
||||
String msg = "This IndexSchema is not mutable.";
|
||||
log.error(msg);
|
||||
throw new SolrException(ErrorCode.SERVER_ERROR, msg);
|
||||
return addField(newField, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies this schema, adds the given field to the copy, then persists the
|
||||
* new schema. Requires synchronizing on the object returned by
|
||||
* Copies this schema, adds the given field to the copy
|
||||
* Requires synchronizing on the object returned by
|
||||
* {@link #getSchemaUpdateLock()}.
|
||||
*
|
||||
* @param newField the SchemaField to add
|
||||
|
@ -1498,14 +1504,12 @@ public class IndexSchema {
|
|||
* @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);
|
||||
return addFields(singletonList(newField), singletonMap(newField.getName(), copyFieldNames), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies this schema, adds the given fields to the copy, then persists the
|
||||
* new schema. Requires synchronizing on the object returned by
|
||||
* Copies this schema, adds the given fields to the copy.
|
||||
* Requires synchronizing on the object returned by
|
||||
* {@link #getSchemaUpdateLock()}.
|
||||
*
|
||||
* @param newFields the SchemaFields to add
|
||||
|
@ -1513,99 +1517,57 @@ public class IndexSchema {
|
|||
* @see #newField(String, String, Map)
|
||||
*/
|
||||
public IndexSchema addFields(Collection<SchemaField> newFields) {
|
||||
String msg = "This IndexSchema is not mutable.";
|
||||
log.error(msg);
|
||||
throw new SolrException(ErrorCode.SERVER_ERROR, msg);
|
||||
return addFields(newFields, Collections.<String, Collection<String>>emptyMap(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies this schema, adds the given fields to the copy, then persists the
|
||||
* new schema. Requires synchronizing on the object returned by
|
||||
* Copies this schema, adds the given fields to the copy
|
||||
* Requires synchronizing on the object returned by
|
||||
* {@link #getSchemaUpdateLock()}.
|
||||
*
|
||||
* @param newFields the SchemaFields to add
|
||||
* @param copyFieldNames 0 or more names of targets to copy this field to. The target fields must already exist.
|
||||
* @param persist Persist the schema or not
|
||||
* @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) {
|
||||
public IndexSchema addFields(Collection<SchemaField> newFields, Map<String, Collection<String>> copyFieldNames, boolean persist) {
|
||||
String msg = "This IndexSchema is not mutable.";
|
||||
log.error(msg);
|
||||
throw new SolrException(ErrorCode.SERVER_ERROR, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies this schema, adds the given dynamic field to the copy, then persists the
|
||||
* new schema. Requires synchronizing on the object returned by
|
||||
* {@link #getSchemaUpdateLock()}.
|
||||
*
|
||||
* @param newDynamicField the SchemaField to add
|
||||
* @return a new IndexSchema based on this schema with newField added
|
||||
* @see #newDynamicField(String, String, Map)
|
||||
*/
|
||||
public IndexSchema addDynamicField(SchemaField newDynamicField) {
|
||||
String msg = "This IndexSchema is not mutable.";
|
||||
log.error(msg);
|
||||
throw new SolrException(ErrorCode.SERVER_ERROR, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies this schema, adds the given dynamic field to the copy, then persists the
|
||||
* new schema. Requires synchronizing on the object returned by
|
||||
* {@link #getSchemaUpdateLock()}.
|
||||
*
|
||||
* @param newDynamicField the SchemaField to add
|
||||
* @param copyFieldNames 0 or more names of targets to copy this field to. The targets must already exist.
|
||||
* @return a new IndexSchema based on this schema with newDynamicField added
|
||||
* @see #newDynamicField(String, String, Map)
|
||||
*/
|
||||
public IndexSchema addDynamicField(SchemaField newDynamicField, 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 dynamic fields to the copy, then persists the
|
||||
* new schema. Requires synchronizing on the object returned by
|
||||
* {@link #getSchemaUpdateLock()}.
|
||||
*
|
||||
* @param newDynamicFields the SchemaFields to add
|
||||
* @return a new IndexSchema based on this schema with newDynamicFields added
|
||||
* @see #newDynamicField(String, String, Map)
|
||||
*/
|
||||
public IndexSchema addDynamicFields(Collection<SchemaField> newDynamicFields) {
|
||||
String msg = "This IndexSchema is not mutable.";
|
||||
log.error(msg);
|
||||
throw new SolrException(ErrorCode.SERVER_ERROR, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies this schema, adds the given dynamic fields to the copy, then persists the
|
||||
* new schema. Requires synchronizing on the object returned by
|
||||
* Copies this schema, adds the given dynamic fields to the copy,
|
||||
* Requires synchronizing on the object returned by
|
||||
* {@link #getSchemaUpdateLock()}.
|
||||
*
|
||||
* @param newDynamicFields the SchemaFields to add
|
||||
* @param copyFieldNames 0 or more names of targets to copy this field to. The target fields must already exist.
|
||||
* @param persist to persist the schema or not or not
|
||||
* @return a new IndexSchema based on this schema with newDynamicFields added
|
||||
* @see #newDynamicField(String, String, Map)
|
||||
*/
|
||||
public IndexSchema addDynamicFields
|
||||
(Collection<SchemaField> newDynamicFields, Map<String, Collection<String>> copyFieldNames) {
|
||||
(Collection<SchemaField> newDynamicFields,
|
||||
Map<String, Collection<String>> copyFieldNames,
|
||||
boolean persist) {
|
||||
String msg = "This IndexSchema is not mutable.";
|
||||
log.error(msg);
|
||||
throw new SolrException(ErrorCode.SERVER_ERROR, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies this schema and adds the new copy fields to the copy, then
|
||||
* persists the new schema. Requires synchronizing on the object returned by
|
||||
* Copies this schema and adds the new copy fields to the copy
|
||||
* Requires synchronizing on the object returned by
|
||||
* {@link #getSchemaUpdateLock()}.
|
||||
*
|
||||
* @param copyFields Key is the name of the source field name, value is a collection of target field names. Fields must exist.
|
||||
* @param persist to persist the schema or not or not
|
||||
* @return The new Schema with the copy fields added
|
||||
*/
|
||||
public IndexSchema addCopyFields(Map<String, Collection<String>> copyFields){
|
||||
public IndexSchema addCopyFields(Map<String, Collection<String>> copyFields, boolean persist){
|
||||
String msg = "This IndexSchema is not mutable.";
|
||||
log.error(msg);
|
||||
throw new SolrException(ErrorCode.SERVER_ERROR, msg);
|
||||
|
@ -1660,30 +1622,16 @@ public class IndexSchema {
|
|||
}
|
||||
|
||||
/**
|
||||
* Copies this schema, adds the given field type to the copy, then persists the
|
||||
* new schema. Requires synchronizing on the object returned by
|
||||
* {@link #getSchemaUpdateLock()}.
|
||||
*
|
||||
* @param fieldType the FieldType to add
|
||||
* @return a new IndexSchema based on this schema with the new FieldType added
|
||||
* @see #newFieldType(String, String, Map)
|
||||
*/
|
||||
public IndexSchema addFieldType(FieldType fieldType) {
|
||||
String msg = "This IndexSchema is not mutable.";
|
||||
log.error(msg);
|
||||
throw new SolrException(ErrorCode.SERVER_ERROR, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies this schema, adds the given field type to the copy, then persists the
|
||||
* new schema. Requires synchronizing on the object returned by
|
||||
* Copies this schema, adds the given field type to the copy,
|
||||
* Requires synchronizing on the object returned by
|
||||
* {@link #getSchemaUpdateLock()}.
|
||||
*
|
||||
* @param fieldTypeList a list of FieldTypes to add
|
||||
* @param persist to persist the schema or not or not
|
||||
* @return a new IndexSchema based on this schema with the new types added
|
||||
* @see #newFieldType(String, String, Map)
|
||||
*/
|
||||
public IndexSchema addFieldTypes(List<FieldType> fieldTypeList) {
|
||||
public IndexSchema addFieldTypes(List<FieldType> fieldTypeList, boolean persist) {
|
||||
String msg = "This IndexSchema is not mutable.";
|
||||
log.error(msg);
|
||||
throw new SolrException(ErrorCode.SERVER_ERROR, msg);
|
||||
|
@ -1692,13 +1640,13 @@ public class IndexSchema {
|
|||
/**
|
||||
* Returns a FieldType if the given typeName does not already
|
||||
* exist in this schema. The resulting FieldType can be used in a call
|
||||
* to {@link #addFieldType(FieldType)}.
|
||||
* to {@link #addFieldTypes(java.util.List, boolean)}.
|
||||
*
|
||||
* @param typeName the name of the type to add
|
||||
* @param className the name of the FieldType class
|
||||
* @param options the options to use when creating the FieldType
|
||||
* @return The created FieldType
|
||||
* @see #addFieldType(FieldType)
|
||||
* @see #addFieldTypes(java.util.List, boolean)
|
||||
*/
|
||||
public FieldType newFieldType(String typeName, String className, Map<String,?> options) {
|
||||
String msg = "This IndexSchema is not mutable.";
|
||||
|
|
|
@ -80,6 +80,9 @@ import java.util.concurrent.Executors;
|
|||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
|
||||
/** Solr-managed schema - non-user-editable, but can be mutable via internal and external REST API requests. */
|
||||
public final class ManagedIndexSchema extends IndexSchema {
|
||||
|
||||
|
@ -386,23 +389,11 @@ public final class ManagedIndexSchema extends IndexSchema {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public ManagedIndexSchema addField(SchemaField newField) {
|
||||
return addFields(Arrays.asList(newField));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ManagedIndexSchema addField(SchemaField newField, Collection<String> copyFieldNames) {
|
||||
return addFields(Arrays.asList(newField), Collections.singletonMap(newField.getName(), copyFieldNames));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ManagedIndexSchema addFields(Collection<SchemaField> newFields) {
|
||||
return addFields(newFields, Collections.<String, Collection<String>>emptyMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ManagedIndexSchema addFields(Collection<SchemaField> newFields, Map<String, Collection<String>> copyFieldNames) {
|
||||
public ManagedIndexSchema addFields(Collection<SchemaField> newFields,
|
||||
Map<String, Collection<String>> copyFieldNames,
|
||||
boolean persist) {
|
||||
ManagedIndexSchema newSchema = null;
|
||||
if (isMutable) {
|
||||
boolean success = false;
|
||||
|
@ -439,6 +430,8 @@ public final class ManagedIndexSchema extends IndexSchema {
|
|||
aware.inform(newSchema);
|
||||
}
|
||||
newSchema.refreshAnalyzers();
|
||||
|
||||
if(persist) {
|
||||
success = newSchema.persistManagedSchema(false); // don't just create - update it if it already exists
|
||||
if (success) {
|
||||
log.debug("Added field(s): {}", newFields);
|
||||
|
@ -446,6 +439,7 @@ public final class ManagedIndexSchema extends IndexSchema {
|
|||
log.error("Failed to add field(s): {}", newFields);
|
||||
newSchema = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String msg = "This ManagedIndexSchema is not mutable.";
|
||||
log.error(msg);
|
||||
|
@ -454,25 +448,10 @@ public final class ManagedIndexSchema extends IndexSchema {
|
|||
return newSchema;
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexSchema addDynamicField(SchemaField newDynamicField) {
|
||||
return addDynamicFields(Arrays.asList(newDynamicField));
|
||||
}
|
||||
|
||||
@Override
|
||||
public IndexSchema addDynamicField(SchemaField newDynamicField, Collection<String> copyFieldNames) {
|
||||
return addDynamicFields(Arrays.asList(newDynamicField),
|
||||
Collections.singletonMap(newDynamicField.getName(), copyFieldNames));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ManagedIndexSchema addDynamicFields(Collection<SchemaField> newDynamicFields) {
|
||||
return addDynamicFields(newDynamicFields, Collections.<String,Collection<String>>emptyMap());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ManagedIndexSchema addDynamicFields(Collection<SchemaField> newDynamicFields,
|
||||
Map<String,Collection<String>> copyFieldNames) {
|
||||
Map<String,Collection<String>> copyFieldNames, boolean persist) {
|
||||
ManagedIndexSchema newSchema = null;
|
||||
if (isMutable) {
|
||||
boolean success = false;
|
||||
|
@ -503,12 +482,14 @@ public final class ManagedIndexSchema extends IndexSchema {
|
|||
aware.inform(newSchema);
|
||||
}
|
||||
newSchema.refreshAnalyzers();
|
||||
if(persist) {
|
||||
success = newSchema.persistManagedSchema(false); // don't just create - update it if it already exists
|
||||
if (success) {
|
||||
log.debug("Added dynamic field(s): {}", newDynamicFields);
|
||||
} else {
|
||||
log.error("Failed to add dynamic field(s): {}", newDynamicFields);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String msg = "This ManagedIndexSchema is not mutable.";
|
||||
log.error(msg);
|
||||
|
@ -518,7 +499,7 @@ public final class ManagedIndexSchema extends IndexSchema {
|
|||
}
|
||||
|
||||
@Override
|
||||
public ManagedIndexSchema addCopyFields(Map<String, Collection<String>> copyFields) {
|
||||
public ManagedIndexSchema addCopyFields(Map<String, Collection<String>> copyFields, boolean persist) {
|
||||
ManagedIndexSchema newSchema = null;
|
||||
if (isMutable) {
|
||||
boolean success = false;
|
||||
|
@ -536,6 +517,7 @@ public final class ManagedIndexSchema extends IndexSchema {
|
|||
aware.inform(newSchema);
|
||||
}
|
||||
newSchema.refreshAnalyzers();
|
||||
if(persist) {
|
||||
success = newSchema.persistManagedSchema(false); // don't just create - update it if it already exists
|
||||
if (success) {
|
||||
log.debug("Added copy fields for {} sources", copyFields.size());
|
||||
|
@ -543,14 +525,11 @@ public final class ManagedIndexSchema extends IndexSchema {
|
|||
log.error("Failed to add copy fields for {} sources", copyFields.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
return newSchema;
|
||||
}
|
||||
|
||||
public ManagedIndexSchema addFieldType(FieldType fieldType) {
|
||||
return addFieldTypes(Collections.singletonList(fieldType));
|
||||
}
|
||||
|
||||
public ManagedIndexSchema addFieldTypes(List<FieldType> fieldTypeList) {
|
||||
public ManagedIndexSchema addFieldTypes(List<FieldType> fieldTypeList, boolean persist) {
|
||||
if (!isMutable) {
|
||||
String msg = "This ManagedIndexSchema is not mutable.";
|
||||
log.error(msg);
|
||||
|
@ -612,6 +591,7 @@ public final class ManagedIndexSchema extends IndexSchema {
|
|||
|
||||
newSchema.refreshAnalyzers();
|
||||
|
||||
if (persist) {
|
||||
boolean success = newSchema.persistManagedSchema(false);
|
||||
if (success) {
|
||||
if (log.isDebugEnabled()) {
|
||||
|
@ -628,6 +608,7 @@ public final class ManagedIndexSchema extends IndexSchema {
|
|||
throw new SolrException(ErrorCode.SERVER_ERROR,
|
||||
"Failed to persist updated schema due to underlying storage issue; check log for more details!");
|
||||
}
|
||||
}
|
||||
|
||||
return newSchema;
|
||||
}
|
||||
|
@ -834,7 +815,7 @@ public final class ManagedIndexSchema extends IndexSchema {
|
|||
* are copied; otherwise, they are not.
|
||||
* @return A shallow copy of this schema
|
||||
*/
|
||||
private ManagedIndexSchema shallowCopy(boolean includeFieldDataStructures) {
|
||||
ManagedIndexSchema shallowCopy(boolean includeFieldDataStructures) {
|
||||
ManagedIndexSchema newSchema = null;
|
||||
try {
|
||||
newSchema = new ManagedIndexSchema
|
||||
|
|
|
@ -0,0 +1,380 @@
|
|||
package org.apache.solr.schema;
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
import org.apache.solr.cloud.ZkSolrResourceLoader;
|
||||
import org.apache.solr.common.SolrException;
|
||||
import org.apache.solr.core.CoreDescriptor;
|
||||
import org.apache.solr.core.SolrCore;
|
||||
import org.apache.solr.request.SolrQueryRequest;
|
||||
import org.apache.solr.rest.BaseSolrResource;
|
||||
import org.noggit.JSONParser;
|
||||
import org.noggit.ObjectBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Collections.EMPTY_LIST;
|
||||
import static java.util.Collections.EMPTY_MAP;
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.apache.solr.common.cloud.ZkNodeProps.makeMap;
|
||||
import static org.apache.solr.schema.FieldType.CLASS_NAME;
|
||||
import static org.apache.solr.schema.IndexSchema.DESTINATION;
|
||||
import static org.apache.solr.schema.IndexSchema.NAME;
|
||||
import static org.apache.solr.schema.IndexSchema.SOURCE;
|
||||
import static org.apache.solr.schema.IndexSchema.TYPE;
|
||||
|
||||
/**A utility class to manipulate schema using the bulk mode.
|
||||
* This class takes in all the commands and process them completely. It is an all or none
|
||||
* operation
|
||||
*/
|
||||
public class SchemaManager {
|
||||
private static final Logger log = LoggerFactory.getLogger(SchemaManager.class);
|
||||
|
||||
final SolrQueryRequest req;
|
||||
ManagedIndexSchema managedIndexSchema;
|
||||
|
||||
public static final String ADD_FIELD = "add-field";
|
||||
public static final String ADD_COPY_FIELD = "add-copy-field";
|
||||
public static final String ADD_DYNAMIC_FIELD = "add-dynamic-field";
|
||||
public static final String ADD_FIELD_TYPE = "add-field-type";
|
||||
|
||||
private static final Set<String> KNOWN_OPS = new HashSet<>();
|
||||
static {
|
||||
KNOWN_OPS.add(ADD_COPY_FIELD);
|
||||
KNOWN_OPS.add(ADD_FIELD);
|
||||
KNOWN_OPS.add(ADD_DYNAMIC_FIELD);
|
||||
KNOWN_OPS.add(ADD_FIELD_TYPE);
|
||||
}
|
||||
|
||||
public SchemaManager(SolrQueryRequest req){
|
||||
this.req = req;
|
||||
|
||||
}
|
||||
|
||||
/**Take in a JSON command set and execute them . It tries to capture as many errors
|
||||
* as possible instead of failing at the frst error it encounters
|
||||
* @param rdr The input as a Reader
|
||||
* @return Lis of errors . If the List is empty then the operation is successful.
|
||||
*/
|
||||
public List performOperations(Reader rdr) {
|
||||
List<Operation> ops = null;
|
||||
try {
|
||||
ops = SchemaManager.parse(rdr);
|
||||
} catch (Exception e) {
|
||||
String msg= "Error parsing schema operations ";
|
||||
log.warn(msg ,e );
|
||||
return Collections.singletonList(singletonMap(ERR_MSGS, msg + ":" + e.getMessage()));
|
||||
}
|
||||
List errs = captureErrors(ops);
|
||||
if(!errs.isEmpty()) return errs;
|
||||
|
||||
IndexSchema schema = req.getCore().getLatestSchema();
|
||||
if (!(schema instanceof ManagedIndexSchema)) {
|
||||
return singletonList( singletonMap(ERR_MSGS,"schema is not editable"));
|
||||
}
|
||||
|
||||
synchronized (schema.getSchemaUpdateLock()) {
|
||||
return doOperations(ops);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private List<String> doOperations(List<Operation> operations){
|
||||
int timeout = req.getParams().getInt(BaseSolrResource.UPDATE_TIMEOUT_SECS, -1);
|
||||
long startTime = System.nanoTime();
|
||||
long endTime = timeout >0 ? System.nanoTime()+ (timeout * 1000*1000) : Long.MAX_VALUE;
|
||||
SolrCore core = req.getCore();
|
||||
for(;System.nanoTime() < endTime ;) {
|
||||
managedIndexSchema = (ManagedIndexSchema) core.getLatestSchema();
|
||||
for (Operation op : operations) {
|
||||
if (ADD_FIELD.equals(op.name) || ADD_DYNAMIC_FIELD.equals(op.name)) {
|
||||
applyAddField(op);
|
||||
} else if(ADD_COPY_FIELD.equals(op.name)) {
|
||||
applyAddCopyField(op);
|
||||
} else if(ADD_FIELD_TYPE.equals(op.name)) {
|
||||
applyAddType(op);
|
||||
|
||||
} else {
|
||||
op.addError("No such operation : " + op.name);
|
||||
}
|
||||
}
|
||||
List errs = captureErrors(operations);
|
||||
if (!errs.isEmpty()) return errs;
|
||||
|
||||
try {
|
||||
managedIndexSchema.persistManagedSchema(false);
|
||||
core.setLatestSchema(managedIndexSchema);
|
||||
waitForOtherReplicasToUpdate(timeout, startTime);
|
||||
return EMPTY_LIST;
|
||||
} catch (ManagedIndexSchema.SchemaChangedInZkException e) {
|
||||
String s = "Failed to update schema because schema is modified";
|
||||
log.warn(s, e);
|
||||
continue;
|
||||
} catch (Exception e){
|
||||
String s = "Exception persisting schema";
|
||||
log.warn(s, e);
|
||||
return singletonList(s + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return singletonList("Unable to persist schema");
|
||||
|
||||
}
|
||||
|
||||
private void waitForOtherReplicasToUpdate(int timeout, long startTime) {
|
||||
if(timeout > 0 && managedIndexSchema.getResourceLoader()instanceof ZkSolrResourceLoader){
|
||||
CoreDescriptor cd = req.getCore().getCoreDescriptor();
|
||||
String collection = cd.getCollectionName();
|
||||
if (collection != null) {
|
||||
ZkSolrResourceLoader zkLoader = (ZkSolrResourceLoader) managedIndexSchema.getResourceLoader();
|
||||
long timeLeftSecs1 = timeout - ((System.nanoTime() - startTime) /1000000);
|
||||
int secsLeft = (int) (timeLeftSecs1 > 0 ? timeLeftSecs1 : -1);
|
||||
if(secsLeft<=0) throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Not enough time left to update replicas. However the schema is updated already");
|
||||
long timeLeftSecs = timeout - ((System.nanoTime() - startTime) /1000000);
|
||||
ManagedIndexSchema.waitForSchemaZkVersionAgreement(collection,
|
||||
cd.getCloudDescriptor().getCoreNodeName(),
|
||||
(managedIndexSchema).getSchemaZkVersion(),
|
||||
zkLoader.getZkController(),
|
||||
(int) (timeLeftSecs > 0 ? timeLeftSecs : -1));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private boolean applyAddType(Operation op) {
|
||||
String name = op.getStr(NAME);
|
||||
String clz = op.getStr(CLASS_NAME);
|
||||
if(op.hasError())
|
||||
return false;
|
||||
try {
|
||||
FieldType fieldType = managedIndexSchema.newFieldType(name, clz, (Map<String, ?>) op.commandData);
|
||||
managedIndexSchema = managedIndexSchema.addFieldTypes(singletonList(fieldType), false);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
op.addError(getErrorStr(e));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private String getErrorStr(Exception e) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Throwable cause= e;
|
||||
for(int i =0;i<5;i++) {
|
||||
sb.append(cause.getMessage()).append("\n");
|
||||
if(cause.getCause() == null || cause.getCause() == cause) break;
|
||||
cause = cause.getCause();
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private boolean applyAddCopyField(Operation op) {
|
||||
String src = op.getStr(SOURCE);
|
||||
List<String> dest = op.getStrs(DESTINATION);
|
||||
if(op.hasError())
|
||||
return false;
|
||||
try {
|
||||
managedIndexSchema = managedIndexSchema.addCopyFields(Collections.<String,Collection<String>>singletonMap(src,dest), false);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
op.addError(getErrorStr(e));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean applyAddField( Operation op) {
|
||||
String name = op.getStr(NAME);
|
||||
String type = op.getStr(TYPE);
|
||||
if(op.hasError())
|
||||
return false;
|
||||
FieldType ft = managedIndexSchema.getFieldTypeByName(type);
|
||||
if(ft==null){
|
||||
op.addError("No such field type '"+type+"'");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
if(ADD_DYNAMIC_FIELD.equals(op.name)){
|
||||
managedIndexSchema = managedIndexSchema.addDynamicFields(
|
||||
singletonList(SchemaField.create(name, ft, op.getValuesExcluding(NAME, TYPE))),
|
||||
EMPTY_MAP,false);
|
||||
} else {
|
||||
managedIndexSchema = managedIndexSchema.addFields(
|
||||
singletonList( SchemaField.create(name, ft, op.getValuesExcluding(NAME, TYPE))),
|
||||
EMPTY_MAP,
|
||||
false);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
op.addError(getErrorStr(e));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static class Operation {
|
||||
public final String name;
|
||||
private Object commandData;//this is most often a map
|
||||
private List<String> errors = new ArrayList<>();
|
||||
|
||||
Operation(String operationName, Object metaData) {
|
||||
commandData = metaData;
|
||||
this.name = operationName;
|
||||
if(!KNOWN_OPS.contains(this.name)) errors.add("Unknown Operation :"+this.name);
|
||||
}
|
||||
|
||||
public String getStr(String key, String def){
|
||||
String s = (String) getMapVal(key);
|
||||
return s == null ? def : s;
|
||||
}
|
||||
|
||||
private Object getMapVal(String key) {
|
||||
if (commandData instanceof Map) {
|
||||
Map metaData = (Map) commandData;
|
||||
return metaData.get(key);
|
||||
} else {
|
||||
String msg= " value has to be an object for operation :"+name;
|
||||
if(!errors.contains(msg)) errors.add(msg);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getStrs(String key){
|
||||
List<String> val = getStrs(key, null);
|
||||
if(val == null) errors.add("'"+key + "' is a required field");
|
||||
return val;
|
||||
|
||||
}
|
||||
|
||||
/**Get collection of values for a key. If only one val is present a
|
||||
* single value collection is returned
|
||||
*/
|
||||
public List<String> getStrs(String key, List<String> def){
|
||||
Object v = getMapVal(key);
|
||||
if(v == null){
|
||||
return def;
|
||||
} else {
|
||||
if (v instanceof List) {
|
||||
ArrayList<String> l = new ArrayList<>();
|
||||
for (Object o : (List)v) {
|
||||
l.add(String.valueOf(o));
|
||||
}
|
||||
if(l.isEmpty()) return def;
|
||||
return l;
|
||||
} else {
|
||||
return singletonList(String.valueOf(v));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**Get a required field. If missing it adds to the errors
|
||||
*/
|
||||
public String getStr(String key){
|
||||
String s = getStr(key,null);
|
||||
if(s==null) errors.add("'"+key + "' is a required field");
|
||||
return s;
|
||||
}
|
||||
|
||||
private Map errorDetails(){
|
||||
return makeMap(name, commandData, ERR_MSGS, errors);
|
||||
}
|
||||
|
||||
public boolean hasError() {
|
||||
return !errors.isEmpty();
|
||||
}
|
||||
|
||||
public void addError(String s) {
|
||||
errors.add(s);
|
||||
}
|
||||
|
||||
/**Get all the values from the metadata for the command
|
||||
* without the specified keys
|
||||
*/
|
||||
public Map getValuesExcluding(String... keys) {
|
||||
getMapVal(null);
|
||||
if(hasError()) return emptyMap();//just to verify the type is Map
|
||||
LinkedHashMap<String, Object> cp = new LinkedHashMap<>((Map<String,?>) commandData);
|
||||
if(keys == null) return cp;
|
||||
for (String key : keys) {
|
||||
cp.remove(key);
|
||||
}
|
||||
return cp;
|
||||
}
|
||||
|
||||
|
||||
public List<String> getErrors() {
|
||||
return errors;
|
||||
}
|
||||
}
|
||||
|
||||
/**Parse the command operations into command objects
|
||||
*/
|
||||
static List<Operation> parse(Reader rdr ) throws IOException {
|
||||
JSONParser parser = new JSONParser(rdr);
|
||||
|
||||
ObjectBuilder ob = new ObjectBuilder(parser);
|
||||
|
||||
if(parser.lastEvent() != JSONParser.OBJECT_START) {
|
||||
throw new RuntimeException("The JSON must be an Object of the form {\"command\": {...},...");
|
||||
}
|
||||
List<Operation> operations = new ArrayList<>();
|
||||
for(;;) {
|
||||
int ev = parser.nextEvent();
|
||||
if (ev==JSONParser.OBJECT_END) return operations;
|
||||
Object key = ob.getKey();
|
||||
ev = parser.nextEvent();
|
||||
Object val = ob.getVal();
|
||||
if (val instanceof List) {
|
||||
List list = (List) val;
|
||||
for (Object o : list) {
|
||||
operations.add(new Operation(String.valueOf(key), o));
|
||||
}
|
||||
} else {
|
||||
operations.add(new Operation(String.valueOf(key), val));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static List<Map> captureErrors(List<Operation> ops){
|
||||
List<Map> errors = new ArrayList<>();
|
||||
for (SchemaManager.Operation op : ops) {
|
||||
if(op.hasError()) {
|
||||
errors.add(op.errorDetails());
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
public static final String ERR_MSGS = "errorMessages";
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,236 @@
|
|||
package org.apache.solr.rest.schema;
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.solr.SolrTestCaseJ4;
|
||||
import org.apache.solr.schema.SchemaManager;
|
||||
import org.apache.solr.util.RestTestBase;
|
||||
import org.apache.solr.util.RestTestHarness;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.noggit.JSONParser;
|
||||
import org.noggit.ObjectBuilder;
|
||||
import org.restlet.ext.servlet.ServerServlet;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.StringReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
|
||||
|
||||
public class TestBulkSchemaAPI extends RestTestBase {
|
||||
|
||||
private static File tmpSolrHome;
|
||||
private static File tmpConfDir;
|
||||
|
||||
private static final String collection = "collection1";
|
||||
private static final String confDir = collection + "/conf";
|
||||
|
||||
|
||||
@Before
|
||||
public void before() throws Exception {
|
||||
tmpSolrHome = createTempDir().toFile();
|
||||
tmpConfDir = new File(tmpSolrHome, confDir);
|
||||
FileUtils.copyDirectory(new File(TEST_HOME()), tmpSolrHome.getAbsoluteFile());
|
||||
|
||||
final SortedMap<ServletHolder,String> extraServlets = new TreeMap<>();
|
||||
final ServletHolder solrRestApi = new ServletHolder("SolrSchemaRestApi", ServerServlet.class);
|
||||
solrRestApi.setInitParameter("org.restlet.application", "org.apache.solr.rest.SolrSchemaRestApi");
|
||||
extraServlets.put(solrRestApi, "/schema/*"); // '/schema/*' matches '/schema', '/schema/', and '/schema/whatever...'
|
||||
|
||||
System.setProperty("managed.schema.mutable", "true");
|
||||
System.setProperty("enable.update.log", "false");
|
||||
|
||||
createJettyAndHarness(tmpSolrHome.getAbsolutePath(), "solrconfig-managed-schema.xml", "schema-rest.xml",
|
||||
"/solr", true, extraServlets);
|
||||
}
|
||||
|
||||
@After
|
||||
public void after() throws Exception {
|
||||
if (jetty != null) {
|
||||
jetty.stop();
|
||||
jetty = null;
|
||||
}
|
||||
server = null;
|
||||
restTestHarness = null;
|
||||
}
|
||||
|
||||
public void testMultipleAddFieldWithErrors() throws Exception {
|
||||
|
||||
String payload = SolrTestCaseJ4.json( "{\n" +
|
||||
" 'add-field' : {\n" +
|
||||
" 'name':'a1',\n" +
|
||||
" 'type': 'string1',\n" +
|
||||
" 'stored':true,\n" +
|
||||
" 'indexed':false\n" +
|
||||
" },\n" +
|
||||
" 'add-field' : {\n" +
|
||||
" 'type': 'string',\n" +
|
||||
" 'stored':true,\n" +
|
||||
" 'indexed':true\n" +
|
||||
" }\n" +
|
||||
" \n" +
|
||||
" }");
|
||||
|
||||
String response = restTestHarness.post("/schema?wt=json", payload);
|
||||
Map map = (Map) ObjectBuilder.getVal(new JSONParser(new StringReader(response)));
|
||||
List l = (List) map.get("errors");
|
||||
|
||||
List errorList = (List) ((Map) l.get(0)).get(SchemaManager.ERR_MSGS);
|
||||
assertEquals(1, errorList.size());
|
||||
assertTrue (((String)errorList.get(0)).contains("No such field type"));
|
||||
errorList = (List) ((Map) l.get(1)).get(SchemaManager.ERR_MSGS);
|
||||
assertEquals(1, errorList.size());
|
||||
assertTrue (((String)errorList.get(0)).contains("is a required field"));
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void testMultipleCommands() throws Exception{
|
||||
String payload = "{\n" +
|
||||
" 'add-field' : {\n" +
|
||||
" 'name':'a1',\n" +
|
||||
" 'type': 'string',\n" +
|
||||
" 'stored':true,\n" +
|
||||
" 'indexed':false\n" +
|
||||
" },\n" +
|
||||
" 'add-field' : {\n" +
|
||||
" 'name':'a2',\n" +
|
||||
" 'type': 'string',\n" +
|
||||
" 'stored':true,\n" +
|
||||
" 'indexed':true\n" +
|
||||
" },\n" +
|
||||
" 'add-dynamic-field' : {\n" +
|
||||
" 'name' :'*_lol',\n" +
|
||||
" 'type':'string',\n" +
|
||||
" 'stored':true,\n" +
|
||||
" 'indexed':true\n" +
|
||||
" },\n" +
|
||||
" 'add-copy-field' : {\n" +
|
||||
" 'source' :'a1',\n" +
|
||||
" 'dest':['a2','hello_lol']\n" +
|
||||
" },\n" +
|
||||
" 'add-field-type' : {\n" +
|
||||
" 'name' :'mystr',\n" +
|
||||
" 'class' : 'solr.StrField',\n" +
|
||||
" 'sortMissingLast':'true'\n" +
|
||||
" },\n" +
|
||||
" 'add-field-type' : {" +
|
||||
" 'name' : 'myNewTxtField',\n" +
|
||||
" 'class':'solr.TextField','positionIncrementGap':'100',\n" +
|
||||
" 'analyzer' : {\n" +
|
||||
" 'charFilters':[\n" +
|
||||
" {'class':'solr.PatternReplaceCharFilterFactory','replacement':'$1$1','pattern':'([a-zA-Z])\\\\\\\\1+'}\n" +
|
||||
" ],\n" +
|
||||
" 'tokenizer':{'class':'solr.WhitespaceTokenizerFactory'},\n" +
|
||||
" 'filters':[\n" +
|
||||
" {'class':'solr.WordDelimiterFilterFactory','preserveOriginal':'0'},\n" +
|
||||
" {'class':'solr.StopFilterFactory','words':'stopwords.txt','ignoreCase':'true'},\n" +
|
||||
" {'class':'solr.LowerCaseFilterFactory'},\n" +
|
||||
" {'class':'solr.ASCIIFoldingFilterFactory'},\n" +
|
||||
" {'class':'solr.KStemFilterFactory'}\n" +
|
||||
" ]\n" +
|
||||
" }\n" +
|
||||
" }"+
|
||||
" }";
|
||||
|
||||
RestTestHarness harness = restTestHarness;
|
||||
|
||||
|
||||
String response = harness.post("/schema?wt=json", SolrTestCaseJ4.json( payload));
|
||||
|
||||
Map map = (Map) ObjectBuilder.getVal(new JSONParser(new StringReader(response)));
|
||||
assertNull(response, map.get("errors"));
|
||||
|
||||
|
||||
Map m = getObj(harness, "a1", "fields");
|
||||
assertNotNull("field a1 not created", m);
|
||||
|
||||
assertEquals("string", m.get("type"));
|
||||
assertEquals(Boolean.TRUE, m.get("stored"));
|
||||
assertEquals(Boolean.FALSE, m.get("indexed"));
|
||||
|
||||
m = getObj(harness,"a2", "fields");
|
||||
assertNotNull("field a2 not created", m);
|
||||
|
||||
assertEquals("string", m.get("type"));
|
||||
assertEquals(Boolean.TRUE, m.get("stored"));
|
||||
assertEquals(Boolean.TRUE, m.get("indexed"));
|
||||
|
||||
m = getObj(harness,"*_lol", "dynamicFields");
|
||||
assertNotNull("field *_lol not created",m );
|
||||
|
||||
assertEquals("string", m.get("type"));
|
||||
assertEquals(Boolean.TRUE, m.get("stored"));
|
||||
assertEquals(Boolean.TRUE, m.get("indexed"));
|
||||
|
||||
List l = getCopyFields(harness,"a1");
|
||||
Set s =new HashSet();
|
||||
assertEquals(2,l.size());
|
||||
s.add(((Map) l.get(0)).get("dest"));
|
||||
s.add(((Map) l.get(1)).get("dest"));
|
||||
assertTrue(s.contains("hello_lol"));
|
||||
assertTrue(s.contains("a2"));
|
||||
|
||||
m = getObj(harness,"mystr", "fieldTypes");
|
||||
assertNotNull(m);
|
||||
assertEquals("solr.StrField",m.get("class"));
|
||||
assertEquals("true",String.valueOf(m.get("sortMissingLast")));
|
||||
|
||||
m = getObj(harness,"myNewTxtField", "fieldTypes");
|
||||
assertNotNull(m);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static Map getObj(RestTestHarness restHarness, String fld, String key) throws Exception {
|
||||
Map map = getRespMap(restHarness);
|
||||
List l = (List) ((Map)map.get("schema")).get(key);
|
||||
for (Object o : l) {
|
||||
Map m = (Map) o;
|
||||
if(fld.equals(m.get("name"))) return m;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Map getRespMap(RestTestHarness restHarness) throws Exception {
|
||||
String response = restHarness.query("/schema?wt=json");
|
||||
return (Map) ObjectBuilder.getVal(new JSONParser(new StringReader(response)));
|
||||
}
|
||||
|
||||
public static List getCopyFields(RestTestHarness harness, String src) throws Exception {
|
||||
Map map = getRespMap(harness);
|
||||
List l = (List) ((Map)map.get("schema")).get("copyFields");
|
||||
List result = new ArrayList();
|
||||
for (Object o : l) {
|
||||
Map m = (Map) o;
|
||||
if(src.equals(m.get("source"))) result.add(m);
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,194 @@
|
|||
package org.apache.solr.schema;
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
|
||||
import org.apache.solr.SolrTestCaseJ4;
|
||||
import org.apache.solr.client.solrj.SolrServer;
|
||||
import org.apache.solr.client.solrj.impl.HttpSolrServer;
|
||||
import org.apache.solr.cloud.AbstractFullDistribZkTestBase;
|
||||
import org.apache.solr.common.cloud.ZkStateReader;
|
||||
import org.apache.solr.util.RESTfulServerProvider;
|
||||
import org.apache.solr.util.RestTestHarness;
|
||||
import org.noggit.JSONParser;
|
||||
import org.noggit.ObjectBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static java.text.MessageFormat.format;
|
||||
import static org.apache.solr.rest.schema.TestBulkSchemaAPI.getCopyFields;
|
||||
import static org.apache.solr.rest.schema.TestBulkSchemaAPI.getObj;
|
||||
|
||||
public class TestBulkSchemaConcurrent extends AbstractFullDistribZkTestBase {
|
||||
static final Logger log = LoggerFactory.getLogger(TestBulkSchemaConcurrent.class);
|
||||
private List<RestTestHarness> restTestHarnesses = new ArrayList<>();
|
||||
|
||||
private void setupHarnesses() {
|
||||
for (final SolrServer client : clients) {
|
||||
RestTestHarness harness = new RestTestHarness(new RESTfulServerProvider() {
|
||||
@Override
|
||||
public String getBaseURL() {
|
||||
return ((HttpSolrServer)client).getBaseURL();
|
||||
}
|
||||
});
|
||||
restTestHarnesses.add(harness);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void doTest() throws Exception {
|
||||
|
||||
final int threadCount = 5;
|
||||
setupHarnesses();
|
||||
Thread[] threads = new Thread[threadCount];
|
||||
final List<List> collectErrors = new ArrayList<>();
|
||||
|
||||
|
||||
for(int i=0;i<threadCount;i++){
|
||||
final int finalI = i;
|
||||
threads[i] = new Thread(){
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
ArrayList errs = new ArrayList();
|
||||
collectErrors.add(errs);
|
||||
invokeBulkCall(finalI,errs);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
threads[i].start();
|
||||
}
|
||||
|
||||
for (Thread thread : threads) thread.join();
|
||||
|
||||
boolean success = true;
|
||||
|
||||
for (List e : collectErrors) {
|
||||
if(!e.isEmpty()){
|
||||
success = false;
|
||||
log.error(e.toString());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
assertTrue(success);
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void invokeBulkCall(int seed, ArrayList<String> errs) throws Exception {
|
||||
String payload = "{\n" +
|
||||
" 'add-field' : {\n" +
|
||||
" 'name':'replaceFieldA',\n" +
|
||||
" 'type': 'string',\n" +
|
||||
" 'stored':true,\n" +
|
||||
" 'indexed':false\n" +
|
||||
" },\n" +
|
||||
" 'add-dynamic-field' : {\n" +
|
||||
" 'name' :'replaceDynamicField',\n" +
|
||||
" 'type':'string',\n" +
|
||||
" 'stored':true,\n" +
|
||||
" 'indexed':true\n" +
|
||||
" },\n" +
|
||||
" 'add-copy-field' : {\n" +
|
||||
" 'source' :'replaceFieldA',\n" +
|
||||
" 'dest':['replaceDynamicCopyFieldDest']\n" +
|
||||
" },\n" +
|
||||
" 'add-field-type' : {\n" +
|
||||
" 'name' :'myNewFieldTypeName',\n" +
|
||||
" 'class' : 'solr.StrField',\n" +
|
||||
" 'sortMissingLast':'true'\n" +
|
||||
" }\n" +
|
||||
"\n" +
|
||||
" }";
|
||||
String aField = "a" + seed;
|
||||
String dynamicFldName = "*_lol" + seed;
|
||||
String dynamicCopyFldDest = "hello_lol"+seed;
|
||||
String newFieldTypeName = "mystr" + seed;
|
||||
|
||||
|
||||
RestTestHarness publisher = restTestHarnesses.get(r.nextInt(restTestHarnesses.size()));
|
||||
payload = payload.replace("replaceFieldA1", aField);
|
||||
|
||||
payload = payload.replace("replaceDynamicField", dynamicFldName);
|
||||
payload = payload.replace("dynamicFieldLol","lol"+seed);
|
||||
|
||||
payload = payload.replace("replaceDynamicCopyFieldDest",dynamicCopyFldDest);
|
||||
payload = payload.replace("myNewFieldTypeName", newFieldTypeName);
|
||||
String response = publisher.post("/schema?wt=json", SolrTestCaseJ4.json(payload));
|
||||
Map map = (Map) ObjectBuilder.getVal(new JSONParser(new StringReader(response)));
|
||||
Object errors = map.get("errors");
|
||||
if(errors!= null){
|
||||
errs.add(new String(ZkStateReader.toJSON(errors), StandardCharsets.UTF_8));
|
||||
return;
|
||||
}
|
||||
|
||||
//get another node
|
||||
RestTestHarness harness = restTestHarnesses.get(r.nextInt(restTestHarnesses.size()));
|
||||
long startTime = System.nanoTime();
|
||||
boolean success = false;
|
||||
long maxTimeoutMillis = 100000;
|
||||
Set<String> errmessages = new HashSet<>();
|
||||
while ( ! success
|
||||
&& TimeUnit.MILLISECONDS.convert(System.nanoTime() - startTime, TimeUnit.NANOSECONDS) < maxTimeoutMillis) {
|
||||
errmessages.clear();
|
||||
Map m = getObj(harness, aField, "fields");
|
||||
if(m== null) errmessages.add(format("field {0} not created", aField));
|
||||
|
||||
m = getObj(harness, dynamicFldName, "dynamicFields");
|
||||
if(m== null) errmessages.add(format("dynamic field {0} not created", dynamicFldName));
|
||||
|
||||
List l = getCopyFields(harness, "a1");
|
||||
if(!checkCopyField(l,aField,dynamicCopyFldDest))
|
||||
errmessages.add(format("CopyField source={0},dest={1} not created" , aField,dynamicCopyFldDest));
|
||||
|
||||
m = getObj(harness, "mystr", "fieldTypes");
|
||||
if(m == null) errmessages.add(format("new type {} not created" , newFieldTypeName));
|
||||
Thread.sleep(10);
|
||||
}
|
||||
if(!errmessages.isEmpty()){
|
||||
errs.addAll(errmessages);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkCopyField(List<Map> l, String src, String dest) {
|
||||
if(l == null) return false;
|
||||
for (Map map : l) {
|
||||
if(src.equals(map.get("source")) &&
|
||||
dest.equals(map.get("dest"))) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
package org.apache.solr.schema;
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import org.apache.solr.SolrTestCaseJ4;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringReader;
|
||||
import java.util.List;
|
||||
|
||||
public class TestSchemaManager extends SolrTestCaseJ4 {
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
initCore("solrconfig.xml","schema-tiny.xml");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParsing() throws IOException {
|
||||
String x = "{\n" +
|
||||
" \"add-field\" : {\n" +
|
||||
" \"name\":\"a\",\n" +
|
||||
" \"type\": \"string\",\n" +
|
||||
" \"stored\":true,\n" +
|
||||
" \"indexed\":false\n" +
|
||||
" },\n" +
|
||||
" \"add-field\" : {\n" +
|
||||
" \"name\":\"b\",\n" +
|
||||
" \"type\": \"string\",\n" +
|
||||
" \"stored\":true,\n" +
|
||||
" \"indexed\":false\n" +
|
||||
" }\n" +
|
||||
"\n" +
|
||||
"}";
|
||||
|
||||
List<SchemaManager.Operation> ops = SchemaManager.parse(new StringReader(x));
|
||||
assertEquals(2,ops.size());
|
||||
assertTrue( SchemaManager.captureErrors(ops).isEmpty());
|
||||
|
||||
x = " {\"add-field\" : [{\n" +
|
||||
" \"name\":\"a1\",\n" +
|
||||
" \"type\": \"string\",\n" +
|
||||
" \"stored\":true,\n" +
|
||||
" \"indexed\":false\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"name\":\"a2\",\n" +
|
||||
" \"type\": \"string\",\n" +
|
||||
" \"stored\":true,\n" +
|
||||
" \"indexed\":true\n" +
|
||||
" }]\n" +
|
||||
" }";
|
||||
ops = SchemaManager.parse(new StringReader(x));
|
||||
assertEquals(2,ops.size());
|
||||
assertTrue( SchemaManager.captureErrors(ops).isEmpty());
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
Loading…
Reference in New Issue