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

com.bigdata.bop.solutions.SliceOp 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 Sep 5, 2010
 */

package com.bigdata.bop.solutions;

import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.log4j.Logger;

import com.bigdata.bop.BOp;
import com.bigdata.bop.BOpContext;
import com.bigdata.bop.IBindingSet;
import com.bigdata.bop.NV;
import com.bigdata.bop.PipelineOp;
import com.bigdata.bop.engine.BOpStats;
import com.bigdata.relation.accesspath.IBlockingBuffer;
import com.bigdata.relation.accesspath.UnsynchronizedArrayBuffer;
import com.bigdata.service.IBigdataFederation;

import cutthecrap.utils.striterators.ICloseableIterator;

/**
 * An operator which imposes an offset/limit on a binding set pipeline.
 * 

* Note: join processing typically involves concurrent processes, hence the * order of the results will not be stable unless the results are sorted before * applying the slice. When a slice is applied without a sort, the same query * may return different results each time it is evaluated. *

* Note: When running on an {@link IBigdataFederation}, this operator must be * imposed on the query controller so it can count the solutions as they flow * through - see {@link #getEvaluationContext()}. *

* Note: {@link SliceOp} is safe for concurrent invocations for the same query. * Multiple chunks may flow through multiple invocations of the operator so long * as they use the same {@link BOpStats} object. * * @author Bryan Thompson * @version $Id$ */ public class SliceOp extends PipelineOp { private final static transient Logger log = Logger.getLogger(SliceOp.class); /** * */ private static final long serialVersionUID = 1L; public interface Annotations extends PipelineOp.Annotations { /** * The first solution to be returned to the caller (origin ZERO). */ String OFFSET = SliceOp.class.getName() + ".offset"; long DEFAULT_OFFSET = 0L; /** * The maximum #of solutions to be returned to the caller (default is * all). */ String LIMIT = SliceOp.class.getName() + ".limit"; /** * A value of {@link Long#MAX_VALUE} is used to indicate that there is * no limit. */ long DEFAULT_LIMIT = Long.MAX_VALUE; } /** * Deep Copy constructor. * @param op */ public SliceOp(final SliceOp op) { super(op); } public SliceOp(final BOp[] args, final NV... annotations) { this(args, NV.asMap(annotations)); } /** * Shallow copy constructor. * * @param args * @param annotations */ public SliceOp(final BOp[] args, final Map annotations) { super(args, annotations); // if (args.length != 1) // throw new IllegalArgumentException(); // // if (!(args[0] instanceof BindingSetPipelineOp)) // throw new IllegalArgumentException(); switch (getEvaluationContext()) { case CONTROLLER: break; default: throw new UnsupportedOperationException( Annotations.EVALUATION_CONTEXT + "=" + getEvaluationContext()); } if (!isPipelinedEvaluation()) { /* * Evaluation must be pipelined or the SLICE will wait for all * solutions to be materialized before it runs. */ throw new UnsupportedOperationException(Annotations.PIPELINED + "=" + isPipelinedEvaluation()); } if (!isSharedState()) throw new UnsupportedOperationException(Annotations.SHARED_STATE + "=" + isSharedState()); // SLICE must preserve order. if (isReorderSolutions()) throw new UnsupportedOperationException( Annotations.REORDER_SOLUTIONS + "=" + isReorderSolutions()); } /** * @see Annotations#OFFSET */ public long getOffset() { return getProperty(Annotations.OFFSET, Annotations.DEFAULT_OFFSET); } /** * @see Annotations#LIMIT */ public long getLimit() { return getProperty(Annotations.LIMIT, Annotations.DEFAULT_LIMIT); } // /** // * Overridden to return true since the correct decision // * semantics for the slice depend on concurrent invocations for the same // * query having the same {@link SliceStats} object. // *

// * {@inheritDoc} // */ // @Override // final public boolean isSharedState() { // // return true; // // } /** * Extends {@link BOpStats} to capture the state of the {@link SliceOp}. */ public static class SliceStats extends BOpStats { /** * */ private static final long serialVersionUID = 1L; /** #of solutions visited. */ public final AtomicLong nseen = new AtomicLong(); /** #of solutions accepted. */ public final AtomicLong naccepted = new AtomicLong(); @Override public void add(final BOpStats o) { if (this == o) { // Do not add to self! return; } super.add(o); if (o instanceof SliceStats) { final SliceStats t = (SliceStats) o; nseen.addAndGet(t.nseen.get()); naccepted.addAndGet(t.naccepted.get()); } } @Override protected void toString(final StringBuilder sb) { sb.append(",nseen=" + nseen); sb.append(",naccepted=" + naccepted); } } @Override public SliceStats newStats() { return new SliceStats(); } @Override public FutureTask eval(final BOpContext context) { return new FutureTask(new SliceTask(this, context)); } /** * Copy the source to the sink or the alternative sink depending on the * condition. */ static private class SliceTask implements Callable { private final SliceOp op; private final BOpContext context; /** #of solutions to skip before accepting the first solution. */ private final long offset; /** #of solutions to accept. */ private final long limit; // private final long last; private final SliceStats stats; SliceTask(final SliceOp op, final BOpContext context) { this.op = op; this.context = context; this.offset = op.getOffset(); this.limit = op.getLimit(); if (offset < 0) throw new IllegalArgumentException(Annotations.OFFSET); if (limit < 0) throw new IllegalArgumentException(Annotations.LIMIT); this.stats = (SliceStats) context.getStats(); // this.last = offset + limit; // this.last = BigInteger.valueOf(offset).add( // BigInteger.valueOf(limit)).min( // BigInteger.valueOf(Long.MAX_VALUE)).longValue(); } @Override public Void call() throws Exception { final ICloseableIterator source = context .getSource(); final IBlockingBuffer sink = context.getSink(); try { /* * buffer forms chunks which get flushed onto the sink. * * @todo if we have visibility into the #of source chunks, then do * not buffer more than min(#source,#needed). */ final UnsynchronizedArrayBuffer out = new UnsynchronizedArrayBuffer( sink, IBindingSet.class, op.getChunkCapacity()); while (source.hasNext()) { final IBindingSet[] chunk = source.next(); /* * Batch each chunk through a lock for better concurrency * (avoids CAS contention). * * Note: This is safe because the source chunk is already * materialized and the sink will not block (that is part of the * bop evaluation contract). * * Note: We need to be careful here with concurrent close of the * sink (which is the shared queryBuffer) by concurrent * SliceOps. The problem is that the slice can count off the * solutions without having them flushed all the way through to * the queryBuffer, but we can not close the query buffer until * we actually see the last solution added to the query buffer. * This is why the slice flushes the buffer while it is * synchronized. */ synchronized (stats) { if (log.isTraceEnabled()) log.trace(toString() + ": stats=" + stats + ", sink=" + sink); final boolean halt = handleChunk(out, chunk); if (!out.isEmpty()) out.flush(); sink.flush(); if (halt) { if (log.isInfoEnabled()) log.info("Slice will interrupt query."); context.getRunningQuery().halt((Void) null); } } } } finally { sink.close(); } return null; } /** *

* Apply the slice semantics to a chunk of binding sets. *

*

example

*

* offset=2, limit=3, last=3+2=5. The number line represents the * observed binding sets. The first binding set is at index ZERO (0). * The initial conditions are: nseen(S)=0 and naccepted(A)=0. S is * placed beneath each observation and paired with the value of A for * that observation. The offset is satisfied when S=2 and observation * ONE (1) is the first observation accepted. The limit is satisfied * when A=3, which occurs at observation FOUR (4) which is also * S=last=5. The observation on which the limit is satisfied is accepted * and the slice halts as no more observations should be made. {2,3,4} * are accepted. *

* *
         *  0 1 2 3 4 5 6 7 8 9
         *  S=1, A=0
         * 
* *
         *  0 1 2 3 4 5 6 7 8 9
         *    S=2, A=0
         * 
* *
         *  0 1 2 3 4 5 6 7 8 9
         *      S=3, A=1 {2}
         * 
* *
         *  0 1 2 3 4 5 6 7 8 9
         *        S=4, A=2 {2,3}
         * 
* *
         *  0 1 2 3 4 5 6 7 8 9
         *          S=5, A=3 {2,3,4}
         * 
*

* Note: The caller MUST be synchronized on the shared * {@link SliceStats} in order for the decision process to be thread * safe. * * @param chunk * The chunk of binding sets. * * @return true if the slice is satisfied and the query * should halt. */ private boolean handleChunk( final UnsynchronizedArrayBuffer out, final IBindingSet[] chunk) { stats.chunksIn.increment(); // int nadded = 0; for (int i = 0; i < chunk.length; i++) { if (stats.naccepted.get() >= limit) return true; // nothing more will be accepted. stats.unitsIn.increment(); final long S = stats.nseen.incrementAndGet(); if (S <= offset) continue; // skip solution. final long A = stats.naccepted.get(); if (A < limit) { final IBindingSet bset = chunk[i]; out.add(bset); // nadded++; stats.naccepted.incrementAndGet(); if (log.isTraceEnabled()) log.trace(toString() + ":" + bset); } } // next bindingSet return false; } @Override public String toString() { return super.toString() + "{offset=" + offset + ",limit=" + limit + ",nseen=" + stats.nseen + ",naccepted=" + stats.naccepted + "}"; } } // /** // * This operator must be evaluated on the query controller. // */ // @Override // public BOpEvaluationContext getEvaluationContext() { // // return BOpEvaluationContext.CONTROLLER; // // } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy