Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* 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.
*/
package org.apache.solr.update.processor;
import static org.apache.solr.common.params.CommonParams.ID;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.BytesRefBuilder;
import org.apache.solr.cloud.CloudDescriptor;
import org.apache.solr.cloud.ZkController;
import org.apache.solr.common.SolrDocumentBase;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.SolrInputField;
import org.apache.solr.common.cloud.DocCollection;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.core.SolrCore;
import org.apache.solr.handler.component.RealTimeGetComponent;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.schema.CopyField;
import org.apache.solr.schema.IndexSchema;
import org.apache.solr.schema.NumericValueFieldType;
import org.apache.solr.schema.SchemaField;
import org.apache.solr.update.AddUpdateCommand;
import org.apache.solr.util.RefCounted;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @lucene.experimental
*/
public class AtomicUpdateDocumentMerger {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
protected final IndexSchema schema;
protected final SchemaField idField;
public AtomicUpdateDocumentMerger(SolrQueryRequest queryReq) {
schema = queryReq.getSchema();
idField = schema.getUniqueKeyField();
}
/**
* Utility method that examines the SolrInputDocument in an AddUpdateCommand
* and returns true if the documents contains atomic update instructions.
*/
public static boolean isAtomicUpdate(final AddUpdateCommand cmd) {
SolrInputDocument sdoc = cmd.getSolrInputDocument();
for (SolrInputField sif : sdoc.values()) {
Object val = sif.getValue();
if (val instanceof Map && !(val instanceof SolrDocumentBase)) {
return true;
}
}
return false;
}
/**
* Merges the fromDoc into the toDoc using the atomic update syntax.
*
* @param fromDoc SolrInputDocument which will merged into the toDoc
* @param toDoc the final SolrInputDocument that will be mutated with the values from the fromDoc atomic commands
* @return toDoc with mutated values
*/
public SolrInputDocument merge(final SolrInputDocument fromDoc, SolrInputDocument toDoc) {
for (SolrInputField sif : fromDoc.values()) {
Object val = sif.getValue();
if (val instanceof Map) {
for (Entry entry : ((Map) val).entrySet()) {
String key = entry.getKey();
Object fieldVal = entry.getValue();
switch (key) {
case "add":
doAdd(toDoc, sif, fieldVal);
break;
case "set":
doSet(toDoc, sif, fieldVal);
break;
case "remove":
doRemove(toDoc, sif, fieldVal);
break;
case "removeregex":
doRemoveRegex(toDoc, sif, fieldVal);
break;
case "inc":
doInc(toDoc, sif, fieldVal);
break;
case "add-distinct":
doAddDistinct(toDoc, sif, fieldVal);
break;
default:
Object id = toDoc.containsKey(idField.getName())? toDoc.getFieldValue(idField.getName()):
fromDoc.getFieldValue(idField.getName());
String err = "Unknown operation for the an atomic update, operation ignored: " + key;
if (id != null) {
err = err + " for id:" + id;
}
throw new SolrException(ErrorCode.BAD_REQUEST, err);
}
// validate that the field being modified is not the id field.
if (idField.getName().equals(sif.getName())) {
throw new SolrException(ErrorCode.BAD_REQUEST, "Invalid update of id field: " + sif);
}
}
} else {
// normal fields are treated as a "set"
toDoc.put(sif.getName(), sif);
}
}
return toDoc;
}
/**
* Given a schema field, return whether or not such a field is supported for an in-place update.
* Note: If an update command has updates to only supported fields (and _version_ is also supported),
* only then is such an update command executed as an in-place update.
*/
public static boolean isSupportedFieldForInPlaceUpdate(SchemaField schemaField) {
return !(schemaField.indexed() || schemaField.stored() || !schemaField.hasDocValues() ||
schemaField.multiValued() || !(schemaField.getType() instanceof NumericValueFieldType));
}
/**
* Given an add update command, compute a list of fields that can be updated in-place. If there is even a single
* field in the update that cannot be updated in-place, the entire update cannot be executed in-place (and empty set
* will be returned in that case).
*
* @return Return a set of fields that can be in-place updated.
*/
public static Set computeInPlaceUpdatableFields(AddUpdateCommand cmd) throws IOException {
SolrInputDocument sdoc = cmd.getSolrInputDocument();
IndexSchema schema = cmd.getReq().getSchema();
final SchemaField uniqueKeyField = schema.getUniqueKeyField();
final String uniqueKeyFieldName = null == uniqueKeyField ? null : uniqueKeyField.getName();
final Set candidateFields = new HashSet<>();
// if _version_ field is not supported for in-place update, bail out early
SchemaField versionField = schema.getFieldOrNull(CommonParams.VERSION_FIELD);
if (versionField == null || !isSupportedFieldForInPlaceUpdate(versionField)) {
return Collections.emptySet();
}
String routeFieldOrNull = getRouteField(cmd);
// first pass, check the things that are virtually free,
// and bail out early if anything is obviously not a valid in-place update
for (String fieldName : sdoc.getFieldNames()) {
if (fieldName.equals(uniqueKeyFieldName)
|| fieldName.equals(CommonParams.VERSION_FIELD)
|| fieldName.equals(routeFieldOrNull)) {
continue;
}
Object fieldValue = sdoc.getField(fieldName).getValue();
if (! (fieldValue instanceof Map) ) {
// not an in-place update if there are fields that are not maps
return Collections.emptySet();
}
// else it's a atomic update map...
for (String op : ((Map)fieldValue).keySet()) {
if (!op.equals("set") && !op.equals("inc")) {
// not a supported in-place update op
return Collections.emptySet();
}
// fail fast if child doc
if(isChildDoc(((Map) fieldValue).get(op))) {
return Collections.emptySet();
}
}
candidateFields.add(fieldName);
}
if (candidateFields.isEmpty()) {
return Collections.emptySet();
}
// second pass over the candidates for in-place updates
// this time more expensive checks involving schema/config settings
for (String fieldName: candidateFields) {
SchemaField schemaField = schema.getField(fieldName);
if (!isSupportedFieldForInPlaceUpdate(schemaField)) {
return Collections.emptySet();
}
// if this field has copy target which is not supported for in place, then empty
for (CopyField copyField: schema.getCopyFieldsList(fieldName)) {
if (!isSupportedFieldForInPlaceUpdate(copyField.getDestination()))
return Collections.emptySet();
}
}
// third pass: requiring checks against the actual IndexWriter due to internal DV update limitations
SolrCore core = cmd.getReq().getCore();
RefCounted holder = core.getSolrCoreState().getIndexWriter(core);
Set fieldNamesFromIndexWriter = null;
Set segmentSortingFields = null;
try {
IndexWriter iw = holder.get();
fieldNamesFromIndexWriter = iw.getFieldNames(); // This shouldn't be needed once LUCENE-7659 is resolved
segmentSortingFields = iw.getConfig().getIndexSortFields();
} finally {
holder.decref();
}
for (String fieldName: candidateFields) {
if (! fieldNamesFromIndexWriter.contains(fieldName) ) {
return Collections.emptySet(); // if this field doesn't exist, DV update can't work
}
if (segmentSortingFields.contains(fieldName) ) {
return Collections.emptySet(); // if this is used for segment sorting, DV updates can't work
}
}
return candidateFields;
}
private static String getRouteField(AddUpdateCommand cmd) {
String result = null;
SolrCore core = cmd.getReq().getCore();
CloudDescriptor cloudDescriptor = core.getCoreDescriptor().getCloudDescriptor();
if (cloudDescriptor != null) {
String collectionName = cloudDescriptor.getCollectionName();
ZkController zkController = core.getCoreContainer().getZkController();
DocCollection collection = zkController.getClusterState().getCollection(collectionName);
result = collection.getRouter().getRouteField(collection);
}
return result;
}
/**
*
* @param fullDoc the full doc to be compared against
* @param partialDoc the sub document to be tested
* @return whether partialDoc is derived from fullDoc
*/
public static boolean isDerivedFromDoc(SolrInputDocument fullDoc, SolrInputDocument partialDoc) {
for(SolrInputField subSif: partialDoc) {
Collection