/*
* Copyright 2014, Stratio.
*
* Licensed 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.index;
import com.google.common.base.Objects;
import com.stratio.cassandra.index.query.Search;
import com.stratio.cassandra.index.schema.Schema;
import com.stratio.cassandra.index.service.RowService;
import com.stratio.cassandra.util.Log;
import org.apache.cassandra.db.DataRange;
import org.apache.cassandra.db.IndexExpression;
import org.apache.cassandra.db.Row;
import org.apache.cassandra.db.filter.ExtendedFilter;
import org.apache.cassandra.db.index.SecondaryIndexManager;
import org.apache.cassandra.db.index.SecondaryIndexSearcher;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import static org.apache.cassandra.cql3.Operator.EQ;
/**
* A {@link SecondaryIndexSearcher} for {@link RowIndex}.
*
* @author Andres de la Pena <adelapena@stratio.com>
*/
public class RowIndexSearcher extends SecondaryIndexSearcher {
protected static final Logger logger = LoggerFactory.getLogger(SecondaryIndexSearcher.class);
private final RowIndex index;
private final RowService rowService;
private final Schema schema;
private final ByteBuffer indexedColumnName;
/**
* Returns a new {@code RowIndexSearcher}.
*
* @param indexManager A 2i manger.
* @param index A {@link RowIndex}.
* @param columns A set of columns.
* @param rowService A {@link RowService}.
*/
public RowIndexSearcher(SecondaryIndexManager indexManager,
RowIndex index,
Set<ByteBuffer> columns,
RowService rowService) {
super(indexManager, columns);
this.index = index;
this.rowService = rowService;
schema = rowService.getSchema();
indexedColumnName = index.getColumnDefinition().name.bytes;
}
/**
* {@inheritDoc}
*/
@Override
public List<Row> search(ExtendedFilter extendedFilter) {
long timestamp = extendedFilter.timestamp;
int limit = extendedFilter.currentLimit();
DataRange dataRange = extendedFilter.dataRange;
List<IndexExpression> clause = extendedFilter.getClause();
List<IndexExpression> filteredExpressions = filteredExpressions(clause);
Search search = search(clause);
return rowService.search(search, filteredExpressions, dataRange, limit, timestamp);
}
/**
* {@inheritDoc}
*/
@Override
public boolean canHandleIndexClause(List<IndexExpression> clause) {
for (IndexExpression expression : clause) {
ByteBuffer columnName = expression.column;
boolean sameName = indexedColumnName.equals(columnName);
if (expression.operator.equals(EQ) && sameName) {
return true;
}
}
return false;
}
/**
* {@inheritDoc}
*/
@Override
public IndexExpression highestSelectivityPredicate(List<IndexExpression> clause, boolean trace) {
for (IndexExpression expression : clause) {
ByteBuffer columnName = expression.column;
boolean sameName = indexedColumnName.equals(columnName);
if (expression.operator.equals(EQ) && sameName) {
return expression;
}
}
return null;
}
/**
* {@inheritDoc}
*/
@Override
public void validate(IndexExpression indexExpression) throws InvalidRequestException {
try {
String json = UTF8Type.instance.compose(indexExpression.value);
Search.fromJson(json).validate(schema);
} catch (Exception e) {
throw new InvalidRequestException(e.getMessage());
}
}
/**
* Returns the {@link Search} contained in the specified list of {@link IndexExpression}s.
*
* @param clause A list of {@link IndexExpression}s.
* @return The {@link Search} contained in the specified list of {@link IndexExpression}s.
*/
private Search search(List<IndexExpression> clause) {
IndexExpression indexedExpression = indexedExpression(clause);
String json = UTF8Type.instance.compose(indexedExpression.value);
return Search.fromJson(json);
}
/**
* Returns the {@link IndexExpression} relative to this index.
*
* @param clause A list of {@link IndexExpression}s.
* @return The {@link IndexExpression} relative to this index.
*/
private IndexExpression indexedExpression(List<IndexExpression> clause) {
for (IndexExpression indexExpression : clause) {
ByteBuffer columnName = indexExpression.column;
if (indexedColumnName.equals(columnName)) {
return indexExpression;
}
}
return null;
}
/**
* Returns the {@link IndexExpression} not relative to this index.
*
* @param clause A list of {@link IndexExpression}s.
* @return The {@link IndexExpression} not relative to this index.
*/
private List<IndexExpression> filteredExpressions(List<IndexExpression> clause) {
List<IndexExpression> filteredExpressions = new ArrayList<>(clause.size());
for (IndexExpression ie : clause) {
ByteBuffer columnName = ie.column;
if (!indexedColumnName.equals(columnName)) {
filteredExpressions.add(ie);
}
}
return filteredExpressions;
}
/** {@inheritDoc} */
@Override
public boolean requiresScanningAllRanges(List<IndexExpression> clause) {
Search search = search(clause);
return search.usesRelevanceOrSorting();
}
/** {@inheritDoc} */
@Override
public List<Row> postReconciliationProcessing(List<IndexExpression> clause, List<Row> rows) {
int startSize = rows.size();
long startTime = System.currentTimeMillis();
// Remove duplicates
TreeSet<Row> set = new TreeSet<>(rowService.comparator());
set.addAll(rows);
List<Row> result = new ArrayList<>(set);
// Sort
Search search = search(clause);
Comparator<Row> comparator = rowService.comparator(search);
Collections.sort(result, comparator);
String comparatorName = comparator.getClass().getSimpleName();
int endSize = result.size();
long endTime = System.currentTimeMillis() - startTime;
Log.debug("Sorted %d rows to %d with comparator %s in %d ms\n", startSize, endSize, comparatorName, endTime);
return result;
}
/** {@inheritDoc} */
@Override
public String toString() {
return Objects.toStringHelper(this)
.add("index", index.getIndexName())
.add("keyspace", index.getKeyspaceName())
.add("table", index.getTableName())
.add("column", index.getColumnName())
.toString();
}
}