You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by an...@apache.org on 2015/11/14 15:42:12 UTC

[04/13] jena git commit: JENA-624: New transactional in-memory Dataset with MR+SW locking

JENA-624: New transactional in-memory Dataset with MR+SW locking

Introduces a new package in jena-arq named o.a.j.sparql.core.mem which includes a DatasetGraph implementation named DatasetGraphInMemory. This class uses types in a new package in jena-base named o.a.j.atlas.lib.persistent that implement simple persistent data structures (persistent in the sense of immutable) and a new MR+SW lock in jena-core named o.a.j.shared.LockMRPlusSW. DatasetGraphInMemory provides MR+SW locking semantics and full transactionality. In default operation it provides speedy query behavior for almost all cases, due to a symmetrical design using multiple covering indexes (six for quads and three for triples). However, it may also be used with other index designs by constructor injection using the types o.a.j.sparql.core.mem.QuadTable and o.a.j.sparql.core.mem.TripleTable.


Project: http://git-wip-us.apache.org/repos/asf/jena/repo
Commit: http://git-wip-us.apache.org/repos/asf/jena/commit/61054a21
Tree: http://git-wip-us.apache.org/repos/asf/jena/tree/61054a21
Diff: http://git-wip-us.apache.org/repos/asf/jena/diff/61054a21

Branch: refs/heads/master
Commit: 61054a21351736125e7b9f8920933ddafd1feb8a
Parents: 2410605
Author: ajs6f <aj...@virginia.edu>
Authored: Thu Nov 12 09:43:48 2015 -0500
Committer: ajs6f <aj...@virginia.edu>
Committed: Thu Nov 12 09:53:44 2015 -0500

----------------------------------------------------------------------
 jena-arq/DEPENDENCIES                           |   4 +
 jena-arq/pom.xml                                |   9 +-
 .../org/apache/jena/query/DatasetFactory.java   | 467 +++++++++----------
 .../jena/sparql/core/DatasetGraphFactory.java   |  56 +--
 .../core/assembler/InMemDatasetAssembler.java   |  66 +++
 .../sparql/core/mem/DatasetGraphInMemory.java   | 311 ++++++++++++
 .../core/mem/DatasetPrefixStorageInMemory.java  |  99 ++++
 .../jena/sparql/core/mem/FourTupleMap.java      |  91 ++++
 .../jena/sparql/core/mem/GraphInMemory.java     |  50 ++
 .../apache/jena/sparql/core/mem/HexTable.java   | 125 +++++
 .../jena/sparql/core/mem/PMapQuadTable.java     | 137 ++++++
 .../jena/sparql/core/mem/PMapTripleTable.java   | 118 +++++
 .../jena/sparql/core/mem/PMapTupleTable.java    | 120 +++++
 .../apache/jena/sparql/core/mem/QuadTable.java  |  67 +++
 .../jena/sparql/core/mem/QuadTableForm.java     | 267 +++++++++++
 .../apache/jena/sparql/core/mem/TriTable.java   | 117 +++++
 .../jena/sparql/core/mem/TripleTable.java       |  46 ++
 .../jena/sparql/core/mem/TripleTableForm.java   | 153 ++++++
 .../apache/jena/sparql/core/mem/TupleSlot.java  |  27 ++
 .../apache/jena/sparql/core/mem/TupleTable.java |  53 +++
 .../jena/sparql/core/mem/package-info.java      |  22 +
 .../org/apache/jena/sparql/core/TS_Core.java    |   4 +
 .../sparql/core/assembler/TestAssembler.java    |  27 ++
 .../assembler/TestInMemDatasetAssembler.java    | 140 ++++++
 .../jena/sparql/core/mem/QuadTableTest.java     |  50 ++
 .../core/mem/TestDatasetGraphInMemory.java      | 255 ++++++++++
 .../jena/sparql/core/mem/TestHexTable.java      |  93 ++++
 .../jena/sparql/core/mem/TestInMemory.java      |  32 ++
 .../sparql/core/mem/TestQuadTableForms.java     |  92 ++++
 .../jena/sparql/core/mem/TestTriTable.java      |  30 ++
 .../sparql/core/mem/TestTripleTableForms.java   |  84 ++++
 .../jena/sparql/core/mem/TripleTableTest.java   |  56 +++
 .../sparql/core/mem/TupleTableFormsTest.java    |  73 +++
 .../jena/sparql/core/mem/TupleTableTest.java    | 112 +++++
 jena-base/pom.xml                               |   8 +-
 .../apache/jena/atlas/lib/persistent/PMap.java  |  84 ++++
 .../apache/jena/atlas/lib/persistent/PSet.java  |  68 +++
 .../atlas/lib/persistent/PersistentMap.java     |  79 ++++
 .../atlas/lib/persistent/PersistentSet.java     |  59 +++
 .../jena/atlas/lib/persistent/package-info.java |  22 +
 .../java/org/apache/jena/atlas/TC_Atlas.java    |   4 +-
 .../atlas/lib/persistent/TS_Persistent.java     |  26 ++
 .../jena/atlas/lib/persistent/TestPMap.java     |  74 +++
 .../jena/atlas/lib/persistent/TestPSet.java     |  47 ++
 jena-core/DEPENDENCIES                          |   3 +
 jena-core/pom.xml                               |   6 +
 .../main/java/org/apache/jena/assembler/JA.java |  46 +-
 .../org/apache/jena/shared/LockMRPlusSW.java    |  39 ++
 .../apache/jena/shared/TestLockMRPlusSW.java    |  82 ++++
 .../apache/jena/shared/TestSharedPackage.java   |   8 +-
 jena-parent/pom.xml                             |  12 +
 51 files changed, 3813 insertions(+), 307 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jena/blob/61054a21/jena-arq/DEPENDENCIES
----------------------------------------------------------------------
diff --git a/jena-arq/DEPENDENCIES b/jena-arq/DEPENDENCIES
index 0b0710f..bba60f4 100644
--- a/jena-arq/DEPENDENCIES
+++ b/jena-arq/DEPENDENCIES
@@ -14,3 +14,7 @@ SLF4J : http://www.slf4j.org/
 
 JUnit : http://junit.org/
   Common Public License - v 1.0
+  
+Awaitility : https://github.com/jayway/awaitility/
+  Apache Software License - v 2.0
+

http://git-wip-us.apache.org/repos/asf/jena/blob/61054a21/jena-arq/pom.xml
----------------------------------------------------------------------
diff --git a/jena-arq/pom.xml b/jena-arq/pom.xml
index d0ce43a..affd48f 100644
--- a/jena-arq/pom.xml
+++ b/jena-arq/pom.xml
@@ -94,7 +94,7 @@
       <groupId>org.apache.httpcomponents</groupId>
       <artifactId>httpclient-cache</artifactId>
     </dependency>
-
+    
     <dependency>
       <groupId>org.apache.thrift</groupId>
       <artifactId>libthrift</artifactId>
@@ -118,11 +118,10 @@
       <groupId>org.apache.commons</groupId>
       <artifactId>commons-lang3</artifactId>
     </dependency>
-    
+
     <dependency>
-      <groupId>org.mockito</groupId>
-      <artifactId>mockito-core</artifactId>
-      <version>1.9.5</version>
+      <groupId>com.jayway.awaitility</groupId>
+      <artifactId>awaitility</artifactId>
       <scope>test</scope>
     </dependency>
 

http://git-wip-us.apache.org/repos/asf/jena/blob/61054a21/jena-arq/src/main/java/org/apache/jena/query/DatasetFactory.java
----------------------------------------------------------------------
diff --git a/jena-arq/src/main/java/org/apache/jena/query/DatasetFactory.java b/jena-arq/src/main/java/org/apache/jena/query/DatasetFactory.java
index fdd6739..3acbdf2 100644
--- a/jena-arq/src/main/java/org/apache/jena/query/DatasetFactory.java
+++ b/jena-arq/src/main/java/org/apache/jena/query/DatasetFactory.java
@@ -16,253 +16,226 @@
  * limitations under the License.
  */
 
-package org.apache.jena.query ;
-
-import java.util.List ;
-
-import org.apache.jena.assembler.Assembler ;
-import org.apache.jena.rdf.model.Model ;
-import org.apache.jena.rdf.model.Resource ;
-import org.apache.jena.sparql.ARQException ;
-import org.apache.jena.sparql.core.DatasetGraph ;
-import org.apache.jena.sparql.core.DatasetGraphFactory ;
-import org.apache.jena.sparql.core.DatasetImpl ;
-import org.apache.jena.sparql.core.assembler.DatasetAssembler ;
-import org.apache.jena.sparql.util.DatasetUtils ;
-import org.apache.jena.sparql.util.graph.GraphUtils ;
-import org.apache.jena.util.FileManager ;
-
-/** Make Datasets and DataSources in various ways */
-
+package org.apache.jena.query;
+
+import java.util.List;
+
+import org.apache.jena.assembler.Assembler;
+import org.apache.jena.rdf.model.Model;
+import org.apache.jena.rdf.model.Resource;
+import org.apache.jena.sparql.ARQException;
+import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.sparql.core.DatasetGraphFactory;
+import org.apache.jena.sparql.core.DatasetImpl;
+import org.apache.jena.sparql.core.assembler.DatasetAssembler;
+import org.apache.jena.sparql.util.DatasetUtils;
+import org.apache.jena.sparql.util.graph.GraphUtils;
+import org.apache.jena.util.FileManager;
+
+/**
+ * Makes {@link Dataset}s in various ways.
+ *
+ */
 public class DatasetFactory {
-    /** Create an in-memory, modifiable Dataset */
-    public static Dataset createMem() {
-        return create(DatasetGraphFactory.createMem()) ;
-    }
-
-    /**
-     * Create an in-memory, modifiable Dataset. New graphs must be explicitly
-     * added using .addGraph.
-     */
-    public static Dataset createMemFixed() {
-        return create(DatasetGraphFactory.createMemFixed()) ;
-    }
-
-    /**
-     * Create a dataset with the given model as the default graph
-     * 
-     * @param model
-     *            The model for the default graph
-     * @return Dataset
-     */
-    public static Dataset create(Model model) {
-        return new DatasetImpl(model) ;
-    }
-
-    /**
-     * Create a dataset: clone the dataset structure of named graohs, and share
-     * the graphs themselves.
-     * 
-     * @param dataset
-     *            Dataset to clone structure from.
-     * @return Dataset
-     */
-    public static Dataset create(Dataset dataset) {
-        return new DatasetImpl(dataset) ;
-    }
-
-    /**
-     * Wrap a datasetgraph to make a mutable dataset
-     * 
-     * @param dataset
-     *            DatasetGraph
-     * @return Dataset
-     */
-    public static Dataset create(DatasetGraph dataset) {
-        return DatasetImpl.wrap(dataset) ;
-    }
-
-    /**
-     * Create a dataset based on a list of URIs : these are merged into the
-     * default graph of the dataset.
-     * 
-     * @param uriList
-     *            URIs merged to form the default dataset
-     * @return Dataset
-     */
-
-    public static Dataset create(List<String> uriList) {
-        return create(uriList, null, null) ;
-    }
-
-    /**
-     * Create a dataset with a default graph and no named graphs
-     * 
-     * @param uri
-     *            URIs merged to form the default dataset
-     * @return Dataset
-     */
-
-    public static Dataset create(String uri) {
-        return create(uri, null, null) ;
-    }
-
-    /**
-     * Create a dataset based on a list of URIs : these are merged into the
-     * default graph of the dataset.
-     * 
-     * @param uriList
-     *            URIs merged to form the default dataset
-     * @param fileManager
-     * @return Dataset
-     */
-
-    /**
-     * Create a named graph container of graphs based on a list of URIs.
-     * 
-     * @param namedSourceList
-     * @return Dataset
-     */
-
-    public static Dataset createNamed(List<String> namedSourceList) {
-        return create((List<String>)null, namedSourceList, null) ;
-    }
-
-    /**
-     * Create a dataset based on two list of URIs. The first lists is used to
-     * create the background (unnamed graph) by merging, the second is used to
-     * create the collection of named graphs.
-     * 
-     * (Jena calls graphs "Models" and triples "Statements")
-     * 
-     * @param uriList
-     *            graphs to be loaded into the unnamed, default graph
-     * @param namedSourceList
-     *            graphs to be atatched as named graphs
-     * @return Dataset
-     */
-
-    public static Dataset create(List<String> uriList, List<String> namedSourceList) {
-        return create(uriList, namedSourceList, null) ;
-    }
-
-    /**
-     * Create a dataset container based on two list of URIs. The first is used
-     * to create the background (unnamed graph), the second is used to create
-     * the collection of named graphs.
-     * 
-     * (Jena calls graphs "Models" and triples "Statements")
-     * 
-     * @param uri
-     *            graph to be loaded into the unnamed, default graph
-     * @param namedSourceList
-     *            graphs to be attached as named graphs
-     * @return Dataset
-     */
-
-    public static Dataset create(String uri, List<String> namedSourceList) {
-        return create(uri, namedSourceList, null) ;
-    }
-
-    /**
-     * Create a named graph container based on two list of URIs. The first is
-     * used to create the background (unnamed graph), the second is used to
-     * create the collection of named graphs.
-     * 
-     * (Jena calls graphs "Models" and triples "Statements")
-     * 
-     * @param uri
-     *            graph to be loaded into the unnamed, default graph
-     * @param namedSourceList
-     *            graphs to be atatched as named graphs
-     * @param baseURI
-     *            baseURI for relative URI expansion
-     * @return Dataset
-     */
-
-    public static Dataset create(String uri, List<String> namedSourceList, String baseURI) {
-        return DatasetUtils.createDataset(uri, namedSourceList, baseURI) ;
-    }
-
-    /**
-     * Create a named graph container based on two list of URIs. The first is
-     * used to create the background (unnamed graph), the second is used to
-     * create the collection of named graphs.
-     * 
-     * (Jena calls graphs "Models" and triples "Statements")
-     * 
-     * @param uriList
-     *            graphs to be loaded into the unnamed, default graph
-     * @param namedSourceList
-     *            graphs to be atatched as named graphs
-     * @param baseURI
-     *            baseURI for relative URI expansion
-     * @return Dataset
-     */
-
-    public static Dataset create(List<String> uriList, List<String> namedSourceList, String baseURI) {
-        return DatasetUtils.createDataset(uriList, namedSourceList, baseURI) ;
-    }
-
-    public static Dataset make(Dataset ds, Model defaultModel) {
-        Dataset ds2 = new DatasetImpl(ds) ;
-        ds2.setDefaultModel(defaultModel) ;
-        return ds2 ;
-    }
-
-    // Assembler.
-    /**
-     * Assembler a dataset from the model in a file
-     * 
-     * @param filename
-     *            The filename
-     * @return Dataset
-     */
-    public static Dataset assemble(String filename) {
-        Model model = FileManager.get().loadModel(filename) ;
-        return assemble(model) ;
-    }
-
-    /**
-     * Assembler a dataset from the model in a file
-     * 
-     * @param filename
-     *            The filename
-     * @param resourceURI
-     *            URI for the dataset to assembler
-     * @return Dataset
-     */
-    public static Dataset assemble(String filename, String resourceURI) {
-        Model model = FileManager.get().loadModel(filename) ;
-        Resource r = model.createResource(resourceURI) ;
-        return assemble(r) ;
-    }
-
-    /**
-     * Assembler a dataset from the model
-     * 
-     * @param model
-     * @return Dataset
-     */
-    public static Dataset assemble(Model model) {
-        Resource r = GraphUtils.findRootByType(model, DatasetAssembler.getType()) ;
-        if ( r == null )
-            throw new ARQException("No root found for type <" + DatasetAssembler.getType() + ">") ;
-
-        return assemble(r) ;
-    }
-
-    /**
-     * Assembler a dataset from a resource
-     * 
-     * @param resource
-     *            The resource for the dataset
-     * @return Dataset
-     */
-
-    public static Dataset assemble(Resource resource) {
-        Dataset ds = (Dataset)Assembler.general.open(resource) ;
-        return ds ;
-    }
 
+	/**
+	 * @return an in-memory, modifiable Dataset
+	 * @deprecated Prefer {@link #createGeneral()} or {@link #createTxnMem()}
+	 */
+	@Deprecated
+	public static Dataset createMem() {
+		return create(DatasetGraphFactory.createMem());
+	}
+
+	/**
+	 * @return a transactional, in-memory, modifiable Dataset with MR+SW locking
+	 */
+	public static Dataset createTxnMem() {
+		return create(DatasetGraphFactory.createTxnMem());
+	}
+
+	/**
+	 * @return a general-purpose, in-memory, modifiable Dataset
+	 */
+	public static Dataset createGeneral() {
+		return createMem();
+	}
+
+	/**
+	 * @return an in-memory, modifiable Dataset. New graphs must be explicitly added using .addGraph.
+	 */
+	public static Dataset createMemFixed() {
+		return create(DatasetGraphFactory.createMemFixed());
+	}
+
+	/**
+	 * @param model The model for the default graph
+	 * @return a dataset with the given model as the default graph
+	 */
+	public static Dataset create(final Model model) {
+		return new DatasetImpl(model);
+	}
+
+	/**
+	 * @param dataset Dataset to clone structure from.
+	 * @return a dataset: clone the dataset structure of named graohs, and share the graphs themselves.
+	 */
+	public static Dataset create(final Dataset dataset) {
+		return new DatasetImpl(dataset);
+	}
+
+	/**
+	 * Wrap a {@link DatasetGraph} to make a mutable dataset
+	 *
+	 * @param dataset DatasetGraph
+	 * @return Dataset
+	 */
+	public static Dataset create(final DatasetGraph dataset) {
+		return DatasetImpl.wrap(dataset);
+	}
+
+	/**
+	 * @param uriList URIs merged to form the default dataset
+	 * @return a dataset based on a list of URIs : these are merged into the default graph of the dataset.
+	 */
+	public static Dataset create(final List<String> uriList) {
+		return create(uriList, null, null);
+	}
+
+	/**
+	 * @param uri URIs merged to form the default dataset
+	 * @return a dataset with a default graph and no named graphs
+	 */
+
+	public static Dataset create(final String uri) {
+		return create(uri, null, null);
+	}
+
+	/**
+	 * @param namedSourceList
+	 * @return a named graph container of graphs based on a list of URIs.
+	 */
+
+	public static Dataset createNamed(final List<String> namedSourceList) {
+		return create((List<String>) null, namedSourceList, null);
+	}
+
+	/**
+	 * Create a dataset based on two list of URIs. The first lists is used to create the background (unnamed graph) by
+	 * merging, the second is used to create the collection of named graphs.
+	 *
+	 * (Jena calls graphs "Models" and triples "Statements")
+	 *
+	 * @param uriList graphs to be loaded into the unnamed, default graph
+	 * @param namedSourceList graphs to be atatched as named graphs
+	 * @return Dataset
+	 */
+
+	public static Dataset create(final List<String> uriList, final List<String> namedSourceList) {
+		return create(uriList, namedSourceList, null);
+	}
+
+	/**
+	 * Create a dataset container based on two list of URIs. The first is used to create the background (unnamed graph),
+	 * the second is used to create the collection of named graphs.
+	 *
+	 * (Jena calls graphs "Models" and triples "Statements")
+	 *
+	 * @param uri graph to be loaded into the unnamed, default graph
+	 * @param namedSourceList graphs to be attached as named graphs
+	 * @return Dataset
+	 */
+
+	public static Dataset create(final String uri, final List<String> namedSourceList) {
+		return create(uri, namedSourceList, null);
+	}
+
+	/**
+	 * Create a named graph container based on two list of URIs. The first is used to create the background (unnamed
+	 * graph), the second is used to create the collection of named graphs.
+	 *
+	 * (Jena calls graphs "Models" and triples "Statements")
+	 *
+	 * @param uri graph to be loaded into the unnamed, default graph
+	 * @param namedSourceList graphs to be atatched as named graphs
+	 * @param baseURI baseURI for relative URI expansion
+	 * @return Dataset
+	 */
+
+	public static Dataset create(final String uri, final List<String> namedSourceList, final String baseURI) {
+		return DatasetUtils.createDataset(uri, namedSourceList, baseURI);
+	}
+
+	/**
+	 * Create a named graph container based on two list of URIs. The first is used to create the background (unnamed
+	 * graph), the second is used to create the collection of named graphs.
+	 *
+	 * (Jena calls graphs "Models" and triples "Statements")
+	 *
+	 * @param uriList graphs to be loaded into the unnamed, default graph
+	 * @param namedSourceList graphs to be atatched as named graphs
+	 * @param baseURI baseURI for relative URI expansion
+	 * @return Dataset
+	 */
+
+	public static Dataset create(final List<String> uriList, final List<String> namedSourceList, final String baseURI) {
+		return DatasetUtils.createDataset(uriList, namedSourceList, baseURI);
+	}
+
+	public static Dataset make(final Dataset ds, final Model defaultModel) {
+		final Dataset ds2 = new DatasetImpl(ds);
+		ds2.setDefaultModel(defaultModel);
+		return ds2;
+	}
+
+	// Assembler-based Dataset creation.
+
+	/**
+	 * Assemble a dataset from the model in a file
+	 *
+	 * @param filename The filename
+	 * @return Dataset
+	 */
+	public static Dataset assemble(final String filename) {
+		final Model model = FileManager.get().loadModel(filename);
+		return assemble(model);
+	}
+
+	/**
+	 * Assemble a dataset from the model in a file
+	 *
+	 * @param filename The filename
+	 * @param resourceURI URI for the dataset to assembler
+	 * @return Dataset
+	 */
+	public static Dataset assemble(final String filename, final String resourceURI) {
+		final Model model = FileManager.get().loadModel(filename);
+		final Resource r = model.createResource(resourceURI);
+		return assemble(r);
+	}
+
+	/**
+	 * Assemble a dataset from the model
+	 *
+	 * @param model
+	 * @return Dataset
+	 */
+	public static Dataset assemble(final Model model) {
+		final Resource r = GraphUtils.findRootByType(model, DatasetAssembler.getType());
+		if (r == null) throw new ARQException("No root found for type <" + DatasetAssembler.getType() + ">");
+
+		return assemble(r);
+	}
+
+	/**
+	 * Assemble a dataset from a resource
+	 *
+	 * @param resource The resource for the dataset
+	 * @return Dataset
+	 */
+
+	public static Dataset assemble(final Resource resource) {
+		final Dataset ds = (Dataset) Assembler.general.open(resource);
+		return ds;
+	}
 }

http://git-wip-us.apache.org/repos/asf/jena/blob/61054a21/jena-arq/src/main/java/org/apache/jena/sparql/core/DatasetGraphFactory.java
----------------------------------------------------------------------
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/core/DatasetGraphFactory.java b/jena-arq/src/main/java/org/apache/jena/sparql/core/DatasetGraphFactory.java
index 81139f1..d456bd5 100644
--- a/jena-arq/src/main/java/org/apache/jena/sparql/core/DatasetGraphFactory.java
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/core/DatasetGraphFactory.java
@@ -22,29 +22,30 @@ import java.util.Iterator ;
 
 import org.apache.jena.graph.Graph ;
 import org.apache.jena.graph.Node ;
+import org.apache.jena.sparql.core.mem.DatasetGraphInMemory;
 import org.apache.jena.sparql.graph.GraphFactory ;
 
 public class DatasetGraphFactory
 {
     /** Create a DatasetGraph based on an existing one;
-     *  this is a structure copy of the dataset struture 
-     *  but graphs are shared 
-     */ 
-    public static DatasetGraph create(DatasetGraph dsg)
-    { 
+     *  this is a structure copy of the dataset struture
+     *  but graphs are shared
+     */
+    public static DatasetGraph create(final DatasetGraph dsg)
+    {
         // Fixed - requires explicit "add graph"
         return new DatasetGraphMap(dsg) ;
 //        DatasetGraph dsg2 = createMem() ;
 //        copyOver(dsg2, dsg2) ;
 //        return dsg2 ;
     }
-    
-    private static void copyOver(DatasetGraph dsgDest, DatasetGraph dsgSrc)
+
+    private static void copyOver(final DatasetGraph dsgDest, final DatasetGraph dsgSrc)
     {
         dsgDest.setDefaultGraph(dsgSrc.getDefaultGraph()) ;
-        for ( Iterator<Node> names = dsgSrc.listGraphNodes() ; names.hasNext() ; )
+        for ( final Iterator<Node> names = dsgSrc.listGraphNodes() ; names.hasNext() ; )
         {
-            Node gn = names.next() ;
+            final Node gn = names.next() ;
             dsgDest.addGraph(gn, dsgSrc.getGraph(gn)) ;
         }
     }
@@ -53,45 +54,38 @@ public class DatasetGraphFactory
      * Create a DatasetGraph starting with a single graph.
      * New graphs must be explicitly added.
      */
-    public static DatasetGraph create(Graph graph)
+    public static DatasetGraph create(final Graph graph)
     {
-        DatasetGraph dsg2 = createMemFixed() ;
+        final DatasetGraph dsg2 = createMemFixed() ;
         dsg2.setDefaultGraph(graph) ;
         return dsg2 ;
     }
-    
+
     /**
      * Create a DatasetGraph which only ever has a single default graph.
      */
-    public static DatasetGraph createOneGraph(Graph graph) { return new DatasetGraphOne(graph) ; }
+    public static DatasetGraph createOneGraph(final Graph graph) { return new DatasetGraphOne(graph) ; }
 
     /** Interface for makign graphs when a dataset needs to add a new graph.
      *  Return null for no graph created.
-     */ 
+     */
     public interface GraphMaker { public Graph create() ; }
 
     /** A graph maker that doesn't make graphs */
-    public static GraphMaker graphMakerNull = new GraphMaker() {
-        @Override
-        public Graph create()
-        {
-            return null ;
-        } } ;
-    
-    private static GraphMaker memGraphMaker = new GraphMaker()
-    {
-        @Override
-        public Graph create()
-        {
-            return GraphFactory.createDefaultGraph() ;
-        }
-    } ;
-    
+    public static GraphMaker graphMakerNull = () -> null ;
+
+    private static GraphMaker memGraphMaker = () -> GraphFactory.createDefaultGraph() ;
+
+    /**
+     * @return a DatasetGraph which features transactional in-memory operation
+     */
+    public static DatasetGraph createTxnMem() { return new DatasetGraphInMemory(); }
+
     /**
      * Create a DatasetGraph which has all graphs in memory.
      */
 
     public static DatasetGraph createMem() { return new DatasetGraphMaker(memGraphMaker) ; }
-    
+
     public static DatasetGraph createMemFixed() { return new DatasetGraphMap(GraphFactory.createDefaultGraph()) ; }
 }

http://git-wip-us.apache.org/repos/asf/jena/blob/61054a21/jena-arq/src/main/java/org/apache/jena/sparql/core/assembler/InMemDatasetAssembler.java
----------------------------------------------------------------------
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/core/assembler/InMemDatasetAssembler.java b/jena-arq/src/main/java/org/apache/jena/sparql/core/assembler/InMemDatasetAssembler.java
new file mode 100644
index 0000000..127f8f8
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/core/assembler/InMemDatasetAssembler.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.sparql.core.assembler;
+
+import static org.apache.jena.assembler.JA.MemoryDataset;
+import static org.apache.jena.assembler.JA.data;
+import static org.apache.jena.query.DatasetFactory.createTxnMem;
+import static org.apache.jena.query.ReadWrite.WRITE;
+import static org.apache.jena.riot.RDFDataMgr.read;
+import static org.apache.jena.sparql.core.assembler.AssemblerUtils.setContext;
+import static org.apache.jena.sparql.core.assembler.DatasetAssemblerVocab.pGraphName;
+import static org.apache.jena.sparql.core.assembler.DatasetAssemblerVocab.pNamedGraph;
+import static org.apache.jena.sparql.util.graph.GraphUtils.getAsStringValue;
+import static org.apache.jena.sparql.util.graph.GraphUtils.multiValueResource;
+
+import org.apache.jena.assembler.Assembler;
+import org.apache.jena.assembler.Mode;
+import org.apache.jena.assembler.assemblers.AssemblerBase;
+import org.apache.jena.query.Dataset;
+import org.apache.jena.rdf.model.Resource;
+
+/**
+ * An {@link Assembler} that creates in-memory {@link Dataset}s.
+ *
+ */
+public class InMemDatasetAssembler extends AssemblerBase {
+
+	@Override
+	public Dataset open(final Assembler assembler, final Resource root, final Mode mode) {
+		checkType(root, MemoryDataset);
+		final Dataset dataset = createTxnMem();
+		setContext(root, dataset.getContext());
+
+		dataset.begin(WRITE);
+
+		// load data into the default graph
+		if (root.hasProperty(data)) multiValueResource(root, data)
+				.forEach(defaultGraphDocument -> read(dataset, defaultGraphDocument.getURI()));
+
+		// load data into named graphs
+		multiValueResource(root, pNamedGraph).forEach(namedGraphResource -> {
+			final String graphName = getAsStringValue(namedGraphResource, pGraphName);
+			if (namedGraphResource.hasProperty(data)) multiValueResource(namedGraphResource, data)
+					.forEach(namedGraphData -> read(dataset.getNamedModel(graphName), namedGraphData.getURI()));
+		});
+
+		dataset.commit();
+		return dataset;
+	}
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/61054a21/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/DatasetGraphInMemory.java
----------------------------------------------------------------------
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/DatasetGraphInMemory.java b/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/DatasetGraphInMemory.java
new file mode 100644
index 0000000..47452ba
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/DatasetGraphInMemory.java
@@ -0,0 +1,311 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.sparql.core.mem;
+
+import static java.lang.ThreadLocal.withInitial;
+import static org.apache.jena.graph.Node.ANY;
+import static org.apache.jena.query.ReadWrite.READ;
+import static org.apache.jena.query.ReadWrite.WRITE;
+import static org.apache.jena.sparql.core.Quad.isUnionGraph;
+
+import java.util.Iterator;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import org.apache.jena.graph.Graph;
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Triple;
+import org.apache.jena.query.ReadWrite;
+import org.apache.jena.shared.Lock;
+import org.apache.jena.shared.LockMRPlusSW;
+import org.apache.jena.sparql.JenaTransactionException;
+import org.apache.jena.sparql.core.DatasetGraph;
+import org.apache.jena.sparql.core.DatasetGraphTriplesQuads;
+import org.apache.jena.sparql.core.DatasetPrefixStorage;
+import org.apache.jena.sparql.core.Quad;
+import org.apache.jena.sparql.core.Transactional;
+
+/**
+ * A {@link DatasetGraph} backed by an {@link QuadTable}. By default, this is a {@link HexTable} designed for high-speed
+ * in-memory operation.
+ *
+ */
+public class DatasetGraphInMemory extends DatasetGraphTriplesQuads implements Transactional {
+
+	private final DatasetPrefixStorage prefixes = new DatasetPrefixStorageInMemory();
+
+	private final Lock writeLock = new LockMRPlusSW();
+
+	private Lock writeLock() {
+		return writeLock;
+	}
+
+	private final ReentrantReadWriteLock commitLock = new ReentrantReadWriteLock(true);
+
+	/**
+	 * Commits must be atomic, and because a thread that is committing alters the various indexes one after another, we
+	 * lock out {@link #begin(ReadWrite)} while {@link #commit()} is executing.
+	 */
+	private ReentrantReadWriteLock commitLock() {
+		return commitLock;
+	}
+
+	private final ThreadLocal<Boolean> isInTransaction = withInitial(() -> false);
+
+	@Override
+	public boolean isInTransaction() {
+		return isInTransaction.get();
+	}
+
+	protected void isInTransaction(final boolean b) {
+		isInTransaction.set(b);
+	}
+
+	private final ThreadLocal<ReadWrite> transactionType = withInitial(() -> null);
+
+	/**
+	 * @return the type of transaction in progress
+	 */
+	public ReadWrite transactionType() {
+		return transactionType.get();
+	}
+
+	protected void transactionType(final ReadWrite readWrite) {
+		transactionType.set(readWrite);
+	}
+
+	private final QuadTable quadsIndex;
+
+	private QuadTable quadsIndex() {
+		return quadsIndex;
+	}
+
+	private final TripleTable defaultGraph;
+
+	private TripleTable defaultGraph() {
+		return defaultGraph;
+	}
+
+	@Override
+	public Lock getLock() {
+		return writeLock();
+	}
+
+	/**
+	 * Default constructor.
+	 */
+	public DatasetGraphInMemory() {
+		this(new HexTable(), new TriTable());
+	}
+
+	/**
+	 * @param i a table in which to store quads
+	 * @param t a table in which to store triples
+	 */
+	public DatasetGraphInMemory(final QuadTable i, final TripleTable t) {
+		this.quadsIndex = i;
+		this.defaultGraph = t;
+	}
+
+	@Override
+	public void begin(final ReadWrite readWrite) {
+		if (isInTransaction()) throw new JenaTransactionException("Transactions cannot be nested!");
+		transactionType(readWrite);
+		isInTransaction(true);
+		getLock().enterCriticalSection(readWrite.equals(READ)); // get the dataset write lock, if needed.
+		commitLock().readLock().lock(); // if a commit is proceeding, wait so that we see a coherent index state
+		try {
+			quadsIndex().begin(readWrite);
+		} finally {
+			commitLock().readLock().unlock();
+		}
+	}
+
+	@Override
+	public void commit() {
+		if (!isInTransaction()) throw new JenaTransactionException("Tried to commit outside a transaction!");
+		commitLock().writeLock().lock();
+		try {
+			quadsIndex().commit();
+			defaultGraph().commit();
+		} finally {
+			commitLock().writeLock().unlock();
+		}
+		end();
+	}
+
+	@Override
+	public void abort() {
+		if (!isInTransaction()) throw new JenaTransactionException("Tried to abort outside a transaction!");
+		end();
+	}
+
+	@Override
+	public void close() {
+		if (isInTransaction()) abort();
+
+	}
+
+	@Override
+	public void end() {
+		quadsIndex().end();
+		defaultGraph().end();
+		isInTransaction(false);
+		transactionType(null);
+		getLock().leaveCriticalSection();
+	}
+
+	private <T> Iterator<T> access(final Supplier<Iterator<T>> source) {
+		if (!isInTransaction()) {
+			begin(READ);
+			try {
+				return source.get();
+			} finally {
+				end();
+			}
+		}
+		return source.get();
+	}
+
+	@Override
+	public Iterator<Node> listGraphNodes() {
+		return access(() -> quadsIndex().listGraphNodes().iterator());
+	}
+
+	private Iterator<Quad> quadsFinder(final Node g, final Node s, final Node p, final Node o) {
+		if (isUnionGraph(g)) return findInUnionGraph(s, p, o);
+		return quadsIndex().find(g, s, p, o).iterator();
+	};
+
+	/**
+	 * Union graph is the merge of named graphs.
+	 */
+	public Iterator<Quad> findInUnionGraph(final Node s, final Node p, final Node o) {
+		return access(() -> quadsIndex().findInUnionGraph(s, p, o).iterator());
+	}
+
+	private Iterator<Quad> triplesFinder(final Node s, final Node p, final Node o) {
+		return triples2quadsDftGraph(defaultGraph().find(s, p, o).iterator());
+	};
+
+	@Override
+	public void setDefaultGraph(final Graph g) {
+		mutate(graph -> {
+			defaultGraph().clear();
+			graph.find(ANY, ANY, ANY)
+					.forEachRemaining(t -> addToDftGraph(t.getSubject(), t.getPredicate(), t.getObject()));
+		} , g);
+	}
+
+	@Override
+	public Graph getGraph(final Node graphNode) {
+		return new GraphInMemory(this, graphNode);
+	}
+
+	@Override
+	public Graph getDefaultGraph() {
+		return getGraph(Quad.defaultGraphNodeGenerated);
+	}
+
+	private Consumer<Graph> addGraph(final Node name) {
+		return g -> g.find(ANY, ANY, ANY).forEachRemaining(t -> add(new Quad(name, t)));
+	}
+
+	private final Consumer<Graph> removeGraph = g -> g.find(ANY, ANY, ANY).forEachRemaining(g::delete);
+
+	@Override
+	public void addGraph(final Node graphName, final Graph graph) {
+		mutate(addGraph(graphName), graph);
+	}
+
+	@Override
+	public void removeGraph(final Node graphName) {
+		mutate(removeGraph, getGraph(graphName));
+	}
+
+	/**
+	 * Wrap a mutation in a WRITE transaction iff necessary.
+	 *
+	 * @param mutator
+	 * @param payload
+	 */
+	private <T> void mutate(final Consumer<T> mutator, final T payload) {
+		if (!isInTransaction()) {
+			begin(WRITE);
+			try {
+				mutator.accept(payload);
+				commit();
+			} finally {
+				end();
+			}
+		} else if (transactionType().equals(WRITE)) mutator.accept(payload);
+		else throw new JenaTransactionException("Tried to write inside a READ transaction!");
+	}
+
+	/**
+	 * @return the prefixes in use in this dataset
+	 */
+	public DatasetPrefixStorage prefixes() {
+		return prefixes;
+	}
+
+	@Override
+	public void clear() {
+		mutate(x -> {
+			defaultGraph().clear();
+			quadsIndex().clear();
+		} , null);
+	}
+
+	@Override
+	protected void addToDftGraph(final Node s, final Node p, final Node o) {
+		mutate(defaultGraph()::add, Triple.create(s, p, o));
+	}
+
+	@Override
+	protected void addToNamedGraph(final Node g, final Node s, final Node p, final Node o) {
+		mutate(quadsIndex()::add, Quad.create(g, s, p, o));
+	}
+
+	@Override
+	protected void deleteFromDftGraph(final Node s, final Node p, final Node o) {
+		mutate(defaultGraph()::delete, Triple.create(s, p, o));
+	}
+
+	@Override
+	protected void deleteFromNamedGraph(final Node g, final Node s, final Node p, final Node o) {
+		mutate(quadsIndex()::delete, Quad.create(g, s, p, o));
+	}
+
+	@Override
+	protected Iterator<Quad> findInDftGraph(final Node s, final Node p, final Node o) {
+		return access(() -> triplesFinder(s, p, o));
+	}
+
+	@Override
+	protected Iterator<Quad> findInSpecificNamedGraph(final Node g, final Node s, final Node p, final Node o) {
+		return access(() -> quadsFinder(g, s, p, o));
+	}
+
+	@Override
+	protected Iterator<Quad> findInAnyNamedGraphs(final Node s, final Node p, final Node o) {
+		return findInSpecificNamedGraph(ANY, s, p, o);
+	}
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/61054a21/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/DatasetPrefixStorageInMemory.java
----------------------------------------------------------------------
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/DatasetPrefixStorageInMemory.java b/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/DatasetPrefixStorageInMemory.java
new file mode 100644
index 0000000..da9ce8e
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/DatasetPrefixStorageInMemory.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.sparql.core.mem;
+
+import static org.apache.jena.sparql.core.Quad.defaultGraphIRI;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.jena.shared.PrefixMapping;
+import org.apache.jena.shared.impl.PrefixMappingImpl;
+import org.apache.jena.sparql.core.DatasetPrefixStorage;
+
+/**
+ * A simple {@link DatasetPrefixStorage} for in-memory datasets.
+ */
+public class DatasetPrefixStorageInMemory implements DatasetPrefixStorage {
+
+	private Map<String, PrefixMapping> prefixMappings = new ConcurrentHashMap<>();
+
+	/**
+	 * A mapping from graph name to {@link PrefixMapping} for that graph.
+	 */
+	private Map<String, PrefixMapping> prefixMappings() {
+		return prefixMappings;
+	}
+
+	@Override
+	public void close() {
+		prefixMappings = null;
+	}
+
+	@Override
+	public void sync() {
+		// NO OP
+	}
+
+	@Override
+	public Set<String> graphNames() {
+		return prefixMappings().keySet();
+	}
+
+	@Override
+	public String readPrefix(final String graphName, final String prefix) {
+		return getPrefixMapping(graphName).getNsPrefixURI(prefix);
+	}
+
+	@Override
+	public String readByURI(final String graphName, final String uriStr) {
+		return getPrefixMapping(graphName).getNsURIPrefix(uriStr);
+	}
+
+	@Override
+	public Map<String, String> readPrefixMap(final String graphName) {
+		return getPrefixMapping(graphName).getNsPrefixMap();
+	}
+
+	@Override
+	public void insertPrefix(final String graphName, final String prefix, final String uri) {
+		getPrefixMapping(graphName).setNsPrefix(prefix, uri);
+	}
+
+	@Override
+	public void loadPrefixMapping(final String graphName, final PrefixMapping pmap) {
+		getPrefixMapping(graphName).setNsPrefixes(pmap);
+	}
+
+	@Override
+	public void removeFromPrefixMap(final String graphName, final String prefix) {
+		getPrefixMapping(graphName).removeNsPrefix(prefix);
+	}
+
+	@Override
+	public PrefixMapping getPrefixMapping() {
+		return getPrefixMapping(defaultGraphIRI.getURI());
+	}
+
+	@Override
+	public PrefixMapping getPrefixMapping(final String graphName) {
+		return prefixMappings().computeIfAbsent(graphName, x -> new PrefixMappingImpl());
+	}
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/61054a21/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/FourTupleMap.java
----------------------------------------------------------------------
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/FourTupleMap.java b/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/FourTupleMap.java
new file mode 100644
index 0000000..c96956b
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/FourTupleMap.java
@@ -0,0 +1,91 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.sparql.core.mem;
+
+import org.apache.jena.atlas.lib.persistent.PMap;
+import org.apache.jena.atlas.lib.persistent.PersistentSet;
+import org.apache.jena.graph.Node;
+import org.apache.jena.sparql.core.mem.FourTupleMap.ThreeTupleMap;
+
+import com.github.andrewoma.dexx.collection.Map;
+
+/**
+ * A {@link PMap} of {@link Node}s: {@code Node->Node->Node->PersistentSet<Node>}
+ */
+public class FourTupleMap extends PMap<Node, ThreeTupleMap, FourTupleMap> {
+
+	private FourTupleMap(final com.github.andrewoma.dexx.collection.Map<Node, ThreeTupleMap> wrappedMap) {
+		super(wrappedMap);
+	}
+
+	/**
+	 * Default constructor.
+	 */
+	public FourTupleMap() {
+		super();
+	}
+
+	@Override
+	protected FourTupleMap wrap(final Map<Node, ThreeTupleMap> wrappedMap) {
+		return new FourTupleMap(wrappedMap);
+	}
+
+	/**
+	 * A {@link PMap} of {@link Node}s: {@code Node->Node->PersistentSet<Node>}
+	 */
+	public static class ThreeTupleMap extends PMap<Node, TwoTupleMap, ThreeTupleMap> {
+		private ThreeTupleMap(final com.github.andrewoma.dexx.collection.Map<Node, TwoTupleMap> wrappedMap) {
+			super(wrappedMap);
+		}
+
+		/**
+		 * Default constructor.
+		 */
+		public ThreeTupleMap() {
+			super();
+		}
+
+		@Override
+		protected ThreeTupleMap wrap(final Map<Node, TwoTupleMap> wrappedMap) {
+			return new ThreeTupleMap(wrappedMap);
+		}
+	}
+
+	/**
+	 * A {@link PMap} of {@link Node}s: {@code Node->PersistentSet<Node>}
+	 */
+	public static class TwoTupleMap extends PMap<Node, PersistentSet<Node>, TwoTupleMap> {
+
+		private TwoTupleMap(final com.github.andrewoma.dexx.collection.Map<Node, PersistentSet<Node>> wrappedMap) {
+			super(wrappedMap);
+		}
+
+		/**
+		 * Default constructor.
+		 */
+		public TwoTupleMap() {
+			super();
+		}
+
+		@Override
+		protected TwoTupleMap wrap(final Map<Node, PersistentSet<Node>> wrappedMap) {
+			return new TwoTupleMap(wrappedMap);
+		}
+	}
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/61054a21/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/GraphInMemory.java
----------------------------------------------------------------------
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/GraphInMemory.java b/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/GraphInMemory.java
new file mode 100644
index 0000000..78cad2d
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/GraphInMemory.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.sparql.core.mem;
+
+import org.apache.jena.graph.Node;
+import org.apache.jena.shared.PrefixMapping;
+import org.apache.jena.sparql.core.DatasetPrefixStorage;
+import org.apache.jena.sparql.core.GraphView;
+
+/**
+ * A {@link GraphView} specialization that relies on the {@link DatasetGraphInMemory} from which this graph is drawn to
+ * manage prefixes.
+ *
+ */
+public class GraphInMemory extends GraphView {
+
+	private final DatasetGraphInMemory datasetGraph;
+
+	GraphInMemory(final DatasetGraphInMemory dsg, final Node gn) {
+		super(dsg, gn);
+		this.datasetGraph = dsg;
+	}
+
+	@Override
+	protected PrefixMapping createPrefixMapping() {
+		final DatasetPrefixStorage prefixes = datasetGraph().prefixes();
+		return isDefaultGraph() || isUnionGraph() ? prefixes.getPrefixMapping() : prefixes
+				.getPrefixMapping(getGraphName().getURI());
+	}
+
+	private DatasetGraphInMemory datasetGraph() {
+		return datasetGraph;
+	}
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/61054a21/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/HexTable.java
----------------------------------------------------------------------
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/HexTable.java b/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/HexTable.java
new file mode 100644
index 0000000..a968a54
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/HexTable.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.sparql.core.mem;
+
+import static java.lang.ThreadLocal.withInitial;
+import static java.util.EnumSet.noneOf;
+import static java.util.Objects.nonNull;
+import static java.util.stream.Collectors.toMap;
+import static org.apache.jena.sparql.core.mem.QuadTableForm.*;
+import static org.apache.jena.sparql.core.mem.TupleSlot.*;
+
+import java.util.EnumMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import org.apache.jena.graph.Node;
+import org.apache.jena.query.ReadWrite;
+import org.apache.jena.sparql.core.Quad;
+
+/**
+ * A six-way {@link QuadTable} using all of the available forms in {@link QuadTableForm}. This class binds together all
+ * of the enumerated values in {@code enum QuadTableForm}, each of which implements {@link QuadTable}, into one
+ * implementation of {@code QuadTable} that selects the most useful index form(s) for any given operation.
+ *
+ */
+public class HexTable implements QuadTable {
+
+	private final ThreadLocal<Boolean> isInTransaction = withInitial(() -> false);
+
+	@Override
+	public boolean isInTransaction() {
+		return isInTransaction.get();
+	}
+
+	protected void isInTransaction(final boolean b) {
+		isInTransaction.set(b);
+	}
+
+	private final Map<QuadTableForm, QuadTable> indexBlock = new EnumMap<QuadTableForm, QuadTable>(
+			tableForms().collect(toMap(x -> x, QuadTableForm::get)));
+
+	/**
+	 * A block of six indexes to which we provide access as though they were one.
+	 */
+	protected Map<QuadTableForm, QuadTable> indexBlock() {
+		return indexBlock;
+	}
+
+	@Override
+	public Stream<Quad> find(final Node g, final Node s, final Node p, final Node o) {
+		final Set<TupleSlot> pattern = noneOf(TupleSlot.class);
+		if (isConcrete(g)) pattern.add(GRAPH);
+		if (isConcrete(s)) pattern.add(SUBJECT);
+		if (isConcrete(p)) pattern.add(PREDICATE);
+		if (isConcrete(o)) pattern.add(OBJECT);
+		final QuadTableForm choice = chooseFrom(pattern);
+		return indexBlock().get(choice).find(g, s, p, o);
+	}
+
+	private static boolean isConcrete(final Node n) {
+		return nonNull(n) && n.isConcrete();
+	}
+
+	@Override
+	public void add(final Quad q) {
+		indexBlock().values().forEach(index -> index.add(q));
+	}
+
+	@Override
+	public void delete(final Quad q) {
+		indexBlock().values().forEach(index -> index.delete(q));
+	}
+
+	@Override
+	public Stream<Node> listGraphNodes() {
+		// GSPO is specially equipped with an efficient listGraphNodes().
+		return indexBlock().get(GSPO).listGraphNodes();
+	}
+
+	@Override
+	public Stream<Quad> findInUnionGraph(final Node s, final Node p, final Node o) {
+		// we can use adjacency in SPOG to solve this problem without building up a set of already-seen triples.
+		return indexBlock().get(SPOG).findInUnionGraph(s, p, o);
+	}
+
+	@Override
+	public void begin(final ReadWrite rw) {
+		isInTransaction(true);
+		indexBlock().values().forEach(table -> table.begin(rw));
+	}
+
+	@Override
+	public void end() {
+		indexBlock().values().forEach(QuadTable::end);
+		isInTransaction(false);
+	}
+
+	@Override
+	public void commit() {
+		indexBlock().values().forEach(QuadTable::commit);
+		isInTransaction(false);
+	}
+
+	@Override
+	public void clear() {
+		indexBlock().values().forEach(QuadTable::clear);
+	}
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/61054a21/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/PMapQuadTable.java
----------------------------------------------------------------------
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/PMapQuadTable.java b/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/PMapQuadTable.java
new file mode 100644
index 0000000..28685ee
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/PMapQuadTable.java
@@ -0,0 +1,137 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.sparql.core.mem;
+
+import static java.util.stream.Stream.empty;
+import static java.util.stream.Stream.of;
+import static org.apache.jena.sparql.core.Quad.create;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.stream.Stream;
+
+import org.apache.jena.atlas.lib.persistent.PMap;
+import org.apache.jena.atlas.lib.persistent.PersistentSet;
+import org.apache.jena.graph.Node;
+import org.apache.jena.sparql.core.Quad;
+import org.apache.jena.sparql.core.mem.FourTupleMap.ThreeTupleMap;
+import org.apache.jena.sparql.core.mem.FourTupleMap.TwoTupleMap;
+import org.slf4j.Logger;
+
+/**
+ * An implementation of {@link QuadTable} based on the use of nested {@link PMap}s. Intended for high-speed in-memory
+ * use.
+ *
+ */
+public abstract class PMapQuadTable extends PMapTupleTable<FourTupleMap, Quad>implements QuadTable {
+
+	/**
+	 * @param tableName a name for this table
+	 */
+	public PMapQuadTable(final String tableName) {
+		super(tableName);
+	}
+
+	private static final Logger log = getLogger(PMapQuadTable.class);
+
+	@Override
+	protected Logger log() {
+		return log;
+	}
+
+	@Override
+	protected FourTupleMap initial() {
+		return new FourTupleMap();
+	}
+
+	/**
+	 * We descend through the nested {@link PMap}s building up {@link Stream}s of partial tuples from which we develop a
+	 * {@link Stream} of full tuples which is our result. Use {@link Node#ANY} or <code>null</code> for a wildcard.
+	 *
+	 * @param first the value in the first slot of the tuple
+	 * @param second the value in the second slot of the tuple
+	 * @param third the value in the third slot of the tuple
+	 * @param fourth the value in the fourth slot of the tuple
+	 * @return a <code>Stream</code> of tuples matching the pattern
+	 */
+	protected Stream<Quad> _find(final Node first, final Node second, final Node third, final Node fourth) {
+		debug("Querying on four-tuple pattern: {} {} {} {} .", first, second, third, fourth);
+		final FourTupleMap fourTuples = local().get();
+		if (isConcrete(first)) {
+			debug("Using a specific first slot value.");
+			return (Stream<Quad>) fourTuples.get(first).map(threeTuples -> {
+				if (isConcrete(second)) {
+					debug("Using a specific second slot value.");
+					return threeTuples.get(second).map(twoTuples -> {
+						if (isConcrete(third)) {
+							debug("Using a specific third slot value.");
+							return twoTuples.get(third).map(oneTuples -> {
+								if (isConcrete(fourth)) {
+									debug("Using a specific fourth slot value.");
+									return oneTuples
+											.contains(fourth) ? of(create(first, second, third, fourth)) : empty();
+								}
+								debug("Using a wildcard fourth slot value.");
+								return oneTuples.stream().map(slot4 -> create(first, second, third, slot4));
+							}).orElse(empty());
+
+						}
+						debug("Using wildcard third and fourth slot values.");
+						return twoTuples.flatten((slot3, oneTuples) -> oneTuples.stream()
+								.map(slot4 -> create(first, second, slot3, slot4)));
+					}).orElse(empty());
+				}
+				debug("Using wildcard second, third and fourth slot values.");
+				return threeTuples.flatten((slot2, twoTuples) -> twoTuples.flatten(
+						(slot3, oneTuples) -> oneTuples.stream().map(slot4 -> create(first, slot2, slot3, slot4))));
+			}).orElse(empty());
+		}
+		debug("Using a wildcard for all slot values.");
+		return fourTuples.flatten((slot1, threeTuples) -> threeTuples.flatten((slot2, twoTuples) -> twoTuples
+				.flatten((slot3, oneTuples) -> oneTuples.stream().map(slot4 -> create(slot1, slot2, slot3, slot4)))));
+	}
+
+	protected void _add(final Node first, final Node second, final Node third, final Node fourth) {
+		debug("Adding four-tuple: {} {} {} {} .", first, second, third, fourth);
+		final FourTupleMap fourTuples = local().get();
+		ThreeTupleMap threeTuples = fourTuples.get(first).orElse(new ThreeTupleMap());
+		TwoTupleMap twoTuples = threeTuples.get(second).orElse(new TwoTupleMap());
+		PersistentSet<Node> oneTuples = twoTuples.get(third).orElse(PersistentSet.empty());
+
+		if (!oneTuples.contains(fourth)) oneTuples = oneTuples.plus(fourth);
+		twoTuples = twoTuples.minus(third).plus(third, oneTuples);
+		threeTuples = threeTuples.minus(second).plus(second, twoTuples);
+		debug("Setting transactional index to new value.");
+		local().set(fourTuples.minus(first).plus(first, threeTuples));
+	}
+
+	protected void _delete(final Node first, final Node second, final Node third, final Node fourth) {
+		debug("Removing four-tuple: {} {} {} {} .", first, second, third, fourth);
+		final FourTupleMap fourTuples = local().get();
+		fourTuples.get(first).ifPresent(threeTuples -> threeTuples.get(second)
+				.ifPresent(twoTuples -> twoTuples.get(third).ifPresent(oneTuples -> {
+					if (oneTuples.contains(fourth)) {
+						oneTuples = oneTuples.minus(fourth);
+						final TwoTupleMap newTwoTuples = twoTuples.minus(third).plus(third, oneTuples);
+						final ThreeTupleMap newThreeTuples = threeTuples.minus(second).plus(second, newTwoTuples);
+						debug("Setting transactional index to new value.");
+						local().set(fourTuples.minus(first).plus(first, newThreeTuples));
+					}
+				})));
+	}
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/61054a21/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/PMapTripleTable.java
----------------------------------------------------------------------
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/PMapTripleTable.java b/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/PMapTripleTable.java
new file mode 100644
index 0000000..916f7de
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/PMapTripleTable.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.sparql.core.mem;
+
+import static java.util.stream.Stream.empty;
+import static org.apache.jena.graph.Triple.create;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.stream.Stream;
+
+import org.apache.jena.atlas.lib.persistent.PMap;
+import org.apache.jena.atlas.lib.persistent.PersistentSet;
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Triple;
+import org.apache.jena.sparql.core.mem.FourTupleMap.ThreeTupleMap;
+import org.apache.jena.sparql.core.mem.FourTupleMap.TwoTupleMap;
+import org.slf4j.Logger;
+
+/**
+ * A {@link TripleTable} employing persistent maps to index triples in one particular slot order (e.g. SPO, OSP or POS).
+ *
+ */
+public abstract class PMapTripleTable extends PMapTupleTable<ThreeTupleMap, Triple>implements TripleTable {
+
+	private final static Logger log = getLogger(PMapTripleTable.class);
+
+	@Override
+	protected Logger log() {
+		return log;
+	}
+
+	@Override
+	protected ThreeTupleMap initial() {
+		return new ThreeTupleMap();
+	}
+
+	/**
+	 * @param tableName a name for this table
+	 */
+	public PMapTripleTable(final String tableName) {
+		super(tableName);
+	}
+
+	/**
+	 * We descend through the nested {@link PMap}s building up {@link Stream}s of partial tuples from which we develop a
+	 * {@link Stream} of full tuples which is our result. Use {@link Node#ANY} or <code>null</code> for a wildcard.
+	 *
+	 * @param first the value in the first slot of the tuple
+	 * @param second the value in the second slot of the tuple
+	 * @param third the value in the third slot of the tuple
+	 * @return a <code>Stream</code> of tuples matching the pattern
+	 */
+	public Stream<Triple> _find(final Node first, final Node second, final Node third) {
+		debug("Querying on three-tuple pattern: {} {} {} .", first, second, third);
+		final ThreeTupleMap threeTuples = local().get();
+		if (isConcrete(first)) {
+			debug("Using a specific first slot value.");
+			return (Stream<Triple>) threeTuples.get(first).map(twoTuples -> {
+				if (isConcrete(second)) {
+					debug("Using a specific second slot value.");
+					return twoTuples.get(second).map(oneTuples -> {
+						if (isConcrete(third)) {
+							debug("Using a specific third slot value.");
+							return oneTuples.contains(third) ? Stream.of(create(first, second, third)) : empty();
+						}
+						debug("Using a wildcard third slot value.");
+						return oneTuples.stream().map(slot3 -> create(first, second, slot3));
+					}).orElse(empty());
+				}
+				debug("Using wildcard second and third slot values.");
+				return twoTuples
+						.flatten((slot2, oneTuples) -> oneTuples.stream().map(slot3 -> create(first, slot2, slot3)));
+			}).orElse(empty());
+		}
+		debug("Using a wildcard for all slot values.");
+		return threeTuples.flatten((slot1, twoTuples) -> twoTuples
+				.flatten((slot2, oneTuples) -> oneTuples.stream().map(slot3 -> create(slot1, slot2, slot3))));
+	}
+
+	protected void _add(final Node first, final Node second, final Node third) {
+		debug("Adding three-tuple {} {} {}", first, second, third);
+		final ThreeTupleMap threeTuples = local().get();
+		TwoTupleMap twoTuples = threeTuples.get(first).orElse(new TwoTupleMap());
+		PersistentSet<Node> oneTuples = twoTuples.get(second).orElse(PersistentSet.empty());
+
+		oneTuples = oneTuples.plus(third);
+		twoTuples = twoTuples.minus(second).plus(second, oneTuples);
+		local().set(threeTuples.minus(first).plus(first, twoTuples));
+	}
+
+	protected void _delete(final Node first, final Node second, final Node third) {
+		debug("Deleting three-tuple {} {} {}", first, second, third);
+		final ThreeTupleMap threeTuples = local().get();
+		threeTuples.get(first).ifPresent(twoTuples -> twoTuples.get(second).ifPresent(oneTuples -> {
+			if (oneTuples.contains(third)) {
+				final TwoTupleMap newTwoTuples = twoTuples.minus(second).plus(second, oneTuples.minus(third));
+				debug("Setting transactional index to new value.");
+				local().set(threeTuples.minus(first).plus(first, newTwoTuples));
+			}
+		}));
+	}
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/61054a21/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/PMapTupleTable.java
----------------------------------------------------------------------
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/PMapTupleTable.java b/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/PMapTupleTable.java
new file mode 100644
index 0000000..db00dbf
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/PMapTupleTable.java
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.sparql.core.mem;
+
+import static java.lang.ThreadLocal.withInitial;
+
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.apache.jena.graph.Node;
+import org.apache.jena.query.ReadWrite;
+import org.slf4j.Logger;
+
+/**
+ * A partial implementation of {@link TupleTable} that contains some common state management.
+ *
+ * @param <TupleMapType> the type of the internal structure holding table data
+ * @param <TupleType> the type of tuple in which a subclass of this class transacts
+ */
+public abstract class PMapTupleTable<TupleMapType, TupleType> implements TupleTable<TupleType> {
+
+	/**
+	 * This method should always return the same value, but note that the same value may not necessarily be the same
+	 * instance.
+	 *
+	 * @return a value to which to initialize the master table data.
+	 */
+	protected abstract TupleMapType initial();
+
+	private final AtomicReference<TupleMapType> master = new AtomicReference<>(initial());
+
+	/**
+	 * We use an {@link AtomicReference} to the internal structure that holds our table data to be able to swap
+	 * transactional versions of the data with the shared version atomically.
+	 */
+	protected AtomicReference<TupleMapType> master() {
+		return master;
+	}
+
+	private final ThreadLocal<TupleMapType> local = withInitial(() -> master().get());
+
+	/**
+	 * @return a thread-local transactional reference to the internal table structure
+	 */
+	protected ThreadLocal<TupleMapType> local() {
+		return local;
+	}
+
+	private final ThreadLocal<Boolean> isInTransaction = withInitial(() -> false);
+
+	@Override
+	public boolean isInTransaction() {
+		return isInTransaction.get();
+	};
+
+	protected void isInTransaction(final boolean b) {
+		isInTransaction.set(b);
+	}
+
+	private final String tableName;
+
+	/**
+	 * @param n a name for this table
+	 */
+	public PMapTupleTable(final String n) {
+		this.tableName = n;
+	}
+
+	protected abstract Logger log();
+
+	/**
+	 * Logs to DEBUG prepending the table name in order to distinguish amongst different indexes
+	 */
+	protected void debug(final String msg, final Object... values) {
+		log().debug(tableName + ": " + msg, values);
+	}
+
+	@Override
+	public void begin(final ReadWrite rw) {
+		isInTransaction(true);
+	}
+
+	@Override
+	public void end() {
+		debug("Abandoning transactional reference.");
+		local.remove();
+		isInTransaction(false);
+	}
+
+	@Override
+	public void commit() {
+		debug("Swapping transactional reference in for shared reference");
+		master().set(local.get());
+		end();
+	}
+
+	@Override
+	public void clear() {
+		local().set(initial());
+	}
+
+	protected boolean isConcrete(final Node n) {
+		return n != null && n.isConcrete();
+	}
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/61054a21/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/QuadTable.java
----------------------------------------------------------------------
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/QuadTable.java b/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/QuadTable.java
new file mode 100644
index 0000000..7505d1a
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/QuadTable.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.sparql.core.mem;
+
+import static org.apache.jena.graph.Node.ANY;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Triple;
+import org.apache.jena.sparql.core.Quad;
+
+/**
+ * A simplex or multiplex table of {@link Quad}s. Implementations may wish to override {@link #listGraphNodes()} with a
+ * more efficient implementation.
+ *
+ */
+public interface QuadTable extends TupleTable<Quad> {
+
+	/**
+	 * Search the table using a pattern of slots. {@link Node#ANY} or <code>null</code> will work as a wildcard.
+	 *
+	 * @param g the graph node of the pattern
+	 * @param s the subject node of the pattern
+	 * @param p the predicate node of the pattern
+	 * @param o the object node of the pattern
+	 * @return an {@link Stream} of matched quads
+	 */
+	Stream<Quad> find(Node g, Node s, Node p, Node o);
+
+	/**
+	 * Discover the graphs named in the table
+	 *
+	 * @return an {@link Stream} of graph names used in this table
+	 */
+	default Stream<Node> listGraphNodes() {
+		return find(ANY, ANY, ANY, ANY).map(Quad::getGraph).distinct();
+	}
+
+	@Override
+	default void clear() {
+		find(ANY, ANY, ANY, ANY).forEach(this::delete);
+	}
+
+	default Stream<Quad> findInUnionGraph(final Node s, final Node p, final Node o) {
+		final Set<Triple> seen = new HashSet<>();
+		return find(ANY, s, p, o).filter(q -> !q.isDefaultGraph() && seen.add(q.asTriple()));
+	}
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/61054a21/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/QuadTableForm.java
----------------------------------------------------------------------
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/QuadTableForm.java b/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/QuadTableForm.java
new file mode 100644
index 0000000..22efe73
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/QuadTableForm.java
@@ -0,0 +1,267 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.sparql.core.mem;
+
+import static java.util.Arrays.asList;
+import static java.util.Arrays.stream;
+import static java.util.EnumSet.copyOf;
+import static org.apache.jena.graph.Node.ANY;
+import static org.apache.jena.sparql.core.mem.TupleSlot.*;
+
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Triple;
+import org.apache.jena.sparql.core.Quad;
+
+/**
+ * Six covering table forms and machinery to determine which of them is best suited to answer a given query. Please
+ * notice that the individual values of this {@code enum} are what implement the various interfaces named in the
+ * signature of this type. In particular, any value from this {@code enum} is a complete implementation of
+ * {@link QuadTable}. {@link HexTable} binds up all these six forms into a single implementation of {@code QuadTable}
+ * that selects the most useful table form(s) for any given operation.
+ *
+ * @see HexTable
+ */
+public enum QuadTableForm implements Supplier<QuadTable>,Predicate<Set<TupleSlot>> {
+
+	/**
+	 * Graph-subject-predicate-object.
+	 */
+	GSPO(asList(GRAPH, SUBJECT, PREDICATE, OBJECT)) {
+		@Override
+		public PMapQuadTable get() {
+			return new PMapQuadTable(name()) {
+				@Override
+				public Stream<Quad> find(final Node g, final Node s, final Node p, final Node o) {
+					return _find(g, s, p, o);
+				}
+
+				@Override
+				public void add(final Quad q) {
+					_add(q.getGraph(), q.getSubject(), q.getPredicate(), q.getObject());
+				}
+
+				@Override
+				public void delete(final Quad q) {
+					_delete(q.getGraph(), q.getSubject(), q.getPredicate(), q.getObject());
+				}
+
+				@Override
+				public Stream<Node> listGraphNodes() {
+					return local().get().entryStream().map(Entry::getKey);
+				}
+			};
+		}
+	},
+
+	/**
+	 * Graph-object-predicate-subject.
+	 */
+	GOPS(asList(GRAPH, OBJECT, PREDICATE, SUBJECT)) {
+		@Override
+		public PMapQuadTable get() {
+			return new PMapQuadTable(name()) {
+
+				@Override
+				public Stream<Quad> find(final Node g, final Node s, final Node p, final Node o) {
+					return _find(g, o, p, s);
+				}
+
+				@Override
+				public void add(final Quad q) {
+					_add(q.getGraph(), q.getObject(), q.getPredicate(), q.getSubject());
+				}
+
+				@Override
+				public void delete(final Quad q) {
+					_delete(q.getGraph(), q.getObject(), q.getPredicate(), q.getSubject());
+				}
+			};
+
+		}
+	},
+
+	/**
+	 * Subject-predicate-object-graph.
+	 */
+	SPOG(asList(SUBJECT, PREDICATE, OBJECT, GRAPH)) {
+		@Override
+		public PMapQuadTable get() {
+			return new PMapQuadTable(name()) {
+
+				@Override
+				public Stream<Quad> find(final Node g, final Node s, final Node p, final Node o) {
+					return _find(s, p, o, g);
+				}
+
+				@Override
+				public Stream<Quad> findInUnionGraph(final Node s, final Node p, final Node o) {
+					final AtomicReference<Triple> mostRecentlySeen = new AtomicReference<>();
+					return find(ANY, s, p, o).filter(currentQuad -> {
+						final Triple currentTriple = currentQuad.asTriple();
+						return !mostRecentlySeen.getAndSet(currentTriple).equals(currentTriple);
+					});
+				}
+
+				@Override
+				public void add(final Quad q) {
+					_add(q.getSubject(), q.getPredicate(), q.getObject(), q.getGraph());
+				}
+
+				@Override
+				public void delete(final Quad q) {
+					_delete(q.getSubject(), q.getPredicate(), q.getObject(), q.getGraph());
+				}
+			};
+		}
+	},
+
+	/**
+	 * Object-subject-graph-predicate.
+	 */
+	OSGP(asList(OBJECT, SUBJECT, GRAPH, PREDICATE)) {
+		@Override
+		public PMapQuadTable get() {
+			return new PMapQuadTable(name()) {
+
+				@Override
+				public Stream<Quad> find(final Node g, final Node s, final Node p, final Node o) {
+					return _find(o, s, g, p);
+				}
+
+				@Override
+				public void add(final Quad q) {
+					_add(q.getObject(), q.getSubject(), q.getGraph(), q.getPredicate());
+				}
+
+				@Override
+				public void delete(final Quad q) {
+					_delete(q.getObject(), q.getSubject(), q.getGraph(), q.getPredicate());
+				}
+			};
+		}
+	},
+
+	/**
+	 * Predicate-graph-subject-object.
+	 */
+	PGSO(asList(PREDICATE, GRAPH, SUBJECT, OBJECT)) {
+		@Override
+		public PMapQuadTable get() {
+			return new PMapQuadTable(name()) {
+
+				@Override
+				public Stream<Quad> find(final Node g, final Node s, final Node p, final Node o) {
+					return _find(p, g, s, o);
+				}
+
+				@Override
+				public void add(final Quad q) {
+					_add(q.getPredicate(), q.getGraph(), q.getSubject(), q.getObject());
+				}
+
+				@Override
+				public void delete(final Quad q) {
+					_delete(q.getPredicate(), q.getGraph(), q.getSubject(), q.getObject());
+				}
+			};
+		}
+	},
+
+	/**
+	 * Object-predicate-subject-graph.
+	 */
+	OPSG(asList(OBJECT, PREDICATE, SUBJECT, GRAPH)) {
+		@Override
+		public PMapQuadTable get() {
+			return new PMapQuadTable(name()) {
+
+				@Override
+				public Stream<Quad> find(final Node g, final Node s, final Node p, final Node o) {
+					return _find(o, p, s, g);
+				}
+
+				@Override
+				public Stream<Quad> findInUnionGraph(final Node s, final Node p, final Node o) {
+					final AtomicReference<Triple> mostRecentlySeen = new AtomicReference<>();
+					return find(ANY, s, p, o).filter(currentQuad -> {
+						final Triple currentTriple = currentQuad.asTriple();
+						return !mostRecentlySeen.getAndSet(currentTriple).equals(currentTriple);
+					});
+				}
+
+				@Override
+				public void add(final Quad q) {
+					_add(q.getObject(), q.getPredicate(), q.getSubject(), q.getGraph());
+				}
+
+				@Override
+				public void delete(final Quad q) {
+					_delete(q.getObject(), q.getPredicate(), q.getSubject(), q.getGraph());
+				}
+			};
+		}
+	};
+
+	private QuadTableForm(final List<TupleSlot> fp) {
+		this.fullpattern = fp;
+	}
+
+	/**
+	 * The full pattern of this table form.
+	 */
+	public final List<TupleSlot> fullpattern;
+
+	/**
+	 * @param pattern
+	 * @return whether this table form avoids traversal for a query of this pattern
+	 */
+	@Override
+	public boolean test(final Set<TupleSlot> pattern) {
+		for (byte i = 4; i > 0; i--) {
+			// copy into a set because order does not matter for this comparison: the ordering of tuples is
+			// handled by individual table forms
+			final Set<TupleSlot> prefix = copyOf(fullpattern.subList(0, i));
+			if (prefix.equals(pattern)) return true;
+		}
+		return false;
+	}
+
+	/**
+	 * @param pattern
+	 * @return the most appropriate choice of table form for that query
+	 */
+	public static QuadTableForm chooseFrom(final Set<TupleSlot> pattern) {
+		return tableForms().filter(f -> f.test(pattern)).findFirst().orElse(GSPO);
+	}
+
+	/**
+	 * @return a stream of these table forms
+	 */
+	public static Stream<QuadTableForm> tableForms() {
+		return stream(values());
+	}
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/61054a21/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/TriTable.java
----------------------------------------------------------------------
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/TriTable.java b/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/TriTable.java
new file mode 100644
index 0000000..f283d9d
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/TriTable.java
@@ -0,0 +1,117 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.sparql.core.mem;
+
+import static java.lang.ThreadLocal.withInitial;
+import static java.util.EnumSet.noneOf;
+import static java.util.Objects.nonNull;
+import static java.util.stream.Collectors.toMap;
+import static org.apache.jena.sparql.core.mem.TripleTableForm.chooseFrom;
+import static org.apache.jena.sparql.core.mem.TripleTableForm.tableForms;
+import static org.apache.jena.sparql.core.mem.TupleSlot.*;
+
+import java.util.EnumMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Triple;
+import org.apache.jena.query.ReadWrite;
+
+/**
+ * A three-way {@link TripleTable} using all of the available forms in {@link TripleTableForm}.
+ *
+ */
+public class TriTable implements TripleTable {
+
+	private final Map<TripleTableForm, TripleTable> indexBlock = new EnumMap<TripleTableForm, TripleTable>(
+			tableForms().collect(toMap(x -> x, TripleTableForm::get)));
+
+	/**
+	 * A block of three indexes to which we provide access as though they were one.
+	 */
+	protected Map<TripleTableForm, TripleTable> indexBlock() {
+		return indexBlock;
+	}
+
+	private final ThreadLocal<Boolean> isInTransaction = withInitial(() -> false);
+
+	@Override
+	public boolean isInTransaction() {
+		return isInTransaction.get();
+	}
+
+	protected void isInTransaction(final boolean b) {
+		isInTransaction.set(b);
+	}
+
+	@Override
+	public void commit() {
+		indexBlock().values().forEach(TripleTable::commit);
+		end();
+	}
+
+	@Override
+	public void abort() {
+		indexBlock().values().forEach(TripleTable::abort);
+		end();
+	}
+
+	@Override
+	public void end() {
+		indexBlock().values().forEach(TripleTable::end);
+		isInTransaction(false);
+	}
+
+	@Override
+	public Stream<Triple> find(final Node s, final Node p, final Node o) {
+		final Set<TupleSlot> pattern = noneOf(TupleSlot.class);
+		if (isConcrete(s)) pattern.add(SUBJECT);
+		if (isConcrete(p)) pattern.add(PREDICATE);
+		if (isConcrete(o)) pattern.add(OBJECT);
+		final TripleTableForm choice = chooseFrom(pattern);
+		return indexBlock().get(choice).find(s, p, o);
+	}
+
+	private static boolean isConcrete(final Node n) {
+		return nonNull(n) && n.isConcrete();
+	}
+
+	@Override
+	public void add(final Triple t) {
+		indexBlock().values().forEach(index -> index.add(t));
+	}
+
+	@Override
+	public void delete(final Triple t) {
+		indexBlock().values().forEach(index -> index.delete(t));
+	}
+
+	@Override
+	public void begin(final ReadWrite rw) {
+		isInTransaction(true);
+		indexBlock().values().forEach(table -> table.begin(rw));
+	}
+
+	@Override
+	public void clear() {
+		indexBlock().values().forEach(TripleTable::clear);
+	}
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/61054a21/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/TripleTable.java
----------------------------------------------------------------------
diff --git a/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/TripleTable.java b/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/TripleTable.java
new file mode 100644
index 0000000..a748b25
--- /dev/null
+++ b/jena-arq/src/main/java/org/apache/jena/sparql/core/mem/TripleTable.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jena.sparql.core.mem;
+
+import java.util.stream.Stream;
+
+import org.apache.jena.graph.Node;
+import org.apache.jena.graph.Triple;
+
+/**
+ * A simplex or multiplex table of {@link Triple}s.
+ *
+ */
+public interface TripleTable extends TupleTable<Triple> {
+
+	/**
+	 * Search the table using a pattern of slots. {@link Node#ANY} or <code>null</code> will work as a wildcard.
+	 *
+	 * @param s the subject node of the pattern
+	 * @param p the predicate node of the pattern
+	 * @param o the object node of the pattern
+	 * @return an {@link Stream} of matched triples
+	 */
+	Stream<Triple> find(Node s, Node p, Node o);
+
+	@Override
+	default void clear() {
+		find(null, null, null).forEach(this::delete);
+	}
+}