/*
* Licensed to STRATIO (C) under one or more contributor license agreements.
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership. The STRATIO (C) 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 com.stratio.cassandra.lucene.key;
import com.stratio.cassandra.lucene.IndexException;
import com.stratio.cassandra.lucene.column.Column;
import com.stratio.cassandra.lucene.column.Columns;
import com.stratio.cassandra.lucene.util.ByteBufferUtils;
import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.ColumnDefinition;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.filter.ClusteringIndexFilter;
import org.apache.cassandra.db.filter.ClusteringIndexNamesFilter;
import org.apache.cassandra.db.filter.ClusteringIndexSliceFilter;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.db.marshal.CompositeType;
import org.apache.cassandra.db.marshal.LongType;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.FieldType;
import org.apache.lucene.index.DocValuesType;
import org.apache.lucene.index.IndexOptions;
import org.apache.lucene.index.Term;
import org.apache.lucene.search.*;
import org.apache.lucene.util.BytesRef;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.NavigableSet;
import java.util.Optional;
import static org.apache.lucene.search.BooleanClause.Occur.SHOULD;
/**
* Class for several clustering key mappings between Cassandra and Lucene.
*
* @author Andres de la Pena {@literal <adelapena@stratio.com>}
*/
public final class KeyMapper {
public static final Logger logger = LoggerFactory.getLogger(KeyMapper.class);
/** The Lucene field name. */
public static final String FIELD_NAME = "_primary_key";
/** The Lucene field type. */
public static final FieldType FIELD_TYPE = new FieldType();
static {
FIELD_TYPE.setOmitNorms(true);
FIELD_TYPE.setIndexOptions(IndexOptions.DOCS);
FIELD_TYPE.setTokenized(false);
FIELD_TYPE.setStored(true);
FIELD_TYPE.setDocValuesType(DocValuesType.SORTED);
FIELD_TYPE.freeze();
}
/** The indexed table metadata */
private final CFMetaData metadata;
/** The clustering key comparator */
private final ClusteringComparator clusteringComparator;
/** A composite type composed by the types of the clustering key */
private final CompositeType clusteringType;
/** The type of the primary key, which is composed by token, partition key and clustering key types. */
private final CompositeType type;
/**
* Constructor specifying the partition and clustering key mappers.
*
* @param metadata the indexed table metadata
*/
public KeyMapper(CFMetaData metadata) {
this.metadata = metadata;
clusteringComparator = metadata.comparator;
clusteringType = CompositeType.getInstance(clusteringComparator.subtypes());
type = CompositeType.getInstance(LongType.instance, metadata.getKeyValidator(), clusteringType);
}
/**
* Returns the clustering key comparator.
*
* @return the comparator
*/
public ClusteringComparator clusteringComparator() {
return clusteringComparator;
}
/**
* The type of the primary key, which is composed by token, partition key and clustering key types.
*
* @return the composite type
*/
public CompositeType clusteringType() {
return clusteringType;
}
/**
* Adds the {@link Column}s contained in the specified {@link Clustering} to the specified {@link Column}s.
*
* @param columns the {@link Columns} in which the {@link Clustering} {@link Column}s are going to be added
* @param clustering the clustering key
*/
public void addColumns(Columns columns, Clustering clustering) {
for (ColumnDefinition columnDefinition : metadata.clusteringColumns()) {
String name = columnDefinition.name.toString();
int position = columnDefinition.position();
ByteBuffer value = clustering.get(position);
AbstractType<?> valueType = columnDefinition.cellValueType();
columns.add(Column.builder(name).buildWithDecomposed(value, valueType));
}
}
/**
* Returns the {@link KeyEntry} represented by the specified Lucene {@link BytesRef}.
*
* @param bytesRef the Lucene field binary value
* @return the represented key entry
*/
public KeyEntry entry(BytesRef bytesRef) {
ByteBuffer bb = ByteBufferUtils.byteBuffer(bytesRef);
ByteBuffer[] components = type.split(bb);
return new KeyEntry(this, components);
}
/**
* Returns the {@code String} human-readable representation of the specified {@link ClusteringPrefix}.
*
* @param prefix the clustering prefix
* @return a {@code String} representing {@code prefix}
*/
public String toString(ClusteringPrefix prefix) {
return prefix.toString(metadata);
}
/**
* Returns the {@link ByteBuffer} representation of the primary key formed by the specified partition key and the
* clustering key.
*
* @param key the partition key
* @param clustering the clustering key
* @return the {@link ByteBuffer} representation of the primary key
*/
public ByteBuffer byteBuffer(DecoratedKey key, Clustering clustering) {
return type.builder()
.add(TokenMapper.byteBuffer(key.getToken()))
.add(key.getKey())
.add(byteBuffer(clustering))
.build();
}
/**
* Returns a {@link ByteBuffer} representing the specified clustering key
*
* @param clustering the clustering key
* @return the byte buffer representing {@code clustering}
*/
public ByteBuffer byteBuffer(Clustering clustering) {
CompositeType.Builder builder = type.builder();
for (ByteBuffer component : clustering.getRawValues()) {
builder.add(component);
}
return builder.build();
}
/**
* Adds to the specified Lucene {@link Document} the primary key formed by the specified partition key and the
* clustering key.
*
* @param document the Lucene {@link Document} in which the key is going to be added
* @param key the partition key
* @param clustering the clustering key
*/
public void addFields(Document document, DecoratedKey key, Clustering clustering) {
ByteBuffer bb = byteBuffer(key, clustering);
BytesRef bytesRef = ByteBufferUtils.bytesRef(bb);
Field field = new Field(FIELD_NAME, bytesRef, FIELD_TYPE);
document.add(field);
}
/**
* Returns the Lucene {@link Term} representing the primary key formed by the specified partition key and the
* clustering key.
*
* @param key the partition key
* @param clustering the clustering key
* @return the Lucene {@link Term} representing the primary key
*/
public Term term(DecoratedKey key, Clustering clustering) {
return new Term(FIELD_NAME, bytesRef(key, clustering));
}
/**
* Returns the {@link BytesRef} representation of the specified primary key.
*
* @param key the partition key
* @param clustering the clustering key
* @return the Lucene field binary value
*/
public BytesRef bytesRef(DecoratedKey key, Clustering clustering) {
ByteBuffer bb = byteBuffer(key, clustering);
return ByteBufferUtils.bytesRef(bb);
}
/**
* Returns the clustering key contained in the specified {@link Document}.
*
* @param document a {@link Document} containing the clustering key to be get
* @return the clustering key contained in {@code document}
*/
public Clustering clustering(Document document) {
BytesRef bytesRef = document.getBinaryValue(FIELD_NAME);
return entry(bytesRef).getClustering();
}
/**
* Returns the start {@link ClusteringPrefix} of the first partition of the specified {@link DataRange}.
*
* @param dataRange the data range
* @return the start clustering prefix of {@code dataRange}, or {@code null} if there is no such start
*/
public static Optional<ClusteringPrefix> startClusteringPrefix(DataRange dataRange) {
PartitionPosition startPosition = dataRange.startKey();
if (startPosition instanceof DecoratedKey) {
DecoratedKey startKey = (DecoratedKey) startPosition;
ClusteringIndexFilter filter = dataRange.clusteringIndexFilter(startKey);
if (filter instanceof ClusteringIndexSliceFilter) {
ClusteringIndexSliceFilter sliceFilter = (ClusteringIndexSliceFilter) filter;
Slices slices = sliceFilter.requestedSlices();
return Optional.of(slices.get(0).start());
}
}
return Optional.empty();
}
/**
* Returns the stop {@link ClusteringPrefix} of the last partition of the specified {@link DataRange}.
*
* @param dataRange the data range
* @return the stop clustering prefix of {@code dataRange}, or {@code null} if there is no such start
*/
public static Optional<ClusteringPrefix> stopClusteringPrefix(DataRange dataRange) {
PartitionPosition stopPosition = dataRange.stopKey();
if (stopPosition instanceof DecoratedKey) {
DecoratedKey stopKey = (DecoratedKey) stopPosition;
ClusteringIndexFilter filter = dataRange.clusteringIndexFilter(stopKey);
if (filter instanceof ClusteringIndexSliceFilter) {
ClusteringIndexSliceFilter sliceFilter = (ClusteringIndexSliceFilter) filter;
Slices slices = sliceFilter.requestedSlices();
return Optional.of(slices.get(slices.size() - 1).end());
}
}
return Optional.empty();
}
/**
* Returns the {@link Term} representing the primary key of the specified {@link Document}.
*
* @param document the document
* @return the clustering key term
*/
public static Term term(Document document) {
BytesRef bytesRef = document.getBinaryValue(FIELD_NAME);
return new Term(FIELD_NAME, bytesRef);
}
/**
* Returns a Lucene {@link SortField} to sort documents by primary key according to Cassandra's natural order.
*
* @return the sort field
*/
public SortField sortField() {
return new SortField(FIELD_NAME, new FieldComparatorSource() {
@Override
public FieldComparator<?> newComparator(String field, int hits, int sort, boolean reversed)
throws IOException {
return new FieldComparator.TermValComparator(hits, field, false) {
@Override
public int compareValues(BytesRef val1, BytesRef val2) {
return entry(val1).compareTo(entry(val2));
}
};
}
});
}
/**
* Returns a Lucene {@link Query} to retrieve all the rows in the specified partition slice.
*
* @param key the partition key
* @param start the start clustering prefix
* @param stop the stop clustering prefix
* @param acceptLowerConflicts if rows with the same token before key should be accepted
* @param acceptUpperConflicts if rows with the same token after key should be accepted
* @return the Lucene query
*/
public Query query(DecoratedKey key,
ClusteringPrefix start,
ClusteringPrefix stop,
boolean acceptLowerConflicts,
boolean acceptUpperConflicts) {
return new KeyQuery(this, key, start, stop, acceptLowerConflicts, acceptUpperConflicts);
}
/**
* Returns a Lucene {@link Query} to retrieve all the rows in the specified clustering slice filter.
*
* @param key the partition key
* @param sliceFilter the slice filter
* @return the Lucene query
*/
public Query query(DecoratedKey key, ClusteringIndexSliceFilter sliceFilter) {
Slices slices = sliceFilter.requestedSlices();
ClusteringPrefix startBound = slices.get(0).start();
ClusteringPrefix stopBound = slices.get(slices.size() - 1).end();
return query(key, startBound, stopBound, false, false);
}
/**
* Returns a Lucene {@link Query} to retrieve all the rows in the specified clustering names filter.
*
* @param key the partition key
* @param namesFilter the names filter
* @return the Lucene query
*/
public Query query(DecoratedKey key, ClusteringIndexNamesFilter namesFilter) {
NavigableSet<Clustering> clusterings = namesFilter.requestedRows();
if (!clusterings.isEmpty()) {
BooleanQuery.Builder builder = new BooleanQuery.Builder();
for (Clustering clustering : clusterings) {
builder.add(query(key, clustering), SHOULD);
}
return builder.build();
}
return null;
}
/**
* Returns a Lucene {@link Query} to retrieve all the rows in the specified clustering filter.
*
* @param key the partition's key
* @param filter the clustering filter
* @return the Lucene query
*/
public Query query(DecoratedKey key, ClusteringIndexFilter filter) {
if (filter instanceof ClusteringIndexNamesFilter) {
return query(key, (ClusteringIndexNamesFilter) filter);
} else if (filter instanceof ClusteringIndexSliceFilter) {
return query(key, (ClusteringIndexSliceFilter) filter);
} else {
throw new IndexException("Unknown filter type %s", filter);
}
}
/**
* Returns a Lucene {@link Query} to retrieve the row with the specified primary key.
*
* @param key the partition key
* @param clustering the clustering key
* @return the Lucene query
*/
public Query query(DecoratedKey key, Clustering clustering) {
return new TermQuery(term(key, clustering));
}
}