blob: c3e51d3099fadc9bedf8a597e6217c61ca6c0397 [file] [log] [blame]
/*
* 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.lucene.index;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Random;
import java.util.TimeZone;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.lucene.analysis.MockAnalyzer;
import org.apache.lucene.codecs.Codec;
import org.apache.lucene.document.BinaryDocValuesField;
import org.apache.lucene.document.BinaryPoint;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.DoubleDocValuesField;
import org.apache.lucene.document.DoublePoint;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.document.FloatDocValuesField;
import org.apache.lucene.document.FloatPoint;
import org.apache.lucene.document.IntPoint;
import org.apache.lucene.document.LongPoint;
import org.apache.lucene.document.NumericDocValuesField;
import org.apache.lucene.document.SortedDocValuesField;
import org.apache.lucene.document.SortedNumericDocValuesField;
import org.apache.lucene.document.SortedSetDocValuesField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.search.DocIdSetIterator;
import org.apache.lucene.search.DocValuesFieldExistsQuery;
import org.apache.lucene.search.FieldDoc;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.NormsFieldExistsQuery;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.Sort;
import org.apache.lucene.search.SortField;
import org.apache.lucene.search.TermQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.BaseDirectoryWrapper;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.store.NIOFSDirectory;
import org.apache.lucene.store.RAMDirectory;
import org.apache.lucene.store.SimpleFSDirectory;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.IOUtils;
import org.apache.lucene.util.InfoStream;
import org.apache.lucene.util.LineFileDocs;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.util.TestUtil;
import org.apache.lucene.util.Version;
import org.junit.AfterClass;
import org.junit.BeforeClass;
/*
Verify we can read previous versions' indexes, do searches
against them, and add documents to them.
*/
// See: https://issues.apache.org/jira/browse/SOLR-12028 Tests cannot remove files on Windows machines occasionally
public class TestBackwardsCompatibility extends LuceneTestCase {
// Backcompat index generation, described below, is mostly automated in:
//
// dev-tools/scripts/addBackcompatIndexes.py
//
// For usage information, see:
//
// http://wiki.apache.org/lucene-java/ReleaseTodo#Generate_Backcompat_Indexes
//
// -----
//
// To generate backcompat indexes with the current default codec, run the following ant command:
// ant test -Dtestcase=TestBackwardsCompatibility -Dtests.bwcdir=/path/to/store/indexes
// -Dtests.codec=default -Dtests.useSecurityManager=false
// Also add testmethod with one of the index creation methods below, for example:
// -Dtestmethod=testCreateCFS
//
// Zip up the generated indexes:
//
// cd /path/to/store/indexes/index.cfs ; zip index.<VERSION>-cfs.zip *
// cd /path/to/store/indexes/index.nocfs ; zip index.<VERSION>-nocfs.zip *
//
// Then move those 2 zip files to your trunk checkout and add them
// to the oldNames array.
public void testCreateCFS() throws IOException {
createIndex("index.cfs", true, false);
}
public void testCreateNoCFS() throws IOException {
createIndex("index.nocfs", false, false);
}
// These are only needed for the special upgrade test to verify
// that also single-segment indexes are correctly upgraded by IndexUpgrader.
// You don't need them to be build for non-4.0 (the test is happy with just one
// "old" segment format, version is unimportant:
public void testCreateSingleSegmentCFS() throws IOException {
createIndex("index.singlesegment-cfs", true, true);
}
public void testCreateSingleSegmentNoCFS() throws IOException {
createIndex("index.singlesegment-nocfs", false, true);
}
private Path getIndexDir() {
String path = System.getProperty("tests.bwcdir");
assumeTrue("backcompat creation tests must be run with -Dtests.bwcdir=/path/to/write/indexes", path != null);
return Paths.get(path);
}
public void testCreateMoreTermsIndex() throws Exception {
Path indexDir = getIndexDir().resolve("moreterms");
Files.deleteIfExists(indexDir);
Directory dir = newFSDirectory(indexDir);
LogByteSizeMergePolicy mp = new LogByteSizeMergePolicy();
mp.setNoCFSRatio(1.0);
mp.setMaxCFSSegmentSizeMB(Double.POSITIVE_INFINITY);
MockAnalyzer analyzer = new MockAnalyzer(random());
analyzer.setMaxTokenLength(TestUtil.nextInt(random(), 1, IndexWriter.MAX_TERM_LENGTH));
IndexWriterConfig conf = new IndexWriterConfig(analyzer)
.setMergePolicy(mp).setUseCompoundFile(false);
IndexWriter writer = new IndexWriter(dir, conf);
LineFileDocs docs = new LineFileDocs(new Random(0));
for(int i=0;i<50;i++) {
writer.addDocument(docs.nextDoc());
}
docs.close();
writer.close();
dir.close();
// Gives you time to copy the index out!: (there is also
// a test option to not remove temp dir...):
Thread.sleep(100000);
}
// ant test -Dtestcase=TestBackwardsCompatibility -Dtestmethod=testCreateSortedIndex -Dtests.codec=default -Dtests.useSecurityManager=false -Dtests.bwcdir=/tmp/sorted
public void testCreateSortedIndex() throws Exception {
Path indexDir = getIndexDir().resolve("sorted");
Files.deleteIfExists(indexDir);
Directory dir = newFSDirectory(indexDir);
LogByteSizeMergePolicy mp = new LogByteSizeMergePolicy();
mp.setNoCFSRatio(1.0);
mp.setMaxCFSSegmentSizeMB(Double.POSITIVE_INFINITY);
MockAnalyzer analyzer = new MockAnalyzer(random());
analyzer.setMaxTokenLength(TestUtil.nextInt(random(), 1, IndexWriter.MAX_TERM_LENGTH));
// TODO: remove randomness
IndexWriterConfig conf = new IndexWriterConfig(analyzer);
conf.setMergePolicy(mp);
conf.setUseCompoundFile(false);
conf.setIndexSort(new Sort(new SortField("dateDV", SortField.Type.LONG, true)));
IndexWriter writer = new IndexWriter(dir, conf);
LineFileDocs docs = new LineFileDocs(random());
SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd", Locale.ROOT);
parser.setTimeZone(TimeZone.getTimeZone("UTC"));
ParsePosition position = new ParsePosition(0);
Field dateDVField = null;
for(int i=0;i<50;i++) {
Document doc = docs.nextDoc();
String dateString = doc.get("date");
position.setIndex(0);
Date date = parser.parse(dateString, position);
if (position.getErrorIndex() != -1) {
throw new AssertionError("failed to parse \"" + dateString + "\" as date");
}
if (position.getIndex() != dateString.length()) {
throw new AssertionError("failed to parse \"" + dateString + "\" as date");
}
if (dateDVField == null) {
dateDVField = new NumericDocValuesField("dateDV", 0l);
doc.add(dateDVField);
}
dateDVField.setLongValue(date.getTime());
if (i == 250) {
writer.commit();
}
writer.addDocument(doc);
}
writer.forceMerge(1);
writer.close();
dir.close();
}
private void updateNumeric(IndexWriter writer, String id, String f, String cf, long value) throws IOException {
writer.updateNumericDocValue(new Term("id", id), f, value);
writer.updateNumericDocValue(new Term("id", id), cf, value*2);
}
private void updateBinary(IndexWriter writer, String id, String f, String cf, long value) throws IOException {
writer.updateBinaryDocValue(new Term("id", id), f, toBytes(value));
writer.updateBinaryDocValue(new Term("id", id), cf, toBytes(value*2));
}
// Creates an index with DocValues updates
public void testCreateIndexWithDocValuesUpdates() throws Exception {
Path indexDir = getIndexDir().resolve("dvupdates");
Files.deleteIfExists(indexDir);
Directory dir = newFSDirectory(indexDir);
IndexWriterConfig conf = new IndexWriterConfig(new MockAnalyzer(random()))
.setUseCompoundFile(false).setMergePolicy(NoMergePolicy.INSTANCE);
IndexWriter writer = new IndexWriter(dir, conf);
// create an index w/ few doc-values fields, some with updates and some without
for (int i = 0; i < 30; i++) {
Document doc = new Document();
doc.add(new StringField("id", "" + i, Field.Store.NO));
doc.add(new NumericDocValuesField("ndv1", i));
doc.add(new NumericDocValuesField("ndv1_c", i*2));
doc.add(new NumericDocValuesField("ndv2", i*3));
doc.add(new NumericDocValuesField("ndv2_c", i*6));
doc.add(new BinaryDocValuesField("bdv1", toBytes(i)));
doc.add(new BinaryDocValuesField("bdv1_c", toBytes(i*2)));
doc.add(new BinaryDocValuesField("bdv2", toBytes(i*3)));
doc.add(new BinaryDocValuesField("bdv2_c", toBytes(i*6)));
writer.addDocument(doc);
if ((i+1) % 10 == 0) {
writer.commit(); // flush every 10 docs
}
}
// first segment: no updates
// second segment: update two fields, same gen
updateNumeric(writer, "10", "ndv1", "ndv1_c", 100L);
updateBinary(writer, "11", "bdv1", "bdv1_c", 100L);
writer.commit();
// third segment: update few fields, different gens, few docs
updateNumeric(writer, "20", "ndv1", "ndv1_c", 100L);
updateBinary(writer, "21", "bdv1", "bdv1_c", 100L);
writer.commit();
updateNumeric(writer, "22", "ndv1", "ndv1_c", 200L); // update the field again
writer.commit();
writer.close();
dir.close();
}
public void testCreateEmptyIndex() throws Exception {
Path indexDir = getIndexDir().resolve("emptyIndex");
Files.deleteIfExists(indexDir);
IndexWriterConfig conf = new IndexWriterConfig(new MockAnalyzer(random()))
.setUseCompoundFile(false).setMergePolicy(NoMergePolicy.INSTANCE);
try (Directory dir = newFSDirectory(indexDir);
IndexWriter writer = new IndexWriter(dir, conf)) {
writer.flush();
}
}
final static String[] oldNames = {
"7.0.0-cfs",
"7.0.0-nocfs",
"7.0.1-cfs",
"7.0.1-nocfs",
"7.1.0-cfs",
"7.1.0-nocfs",
"7.2.0-cfs",
"7.2.0-nocfs",
"7.2.1-cfs",
"7.2.1-nocfs",
"7.3.0-cfs",
"7.3.0-nocfs",
"7.3.1-cfs",
"7.3.1-nocfs",
"7.4.0-cfs",
"7.4.0-nocfs",
"7.5.0-cfs",
"7.5.0-nocfs",
"7.6.0-cfs",
"7.6.0-nocfs",
"7.7.0-cfs",
"7.7.0-nocfs",
"7.7.1-cfs",
"7.7.1-nocfs",
"7.7.2-cfs",
"7.7.2-nocfs",
"7.7.3-cfs",
"7.7.3-nocfs",
"8.0.0-cfs",
"8.0.0-nocfs",
"8.1.0-cfs",
"8.1.0-nocfs",
"8.1.1-cfs",
"8.1.1-nocfs",
"8.2.0-cfs",
"8.2.0-nocfs",
"8.3.0-cfs",
"8.3.0-nocfs",
"8.3.1-cfs",
"8.3.1-nocfs",
"8.4.0-cfs",
"8.4.0-nocfs",
"8.4.1-cfs",
"8.4.1-nocfs",
"8.5.0-cfs",
"8.5.0-nocfs",
"8.5.1-cfs",
"8.5.1-nocfs",
"8.5.2-cfs",
"8.5.2-nocfs",
"8.6.0-cfs",
"8.6.0-nocfs",
"8.6.1-cfs",
"8.6.1-nocfs",
"8.6.2-cfs",
"8.6.2-nocfs",
"8.6.3-cfs",
"8.6.3-nocfs",
"8.7.0-cfs",
"8.7.0-nocfs",
"8.8.0-cfs",
"8.8.0-nocfs",
"8.8.1-cfs",
"8.8.1-nocfs",
"8.8.2-cfs",
"8.8.2-nocfs",
"8.9.0-cfs",
"8.9.0-nocfs",
"8.10.0-cfs",
"8.10.0-nocfs",
"8.10.1-cfs",
"8.10.1-nocfs"
};
public static String[] getOldNames() {
return oldNames;
}
final static String[] oldSortedNames = {
"sorted.7.0.0",
"sorted.7.0.1",
"sorted.7.1.0",
"sorted.7.2.0",
"sorted.7.2.1",
"sorted.7.3.0",
"sorted.7.3.1",
"sorted.7.4.0",
"sorted.7.5.0",
"sorted.7.6.0",
"sorted.7.7.0",
"sorted.7.7.1",
"sorted.7.7.2",
"sorted.7.7.3",
"sorted.8.0.0",
"sorted.8.1.0",
"sorted.8.1.1",
"sorted.8.2.0",
"sorted.8.3.0",
"sorted.8.3.1",
"sorted.8.4.0",
"sorted.8.4.1",
"sorted.8.5.0",
"sorted.8.5.1",
"sorted.8.5.2",
"sorted.8.6.0",
"sorted.8.6.1",
"sorted.8.6.2",
"sorted.8.6.3",
"sorted.8.7.0",
"sorted.8.8.0",
"sorted.8.8.1",
"sorted.8.8.2",
"sorted.8.9.0",
"sorted.8.10.0",
"sorted.8.10.1"
};
public static String[] getOldSortedNames() {
return oldSortedNames;
}
final String[] unsupportedNames = {
"1.9.0-cfs",
"1.9.0-nocfs",
"2.0.0-cfs",
"2.0.0-nocfs",
"2.1.0-cfs",
"2.1.0-nocfs",
"2.2.0-cfs",
"2.2.0-nocfs",
"2.3.0-cfs",
"2.3.0-nocfs",
"2.4.0-cfs",
"2.4.0-nocfs",
"2.4.1-cfs",
"2.4.1-nocfs",
"2.9.0-cfs",
"2.9.0-nocfs",
"2.9.1-cfs",
"2.9.1-nocfs",
"2.9.2-cfs",
"2.9.2-nocfs",
"2.9.3-cfs",
"2.9.3-nocfs",
"2.9.4-cfs",
"2.9.4-nocfs",
"3.0.0-cfs",
"3.0.0-nocfs",
"3.0.1-cfs",
"3.0.1-nocfs",
"3.0.2-cfs",
"3.0.2-nocfs",
"3.0.3-cfs",
"3.0.3-nocfs",
"3.1.0-cfs",
"3.1.0-nocfs",
"3.2.0-cfs",
"3.2.0-nocfs",
"3.3.0-cfs",
"3.3.0-nocfs",
"3.4.0-cfs",
"3.4.0-nocfs",
"3.5.0-cfs",
"3.5.0-nocfs",
"3.6.0-cfs",
"3.6.0-nocfs",
"3.6.1-cfs",
"3.6.1-nocfs",
"3.6.2-cfs",
"3.6.2-nocfs",
"4.0.0-cfs",
"4.0.0-nocfs",
"4.0.0.1-cfs",
"4.0.0.1-nocfs",
"4.0.0.2-cfs",
"4.0.0.2-nocfs",
"4.1.0-cfs",
"4.1.0-nocfs",
"4.2.0-cfs",
"4.2.0-nocfs",
"4.2.1-cfs",
"4.2.1-nocfs",
"4.3.0-cfs",
"4.3.0-nocfs",
"4.3.1-cfs",
"4.3.1-nocfs",
"4.4.0-cfs",
"4.4.0-nocfs",
"4.5.0-cfs",
"4.5.0-nocfs",
"4.5.1-cfs",
"4.5.1-nocfs",
"4.6.0-cfs",
"4.6.0-nocfs",
"4.6.1-cfs",
"4.6.1-nocfs",
"4.7.0-cfs",
"4.7.0-nocfs",
"4.7.1-cfs",
"4.7.1-nocfs",
"4.7.2-cfs",
"4.7.2-nocfs",
"4.8.0-cfs",
"4.8.0-nocfs",
"4.8.1-cfs",
"4.8.1-nocfs",
"4.9.0-cfs",
"4.9.0-nocfs",
"4.9.1-cfs",
"4.9.1-nocfs",
"4.10.0-cfs",
"4.10.0-nocfs",
"4.10.1-cfs",
"4.10.1-nocfs",
"4.10.2-cfs",
"4.10.2-nocfs",
"4.10.3-cfs",
"4.10.3-nocfs",
"4.10.4-cfs",
"4.10.4-nocfs",
"5x-with-4x-segments-cfs",
"5x-with-4x-segments-nocfs",
"5.0.0.singlesegment-cfs",
"5.0.0.singlesegment-nocfs",
"5.0.0-cfs",
"5.0.0-nocfs",
"5.1.0-cfs",
"5.1.0-nocfs",
"5.2.0-cfs",
"5.2.0-nocfs",
"5.2.1-cfs",
"5.2.1-nocfs",
"5.3.0-cfs",
"5.3.0-nocfs",
"5.3.1-cfs",
"5.3.1-nocfs",
"5.3.2-cfs",
"5.3.2-nocfs",
"5.4.0-cfs",
"5.4.0-nocfs",
"5.4.1-cfs",
"5.4.1-nocfs",
"5.5.0-cfs",
"5.5.0-nocfs",
"5.5.1-cfs",
"5.5.1-nocfs",
"5.5.2-cfs",
"5.5.2-nocfs",
"5.5.3-cfs",
"5.5.3-nocfs",
"5.5.4-cfs",
"5.5.4-nocfs",
"5.5.5-cfs",
"5.5.5-nocfs",
"6.0.0-cfs",
"6.0.0-nocfs",
"6.0.1-cfs",
"6.0.1-nocfs",
"6.1.0-cfs",
"6.1.0-nocfs",
"6.2.0-cfs",
"6.2.0-nocfs",
"6.2.1-cfs",
"6.2.1-nocfs",
"6.3.0-cfs",
"6.3.0-nocfs",
"6.4.0-cfs",
"6.4.0-nocfs",
"6.4.1-cfs",
"6.4.1-nocfs",
"6.4.2-cfs",
"6.4.2-nocfs",
"6.5.0-cfs",
"6.5.0-nocfs",
"6.5.1-cfs",
"6.5.1-nocfs",
"6.6.0-cfs",
"6.6.0-nocfs",
"6.6.1-cfs",
"6.6.1-nocfs",
"6.6.2-cfs",
"6.6.2-nocfs",
"6.6.3-cfs",
"6.6.3-nocfs",
"6.6.4-cfs",
"6.6.4-nocfs",
"6.6.5-cfs",
"6.6.5-nocfs",
"6.6.6-cfs",
"6.6.6-nocfs"
};
// TODO: on 6.0.0 release, gen the single segment indices and add here:
final static String[] oldSingleSegmentNames = {
};
public static String[] getOldSingleSegmentNames() {
return oldSingleSegmentNames;
}
static Map<String,Directory> oldIndexDirs;
/**
* Randomizes the use of some of hte constructor variations
*/
private static IndexUpgrader newIndexUpgrader(Directory dir) {
final boolean streamType = random().nextBoolean();
final int choice = TestUtil.nextInt(random(), 0, 2);
switch (choice) {
case 0: return new IndexUpgrader(dir);
case 1: return new IndexUpgrader(dir, streamType ? null : InfoStream.NO_OUTPUT, false);
case 2: return new IndexUpgrader(dir, newIndexWriterConfig(null), false);
default: fail("case statement didn't get updated when random bounds changed");
}
return null; // never get here
}
@BeforeClass
public static void beforeClass() throws Exception {
List<String> names = new ArrayList<>(oldNames.length + oldSingleSegmentNames.length);
names.addAll(Arrays.asList(oldNames));
names.addAll(Arrays.asList(oldSingleSegmentNames));
oldIndexDirs = new HashMap<>();
for (String name : names) {
Path dir = createTempDir(name);
InputStream resource = TestBackwardsCompatibility.class.getResourceAsStream("index." + name + ".zip");
assertNotNull("Index name " + name + " not found", resource);
TestUtil.unzip(resource, dir);
oldIndexDirs.put(name, newFSDirectory(dir));
}
}
@AfterClass
public static void afterClass() throws Exception {
for (Directory d : oldIndexDirs.values()) {
d.close();
}
oldIndexDirs = null;
}
public void testAllVersionHaveCfsAndNocfs() {
// ensure all tested versions with cfs also have nocfs
String[] files = new String[oldNames.length];
System.arraycopy(oldNames, 0, files, 0, oldNames.length);
Arrays.sort(files);
String prevFile = "";
for (String file : files) {
if (prevFile.endsWith("-cfs")) {
String prefix = prevFile.replace("-cfs", "");
assertEquals("Missing -nocfs for backcompat index " + prefix, prefix + "-nocfs", file);
}
}
}
public void testAllVersionsTested() throws Exception {
Pattern constantPattern = Pattern.compile("LUCENE_(\\d+)_(\\d+)_(\\d+)(_ALPHA|_BETA)?");
// find the unique versions according to Version.java
List<String> expectedVersions = new ArrayList<>();
for (java.lang.reflect.Field field : Version.class.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers()) && field.getType() == Version.class) {
Version v = (Version)field.get(Version.class);
if (v.equals(Version.LATEST)) {
continue;
}
Matcher constant = constantPattern.matcher(field.getName());
if (constant.matches() == false) {
continue;
}
expectedVersions.add(v.toString() + "-cfs");
}
}
// BEGIN TRUNK ONLY BLOCK
// on trunk, the last release of the prev major release is also untested
Version lastPrevMajorVersion = null;
for (java.lang.reflect.Field field : Version.class.getDeclaredFields()) {
if (Modifier.isStatic(field.getModifiers()) && field.getType() == Version.class) {
Version v = (Version)field.get(Version.class);
Matcher constant = constantPattern.matcher(field.getName());
if (constant.matches() == false) continue;
if (v.major == Version.LATEST.major - 1 &&
(lastPrevMajorVersion == null || v.onOrAfter(lastPrevMajorVersion))) {
lastPrevMajorVersion = v;
}
}
}
assertNotNull(lastPrevMajorVersion);
expectedVersions.remove(lastPrevMajorVersion.toString() + "-cfs");
// END TRUNK ONLY BLOCK
Collections.sort(expectedVersions);
// find what versions we are testing
List<String> testedVersions = new ArrayList<>();
for (String testedVersion : oldNames) {
if (testedVersion.endsWith("-cfs") == false) {
continue;
}
testedVersions.add(testedVersion);
}
Collections.sort(testedVersions);
int i = 0;
int j = 0;
List<String> missingFiles = new ArrayList<>();
List<String> extraFiles = new ArrayList<>();
while (i < expectedVersions.size() && j < testedVersions.size()) {
String expectedVersion = expectedVersions.get(i);
String testedVersion = testedVersions.get(j);
int compare = expectedVersion.compareTo(testedVersion);
if (compare == 0) { // equal, we can move on
++i;
++j;
} else if (compare < 0) { // didn't find test for version constant
missingFiles.add(expectedVersion);
++i;
} else { // extra test file
extraFiles.add(testedVersion);
++j;
}
}
while (i < expectedVersions.size()) {
missingFiles.add(expectedVersions.get(i));
++i;
}
while (j < testedVersions.size()) {
missingFiles.add(testedVersions.get(j));
++j;
}
// we could be missing up to 1 file, which may be due to a release that is in progress
if (missingFiles.size() <= 1 && extraFiles.isEmpty()) {
// success
return;
}
StringBuffer msg = new StringBuffer();
if (missingFiles.size() > 1) {
msg.append("Missing backcompat test files:\n");
for (String missingFile : missingFiles) {
msg.append(" " + missingFile + "\n");
}
}
if (extraFiles.isEmpty() == false) {
msg.append("Extra backcompat test files:\n");
for (String extraFile : extraFiles) {
msg.append(" " + extraFile + "\n");
}
}
fail(msg.toString());
}
/** This test checks that *only* IndexFormatTooOldExceptions are thrown when you open and operate on too old indexes! */
public void testUnsupportedOldIndexes() throws Exception {
for(int i=0;i<unsupportedNames.length;i++) {
if (VERBOSE) {
System.out.println("TEST: index " + unsupportedNames[i]);
}
Path oldIndexDir = createTempDir(unsupportedNames[i]);
TestUtil.unzip(getDataInputStream("unsupported." + unsupportedNames[i] + ".zip"), oldIndexDir);
BaseDirectoryWrapper dir = newFSDirectory(oldIndexDir);
// don't checkindex, these are intentionally not supported
dir.setCheckIndexOnClose(false);
IndexReader reader = null;
IndexWriter writer = null;
try {
reader = DirectoryReader.open(dir);
fail("DirectoryReader.open should not pass for "+unsupportedNames[i]);
} catch (IndexFormatTooOldException e) {
if (e.getReason() != null) {
assertNull(e.getVersion());
assertNull(e.getMinVersion());
assertNull(e.getMaxVersion());
assertEquals(e.getMessage(), new IndexFormatTooOldException(e.getResourceDescription(), e.getReason()).getMessage());
} else {
assertNotNull(e.getVersion());
assertNotNull(e.getMinVersion());
assertNotNull(e.getMaxVersion());
assertTrue(e.getMessage(), e.getMaxVersion() >= e.getMinVersion());
assertTrue(e.getMessage(), e.getMaxVersion() < e.getVersion() || e.getVersion() < e.getMinVersion());
assertEquals(e.getMessage(), new IndexFormatTooOldException(e.getResourceDescription(), e.getVersion(), e.getMinVersion(), e.getMaxVersion()).getMessage());
}
// pass
if (VERBOSE) {
System.out.println("TEST: got expected exc:");
e.printStackTrace(System.out);
}
} finally {
if (reader != null) reader.close();
reader = null;
}
try {
writer = new IndexWriter(dir, newIndexWriterConfig(new MockAnalyzer(random())).setCommitOnClose(false));
fail("IndexWriter creation should not pass for "+unsupportedNames[i]);
} catch (IndexFormatTooOldException e) {
if (e.getReason() != null) {
assertNull(e.getVersion());
assertNull(e.getMinVersion());
assertNull(e.getMaxVersion());
assertEquals(e.getMessage(), new IndexFormatTooOldException(e.getResourceDescription(), e.getReason()).getMessage());
} else {
assertNotNull(e.getVersion());
assertNotNull(e.getMinVersion());
assertNotNull(e.getMaxVersion());
assertTrue(e.getMessage(), e.getMaxVersion() >= e.getMinVersion());
assertTrue(e.getMessage(), e.getMaxVersion() < e.getVersion() || e.getVersion() < e.getMinVersion());
assertEquals(e.getMessage(), new IndexFormatTooOldException(e.getResourceDescription(), e.getVersion(), e.getMinVersion(), e.getMaxVersion()).getMessage());
}
// pass
if (VERBOSE) {
System.out.println("TEST: got expected exc:");
e.printStackTrace(System.out);
}
// Make sure exc message includes a path=
assertTrue("got exc message: " + e.getMessage(), e.getMessage().indexOf("path=\"") != -1);
} finally {
// we should fail to open IW, and so it should be null when we get here.
// However, if the test fails (i.e., IW did not fail on open), we need
// to close IW. However, if merges are run, IW may throw
// IndexFormatTooOldException, and we don't want to mask the fail()
// above, so close without waiting for merges.
if (writer != null) {
try {
writer.commit();
} finally {
writer.close();
}
}
}
ByteArrayOutputStream bos = new ByteArrayOutputStream(1024);
CheckIndex checker = new CheckIndex(dir);
checker.setInfoStream(new PrintStream(bos, false, IOUtils.UTF_8));
CheckIndex.Status indexStatus = checker.checkIndex();
assertFalse(indexStatus.clean);
assertTrue(bos.toString(IOUtils.UTF_8).contains(IndexFormatTooOldException.class.getName()));
checker.close();
dir.close();
}
}
public void testFullyMergeOldIndex() throws Exception {
for (String name : oldNames) {
if (VERBOSE) {
System.out.println("\nTEST: index=" + name);
}
Directory dir = newDirectory(oldIndexDirs.get(name));
final SegmentInfos oldSegInfos = SegmentInfos.readLatestCommit(dir);
IndexWriter w = new IndexWriter(dir, new IndexWriterConfig(new MockAnalyzer(random())));
w.forceMerge(1);
w.close();
final SegmentInfos segInfos = SegmentInfos.readLatestCommit(dir);
assertEquals(oldSegInfos.getIndexCreatedVersionMajor(), segInfos.getIndexCreatedVersionMajor());
assertEquals(Version.LATEST, segInfos.asList().get(0).info.getVersion());
assertEquals(oldSegInfos.asList().get(0).info.getMinVersion(), segInfos.asList().get(0).info.getMinVersion());
dir.close();
}
}
public void testAddOldIndexes() throws IOException {
for (String name : oldNames) {
if (VERBOSE) {
System.out.println("\nTEST: old index " + name);
}
Directory oldDir = oldIndexDirs.get(name);
SegmentInfos infos = SegmentInfos.readLatestCommit(oldDir);
Directory targetDir = newDirectory();
if (infos.getCommitLuceneVersion().major != Version.LATEST.major) {
// both indexes are not compatible
Directory targetDir2 = newDirectory();
IndexWriter w = new IndexWriter(targetDir2, newIndexWriterConfig(new MockAnalyzer(random())));
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> w.addIndexes(oldDir));
assertTrue(e.getMessage(), e.getMessage().startsWith("Cannot use addIndexes(Directory) with indexes that have been created by a different Lucene version."));
w.close();
targetDir2.close();
// for the next test, we simulate writing to an index that was created on the same major version
new SegmentInfos(infos.getIndexCreatedVersionMajor()).commit(targetDir);
}
IndexWriter w = new IndexWriter(targetDir, newIndexWriterConfig(new MockAnalyzer(random())));
w.addIndexes(oldDir);
w.close();
SegmentInfos si = SegmentInfos.readLatestCommit(targetDir);
assertNull("none of the segments should have been upgraded",
si.asList().stream().filter( // depending on the MergePolicy we might see these segments merged away
sci -> sci.getId() != null && sci.info.getVersion().onOrAfter(Version.LUCENE_8_6_0) == false
).findAny().orElse(null));
if (VERBOSE) {
System.out.println("\nTEST: done adding indices; now close");
}
targetDir.close();
}
}
public void testAddOldIndexesReader() throws IOException {
for (String name : oldNames) {
Directory oldDir = oldIndexDirs.get(name);
SegmentInfos infos = SegmentInfos.readLatestCommit(oldDir);
DirectoryReader reader = DirectoryReader.open(oldDir);
Directory targetDir = newDirectory();
if (infos.getCommitLuceneVersion().major != Version.LATEST.major) {
Directory targetDir2 = newDirectory();
IndexWriter w = new IndexWriter(targetDir2, newIndexWriterConfig(new MockAnalyzer(random())));
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> TestUtil.addIndexesSlowly(w, reader));
assertEquals(e.getMessage(), "Cannot merge a segment that has been created with major version 7 into this index which has been created by major version 8");
w.close();
targetDir2.close();
// for the next test, we simulate writing to an index that was created on the same major version
new SegmentInfos(infos.getIndexCreatedVersionMajor()).commit(targetDir);
}
IndexWriter w = new IndexWriter(targetDir, newIndexWriterConfig(new MockAnalyzer(random())));
TestUtil.addIndexesSlowly(w, reader);
w.close();
reader.close();
SegmentInfos si = SegmentInfos.readLatestCommit(targetDir);
assertNull("all SCIs should have an id now",
si.asList().stream().filter(sci -> sci.getId() == null).findAny().orElse(null));
targetDir.close();
}
}
public void testSearchOldIndex() throws IOException {
for (String name : oldNames) {
searchIndex(oldIndexDirs.get(name), name);
}
}
public void testIndexOldIndexNoAdds() throws IOException {
for (String name : oldNames) {
Directory dir = newDirectory(oldIndexDirs.get(name));
changeIndexNoAdds(random(), dir);
dir.close();
}
}
public void testIndexOldIndex() throws Exception {
for (String name : oldNames) {
if (VERBOSE) {
System.out.println("TEST: oldName=" + name);
}
Directory dir = newDirectory(oldIndexDirs.get(name));
Version v = Version.parse(name.substring(0, name.indexOf('-')));
changeIndexWithAdds(random(), dir, v);
dir.close();
}
}
private void doTestHits(ScoreDoc[] hits, int expectedCount, IndexReader reader) throws IOException {
final int hitCount = hits.length;
assertEquals("wrong number of hits", expectedCount, hitCount);
for(int i=0;i<hitCount;i++) {
reader.document(hits[i].doc);
reader.getTermVectors(hits[i].doc);
}
}
public void searchIndex(Directory dir, String oldName) throws IOException {
//QueryParser parser = new QueryParser("contents", new MockAnalyzer(random));
//Query query = parser.parse("handle:1");
IndexReader reader = DirectoryReader.open(dir);
IndexSearcher searcher = newSearcher(reader);
TestUtil.checkIndex(dir);
final Bits liveDocs = MultiBits.getLiveDocs(reader);
assertNotNull(liveDocs);
for(int i=0;i<35;i++) {
if (liveDocs.get(i)) {
Document d = reader.document(i);
List<IndexableField> fields = d.getFields();
boolean isProxDoc = d.getField("content3") == null;
if (isProxDoc) {
assertEquals(7, fields.size());
IndexableField f = d.getField("id");
assertEquals(""+i, f.stringValue());
f = d.getField("utf8");
assertEquals("Lu\uD834\uDD1Ece\uD834\uDD60ne \u0000 \u2620 ab\ud917\udc17cd", f.stringValue());
f = d.getField("autf8");
assertEquals("Lu\uD834\uDD1Ece\uD834\uDD60ne \u0000 \u2620 ab\ud917\udc17cd", f.stringValue());
f = d.getField("content2");
assertEquals("here is more content with aaa aaa aaa", f.stringValue());
f = d.getField("fie\u2C77ld");
assertEquals("field with non-ascii name", f.stringValue());
}
Fields tfvFields = reader.getTermVectors(i);
assertNotNull("i=" + i, tfvFields);
Terms tfv = tfvFields.terms("utf8");
assertNotNull("docID=" + i + " index=" + oldName, tfv);
} else {
// Only ID 7 is deleted
assertEquals(7, i);
}
}
// check docvalues fields
NumericDocValues dvByte = MultiDocValues.getNumericValues(reader, "dvByte");
BinaryDocValues dvBytesDerefFixed = MultiDocValues.getBinaryValues(reader, "dvBytesDerefFixed");
BinaryDocValues dvBytesDerefVar = MultiDocValues.getBinaryValues(reader, "dvBytesDerefVar");
SortedDocValues dvBytesSortedFixed = MultiDocValues.getSortedValues(reader, "dvBytesSortedFixed");
SortedDocValues dvBytesSortedVar = MultiDocValues.getSortedValues(reader, "dvBytesSortedVar");
BinaryDocValues dvBytesStraightFixed = MultiDocValues.getBinaryValues(reader, "dvBytesStraightFixed");
BinaryDocValues dvBytesStraightVar = MultiDocValues.getBinaryValues(reader, "dvBytesStraightVar");
NumericDocValues dvDouble = MultiDocValues.getNumericValues(reader, "dvDouble");
NumericDocValues dvFloat = MultiDocValues.getNumericValues(reader, "dvFloat");
NumericDocValues dvInt = MultiDocValues.getNumericValues(reader, "dvInt");
NumericDocValues dvLong = MultiDocValues.getNumericValues(reader, "dvLong");
NumericDocValues dvPacked = MultiDocValues.getNumericValues(reader, "dvPacked");
NumericDocValues dvShort = MultiDocValues.getNumericValues(reader, "dvShort");
SortedSetDocValues dvSortedSet = MultiDocValues.getSortedSetValues(reader, "dvSortedSet");
SortedNumericDocValues dvSortedNumeric = MultiDocValues.getSortedNumericValues(reader, "dvSortedNumeric");
for (int i=0;i<35;i++) {
int id = Integer.parseInt(reader.document(i).get("id"));
assertEquals(i, dvByte.nextDoc());
assertEquals(id, dvByte.longValue());
byte bytes[] = new byte[] {
(byte)(id >>> 24), (byte)(id >>> 16),(byte)(id >>> 8),(byte)id
};
BytesRef expectedRef = new BytesRef(bytes);
assertEquals(i, dvBytesDerefFixed.nextDoc());
BytesRef term = dvBytesDerefFixed.binaryValue();
assertEquals(expectedRef, term);
assertEquals(i, dvBytesDerefVar.nextDoc());
term = dvBytesDerefVar.binaryValue();
assertEquals(expectedRef, term);
assertEquals(i, dvBytesSortedFixed.nextDoc());
term = dvBytesSortedFixed.binaryValue();
assertEquals(expectedRef, term);
assertEquals(i, dvBytesSortedVar.nextDoc());
term = dvBytesSortedVar.binaryValue();
assertEquals(expectedRef, term);
assertEquals(i, dvBytesStraightFixed.nextDoc());
term = dvBytesStraightFixed.binaryValue();
assertEquals(expectedRef, term);
assertEquals(i, dvBytesStraightVar.nextDoc());
term = dvBytesStraightVar.binaryValue();
assertEquals(expectedRef, term);
assertEquals(i, dvDouble.nextDoc());
assertEquals((double)id, Double.longBitsToDouble(dvDouble.longValue()), 0D);
assertEquals(i, dvFloat.nextDoc());
assertEquals((float)id, Float.intBitsToFloat((int)dvFloat.longValue()), 0F);
assertEquals(i, dvInt.nextDoc());
assertEquals(id, dvInt.longValue());
assertEquals(i, dvLong.nextDoc());
assertEquals(id, dvLong.longValue());
assertEquals(i, dvPacked.nextDoc());
assertEquals(id, dvPacked.longValue());
assertEquals(i, dvShort.nextDoc());
assertEquals(id, dvShort.longValue());
assertEquals(i, dvSortedSet.nextDoc());
long ord = dvSortedSet.nextOrd();
assertEquals(SortedSetDocValues.NO_MORE_ORDS, dvSortedSet.nextOrd());
term = dvSortedSet.lookupOrd(ord);
assertEquals(expectedRef, term);
assertEquals(i, dvSortedNumeric.nextDoc());
assertEquals(1, dvSortedNumeric.docValueCount());
assertEquals(id, dvSortedNumeric.nextValue());
}
ScoreDoc[] hits = searcher.search(new TermQuery(new Term(new String("content"), "aaa")), 1000).scoreDocs;
// First document should be #0
Document d = searcher.getIndexReader().document(hits[0].doc);
assertEquals("didn't get the right document first", "0", d.get("id"));
doTestHits(hits, 34, searcher.getIndexReader());
hits = searcher.search(new TermQuery(new Term(new String("content5"), "aaa")), 1000).scoreDocs;
doTestHits(hits, 34, searcher.getIndexReader());
hits = searcher.search(new TermQuery(new Term(new String("content6"), "aaa")), 1000).scoreDocs;
doTestHits(hits, 34, searcher.getIndexReader());
hits = searcher.search(new TermQuery(new Term("utf8", "\u0000")), 1000).scoreDocs;
assertEquals(34, hits.length);
hits = searcher.search(new TermQuery(new Term(new String("utf8"), "lu\uD834\uDD1Ece\uD834\uDD60ne")), 1000).scoreDocs;
assertEquals(34, hits.length);
hits = searcher.search(new TermQuery(new Term("utf8", "ab\ud917\udc17cd")), 1000).scoreDocs;
assertEquals(34, hits.length);
doTestHits(searcher.search(IntPoint.newRangeQuery("intPoint1d", 0, 34), 1000).scoreDocs, 34, searcher.getIndexReader());
doTestHits(searcher.search(IntPoint.newRangeQuery("intPoint2d", new int[] {0, 0}, new int[] {34, 68}), 1000).scoreDocs, 34, searcher.getIndexReader());
doTestHits(searcher.search(FloatPoint.newRangeQuery("floatPoint1d", 0f, 34f), 1000).scoreDocs, 34, searcher.getIndexReader());
doTestHits(searcher.search(FloatPoint.newRangeQuery("floatPoint2d", new float[] {0f, 0f}, new float[] {34f, 68f}), 1000).scoreDocs, 34, searcher.getIndexReader());
doTestHits(searcher.search(LongPoint.newRangeQuery("longPoint1d", 0, 34), 1000).scoreDocs, 34, searcher.getIndexReader());
doTestHits(searcher.search(LongPoint.newRangeQuery("longPoint2d", new long[] {0, 0}, new long[] {34, 68}), 1000).scoreDocs, 34, searcher.getIndexReader());
doTestHits(searcher.search(DoublePoint.newRangeQuery("doublePoint1d", 0.0, 34.0), 1000).scoreDocs, 34, searcher.getIndexReader());
doTestHits(searcher.search(DoublePoint.newRangeQuery("doublePoint2d", new double[] {0.0, 0.0}, new double[] {34.0, 68.0}), 1000).scoreDocs, 34, searcher.getIndexReader());
byte[] bytes1 = new byte[4];
byte[] bytes2 = new byte[] {0, 0, 0, (byte) 34};
doTestHits(searcher.search(BinaryPoint.newRangeQuery("binaryPoint1d", bytes1, bytes2), 1000).scoreDocs, 34, searcher.getIndexReader());
byte[] bytes3 = new byte[] {0, 0, 0, (byte) 68};
doTestHits(searcher.search(BinaryPoint.newRangeQuery("binaryPoint2d", new byte[][] {bytes1, bytes1}, new byte[][] {bytes2, bytes3}), 1000).scoreDocs, 34, searcher.getIndexReader());
reader.close();
}
public void changeIndexWithAdds(Random random, Directory dir, Version nameVersion) throws IOException {
SegmentInfos infos = SegmentInfos.readLatestCommit(dir);
assertEquals(nameVersion, infos.getCommitLuceneVersion());
assertEquals(nameVersion, infos.getMinSegmentLuceneVersion());
// open writer
IndexWriter writer = new IndexWriter(dir, newIndexWriterConfig(new MockAnalyzer(random))
.setOpenMode(OpenMode.APPEND)
.setMergePolicy(newLogMergePolicy()));
// add 10 docs
for(int i=0;i<10;i++) {
addDoc(writer, 35+i);
}
// make sure writer sees right total -- writer seems not to know about deletes in .del?
final int expected = 45;
assertEquals("wrong doc count", expected, writer.getDocStats().numDocs);
writer.close();
// make sure searching sees right # hits
IndexReader reader = DirectoryReader.open(dir);
IndexSearcher searcher = newSearcher(reader);
ScoreDoc[] hits = searcher.search(new TermQuery(new Term("content", "aaa")), 1000).scoreDocs;
Document d = searcher.getIndexReader().document(hits[0].doc);
assertEquals("wrong first document", "0", d.get("id"));
doTestHits(hits, 44, searcher.getIndexReader());
reader.close();
// fully merge
writer = new IndexWriter(dir, newIndexWriterConfig(new MockAnalyzer(random))
.setOpenMode(OpenMode.APPEND)
.setMergePolicy(newLogMergePolicy()));
writer.forceMerge(1);
writer.close();
reader = DirectoryReader.open(dir);
searcher = newSearcher(reader);
hits = searcher.search(new TermQuery(new Term("content", "aaa")), 1000).scoreDocs;
assertEquals("wrong number of hits", 44, hits.length);
d = searcher.doc(hits[0].doc);
doTestHits(hits, 44, searcher.getIndexReader());
assertEquals("wrong first document", "0", d.get("id"));
reader.close();
}
public void changeIndexNoAdds(Random random, Directory dir) throws IOException {
// make sure searching sees right # hits
DirectoryReader reader = DirectoryReader.open(dir);
IndexSearcher searcher = newSearcher(reader);
ScoreDoc[] hits = searcher.search(new TermQuery(new Term("content", "aaa")), 1000).scoreDocs;
assertEquals("wrong number of hits", 34, hits.length);
Document d = searcher.doc(hits[0].doc);
assertEquals("wrong first document", "0", d.get("id"));
reader.close();
// fully merge
IndexWriter writer = new IndexWriter(dir, newIndexWriterConfig(new MockAnalyzer(random))
.setOpenMode(OpenMode.APPEND));
writer.forceMerge(1);
writer.close();
reader = DirectoryReader.open(dir);
searcher = newSearcher(reader);
hits = searcher.search(new TermQuery(new Term("content", "aaa")), 1000).scoreDocs;
assertEquals("wrong number of hits", 34, hits.length);
doTestHits(hits, 34, searcher.getIndexReader());
reader.close();
}
public void createIndex(String dirName, boolean doCFS, boolean fullyMerged) throws IOException {
Path indexDir = getIndexDir().resolve(dirName);
Files.deleteIfExists(indexDir);
Directory dir = newFSDirectory(indexDir);
LogByteSizeMergePolicy mp = new LogByteSizeMergePolicy();
mp.setNoCFSRatio(doCFS ? 1.0 : 0.0);
mp.setMaxCFSSegmentSizeMB(Double.POSITIVE_INFINITY);
// TODO: remove randomness
IndexWriterConfig conf = new IndexWriterConfig(new MockAnalyzer(random()))
.setMaxBufferedDocs(10).setMergePolicy(NoMergePolicy.INSTANCE);
IndexWriter writer = new IndexWriter(dir, conf);
for(int i=0;i<35;i++) {
addDoc(writer, i);
}
assertEquals("wrong doc count", 35, writer.getDocStats().maxDoc);
if (fullyMerged) {
writer.forceMerge(1);
}
writer.close();
if (!fullyMerged) {
// open fresh writer so we get no prx file in the added segment
mp = new LogByteSizeMergePolicy();
mp.setNoCFSRatio(doCFS ? 1.0 : 0.0);
// TODO: remove randomness
conf = new IndexWriterConfig(new MockAnalyzer(random()))
.setMaxBufferedDocs(10).setMergePolicy(NoMergePolicy.INSTANCE);
writer = new IndexWriter(dir, conf);
addNoProxDoc(writer);
writer.close();
conf = new IndexWriterConfig(new MockAnalyzer(random()))
.setMaxBufferedDocs(10).setMergePolicy(NoMergePolicy.INSTANCE);
writer = new IndexWriter(dir, conf);
Term searchTerm = new Term("id", "7");
writer.deleteDocuments(searchTerm);
writer.close();
}
dir.close();
}
private void addDoc(IndexWriter writer, int id) throws IOException
{
Document doc = new Document();
doc.add(new TextField("content", "aaa", Field.Store.NO));
doc.add(new StringField("id", Integer.toString(id), Field.Store.YES));
FieldType customType2 = new FieldType(TextField.TYPE_STORED);
customType2.setStoreTermVectors(true);
customType2.setStoreTermVectorPositions(true);
customType2.setStoreTermVectorOffsets(true);
doc.add(new Field("autf8", "Lu\uD834\uDD1Ece\uD834\uDD60ne \u0000 \u2620 ab\ud917\udc17cd", customType2));
doc.add(new Field("utf8", "Lu\uD834\uDD1Ece\uD834\uDD60ne \u0000 \u2620 ab\ud917\udc17cd", customType2));
doc.add(new Field("content2", "here is more content with aaa aaa aaa", customType2));
doc.add(new Field("fie\u2C77ld", "field with non-ascii name", customType2));
// add docvalues fields
doc.add(new NumericDocValuesField("dvByte", (byte) id));
byte bytes[] = new byte[] {
(byte)(id >>> 24), (byte)(id >>> 16),(byte)(id >>> 8),(byte)id
};
BytesRef ref = new BytesRef(bytes);
doc.add(new BinaryDocValuesField("dvBytesDerefFixed", ref));
doc.add(new BinaryDocValuesField("dvBytesDerefVar", ref));
doc.add(new SortedDocValuesField("dvBytesSortedFixed", ref));
doc.add(new SortedDocValuesField("dvBytesSortedVar", ref));
doc.add(new BinaryDocValuesField("dvBytesStraightFixed", ref));
doc.add(new BinaryDocValuesField("dvBytesStraightVar", ref));
doc.add(new DoubleDocValuesField("dvDouble", (double)id));
doc.add(new FloatDocValuesField("dvFloat", (float)id));
doc.add(new NumericDocValuesField("dvInt", id));
doc.add(new NumericDocValuesField("dvLong", id));
doc.add(new NumericDocValuesField("dvPacked", id));
doc.add(new NumericDocValuesField("dvShort", (short)id));
doc.add(new SortedSetDocValuesField("dvSortedSet", ref));
doc.add(new SortedNumericDocValuesField("dvSortedNumeric", id));
doc.add(new IntPoint("intPoint1d", id));
doc.add(new IntPoint("intPoint2d", id, 2*id));
doc.add(new FloatPoint("floatPoint1d", (float) id));
doc.add(new FloatPoint("floatPoint2d", (float) id, (float) 2*id));
doc.add(new LongPoint("longPoint1d", id));
doc.add(new LongPoint("longPoint2d", id, 2*id));
doc.add(new DoublePoint("doublePoint1d", (double) id));
doc.add(new DoublePoint("doublePoint2d", (double) id, (double) 2*id));
doc.add(new BinaryPoint("binaryPoint1d", bytes));
doc.add(new BinaryPoint("binaryPoint2d", bytes, bytes));
// a field with both offsets and term vectors for a cross-check
FieldType customType3 = new FieldType(TextField.TYPE_STORED);
customType3.setStoreTermVectors(true);
customType3.setStoreTermVectorPositions(true);
customType3.setStoreTermVectorOffsets(true);
customType3.setIndexOptions(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS_AND_OFFSETS);
doc.add(new Field("content5", "here is more content with aaa aaa aaa", customType3));
// a field that omits only positions
FieldType customType4 = new FieldType(TextField.TYPE_STORED);
customType4.setStoreTermVectors(true);
customType4.setStoreTermVectorPositions(false);
customType4.setStoreTermVectorOffsets(true);
customType4.setIndexOptions(IndexOptions.DOCS_AND_FREQS);
doc.add(new Field("content6", "here is more content with aaa aaa aaa", customType4));
// TODO:
// index different norms types via similarity (we use a random one currently?!)
// remove any analyzer randomness, explicitly add payloads for certain fields.
writer.addDocument(doc);
}
private void addNoProxDoc(IndexWriter writer) throws IOException {
Document doc = new Document();
FieldType customType = new FieldType(TextField.TYPE_STORED);
customType.setIndexOptions(IndexOptions.DOCS);
Field f = new Field("content3", "aaa", customType);
doc.add(f);
FieldType customType2 = new FieldType();
customType2.setStored(true);
customType2.setIndexOptions(IndexOptions.DOCS);
f = new Field("content4", "aaa", customType2);
doc.add(f);
writer.addDocument(doc);
}
private int countDocs(PostingsEnum docs) throws IOException {
int count = 0;
while((docs.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
count ++;
}
return count;
}
// flex: test basics of TermsEnum api on non-flex index
public void testNextIntoWrongField() throws Exception {
for (String name : oldNames) {
Directory dir = oldIndexDirs.get(name);
IndexReader r = DirectoryReader.open(dir);
TermsEnum terms = MultiTerms.getTerms(r, "content").iterator();
BytesRef t = terms.next();
assertNotNull(t);
// content field only has term aaa:
assertEquals("aaa", t.utf8ToString());
assertNull(terms.next());
BytesRef aaaTerm = new BytesRef("aaa");
// should be found exactly
assertEquals(TermsEnum.SeekStatus.FOUND,
terms.seekCeil(aaaTerm));
assertEquals(35, countDocs(TestUtil.docs(random(), terms, null, PostingsEnum.NONE)));
assertNull(terms.next());
// should hit end of field
assertEquals(TermsEnum.SeekStatus.END,
terms.seekCeil(new BytesRef("bbb")));
assertNull(terms.next());
// should seek to aaa
assertEquals(TermsEnum.SeekStatus.NOT_FOUND,
terms.seekCeil(new BytesRef("a")));
assertTrue(terms.term().bytesEquals(aaaTerm));
assertEquals(35, countDocs(TestUtil.docs(random(), terms, null, PostingsEnum.NONE)));
assertNull(terms.next());
assertEquals(TermsEnum.SeekStatus.FOUND,
terms.seekCeil(aaaTerm));
assertEquals(35, countDocs(TestUtil.docs(random(), terms, null, PostingsEnum.NONE)));
assertNull(terms.next());
r.close();
}
}
/**
* Test that we didn't forget to bump the current Constants.LUCENE_MAIN_VERSION.
* This is important so that we can determine which version of lucene wrote the segment.
*/
public void testOldVersions() throws Exception {
// first create a little index with the current code and get the version
Directory currentDir = newDirectory();
RandomIndexWriter riw = new RandomIndexWriter(random(), currentDir);
riw.addDocument(new Document());
riw.close();
DirectoryReader ir = DirectoryReader.open(currentDir);
SegmentReader air = (SegmentReader)ir.leaves().get(0).reader();
Version currentVersion = air.getSegmentInfo().info.getVersion();
assertNotNull(currentVersion); // only 3.0 segments can have a null version
ir.close();
currentDir.close();
// now check all the old indexes, their version should be < the current version
for (String name : oldNames) {
Directory dir = oldIndexDirs.get(name);
DirectoryReader r = DirectoryReader.open(dir);
for (LeafReaderContext context : r.leaves()) {
air = (SegmentReader) context.reader();
Version oldVersion = air.getSegmentInfo().info.getVersion();
assertNotNull(oldVersion); // only 3.0 segments can have a null version
assertTrue("current Version.LATEST is <= an old index: did you forget to bump it?!",
currentVersion.onOrAfter(oldVersion));
}
r.close();
}
}
public void testIndexCreatedVersion() throws IOException {
for (String name : oldNames) {
Directory dir = oldIndexDirs.get(name);
SegmentInfos infos = SegmentInfos.readLatestCommit(dir);
// those indexes are created by a single version so we can
// compare the commit version with the created version
assertEquals(infos.getCommitLuceneVersion().major, infos.getIndexCreatedVersionMajor());
}
}
public void testSegmentCommitInfoId() throws IOException {
for (String name : oldNames) {
Directory dir = oldIndexDirs.get(name);
SegmentInfos infos = SegmentInfos.readLatestCommit(dir);
for (SegmentCommitInfo info : infos) {
if (info.info.getVersion().onOrAfter(Version.LUCENE_8_6_0)) {
assertNotNull(info.toString(), info.getId());
} else {
assertNull(info.toString(), info.getId());
}
}
}
}
public void verifyUsesDefaultCodec(Directory dir, String name) throws Exception {
DirectoryReader r = DirectoryReader.open(dir);
for (LeafReaderContext context : r.leaves()) {
SegmentReader air = (SegmentReader) context.reader();
Codec codec = air.getSegmentInfo().info.getCodec();
assertTrue("codec used in " + name + " (" + codec.getName() + ") is not a default codec (does not begin with Lucene)",
codec.getName().startsWith("Lucene"));
}
r.close();
}
public void testAllIndexesUseDefaultCodec() throws Exception {
for (String name : oldNames) {
Directory dir = oldIndexDirs.get(name);
verifyUsesDefaultCodec(dir, name);
}
}
private int checkAllSegmentsUpgraded(Directory dir, int indexCreatedVersion) throws IOException {
final SegmentInfos infos = SegmentInfos.readLatestCommit(dir);
if (VERBOSE) {
System.out.println("checkAllSegmentsUpgraded: " + infos);
}
for (SegmentCommitInfo si : infos) {
assertEquals(Version.LATEST, si.info.getVersion());
assertNotNull(si.getId());
}
assertEquals(Version.LATEST, infos.getCommitLuceneVersion());
assertEquals(indexCreatedVersion, infos.getIndexCreatedVersionMajor());
return infos.size();
}
private int getNumberOfSegments(Directory dir) throws IOException {
final SegmentInfos infos = SegmentInfos.readLatestCommit(dir);
return infos.size();
}
public void testUpgradeOldIndex() throws Exception {
List<String> names = new ArrayList<>(oldNames.length + oldSingleSegmentNames.length);
names.addAll(Arrays.asList(oldNames));
names.addAll(Arrays.asList(oldSingleSegmentNames));
for(String name : names) {
if (VERBOSE) {
System.out.println("testUpgradeOldIndex: index=" +name);
}
Directory dir = newDirectory(oldIndexDirs.get(name));
int indexCreatedVersion = SegmentInfos.readLatestCommit(dir).getIndexCreatedVersionMajor();
newIndexUpgrader(dir).upgrade();
checkAllSegmentsUpgraded(dir, indexCreatedVersion);
dir.close();
}
}
public void testCommandLineArgs() throws Exception {
PrintStream savedSystemOut = System.out;
System.setOut(new PrintStream(new ByteArrayOutputStream(), false, "UTF-8"));
try {
for (Map.Entry<String,Directory> entry : oldIndexDirs.entrySet()) {
String name = entry.getKey();
int indexCreatedVersion = SegmentInfos.readLatestCommit(entry.getValue()).getIndexCreatedVersionMajor();
Path dir = createTempDir(name);
TestUtil.unzip(getDataInputStream("index." + name + ".zip"), dir);
String path = dir.toAbsolutePath().toString();
List<String> args = new ArrayList<>();
if (random().nextBoolean()) {
args.add("-verbose");
}
if (random().nextBoolean()) {
args.add("-delete-prior-commits");
}
if (random().nextBoolean()) {
// TODO: need to better randomize this, but ...
// - LuceneTestCase.FS_DIRECTORIES is private
// - newFSDirectory returns BaseDirectoryWrapper
// - BaseDirectoryWrapper doesn't expose delegate
Class<? extends FSDirectory> dirImpl = random().nextBoolean() ?
SimpleFSDirectory.class : NIOFSDirectory.class;
args.add("-dir-impl");
args.add(dirImpl.getName());
}
args.add(path);
IndexUpgrader upgrader = null;
try {
upgrader = IndexUpgrader.parseArgs(args.toArray(new String[0]));
} catch (Exception e) {
throw new AssertionError("unable to parse args: " + args, e);
}
upgrader.upgrade();
Directory upgradedDir = newFSDirectory(dir);
try {
checkAllSegmentsUpgraded(upgradedDir, indexCreatedVersion);
} finally {
upgradedDir.close();
}
}
} finally {
System.setOut(savedSystemOut);
}
}
public void testUpgradeOldSingleSegmentIndexWithAdditions() throws Exception {
for (String name : oldSingleSegmentNames) {
if (VERBOSE) {
System.out.println("testUpgradeOldSingleSegmentIndexWithAdditions: index=" +name);
}
Directory dir = newDirectory(oldIndexDirs.get(name));
assertEquals("Original index must be single segment", 1, getNumberOfSegments(dir));
int indexCreatedVersion = SegmentInfos.readLatestCommit(dir).getIndexCreatedVersionMajor();
// create a bunch of dummy segments
int id = 40;
RAMDirectory ramDir = new RAMDirectory();
for (int i = 0; i < 3; i++) {
// only use Log- or TieredMergePolicy, to make document addition predictable and not suddenly merge:
MergePolicy mp = random().nextBoolean() ? newLogMergePolicy() : newTieredMergePolicy();
IndexWriterConfig iwc = new IndexWriterConfig(new MockAnalyzer(random()))
.setMergePolicy(mp);
IndexWriter w = new IndexWriter(ramDir, iwc);
// add few more docs:
for(int j = 0; j < RANDOM_MULTIPLIER * random().nextInt(30); j++) {
addDoc(w, id++);
}
try {
w.commit();
} finally {
w.close();
}
}
// add dummy segments (which are all in current
// version) to single segment index
MergePolicy mp = random().nextBoolean() ? newLogMergePolicy() : newTieredMergePolicy();
IndexWriterConfig iwc = new IndexWriterConfig(null)
.setMergePolicy(mp);
IndexWriter w = new IndexWriter(dir, iwc);
w.addIndexes(ramDir);
try {
w.commit();
} finally {
w.close();
}
// determine count of segments in modified index
final int origSegCount = getNumberOfSegments(dir);
// ensure there is only one commit
assertEquals(1, DirectoryReader.listCommits(dir).size());
newIndexUpgrader(dir).upgrade();
final int segCount = checkAllSegmentsUpgraded(dir, indexCreatedVersion);
assertEquals("Index must still contain the same number of segments, as only one segment was upgraded and nothing else merged",
origSegCount, segCount);
dir.close();
}
}
public static final String emptyIndex = "empty.7.0.0.zip";
public void testUpgradeEmptyOldIndex() throws Exception {
Path oldIndexDir = createTempDir("emptyIndex");
TestUtil.unzip(getDataInputStream(emptyIndex), oldIndexDir);
Directory dir = newFSDirectory(oldIndexDir);
newIndexUpgrader(dir).upgrade();
checkAllSegmentsUpgraded(dir, 7);
dir.close();
}
public static final String moreTermsIndex = "moreterms.7.0.0.zip";
public void testMoreTerms() throws Exception {
Path oldIndexDir = createTempDir("moreterms");
TestUtil.unzip(getDataInputStream(moreTermsIndex), oldIndexDir);
Directory dir = newFSDirectory(oldIndexDir);
DirectoryReader reader = DirectoryReader.open(dir);
verifyUsesDefaultCodec(dir, moreTermsIndex);
TestUtil.checkIndex(dir);
searchExampleIndex(reader);
reader.close();
dir.close();
}
public static final String dvUpdatesIndex = "dvupdates.7.0.0.zip";
private void assertNumericDocValues(LeafReader r, String f, String cf) throws IOException {
NumericDocValues ndvf = r.getNumericDocValues(f);
NumericDocValues ndvcf = r.getNumericDocValues(cf);
for (int i = 0; i < r.maxDoc(); i++) {
assertEquals(i, ndvcf.nextDoc());
assertEquals(i, ndvf.nextDoc());
assertEquals(ndvcf.longValue(), ndvf.longValue()*2);
}
}
private void assertBinaryDocValues(LeafReader r, String f, String cf) throws IOException {
BinaryDocValues bdvf = r.getBinaryDocValues(f);
BinaryDocValues bdvcf = r.getBinaryDocValues(cf);
for (int i = 0; i < r.maxDoc(); i++) {
assertEquals(i, bdvf.nextDoc());
assertEquals(i, bdvcf.nextDoc());
assertEquals(getValue(bdvcf), getValue(bdvf)*2);
}
}
private void verifyDocValues(Directory dir) throws IOException {
DirectoryReader reader = DirectoryReader.open(dir);
for (LeafReaderContext context : reader.leaves()) {
LeafReader r = context.reader();
assertNumericDocValues(r, "ndv1", "ndv1_c");
assertNumericDocValues(r, "ndv2", "ndv2_c");
assertBinaryDocValues(r, "bdv1", "bdv1_c");
assertBinaryDocValues(r, "bdv2", "bdv2_c");
}
reader.close();
}
public void testDocValuesUpdates() throws Exception {
Path oldIndexDir = createTempDir("dvupdates");
TestUtil.unzip(getDataInputStream(dvUpdatesIndex), oldIndexDir);
Directory dir = newFSDirectory(oldIndexDir);
verifyUsesDefaultCodec(dir, dvUpdatesIndex);
verifyDocValues(dir);
// update fields and verify index
IndexWriterConfig conf = new IndexWriterConfig(new MockAnalyzer(random()));
IndexWriter writer = new IndexWriter(dir, conf);
updateNumeric(writer, "1", "ndv1", "ndv1_c", 300L);
updateNumeric(writer, "1", "ndv2", "ndv2_c", 300L);
updateBinary(writer, "1", "bdv1", "bdv1_c", 300L);
updateBinary(writer, "1", "bdv2", "bdv2_c", 300L);
writer.commit();
verifyDocValues(dir);
// merge all segments
writer.forceMerge(1);
writer.commit();
verifyDocValues(dir);
writer.close();
dir.close();
}
public void testSoftDeletes() throws Exception {
Path oldIndexDir = createTempDir("dvupdates");
TestUtil.unzip(getDataInputStream(dvUpdatesIndex), oldIndexDir);
Directory dir = newFSDirectory(oldIndexDir);
verifyUsesDefaultCodec(dir, dvUpdatesIndex);
IndexWriterConfig conf = new IndexWriterConfig(new MockAnalyzer(random())).setSoftDeletesField("__soft_delete");
IndexWriter writer = new IndexWriter(dir, conf);
int maxDoc = writer.getDocStats().maxDoc;
writer.updateDocValues(new Term("id", "1"),new NumericDocValuesField("__soft_delete", 1));
if (random().nextBoolean()) {
writer.commit();
}
writer.forceMerge(1);
writer.commit();
assertEquals(maxDoc-1, writer.getDocStats().maxDoc);
writer.close();
dir.close();
}
public void testDocValuesUpdatesWithNewField() throws Exception {
Path oldIndexDir = createTempDir("dvupdates");
TestUtil.unzip(getDataInputStream(dvUpdatesIndex), oldIndexDir);
Directory dir = newFSDirectory(oldIndexDir);
verifyUsesDefaultCodec(dir, dvUpdatesIndex);
// update fields and verify index
IndexWriterConfig conf = new IndexWriterConfig(new MockAnalyzer(random()));
IndexWriter writer = new IndexWriter(dir, conf);
// introduce a new field that we later update
writer.addDocument(Arrays.asList(new StringField("id", "" + Integer.MAX_VALUE, Field.Store.NO),
new NumericDocValuesField("new_numeric", 1),
new BinaryDocValuesField("new_binary", toBytes(1))));
writer.updateNumericDocValue(new Term("id", "1"), "new_numeric", 1);
writer.updateBinaryDocValue(new Term("id", "1"), "new_binary", toBytes(1));
writer.commit();
Runnable assertDV = () -> {
boolean found = false;
try (DirectoryReader reader = DirectoryReader.open(dir)) {
for (LeafReaderContext ctx : reader.leaves()) {
LeafReader leafReader = ctx.reader();
TermsEnum id = leafReader.terms("id").iterator();
if (id.seekExact(new BytesRef("1"))) {
PostingsEnum postings = id.postings(null, PostingsEnum.NONE);
NumericDocValues numericDocValues = leafReader.getNumericDocValues("new_numeric");
BinaryDocValues binaryDocValues = leafReader.getBinaryDocValues("new_binary");
int doc;
while ((doc = postings.nextDoc()) != DocIdSetIterator.NO_MORE_DOCS) {
found = true;
assertTrue(binaryDocValues.advanceExact(doc));
assertTrue(numericDocValues.advanceExact(doc));
assertEquals(1, numericDocValues.longValue());
assertEquals(toBytes(1), binaryDocValues.binaryValue());
}
}
}
} catch (IOException e) {
throw new AssertionError(e);
}
assertTrue(found);
};
assertDV.run();
// merge all segments
writer.forceMerge(1);
writer.commit();
assertDV.run();
writer.close();
dir.close();
}
// LUCENE-5907
public void testUpgradeWithNRTReader() throws Exception {
for (String name : oldNames) {
Directory dir = newDirectory(oldIndexDirs.get(name));
IndexWriter writer = new IndexWriter(dir, newIndexWriterConfig(new MockAnalyzer(random()))
.setOpenMode(OpenMode.APPEND));
writer.addDocument(new Document());
DirectoryReader r = DirectoryReader.open(writer);
writer.commit();
r.close();
writer.forceMerge(1);
writer.commit();
writer.rollback();
SegmentInfos.readLatestCommit(dir);
dir.close();
}
}
// LUCENE-5907
public void testUpgradeThenMultipleCommits() throws Exception {
for (String name : oldNames) {
Directory dir = newDirectory(oldIndexDirs.get(name));
IndexWriter writer = new IndexWriter(dir, newIndexWriterConfig(new MockAnalyzer(random()))
.setOpenMode(OpenMode.APPEND));
writer.addDocument(new Document());
writer.commit();
writer.addDocument(new Document());
writer.commit();
writer.close();
dir.close();
}
}
public void testSortedIndex() throws Exception {
for(String name : oldSortedNames) {
Path path = createTempDir("sorted");
InputStream resource = TestBackwardsCompatibility.class.getResourceAsStream(name + ".zip");
assertNotNull("Sorted index index " + name + " not found", resource);
TestUtil.unzip(resource, path);
Directory dir = newFSDirectory(path);
DirectoryReader reader = DirectoryReader.open(dir);
assertEquals(1, reader.leaves().size());
Sort sort = reader.leaves().get(0).reader().getMetaData().getSort();
assertNotNull(sort);
assertEquals("<long: \"dateDV\">!", sort.toString());
reader.close();
// This will confirm the docs are really sorted
TestUtil.checkIndex(dir);
dir.close();
}
}
private void searchExampleIndex(DirectoryReader reader) throws IOException {
IndexSearcher searcher = newSearcher(reader);
TopDocs topDocs = searcher.search(new NormsFieldExistsQuery("titleTokenized"), 10);
assertEquals(50, topDocs.totalHits.value);
topDocs = searcher.search(new DocValuesFieldExistsQuery("titleDV"), 10);
assertEquals(50, topDocs.totalHits.value);
topDocs = searcher.search(new TermQuery(new Term("body", "ja")), 10);
assertTrue(topDocs.totalHits.value > 0);
topDocs =
searcher.search(
IntPoint.newRangeQuery("docid_int", 42, 44),
10,
new Sort(new SortField("docid_intDV", SortField.Type.INT)));
assertEquals(3, topDocs.totalHits.value);
assertEquals(3, topDocs.scoreDocs.length);
assertEquals(42, ((FieldDoc) topDocs.scoreDocs[0]).fields[0]);
assertEquals(43, ((FieldDoc) topDocs.scoreDocs[1]).fields[0]);
assertEquals(44, ((FieldDoc) topDocs.scoreDocs[2]).fields[0]);
topDocs = searcher.search(new TermQuery(new Term("body", "the")), 5);
assertTrue(topDocs.totalHits.value > 0);
topDocs =
searcher.search(
new MatchAllDocsQuery(), 5, new Sort(new SortField("dateDV", SortField.Type.LONG)));
assertEquals(50, topDocs.totalHits.value);
assertEquals(5, topDocs.scoreDocs.length);
long firstDate = (Long) ((FieldDoc) topDocs.scoreDocs[0]).fields[0];
long lastDate = (Long) ((FieldDoc) topDocs.scoreDocs[4]).fields[0];
assertTrue(firstDate <= lastDate);
}
/**
* Tests that {@link CheckIndex} can detect invalid sort on sorted indices created
* before https://issues.apache.org/jira/browse/LUCENE-8592.
*/
public void testSortedIndexWithInvalidSort() throws Exception {
Path path = createTempDir("sorted");
String name = "sorted-invalid.7.5.0.zip";
InputStream resource = TestBackwardsCompatibility.class.getResourceAsStream(name);
assertNotNull("Sorted index index " + name + " not found", resource);
TestUtil.unzip(resource, path);
Directory dir = FSDirectory.open(path);
DirectoryReader reader = DirectoryReader.open(dir);
assertEquals(1, reader.leaves().size());
SegmentReader segmentReader = (SegmentReader) reader.leaves().get(0).reader();
Sort sort = segmentReader.getMetaData().getSort();
assertNotNull(sort);
assertEquals("<long: \"dateDV\">! missingValue=-9223372036854775808", sort.toString());
// invalid sort index will cause checkIndex to fail with exception globally
expectThrows(RuntimeException.class, () -> {
TestUtil.checkIndex(dir);
});
CheckIndex.Status.IndexSortStatus indexSortStatus = CheckIndex.testSort(segmentReader, sort, null, false);
assertNotNull(indexSortStatus.error);
assertEquals(indexSortStatus.error.getMessage(),
"segment has indexSort=<long: \"dateDV\">! missingValue=-9223372036854775808 but docID=4 sorts after docID=5");
reader.close();
dir.close();
}
static long getValue(BinaryDocValues bdv) throws IOException {
BytesRef term = bdv.binaryValue();
int idx = term.offset;
byte b = term.bytes[idx++];
long value = b & 0x7FL;
for (int shift = 7; (b & 0x80L) != 0; shift += 7) {
b = term.bytes[idx++];
value |= (b & 0x7FL) << shift;
}
return value;
}
// encodes a long into a BytesRef as VLong so that we get varying number of bytes when we update
static BytesRef toBytes(long value) {
BytesRef bytes = new BytesRef(10); // negative longs may take 10 bytes
while ((value & ~0x7FL) != 0L) {
bytes.bytes[bytes.length++] = (byte) ((value & 0x7FL) | 0x80L);
value >>>= 7;
}
bytes.bytes[bytes.length++] = (byte) value;
return bytes;
}
}