Package org.exist.xquery.modules.range

Source Code of org.exist.xquery.modules.range.OptimizeFieldPragma

/*
*  eXist Open Source Native XML Database
*  Copyright (C) 2013 The eXist Project
*  http://exist-db.org
*
*  This program is free software; you can redistribute it and/or
*  modify it under the terms of the GNU Lesser General Public License
*  as published by the Free Software Foundation; either version 2
*  of the License, or (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU Lesser General Public License for more details.
*
*  You should have received a copy of the GNU Lesser General Public
*  License along with this library; if not, write to the Free Software
*  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
*
*  $Id$
*/
package org.exist.xquery.modules.range;

import org.exist.Namespaces;
import org.exist.collections.Collection;
import org.exist.dom.NodeSet;
import org.exist.dom.QName;
import org.exist.indexing.range.*;
import org.exist.storage.IndexSpec;
import org.exist.storage.NodePath;
import org.exist.xmldb.XmldbURI;
import org.exist.xquery.*;
import org.exist.xquery.pragmas.*;
import org.exist.xquery.value.*;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
* A pragma which checks if an XPath expression could be replaced with a range field lookup.
*
* @author wolf
*/
public class OptimizeFieldPragma extends Pragma {

    public  final static QName OPTIMIZE_RANGE_PRAGMA = new QName("optimize-field", Namespaces.EXIST_NS, "exist");

    private final XQueryContext context;
    private Expression rewritten = null;
    private AnalyzeContextInfo contextInfo;
    private int axis;

    public OptimizeFieldPragma(QName qname, String contents, XQueryContext context) throws XPathException {
        super(qname, contents);
        this.context = context;
    }

    @Override
    public void analyze(AnalyzeContextInfo contextInfo) throws XPathException {
        super.analyze(contextInfo);
        this.contextInfo = contextInfo;
    }

    @Override
    public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathException {
        if (rewritten != null) {
            rewritten.analyze(contextInfo);
            return rewritten.eval(contextSequence, contextItem);
        }
        return null;
    }

    @Override
    public void before(XQueryContext context, Expression expression, Sequence contextSequence) throws XPathException {
        LocationStep locationStep = (LocationStep) expression;
        if (locationStep.hasPredicates()) {
            Expression parentExpr = locationStep.getParentExpression();
            if (!(parentExpr instanceof RewritableExpression)) {
                return;
            }
            final List<Predicate> preds = locationStep.getPredicates();

            // get path of path expression before the predicates
            NodePath contextPath = RangeQueryRewriter.toNodePath(RangeQueryRewriter.getPrecedingSteps(locationStep));

            rewritten = tryRewriteToFields(locationStep, preds, contextPath, contextSequence);
            axis = locationStep.getAxis();
        }
    }

    @Override
    public void after(XQueryContext context, Expression expression) throws XPathException {

    }

    private Expression tryRewriteToFields(LocationStep locationStep, List<Predicate> preds, NodePath contextPath, Sequence contextSequence) throws XPathException {
        // without context path, we cannot rewrite the entire query
        if (contextPath != null) {
            List<Expression> args = null;
            SequenceConstructor arg0 = null;
            SequenceConstructor arg1 = null;

            List<Predicate> notOptimizable = new ArrayList<Predicate>(preds.size());
            List<RangeIndexConfig> configs = getConfigurations(contextSequence);
            // walk through the predicates attached to the current location step
            // check if expression can be optimized
            for (final Predicate pred : preds) {
                if (pred.getLength() != 1) {
                    // can only optimize predicates with one expression
                    notOptimizable.add(pred);
                    continue;
                }
                Expression innerExpr = pred.getExpression(0);
                List<LocationStep> steps = RangeQueryRewriter.getStepsToOptimize(innerExpr);
                if (steps == null) {
                    notOptimizable.add(pred);
                    continue;
                }
                // compute left hand path
                NodePath innerPath = RangeQueryRewriter.toNodePath(steps);
                if (innerPath == null) {
                    notOptimizable.add(pred);
                    continue;
                }
                NodePath path = new NodePath(contextPath);
                path.append(innerPath);

                if (path.length() > 0) {
                    // find a range index configuration matching the full path to the predicate expression
                    RangeIndexConfigElement rice = findConfiguration(path, true, configs);
                    // found index configuration with sub-fields
                    if (rice != null && rice.isComplex() && rice.getNodePath().match(contextPath) && findConfiguration(path, false, configs) == null) {
                        // check for a matching sub-path and retrieve field information
                        RangeIndexConfigField field = ((ComplexRangeIndexConfigElement) rice).getField(path);
                        if (field != null) {
                            if (args == null) {
                                // initialize args
                                args = new ArrayList<Expression>(4);
                                arg0 = new SequenceConstructor(context);
                                args.add(arg0);
                                arg1 = new SequenceConstructor(context);
                                args.add(arg1);
                            }
                            // field is added to the sequence in first parameter
                            arg0.add(new LiteralValue(context, new StringValue(field.getName())));
                            // operator
                            arg1.add(new LiteralValue(context, new StringValue(RangeQueryRewriter.getOperator(innerExpr).toString())));
                            // append right hand expression as additional parameter
                            args.add(getKeyArg(innerExpr));
                        } else {
                            notOptimizable.add(pred);
                            continue;
                        }
                    } else {
                        notOptimizable.add(pred);
                        continue;
                    }
                } else {
                    notOptimizable.add(pred);
                    continue;
                }
            }
            if (args != null) {
                // the entire filter expression can be replaced
                // create range:field-equals function
                FieldLookup func = new FieldLookup(context, FieldLookup.signatures[0]);
                func.setFallback(locationStep);
                func.setLocation(locationStep.getLine(), locationStep.getColumn());
                func.setArguments(args);

                Expression optimizedExpr = new InternalFunctionCall(func);
                if (notOptimizable.size() > 0) {
                    final FilteredExpression filtered = new FilteredExpression(context, optimizedExpr);
                    for (Predicate pred : notOptimizable) {
                        filtered.addPredicate(pred);
                    }
                    optimizedExpr = filtered;
                }

                return optimizedExpr;
            }
        }
        return null;
    }

    private Expression getKeyArg(Expression expression) {
        if (expression instanceof GeneralComparison) {
            return ((GeneralComparison)expression).getRight();
        } else if (expression instanceof InternalFunctionCall) {
            InternalFunctionCall fcall = (InternalFunctionCall) expression;
            Function function = fcall.getFunction();
            if (function instanceof Lookup) {
                return function.getArgument(1);
            }
        }
        return null;
    }

    /**
     * Scan all index configurations to find one matching path.
     */
    private RangeIndexConfigElement findConfiguration(NodePath path, boolean complex, List<RangeIndexConfig> configs) {
        for (RangeIndexConfig config : configs) {
            final RangeIndexConfigElement rice = config.find(path);
            if (rice != null && ((complex && rice.isComplex()) ||
                    (!complex && !rice.isComplex()))) {
                return rice;
            }
        }
        return null;
    }

    private List<RangeIndexConfig> getConfigurations(Sequence contextSequence) {
        List<RangeIndexConfig> configs = new ArrayList<RangeIndexConfig>();
        for (final Iterator<Collection> i = contextSequence.getCollectionIterator(); i.hasNext(); ) {
            final Collection collection = i.next();
            if (collection.getURI().startsWith(XmldbURI.SYSTEM_COLLECTION_URI)) {
                continue;
            }
            IndexSpec idxConf = collection.getIndexConfiguration(context.getBroker());
            if (idxConf != null) {
                final RangeIndexConfig config = (RangeIndexConfig) idxConf.getCustomIndexSpec(RangeIndex.ID);
                if (config != null) {
                    configs.add(config);
                }
            }
        }
        return configs;
    }
}
TOP

Related Classes of org.exist.xquery.modules.range.OptimizeFieldPragma

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.