com.bigdata.bop.controller.JVMNamedSubqueryOp Maven / Gradle / Ivy
/**
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
[email protected]
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; version 2 of the License.
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, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Aug 18, 2010
*/
package com.bigdata.bop.controller;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import org.apache.log4j.Logger;
import com.bigdata.bop.BOp;
import com.bigdata.bop.BOpContext;
import com.bigdata.bop.BOpEvaluationContext;
import com.bigdata.bop.BOpUtility;
import com.bigdata.bop.IBindingSet;
import com.bigdata.bop.IQueryAttributes;
import com.bigdata.bop.ISingleThreadedOp;
import com.bigdata.bop.IVariable;
import com.bigdata.bop.NV;
import com.bigdata.bop.PipelineOp;
import com.bigdata.bop.engine.AbstractRunningQuery;
import com.bigdata.bop.engine.BOpStats;
import com.bigdata.bop.engine.IRunningQuery;
import com.bigdata.bop.engine.QueryEngine;
import com.bigdata.bop.join.JVMHashJoinAnnotations;
import com.bigdata.bop.join.JVMHashJoinUtility;
import com.bigdata.bop.join.JVMSolutionSetHashJoinOp;
import com.bigdata.bop.join.JoinTypeEnum;
import com.bigdata.bop.join.NamedSolutionSetStats;
import com.bigdata.relation.accesspath.IBlockingBuffer;
import cutthecrap.utils.striterators.ICloseableIterator;
import cutthecrap.utils.striterators.SingleValueIterator;
/**
* Evaluation of a subquery, producing a named result set. This operator passes
* through any source binding sets without modification. The subquery is
* evaluated exactly once, the first time this operator is invoked for a given
* query plan. If some variables are known to be bound, then they should be
* rewritten into constants or their bindings should be inserted into the
* subquery using LET() operator.
*
* This operator is NOT thread-safe. It relies on the query engine to provide
* synchronization for the "run-once" contract of the subquery. The operator
* MUST be run on the query controller.
*
* @see JVMSolutionSetHashJoinOp
*
* @author Bryan Thompson
*/
public class JVMNamedSubqueryOp extends PipelineOp implements INamedSubqueryOp,
ISingleThreadedOp {
static private final transient Logger log = Logger
.getLogger(JVMNamedSubqueryOp.class);
/**
*
*/
private static final long serialVersionUID = 1L;
public interface Annotations extends SubqueryAnnotations,
JVMHashJoinAnnotations, NamedSetAnnotations {
}
/**
* Deep copy constructor.
*/
public JVMNamedSubqueryOp(final JVMNamedSubqueryOp op) {
super(op);
}
/**
* Shallow copy constructor.
*
* @param args
* @param annotations
*/
public JVMNamedSubqueryOp(final BOp[] args,
final Map annotations) {
super(args, annotations);
if (getEvaluationContext() != BOpEvaluationContext.CONTROLLER) {
throw new IllegalArgumentException(
BOp.Annotations.EVALUATION_CONTEXT + "="
+ getEvaluationContext());
}
assertMaxParallelOne();
if (!isAtOnceEvaluation())
throw new IllegalArgumentException();
getRequiredProperty(Annotations.SUBQUERY);
getRequiredProperty(Annotations.NAMED_SET_REF);
// Join variables must be specified.
final IVariable>[] joinVars = (IVariable[]) getRequiredProperty(Annotations.JOIN_VARS);
// if (joinVars.length == 0)
// throw new IllegalArgumentException(Annotations.JOIN_VARS);
for (IVariable> var : joinVars) {
if (var == null)
throw new IllegalArgumentException(Annotations.JOIN_VARS);
}
}
public JVMNamedSubqueryOp(final BOp[] args, final NV... annotations) {
this(args, NV.asMap(annotations));
}
@Override
public BOpStats newStats() {
return new NamedSolutionSetStats();
}
@Override
public FutureTask eval(final BOpContext context) {
return new FutureTask(new ControllerTask(this, context));
}
/**
* Evaluates the subquery for each source binding set. If the controller
* operator is interrupted, then the subqueries are cancelled. If a subquery
* fails, then all subqueries are cancelled.
*/
private static class ControllerTask implements Callable {
private final BOpContext context;
private final NamedSolutionSetStats stats;
/** The subquery which is evaluated for each input binding set. */
private final PipelineOp subquery;
/** Metadata to identify the named solution set. */
private final INamedSolutionSetRef namedSetRef;
/**
* The {@link IQueryAttributes} for the {@link IRunningQuery} off which
* we will hang the named solution set.
*/
private final IQueryAttributes attrs;
/**
* true
iff this is the first time the task is being
* invoked, in which case we will evaluate the subquery and save its
* result set on {@link #solutions}.
*/
private final boolean first;
private final JVMHashJoinUtility state;
public ControllerTask(final JVMNamedSubqueryOp op,
final BOpContext context) {
if (op == null)
throw new IllegalArgumentException();
if (context == null)
throw new IllegalArgumentException();
this.context = context;
this.stats = ((NamedSolutionSetStats) context.getStats());
this.subquery = (PipelineOp) op
.getRequiredProperty(Annotations.SUBQUERY);
this.namedSetRef = (INamedSolutionSetRef) op
.getRequiredProperty(Annotations.NAMED_SET_REF);
{
/*
* First, see if the map already exists.
*
* Note: Since the operator is not thread-safe, we do not need
* to use a putIfAbsent pattern here.
*/
/*
* Lookup the attributes for the query on which we will hang the
* solution set. See BLZG-1493 (if queryId is null, use the query
* attributes for this running query).
*/
attrs = context.getQueryAttributes(namedSetRef.getQueryId());
JVMHashJoinUtility state = (JVMHashJoinUtility) attrs
.get(namedSetRef);
if (state == null) {
/*
* Note: This operator does not support optional semantics.
*/
state = new JVMHashJoinUtility(op, JoinTypeEnum.Normal);
if (attrs.putIfAbsent(namedSetRef, state) != null)
throw new AssertionError();
this.first = true;
} else {
this.first = false;
}
this.state = state;
}
}
/**
* Evaluate.
*/
@Override
public Void call() throws Exception {
try {
final IBindingSet[] bindingSets = BOpUtility.toArray(
context.getSource(), stats);
if(first) {
// final IBindingSet tmp;
// if(a.length != 1) {
// // Unbound if more than one source solution (should not happen).
// tmp = new ListBindingSet();
// } else {
// // Only one solution.
// tmp = a[0];
// }
// Generate the result set and write it on the HTree.
new SubqueryTask(bindingSets, subquery, context).call();
}
// source.
final Iterator source = new SingleValueIterator(bindingSets);
// default sink
final IBlockingBuffer sink = context.getSink();
BOpUtility.copy(//
source, //
sink,//
null, // sink2
null, // mergeSolution (aka parent's source solution).
null, // selectVars (aka projection).
null, // constraints
null // stats were updated above.
// context.getStats()//
);
sink.flush();
// Done.
return null;
} finally {
context.getSource().close();
context.getSink().close();
if (context.getSink2() != null)
context.getSink2().close();
}
}
/**
* Run a subquery.
*/
private class SubqueryTask implements Callable {
/**
* The evaluation context for the parent query.
*/
private final BOpContext parentContext;
/**
* The source binding sets.
*/
private final IBindingSet[] bindingSets;
/**
* The root operator for the subquery.
*/
private final BOp subQueryOp;
public SubqueryTask(final IBindingSet[] bindingSets,
final BOp subQuery,
final BOpContext parentContext) {
this.bindingSets = bindingSets;
this.subQueryOp = subQuery;
this.parentContext = parentContext;
}
@Override
public Void call() throws Exception {
// The subquery
IRunningQuery runningSubquery = null;
// The iterator draining the subquery
ICloseableIterator subquerySolutionItr = null;
try {
final QueryEngine queryEngine = parentContext.getRunningQuery()
.getQueryEngine();
runningSubquery = queryEngine.eval((PipelineOp) subQueryOp,
bindingSets);
try {
// Declare the child query to the parent.
((AbstractRunningQuery) parentContext.getRunningQuery())
.addChild(runningSubquery);
// Iterator visiting the subquery solutions.
subquerySolutionItr = runningSubquery.iterator();
// Buffer the solutions on the hash index.
final long ncopied = state.acceptSolutions(
subquerySolutionItr, stats);
// Wait for the subquery to halt / test for errors.
runningSubquery.get();
// Report the #of solutions in the named solution set.
stats.solutionSetSize.add(ncopied);
// // Publish the solution set on the query context.
// saveSolutionSet();
if (log.isInfoEnabled())
log.info("Solution set " + namedSetRef + " has "
+ ncopied + " solutions.");
} catch (InterruptedException ex) {
// this thread was interrupted, so cancel the subquery.
runningSubquery
.cancel(true/* mayInterruptIfRunning */);
// rethrow the exception.
throw ex;
}
} catch (Throwable t) {
if (runningSubquery == null
|| runningSubquery.getCause() != null) {
/*
* If things fail before we start the subquery, or if a
* subquery fails (due to abnormal termination), then
* propagate the error to the parent and rethrow the
* first cause error out of the subquery.
*
* Note: IHaltable#getCause() considers exceptions
* triggered by an interrupt to be normal termination.
* Such exceptions are NOT propagated here and WILL NOT
* cause the parent query to terminate.
*/
throw new RuntimeException(ControllerTask.this.context
.getRunningQuery().halt(
runningSubquery == null ? t
: runningSubquery.getCause()));
}
} finally {
try {
// ensure subquery is halted.
if (runningSubquery != null)
runningSubquery
.cancel(true/* mayInterruptIfRunning */);
} finally {
// ensure the subquery solution iterator is closed.
if (subquerySolutionItr != null)
subquerySolutionItr.close();
}
}
// Done.
return null;
}
} // SubqueryTask
} // ControllerTask
}