/*******************************************************************************
* Copyright (c) 2004, 2007 IBM Corporation and Cambridge Semantics Incorporated.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* File: $Source: /cvsroot/slrp/glitter/com.ibm.adtech.glitter/src/com/ibm/adtech/glitter/query/SerialQueryExecutor.java,v $
* Created by: Lee Feigenbaum (<a href="mailto:feigenbl@us.ibm.com">feigenbl@us.ibm.com</a>)
* Created on: 10/23/06
* Revision: $Id: SerialQueryExecutor.java 164 2007-07-31 14:11:09Z mroy $
*
* Contributors: IBM Corporation - initial API and implementation
* Cambridge Semantics Incorporated - Fork to Anzo
*******************************************************************************/
package org.openanzo.glitter.query;
import java.util.EnumSet;
import java.util.Set;
import org.openanzo.analysis.RequestAnalysis;
import org.openanzo.exceptions.Assert;
import org.openanzo.exceptions.LogUtils;
import org.openanzo.glitter.EngineConfig;
import org.openanzo.glitter.exception.GlitterException;
import org.openanzo.glitter.query.QueryController.QueryStringPrintOptions;
import org.openanzo.glitter.syntax.abstrakt.Expression;
import org.openanzo.glitter.syntax.abstrakt.GraphPattern;
import org.openanzo.glitter.syntax.abstrakt.Group;
import org.openanzo.glitter.syntax.abstrakt.Optional;
import org.openanzo.glitter.syntax.abstrakt.TreeNode;
import org.openanzo.rdf.URI;
import org.openanzo.rdf.Variable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link SerialQueryExecutor} is the standard {@link QueryExecutor}. It uses the supplied execution plan to generate bindings serially; it repeatedly asks
* a solution generator to generate bindings for as large a part of the query as it can, and the executor composes these results into the final
* {@link SolutionSet}.
*
* @author lee <lee@cambridgesemantics.com>
*
*/
public class SerialQueryExecutor implements QueryExecutor, NodeSolver {
private static final Logger log = LoggerFactory.getLogger(SerialQueryExecutor.class);
private SolutionGenerator generator;
private QueryController controller;
private EngineConfig config;
private final boolean composedSolutions;
private boolean done;
private SerialInMemoryNodeSolver solver;
/**
* Constructor.
*/
public SerialQueryExecutor() {
this.composedSolutions = false;
this.done = false;
}
public void initialize(EngineConfig config, QueryController controller, SolutionGenerator sg, QueryExecutionPlan plan) {
this.config = config;
this.generator = sg;
this.controller = controller;
this.solver = new SerialInMemoryNodeSolver(this, controller, plan, generator.canBindGraphVariables());
}
public SolutionSet executeQuery() throws org.openanzo.glitter.exception.GlitterException {
Assert.isTrue(!this.done); // prevent reuse of these objects
GraphPattern root = this.controller.getQueryPattern();
if (log.isInfoEnabled()) {
log.info(LogUtils.GLITTER_MARKER, "Executing query:" + this.controller.getQueryString(true));
}
if (RequestAnalysis.getAnalysisLogger().isDebugEnabled()) {
RequestAnalysis.getAnalysisLogger().debug(LogUtils.GLITTER_MARKER, "[glitter_SerialQueryExecutor_query]\n" + this.controller.prettyPrintQueryString(EnumSet.of(QueryStringPrintOptions.INDENT)));
}
SolutionSet solutions = solveNode(root, SolutionUtils.unconstrainedSolutions(), null, null);
this.done = true;
return solutions;
}
public boolean composedSolutions() {
return this.composedSolutions;
}
/**
* Returns a list of pattern solutions that satisfy the given tree node, in light of the named graph context supplied and the answers known already to be
* true. The most interesting part of these parameters is 'answers' (see below).
*
* @param n
* The tree node for which we are seeking solutions
* @param answerConstraints
* These represent bindings that must be in the solution set due to nodes of the AST that have already been visited. Exactly how new solutions
* are combined with these solutions depends on the semantics of the particular node being processed. N.B. A null answers is equivalent to an
* answers containing a single solution with no bindings. This is the default state of the world and is *very* different from a non-null but
* empty list of solutions (which indicates that no solutions will work.)
* @param namedGraphContext
* The named graph in which answers should be sought, or null if the matching is taking place against the default graph.
* @return Solutions generated from node n *only*. It is the responsibility of the caller (not the callee) to algebraically compose these solutions with the
* known constraints in whatever manner is appropriate.
*/
public SolutionSet solveNode(TreeNode n, SolutionSet answerConstraints, URI namedGraphContext, Variable namedGraphVariable) throws GlitterException {
if (answerConstraints == null)
answerConstraints = SolutionUtils.unconstrainedSolutions();
SolutionUtils.substituteFilterBindings(n, answerConstraints);
// TODO write tests for this
if (this.config.substituteFixedBindings()) {
n = SolutionUtils.substituteFixedBindings(n, answerConstraints);
}
// first, see if our SolutionGenerator wants a crack at this node
// if this node is an optional with filters that the SolutionGenerator cannot handle,
// don't give it a chance
// TODO this is broken because the optional w/ filters could be embedded deep w/in a group
// we probably need to rely on the solution generator telling us it can't handle it
if (!(n instanceof Optional && n.getFilters() != null && n.getFilters().size() > 0 && !this.generator.willHandleFilters(n.getFilters()))) {
SolutionSet results = delegateNodeToSolutionGenerator(n, answerConstraints, namedGraphContext, namedGraphVariable);
if (results != null) {
return results;
}
}
// if we get to this point, then we need to handle this node ourselves
return solver.solveNode(n, answerConstraints, namedGraphContext, namedGraphVariable);
}
/*
* Send the solution to the generator and see if it can solve it. If it can't it will throw a GlitterException.
*/
private SolutionSet delegateNodeToSolutionGenerator(TreeNode n, SolutionSet answerConstraints, URI namedGraphContext, Variable namedGraphVariable) throws GlitterException {
long start = 0;
long totalTime = 0;
boolean isEnabled = RequestAnalysis.getAnalysisLogger().isDebugEnabled();
SolutionSet answers = null;
try {
if (isEnabled) {
start = System.currentTimeMillis();
totalTime = System.currentTimeMillis();
}
answers = this.generator.generateSolutions(n, namedGraphContext, namedGraphVariable, answerConstraints, controller);
if (answers == null)
return null;
if (isEnabled) {
StringBuilder sb = new StringBuilder();
n.prettyPrint(sb, true);
RequestAnalysis.getAnalysisLogger().debug(LogUtils.GLITTER_MARKER, "[glitter_SerialQueryExecutor_newAnswersFromSolutionGeneratorForNode] [{}] = [{}] {}:{}", new Object[] { n.toString(), sb.toString(), Integer.toString(answers.size()), Long.toString(System.currentTimeMillis() - start) });
RequestAnalysis.getAnalysisLogger().debug(LogUtils.TIMING_MARKER, "glitter_SerialQueryExecutor_newAnswersFromSolutionGeneratorForNode,{},{},{}", new Object[] { Long.toString(System.currentTimeMillis() - start), Integer.toString(answers.size()), sb.toString() });
start = System.currentTimeMillis();
}
if (n instanceof Group) {
Group g = (Group) n;
if (g.getAssignments().size() > 0 && !generator.willHandleAssignments(g.getAssignments())) {
answers = SPARQLAlgebra.processAssignments(answers, g.getAssignments());
if (isEnabled) {
RequestAnalysis.getAnalysisLogger().debug(LogUtils.GLITTER_MARKER, "[glitter_SerialQueryExecutor_processedAssignments] {}:{}", Long.toString(System.currentTimeMillis() - start), Integer.toString(answers.size()));
RequestAnalysis.getAnalysisLogger().debug(LogUtils.TIMING_MARKER, "glitter_SerialQueryExecutor_processedAssignments,{},{}", Long.toString(System.currentTimeMillis() - start), Integer.toString(answers.size()));
start = System.currentTimeMillis();
}
}
}
Set<Expression> filters = n.getFilters();
if (filters != null && filters.size() > 0 && !this.generator.willHandleFilters(filters)) {
// we need to apply the filters ourselves
answers = SPARQLAlgebra.filterSolutions(answers, n.getFilters());
if (isEnabled) {
RequestAnalysis.getAnalysisLogger().debug(LogUtils.GLITTER_MARKER, "[glitter_SerialQueryExecutor_filteredSolutions] {}:{}", Long.toString(System.currentTimeMillis() - start), Integer.toString(answers.size()));
RequestAnalysis.getAnalysisLogger().debug(LogUtils.TIMING_MARKER, "glitter_SerialQueryExecutor_filteredSolutions,{},{}", Long.toString(System.currentTimeMillis() - start), Integer.toString(answers.size()));
start = System.currentTimeMillis();
}
// if this node is OPTIONAL(A, B, F) then answers is the LeftJoin(eval(A), eval(B))
// Therefore, answers contains some members of eval(A) (for which there were
// no compatible bindings in eval(B)) and all the members of Join((eval(A), eval(B))).
// If a filter eliminates a solution S, and S was the result of joining a member
// of eval(A), SA, and of eval(B), SB, then we need to make sure that SA is in the answer
// set. We have no way of doing that at this granularity. We could redo all the algebra,
// but it's probably better in the first place to require the solution generator to handle
// optionals... (see note above)
}
if (isEnabled) {
RequestAnalysis.getAnalysisLogger().debug(LogUtils.GLITTER_MARKER, "[glitter_SerialQueryExecutor_solvedNodeWithSolutionGenerator] {}:{}", new Object[] { Long.toString(System.currentTimeMillis() - totalTime), Integer.toString((answers != null) ? answers.size() : 0) });
RequestAnalysis.getAnalysisLogger().debug(LogUtils.TIMING_MARKER, "glitter_SerialQueryExecutor_solvedNodeWithSolutionGenerator,{},{}", new Object[] { Long.toString(System.currentTimeMillis() - totalTime), Integer.toString((answers != null) ? answers.size() : 0) });
}
return answers;
} finally {
}
}
}