/* * 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.cassandra.index.internal.keys; import java.nio.ByteBuffer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.cassandra.config.CFMetaData; import org.apache.cassandra.config.ColumnDefinition; import org.apache.cassandra.db.*; import org.apache.cassandra.db.filter.ColumnFilter; import org.apache.cassandra.db.filter.DataLimits; import org.apache.cassandra.db.filter.RowFilter; import org.apache.cassandra.db.partitions.ImmutableBTreePartition; import org.apache.cassandra.db.partitions.UnfilteredPartitionIterator; import org.apache.cassandra.db.rows.*; import org.apache.cassandra.index.internal.CassandraIndex; import org.apache.cassandra.index.internal.CassandraIndexSearcher; import org.apache.cassandra.utils.concurrent.OpOrder; public class KeysSearcher extends CassandraIndexSearcher { private static final Logger logger = LoggerFactory.getLogger(KeysSearcher.class); public KeysSearcher(ReadCommand command, RowFilter.Expression expression, CassandraIndex indexer) { super(command, expression, indexer); } protected UnfilteredPartitionIterator queryDataFromIndex(final DecoratedKey indexKey, final RowIterator indexHits, final ReadCommand command, final ReadOrderGroup orderGroup) { assert indexHits.staticRow() == Rows.EMPTY_STATIC_ROW; return new UnfilteredPartitionIterator() { private UnfilteredRowIterator next; public boolean isForThrift() { return command.isForThrift(); } public CFMetaData metadata() { return command.metadata(); } public boolean hasNext() { return prepareNext(); } public UnfilteredRowIterator next() { if (next == null) prepareNext(); UnfilteredRowIterator toReturn = next; next = null; return toReturn; } private boolean prepareNext() { while (next == null && indexHits.hasNext()) { Row hit = indexHits.next(); DecoratedKey key = index.baseCfs.decorateKey(hit.clustering().get(0)); if (!command.selectsKey(key)) continue; ColumnFilter extendedFilter = getExtendedFilter(command.columnFilter()); SinglePartitionReadCommand dataCmd = SinglePartitionReadCommand.create(isForThrift(), index.baseCfs.metadata, command.nowInSec(), extendedFilter, command.rowFilter(), DataLimits.NONE, key, command.clusteringIndexFilter(key)); @SuppressWarnings("resource") // filterIfStale closes it's iterator if either it materialize it or if it returns null. // Otherwise, we close right away if empty, and if it's assigned to next it will be called either // by the next caller of next, or through closing this iterator is this come before. UnfilteredRowIterator dataIter = filterIfStale(dataCmd.queryMemtableAndDisk(index.baseCfs, orderGroup.baseReadOpOrderGroup()), hit, indexKey.getKey(), orderGroup.writeOpOrderGroup(), isForThrift(), command.nowInSec()); if (dataIter != null) { if (dataIter.isEmpty()) dataIter.close(); else next = dataIter; } } return next != null; } public void remove() { throw new UnsupportedOperationException(); } public void close() { indexHits.close(); if (next != null) next.close(); } }; } private ColumnFilter getExtendedFilter(ColumnFilter initialFilter) { if (command.columnFilter().includes(index.getIndexedColumn())) return initialFilter; ColumnFilter.Builder builder = ColumnFilter.selectionBuilder(); builder.addAll(initialFilter.fetchedColumns()); builder.add(index.getIndexedColumn()); return builder.build(); } private UnfilteredRowIterator filterIfStale(UnfilteredRowIterator iterator, Row indexHit, ByteBuffer indexedValue, OpOrder.Group writeOp, boolean isForThrift, int nowInSec) { if (isForThrift) { // The data we got has gone though ThrifResultsMerger, so we're looking for the row whose clustering // is the indexed name and so we need to materialize the partition. ImmutableBTreePartition result = ImmutableBTreePartition.create(iterator); iterator.close(); Row data = result.getRow(new Clustering(index.getIndexedColumn().name.bytes)); if (data == null) return null; // for thrift tables, we need to compare the index entry against the compact value column, // not the column actually designated as the indexed column so we don't use the index function // lib for the staleness check like we do in every other case Cell baseData = data.getCell(index.baseCfs.metadata.compactValueColumn()); if (baseData == null || !baseData.isLive(nowInSec) || index.getIndexedColumn().type.compare(indexedValue, baseData.value()) != 0) { // Index is stale, remove the index entry and ignore index.deleteStaleEntry(index.getIndexCfs().decorateKey(indexedValue), new Clustering(index.getIndexedColumn().name.bytes), new DeletionTime(indexHit.primaryKeyLivenessInfo().timestamp(), nowInSec), writeOp); return null; } else { if (command.columnFilter().includes(index.getIndexedColumn())) return result.unfilteredIterator(); // The query on the base table used an extended column filter to ensure that the // indexed column was actually read for use in the staleness check, before // returning the results we must filter the base table partition so that it // contains only the originally requested columns. See CASSANDRA-11523 ClusteringComparator comparator = result.metadata().comparator; Slices.Builder slices = new Slices.Builder(comparator); for (ColumnDefinition selected : command.columnFilter().fetchedColumns()) slices.add(Slice.make(comparator, selected.name.bytes)); return result.unfilteredIterator(ColumnFilter.all(command.metadata()), slices.build(), false); } } else { assert iterator.metadata().isCompactTable(); Row data = iterator.staticRow(); if (index.isStale(data, indexedValue, nowInSec)) { // Index is stale, remove the index entry and ignore index.deleteStaleEntry(index.getIndexCfs().decorateKey(indexedValue), makeIndexClustering(iterator.partitionKey().getKey(), Clustering.EMPTY), new DeletionTime(indexHit.primaryKeyLivenessInfo().timestamp(), nowInSec), writeOp); iterator.close(); return null; } else { return iterator; } } } }