blob: b8fe341fc6bbf3ff5424479832505913dc176c3b [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.util.Map;
import java.util.Objects;
/**
* Access to the Field Info file that describes document fields and whether or
* not they are indexed. Each segment has a separate Field Info file. Objects
* of this class are thread-safe for multiple readers, but only one thread can
* be adding documents at a time, with no other reader or writer threads
* accessing this object.
**/
public final class FieldInfo {
/** Field's name */
public final String name;
/** Internal field number */
public final int number;
private DocValuesType docValuesType = DocValuesType.NONE;
// True if any document indexed term vectors
private boolean storeTermVector;
private boolean omitNorms; // omit norms associated with indexed fields
private IndexOptions indexOptions = IndexOptions.NONE;
private boolean storePayloads; // whether this field stores payloads together with term positions
private final Map<String,String> attributes;
private long dvGen;
/** If both of these are positive it means this field indexed points
* (see {@link org.apache.lucene.codecs.PointsFormat}). */
private int pointDimensionCount;
private int pointIndexDimensionCount;
private int pointNumBytes;
// whether this field is used as the soft-deletes field
private final boolean softDeletesField;
/**
* Sole constructor.
*
* @lucene.experimental
*/
public FieldInfo(String name, int number, boolean storeTermVector, boolean omitNorms, boolean storePayloads,
IndexOptions indexOptions, DocValuesType docValues, long dvGen, Map<String,String> attributes,
int pointDimensionCount, int pointIndexDimensionCount, int pointNumBytes, boolean softDeletesField) {
this.name = Objects.requireNonNull(name);
this.number = number;
this.docValuesType = Objects.requireNonNull(docValues, "DocValuesType must not be null (field: \"" + name + "\")");
this.indexOptions = Objects.requireNonNull(indexOptions, "IndexOptions must not be null (field: \"" + name + "\")");
if (indexOptions != IndexOptions.NONE) {
this.storeTermVector = storeTermVector;
this.storePayloads = storePayloads;
this.omitNorms = omitNorms;
} else { // for non-indexed fields, leave defaults
this.storeTermVector = false;
this.storePayloads = false;
this.omitNorms = false;
}
this.dvGen = dvGen;
this.attributes = Objects.requireNonNull(attributes);
this.pointDimensionCount = pointDimensionCount;
this.pointIndexDimensionCount = pointIndexDimensionCount;
this.pointNumBytes = pointNumBytes;
this.softDeletesField = softDeletesField;
this.checkConsistency();
}
/**
* Performs internal consistency checks.
* Always returns true (or throws IllegalStateException)
*/
public boolean checkConsistency() {
if (indexOptions != IndexOptions.NONE) {
// Cannot store payloads unless positions are indexed:
if (indexOptions.compareTo(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS) < 0 && storePayloads) {
throw new IllegalStateException("indexed field '" + name + "' cannot have payloads without positions");
}
} else {
if (storeTermVector) {
throw new IllegalStateException("non-indexed field '" + name + "' cannot store term vectors");
}
if (storePayloads) {
throw new IllegalStateException("non-indexed field '" + name + "' cannot store payloads");
}
if (omitNorms) {
throw new IllegalStateException("non-indexed field '" + name + "' cannot omit norms");
}
}
if (pointDimensionCount < 0) {
throw new IllegalStateException("pointDimensionCount must be >= 0; got " + pointDimensionCount);
}
if (pointIndexDimensionCount < 0) {
throw new IllegalStateException("pointIndexDimensionCount must be >= 0; got " + pointIndexDimensionCount);
}
if (pointNumBytes < 0) {
throw new IllegalStateException("pointNumBytes must be >= 0; got " + pointNumBytes);
}
if (pointDimensionCount != 0 && pointNumBytes == 0) {
throw new IllegalStateException("pointNumBytes must be > 0 when pointDimensionCount=" + pointDimensionCount);
}
if (pointIndexDimensionCount != 0 && pointDimensionCount == 0) {
throw new IllegalStateException("pointIndexDimensionCount must be 0 when pointDimensionCount=0");
}
if (pointNumBytes != 0 && pointDimensionCount == 0) {
throw new IllegalStateException("pointDimensionCount must be > 0 when pointNumBytes=" + pointNumBytes);
}
if (dvGen != -1 && docValuesType == DocValuesType.NONE) {
throw new IllegalStateException("field '" + name + "' cannot have a docvalues update generation without having docvalues");
}
return true;
}
// should only be called by FieldInfos#addOrUpdate
void update(boolean storeTermVector, boolean omitNorms, boolean storePayloads, IndexOptions indexOptions,
Map<String, String> attributes, int dimensionCount, int indexDimensionCount, int dimensionNumBytes) {
if (indexOptions == null) {
throw new NullPointerException("IndexOptions must not be null (field: \"" + name + "\")");
}
//System.out.println("FI.update field=" + name + " indexed=" + indexed + " omitNorms=" + omitNorms + " this.omitNorms=" + this.omitNorms);
if (this.indexOptions != indexOptions) {
if (this.indexOptions == IndexOptions.NONE) {
this.indexOptions = indexOptions;
} else if (indexOptions != IndexOptions.NONE) {
throw new IllegalArgumentException("cannot change field \"" + name + "\" from index options=" + this.indexOptions + " to inconsistent index options=" + indexOptions);
}
}
if (this.pointDimensionCount == 0 && dimensionCount != 0) {
this.pointDimensionCount = dimensionCount;
this.pointIndexDimensionCount = indexDimensionCount;
this.pointNumBytes = dimensionNumBytes;
} else if (dimensionCount != 0 && (this.pointDimensionCount != dimensionCount || this.pointIndexDimensionCount != indexDimensionCount || this.pointNumBytes != dimensionNumBytes)) {
throw new IllegalArgumentException("cannot change field \"" + name + "\" from points dimensionCount=" + this.pointDimensionCount + ", indexDimensionCount=" + this.pointIndexDimensionCount + ", numBytes=" + this.pointNumBytes + " to inconsistent dimensionCount=" + dimensionCount +", indexDimensionCount=" + indexDimensionCount + ", numBytes=" + dimensionNumBytes);
}
if (this.indexOptions != IndexOptions.NONE) { // if updated field data is not for indexing, leave the updates out
this.storeTermVector |= storeTermVector; // once vector, always vector
this.storePayloads |= storePayloads;
// Awkward: only drop norms if incoming update is indexed:
if (indexOptions != IndexOptions.NONE && this.omitNorms != omitNorms) {
this.omitNorms = true; // if one require omitNorms at least once, it remains off for life
}
}
if (this.indexOptions == IndexOptions.NONE || this.indexOptions.compareTo(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS) < 0) {
// cannot store payloads if we don't store positions:
this.storePayloads = false;
}
if (attributes != null) {
this.attributes.putAll(attributes);
}
this.checkConsistency();
}
/** Record that this field is indexed with points, with the
* specified number of dimensions and bytes per dimension. */
public void setPointDimensions(int dimensionCount, int indexDimensionCount, int numBytes) {
if (dimensionCount <= 0) {
throw new IllegalArgumentException("point dimension count must be >= 0; got " + dimensionCount + " for field=\"" + name + "\"");
}
if (indexDimensionCount > PointValues.MAX_INDEX_DIMENSIONS) {
throw new IllegalArgumentException("point index dimension count must be < PointValues.MAX_INDEX_DIMENSIONS (= " + PointValues.MAX_INDEX_DIMENSIONS + "); got " + indexDimensionCount + " for field=\"" + name + "\"");
}
if (indexDimensionCount > dimensionCount) {
throw new IllegalArgumentException("point index dimension count must be <= point dimension count (= " + dimensionCount + "); got " + indexDimensionCount + " for field=\"" + name + "\"");
}
if (numBytes <= 0) {
throw new IllegalArgumentException("point numBytes must be >= 0; got " + numBytes + " for field=\"" + name + "\"");
}
if (numBytes > PointValues.MAX_NUM_BYTES) {
throw new IllegalArgumentException("point numBytes must be <= PointValues.MAX_NUM_BYTES (= " + PointValues.MAX_NUM_BYTES + "); got " + numBytes + " for field=\"" + name + "\"");
}
if (pointDimensionCount != 0 && pointDimensionCount != dimensionCount) {
throw new IllegalArgumentException("cannot change point dimension count from " + pointDimensionCount + " to " + dimensionCount + " for field=\"" + name + "\"");
}
if (pointIndexDimensionCount != 0 && pointIndexDimensionCount != indexDimensionCount) {
throw new IllegalArgumentException("cannot change point index dimension count from " + pointIndexDimensionCount + " to " + indexDimensionCount + " for field=\"" + name + "\"");
}
if (pointNumBytes != 0 && pointNumBytes != numBytes) {
throw new IllegalArgumentException("cannot change point numBytes from " + pointNumBytes + " to " + numBytes + " for field=\"" + name + "\"");
}
pointDimensionCount = dimensionCount;
pointIndexDimensionCount = indexDimensionCount;
pointNumBytes = numBytes;
this.checkConsistency();
}
/** Return point data dimension count */
public int getPointDimensionCount() {
return pointDimensionCount;
}
/** Return point data dimension count */
public int getPointIndexDimensionCount() {
return pointIndexDimensionCount;
}
/** Return number of bytes per dimension */
public int getPointNumBytes() {
return pointNumBytes;
}
/** Record that this field is indexed with docvalues, with the specified type */
public void setDocValuesType(DocValuesType type) {
if (type == null) {
throw new NullPointerException("DocValuesType must not be null (field: \"" + name + "\")");
}
if (docValuesType != DocValuesType.NONE && type != DocValuesType.NONE && docValuesType != type) {
throw new IllegalArgumentException("cannot change DocValues type from " + docValuesType + " to " + type + " for field \"" + name + "\"");
}
docValuesType = type;
this.checkConsistency();
}
/** Returns IndexOptions for the field, or IndexOptions.NONE if the field is not indexed */
public IndexOptions getIndexOptions() {
return indexOptions;
}
/** Record the {@link IndexOptions} to use with this field. */
public void setIndexOptions(IndexOptions newIndexOptions) {
if (indexOptions != newIndexOptions) {
if (indexOptions == IndexOptions.NONE) {
indexOptions = newIndexOptions;
} else if (newIndexOptions != IndexOptions.NONE) {
throw new IllegalArgumentException("cannot change field \"" + name + "\" from index options=" + indexOptions + " to inconsistent index options=" + newIndexOptions);
}
}
if (indexOptions == IndexOptions.NONE || indexOptions.compareTo(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS) < 0) {
// cannot store payloads if we don't store positions:
storePayloads = false;
}
this.checkConsistency();
}
/**
* Returns {@link DocValuesType} of the docValues; this is
* {@code DocValuesType.NONE} if the field has no docvalues.
*/
public DocValuesType getDocValuesType() {
return docValuesType;
}
/** Sets the docValues generation of this field. */
void setDocValuesGen(long dvGen) {
this.dvGen = dvGen;
this.checkConsistency();
}
/**
* Returns the docValues generation of this field, or -1 if no docValues
* updates exist for it.
*/
public long getDocValuesGen() {
return dvGen;
}
void setStoreTermVectors() {
storeTermVector = true;
this.checkConsistency();
}
void setStorePayloads() {
if (indexOptions != IndexOptions.NONE && indexOptions.compareTo(IndexOptions.DOCS_AND_FREQS_AND_POSITIONS) >= 0) {
storePayloads = true;
}
this.checkConsistency();
}
/**
* Returns true if norms are explicitly omitted for this field
*/
public boolean omitsNorms() {
return omitNorms;
}
/** Omit norms for this field. */
public void setOmitsNorms() {
if (indexOptions == IndexOptions.NONE) {
throw new IllegalStateException("cannot omit norms: this field is not indexed");
}
omitNorms = true;
this.checkConsistency();
}
/**
* Returns true if this field actually has any norms.
*/
public boolean hasNorms() {
return indexOptions != IndexOptions.NONE && omitNorms == false;
}
/**
* Returns true if any payloads exist for this field.
*/
public boolean hasPayloads() {
return storePayloads;
}
/**
* Returns true if any term vectors exist for this field.
*/
public boolean hasVectors() {
return storeTermVector;
}
/**
* Get a codec attribute value, or null if it does not exist
*/
public String getAttribute(String key) {
return attributes.get(key);
}
/**
* Puts a codec attribute value.
* <p>
* This is a key-value mapping for the field that the codec can use
* to store additional metadata, and will be available to the codec
* when reading the segment via {@link #getAttribute(String)}
* <p>
* If a value already exists for the key in the field, it will be replaced with
* the new value. If the value of the attributes for a same field is changed between
* the documents, the behaviour after merge is undefined.
*/
public String putAttribute(String key, String value) {
return attributes.put(key, value);
}
/**
* Returns internal codec attributes map.
*/
public Map<String,String> attributes() {
return attributes;
}
/**
* Returns true if this field is configured and used as the soft-deletes field.
* See {@link IndexWriterConfig#softDeletesField}
*/
public boolean isSoftDeletesField() {
return softDeletesField;
}
}