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

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

There is a newer version: 3.9.1
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.stratio.cassandra.lucene.search.Search;
import com.stratio.cassandra.lucene.util.TimeCounter;
import org.apache.cassandra.cql3.*;
import org.apache.cassandra.cql3.statements.BatchStatement;
import org.apache.cassandra.cql3.statements.IndexTarget;
import org.apache.cassandra.cql3.statements.ParsedStatement;
import org.apache.cassandra.cql3.statements.SelectStatement;
import org.apache.cassandra.db.ColumnFamilyStore;
import org.apache.cassandra.db.ConsistencyLevel;
import org.apache.cassandra.db.Keyspace;
import org.apache.cassandra.db.ReadQuery;
import org.apache.cassandra.db.filter.RowFilter;
import org.apache.cassandra.db.marshal.UTF8Type;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.service.QueryState;
import org.apache.cassandra.transport.messages.ResultMessage;
import org.apache.cassandra.utils.FBUtilities;
import org.apache.cassandra.utils.MD5Digest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Map;

import static org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull;

/**
 * {@link QueryHandler} to be used with Lucene searches.
 *
 * @author Andres de la Pena {@literal }
 */
class IndexQueryHandler implements QueryHandler {

    private static final Logger logger = LoggerFactory.getLogger(IndexQueryHandler.class);

    /** {@inheritDoc} */
    @Override
    public ResultMessage.Prepared prepare(String query, QueryState state, Map customPayload) {
        return QueryProcessor.instance.prepare(query, state);
    }

    /** {@inheritDoc} */
    @Override
    public ParsedStatement.Prepared getPrepared(MD5Digest id) {
        return QueryProcessor.instance.getPrepared(id);
    }

    /** {@inheritDoc} */
    @Override
    public ParsedStatement.Prepared getPreparedForThrift(Integer id) {
        return QueryProcessor.instance.getPreparedForThrift(id);
    }

    /** {@inheritDoc} */
    @Override
    public ResultMessage processBatch(BatchStatement statement,
                                      QueryState state,
                                      BatchQueryOptions options,
                                      Map customPayload) {
        return QueryProcessor.instance.processBatch(statement, state, options, customPayload);
    }

    /** {@inheritDoc} */
    @Override
    public ResultMessage processPrepared(CQLStatement statement,
                                         QueryState state,
                                         QueryOptions options,
                                         Map customPayload) {
        QueryProcessor.metrics.preparedStatementsExecuted.inc();
        return processStatement(statement, state, options);
    }

    /** {@inheritDoc} */
    @Override
    public ResultMessage process(String query,
                                 QueryState state,
                                 QueryOptions options,
                                 Map customPayload) {
        ParsedStatement.Prepared p = QueryProcessor.getStatement(query, state.getClientState());
        options.prepare(p.boundNames);
        CQLStatement prepared = p.statement;
        if (prepared.getBoundTerms() != options.getValues().size()) {
            throw new InvalidRequestException("Invalid amount of bind variables");
        }

        if (!state.getClientState().isInternal) {
            QueryProcessor.metrics.regularStatementsExecuted.inc();
        }

        return processStatement(prepared, state, options);
    }

    private ResultMessage processStatement(CQLStatement statement, QueryState state, QueryOptions options) {

        logger.trace("Process {} @CL.{}", statement, options.getConsistency());
        ClientState clientState = state.getClientState();
        statement.checkAccess(clientState);
        statement.validate(clientState);

        // Intercept Lucene index searches
        if (statement instanceof SelectStatement) {
            SelectStatement select = (SelectStatement) statement;
            List expressions = select.getRowFilter(options).getExpressions();
            for (RowFilter.Expression expression : expressions) {
                if (expression.isCustom()) {
                    RowFilter.CustomExpression customExpression = (RowFilter.CustomExpression) expression;
                    String clazz = customExpression.getTargetIndex().options.get(IndexTarget.CUSTOM_INDEX_OPTION_NAME);
                    if (clazz.equals(Index.class.getCanonicalName())) {
                        TimeCounter time = TimeCounter.create().start();
                        try {
                            return process(select, state, options, customExpression);
                        } catch (ReflectiveOperationException e) {
                            throw new IndexException(e);
                        } finally {
                            logger.debug("Lucene search total time: {}\n", time.stop());
                        }
                    }
                }
            }
        }

        return execute(statement, state, options);
    }

    private ResultMessage process(SelectStatement select,
                                  QueryState state,
                                  QueryOptions options,
                                  RowFilter.CustomExpression expression) throws ReflectiveOperationException {

        // Validate expression
        ColumnFamilyStore cfs = Keyspace.open(select.keyspace()).getColumnFamilyStore(select.columnFamily());
        Index index = (Index) cfs.indexManager.getIndex(expression.getTargetIndex());
        Search search = index.validate(expression);

        // Check paging
        int limit = select.getLimit(options);
        int page = getPageSize(select, options);

        if (search.isTopK()) {

            // Avoid IN operator
            if (select.getRestrictions().getPartitionKeys(options).size() > 1) {
                throw new InvalidRequestException(
                        "Top-k searches can't be directed to more than one partition, you should either " +
                        "direct them to one single partition or don't use any partition restrictions.");
            }

            // Avoid unlimited
            if (limit == Integer.MAX_VALUE) {
                throw new InvalidRequestException(
                        "Top-k searches don't support paging, so a cautious LIMIT clause should be provided " +
                        "to prevent excessive memory consumption.");
            } else if (page < limit) {
                String json = UTF8Type.instance.compose(expression.getValue());
                logger.warn("Disabling paging of {} rows per page for top-k search requesting {} rows: {}",
                            page,
                            limit,
                            json);
                return executeWithoutPaging(select, state, options);
            }
        }

        // Process
        return execute(select, state, options);
    }

    private ResultMessage execute(CQLStatement statement, QueryState state, QueryOptions options) {
        ResultMessage result = statement.execute(state, options);
        return result == null ? new ResultMessage.Void() : result;
    }

    private int getPageSize(SelectStatement select, QueryOptions options) throws ReflectiveOperationException {
        Method method = select.getClass().getDeclaredMethod("getPageSize", QueryOptions.class);
        method.setAccessible(true);
        return (int) method.invoke(select, options);
    }

    private ResultMessage.Rows executeWithoutPaging(SelectStatement select, QueryState state, QueryOptions options)
    throws ReflectiveOperationException {

        ConsistencyLevel cl = options.getConsistency();
        checkNotNull(cl, "Invalid empty consistency level");

        cl.validateForRead(select.keyspace());

        int nowInSec = FBUtilities.nowInSeconds();
        int userLimit = select.getLimit(options);
        ReadQuery query = select.getQuery(options, nowInSec, userLimit);

        Method method = select.getClass()
                              .getDeclaredMethod("execute",
                                                 ReadQuery.class,
                                                 QueryOptions.class,
                                                 QueryState.class,
                                                 int.class,
                                                 int.class);
        method.setAccessible(true);
        return (ResultMessage.Rows) method.invoke(select, query, options, state, nowInSec, userLimit);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy