/* * 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.service; import com.google.common.collect.Lists; import org.apache.cassandra.config.ColumnDefinition; import org.apache.cassandra.db.*; import org.apache.cassandra.db.composites.CellName; import org.apache.cassandra.db.filter.ColumnSlice; import org.apache.cassandra.db.filter.QueryFilter; import org.apache.cassandra.db.filter.SliceQueryFilter; import org.apache.lucene.document.Document; import org.apache.lucene.index.Term; import org.apache.lucene.search.Query; import java.nio.ByteBuffer; import java.util.*; /** * {@link RowService} that manages wide rows. * * @author Andres de la Pena <adelapena@stratio.com> */ public class RowServiceWide extends RowService { /** The names of the Lucene fields to be loaded. */ private static final Set<String> FIELDS_TO_LOAD; static { FIELDS_TO_LOAD = new HashSet<>(); FIELDS_TO_LOAD.add(PartitionKeyMapper.FIELD_NAME); FIELDS_TO_LOAD.add(ClusteringKeyMapper.FIELD_NAME); } /** The used row mapper. */ private final RowMapperWide rowMapper; /** * Returns a new {@code RowServiceWide} for manage wide rows. * * @param baseCfs The base column family store. * @param columnDefinition The indexed column definition. */ public RowServiceWide(ColumnFamilyStore baseCfs, ColumnDefinition columnDefinition) { super(baseCfs, columnDefinition); this.rowMapper = (RowMapperWide) super.rowMapper; luceneIndex.init(rowMapper.sort()); } /** * {@inheritDoc} * <p/> * These fields are the partition and clustering keys. */ @Override public Set<String> fieldsToLoad() { return FIELDS_TO_LOAD; } /** {@inheritDoc} */ @Override public void indexInner(ByteBuffer key, ColumnFamily columnFamily, long timestamp) { DeletionInfo deletionInfo = columnFamily.deletionInfo(); DecoratedKey partitionKey = rowMapper.partitionKey(key); if (columnFamily.iterator().hasNext()) { List<CellName> clusteringKeys = rowMapper.clusteringKeys(columnFamily); Map<CellName, Row> rows = rows(partitionKey, clusteringKeys, timestamp); for (Map.Entry<CellName, Row> entry : rows.entrySet()) { CellName clusteringKey = entry.getKey(); Row row = entry.getValue(); Document document = rowMapper.document(row); Term term = rowMapper.term(partitionKey, clusteringKey); luceneIndex.upsert(term, document); // Store document } } else if (deletionInfo != null) { Iterator<RangeTombstone> iterator = deletionInfo.rangeIterator(); if (iterator.hasNext()) { while (iterator.hasNext()) { RangeTombstone rangeTombstone = iterator.next(); Query query = rowMapper.query(partitionKey, rangeTombstone); luceneIndex.delete(query); } } else { Term term = rowMapper.term(partitionKey); luceneIndex.delete(term); } } } /** {@inheritDoc} */ @Override public void deleteInner(DecoratedKey partitionKey) { Term term = rowMapper.term(partitionKey); luceneIndex.delete(term); } /** * {@inheritDoc} * <p/> * The {@link Row} is a logical one. */ @Override protected List<Row> rows(List<SearchResult> searchResults, long timestamp, boolean usesRelevance) { // Initialize result List<Row> rows = new ArrayList<>(searchResults.size()); // Group key queries by partition keys Map<CellName, Float> scoresByClusteringKey = new HashMap<>(searchResults.size()); Map<DecoratedKey, List<CellName>> keys = new HashMap<>(); for (SearchResult searchResult : searchResults) { DecoratedKey partitionKey = searchResult.getPartitionKey(); CellName clusteringKey = searchResult.getClusteringKey(); Float score = searchResult.getScore(); scoresByClusteringKey.put(clusteringKey, score); List<CellName> clusteringKeys = keys.get(partitionKey); if (clusteringKeys == null) { clusteringKeys = new ArrayList<>(); keys.put(partitionKey, clusteringKeys); } clusteringKeys.add(clusteringKey); } for (Map.Entry<DecoratedKey, List<CellName>> entry : keys.entrySet()) { DecoratedKey partitionKey = entry.getKey(); for (List<CellName> clusteringKeys : Lists.partition(entry.getValue(), 1000)) { Map<CellName, Row> partitionRows = rows(partitionKey, clusteringKeys, timestamp); for (Map.Entry<CellName, Row> entry1 : partitionRows.entrySet()) { Row row = entry1.getValue(); if (usesRelevance) { CellName clusteringKey = entry1.getKey(); Float score = scoresByClusteringKey.get(clusteringKey); Row scoredRow = addScoreColumn(row, timestamp, score); rows.add(scoredRow); } else { rows.add(row); } } } } return rows; } /** * Returns the CQL3 {@link Row} identified by the specified key pair, using the specified time stamp to ignore * deleted columns. The {@link Row} is retrieved from the storage engine, so it involves IO operations. * * @param partitionKey The partition key. * @param clusteringKeys The clustering keys. * @param timestamp The time stamp to ignore deleted columns. * @return The CQL3 {@link Row} identified by the specified key pair. */ private Map<CellName, Row> rows(DecoratedKey partitionKey, List<CellName> clusteringKeys, long timestamp) { ColumnSlice[] slices = rowMapper.columnSlices(clusteringKeys); if (baseCfs.metadata.hasStaticColumns()) { LinkedList<ColumnSlice> l = new LinkedList<>(Arrays.asList(slices)); l.addFirst(baseCfs.metadata.comparator.staticPrefix().slice()); slices = new ColumnSlice[l.size()]; slices = l.toArray(slices); } SliceQueryFilter dataFilter = new SliceQueryFilter(slices, false, Integer.MAX_VALUE, baseCfs.metadata.clusteringColumns().size()); QueryFilter queryFilter = new QueryFilter(partitionKey, baseCfs.name, dataFilter, timestamp); ColumnFamily queryColumnFamily = baseCfs.getColumnFamily(queryFilter); // Avoid null if (queryColumnFamily == null) { return null; } // Remove deleted/expired columns ColumnFamily cleanQueryColumnFamily = cleanExpired(queryColumnFamily, timestamp); // Split CQL3 row column families Map<CellName, ColumnFamily> columnFamilies = rowMapper.splitRows(cleanQueryColumnFamily); // Build and return rows Map<CellName, Row> rows = new HashMap<>(columnFamilies.size()); for (Map.Entry<CellName, ColumnFamily> entry : columnFamilies.entrySet()) { Row row = new Row(partitionKey, entry.getValue()); rows.put(entry.getKey(), row); } return rows; } }