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

org.apache.jackrabbit.oak.plugins.index.property.OrderedPropertyIndex Maven / Gradle / Ivy

/*
 * 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.jackrabbit.oak.plugins.index.property;

import static org.apache.jackrabbit.oak.plugins.index.property.OrderedIndex.TYPE;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.jackrabbit.oak.api.PropertyValue;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.commons.PathUtils;
import org.apache.jackrabbit.oak.spi.query.Cursor;
import org.apache.jackrabbit.oak.spi.query.Cursors;
import org.apache.jackrabbit.oak.spi.query.Filter;
import org.apache.jackrabbit.oak.spi.query.Filter.PropertyRestriction;
import org.apache.jackrabbit.oak.spi.query.QueryIndex.AdvancedQueryIndex;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.ImmutableList;

/**
 * A property index that supports ordering keys.
 */
public class OrderedPropertyIndex extends PropertyIndex implements AdvancedQueryIndex {
    private static final Logger LOG = LoggerFactory.getLogger(OrderedPropertyIndex.class);
    
    @Override
    public String getIndexName() {
        return TYPE;
    }

    @Override
    PropertyIndexLookup getLookup(NodeState root) {
        return new OrderedPropertyIndexLookup(root);
    }

    /**
     * retrieve the cost for the query.
     * 
     * !!! for now we want to skip the use-case of NON range-queries !!!
     */
    @Override
    public double getCost(Filter filter, NodeState root) {
        throw new UnsupportedOperationException("Not supported as implementing AdvancedQueryIndex");
    }
    
    /**
     * @return an builder with some initial common settings
     */
    private static IndexPlan.Builder getIndexPlanBuilder(final Filter filter) {
        IndexPlan.Builder b = new IndexPlan.Builder();
        b.setCostPerExecution(1); // we're local. Low-cost
        // we're local but slightly more expensive than a standard PropertyIndex
        b.setCostPerEntry(1.3);
        b.setFulltextIndex(false); // we're never full-text
        b.setIncludesNodeData(false); // we should not include node data
        b.setFilter(filter);
        // TODO it's synch for now but we should investigate the indexMeta
        b.setDelayed(false);
        return b;
    }

    @Override
    public List getPlans(Filter filter, List sortOrder, NodeState root) {
        if (LOG.isDebugEnabled()) {
            LOG.debug("getPlans(Filter, List, NodeState)");
            LOG.debug("getPlans() - filter: {} - ", filter);
            LOG.debug("getPlans() - sortOrder: {} - ", sortOrder);
            LOG.debug("getPlans() - rootState: {} - ", root);
        }
        List plans = new ArrayList();
        if (filter.getFullTextConstraint() != null) {
            // not an appropriate index for full-text search
            return plans;
        }
        if (filter.containsNativeConstraint()) {
            // not an appropriate index for native search
            return plans;
        }

        PropertyIndexLookup pil = getLookup(root);
        if (pil instanceof OrderedPropertyIndexLookup) {
            OrderedPropertyIndexLookup lookup = (OrderedPropertyIndexLookup) pil;
            Collection restrictions = filter.getPropertyRestrictions();

            // first we process the sole orders as we could be in a situation where we don't have
            // a where condition indexed but we do for order. In that case we will return always the
            // whole index
            if (sortOrder != null) {
                for (OrderEntry oe : sortOrder) {
                    String propertyName = PathUtils.getName(oe.getPropertyName());
                    if (lookup.isIndexed(propertyName, "/", filter)) {
                        IndexPlan.Builder b = getIndexPlanBuilder(filter);
                        b.setSortOrder(ImmutableList.of(new OrderEntry(
                                oe.getPropertyName(),
                                Type.UNDEFINED,
                                lookup.isAscending(root, propertyName, filter) ? OrderEntry.Order.ASCENDING
                                        : OrderEntry.Order.DESCENDING)));
                        b.setEstimatedEntryCount(lookup.getEstimatedEntryCount(propertyName, null,
                            filter, null));
                        IndexPlan plan = b.build();
                        LOG.debug("plan: {}", plan);
                        plans.add(plan);
                    }
                }
            }

            // then we add plans for each restriction that could apply to us
            for (Filter.PropertyRestriction pr : restrictions) {
                String propertyName = PathUtils.getName(pr.propertyName);
                if (lookup.isIndexed(propertyName, "/", filter)) {
                    PropertyValue value = null;
                    boolean createPlan = false;
                    if (pr.first == null && pr.last == null) {
                        // open query: [property] is not null
                        value = null;
                        createPlan = true;
                    } else if (pr.first != null && pr.first.equals(pr.last) && pr.firstIncluding
                               && pr.lastIncluding) {
                        // [property]=[value]
                        value = pr.first;
                        createPlan = true;
                    } else if (pr.first != null && !pr.first.equals(pr.last)) {
                        // '>' & '>=' use cases
                        value = pr.first;
                        createPlan = true;
                    } else if (pr.last != null && !pr.last.equals(pr.first)) {
                        // '<' & '<='
                        value = pr.last;
                        createPlan = true;
                    }
                    if (createPlan) {
                        // we always return a sorted set
                        IndexPlan.Builder b = getIndexPlanBuilder(filter);
                        b.setSortOrder(ImmutableList.of(new OrderEntry(
                            propertyName,
                            Type.UNDEFINED,
                            lookup.isAscending(root, propertyName, filter) ? OrderEntry.Order.ASCENDING
                                                                           : OrderEntry.Order.DESCENDING)));
                        long count = lookup.getEstimatedEntryCount(propertyName, value, filter, pr);
                        b.setEstimatedEntryCount(count);
                        LOG.debug("estimatedCount: {}", count);

                        IndexPlan plan = b.build();
                        LOG.debug("plan: {}", plan);
                        plans.add(plan);
                    }
                }
            }
        } else {
            LOG.error("Without an OrderedPropertyIndexLookup you should not end here");
        }

        return plans;
    }

    @Override
    public String getPlanDescription(IndexPlan plan) {
        LOG.debug("getPlanDescription() - plan: {}", plan);
        LOG.error("Not implemented yet");
        throw new UnsupportedOperationException("Not implemented yet.");
    }

    @Override
    public Cursor query(IndexPlan plan, NodeState root) {
        LOG.debug("query(IndexPlan, NodeState)");
        LOG.debug("query() - plan: {}", plan);
        LOG.debug("query() - rootState: {}", root);

        Filter filter = plan.getFilter();
        List sortOrder = plan.getSortOrder();
        Iterable paths = null;
        Cursor cursor = null;
        PropertyIndexLookup pil = getLookup(root);
        if (pil instanceof OrderedPropertyIndexLookup) {
            OrderedPropertyIndexLookup lookup = (OrderedPropertyIndexLookup) pil;
            Collection prs = filter.getPropertyRestrictions();
            int depth = 1;
            for (PropertyRestriction pr : prs) {
                String propertyName = PathUtils.getName(pr.propertyName);
                depth = PathUtils.getDepth(pr.propertyName);
                if (lookup.isIndexed(propertyName, "/", filter)) {
                    paths = lookup.query(filter, propertyName, pr);
                } 
            }
            if (paths == null && sortOrder != null && !sortOrder.isEmpty()) {
                // we could be here if we have a query where the ORDER BY makes us play it.
                for (OrderEntry oe : sortOrder) {
                    String propertyName = PathUtils.getName(oe.getPropertyName());
                    depth = PathUtils.getDepth(oe.getPropertyName());
                    if (lookup.isIndexed(propertyName, "/", null)) {
                        paths = lookup.query(filter, propertyName, new PropertyRestriction());
                    }
                }
            }
            if (paths == null) {
                // if still here then something went wrong.
                throw new IllegalStateException(
                    "OrderedPropertyIndex index is used even when no index is available for filter "
                        + filter);
            }
            cursor = Cursors.newPathCursor(paths, filter.getQueryEngineSettings());
            if (depth > 1) {
                cursor = Cursors.newAncestorCursor(cursor, depth - 1, filter.getQueryEngineSettings());
            }
        } else {
            // if for some reasons it's not an Ordered Lookup we delegate up the chain
            cursor = super.query(filter, root);
        }
        return cursor;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy