All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.stratio.cassandra.lucene.IndexPagingState Maven / Gradle / Ivy

There is a newer version: 3.11.3.0
Show newest version
/*
 * Copyright (C) 2014 Stratio (http://stratio.com)
 *
 * 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.lucene;

import com.google.common.base.MoreObjects;
import com.stratio.cassandra.lucene.search.SearchBuilder;
import com.stratio.cassandra.lucene.util.SimplePartitionIterator;
import com.stratio.cassandra.lucene.util.SimpleRowIterator;
import org.apache.cassandra.config.DatabaseDescriptor;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.db.partitions.PartitionIterator;
import org.apache.cassandra.db.rows.RowIterator;
import org.apache.cassandra.dht.AbstractBounds;
import org.apache.cassandra.service.LuceneStorageProxy;
import org.apache.cassandra.service.pager.PagingState;
import org.apache.cassandra.utils.Pair;

import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.util.*;

import static com.stratio.cassandra.lucene.util.ByteBufferUtils.compose;
import static com.stratio.cassandra.lucene.util.ByteBufferUtils.decompose;
import static java.util.stream.Collectors.toList;
import static org.apache.cassandra.db.SinglePartitionReadCommand.Group;
import static org.apache.cassandra.db.filter.RowFilter.Expression;
import static org.apache.cassandra.service.LuceneStorageProxy.RangeMerger;

/**
 * The paging state of a CQL query using Lucene. It tracks the primary keys of the last seen rows for each internal read
 * command of a CQL query. It also keeps the count of the remaining rows. This state can be serialized to be attached to
 * a CQL {@link PagingState} and/or to a search predicate.
 *
 * @author Andres de la Pena {@literal }
 */
public class IndexPagingState {

    /** The number of remaining rows. */
    private int remaining;

    /** If there could be more results. */
    private boolean hasMorePages = true;

    /** The last row positions */
    private final Map entries;

    /**
     * Constructor taking the remaining rows.
     *
     * @param remaining the number of remaining rows to be retrieved
     */
    private IndexPagingState(int remaining) {
        this.remaining = remaining;
        this.entries = new LinkedHashMap<>();
    }

    /**
     * Returns the number of remaining rows.
     *
     * @return the number of remaining rows
     */
    int remaining() {
        return remaining;
    }

    private void clear(AbstractBounds bounds) {
        List keysToRemove = new LinkedList<>();
        for (Map.Entry entry : entries.entrySet()) {
            DecoratedKey key = entry.getKey();
            if (bounds.contains(key)) {
                keysToRemove.add(key);
            }
        }
        keysToRemove.forEach(entries::remove);
    }

    /**
     * Returns the primary key of the last seen row for the specified {@link ReadCommand}.
     *
     * @param command a read command
     * @return the primary key of the last seen row for {@code command}
     */
    Pair forCommand(ReadCommand command) {
        if (command instanceof SinglePartitionReadCommand) {
            return forCommand((SinglePartitionReadCommand) command);
        } else if (command instanceof PartitionRangeReadCommand) {
            return forCommand((PartitionRangeReadCommand) command);
        } else {
            throw new IndexException("Unsupported read command type: {}", command.getClass());
        }
    }

    private Pair forCommand(SinglePartitionReadCommand command) {
        DecoratedKey key = command.partitionKey();
        Clustering clustering = entries.get(key);
        if (clustering == null) {
            return null;
        }
        return Pair.create(key, clustering);
    }

    private Pair forCommand(PartitionRangeReadCommand command) {
        DataRange dataRange = command.dataRange();
        for (Map.Entry entry : entries.entrySet()) {
            DecoratedKey key = entry.getKey();
            if (dataRange.contains(key)) {
                Clustering clustering = entry.getValue();
                return Pair.create(key, clustering);
            }
        }
        return null;
    }

    /**
     * Adds this paging state to the specified {@link ReadQuery}.
     *
     * @param query a CQL query using the Lucene index
     * @throws ReflectiveOperationException if there is any problem with reflection
     */
    void rewrite(ReadQuery query) throws ReflectiveOperationException {
        if (query instanceof Group) {
            rewrite((Group) query);
        } else if (query instanceof ReadCommand) {
            rewrite((ReadCommand) query);
        } else {
            throw new IndexException("Unsupported query type {}", query.getClass());
        }
    }

    private void rewrite(ReadCommand command) throws ReflectiveOperationException {

        Field field = Expression.class.getDeclaredField("value");
        field.setAccessible(true);

        Expression expression = expression(command);

        ByteBuffer value = (ByteBuffer) field.get(expression);
        SearchBuilder searchBuilder = SearchBuilder.fromJson(UTF8Type.instance.compose(value));
        searchBuilder.paging(this);
        ByteBuffer newValue = UTF8Type.instance.decompose(searchBuilder.toJson());
        field.set(expression, newValue);
    }

    private Expression expression(ReadCommand command) throws ReflectiveOperationException {

        // Try with custom expressions
        for (Expression expression : command.rowFilter().getExpressions()) {
            if (expression.isCustom()) {
                return expression;
            }
        }

        // Try with dummy column
        ColumnFamilyStore cfs = Keyspace.open(command.metadata().ksName)
                                        .getColumnFamilyStore(command.metadata().cfName);
        for (Expression expression : command.rowFilter().getExpressions()) {
            for (org.apache.cassandra.index.Index index : cfs.indexManager.listIndexes()) {
                if (index instanceof Index && index.supportsExpression(expression.column(), expression.operator())) {
                    return expression;
                }
            }
        }

        return null;
    }

    private void rewrite(Group group) throws ReflectiveOperationException {
        for (SinglePartitionReadCommand command : group.commands) {
            rewrite(command);
        }
    }

    /**
     * Returns a CQL {@link PagingState} containing this Lucene paging state.
     *
     * @return a CQL paging state
     */
    PagingState toPagingState() {
        return hasMorePages ? new PagingState(toByteBuffer(), null, remaining, remaining) : null;
    }

    /**
     * Returns the Lucene paging state contained in the specified CQL {@link PagingState}. If the specified paging state
     * is {@code null}, then an empty Lucene paging state will be returned.
     *
     * @param state a CQL paging state
     * @param limit the query user limit
     * @return a Lucene paging state
     */
    static IndexPagingState build(PagingState state, int limit) {
        return state == null ? new IndexPagingState(limit) : build(state.partitionKey);
    }

    /**
     * Updates this paging state with the results of the specified query.
     *
     * @param query the query
     * @param data the results of {@code query}
     * @param consistency the query consistency level
     * @return a copy of the query results
     */
    PartitionIterator update(ReadQuery query, PartitionIterator data, ConsistencyLevel consistency) {
        if (query instanceof SinglePartitionReadCommand.Group) {
            return update((SinglePartitionReadCommand.Group) query, data);
        } else if (query instanceof PartitionRangeReadCommand) {
            return update((PartitionRangeReadCommand) query, data, consistency);
        } else {
            throw new IndexException("Unsupported query type {}", query.getClass());
        }
    }

    private PartitionIterator update(Group group, PartitionIterator data) {
        List rowIterators = new LinkedList<>();

        int count = 0;
        while (data.hasNext()) {
            RowIterator partition = data.next();
            DecoratedKey key = partition.partitionKey();
            while (partition.hasNext()) {
                SimpleRowIterator newRowIterator = new SimpleRowIterator(partition);
                rowIterators.add(newRowIterator);
                Clustering clustering = newRowIterator.getRow().clustering();
                entries.put(key, clustering);
                if (remaining > 0) {
                    remaining--;
                }
                count++;
            }
            partition.close();
        }
        data.close();

        hasMorePages = remaining > 0 && count >= group.limits().count();

        return new SimplePartitionIterator(rowIterators);
    }

    private PartitionIterator update(PartitionRangeReadCommand command, PartitionIterator data, ConsistencyLevel cl) {

        // Collect query bounds
        RangeMerger rangeMerger = LuceneStorageProxy.rangeMerger(command, cl);
        List> bounds = new LinkedList<>();
        while (rangeMerger.hasNext()) {
            bounds.add(rangeMerger.next().range);
        }

        List rowIterators = new LinkedList<>();
        int count = 0;
        while (data.hasNext()) {
            RowIterator partition = data.next();
            DecoratedKey key = partition.partitionKey();
            AbstractBounds bound = bounds.stream()
                                                            .filter(b -> b.contains(key))
                                                            .findAny()
                                                            .orElseGet(null);
            while (partition.hasNext()) {
                clear(bound);
                SimpleRowIterator newRowIterator = new SimpleRowIterator(partition);
                rowIterators.add(newRowIterator);
                Clustering clustering = newRowIterator.getRow().clustering();
                entries.put(key, clustering);
                if (remaining > 0) {
                    remaining--;
                }
                count++;
            }
            partition.close();
        }
        data.close();

        hasMorePages = remaining > 0 && count >= command.limits().count();

        return new SimplePartitionIterator(rowIterators);
    }

    /** {@inheritDoc} */
    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this).add("remaining", remaining).add("entries", entries).toString();
    }

    /**
     * Returns a byte buffer representation of this. Thew returned result can be read with {@link #build(ByteBuffer)}.
     *
     * @return a byte buffer representing this
     */
    public ByteBuffer toByteBuffer() {
        ByteBuffer[] entryValues = new ByteBuffer[entries.size()];
        entries.entrySet().stream().map(entry -> {
            DecoratedKey key = entry.getKey();
            Clustering clustering = entry.getValue();
            ByteBuffer[] clusteringValues = clustering.getRawValues();
            ByteBuffer[] values = new ByteBuffer[1 + clusteringValues.length];
            values[0] = key.getKey();
            System.arraycopy(clusteringValues, 0, values, 1, clusteringValues.length);
            return compose(values);
        }).collect(toList()).toArray(entryValues);
        ByteBuffer values = compose(entryValues);
        ByteBuffer out = ByteBuffer.allocate(4 + values.remaining());
        out.putInt(remaining);
        out.put(values);
        out.flip();
        return out;
    }

    /**
     * Returns the paging state represented by the specified byte buffer, which should have been generated with {@link
     * #toByteBuffer()}.
     *
     * @param bb a byte buffer generated by {@link #toByteBuffer()}
     * @return the paging state represented by {@code bb}
     */
    public static IndexPagingState build(ByteBuffer bb) {
        int remaining = bb.getInt();
        IndexPagingState state = new IndexPagingState(remaining);
        Arrays.stream(decompose(bb))
              .forEach(bbe -> {
                  ByteBuffer[] values = decompose(bbe);
                  DecoratedKey key = DatabaseDescriptor.getPartitioner().decorateKey(values[0]);
                  Clustering clustering = Clustering.make(Arrays.copyOfRange(values, 1, values.length));
                  state.entries.put(key, clustering);
              });
        return state;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy