/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch 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.elasticsearch.index;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.lucene.uid.Versions;
import java.io.IOException;
public enum VersionType implements Writeable {
INTERNAL((byte) 0) {
@Override
public boolean isVersionConflictForWrites(long currentVersion, long expectedVersion, boolean deleted) {
return isVersionConflict(currentVersion, expectedVersion, deleted);
}
@Override
public String explainConflictForWrites(long currentVersion, long expectedVersion, boolean deleted) {
if (expectedVersion == Versions.MATCH_DELETED) {
return "document already exists (current version [" + currentVersion + "])";
}
return "current version [" + currentVersion + "] is different than the one provided [" + expectedVersion + "]";
}
@Override
public boolean isVersionConflictForReads(long currentVersion, long expectedVersion) {
return isVersionConflict(currentVersion, expectedVersion, false);
}
@Override
public String explainConflictForReads(long currentVersion, long expectedVersion) {
return "current version [" + currentVersion + "] is different than the one provided [" + expectedVersion + "]";
}
private boolean isVersionConflict(long currentVersion, long expectedVersion, boolean deleted) {
if (expectedVersion == Versions.MATCH_ANY) {
return false;
}
if (expectedVersion == Versions.MATCH_DELETED) {
return deleted == false;
}
if (currentVersion != expectedVersion) {
return true;
}
return false;
}
@Override
public long updateVersion(long currentVersion, long expectedVersion) {
return currentVersion == Versions.NOT_FOUND ? 1 : currentVersion + 1;
}
@Override
public boolean validateVersionForWrites(long version) {
return version > 0L || version == Versions.MATCH_ANY || version == Versions.MATCH_DELETED;
}
@Override
public boolean validateVersionForReads(long version) {
// not allowing Versions.NOT_FOUND as it is not a valid input value.
return version > 0L || version == Versions.MATCH_ANY;
}
@Override
public VersionType versionTypeForReplicationAndRecovery() {
// replicas get the version from the primary after increment. The same version is stored in
// the transaction log. -> the should use the external semantics.
return EXTERNAL;
}
},
EXTERNAL((byte) 1) {
@Override
public boolean isVersionConflictForWrites(long currentVersion, long expectedVersion, boolean deleted) {
if (currentVersion == Versions.NOT_FOUND) {
return false;
}
if (expectedVersion == Versions.MATCH_ANY) {
return true;
}
if (currentVersion >= expectedVersion) {
return true;
}
return false;
}
@Override
public String explainConflictForWrites(long currentVersion, long expectedVersion, boolean deleted) {
return "current version [" + currentVersion + "] is higher or equal to the one provided [" + expectedVersion + "]";
}
@Override
public boolean isVersionConflictForReads(long currentVersion, long expectedVersion) {
if (expectedVersion == Versions.MATCH_ANY) {
return false;
}
if (currentVersion == Versions.NOT_FOUND) {
return true;
}
if (currentVersion != expectedVersion) {
return true;
}
return false;
}
@Override
public String explainConflictForReads(long currentVersion, long expectedVersion) {
return "current version [" + currentVersion + "] is different than the one provided [" + expectedVersion + "]";
}
@Override
public long updateVersion(long currentVersion, long expectedVersion) {
return expectedVersion;
}
@Override
public boolean validateVersionForWrites(long version) {
return version >= 0L;
}
@Override
public boolean validateVersionForReads(long version) {
return version >= 0L || version == Versions.MATCH_ANY;
}
},
EXTERNAL_GTE((byte) 2) {
@Override
public boolean isVersionConflictForWrites(long currentVersion, long expectedVersion, boolean deleted) {
if (currentVersion == Versions.NOT_FOUND) {
return false;
}
if (expectedVersion == Versions.MATCH_ANY) {
return true;
}
if (currentVersion > expectedVersion) {
return true;
}
return false;
}
@Override
public String explainConflictForWrites(long currentVersion, long expectedVersion, boolean deleted) {
return "current version [" + currentVersion + "] is higher than the one provided [" + expectedVersion + "]";
}
@Override
public boolean isVersionConflictForReads(long currentVersion, long expectedVersion) {
if (expectedVersion == Versions.MATCH_ANY) {
return false;
}
if (currentVersion == Versions.NOT_FOUND) {
return true;
}
if (currentVersion != expectedVersion) {
return true;
}
return false;
}
@Override
public String explainConflictForReads(long currentVersion, long expectedVersion) {
return "current version [" + currentVersion + "] is different than the one provided [" + expectedVersion + "]";
}
@Override
public long updateVersion(long currentVersion, long expectedVersion) {
return expectedVersion;
}
@Override
public boolean validateVersionForWrites(long version) {
return version >= 0L;
}
@Override
public boolean validateVersionForReads(long version) {
return version >= 0L || version == Versions.MATCH_ANY;
}
},
/**
* Warning: this version type should be used with care. Concurrent indexing may result in loss of data on replicas
*
* @deprecated this will be removed in 7.0 and should not be used! It is *ONLY* for backward compatibility with 5.0 indices
*/
@Deprecated
FORCE((byte) 3) {
@Override
public boolean isVersionConflictForWrites(long currentVersion, long expectedVersion, boolean deleted) {
if (currentVersion == Versions.NOT_FOUND) {
return false;
}
if (expectedVersion == Versions.MATCH_ANY) {
throw new IllegalStateException("you must specify a version when use VersionType.FORCE");
}
return false;
}
@Override
public String explainConflictForWrites(long currentVersion, long expectedVersion, boolean deleted) {
throw new AssertionError("VersionType.FORCE should never result in a write conflict");
}
@Override
public boolean isVersionConflictForReads(long currentVersion, long expectedVersion) {
return false;
}
@Override
public String explainConflictForReads(long currentVersion, long expectedVersion) {
throw new AssertionError("VersionType.FORCE should never result in a read conflict");
}
@Override
public long updateVersion(long currentVersion, long expectedVersion) {
return expectedVersion;
}
@Override
public boolean validateVersionForWrites(long version) {
return version >= 0L;
}
@Override
public boolean validateVersionForReads(long version) {
return version >= 0L || version == Versions.MATCH_ANY;
}
};
private final byte value;
VersionType(byte value) {
this.value = value;
}
public byte getValue() {
return value;
}
/**
* Checks whether the current version conflicts with the expected version, based on the current version type.
*
* @param currentVersion the current version for the document
* @param expectedVersion the version specified for the write operation
* @param deleted true if the document is currently deleted (note that #currentVersion will typically be
* {@link Versions#NOT_FOUND}, but may be something else if the document was recently deleted
* @return true if versions conflict false o.w.
*/
public abstract boolean isVersionConflictForWrites(long currentVersion, long expectedVersion, boolean deleted);
/**
* Returns a human readable explanation for a version conflict on write.
*
* Note that this method is only called if {@link #isVersionConflictForWrites(long, long, boolean)} returns true;
*
* @param currentVersion the current version for the document
* @param expectedVersion the version specified for the write operation
* @param deleted true if the document is currently deleted (note that #currentVersion will typically be
* {@link Versions#NOT_FOUND}, but may be something else if the document was recently deleted
*/
public abstract String explainConflictForWrites(long currentVersion, long expectedVersion, boolean deleted);
/**
* Checks whether the current version conflicts with the expected version, based on the current version type.
*
* @param currentVersion the current version for the document
* @param expectedVersion the version specified for the read operation
* @return true if versions conflict false o.w.
*/
public abstract boolean isVersionConflictForReads(long currentVersion, long expectedVersion);
/**
* Returns a human readable explanation for a version conflict on read.
*
* Note that this method is only called if {@link #isVersionConflictForReads(long, long)} returns true;
*
* @param currentVersion the current version for the document
* @param expectedVersion the version specified for the read operation
*/
public abstract String explainConflictForReads(long currentVersion, long expectedVersion);
/**
* Returns the new version for a document, based on its current one and the specified in the request
*
* @return new version
*/
public abstract long updateVersion(long currentVersion, long expectedVersion);
/**
* validate the version is a valid value for this type when writing.
*
* @return true if valid, false o.w
*/
public abstract boolean validateVersionForWrites(long version);
/**
* validate the version is a valid value for this type when reading.
*
* @return true if valid, false o.w
*/
public abstract boolean validateVersionForReads(long version);
/**
* Some version types require different semantics for primary and replicas. This version allows
* the type to override the default behavior.
*/
public VersionType versionTypeForReplicationAndRecovery() {
return this;
}
public static VersionType fromString(String versionType) {
if ("internal".equals(versionType)) {
return INTERNAL;
} else if ("external".equals(versionType)) {
return EXTERNAL;
} else if ("external_gt".equals(versionType)) {
return EXTERNAL;
} else if ("external_gte".equals(versionType)) {
return EXTERNAL_GTE;
} else if ("force".equals(versionType)) {
return FORCE;
}
throw new IllegalArgumentException("No version type match [" + versionType + "]");
}
public static VersionType fromString(String versionType, VersionType defaultVersionType) {
if (versionType == null) {
return defaultVersionType;
}
return fromString(versionType);
}
public static VersionType fromValue(byte value) {
if (value == 0) {
return INTERNAL;
} else if (value == 1) {
return EXTERNAL;
} else if (value == 2) {
return EXTERNAL_GTE;
} else if (value == 3) {
return FORCE;
}
throw new IllegalArgumentException("No version type match [" + value + "]");
}
public static VersionType readFromStream(StreamInput in) throws IOException {
return in.readEnum(VersionType.class);
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeEnum(this);
}
}