Package org.apache.cassandra.service.pager

Source Code of org.apache.cassandra.service.pager.AbstractQueryPager

/*
* 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.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Iterator;

import org.apache.cassandra.config.CFMetaData;
import org.apache.cassandra.config.Schema;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.filter.ColumnCounter;
import org.apache.cassandra.db.filter.IDiskAtomFilter;
import org.apache.cassandra.exceptions.RequestExecutionException;
import org.apache.cassandra.exceptions.RequestValidationException;

abstract class AbstractQueryPager implements QueryPager
{
    private final ConsistencyLevel consistencyLevel;
    private final boolean localQuery;

    protected final CFMetaData cfm;
    protected final IDiskAtomFilter columnFilter;
    private final long timestamp;

    private volatile int remaining;
    private volatile boolean exhausted;
    private volatile boolean lastWasRecorded;

    protected AbstractQueryPager(ConsistencyLevel consistencyLevel,
                                 int toFetch,
                                 boolean localQuery,
                                 String keyspace,
                                 String columnFamily,
                                 IDiskAtomFilter columnFilter,
                                 long timestamp)
    {
        this.consistencyLevel = consistencyLevel;
        this.localQuery = localQuery;

        this.cfm = Schema.instance.getCFMetaData(keyspace, columnFamily);
        this.columnFilter = columnFilter;
        this.timestamp = timestamp;

        this.remaining = toFetch;
    }

    public List<Row> fetchPage(int pageSize) throws RequestValidationException, RequestExecutionException
    {
        if (isExhausted())
            return Collections.emptyList();

        int currentPageSize = nextPageSize(pageSize);
        List<Row> rows = filterEmpty(queryNextPage(currentPageSize, consistencyLevel, localQuery));

        if (rows.isEmpty())
        {
            exhausted = true;
            return Collections.emptyList();
        }

        int liveCount = getPageLiveCount(rows);
        remaining -= liveCount;

        // If we've got less than requested, there is no more query to do (but
        // we still need to return the current page)
        if (liveCount < currentPageSize)
            exhausted = true;

        // If it's not the first query and the first column is the last one returned (likely
        // but not certain since paging can race with deletes/expiration), then remove the
        // first column.
        if (containsPreviousLast(rows.get(0)))
        {
            rows = discardFirst(rows);
            remaining++;
        }
        // Otherwise, if 'lastWasRecorded', we queried for one more than the page size,
        // so if the page was is full, trim the last entry
        else if (lastWasRecorded && !exhausted)
        {
            // We've asked for one more than necessary
            rows = discardLast(rows);
            remaining++;
        }

        if (!isExhausted())
            lastWasRecorded = recordLast(rows.get(rows.size() - 1));

        return rows;
    }

    private List<Row> filterEmpty(List<Row> result)
    {
        for (Row row : result)
        {
            if (row.cf == null || row.cf.getColumnCount() == 0)
            {
                List<Row> newResult = new ArrayList<Row>(result.size() - 1);
                for (Row row2 : result)
                {
                    if (row.cf == null || row.cf.getColumnCount() == 0)
                        continue;

                    newResult.add(row2);
                }
                return newResult;
            }
        }
        return result;
    }

    protected void restoreState(int remaining, boolean lastWasRecorded)
    {
        this.remaining = remaining;
        this.lastWasRecorded = lastWasRecorded;
    }

    public boolean isExhausted()
    {
        return exhausted || remaining == 0;
    }

    public int maxRemaining()
    {
        return remaining;
    }

    public long timestamp()
    {
        return timestamp;
    }

    private int nextPageSize(int pageSize)
    {
        return Math.min(remaining, pageSize) + (lastWasRecorded ? 1 : 0);
    }

    public ColumnCounter columnCounter()
    {
        return columnFilter.columnCounter(cfm.comparator, timestamp);
    }

    protected abstract List<Row> queryNextPage(int pageSize, ConsistencyLevel consistency, boolean localQuery) throws RequestValidationException, RequestExecutionException;
    protected abstract boolean containsPreviousLast(Row first);
    protected abstract boolean recordLast(Row last);

    private List<Row> discardFirst(List<Row> rows)
    {
        Row first = rows.get(0);
        ColumnFamily newCf = discardFirst(first.cf);

        int count = newCf.getColumnCount();
        List<Row> newRows = new ArrayList<Row>(count == 0 ? rows.size() - 1 : rows.size());
        if (count != 0)
            newRows.add(new Row(first.key, newCf));
        newRows.addAll(rows.subList(1, rows.size()));

        return newRows;
    }

    private List<Row> discardLast(List<Row> rows)
    {
        Row last = rows.get(rows.size() - 1);
        ColumnFamily newCf = discardLast(last.cf);

        int count = newCf.getColumnCount();
        List<Row> newRows = new ArrayList<Row>(count == 0 ? rows.size() - 1 : rows.size());
        newRows.addAll(rows.subList(0, rows.size() - 1));
        if (count != 0)
            newRows.add(new Row(last.key, newCf));

        return newRows;
    }

    private int getPageLiveCount(List<Row> page)
    {
        int count = 0;
        for (Row row : page)
            count += columnCounter().countAll(row.cf).live();
        return count;
    }

    private ColumnFamily discardFirst(ColumnFamily cf)
    {
        ColumnFamily copy = cf.cloneMeShallow();
        ColumnCounter counter = columnCounter();

        Iterator<Column> iter = cf.iterator();
        DeletionInfo.InOrderTester tester = cf.inOrderDeletionTester();
        // Discard the first live
        while (iter.hasNext())
        {
            Column c = iter.next();
            counter.count(c, tester);
            if (counter.live() > 1)
            {
                copy.addColumn(c);
                while (iter.hasNext())
                    copy.addColumn(iter.next());
            }
        }
        return copy;
    }

    private ColumnFamily discardLast(ColumnFamily cf)
    {
        ColumnFamily copy = cf.cloneMeShallow();
        // Redoing the counting like that is not extremely efficient, but
        // discardLast is only called in case of a race between paging and
        // a deletion, which is pretty unlikely, so probably not a big deal
        int liveCount = columnCounter().countAll(cf).live();

        ColumnCounter counter = columnCounter();
        DeletionInfo.InOrderTester tester = cf.inOrderDeletionTester();
        // Discard the first live
        for (Column c : cf)
        {
            counter.count(c, tester);
            if (counter.live() < liveCount)
                copy.addColumn(c);
        }
        return copy;
    }

    protected static ByteBuffer firstName(ColumnFamily cf)
    {
        return cf.iterator().next().name();
    }

    protected static ByteBuffer lastName(ColumnFamily cf)
    {
        return cf.getReverseSortedColumns().iterator().next().name();
    }
}
TOP

Related Classes of org.apache.cassandra.service.pager.AbstractQueryPager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.