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

com.redhat.lightblue.assoc.QueryPlanChooser Maven / Gradle / Ivy

There is a newer version: 2.18.0
Show newest version
/*
 Copyright 2013 Red Hat, Inc. and/or its affiliates.

 This file is part of lightblue.

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 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 General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see .
 */
package com.redhat.lightblue.assoc;

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

import com.redhat.lightblue.crud.CrudConstants;
import com.redhat.lightblue.metadata.MetadataConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.redhat.lightblue.metadata.CompositeMetadata;
import com.redhat.lightblue.metadata.ResolvedReferenceField;
import com.redhat.lightblue.metadata.ReferenceField;

import com.redhat.lightblue.query.QueryExpression;
import com.redhat.lightblue.query.NaryLogicalExpression;
import com.redhat.lightblue.query.NaryLogicalOperator;

import com.redhat.lightblue.assoc.qrew.QueryRewriter;

import com.redhat.lightblue.util.Path;
import com.redhat.lightblue.util.Error;

public class QueryPlanChooser {

    private static final Logger LOGGER=LoggerFactory.getLogger(QueryPlanChooser.class);

    private static final QueryRewriter qrewriter=new QueryRewriter(true);

    private final CompositeMetadata compositeMetadata;
    private final QueryExpression requestQuery;
    private final QueryPlanIterator qplanIterator;
    private final QueryPlanScorer scorer;

    private QueryPlan qplan;

    private QueryPlan bestPlan;
    private Comparable bestPlanScore;

    public QueryPlanChooser(CompositeMetadata cmd,
                            QueryPlanIterator qpitr,
                            QueryPlanScorer scorer,
                            QueryExpression requestQuery,
                            Set filter) {
        LOGGER.debug("QueryPlanChooser.ctor");
        Error.push("QueryPlanChooser");
        try {
            this.compositeMetadata=cmd;
            this.qplanIterator=qpitr;
            this.scorer=scorer;
            qplan=new QueryPlan(compositeMetadata,scorer,filter);
            LOGGER.debug("Initial query plan:{}",qplan);
            
            this.requestQuery=requestQuery;
            LOGGER.debug("Request query:{}",this.requestQuery);
            
            // Rewrite  request queries  in  conjunctive  normal form  and
            // assign them to nodes/edges
            if(this.requestQuery!=null) {
                List requestQueryClauses=new ArrayList<>();
                rewriteQuery(this.requestQuery,requestQueryClauses,qplan,null);
                LOGGER.debug("Request query clauses:{}",requestQueryClauses);
                assignQueriesToPlanNodesAndEdges(requestQueryClauses,qplan.getUnassignedClauses());
                LOGGER.debug("Completed assigning request query clauses");
            }
            
            // Rewrite association queries in conjunctive normal form, and
            // assign them to nodes/edges
            iterateReferences(compositeMetadata,qplan.getUnassignedClauses());
            
            reset();

            // Check unimplemented features. If there is anything in
            // unassigned clauses list, we fail
            if (!qplan.getUnassignedClauses().isEmpty()) {
                Conjunct x = qplan.getUnassignedClauses().get(0);
                switch (x.getReferredNodes().size()) {
                    case 2:
                        throw Error.get(AssocConstants.ERR_UNRELATED_ENTITY_Q);
                    default:
                        throw Error.get(AssocConstants.ERR_MORE_THAN_TWO_Q);
                }
            }

        } catch(Error e) {
            LOGGER.error("During construction:{}",e);
            throw e;
        } catch(RuntimeException re) {
            LOGGER.error("During construction:{}",re);
            throw Error.get(AssocConstants.ERR_CANNOT_CREATE_CHOOSER,re.toString());
        } finally {
            Error.pop();
        }
    }

    /**
     * Rewrites the query expression in its conjunctive normal form,
     * and adds clauses to the end of the clause list
     */
    private void rewriteQuery(QueryExpression q,
                              List clauseList,
                              QueryPlan qplan,
                              ResolvedReferenceField context) {
        Error.push("rewriteQuery");
        try {
            QueryExpression cnf = qrewriter.rewrite(q);
            LOGGER.debug("Query in conjunctive normal form:{}", cnf);
            if (cnf instanceof NaryLogicalExpression &&
                    ((NaryLogicalExpression) cnf).getOp() == NaryLogicalOperator._and) {
                for (QueryExpression clause : ((NaryLogicalExpression) cnf).getQueries()) {
                    clauseList.add(new Conjunct(clause, compositeMetadata, qplan,context));
                }
            } else {
                clauseList.add(new Conjunct(cnf, compositeMetadata, qplan,context));
            }
        } catch (Error e) {
            // rethrow lightblue error
            throw e;
        } catch (Exception e) {
            // throw new Error (preserves current error context)
            LOGGER.error(e.getMessage(), e);
            throw Error.get(AssocConstants.ERR_REWRITE, e.getMessage());
        } finally {
            Error.pop();
        }
    }

    /**
     * Recursively iterate all associations, and assign queries to
     * nodes and edges.
     */
    private void iterateReferences(CompositeMetadata root,List unassignedClauses) {
        LOGGER.debug("Iterating references to collect clauses");
        Error.push("iterateReferences");
        try {
            Set childPaths = root.getChildPaths();
            for (Path childPath : childPaths) {
                ResolvedReferenceField rrf = root.getChildReference(childPath);
                ReferenceField ref = rrf.getReferenceField();
                if (ref.getQuery() != null) {
                    LOGGER.debug("Association query:{}", ref.getQuery());
                    List refQueryClauses = new ArrayList<>();
                    rewriteQuery(ref.getQuery(), refQueryClauses, qplan,rrf);
                    LOGGER.debug("Association query clauses:{}", refQueryClauses);
                    assignQueriesToPlanNodesAndEdges(refQueryClauses, unassignedClauses);
                }
                iterateReferences(rrf.getReferencedMetadata(), unassignedClauses);
            }
        } catch (Error e) {
            // rethrow lightblue error
            throw e;
        } catch (Exception e) {
            // throw new Error (preserves current error context)
            LOGGER.error(e.getMessage(), e);
            throw Error.get(AssocConstants.ERR_REWRITE, e.getMessage());
        } finally {
            Error.pop();
        }
    }

    
    /**
     * Assigns queries that refer to only one entity to the query plan
     * nodes. These are fixed assignments, and don't change from one
     * query plan to the other
     */
    private void assignQueriesToPlanNodesAndEdges(List queries,List unassigned) {
        Error.push("assignQueriesToPlanNodesAndEdges");
        LOGGER.debug("Assigning queries to query plan nodes and edges");
        try {
            for(Conjunct c:queries) {
                Set nodes=c.getReferredNodes();
                LOGGER.debug("Conjunct {}",c);
                switch(nodes.size()) {
                case 1:
                    // All query clauses that depend on fields from a single
                    // entity can be assigned to a query plan node. If clauses
                    // depend on multiple entities, their assignments may change
                    // based on the query plan.
                    LOGGER.debug("Conjunct has one entity");
                    nodes.iterator().next().getData().getConjuncts().add(c);
                    break;

                case 2:
                    // There are two or more entities referred to in the conjunct
                    // This clause can be associated with an edge
                    Iterator itr=nodes.iterator();
                    QueryPlanNode node1=itr.next();
                    QueryPlanNode node2=itr.next();
                    if(qplan.isUndirectedConnected(node1,node2)) {
                        LOGGER.debug("Conjunct is assigned to an edge");
                        QueryPlanData qd=qplan.getEdgeData(node1,node2);
                        if(qd==null)
                            qplan.setEdgeData(node1,node2,qd=qplan.newData());
                        qd.getConjuncts().add(c);
                        break;
                    }
                    // No break, falls through to default
                default:

                    // Query clause cannot be assigned to a node or edge,
                    // put it into the unassigned list
                    LOGGER.debug("Conjunct is unassigned");
                    unassigned.add(c);
                    break;
                }
            }
        } catch (Error e) {
            // rethrow lightblue error
            throw e;
        } catch (Exception e) {
            // throw new Error (preserves current error context)
            LOGGER.error(e.getMessage(), e);
            throw Error.get(AssocConstants.ERR_REWRITE, e.getMessage());
        } finally {
            Error.pop();
        }
    }

    /**
     * Return the root metadata
     */
    public CompositeMetadata getMetadata() {
        return compositeMetadata;
    }

    /**
     * Return the query expression coming from the request
     */
    public QueryExpression getRequestQuery() {
        return requestQuery;
    }   
    
    /**
     * Returns the query plan that's currently chosen
     */
    public QueryPlan getQueryPlan() {
        return qplan;
    }

    /**
     * Returns the best plan chosen so far
     */
    public QueryPlan getBestPlan() {
        return bestPlan;
    }

    /**
     * Resets the query chooser to a state where it can start evaluating the query plans again
     */
    public void reset() {
        bestPlan=null;
        bestPlanScore=null;
        
        qplanIterator.reset(qplan);
        scorer.reset(this);

        bestPlan=qplan.deepCopy();
        bestPlanScore=scorer.score(bestPlan);
        LOGGER.debug("Storing initial plan as the best plan:{}",bestPlan);
        
    }

    /**
     * Chooses the best query play after scoring all possible plans.
     */
    public QueryPlan choose() {
        while(qplanIterator.next()) {
            LOGGER.debug("Scoring plan {}",qplan);
            Comparable score=scorer.score(qplan);
            if(null!=score&&score.compareTo(bestPlanScore)<0) {
                LOGGER.debug("Score is better, storing this plan");
                bestPlan=qplan.deepCopy();
                bestPlanScore=score;
                LOGGER.debug("Stored plan:{}",bestPlan);
            }
        }

        return bestPlan;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy