/* * 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); } }