blob: 6de68afb4d7b9cb2fc76c3c692e11739386d842e [file] [log] [blame]
using J2N.Runtime.CompilerServices;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using JCG = J2N.Collections.Generic;
namespace Lucene.Net.Index
{
/*
* 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.
*/
/// <summary>
/// A <see cref="CompositeReader"/> which reads multiple, parallel indexes. Each index added
/// must have the same number of documents, and exactly the same hierarchical subreader structure,
/// but typically each contains different fields. Deletions are taken from the first reader.
/// Each document contains the union of the fields of all
/// documents with the same document number. When searching, matches for a
/// query term are from the first index added that has the field.
///
/// <para/>This is useful, e.g., with collections that have large fields which
/// change rarely and small fields that change more frequently. The smaller
/// fields may be re-indexed in a new index and both indexes may be searched
/// together.
///
/// <para/><strong>Warning:</strong> It is up to you to make sure all indexes
/// are created and modified the same way. For example, if you add
/// documents to one index, you need to add the same documents in the
/// same order to the other indexes. <em>Failure to do so will result in
/// undefined behavior</em>.
/// A good strategy to create suitable indexes with <see cref="IndexWriter"/> is to use
/// <see cref="LogDocMergePolicy"/>, as this one does not reorder documents
/// during merging (like <see cref="TieredMergePolicy"/>) and triggers merges
/// by number of documents per segment. If you use different <see cref="MergePolicy"/>s
/// it might happen that the segment structure of your index is no longer predictable.
/// </summary>
public class ParallelCompositeReader : BaseCompositeReader<IndexReader>
{
private readonly bool closeSubReaders;
private readonly ISet<IndexReader> completeReaderSet = new JCG.HashSet<IndexReader>(IdentityEqualityComparer<IndexReader>.Default);
/// <summary>
/// Create a <see cref="ParallelCompositeReader"/> based on the provided
/// readers; auto-disposes the given <paramref name="readers"/> on <see cref="IndexReader.Dispose()"/>.
/// </summary>
public ParallelCompositeReader(params CompositeReader[] readers)
: this(true, readers)
{
}
/// <summary>
/// Create a <see cref="ParallelCompositeReader"/> based on the provided
/// <paramref name="readers"/>.
/// </summary>
public ParallelCompositeReader(bool closeSubReaders, params CompositeReader[] readers)
: this(closeSubReaders, readers, readers)
{
}
/// <summary>
/// Expert: create a <see cref="ParallelCompositeReader"/> based on the provided
/// <paramref name="readers"/> and <paramref name="storedFieldReaders"/>; when a document is
/// loaded, only <paramref name="storedFieldReaders"/> will be used.
/// </summary>
public ParallelCompositeReader(bool closeSubReaders, CompositeReader[] readers, CompositeReader[] storedFieldReaders)
: base(PrepareSubReaders(readers, storedFieldReaders))
{
this.closeSubReaders = closeSubReaders;
completeReaderSet.UnionWith(readers);
completeReaderSet.UnionWith(storedFieldReaders);
// update ref-counts (like MultiReader):
if (!closeSubReaders)
{
foreach (IndexReader reader in completeReaderSet)
{
reader.IncRef();
}
}
// finally add our own synthetic readers, so we close or decRef them, too (it does not matter what we do)
completeReaderSet.UnionWith(GetSequentialSubReaders());
}
private static IndexReader[] PrepareSubReaders(CompositeReader[] readers, CompositeReader[] storedFieldsReaders)
{
if (readers.Length == 0)
{
if (storedFieldsReaders.Length > 0)
{
throw new System.ArgumentException("There must be at least one main reader if storedFieldsReaders are used.");
}
return new IndexReader[0];
}
else
{
IList<IndexReader> firstSubReaders = readers[0].GetSequentialSubReaders();
// check compatibility:
int maxDoc = readers[0].MaxDoc, noSubs = firstSubReaders.Count;
int[] childMaxDoc = new int[noSubs];
bool[] childAtomic = new bool[noSubs];
for (int i = 0; i < noSubs; i++)
{
IndexReader r = firstSubReaders[i];
childMaxDoc[i] = r.MaxDoc;
childAtomic[i] = r is AtomicReader;
}
Validate(readers, maxDoc, childMaxDoc, childAtomic);
Validate(storedFieldsReaders, maxDoc, childMaxDoc, childAtomic);
// hierarchically build the same subreader structure as the first CompositeReader with Parallel*Readers:
IndexReader[] subReaders = new IndexReader[noSubs];
for (int i = 0; i < subReaders.Length; i++)
{
if (firstSubReaders[i] is AtomicReader)
{
AtomicReader[] atomicSubs = new AtomicReader[readers.Length];
for (int j = 0; j < readers.Length; j++)
{
atomicSubs[j] = (AtomicReader)readers[j].GetSequentialSubReaders()[i];
}
AtomicReader[] storedSubs = new AtomicReader[storedFieldsReaders.Length];
for (int j = 0; j < storedFieldsReaders.Length; j++)
{
storedSubs[j] = (AtomicReader)storedFieldsReaders[j].GetSequentialSubReaders()[i];
}
// We pass true for closeSubs and we prevent closing of subreaders in doClose():
// By this the synthetic throw-away readers used here are completely invisible to ref-counting
subReaders[i] = new ParallelAtomicReaderAnonymousInnerClassHelper(atomicSubs, storedSubs);
}
else
{
Debug.Assert(firstSubReaders[i] is CompositeReader);
CompositeReader[] compositeSubs = new CompositeReader[readers.Length];
for (int j = 0; j < readers.Length; j++)
{
compositeSubs[j] = (CompositeReader)readers[j].GetSequentialSubReaders()[i];
}
CompositeReader[] storedSubs = new CompositeReader[storedFieldsReaders.Length];
for (int j = 0; j < storedFieldsReaders.Length; j++)
{
storedSubs[j] = (CompositeReader)storedFieldsReaders[j].GetSequentialSubReaders()[i];
}
// We pass true for closeSubs and we prevent closing of subreaders in doClose():
// By this the synthetic throw-away readers used here are completely invisible to ref-counting
subReaders[i] = new ParallelCompositeReaderAnonymousInnerClassHelper(compositeSubs, storedSubs);
}
}
return subReaders;
}
}
private class ParallelAtomicReaderAnonymousInnerClassHelper : ParallelAtomicReader
{
public ParallelAtomicReaderAnonymousInnerClassHelper(Lucene.Net.Index.AtomicReader[] atomicSubs, Lucene.Net.Index.AtomicReader[] storedSubs)
: base(true, atomicSubs, storedSubs)
{
}
protected internal override void DoClose()
{
}
}
private class ParallelCompositeReaderAnonymousInnerClassHelper : ParallelCompositeReader
{
public ParallelCompositeReaderAnonymousInnerClassHelper(Lucene.Net.Index.CompositeReader[] compositeSubs, Lucene.Net.Index.CompositeReader[] storedSubs)
: base(true, compositeSubs, storedSubs)
{
}
protected internal override void DoClose()
{
}
}
private static void Validate(CompositeReader[] readers, int maxDoc, int[] childMaxDoc, bool[] childAtomic)
{
for (int i = 0; i < readers.Length; i++)
{
CompositeReader reader = readers[i];
IList<IndexReader> subs = reader.GetSequentialSubReaders();
if (reader.MaxDoc != maxDoc)
{
throw new System.ArgumentException("All readers must have same MaxDoc: " + maxDoc + "!=" + reader.MaxDoc);
}
int noSubs = subs.Count;
if (noSubs != childMaxDoc.Length)
{
throw new System.ArgumentException("All readers must have same number of subReaders");
}
for (int subIDX = 0; subIDX < noSubs; subIDX++)
{
IndexReader r = subs[subIDX];
if (r.MaxDoc != childMaxDoc[subIDX])
{
throw new System.ArgumentException("All readers must have same corresponding subReader maxDoc");
}
if (!(childAtomic[subIDX] ? (r is AtomicReader) : (r is CompositeReader)))
{
throw new System.ArgumentException("All readers must have same corresponding subReader types (atomic or composite)");
}
}
}
}
protected internal override void DoClose()
{
lock (this)
{
IOException ioe = null;
foreach (IndexReader reader in completeReaderSet)
{
try
{
if (closeSubReaders)
{
reader.Dispose();
}
else
{
reader.DecRef();
}
}
catch (IOException e)
{
if (ioe == null)
{
ioe = e;
}
}
}
// throw the first exception
if (ioe != null)
{
throw ioe;
}
}
}
}
}