Skip to content

Commit dbcf2b1

Browse files
committed
Allow 0 as a valid external version
Until now all version types have officially required the version to be a positive long number. Despite of this has being documented, ES versions <=1.0 did not enforce it when using the `external` version type. As a result people have succesfully indexed documents with 0 as a version. In 1.1. we introduced validation checks on incoming version values and causing indexing request to fail if the version was set to 0. While this is strictly speaking OK, we effectively have a situation where data already indexed does not match the version invariant. To be lenient and adhere to spirit of our data backward compatibility policy, we have decided to allow 0 as a valid external version type. This is somewhat complicated as 0 is also the internal value of `MATCH_ANY`, which indicates requests should succeed regardles off the current doc version. To keep things simple, this commit changes the internal value of `MATCH_ANY` to `-3` for all version types. Since we're doing this in a minor release (and because versions are stored in the transaction log), the default `internal` version type still accepts 0 as a `MATCH_ANY` value. This is not a problem for other version types as `MATCH_ANY` doesn't make sense in that context. Closes #5662
1 parent 904da3d commit dbcf2b1

File tree

17 files changed

+140
-42
lines changed

17 files changed

+140
-42
lines changed

docs/reference/docs/index_.asciidoc

+4-3
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ By default, internal versioning is used that starts at 1 and increments
109109
with each update, deletes included. Optionally, the version number can be
110110
supplemented with an external value (for example, if maintained in a
111111
database). To enable this functionality, `version_type` should be set to
112-
`external`. The value provided must be a numeric, long value greater than 0,
112+
`external`. The value provided must be a numeric, long value greater or equal to 0,
113113
and less than around 9.2e+18. When using the external version type, instead
114114
of checking for a matching version number, the system checks to see if
115115
the version number passed to the index request is greater than the
@@ -138,12 +138,13 @@ of the stored document.
138138

139139
`external` or `external_gt`:: only index the document if the given version is strictly higher
140140
than the version of the stored document *or* if there is no existing document. The given
141-
version will be used as the new version and will be stored with the new document.
141+
version will be used as the new version and will be stored with the new document. The supplied
142+
version must be a non-negative long number.
142143

143144
`external_gte`:: only index the document if the given version is *equal* or higher
144145
than the version of the stored document. If there is no existing document
145146
the operation will succeed as well. The given version will be used as the new version
146-
and will be stored with the new document.
147+
and will be stored with the new document. The supplied version must be a non-negative long number.
147148

148149
`force`:: the document will be indexed regardless of the version of the stored document or if there
149150
is no existing document. The given version will be used as the new version and will be stored

rest-api-spec/test/index/35_external_version.yaml

+22-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
---
22
"External version":
33

4+
- do:
5+
index:
6+
index: test_1
7+
type: test
8+
id: 1
9+
body: { foo: bar }
10+
version_type: external
11+
version: 0
12+
13+
- match: { _version: 0 }
14+
415
- do:
516
index:
617
index: test_1
@@ -10,7 +21,7 @@
1021
version_type: external
1122
version: 5
1223

13-
- match: { _version: 5}
24+
- match: { _version: 5 }
1425

1526
- do:
1627
catch: conflict
@@ -22,6 +33,16 @@
2233
version_type: external
2334
version: 5
2435

36+
- do:
37+
catch: conflict
38+
index:
39+
index: test_1
40+
type: test
41+
id: 1
42+
body: { foo: bar }
43+
version_type: external
44+
version: 0
45+
2546
- do:
2647
index:
2748
index: test_1

rest-api-spec/test/index/36_external_gte_version.yaml

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
---
22
"External GTE version":
33

4+
- do:
5+
index:
6+
index: test_1
7+
type: test
8+
id: 1
9+
body: { foo: bar }
10+
version_type: external_gte
11+
version: 0
12+
13+
- match: { _version: 0}
14+
415
- do:
516
index:
617
index: test_1
@@ -20,7 +31,7 @@
2031
id: 1
2132
body: { foo: bar }
2233
version_type: external_gte
23-
version: 4
34+
version: 0
2435

2536
- do:
2637
index:

src/main/java/org/elasticsearch/action/delete/DeleteRequest.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.elasticsearch.common.Nullable;
2525
import org.elasticsearch.common.io.stream.StreamInput;
2626
import org.elasticsearch.common.io.stream.StreamOutput;
27+
import org.elasticsearch.common.lucene.uid.Versions;
2728
import org.elasticsearch.index.VersionType;
2829

2930
import java.io.IOException;
@@ -205,7 +206,7 @@ public void readFrom(StreamInput in) throws IOException {
205206
id = in.readString();
206207
routing = in.readOptionalString();
207208
refresh = in.readBoolean();
208-
version = in.readLong();
209+
version = Versions.readVersion(in);
209210
versionType = VersionType.fromValue(in.readByte());
210211
}
211212

@@ -216,7 +217,7 @@ public void writeTo(StreamOutput out) throws IOException {
216217
out.writeString(id);
217218
out.writeOptionalString(routing());
218219
out.writeBoolean(refresh);
219-
out.writeLong(version);
220+
Versions.writeVersion(version, out);
220221
out.writeByte(versionType.getValue());
221222
}
222223

src/main/java/org/elasticsearch/action/delete/TransportDeleteAction.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ protected boolean resolveRequest(final ClusterState state, final DeleteRequest r
112112
if (request.versionType() != VersionType.INTERNAL) {
113113
// TODO: implement this feature
114114
throw new ElasticsearchIllegalArgumentException("routing value is required for deleting documents of type [" + request.type()
115-
+ "] while using version_type [" + request.versionType());
115+
+ "] while using version_type [" + request.versionType() + "]");
116116
}
117117
indexDeleteAction.execute(new IndexDeleteRequest(request), new ActionListener<IndexDeleteResponse>() {
118118
@Override

src/main/java/org/elasticsearch/action/delete/index/IndexDeleteRequest.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.elasticsearch.action.support.replication.IndexReplicationOperationRequest;
2424
import org.elasticsearch.common.io.stream.StreamInput;
2525
import org.elasticsearch.common.io.stream.StreamOutput;
26+
import org.elasticsearch.common.lucene.uid.Versions;
2627

2728
import java.io.IOException;
2829

@@ -72,7 +73,7 @@ public void readFrom(StreamInput in) throws IOException {
7273
type = in.readString();
7374
id = in.readString();
7475
refresh = in.readBoolean();
75-
version = in.readLong();
76+
version = Versions.readVersion(in);
7677
}
7778

7879
@Override
@@ -81,6 +82,6 @@ public void writeTo(StreamOutput out) throws IOException {
8182
out.writeString(type);
8283
out.writeString(id);
8384
out.writeBoolean(refresh);
84-
out.writeLong(version);
85+
Versions.writeVersion(version, out);
8586
}
8687
}

src/main/java/org/elasticsearch/action/delete/index/ShardDeleteRequest.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import org.elasticsearch.action.support.replication.ShardReplicationOperationRequest;
2424
import org.elasticsearch.common.io.stream.StreamInput;
2525
import org.elasticsearch.common.io.stream.StreamOutput;
26+
import org.elasticsearch.common.lucene.uid.Versions;
2627

2728
import java.io.IOException;
2829

@@ -98,7 +99,7 @@ public void readFrom(StreamInput in) throws IOException {
9899
type = in.readString();
99100
id = in.readString();
100101
refresh = in.readBoolean();
101-
version = in.readLong();
102+
version = Versions.readVersion(in);
102103
}
103104

104105
@Override
@@ -108,6 +109,6 @@ public void writeTo(StreamOutput out) throws IOException {
108109
out.writeString(type);
109110
out.writeString(id);
110111
out.writeBoolean(refresh);
111-
out.writeLong(version);
112+
Versions.writeVersion(version, out);
112113
}
113114
}

src/main/java/org/elasticsearch/action/get/GetRequest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ public void readFrom(StreamInput in) throws IOException {
267267
}
268268

269269
this.versionType = VersionType.fromValue(in.readByte());
270-
this.version = in.readVLong();
270+
this.version = Versions.readVersionWithVLongForBW(in);
271271

272272
fetchSourceContext = FetchSourceContext.optionalReadFromStream(in);
273273
}
@@ -298,7 +298,7 @@ public void writeTo(StreamOutput out) throws IOException {
298298
}
299299

300300
out.writeByte(versionType.getValue());
301-
out.writeVLong(version);
301+
Versions.writeVersionWithVLongForBW(version, out);
302302

303303
FetchSourceContext.optionalWriteToStream(fetchSourceContext, out);
304304
}

src/main/java/org/elasticsearch/action/get/MultiGetRequest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ public void readFrom(StreamInput in) throws IOException {
169169
fields[i] = in.readString();
170170
}
171171
}
172-
version = in.readVLong();
172+
version = Versions.readVersionWithVLongForBW(in);
173173
versionType = VersionType.fromValue(in.readByte());
174174

175175
fetchSourceContext = FetchSourceContext.optionalReadFromStream(in);
@@ -190,7 +190,7 @@ public void writeTo(StreamOutput out) throws IOException {
190190
}
191191
}
192192

193-
out.writeVLong(version);
193+
Versions.writeVersionWithVLongForBW(version, out);
194194
out.writeByte(versionType.getValue());
195195

196196
FetchSourceContext.optionalWriteToStream(fetchSourceContext, out);

src/main/java/org/elasticsearch/action/get/MultiGetShardRequest.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.elasticsearch.common.Nullable;
2626
import org.elasticsearch.common.io.stream.StreamInput;
2727
import org.elasticsearch.common.io.stream.StreamOutput;
28+
import org.elasticsearch.common.lucene.uid.Versions;
2829
import org.elasticsearch.index.VersionType;
2930
import org.elasticsearch.search.fetch.source.FetchSourceContext;
3031

@@ -138,7 +139,7 @@ public void readFrom(StreamInput in) throws IOException {
138139
} else {
139140
fields.add(null);
140141
}
141-
versions.add(in.readVLong());
142+
versions.add(Versions.readVersionWithVLongForBW(in));
142143
versionTypes.add(VersionType.fromValue(in.readByte()));
143144

144145
fetchSourceContexts.add(FetchSourceContext.optionalReadFromStream(in));
@@ -175,7 +176,7 @@ public void writeTo(StreamOutput out) throws IOException {
175176
out.writeString(field);
176177
}
177178
}
178-
out.writeVLong(versions.get(i));
179+
Versions.writeVersionWithVLongForBW(versions.get(i), out);
179180
out.writeByte(versionTypes.get(i).getValue());
180181
FetchSourceContext fetchSourceContext = fetchSourceContexts.get(i);
181182
FetchSourceContext.optionalWriteToStream(fetchSourceContext, out);

src/main/java/org/elasticsearch/action/index/IndexRequest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -632,7 +632,7 @@ public void readFrom(StreamInput in) throws IOException {
632632

633633
opType = OpType.fromId(in.readByte());
634634
refresh = in.readBoolean();
635-
version = in.readLong();
635+
version = Versions.readVersion(in);
636636
versionType = VersionType.fromValue(in.readByte());
637637
if (in.getVersion().onOrAfter(Version.V_1_2_0)) {
638638
autoGeneratedId = in.readBoolean();
@@ -651,7 +651,7 @@ public void writeTo(StreamOutput out) throws IOException {
651651
out.writeBytesReference(source);
652652
out.writeByte(opType.id());
653653
out.writeBoolean(refresh);
654-
out.writeLong(version);
654+
Versions.writeVersion(version, out);
655655
out.writeByte(versionType.getValue());
656656
if (out.getVersion().onOrAfter(Version.V_1_2_0)) {
657657
out.writeBoolean(autoGeneratedId);

src/main/java/org/elasticsearch/action/update/UpdateRequest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,7 @@ public void readFrom(StreamInput in) throws IOException {
609609
upsertRequest.readFrom(in);
610610
}
611611
docAsUpsert = in.readBoolean();
612-
version = in.readLong();
612+
version = Versions.readVersion(in);
613613
versionType = VersionType.fromValue(in.readByte());
614614
}
615615

@@ -655,7 +655,7 @@ public void writeTo(StreamOutput out) throws IOException {
655655
upsertRequest.writeTo(out);
656656
}
657657
out.writeBoolean(docAsUpsert);
658-
out.writeLong(version);
658+
Versions.writeVersion(version, out);
659659
out.writeByte(versionType.getValue());
660660
}
661661

src/main/java/org/elasticsearch/common/lucene/uid/Versions.java

+49-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@
2222
import org.apache.lucene.index.*;
2323
import org.apache.lucene.util.Bits;
2424
import org.apache.lucene.util.BytesRef;
25+
import org.elasticsearch.Version;
2526
import org.elasticsearch.common.Numbers;
27+
import org.elasticsearch.common.io.stream.StreamInput;
28+
import org.elasticsearch.common.io.stream.StreamOutput;
2629
import org.elasticsearch.index.mapper.internal.UidFieldMapper;
2730
import org.elasticsearch.index.mapper.internal.VersionFieldMapper;
2831

@@ -32,11 +35,55 @@
3235
/** Utility class to resolve the Lucene doc ID and version for a given uid. */
3336
public class Versions {
3437

35-
public static final long MATCH_ANY = 0L; // Version was not specified by the user
38+
public static final long MATCH_ANY = -3L; // Version was not specified by the user
39+
// the value for MATCH_ANY before ES 1.2.0 - will be removed
40+
public static final long MATCH_ANY_PRE_1_2_0 = 0L;
3641
public static final long NOT_FOUND = -1L;
3742
public static final long NOT_SET = -2L;
3843

39-
private Versions() {}
44+
public static void writeVersion(long version, StreamOutput out) throws IOException {
45+
if (out.getVersion().before(Version.V_1_2_0) && version == MATCH_ANY) {
46+
// we have to send out a value the node will understand
47+
version = MATCH_ANY_PRE_1_2_0;
48+
}
49+
out.writeLong(version);
50+
}
51+
52+
public static long readVersion(StreamInput in) throws IOException {
53+
long version = in.readLong();
54+
if (in.getVersion().before(Version.V_1_2_0) && version == MATCH_ANY_PRE_1_2_0) {
55+
version = MATCH_ANY;
56+
}
57+
return version;
58+
}
59+
60+
public static void writeVersionWithVLongForBW(long version, StreamOutput out) throws IOException {
61+
if (out.getVersion().onOrAfter(Version.V_1_2_0)) {
62+
out.writeLong(version);
63+
return;
64+
}
65+
66+
if (version == MATCH_ANY) {
67+
// we have to send out a value the node will understand
68+
version = MATCH_ANY_PRE_1_2_0;
69+
}
70+
out.writeVLong(version);
71+
}
72+
73+
public static long readVersionWithVLongForBW(StreamInput in) throws IOException {
74+
if (in.getVersion().onOrAfter(Version.V_1_2_0)) {
75+
return in.readLong();
76+
} else {
77+
long version = in.readVLong();
78+
if (version == MATCH_ANY_PRE_1_2_0) {
79+
return MATCH_ANY;
80+
}
81+
return version;
82+
}
83+
}
84+
85+
private Versions() {
86+
}
4087

4188
/** Wraps an {@link AtomicReaderContext}, a doc ID <b>relative to the context doc base</b> and a version. */
4289
public static class DocIdAndVersion {

0 commit comments

Comments
 (0)