Details

      Description

      SPARQL Update uses a MutableTupleQueryResult to store the result of the query body and then iterates over it to apply DELETE and INSERT clauses. The problem is that the MutableTupleQueryResult does not scale, it's internal data structure is a linked list and BindingSets.get() is implemented as a scan. This means, the larger the number of tuples, the longer it takes to get the next tuple. We need a random access list instead.

      package org.openrdf.query.impl;
      
      import java.util.ArrayList;
      import java.util.Arrays;
      import java.util.Collection;
      import java.util.HashSet;
      import java.util.LinkedHashSet;
      import java.util.LinkedList;
      import java.util.List;
      import java.util.NoSuchElementException;
      import java.util.Set;
      
      import info.aduna.iteration.Iteration;
      import info.aduna.iteration.Iterations;
      
      import org.openrdf.query.BindingSet;
      import org.openrdf.query.QueryEvaluationException;
      import org.openrdf.query.TupleQueryResult;
      
      /**
       * An implementation of the {@link TupleQueryResult} interface that stores the
       * complete query result in memory. The query results in a
       * MutableTupleQueryResult can be iterated over multiple times and can also be
       * iterated over in reverse order.
       * 
       * @author Arjohn Kampman
       */
      public class MutableTupleQueryResult implements TupleQueryResult, Cloneable {
      
      	/*-----------*
      	 * Variables *
      	 *-----------*/
      
      	private Set<String> bindingNames = new LinkedHashSet<String>();
      
      	private List<BindingSet> bindingSets = new LinkedList<BindingSet>();
      
      	/**
      	 * The index of the next element that will be returned by a call to
      	 * {@link #next()}.
      	 */
      	private int currentIndex = 0;
      
      	/**
      	 * The index of the last element that was returned by a call to
      	 * {@link #next()} or {@link #previous()}. Equal to -1 if there is no such
      	 * element.
      	 */
      	private int lastReturned = -1;
      
      	/*--------------*
      	 * Constructors *
      	 *--------------*/
      
      	public <E extends Exception> MutableTupleQueryResult(Collection<String> bindingNames,
      			BindingSet... bindingSets)
      	{
      		this(bindingNames, Arrays.asList(bindingSets));
      	}
      
      	/**
      	 * Creates a query result table with the supplied binding names.
      	 * <em>The supplied list of binding names is assumed to be constant</em>;
      	 * care should be taken that the contents of this list doesn't change after
      	 * supplying it to this solution.
      	 * 
      	 * @param bindingNames
      	 *        The binding names, in order of projection.
      	 */
      	public MutableTupleQueryResult(Collection<String> bindingNames,
      			Collection<? extends BindingSet> bindingSets)
      	{
      		this.bindingNames.addAll(bindingNames);
      		this.bindingSets.addAll(bindingSets);
      	}
      
      	public <E extends Exception> MutableTupleQueryResult(Collection<String> bindingNames,
      			Iteration<? extends BindingSet, E> bindingSetIter)
      		throws E
      	{
      		this.bindingNames.addAll(bindingNames);
      		Iterations.addAll(bindingSetIter, this.bindingSets);
      	}
      
      	public MutableTupleQueryResult(TupleQueryResult tqr)
      		throws QueryEvaluationException
      	{
      		this(tqr.getBindingNames(), tqr);
      	}
      
      	/*---------*
      	 * Methods *
      	 *---------*/
      
      	public List<String> getBindingNames() {
      		return new ArrayList<String>(bindingNames);
      	}
      
      	public int size() {
      		return bindingSets.size();
      	}
      
      	public BindingSet get(int index) {
      		return bindingSets.get(index);
      	}
      
      	public int getIndex() {
      		return currentIndex;
      	}
      
      	public void setIndex(int index) {
      		if (index < 0 || index > bindingSets.size() + 1) {
      			throw new IllegalArgumentException("Index out of range: " + index);
      		}
      
      		this.currentIndex = index;
      	}
      
      	public boolean hasNext() {
      		return currentIndex < bindingSets.size();
      	}
      
      	public BindingSet next() {
      		if (hasNext()) {
      			BindingSet result = get(currentIndex);
      			lastReturned = currentIndex;
      			currentIndex++;
      			return result;
      		}
      
      		throw new NoSuchElementException();
      	}
      
      	public boolean hasPrevious() {
      		return currentIndex > 0;
      	}
      
      	public BindingSet previous() {
      		if (hasPrevious()) {
      			BindingSet result = bindingSets.get(currentIndex - 1);
      			currentIndex--;
      			lastReturned = currentIndex;
      			return result;
      		}
      
      		throw new NoSuchElementException();
      	}
      
      	/**
      	 * Moves the cursor to the start of the query result, just before the first
      	 * binding set. After calling this method, the result can be iterated over
      	 * from scratch.
      	 */
      	public void beforeFirst() {
      		currentIndex = 0;
      	}
      
      	/**
      	 * Moves the cursor to the end of the query result, just after the last
      	 * binding set.
      	 */
      	public void afterLast() {
      		currentIndex = bindingSets.size() + 1;
      	}
      
      	/**
      	 * Inserts the specified binding set into the list. The binding set is
      	 * inserted immediately before the next element that would be returned by
      	 * {@link #next()}, if any, and after the next element that would be
      	 * returned by {@link #previous}, if any. (If the table contains no binding
      	 * sets, the new element becomes the sole element on the table.) The new
      	 * element is inserted before the implicit cursor: a subsequent call to
      	 * <tt>next()</tt> would be unaffected, and a subsequent call to
      	 * <tt>previous()</tt> would return the new binding set.
      	 * 
      	 * @param bindingSet
      	 *        The binding set to insert.
      	 */
      	public void insert(BindingSet bindingSet) {
      		insert(currentIndex, bindingSet);
      	}
      
      	public void insert(int index, BindingSet bindingSet) {
      		bindingSets.add(index, bindingSet);
      
      		if (currentIndex > index) {
      			currentIndex++;
      		}
      
      		lastReturned = -1;
      	}
      
      	public void append(BindingSet bindingSet) {
      		bindingSets.add(bindingSet);
      		lastReturned = -1;
      	}
      
      	public void set(BindingSet bindingSet) {
      		if (lastReturned == -1) {
      			throw new IllegalStateException();
      		}
      
      		set(lastReturned, bindingSet);
      	}
      
      	public BindingSet set(int index, BindingSet bindingSet) {
      		return bindingSets.set(index, bindingSet);
      	}
      
      	public void remove() {
      		if (lastReturned == -1) {
      			throw new IllegalStateException();
      		}
      
      		remove(lastReturned);
      
      		if (currentIndex > lastReturned) {
      			currentIndex--;
      		}
      
      		lastReturned = -1;
      	}
      
      	public BindingSet remove(int index) {
      		BindingSet result = bindingSets.remove(index);
      
      		if (currentIndex > index) {
      			currentIndex--;
      		}
      
      		lastReturned = -1;
      
      		return result;
      	}
      
      	public void clear() {
      		bindingNames.clear();
      		bindingSets.clear();
      		currentIndex = 0;
      		lastReturned = -1;
      	}
      
      	public void close() {
      		// no-opp
      	}
      
      	@Override
      	public MutableTupleQueryResult clone()
      		throws CloneNotSupportedException
      	{
      		MutableTupleQueryResult clone = (MutableTupleQueryResult)super.clone();
      		clone.bindingNames = new LinkedHashSet<String>(bindingNames);
      		clone.bindingSets = new LinkedList<BindingSet>(bindingSets);
      		return clone;
      	}
      
      }
      

        Activity

        michaelschmidt michaelschmidt created issue -
        michaelschmidt michaelschmidt made changes -
        Field Original Value New Value
        Parent BLZG-1306 [ 12752 ]
        Issue Type Bug [ 1 ] Sub-task [ 5 ]
        michaelschmidt michaelschmidt made changes -
        Status Open [ 1 ] Accepted [ 10101 ]
        michaelschmidt michaelschmidt made changes -
        Status Accepted [ 10101 ] In Progress [ 3 ]
        michaelschmidt michaelschmidt made changes -
        Status In Progress [ 3 ] Resolved [ 5 ]
        michaelschmidt michaelschmidt made changes -
        Status Resolved [ 5 ] In Review [ 10100 ]
        michaelschmidt michaelschmidt made changes -
        Resolution Done [ 10000 ]
        Status In Review [ 10100 ] Done [ 10000 ]
        michaelschmidt michaelschmidt made changes -
        Fix Version/s BLAZEGRAPH_RELEASE_1_5_3 [ 10165 ]
        michaelschmidt michaelschmidt made changes -
        Affects Version/s BLAZEGRAPH_RELEASE_1_5_2 [ 10164 ]
        michaelschmidt michaelschmidt made changes -
        Component/s SPARQL UPDATE [ 10020 ]
        bryanthompson bryanthompson made changes -
        Parent BLZG-1306 [ 12752 ]
        Issue Type Sub-task [ 5 ] Bug [ 1 ]
        beebs Brad Bebee made changes -
        Workflow Trac Import v7 [ 20119 ] Trac Import v8 [ 21452 ]

          People

          • Assignee:
            bryanthompson bryanthompson
            Reporter:
            michaelschmidt michaelschmidt
          • Votes:
            0 Vote for this issue
            Watchers:
            6 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved: