/* * 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.service.pager; import java.util.ArrayList; import java.util.List; import org.apache.cassandra.db.*; import org.apache.cassandra.exceptions.RequestValidationException; import org.apache.cassandra.exceptions.RequestExecutionException; /** * Pager over a list of ReadCommand. * * Note that this is not easy to make efficient. Indeed, we need to page the first command fully before * returning results from the next one, but if the result returned by each command is small (compared to pageSize), * paging the commands one at a time under-performs compared to parallelizing. On the other, if we parallelize * and each command raised pageSize results, we'll end up with commands.size() * pageSize results in memory, which * defeats the purpose of paging. * * For now, we keep it simple (somewhat) and just do one command at a time. Provided that we make sure to not * create a pager unless we need to, this is probably fine. Though if we later want to get fancy, we could use the * cfs meanRowSize to decide if parallelizing some of the command might be worth it while being confident we don't * blow out memory. */ class MultiPartitionPager implements QueryPager { private final SinglePartitionPager[] pagers; private final long timestamp; private volatile int current; MultiPartitionPager(List<ReadCommand> commands, ConsistencyLevel consistencyLevel, boolean localQuery) { this(commands, consistencyLevel, localQuery, null); } MultiPartitionPager(List<ReadCommand> commands, ConsistencyLevel consistencyLevel, boolean localQuery, PagingState state) { this.pagers = new SinglePartitionPager[commands.size()]; long tstamp = -1; for (int i = 0; i < commands.size(); i++) { ReadCommand command = commands.get(i); if (tstamp == -1) tstamp = command.timestamp; else if (tstamp != command.timestamp) throw new IllegalArgumentException("All commands must have the same timestamp or weird results may happen."); PagingState tmpState = state != null && command.key.equals(state.partitionKey) ? state : null; pagers[i] = command instanceof SliceFromReadCommand ? new SliceQueryPager((SliceFromReadCommand)command, consistencyLevel, localQuery, tmpState) : new NamesQueryPager((SliceByNamesReadCommand)command, consistencyLevel, localQuery, tmpState); } timestamp = tstamp; } public PagingState state() { PagingState state = pagers[current].state(); return state == null ? null : new PagingState(state.partitionKey, state.cellName, maxRemaining()); } public boolean isExhausted() { while (current < pagers.length) { if (!pagers[current].isExhausted()) return false; current++; } return true; } public List<Row> fetchPage(int pageSize) throws RequestValidationException, RequestExecutionException { int remaining = pageSize; List<Row> result = new ArrayList<Row>(); while (!isExhausted() && remaining > 0) { // Exhausted also sets us on the first non-exhausted pager List<Row> page = pagers[current].fetchPage(remaining); if (page.isEmpty()) continue; Row row = page.get(0); remaining -= pagers[current].columnCounter().countAll(row.cf).live(); result.add(row); } return result; } public int maxRemaining() { int max = 0; for (int i = current; i < pagers.length; i++) max += pagers[i].maxRemaining(); return max; } public long timestamp() { return timestamp; } }