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

com.redhat.lightblue.assoc.CompositeFindImpl 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.Set;
import java.util.HashSet;
import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;

import java.util.concurrent.Executors;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.redhat.lightblue.query.QueryExpression;

import com.redhat.lightblue.crud.CRUDFindResponse;
import com.redhat.lightblue.crud.CRUDFindRequest;
import com.redhat.lightblue.crud.DocCtx;
import com.redhat.lightblue.crud.ListDocumentStream;

import com.redhat.lightblue.metadata.CompositeMetadata;

import com.redhat.lightblue.mediator.Finder;
import com.redhat.lightblue.mediator.OperationContext;
import com.redhat.lightblue.mediator.Mediator;

import com.redhat.lightblue.assoc.scorers.IndexedFieldScorer;
import com.redhat.lightblue.assoc.iterators.BruteForceQueryPlanIterator;
import com.redhat.lightblue.assoc.scorers.SimpleScorer;
import com.redhat.lightblue.assoc.iterators.First;

import com.redhat.lightblue.assoc.ep.ExecutionPlan;
import com.redhat.lightblue.assoc.ep.StepResult;
import com.redhat.lightblue.assoc.ep.ResultDocument;
import com.redhat.lightblue.assoc.ep.ExecutionContext;
import com.redhat.lightblue.assoc.ep.MakeDocCtx;
import com.redhat.lightblue.assoc.ep.StepResultDocumentStream;

import com.redhat.lightblue.util.JsonDoc;

/**
 * Finder for searches involving composite entities
 *
 * This implementation builds a search and retrieval plan based on the query
 * plan. The generation of search plan is optional, if it discovers that the
 * entities can be searched and retrieve with a single plan, it only generates a
 * retrieval plan. The retrieval plan has the same structure as the entity
 * composite metadata. First the documents for the root entity are retrieved,
 * then, recursively, the associated documents.
 *
 * The search plan, if exists, has a different structure than the document
 * structure. It is based on the request query, and terminates as soon as the
 * root entity documents are retrieved. Once the root documents are retrieved,
 * that list is used to execute the retrieval plan.
 */
public class CompositeFindImpl implements Finder {

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

    private final CompositeMetadata root;

    // Instance state data. This class is meant to be thrown away after used once.
    // Query analysis information for the request query
    private transient List requestQueryInfo;

    // The search and retrieval query plans
    // If searchQPlan is null, then retrievalQPlan performs both search and retrieval
    // If searchQPlan is not null, then searchQPlan searches the documents, and retrievalQPlan
    // retrieves the found documents
    private transient QueryPlan searchQPlan;
    private transient QueryPlan retrievalQPlan;

    private transient ExecutionPlan executionPlan;
    private int parallelism = 1;

    public CompositeFindImpl(CompositeMetadata md) {
        this.root = md;
    }

    /**
     * Set maximum number of threads that can run in parallel. There's a hard
     * limit on 10
     */
    public void setParallelism(int n) {
        parallelism = n;
        if (parallelism < 1) {
            parallelism = 1;
        }
        if (parallelism > 10) {
            parallelism = 10;
        }
    }

    private void initialize(OperationContext ctx,
                            CRUDFindRequest req) {
        if(executionPlan==null) {
            // Composite find algorithm works like this:
            //   1) Find the minimal entity tree required to evaluate the request.query
            //   2) Find a query plan for that minimal entity tree
            //   3) If the query plan root is also the root entity, then expand that query
            //      plan to include all nodes
            //   4) Execute search based on query plan
            //   5) If query plan node is not the entity root, find documents found for entity root,
            //      and re-retrieve the documents
            // First: detemine minimal entity tree containing the nodes sufficient to
            // evaluate the query
            Set minimalTree = findMinimalSetOfQueryEntities(req.getQuery(),
                                                                               ctx.getTopLevelEntityMetadata());
            
            selectQueryPlan(req.getQuery(), minimalTree);
            LOGGER.debug("Search query plan:{}, retrieval query plan:{}", searchQPlan, retrievalQPlan);
            
            executionPlan = new ExecutionPlan(req.getQuery(),
                                              req.getProjection(),
                                              req.getSort(),
                                              req.getFrom(),
                                              req.getTo(),
                                              root,
                                              searchQPlan,
                                              retrievalQPlan,
                                              ctx);
        }
    }
    
    @Override
    public void explain(OperationContext ctx,
                        CRUDFindRequest req) {
        initialize(ctx,req);
        ExecutionContext executionContext = new ExecutionContext(ctx,null);
        JsonDoc doc=new JsonDoc(executionPlan.explain(executionContext));
        ctx.setDocumentStream(new ListDocumentStream(Arrays.asList(new DocCtx(doc))));
    }
    
    @Override
    public CRUDFindResponse find(OperationContext ctx,
                                 CRUDFindRequest req) {
        LOGGER.debug("Composite find: start");
        
        initialize(ctx,req);

        ctx.setProperty(Mediator.CTX_QPLAN, searchQPlan == null ? retrievalQPlan : searchQPlan);
        LOGGER.debug("Execution plan:{}", executionPlan);

        CRUDFindResponse response = new CRUDFindResponse();
        ExecutionContext executionContext = new ExecutionContext(ctx,
                Executors.newWorkStealingPool(parallelism));
        try {
            StepResult results = executionPlan.getResults(executionContext);
            ctx.setDocumentStream(new StepResultDocumentStream(new MakeDocCtx(results)));            	
            response.setSize(executionContext.getMatchCount());
            LOGGER.debug("Composite find: end");
            return response;
        } finally {
            executionContext.close();
        }
    }

    /**
     * Selects the search and retrieval query plans based on the minimal tree
     * and request query.
     *
     * There is either only a retrieval plan, or both a search plan and
     * retrieval plan. If search plan cannot retrieve the root entity, then we
     * use the search plan to collect the entities matching the search criteria,
     * and then the retrieval plan to retrieve those entities completely. If the
     * search plan can both search and retrieve the entities, there will be only
     * a retrieval plan.
     *
     *
     */
    private void selectQueryPlan(QueryExpression requestQuery,
                                 Set minimalTree) {
        searchQPlan = retrievalQPlan = null;

        if (minimalTree.size() > 1) {
            // There are multiple entities required to evaluate the query

            // Choose a query plan
            QueryPlan searchQP = new QueryPlanChooser(root,
                    new BruteForceQueryPlanIterator(),
                    new IndexedFieldScorer(),
                    requestQuery,
                    minimalTree).choose();
            LOGGER.debug("Candidate plan: {}", searchQP);
            // If the query plan has only one source, and that source is the root, then
            // we don't need to search and retrieve in two separate steps, we can simply
            // retrieve everything while we search
            QueryPlanNode[] roots = searchQP.getSources();
            if (roots.length == 1 && roots[0].getMetadata() == root) {
                LOGGER.debug("Search is trivial, root node is at query plan root, so search and retrieve");
                // Build a new query plan containing all entities. This plan should
                // have the same root as before. If not, something must be
                // wrong, and we fall back to a search/retrieve query
                QueryPlan fullPlan = new QueryPlanChooser(root,
                        new BruteForceQueryPlanIterator(),
                        new IndexedFieldScorer(),
                        requestQuery,
                        null).choose();
                // This plan must also have a single root
                roots = fullPlan.getSources();
                if (roots.length == 1 && roots[0].getMetadata() == root) {
                    // Retrieve everything, no separate search plan
                    retrievalQPlan = fullPlan;
                    searchQPlan = null;
                } else {
                    // Search and retrieve in separate phases
                    searchQPlan = searchQP;
                }
            } else {
                // Multiple roots, search and retrieve in separate phases
                searchQPlan = searchQP;
            }
        } else {
            // Minimal tree has only one entity. That entity must be the root entity
            // That means, a single retrieval plan can search and retrieve            
            searchQPlan = null;
        }
        if (retrievalQPlan == null) {
            if (searchQPlan == null) {
                // Search and retrieve
                retrievalQPlan = new QueryPlanChooser(root,
                        new First(),
                        new SimpleScorer(),
                        requestQuery,
                        null).choose();
            } else {
                // No search, only retrieve. No query.
                retrievalQPlan = new QueryPlanChooser(root,
                        new First(),
                        new SimpleScorer(),
                        null,
                        null).choose();
            }
        }
    }

    /**
     * Determine which entities are required to evaluate the given query
     */
    private Set findMinimalSetOfQueryEntities(QueryExpression query,
                                                                 CompositeMetadata md) {
        Set entities = new HashSet<>();
        if (query != null) {
            AnalyzeQuery aq = new AnalyzeQuery(md, null);
            aq.iterate(query);
            requestQueryInfo = aq.getFieldInfo();
            LOGGER.debug("Analyze query results for {}: {}", query, requestQueryInfo);
            for (QueryFieldInfo fi : requestQueryInfo) {
                CompositeMetadata e = fi.getFieldEntity();
                if (e != md) {
                    entities.add(e);
                }
            }
            // All entities on the path from every entity to the root
            // should also be included
            Set intermediates = new HashSet<>();
            for (CompositeMetadata x : entities) {
                CompositeMetadata trc = x.getParent();
                while (trc != null) {
                    intermediates.add(trc);
                    trc = trc.getParent();
                }
            }
            entities.addAll(intermediates);
        }
        // At this point, entities contains all required entities, but maybe not the root
        entities.add(md);
        LOGGER.debug("Minimal set of entities required to evaluate {}:{}", query, entities);
        return entities;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy