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 2017/09/28 11:08:19 UTC

[03/50] [abbrv] jena git commit: Switchable conatiner datasets

Switchable conatiner datasets


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

Branch: refs/heads/jena-tdb2
Commit: d72f69611db396c556281da83de90db57c0c5387
Parents: 46862c3
Author: Andy Seaborne <an...@seaborne.org>
Authored: Wed Aug 30 18:43:32 2017 +0100
Committer: Andy Seaborne <an...@seaborne.org>
Committed: Wed Aug 30 18:43:32 2017 +0100

----------------------------------------------------------------------
 .../transaction/txn/TransactionCoordinator.java |   8 +
 .../java/org/seaborne/tdb2/DatabaseMgr.java     | 119 ++++++++
 .../java/org/seaborne/tdb2/TDB2Factory.java     |  70 ++---
 .../tdb2/assembler/DatasetAssemblerTDB.java     |   4 +-
 .../tdb2/graph/TransactionHandlerTDB.java       |  47 ++-
 .../org/seaborne/tdb2/repack/DatabaseMgr.java   | 171 -----------
 .../tdb2/repack/DatasetGraphSwitchable.java     | 165 ++++++++++-
 .../java/org/seaborne/tdb2/repack/sys/IOX.java  | 278 ------------------
 .../org/seaborne/tdb2/setup/TDBBuilder.java     |   2 -
 .../org/seaborne/tdb2/store/DatasetControl.java |  44 +++
 .../seaborne/tdb2/store/DatasetControlMRSW.java | 144 +++++++++
 .../seaborne/tdb2/store/DatasetControlNone.java |  48 +++
 .../tdb2/store/DatasetControlReadOnly.java      |  59 ++++
 .../seaborne/tdb2/store/DatasetPrefixesTDB.java |   8 +-
 .../tdb2/store/GraphPrefixesProjectionTDB.java  |   9 +-
 .../java/org/seaborne/tdb2/store/GraphTDB.java  | 104 +------
 .../seaborne/tdb2/store/PrefixMappingCache.java |  33 ++-
 .../java/org/seaborne/tdb2/store/QuadTable.java |   1 -
 .../java/org/seaborne/tdb2/store/TableBase.java |   1 -
 .../org/seaborne/tdb2/store/TripleTable.java    |   1 -
 .../nodetable/TReadAppendFileTransport.java     |   2 +-
 .../store/nodetupletable/NodeTupleTable.java    |   2 +-
 .../nodetupletable/NodeTupleTableConcrete.java  |   2 +-
 .../nodetupletable/NodeTupleTableWrapper.java   |   2 +-
 .../seaborne/tdb2/sys/ConnectionTracker.java    |  23 ++
 .../seaborne/tdb2/sys/DatabaseConnection.java   | 164 +++++++++++
 .../java/org/seaborne/tdb2/sys/DatabaseOps.java | 261 +++++++++++++++++
 .../org/seaborne/tdb2/sys/DatasetControl.java   |  44 ---
 .../seaborne/tdb2/sys/DatasetControlMRSW.java   | 146 ---------
 .../tdb2/sys/DatasetControlMutable.java         |  54 ----
 .../seaborne/tdb2/sys/DatasetControlNone.java   |  48 ---
 .../tdb2/sys/DatasetControlReadOnly.java        |  59 ----
 .../main/java/org/seaborne/tdb2/sys/IOX.java    | 293 +++++++++++++++++++
 .../java/org/seaborne/tdb2/TestTDBFactory.java  |  62 ++--
 .../test/java/org/seaborne/tdb2/junit/TL.java   |   3 +-
 .../tdb2/store/TestTransPromoteTDB.java         |   4 +-
 36 files changed, 1462 insertions(+), 1023 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jena/blob/d72f6961/dboe-transaction/src/main/java/org/seaborne/dboe/transaction/txn/TransactionCoordinator.java
----------------------------------------------------------------------
diff --git a/dboe-transaction/src/main/java/org/seaborne/dboe/transaction/txn/TransactionCoordinator.java b/dboe-transaction/src/main/java/org/seaborne/dboe/transaction/txn/TransactionCoordinator.java
index 56cfbac..56104bf 100644
--- a/dboe-transaction/src/main/java/org/seaborne/dboe/transaction/txn/TransactionCoordinator.java
+++ b/dboe-transaction/src/main/java/org/seaborne/dboe/transaction/txn/TransactionCoordinator.java
@@ -459,6 +459,14 @@ public class TransactionCoordinator {
         Objects.nonNull(readWrite) ;
         checkActive() ;
         
+        // XXX Flag to bounce writers fpor long term "block writers"
+        if ( false /* bounceWritersAtTheMoment */) {
+            if ( readWrite == WRITE ) {
+                throw new TransactionException("Writers currently being rejected");
+            }
+        }
+            
+        
         if ( canBlock )
             exclusivitylock.readLock().lock() ;
         else {

http://git-wip-us.apache.org/repos/asf/jena/blob/d72f6961/tdb2/src/main/java/org/seaborne/tdb2/DatabaseMgr.java
----------------------------------------------------------------------
diff --git a/tdb2/src/main/java/org/seaborne/tdb2/DatabaseMgr.java b/tdb2/src/main/java/org/seaborne/tdb2/DatabaseMgr.java
new file mode 100644
index 0000000..33a4a6b
--- /dev/null
+++ b/tdb2/src/main/java/org/seaborne/tdb2/DatabaseMgr.java
@@ -0,0 +1,119 @@
+/*
+ * 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.seaborne.tdb2;
+
+import org.apache.commons.lang3.NotImplementedException ;
+import org.apache.jena.query.Dataset ;
+import org.apache.jena.sparql.core.DatasetGraph ;
+import org.seaborne.dboe.base.file.Location ;
+import org.seaborne.tdb2.repack.DatasetGraphSwitchable ;
+import org.seaborne.tdb2.store.DatasetGraphTDB ;
+import org.seaborne.tdb2.sys.DatabaseOps ;
+import org.seaborne.tdb2.sys.StoreConnection ;
+
+/** Operations for TDBS DatasetGraph, including admin operations 
+ * See {@link TDB2Factory} for creating API-level {@link Dataset Datasets}.
+ * 
+ * @see TDB2Factory
+ */
+public class DatabaseMgr {
+
+    // All creation of DatasetGraph for TDB2 goes through this method.
+    private static DatasetGraph DB_ConnectCreate(Location location) {
+        //return DatabaseOps.connectOrCreate(location);
+        // One level.
+        StoreConnection sConn = StoreConnection.connectCreate(location) ;
+        return sConn.getDatasetGraph() ; 
+    }
+
+    /** Create or connect to a TDB2-backed dataset */
+    public static DatasetGraph connectDatasetGraph(Location location) {
+        return DB_ConnectCreate(location); 
+    }
+
+    /** Create or connect to a TDB2-backed dataset */
+    public static DatasetGraph connectDatasetGraph(String location) {
+        return connectDatasetGraph(Location.create(location)) ;
+    }
+    
+    /**
+     * Compact a datasets which must be a switchable TDB database.
+     * This is the normal dataset type for on-disk TDB2 databases.
+     *  
+     * @param container
+     */
+    public static void compact(DatasetGraph container) {
+        DatasetGraphSwitchable dsg = requireSwitchable(container);
+        DatabaseOps.compact(dsg);
+    }
+
+    /**
+     * Create a backup for a switchable TDB database. This is the normal dataset type for
+     * on-disk TDB2 databases.
+     * <p>
+     * The backup is created in the databases folder, under "Backups".
+     * <p>
+     * Backup creates a consistent copy og the database. It is performed as a read-transaction
+     * and does not lock out other use of the dataset.
+     * 
+     * @param container
+     */
+    public static void backup(DatasetGraph container) {
+        DatasetGraphSwitchable dsg = requireSwitchable(container);
+        throw new NotImplementedException("DatasetGraph backup not implemented yet");
+    }
+
+
+    /** Create an in-memory TDB2-backed dataset (for testing) */
+    public static DatasetGraph createDatasetGraph() {
+        return connectDatasetGraph(Location.mem()) ;
+    }
+
+    /** Return the location of a dataset if it is backed by TDB, else null */ 
+    public static boolean isBackedByTDB(DatasetGraph datasetGraph) {
+        DatasetGraphTDB dsg = requireDirect(datasetGraph);
+        if ( dsg == null )
+            return false ;
+        return true;
+    }
+
+    /** Return the location of a DatasetGraph if it is backed by TDB, else null */
+    public static Location location(DatasetGraph datasetGraph) {
+        DatasetGraphTDB dsg = requireDirect(datasetGraph);
+        if ( dsg == null )
+            return null ;
+        return dsg.getLocation();
+    }
+
+    private static DatasetGraphSwitchable requireSwitchable(DatasetGraph datasetGraph) {
+        if ( datasetGraph instanceof DatasetGraphSwitchable )
+            return (DatasetGraphSwitchable)datasetGraph;
+        throw new TDBException("Not a switchable TDB database");
+    }
+
+    static DatasetGraphTDB requireDirect(DatasetGraph datasetGraph) {
+        DatasetGraph dsg = datasetGraph;
+        if ( datasetGraph instanceof DatasetGraphSwitchable )
+            dsg = ((DatasetGraphSwitchable)datasetGraph).get();
+        if ( datasetGraph instanceof DatasetGraphTDB )
+            return ((DatasetGraphTDB)datasetGraph);
+        throw new TDBException("Not a TDB database (argument is neither a switchable nor direct TDB DatasetGraph)");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/d72f6961/tdb2/src/main/java/org/seaborne/tdb2/TDB2Factory.java
----------------------------------------------------------------------
diff --git a/tdb2/src/main/java/org/seaborne/tdb2/TDB2Factory.java b/tdb2/src/main/java/org/seaborne/tdb2/TDB2Factory.java
index d784fa3..93cc3d2 100644
--- a/tdb2/src/main/java/org/seaborne/tdb2/TDB2Factory.java
+++ b/tdb2/src/main/java/org/seaborne/tdb2/TDB2Factory.java
@@ -21,26 +21,20 @@ import org.apache.jena.query.Dataset ;
 import org.apache.jena.query.DatasetFactory ;
 import org.apache.jena.sparql.core.DatasetGraph ;
 import org.seaborne.dboe.base.file.Location ;
-import org.seaborne.tdb2.store.DatasetGraphTDB;
-import org.seaborne.tdb2.sys.StoreConnection ;
 
-/** Public factory for connecting to and creating datasets backed by TDB2 storage */
+/**
+ *  Public factory for connecting to and creating datasets backed by TDB2 storage.
+ */
 public class TDB2Factory
 {
     private TDB2Factory() {} 
 
-    /** @deprecated Use {@link #connectDatasetGraph(Location)} */
+    /** @deprecated Use {@link DatabaseMgr#connectDatasetGraph(Location)} */
     @Deprecated
     public static DatasetGraph createDatasetGraph(Location location) {
-        return connectDatasetGraph(location);
+        return DatabaseMgr.connectDatasetGraph(location);
     }
     
-    /** Create or connect to a TDB2-backed dataset */
-    public static DatasetGraph connectDatasetGraph(Location location) {
-        StoreConnection sConn = StoreConnection.connectCreate(location) ;
-        return sConn.getDatasetGraph() ; 
-    }
-
     /** @deprecated Use {@link #connectDataset(Location)} */
     @Deprecated
     public static Dataset createDataset(Location location) {
@@ -49,21 +43,16 @@ public class TDB2Factory
     
     /** Create or connect to a TDB2-backed dataset */
     public static Dataset connectDataset(Location location) {
-        DatasetGraph dsg = connectDatasetGraph(location) ;
+        DatasetGraph dsg = DatabaseMgr.connectDatasetGraph(location) ;
         return DatasetFactory.wrap(dsg) ;
     }
 
-    /** @deprecated Use {@link #connectDatasetGraph(String)} */
+    /** @deprecated Use {@link DatabaseMgr#connectDatasetGraph(String)} */
     @Deprecated
     public static DatasetGraph createDatasetGraph(String location) {
-        return connectDatasetGraph(location);
+        return DatabaseMgr.connectDatasetGraph(location);
     }
     
-    /** Create or connect to a TDB2-backed dataset */
-    public static DatasetGraph connectDatasetGraph(String location) {
-        return connectDatasetGraph(Location.create(location)) ;
-    }
-
     /** @deprecated Use {@link #connectDataset(String)} */
     @Deprecated
     public static Dataset createDataset(String location) {
@@ -74,17 +63,19 @@ public class TDB2Factory
     public static Dataset connectDataset(String location) {
         return connectDataset(Location.create(location)) ;    }
 
-    /** Create an in-memory TDB2-backed dataset (for testing) */
-    public static DatasetGraph createDatasetGraph() {
-        return connectDatasetGraph(Location.mem()) ;
-    }
+    /**
+     * Create an in-memory TDB2-backed dataset (for testing). In-memory TDB2 datasets are use
+     * a simple simulation of disk I/O to give exact semantics, which is useful to create
+     * tests that run fast where setup and teardown of datasets can be the major cost.
+     * <p> 
+     * In-memory TDB2 datasets are not designed to scale, nor provide efficient execution for
+     * applications for long-term use. 
+     */ 
+    public static Dataset createDataset() { return connectDataset(Location.mem()) ; }
 
-    /** Create an in-memory TDB2-backed dataset (for testing) */
-    public static Dataset createDataset() {
-        return connectDataset(Location.mem()) ;
-    }
-
-//    /** Read the file and assembler a dataset */
+//    /**
+//     *  Read the file and assembler a dataset
+//     */
 //    public static Dataset assembleDataset(String assemblerFile) {
 //        return (Dataset)AssemblerUtils.build(assemblerFile, VocabTDB.tDatasetTDB) ;
 //    }
@@ -99,31 +90,16 @@ public class TDB2Factory
 //        _release(location(dataset)) ;
 //    }
 
-    /** Return the location of a dataset if it is backed by TDB, else null */ 
+    /** Tes whether a dataset is backed by TDB or not. */ 
     public static boolean isBackedByTDB(Dataset dataset) {
         DatasetGraph dsg = dataset.asDatasetGraph() ;
-        return isBackedByTDB(dsg) ;
+        return DatabaseMgr.isBackedByTDB(dsg) ;
     }
     
-    /** Return the location of a dataset if it is backed by TDB, else null */ 
-    public static boolean isBackedByTDB(DatasetGraph datasetGraph) {
-        if ( datasetGraph instanceof DatasetGraphTDB )
-            // A transaction or the base storage.
-            return true ;
-        return false ;
-    }
-
     /** Return the location of a dataset if it is backed by TDB, else null */
     public static Location location(Dataset dataset) {
         DatasetGraph dsg = dataset.asDatasetGraph() ;
-        return location(dsg) ;
-    }
-
-    /** Return the location of a DatasetGraph if it is backed by TDB, else null */
-    public static Location location(DatasetGraph datasetGraph) {
-        if ( datasetGraph instanceof DatasetGraphTDB )
-            return ((DatasetGraphTDB)datasetGraph).getLocation() ;
-        return null ;
+        return DatabaseMgr.location(dsg) ;
     }
 
 //    /** Set the {@link StoreParams} for specific Location.

http://git-wip-us.apache.org/repos/asf/jena/blob/d72f6961/tdb2/src/main/java/org/seaborne/tdb2/assembler/DatasetAssemblerTDB.java
----------------------------------------------------------------------
diff --git a/tdb2/src/main/java/org/seaborne/tdb2/assembler/DatasetAssemblerTDB.java b/tdb2/src/main/java/org/seaborne/tdb2/assembler/DatasetAssemblerTDB.java
index ab2ba9c..dc2d67a 100644
--- a/tdb2/src/main/java/org/seaborne/tdb2/assembler/DatasetAssemblerTDB.java
+++ b/tdb2/src/main/java/org/seaborne/tdb2/assembler/DatasetAssemblerTDB.java
@@ -36,8 +36,8 @@ import org.apache.jena.sparql.core.assembler.DatasetAssembler ;
 import org.apache.jena.sparql.expr.NodeValue ;
 import org.apache.jena.system.JenaSystem ;
 import org.seaborne.dboe.base.file.Location ;
+import org.seaborne.tdb2.DatabaseMgr ;
 import org.seaborne.tdb2.TDB2 ;
-import org.seaborne.tdb2.TDB2Factory ;
 
 public class DatasetAssemblerTDB extends DatasetAssembler
 {
@@ -55,7 +55,7 @@ public class DatasetAssemblerTDB extends DatasetAssembler
 
         String dir = getStringValue(root, pLocation) ;
         Location loc = Location.create(dir) ;
-        DatasetGraph dsg = TDB2Factory.connectDatasetGraph(loc) ;
+        DatasetGraph dsg = DatabaseMgr.connectDatasetGraph(loc) ;
 
         if ( root.hasProperty(pUnionDefaultGraph) ) {
             Node b = root.getProperty(pUnionDefaultGraph).getObject().asNode() ;

http://git-wip-us.apache.org/repos/asf/jena/blob/d72f6961/tdb2/src/main/java/org/seaborne/tdb2/graph/TransactionHandlerTDB.java
----------------------------------------------------------------------
diff --git a/tdb2/src/main/java/org/seaborne/tdb2/graph/TransactionHandlerTDB.java b/tdb2/src/main/java/org/seaborne/tdb2/graph/TransactionHandlerTDB.java
index bcd5b3d..664b005 100644
--- a/tdb2/src/main/java/org/seaborne/tdb2/graph/TransactionHandlerTDB.java
+++ b/tdb2/src/main/java/org/seaborne/tdb2/graph/TransactionHandlerTDB.java
@@ -17,45 +17,44 @@
 
 package org.seaborne.tdb2.graph;
 
-import org.apache.jena.graph.Graph ;
 import org.apache.jena.graph.impl.TransactionHandlerBase ;
-import org.seaborne.tdb2.TDB2 ;
+import org.apache.jena.query.ReadWrite;
+import org.seaborne.dboe.transaction.txn.TransactionCoordinator;
+import org.seaborne.tdb2.store.DatasetGraphTDB;
 import org.seaborne.tdb2.store.GraphTDB ;
 
-/** Support for when TDB is used non-transactionally. Does not support ACID transactions.  
- *  Flushes if commit is called although it denies supporting transactions
- */
-
 public class TransactionHandlerTDB extends TransactionHandlerBase //implements TransactionHandler 
 {
-    private final Graph graph ;
+    private final GraphTDB graph ;
+    private final DatasetGraphTDB dsg;
 
-    public TransactionHandlerTDB(GraphTDB graph)
-    {
-        this.graph = graph ;
+    public TransactionHandlerTDB(GraphTDB graph) {
+        this.graph = graph;
+        this.dsg = graph.getDSG();
     }
-    
+
     @Override
-    public void abort()
-    {
-        // Not the Jena old-style transaction interface
-        throw new UnsupportedOperationException("TDB: 'abort' of a transaction not supported") ;
-        //log.warn("'Abort' of a transaction not supported - ignored") ;
+    public void abort() {
+        graph.getDSG().abort();
+        graph.getDSG().end();
     }
 
     @Override
-    public void begin()
-    {}
+    public void begin() {
+        if ( TransactionCoordinator.promotion )
+            dsg.begin(ReadWrite.READ);
+        else
+            dsg.begin(ReadWrite.WRITE);
+    }
 
     @Override
-    public void commit()
-    {
-        TDB2.sync(graph) ;
+    public void commit() {
+        dsg.commit();
+        dsg.end();
     }
 
     @Override
-    public boolean transactionsSupported()
-    {
-        return false ;
+    public boolean transactionsSupported() {
+        return true;
     }
 }

http://git-wip-us.apache.org/repos/asf/jena/blob/d72f6961/tdb2/src/main/java/org/seaborne/tdb2/repack/DatabaseMgr.java
----------------------------------------------------------------------
diff --git a/tdb2/src/main/java/org/seaborne/tdb2/repack/DatabaseMgr.java b/tdb2/src/main/java/org/seaborne/tdb2/repack/DatabaseMgr.java
deleted file mode 100644
index 30b01d6..0000000
--- a/tdb2/src/main/java/org/seaborne/tdb2/repack/DatabaseMgr.java
+++ /dev/null
@@ -1,171 +0,0 @@
-/*
- * 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.seaborne.tdb2.repack;
-
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.List;
-
-import org.apache.jena.sparql.core.DatasetGraph;
-import org.seaborne.dboe.base.file.Location;
-import org.seaborne.dboe.transaction.txn.TransactionCoordinator;
-import org.seaborne.dboe.transaction.txn.TransactionalSystem;
-import org.seaborne.tdb2.TDBException;
-import org.seaborne.tdb2.repack.sys.CopyDSG;
-import org.seaborne.tdb2.repack.sys.FilenameUtils;
-import org.seaborne.tdb2.repack.sys.IOX;
-import org.seaborne.tdb2.repack.sys.Util;
-import org.seaborne.tdb2.setup.StoreParams;
-import org.seaborne.tdb2.store.DatasetGraphTDB;
-import org.seaborne.tdb2.sys.StoreConnection;
-
-/** Create TDB2 databases. 
- * 
- *  TDB2 uses a hierarchical structure to manage on disk.
- * 
- *  If in-memory (non-scalable, not-performant, perfect simulation for functionality using a RAM disk, for testing mainly),
- *  create a 
- *    
- * 
- * Directory and files:
- * <ul>
- * <li> {@code Data-NNNN/} -- databases by version. Compacting creates a new directory, leaving 
- * <li> store params, static (disk layout related - can not chnage once created) and dynamic (settings related to in-memory).
- * <li> {@code Backup-DATE/} -- backups.
- * <li> External indexes like {@code TextIndex/} -- the text index only applies to the current, latest database.
- * </ul>
- */
-public class DatabaseMgr {
-    private static final String dbPrefix   = "Data";
-    private static final String SEP        = "-";
-    private static final String startCount = "0001";
-    
-    // ***** Need to share DatasetGraphSwitchable -> push down into StoreConnection.
-    
-    public static DatasetGraph connectOrCreate(Location location) {
-        if ( location.isMem() )
-            return StoreConnection.connectCreate(location).getDatasetGraph();
-        // Exists?
-       if ( ! location.exists() )
-           throw new TDBException("No such location: "+location);
-       Path path = IOX.asPath(location);
-       // Scan for DBs
-       Path db = findLocation(path, dbPrefix);
-       // XXX [StoreParams]
-       StoreParams params;
-       if ( db == null ) {
-           db = path.resolve(dbPrefix+SEP+startCount);
-           IOX.createDirectory(db);
-       }
-       Location loc2 = IOX.asLocation(db);
-       DatasetGraphTDB dsg = StoreConnection.connectCreate(loc2).getDatasetGraphTDB();
-       DatasetGraph appDSG = new DatasetGraphSwitchable(path, dsg);
-       return appDSG;
-    }
-    
-    // JVM-wide :-(
-    private static Object compactionLock = new Object();
-    
-    public static void compact(DatasetGraphSwitchable container) {
-        synchronized(compactionLock) {
-            
-            Path base = container.getContainerPath();
-            
-            Location loc1a = ((DatasetGraphTDB)container.get()).getLocation();
-            
-            if ( loc1a.isMem() ) {}
-            if ( ! loc1a.exists() )
-                throw new TDBException("No such location: "+loc1a);
-            
-            // Checks
-            Path db1 = findLocation(base, dbPrefix);
-            Location loc1 = IOX.asLocation(db1);
-            // Is this the same database location?
-            if ( ! loc1.equals(loc1a) )
-                throw new TDBException("Inconsistent (not latested?) : "+loc1a+" : "+loc1);
-            
-            // Version
-            int v = IOX.extractIndex(db1.getFileName().toString(), dbPrefix, SEP);
-            String next = FilenameUtils.filename(dbPrefix, SEP, v+1);
-            
-            Path db2 = db1.getParent().resolve(next);
-            IOX.createDirectory(db2);
-            Location loc2 = IOX.asLocation(db2);
-            System.out.printf("Compact %s -> %s\n", db1.getFileName(), db2.getFileName());
-            
-            compact(container, loc1, loc2);
-        }
-    }
-    
-    // XXX Later - switch in a recording dataset, not block writers, and reply after
-    // switch over before releasing the new dataset to the container.
-    // Maybe copy indexes and switch the DSG over (drop switchable).
-    
-    /** Copy the latest version from one location to another. */
-    private static void compact(DatasetGraphSwitchable container, Location loc1, Location loc2) {
-        if ( loc1.isMem() || loc2.isMem() )
-            throw new TDBException("Compact involves a memory location: "+loc1+" : "+loc2);
-        StoreConnection srcConn = StoreConnection.connectExisting(loc1);
-        if ( srcConn == null ) {
-            throw new TDBException("No database at location : "+loc1); 
-        }
-        
-        DatasetGraphTDB dsgBase0 = (DatasetGraphTDB)container.get();
-        if ( ! dsgBase0.getLocation().equals(loc1) )
-            throw new TDBException("Inconsistent locations for base : "+dsgBase0.getLocation()+" , "+dsgBase0.getLocation());
-        
-        DatasetGraphTDB dsgBase = srcConn.getDatasetGraphTDB();
-        if ( dsgBase != dsgBase0 )
-            throw new TDBException("Inconsistent datasets : "+dsgBase0.getLocation()+" , "+dsgBase.getLocation());
-        
-        TransactionalSystem txnSystem = dsgBase.getTxnSystem();
-        TransactionCoordinator txnMgr = dsgBase.getTxnSystem().getTxnMgr();
-        
-        // Stop update.  On exit there are no writers and none will start until switched over.
-        txnMgr.blockWriters();
-        
-        // Copy the latest generation.
-        DatasetGraphTDB dsgCompact = StoreConnection.connectCreate(loc2).getDatasetGraphTDB();
-        CopyDSG.copy(dsgBase, dsgCompact);   
-
-        TransactionCoordinator txnMgr2 = dsgCompact.getTxnSystem().getTxnMgr();
-        txnMgr2.startExclusiveMode();
-
-        txnMgr.startExclusiveMode();
-
-        // No transactions on either database.
-        // Switch.
-        container.set(dsgCompact);
-        txnMgr2.finishExclusiveMode();
-        // New database running.
-
-        // Clean-up.
-        // txnMgr.finishExclusiveMode();
-        // Don't call : txnMgr.startWriters();
-        StoreConnection.release(dsgBase.getLocation());
-    }
-    
-    private static Path findLocation(Path directory, String namebase) {
-        if ( ! Files.exists(directory) )
-            return null;
-        // In-order, low to high.
-        List<Path> maybe = IOX.scanForDirByPattern(directory, namebase, SEP);
-        return Util.getLastOrNull(maybe);
-    }
-}

http://git-wip-us.apache.org/repos/asf/jena/blob/d72f6961/tdb2/src/main/java/org/seaborne/tdb2/repack/DatasetGraphSwitchable.java
----------------------------------------------------------------------
diff --git a/tdb2/src/main/java/org/seaborne/tdb2/repack/DatasetGraphSwitchable.java b/tdb2/src/main/java/org/seaborne/tdb2/repack/DatasetGraphSwitchable.java
index 870f8af..642fb61 100644
--- a/tdb2/src/main/java/org/seaborne/tdb2/repack/DatasetGraphSwitchable.java
+++ b/tdb2/src/main/java/org/seaborne/tdb2/repack/DatasetGraphSwitchable.java
@@ -18,17 +18,30 @@
 package org.seaborne.tdb2.repack;
 
 import java.nio.file.Path;
+import java.util.Map;
 import java.util.concurrent.atomic.AtomicReference;
 
-import org.apache.jena.sparql.core.DatasetGraph ;
-import org.apache.jena.sparql.core.DatasetGraphWrapper ;
+import org.apache.jena.graph.Graph;
+import org.apache.jena.graph.Node;
+import org.apache.jena.shared.PrefixMapping;
+import org.apache.jena.shared.impl.PrefixMappingImpl;
+import org.apache.jena.sparql.core.*;
+import org.seaborne.tdb2.store.DatasetGraphTDB;
 
-final // Encourage the JIT!
+final
 public class DatasetGraphSwitchable extends DatasetGraphWrapper
 {
+    // QueryEngineFactoryWrapper has a QueryEngineFactory that is always loaded that
+    // executes on the unwrapped DSG (recursively). Unwrapping is via getBase, calling
+    // getWrapped() which is implemented with get().
+    
+//    static { 
+//        // QueryEngineRegistry.addFactory(factory());
+//    }
+    
     private final AtomicReference<DatasetGraph> dsgx = new AtomicReference<>();
     // Null for in-memory datasets.
-    private final Path basePath; 
+    private final Path basePath;
     
     public DatasetGraphSwitchable(Path base, DatasetGraph dsg) {
         // Don't use the slot in datasetGraphWrapper - use the AtomicReference
@@ -37,6 +50,11 @@ public class DatasetGraphSwitchable extends DatasetGraphWrapper
         this.basePath = base;  
     }
 
+    /** Is this {@code DatasetGraphSwitchable} just a holder for a {@code DatasetGraph}?
+     *  If so, it does not have a location on disk.
+     */
+    public boolean hasContainerPath() { return basePath != null; } 
+    
     public Path getContainerPath() { return basePath; }
     
     /** The dataset to use for redirection - can be overridden.
@@ -53,5 +71,144 @@ public class DatasetGraphSwitchable extends DatasetGraphWrapper
     public DatasetGraph set(DatasetGraph dsg) { 
         return dsgx.getAndSet(dsg);
     }
+    
+    /** If and only if the current value is the given old value, set the base {@link DatasetGraph}  
+     * Returns true if a swap happened.
+     */ 
+    public boolean change(DatasetGraph oldDSG, DatasetGraph newDSG) { 
+        return dsgx.compareAndSet(oldDSG, newDSG);
+    }
+
+    // Used directly, Does not get prefix mapping right.
+    // GraphView has a private, empty prefix mapping
+
+    Graph dft = new GraphView(this, Quad.defaultGraphNodeGenerated) {
+        @Override
+        protected PrefixMapping createPrefixMapping() {
+            return prefixMapping(null);
+        } 
+    };
+    
+    @Override
+    public Graph getDefaultGraph() {
+        // Via the switchable.
+        return new GraphView(this, Quad.defaultGraphNodeGenerated) {
+            @Override
+            protected PrefixMapping createPrefixMapping() {
+                return prefixMapping(null);
+            }
+        };
+    }
+    
+    @Override
+    public Graph getGraph(Node gn) {
+        if ( Quad.isUnionGraph(gn) )
+            return GraphView.createUnionGraph(get());
+        
+        return new GraphView(this, gn) {
+            @Override
+            protected PrefixMapping createPrefixMapping() {
+                return prefixMapping(gn);
+            }
+        };
+    }
+
+    // TDB2 specific.
+    // Does not cope with blank nodes.
+    // A PrefixMapping sending operations via the switchable.
+    private PrefixMapping prefixMapping(Node graphName) {
+        
+        String gn = (graphName == null) ? "" : graphName.getURI(); 
+        
+        return new PrefixMappingImpl() {
+            
+            DatasetPrefixStorage dps() {
+                return ((DatasetGraphTDB)dsgx.get()).getPrefixes();
+            }
+            
+            Graph graph() {
+                DatasetGraphTDB dsg = (DatasetGraphTDB)dsgx.get();
+                if ( gn == null )
+                    return dsg.getDefaultGraph();
+                else
+                    return dsg.getGraph(graphName);
+            }
+            
+            PrefixMapping prefixMapping() {
+                if ( gn == null )
+                    return dps().getPrefixMapping();
+                else
+                    return dps().getPrefixMapping(gn); 
+            }
+
+            @Override
+            protected void set(String prefix, String uri) {
+                dps().insertPrefix(gn, prefix, uri);
+                super.set(prefix, uri);
+            }
+
+            @Override
+            protected String get(String prefix) {
+                return dps().readPrefix(gn, prefix);
+            }
+
+            @Override
+            protected void remove(String prefix) {
+                dps().getPrefixMapping().removeNsPrefix(prefix);
+                super.remove(prefix);
+            }
+            
+            @Override
+            public Map<String, String> getNsPrefixMap() {
+                return prefixMapping().getNsPrefixMap();
+                //return graph().getPrefixMapping().getNsPrefixMap();
+            }
+        };
+    }
+    
+    //static { register() ; }
+    
+    
+//    static QueryEngineFactory factory() {
+//        return new QueryEngineFactory() {
+//            @Override
+//            public boolean accept(Op op, DatasetGraph dataset, Context context) {
+//                DatasetGraphSwitchable dsg = extract(dataset) ;
+//                if ( dsg == null ) return false;
+//                QueryEngineFactory f = QueryEngineRegistry.findFactory(op, dsg.get(), context);
+//                return f.accept(op, dataset, context);
+//            }
+//
+//            @Override
+//            public Plan create(Op op, DatasetGraph dataset, Binding inputBinding, Context context) {
+//                DatasetGraphSwitchable dsg = extract(dataset) ;
+//                if ( dsg == null ) return null;
+//                QueryEngineFactory f = QueryEngineRegistry.findFactory(op, dsg.get(), context);
+//                return f.create(op, dataset, inputBinding, context);
+//            }
+//
+//            private DatasetGraphSwitchable extract(DatasetGraph dataset) {
+//                if ( dataset instanceof DatasetGraphSwitchable )
+//                    return (DatasetGraphSwitchable)dataset;
+//                return null;
+//            }
+//
+//            @Override
+//            public boolean accept(Query query, DatasetGraph dataset, Context context) {
+//                DatasetGraphSwitchable dsg = extract(dataset) ;
+//                if ( dsg == null ) return false;
+//                QueryEngineFactory f = QueryEngineRegistry.findFactory(query, dsg.get(), context);
+//                return f.accept(query, dataset, context);
+//            }
+//
+//            @Override
+//            public Plan create(Query query, DatasetGraph dataset, Binding inputBinding, Context context) {
+//                DatasetGraphSwitchable dsg = extract(dataset) ;
+//                if ( dsg == null ) return null;
+//                QueryEngineFactory f = QueryEngineRegistry.findFactory(query, dsg.get(), context);
+//                return f.create(query, dataset, inputBinding, context);
+//            }
+//        };
+//    }
 }
 

http://git-wip-us.apache.org/repos/asf/jena/blob/d72f6961/tdb2/src/main/java/org/seaborne/tdb2/repack/sys/IOX.java
----------------------------------------------------------------------
diff --git a/tdb2/src/main/java/org/seaborne/tdb2/repack/sys/IOX.java b/tdb2/src/main/java/org/seaborne/tdb2/repack/sys/IOX.java
deleted file mode 100644
index 183ef30..0000000
--- a/tdb2/src/main/java/org/seaborne/tdb2/repack/sys/IOX.java
+++ /dev/null
@@ -1,278 +0,0 @@
-/*
- * 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.seaborne.tdb2.repack.sys;
-
-import java.io.BufferedOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.nio.file.*;
-import java.nio.file.attribute.FileAttribute;
-import java.util.ArrayList;
-import java.util.Comparator;
-import java.util.List;
-import java.util.function.Function ;
-import java.util.regex.Pattern;
-
-import org.apache.jena.atlas.RuntimeIOException;
-import org.apache.jena.atlas.logging.FmtLog;
-import org.seaborne.dboe.DBOpEnvException;
-import org.seaborne.dboe.base.file.Location;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- *  PathX
- */
-public class IOX {
-    
-    public static Path currentDirectory = Paths.get(".");
-    
-    /** A Consumer that can throw {@link IOException}. */
-    public interface IOConsumer<X> {
-        void actionEx(X arg) throws IOException;
-    }
-
-    /** Convert an {@link IOException} into a {@link RuntimeIOException}.
-     * <p>
-     * Idiom:
-     * <pre>
-     *     catch(IOException ex) { throw new exception(ex); } 
-     * </pre>
-     * @param ioException
-     * @return RuntimeIOException
-     */
-    public static RuntimeIOException exception(IOException ioException) {
-        return new RuntimeIOException(ioException);
-    }
-    
-    /** Write a file safely - the change happens (the function returns true) or
-     * somthing went wrong (the function throws a runtime exception) and the file is not changed.
-     * Note that the tempfile must be in the same direct as the actual file so an OS-atomic rename can be done.  
-     */
-    public static boolean safeWrite(Path file, IOConsumer<OutputStream> writerAction) {
-        Path tmp = createTempFile(file.getParent(), file.getFileName().toString(), ".tmp");
-        return safeWrite(file, tmp, writerAction);
-    }
-
-    /** Write a file safely - the change happens (the function returns true) or
-     * somthing went wrong (the function throws a runtime exception) and the file is not changed.
-     * Note that the tempfile must be in the same direct as the actual file so an OS-atomic rename can be done.  
-     */
-    public static boolean safeWrite(Path file, Path tmpFile, IOConsumer<OutputStream> writerAction) {
-        try {
-            try(OutputStream out = new BufferedOutputStream(Files.newOutputStream(tmpFile)) ) {
-                writerAction.actionEx(out);
-            }
-            move(tmpFile, file);
-            return true;
-        } catch(IOException ex) { throw IOX.exception(ex); }
-    }
-
-    /** Delete a file. */
-    public static void delete(Path path) {
-        try { Files.delete(path); }
-        catch (IOException ex) {
-            FmtLog.error(IOX.class, ex, "IOException deleting %s", path);
-            throw IOX.exception(ex);
-        }
-    }
-    
-    /** Atomically move a file. */
-    public static void move(Path src, Path dst) {
-        try { Files.move(src, dst, StandardCopyOption.ATOMIC_MOVE) ; }
-        catch (IOException ex) {
-            FmtLog.error(IOX.class, ex, "IOException moving %s to %s", src, dst);
-            throw IOX.exception(ex);
-        }
-    }
-    
-    /** Copy a file, not atomic. *
-     * Can copy to a directory or over an existing file.
-     * @param srcFilename
-     * @param dstFilename
-     */
-    public static void copy(String srcFilename, String dstFilename) {
-        Path src = Paths.get(srcFilename);
-        if ( ! Files.exists(src) )
-            throw new RuntimeIOException("No such file: "+srcFilename);
-        
-        Path dst = Paths.get(dstFilename);
-        if ( Files.isDirectory(dst) )
-            dst = dst.resolve(src.getFileName());
-        
-        try { Files.copy(src, dst); }
-        catch (IOException ex) {
-            FmtLog.error(IOX.class, ex, "IOException copying %s to %s", srcFilename, dstFilename);
-            throw IOX.exception(ex);
-        }
-    }
-    
-    /** Create a directory - thgrow a runtime exception if theer are any problems.
-     * This function wraps {@code Files.createDirectory}.
-     */
-    public static void createDirectory(Path dir) {
-        try { Files.createDirectory(dir); }
-        catch (IOException ex) { throw IOX.exception(ex); }
-    }
-
-    /** Convert a {@link Path}  to a {@link Location}. */
-    public static Location asLocation(Path path) {
-        if ( ! Files.isDirectory(path) )
-            throw new RuntimeIOException("Path is not naming a directory: "+path);
-        return Location.create(path.toString());
-    }
-    
-//    /** Convert a {@link org.apache.jena.tdb.base.file.Location} to a {@link Path}. */
-//    public static Path asPath(org.apache.jena.tdb.base.file.Location location) {
-//        if ( location.isMem() )
-//            throw new RuntimeIOException("Location is a memory location: "+location);
-//        return Paths.get(location.getDirectoryPath());
-//    }
-
-    /** Convert a {@link Location} to a {@link Path}. */
-    public static Path asPath(org.seaborne.dboe.base.file.Location location) {
-        if ( location.isMem() )
-            throw new RuntimeIOException("Location is a memory location: "+location);
-        return Paths.get(location.getDirectoryPath());
-    }
-
-    /** Convert a {@link Location} to a {@link File}. */
-    public static File asFile(Location loc) {
-        return new File(loc.getDirectoryPath());
-    }
-
-    /** Read the whole of a file */
-    public static byte[] readAll(Path pathname) {
-        try {
-            return Files.readAllBytes(pathname);
-        } catch (IOException ex) { throw IOX.exception(ex); }
-    }
-    
-    /** Write the whole of a file */
-    public static void writeAll(Path pathname, byte[] value) {
-        try {
-            Files.write(pathname, value, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.CREATE);
-        } catch (IOException ex) { throw IOX.exception(ex); }
-    }
-
-//    /**
-//     * Ensure a directory exists, creating the directory and any missing
-//     * directories on the path to it.
-//     */
-//    public static void ensureDirectory(Path dir, FileAttribute<? >... attrs) {
-//        if ( Files.exists(dir) ) {
-//            if ( ! Files.isDirectory(dir) )
-//                throw new DeltaFileException("Exists but not a directory: "+dir);
-//        }
-//        try { Files.createDirectories(dir, attrs); }
-//        catch (IOException ex) { new DeltaFileException("Failed to create directory: "+dir, ex);}
-//    }
-//    
-//    /**
-//     * Ensure a file exists - create an empty one if not. This operation does
-//     * not create a directory path to the file.
-//     */
-//    public static void ensureFile(Path filePath, FileAttribute<? >... attrs) {
-//        if ( Files.exists(filePath) ) {
-//            if ( ! Files.isRegularFile(filePath) )
-//                throw new DeltaFileException("Exists but not a regular file: "+filePath);
-//        }
-//        try { Files.createFile(filePath); }
-//        catch (IOException ex) { new DeltaFileException("Failed to create file: "+filePath, ex);}
-//    }
-
-    /**
-     * Return a temporary filename path.
-     * <p>
-     * This operation is thread-safe.
-     */
-    public static Path createTempFile(Path dir, String prefix, String suffix, FileAttribute<? >... attrs) {
-        try {
-            return Files.createTempFile(dir, prefix, suffix, attrs);
-        } catch (IOException ex) { throw IOX.exception(ex); }
-    }
-    
-    /** Generate a unique place related to path; 
-     * Optionally, provide a mapping of old name to new nae base.
-     * This method always adds "-1", "-2" etc. 
-     */  
-    public static Path uniqueDerivedPath(Path path, Function<String, String> basenameMapping) {
-        String base = path.getFileName().toString();
-        if ( basenameMapping != null )
-            base = basenameMapping.apply(base);
-        // Some large limit "just in case"
-        for(int x = 0 ; x < 10_000 ; x++ ) {
-            String destname = base+"-"+x; 
-            Path destpath = path.resolveSibling(destname);
-            if ( ! Files.exists(destpath) )
-                return destpath;
-        }
-        return null ;
-    }
-    
-    private static Logger LOG = LoggerFactory.getLogger(Util.class);
-    
-    /** Find the files in this directory that have namebase as a prefix and 
-     *  are then numbered.
-     *  <p>
-     *  Returns a sorted list from, low to high index.
-     */
-    public static List<Path> scanForDirByPattern(Path directory, String namebase, String nameSep) {
-        Pattern pattern = Pattern.compile(Pattern.quote(namebase)+
-                                          Pattern.quote(nameSep)+
-                                          "[\\d]+");
-        List<Path> paths = new ArrayList<>();
-        try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory, namebase + "*")) {
-            for ( Path entry : stream ) {
-                if ( !pattern.matcher(entry.getFileName().toString()).matches() ) {
-                    throw new DBOpEnvException("Invalid filename for matching: "+entry.getFileName());
-                    // Alternative: Skip bad trailing parts but more likely there is a naming problem.  
-                    //   LOG.warn("Invalid filename for matching: {} skipped", entry.getFileName());
-                    //   continue;
-                }
-                // Follows symbolic links.
-                if ( !Files.isDirectory(entry) )
-                    throw new DBOpEnvException("Not a directory: "+entry);
-                paths.add(entry);
-            }
-        }
-        catch (IOException ex) {
-            FmtLog.warn(LOG, "Can't inspect directory: (%s, %s)", directory, namebase);
-            throw new DBOpEnvException(ex);
-        }
-        Comparator<Path> comp = (f1, f2) -> {
-            int num1 = extractIndex(f1.getFileName().toString(), namebase, nameSep);
-            int num2 = extractIndex(f2.getFileName().toString(), namebase, nameSep);
-            return Integer.compare(num1, num2);
-        };
-        paths.sort(comp);
-        //indexes.sort(Long::compareTo);
-        return paths;
-    }
-
-    /** Given a filename in "base-NNNN" format, return the value of NNNN */
-    public static int extractIndex(String name, String namebase, String nameSep) {
-        int i = namebase.length()+nameSep.length();
-        String numStr = name.substring(i);
-        int num = Integer.parseInt(numStr);
-        return num;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/jena/blob/d72f6961/tdb2/src/main/java/org/seaborne/tdb2/setup/TDBBuilder.java
----------------------------------------------------------------------
diff --git a/tdb2/src/main/java/org/seaborne/tdb2/setup/TDBBuilder.java b/tdb2/src/main/java/org/seaborne/tdb2/setup/TDBBuilder.java
index 4490e3f..66c5c89 100644
--- a/tdb2/src/main/java/org/seaborne/tdb2/setup/TDBBuilder.java
+++ b/tdb2/src/main/java/org/seaborne/tdb2/setup/TDBBuilder.java
@@ -49,8 +49,6 @@ import org.seaborne.tdb2.store.nodetupletable.NodeTupleTable ;
 import org.seaborne.tdb2.store.nodetupletable.NodeTupleTableConcrete ;
 import org.seaborne.tdb2.store.tupletable.TupleIndex ;
 import org.seaborne.tdb2.store.tupletable.TupleIndexRecord ;
-import org.seaborne.tdb2.sys.DatasetControl ;
-import org.seaborne.tdb2.sys.DatasetControlMRSW ;
 import org.seaborne.tdb2.sys.SystemTDB ;
 import org.slf4j.Logger ;
 import org.slf4j.LoggerFactory ;

http://git-wip-us.apache.org/repos/asf/jena/blob/d72f6961/tdb2/src/main/java/org/seaborne/tdb2/store/DatasetControl.java
----------------------------------------------------------------------
diff --git a/tdb2/src/main/java/org/seaborne/tdb2/store/DatasetControl.java b/tdb2/src/main/java/org/seaborne/tdb2/store/DatasetControl.java
new file mode 100644
index 0000000..b283ed4
--- /dev/null
+++ b/tdb2/src/main/java/org/seaborne/tdb2/store/DatasetControl.java
@@ -0,0 +1,44 @@
+/*
+ *  Licensed 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.
+ *
+ *  See the NOTICE file distributed with this work for additional
+ *  information regarding copyright ownership.
+ */
+
+package org.seaborne.tdb2.store;
+
+import java.util.Iterator ;
+
+// Used in NodeTupleTables.
+
+/** A DatasetControl is an encapsulation of what to do on reads and writes.
+ *  In addition, iterators returned can be tied back to the original request
+ *  to check they are still valid.  
+ */
+public interface DatasetControl
+{
+    /** Fine grained, internal update - start */  
+    public void startUpdate() ;
+    
+    /** Fine grained, internal update - finish */
+    public void finishUpdate();
+
+    /** Signal the start of an internal read operation */
+    public void startRead();
+    
+    /** Signal the completion of an internal read operation */
+    public void finishRead();
+    
+    /** An iterator being created. */
+    public <T> Iterator<T> iteratorControl(Iterator<T> iter) ;
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/d72f6961/tdb2/src/main/java/org/seaborne/tdb2/store/DatasetControlMRSW.java
----------------------------------------------------------------------
diff --git a/tdb2/src/main/java/org/seaborne/tdb2/store/DatasetControlMRSW.java b/tdb2/src/main/java/org/seaborne/tdb2/store/DatasetControlMRSW.java
new file mode 100644
index 0000000..d4c9f57
--- /dev/null
+++ b/tdb2/src/main/java/org/seaborne/tdb2/store/DatasetControlMRSW.java
@@ -0,0 +1,144 @@
+/*
+ *  Licensed 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.
+ *
+ *  See the NOTICE file distributed with this work for additional
+ *  information regarding copyright ownership.
+ */
+
+package org.seaborne.tdb2.store;
+
+import static java.lang.String.format ;
+
+import java.util.ConcurrentModificationException ;
+import java.util.Iterator ;
+import java.util.NoSuchElementException ;
+import java.util.concurrent.atomic.AtomicLong ;
+
+import org.apache.jena.atlas.iterator.Iter ;
+import org.apache.jena.atlas.lib.Closeable ;
+
+/** A policy that checks, but does not enforce, single writer or multiple writer locking policy */ 
+public class DatasetControlMRSW implements DatasetControl
+{
+    private final boolean concurrencyChecking = true ;
+    private final AtomicLong epoch = new AtomicLong(5) ;                // Update counters, used to check iterators. No need to start at 0.
+    private final AtomicLong readCounter = new AtomicLong(0) ;
+    private final AtomicLong writeCounter = new AtomicLong(0) ;
+    
+    public DatasetControlMRSW()
+    { }
+
+    @Override
+    public void startRead() {
+        readCounter.getAndIncrement() ;
+        checkConcurrency() ;
+    }
+
+    @Override
+    public void finishRead() {
+        readCounter.decrementAndGet() ;
+    }
+
+    @Override
+    public void startUpdate() {
+        epoch.getAndIncrement() ;
+        writeCounter.getAndIncrement() ;
+        checkConcurrency() ;
+    }
+
+    @Override
+    public void finishUpdate() {
+        writeCounter.decrementAndGet() ;
+    }
+
+    private void checkConcurrency() {
+        long R, W ;
+        synchronized (this) {
+            R = readCounter.get() ;
+            W = writeCounter.get() ;
+        }
+
+        if ( R > 0 && W > 0 )
+            policyError(R, W) ;
+        if ( W > 1 )
+            policyError(R, W) ;
+    }
+    
+    @Override
+    public <T> Iterator<T> iteratorControl(Iterator<T> iter) { return new IteratorCheckNotConcurrent<>(iter, epoch) ; }
+    
+    private static class IteratorCheckNotConcurrent<T> implements Iterator<T>, Closeable
+    {
+        private Iterator<T> iter ;
+        private AtomicLong eCount ;
+        private boolean finished = false ;
+        private long startEpoch ; 
+
+        IteratorCheckNotConcurrent(Iterator<T> iter, AtomicLong eCount) {
+            // Assumes correct locking to set up, i.e. eCount not changing
+            // (writer on separate thread).
+            this.iter = iter ;
+            this.eCount = eCount ;
+            this.startEpoch = eCount.get() ;
+        }
+
+        private void checkConcurrentModification() {
+            if ( finished )
+                return ;
+
+            long now = eCount.get() ;
+            if ( now != startEpoch ) {
+                policyError(format("Iterator: started at %d, now %d", startEpoch, now)) ;
+            }
+        }
+
+        @Override
+        public boolean hasNext() {
+            checkConcurrentModification() ;
+            boolean b = iter.hasNext() ;
+            if ( !b )
+                close() ;
+            return b ;
+        }
+
+        @Override
+        public T next() {
+            checkConcurrentModification() ;
+            try { return iter.next() ; }
+            catch (NoSuchElementException ex) {
+                close() ;
+                throw ex ;
+            }
+        }
+
+        @Override
+        public void remove() {
+            checkConcurrentModification() ;
+            iter.remove() ;
+        }
+
+        @Override
+        public void close() {
+            finished = true ;
+            Iter.close(iter) ;
+        }
+    }
+    
+    private static void policyError(long R, long W) {
+        policyError(format("Reader = %d, Writer = %d", R, W)) ;
+    }
+
+    private static void policyError(String message) {
+        throw new ConcurrentModificationException(message) ;
+    }    
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/d72f6961/tdb2/src/main/java/org/seaborne/tdb2/store/DatasetControlNone.java
----------------------------------------------------------------------
diff --git a/tdb2/src/main/java/org/seaborne/tdb2/store/DatasetControlNone.java b/tdb2/src/main/java/org/seaborne/tdb2/store/DatasetControlNone.java
new file mode 100644
index 0000000..b1bf51b
--- /dev/null
+++ b/tdb2/src/main/java/org/seaborne/tdb2/store/DatasetControlNone.java
@@ -0,0 +1,48 @@
+/*
+ *  Licensed 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.
+ *
+ *  See the NOTICE file distributed with this work for additional
+ *  information regarding copyright ownership.
+ */
+
+package org.seaborne.tdb2.store;
+
+import java.util.Iterator ;
+
+/** Concurrency policy  that is a "free for all".
+ *  No checking, no locking.
+ */
+public final class DatasetControlNone implements DatasetControl
+{
+    @Override
+    public void startUpdate()
+    {}
+
+    @Override
+    public void finishUpdate()
+    {}
+
+    @Override
+    public void startRead()
+    {}
+
+    @Override
+    public void finishRead()
+    {}
+
+    @Override
+    public <T> Iterator<T> iteratorControl(Iterator<T> iter)
+    {
+        return iter ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/d72f6961/tdb2/src/main/java/org/seaborne/tdb2/store/DatasetControlReadOnly.java
----------------------------------------------------------------------
diff --git a/tdb2/src/main/java/org/seaborne/tdb2/store/DatasetControlReadOnly.java b/tdb2/src/main/java/org/seaborne/tdb2/store/DatasetControlReadOnly.java
new file mode 100644
index 0000000..85b31be
--- /dev/null
+++ b/tdb2/src/main/java/org/seaborne/tdb2/store/DatasetControlReadOnly.java
@@ -0,0 +1,59 @@
+/*
+ *  Licensed 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.
+ *
+ *  See the NOTICE file distributed with this work for additional
+ *  information regarding copyright ownership.
+ */
+
+package org.seaborne.tdb2.store;
+
+import java.util.Iterator ;
+import java.util.concurrent.atomic.AtomicLong ;
+
+import org.seaborne.tdb2.TDBException ;
+
+/** A policy that provide read-only access */ 
+public class DatasetControlReadOnly implements DatasetControl
+{
+    private final AtomicLong readCounter = new AtomicLong(0) ;
+    
+    public DatasetControlReadOnly()
+    { }
+
+    @Override
+    public void startRead()
+    {
+        readCounter.getAndIncrement() ;
+    }
+
+    @Override
+    public void finishRead()
+    {
+        readCounter.decrementAndGet() ;
+    }
+
+    @Override
+    public void startUpdate()
+    {
+        throw new TDBException("Read-only") ;
+    }
+
+    @Override
+    public void finishUpdate()
+    {
+        throw new TDBException("Read-only") ;
+    }
+
+    @Override
+    public <T> Iterator<T> iteratorControl(Iterator<T> iter) { return iter ; }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/d72f6961/tdb2/src/main/java/org/seaborne/tdb2/store/DatasetPrefixesTDB.java
----------------------------------------------------------------------
diff --git a/tdb2/src/main/java/org/seaborne/tdb2/store/DatasetPrefixesTDB.java b/tdb2/src/main/java/org/seaborne/tdb2/store/DatasetPrefixesTDB.java
index fe21cd7..0b7f072 100644
--- a/tdb2/src/main/java/org/seaborne/tdb2/store/DatasetPrefixesTDB.java
+++ b/tdb2/src/main/java/org/seaborne/tdb2/store/DatasetPrefixesTDB.java
@@ -36,11 +36,8 @@ import org.seaborne.tdb2.store.nodetupletable.NodeTupleTable ;
  */
 public class DatasetPrefixesTDB implements DatasetPrefixStorage
 {
-    /* 
-     * Almost everything is cached in the prefix map asociated with the
-     * graph or dataset so this table is the persistent form and
-     * does not need a cache of it's own.   
-     */
+    // Consider a cache - like PrefixMappingCache was - but needs to respect transactions.
+    // See getPrefixMapping.
     
     static final RecordFactory factory = new RecordFactory(3*NodeId.SIZE, 0) ;
     static final String unamedGraphURI = "" ;
@@ -161,7 +158,6 @@ public class DatasetPrefixesTDB implements DatasetPrefixStorage
     @Override
     public PrefixMapping getPrefixMapping(String graphName) {
         PrefixMapping pm = new GraphPrefixesProjectionTDB(graphName, this) ;
-        pm = new PrefixMappingCache(pm) ;
         return pm ;
     }
     

http://git-wip-us.apache.org/repos/asf/jena/blob/d72f6961/tdb2/src/main/java/org/seaborne/tdb2/store/GraphPrefixesProjectionTDB.java
----------------------------------------------------------------------
diff --git a/tdb2/src/main/java/org/seaborne/tdb2/store/GraphPrefixesProjectionTDB.java b/tdb2/src/main/java/org/seaborne/tdb2/store/GraphPrefixesProjectionTDB.java
index 330ee20..2c50740 100644
--- a/tdb2/src/main/java/org/seaborne/tdb2/store/GraphPrefixesProjectionTDB.java
+++ b/tdb2/src/main/java/org/seaborne/tdb2/store/GraphPrefixesProjectionTDB.java
@@ -28,8 +28,8 @@ import org.apache.jena.sparql.core.DatasetPrefixStorage ;
 public class GraphPrefixesProjectionTDB implements PrefixMapping {
     // Despite the name "TDB" this is general replacement for
     // PrefixMapping over DatasetPrefixStorage.
-    private String graphName ;
-    private DatasetPrefixStorage prefixes ; 
+    private final String graphName ;
+    private final DatasetPrefixStorage prefixes ; 
 
     public GraphPrefixesProjectionTDB(String graphName, DatasetPrefixStorage prefixes)
     { 
@@ -113,10 +113,11 @@ public class GraphPrefixesProjectionTDB implements PrefixMapping {
     @Override
     public String qnameFor(String uri) {
         int split = Util.splitNamespaceXML(uri) ;
-        String ns = uri.substring(0, split), local = uri.substring(split) ;
+        String ns = uri.substring(0, split); 
+        String local = uri.substring(split) ;
         if ( local.equals("") )
             return null ;
-        String prefix = prefixes.readByURI(graphName, uri) ;
+        String prefix = prefixes.readByURI(graphName, ns) ;
         return prefix == null ? null : prefix + ":" + local ;
     }
     

http://git-wip-us.apache.org/repos/asf/jena/blob/d72f6961/tdb2/src/main/java/org/seaborne/tdb2/store/GraphTDB.java
----------------------------------------------------------------------
diff --git a/tdb2/src/main/java/org/seaborne/tdb2/store/GraphTDB.java b/tdb2/src/main/java/org/seaborne/tdb2/store/GraphTDB.java
index fc9c97f..a1e99ce 100644
--- a/tdb2/src/main/java/org/seaborne/tdb2/store/GraphTDB.java
+++ b/tdb2/src/main/java/org/seaborne/tdb2/store/GraphTDB.java
@@ -87,7 +87,7 @@ public class GraphTDB extends GraphView implements Closeable, Sync {
         if ( iterQuads == null )
             return org.apache.jena.util.iterator.NullIterator.instance() ;
         // Can't be duplicates - fixed graph node..
-        Iterator<Triple> iterTriples = new ProjectQuadsToTriples(Quad.defaultGraphIRI, iterQuads) ;
+        Iterator<Triple> iterTriples = projectQuadsToTriples(Quad.defaultGraphIRI, iterQuads) ;
         return WrappedIterator.createNoRemove(iterTriples) ;
     }
 
@@ -97,12 +97,11 @@ public class GraphTDB extends GraphView implements Closeable, Sync {
         if ( isUnionGraph(gn) )
             gn = Node.ANY ;
 
-        Iterator<Quad> iter = dataset.getQuadTable().find(gn, m.getMatchSubject(), m.getMatchPredicate(),
-                                                          m.getMatchObject()) ;
+        Iterator<Quad> iter = dataset.getQuadTable().find(gn, m.getMatchSubject(), m.getMatchPredicate(), m.getMatchObject()) ;
         if ( iter == null )
             return org.apache.jena.util.iterator.NullIterator.instance() ;
 
-        Iterator<Triple> iterTriples = new ProjectQuadsToTriples((gn == Node.ANY ? null : gn), iter) ;
+        Iterator<Triple> iterTriples = projectQuadsToTriples((gn == Node.ANY ? null : gn), iter) ;
 
         if ( gn == Node.ANY )
             iterTriples = Iter.distinct(iterTriples) ;
@@ -142,93 +141,18 @@ public class GraphTDB extends GraphView implements Closeable, Sync {
 			throw new TDBException("Expected a Tuple of 4, got: " + item);
 		return TupleFactory.tuple(item.get(1), item.get(2), item.get(3));
 	};
-
-    // Convert from Iterator<Quad> to Iterator<Triple>
-    static class ProjectQuadsToTriples implements Iterator<Triple> {
-        private final Iterator<Quad> iter ;
-        private final Node           graphNode ;
-
-        /**
-         * Project quads to triples - check the graphNode is as expected if not
-         * null
-         */
-        ProjectQuadsToTriples(Node graphNode, Iterator<Quad> iter) {
-            this.graphNode = graphNode ;
-            this.iter = iter ;
-        }
-
-        @Override
-        public boolean hasNext() {
-            return iter.hasNext() ;
-        }
-
-        @Override
-        public Triple next() {
-            Quad q = iter.next() ;
+	
+	private static Iterator<Triple> projectQuadsToTriples(Node graphNode, Iterator<Quad> iter) {
+	    // Checking.
+        Function<Quad, Triple> f = (q) -> {
             if ( graphNode != null && !q.getGraph().equals(graphNode) )
-                throw new InternalError("ProjectQuadsToTriples: Quads from unexpected graph (expected=" + graphNode
-                                        + ", got=" + q.getGraph() + ")") ;
-            return q.asTriple() ;
-        }
-
-        @Override
-        public void remove() {
-            iter.remove() ;
-        }
-    }
-
-    @Override
-    public Capabilities getCapabilities() {
-        if ( capabilities == null )
-            capabilities = new Capabilities() {
-                @Override
-                public boolean sizeAccurate() {
-                    return true ;
-                }
-
-                @Override
-                public boolean addAllowed() {
-                    return true ;
-                }
-
-                @Override
-                public boolean addAllowed(boolean every) {
-                    return true ;
-                }
-
-                @Override
-                public boolean deleteAllowed() {
-                    return true ;
-                }
-
-                @Override
-                public boolean deleteAllowed(boolean every) {
-                    return true ;
-                }
-
-                @Override
-                public boolean canBeEmpty() {
-                    return true ;
-                }
-
-                @Override
-                public boolean iteratorRemoveAllowed() {
-                    return false ;
-                } /* ** */
-
-                @Override
-                public boolean findContractSafe() {
-                    return true ;
-                }
-
-                @Override
-                public boolean handlesLiteralTyping() {
-                    return false ;
-                } /* ** */
-            } ;
-
-        return super.getCapabilities() ;
-    }
+                throw new InternalError("projectQuadsToTriples: Quads from unexpected graph (expected=" + graphNode + ", got=" + q.getGraph() + ")");
+            return q.asTriple();
+        };
+        // Without.
+        //Function<Quad, Triple> f = (q) -> q.asTriple();
+	    return Iter.map(iter, f);
+	}
 
     @Override
     public TransactionHandler getTransactionHandler() {

http://git-wip-us.apache.org/repos/asf/jena/blob/d72f6961/tdb2/src/main/java/org/seaborne/tdb2/store/PrefixMappingCache.java
----------------------------------------------------------------------
diff --git a/tdb2/src/main/java/org/seaborne/tdb2/store/PrefixMappingCache.java b/tdb2/src/main/java/org/seaborne/tdb2/store/PrefixMappingCache.java
index 5fbe278..ca950d7 100644
--- a/tdb2/src/main/java/org/seaborne/tdb2/store/PrefixMappingCache.java
+++ b/tdb2/src/main/java/org/seaborne/tdb2/store/PrefixMappingCache.java
@@ -21,12 +21,13 @@ import java.util.Map ;
 import java.util.Optional ;
 import java.util.Map.Entry ;
 
+import org.apache.jena.atlas.iterator.Iter;
 import org.apache.jena.atlas.lib.Cache ;
 import org.apache.jena.atlas.lib.CacheFactory ;
 import org.apache.jena.rdf.model.impl.Util ;
 import org.apache.jena.shared.PrefixMapping ;
 
-// Non-transactional cache.
+// Unsafe. Non-transactional cache.
 public class PrefixMappingCache implements PrefixMapping {
 
     private final PrefixMapping other ;
@@ -121,6 +122,7 @@ public class PrefixMappingCache implements PrefixMapping {
 
     @Override
     public Map<String, String> getNsPrefixMap() {
+        // Ignore cache - get everything from the provider.
         return other.getNsPrefixMap() ;
     }
 
@@ -128,22 +130,21 @@ public class PrefixMappingCache implements PrefixMapping {
     // Libraryize?
     @Override
     public String expandPrefix(String prefixed) {
-        {
-            int colon = prefixed.indexOf(':') ;
-            if ( colon < 0 )
-                return prefixed ;
-            else {
-                String prefix = prefixed.substring(0, colon) ;
-                String uri = getNsPrefixURI(prefix) ;
-                return uri == null ? prefixed : uri + prefixed.substring(colon + 1) ;
-            }
+        int colon = prefixed.indexOf(':');
+        if ( colon < 0 )
+            return prefixed;
+        else {
+            String prefix = prefixed.substring(0, colon);
+            String uri = getNsPrefixURI(prefix);
+            return uri == null ? prefixed : uri + prefixed.substring(colon + 1);
         }
     }
 
     @Override
     public String qnameFor(String uri) {
         int split = Util.splitNamespaceXML(uri) ;
-        String ns = uri.substring(0, split), local = uri.substring(split) ;
+        String ns = uri.substring(0, split);
+        String local = uri.substring(split) ;
         if ( local.equals("") )
             return null ;
         String prefix = getNsURIPrefix(ns) ;
@@ -187,5 +188,15 @@ public class PrefixMappingCache implements PrefixMapping {
         return other.numPrefixes() ;
     }
 
+    private static String str(PrefixMapping pmap) {
+        return pmap.getNsPrefixMap().toString();
+    }
+    
+    @Override
+    public String toString() {
+        // Problem : only prints the cache.
+        String x = Iter.iter(prefixToUri.keys()).map(k->k+"->"+prefixToUri.getIfPresent(k)).asString(", ");
+        return "pm cache: ["+prefixToUri.size()+"] "+x+" : " + str(other);
+    }
 }
 

http://git-wip-us.apache.org/repos/asf/jena/blob/d72f6961/tdb2/src/main/java/org/seaborne/tdb2/store/QuadTable.java
----------------------------------------------------------------------
diff --git a/tdb2/src/main/java/org/seaborne/tdb2/store/QuadTable.java b/tdb2/src/main/java/org/seaborne/tdb2/store/QuadTable.java
index 97dc784..15a14ff 100644
--- a/tdb2/src/main/java/org/seaborne/tdb2/store/QuadTable.java
+++ b/tdb2/src/main/java/org/seaborne/tdb2/store/QuadTable.java
@@ -27,7 +27,6 @@ import org.apache.jena.sparql.core.Quad ;
 import org.seaborne.tdb2.lib.TupleLib ;
 import org.seaborne.tdb2.store.nodetable.NodeTable ;
 import org.seaborne.tdb2.store.tupletable.TupleIndex ;
-import org.seaborne.tdb2.sys.DatasetControl ;
 
 /**
  * Quad table - a collection of TupleIndexes for 4-tuples together with a node

http://git-wip-us.apache.org/repos/asf/jena/blob/d72f6961/tdb2/src/main/java/org/seaborne/tdb2/store/TableBase.java
----------------------------------------------------------------------
diff --git a/tdb2/src/main/java/org/seaborne/tdb2/store/TableBase.java b/tdb2/src/main/java/org/seaborne/tdb2/store/TableBase.java
index 81a9593..05b80ab 100644
--- a/tdb2/src/main/java/org/seaborne/tdb2/store/TableBase.java
+++ b/tdb2/src/main/java/org/seaborne/tdb2/store/TableBase.java
@@ -23,7 +23,6 @@ import org.seaborne.tdb2.store.nodetable.NodeTable ;
 import org.seaborne.tdb2.store.nodetupletable.NodeTupleTable ;
 import org.seaborne.tdb2.store.nodetupletable.NodeTupleTableConcrete ;
 import org.seaborne.tdb2.store.tupletable.TupleIndex ;
-import org.seaborne.tdb2.sys.DatasetControl ;
 
 public class TableBase implements Sync, Closeable
 {

http://git-wip-us.apache.org/repos/asf/jena/blob/d72f6961/tdb2/src/main/java/org/seaborne/tdb2/store/TripleTable.java
----------------------------------------------------------------------
diff --git a/tdb2/src/main/java/org/seaborne/tdb2/store/TripleTable.java b/tdb2/src/main/java/org/seaborne/tdb2/store/TripleTable.java
index 2f17208..8e5aeb9 100644
--- a/tdb2/src/main/java/org/seaborne/tdb2/store/TripleTable.java
+++ b/tdb2/src/main/java/org/seaborne/tdb2/store/TripleTable.java
@@ -26,7 +26,6 @@ import org.apache.jena.graph.Triple ;
 import org.seaborne.tdb2.lib.TupleLib ;
 import org.seaborne.tdb2.store.nodetable.NodeTable ;
 import org.seaborne.tdb2.store.tupletable.TupleIndex ;
-import org.seaborne.tdb2.sys.DatasetControl ;
 
 /**
  * TripleTable - a collection of TupleIndexes for 3-tuples together with a node

http://git-wip-us.apache.org/repos/asf/jena/blob/d72f6961/tdb2/src/main/java/org/seaborne/tdb2/store/nodetable/TReadAppendFileTransport.java
----------------------------------------------------------------------
diff --git a/tdb2/src/main/java/org/seaborne/tdb2/store/nodetable/TReadAppendFileTransport.java b/tdb2/src/main/java/org/seaborne/tdb2/store/nodetable/TReadAppendFileTransport.java
index 140047c..34eebfb 100644
--- a/tdb2/src/main/java/org/seaborne/tdb2/store/nodetable/TReadAppendFileTransport.java
+++ b/tdb2/src/main/java/org/seaborne/tdb2/store/nodetable/TReadAppendFileTransport.java
@@ -22,7 +22,7 @@ import java.util.Objects ;
 import org.apache.thrift.transport.TTransport ;
 import org.seaborne.dboe.base.file.BinaryDataFile ;
 
-/** A file transport that support random access read and
+/** A file transport that supports random access read and
  *  buffered append write.
  *  <p>
  *  Adapter TTransport -&gt; BinaryDataFile

http://git-wip-us.apache.org/repos/asf/jena/blob/d72f6961/tdb2/src/main/java/org/seaborne/tdb2/store/nodetupletable/NodeTupleTable.java
----------------------------------------------------------------------
diff --git a/tdb2/src/main/java/org/seaborne/tdb2/store/nodetupletable/NodeTupleTable.java b/tdb2/src/main/java/org/seaborne/tdb2/store/nodetupletable/NodeTupleTable.java
index 4a60695..89139c9 100644
--- a/tdb2/src/main/java/org/seaborne/tdb2/store/nodetupletable/NodeTupleTable.java
+++ b/tdb2/src/main/java/org/seaborne/tdb2/store/nodetupletable/NodeTupleTable.java
@@ -23,10 +23,10 @@ import org.apache.jena.atlas.lib.Closeable ;
 import org.apache.jena.atlas.lib.Sync ;
 import org.apache.jena.atlas.lib.tuple.Tuple ;
 import org.apache.jena.graph.Node ;
+import org.seaborne.tdb2.store.DatasetControl ;
 import org.seaborne.tdb2.store.NodeId ;
 import org.seaborne.tdb2.store.nodetable.NodeTable ;
 import org.seaborne.tdb2.store.tupletable.TupleTable ;
-import org.seaborne.tdb2.sys.DatasetControl ;
 
 public interface NodeTupleTable extends Sync, Closeable
 {

http://git-wip-us.apache.org/repos/asf/jena/blob/d72f6961/tdb2/src/main/java/org/seaborne/tdb2/store/nodetupletable/NodeTupleTableConcrete.java
----------------------------------------------------------------------
diff --git a/tdb2/src/main/java/org/seaborne/tdb2/store/nodetupletable/NodeTupleTableConcrete.java b/tdb2/src/main/java/org/seaborne/tdb2/store/nodetupletable/NodeTupleTableConcrete.java
index 6d92abc..0d967f0 100644
--- a/tdb2/src/main/java/org/seaborne/tdb2/store/nodetupletable/NodeTupleTableConcrete.java
+++ b/tdb2/src/main/java/org/seaborne/tdb2/store/nodetupletable/NodeTupleTableConcrete.java
@@ -27,11 +27,11 @@ import org.apache.jena.atlas.lib.tuple.TupleFactory ;
 import org.apache.jena.graph.Node ;
 import org.seaborne.tdb2.TDBException ;
 import org.seaborne.tdb2.lib.TupleLib ;
+import org.seaborne.tdb2.store.DatasetControl ;
 import org.seaborne.tdb2.store.NodeId ;
 import org.seaborne.tdb2.store.nodetable.NodeTable ;
 import org.seaborne.tdb2.store.tupletable.TupleIndex ;
 import org.seaborne.tdb2.store.tupletable.TupleTable ;
-import org.seaborne.tdb2.sys.DatasetControl ;
 
 /** Group a tuple table and node table together to provide a real NodeTupleTable */
 public class NodeTupleTableConcrete implements NodeTupleTable

http://git-wip-us.apache.org/repos/asf/jena/blob/d72f6961/tdb2/src/main/java/org/seaborne/tdb2/store/nodetupletable/NodeTupleTableWrapper.java
----------------------------------------------------------------------
diff --git a/tdb2/src/main/java/org/seaborne/tdb2/store/nodetupletable/NodeTupleTableWrapper.java b/tdb2/src/main/java/org/seaborne/tdb2/store/nodetupletable/NodeTupleTableWrapper.java
index f47d78e..e54f964 100644
--- a/tdb2/src/main/java/org/seaborne/tdb2/store/nodetupletable/NodeTupleTableWrapper.java
+++ b/tdb2/src/main/java/org/seaborne/tdb2/store/nodetupletable/NodeTupleTableWrapper.java
@@ -21,10 +21,10 @@ import java.util.Iterator ;
 
 import org.apache.jena.atlas.lib.tuple.Tuple ;
 import org.apache.jena.graph.Node ;
+import org.seaborne.tdb2.store.DatasetControl ;
 import org.seaborne.tdb2.store.NodeId ;
 import org.seaborne.tdb2.store.nodetable.NodeTable ;
 import org.seaborne.tdb2.store.tupletable.TupleTable ;
-import org.seaborne.tdb2.sys.DatasetControl ;
 
 public class NodeTupleTableWrapper implements NodeTupleTable
 {

http://git-wip-us.apache.org/repos/asf/jena/blob/d72f6961/tdb2/src/main/java/org/seaborne/tdb2/sys/ConnectionTracker.java
----------------------------------------------------------------------
diff --git a/tdb2/src/main/java/org/seaborne/tdb2/sys/ConnectionTracker.java b/tdb2/src/main/java/org/seaborne/tdb2/sys/ConnectionTracker.java
new file mode 100644
index 0000000..f2950eb
--- /dev/null
+++ b/tdb2/src/main/java/org/seaborne/tdb2/sys/ConnectionTracker.java
@@ -0,0 +1,23 @@
+/*
+ * 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.seaborne.tdb2.sys;
+
+abstract class ConnectionTracker<X> {
+
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/d72f6961/tdb2/src/main/java/org/seaborne/tdb2/sys/DatabaseConnection.java
----------------------------------------------------------------------
diff --git a/tdb2/src/main/java/org/seaborne/tdb2/sys/DatabaseConnection.java b/tdb2/src/main/java/org/seaborne/tdb2/sys/DatabaseConnection.java
new file mode 100644
index 0000000..3fa4983
--- /dev/null
+++ b/tdb2/src/main/java/org/seaborne/tdb2/sys/DatabaseConnection.java
@@ -0,0 +1,164 @@
+/*
+ * 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.seaborne.tdb2.sys;
+
+import java.io.IOException ;
+import java.nio.file.Path ;
+import java.nio.file.Paths ;
+import java.util.Map ;
+import java.util.concurrent.ConcurrentHashMap ;
+
+import org.apache.jena.atlas.io.IO ;
+import org.apache.jena.atlas.lib.FileOps ;
+import org.apache.jena.sparql.core.DatasetGraph ;
+import org.seaborne.dboe.base.file.Location ;
+import org.seaborne.dboe.base.file.ProcessFileLock ;
+import org.seaborne.dboe.sys.Names ;
+import org.seaborne.tdb2.TDBException ;
+import org.seaborne.tdb2.repack.DatasetGraphSwitchable ;
+import org.seaborne.tdb2.setup.StoreParams ;
+
+// StoreConnection, DatabaseConnection < Connection<X> 
+
+public class DatabaseConnection {
+    // ConnectionTracker<X> 
+    
+    private static Map<Location, DatabaseConnection> cache = new ConcurrentHashMap<>() ;
+    
+    /** Get the {@code DatabaseConnection} to a location, 
+     *  creating the storage structures if it does not exist. */ 
+    public synchronized static DatabaseConnection connectCreate(Location location) {
+        return connectCreate(location, null) ;
+    }
+
+    /** Get the {@code DatabaseConnection} to a location, 
+     *  creating the storage structures if it does not exist.
+     *  Use the provided {@link StoreParams} - any persistent setting 
+     *  already at the location take precedence.
+     */ 
+    public synchronized static DatabaseConnection connectCreate(Location location, StoreParams params) {
+        return make(location, params) ;
+    }
+    
+    /**
+     * Return a {@code StoreConnection} for a particular location,
+     * creating it if it does not exist in storage.
+     */
+    private synchronized static DatabaseConnection make(Location location, StoreParams params) {
+        if ( location.isMemUnique() ) {
+            // Uncached, in-memory. 
+            DatasetGraph dsg = DatabaseOps.create(location);
+            DatabaseConnection dbConn = new DatabaseConnection(dsg, location, null);
+            return dbConn;
+        }
+        // Cached by Location. Named in-memory or on-disk.
+        ProcessFileLock lock = null;
+        if (SystemTDB.DiskLocationMultiJvmUsagePrevention && ! location.isMem() ) {
+            lock = lockForLocation(location);
+            // Take the lock.  This is atomic.
+            lock.lockEx();
+        }
+        final ProcessFileLock lock2 = lock;
+        DatabaseConnection dbConn = cache.computeIfAbsent(location, (loc)->buildForCache(loc, lock2, params));
+        return dbConn ;
+    }
+    
+    private static DatabaseConnection buildForCache(Location location,  ProcessFileLock lock, StoreParams params) {
+        if ( location.isMemUnique() ) {
+            throw new TDBException("Can't buildForCache a memory-unique location");
+        }
+        DatasetGraph dsg = DatabaseOps.create(location);
+        return new DatabaseConnection(dsg, location, lock) ;
+    }
+    
+//    private static DatasetGraph buildMem(Location location, StoreParams params) {
+//        return StoreConnection.connectCreate(location, params).getDatasetGraph(); 
+//    }
+//
+//    private static DatasetGraph buildDisk(Location location, StoreParams params) {
+//        return DatabaseOps.create(location);
+//    }
+
+    // DRY
+    /** Create or fetch a {@link ProcessFileLock} for a Location */
+    public static ProcessFileLock lockForLocation(Location location) {
+        FileOps.ensureDir(location.getDirectoryPath());
+        String lockFilename = location.getPath(Names.TDB_LOCK_FILE);
+        Path path = Paths.get(lockFilename);
+        try {
+            path.toFile().createNewFile();
+        } catch(IOException ex) { IO.exception(ex); return null; }
+        return ProcessFileLock.create(lockFilename);
+    }
+    
+    /** Stop managing a location. Use with great care (testing only). */
+    public static synchronized void expel(Location location, boolean force) {
+        DatabaseConnection dbConn = cache.get(location) ;
+        if (dbConn == null) return ;
+        cache.remove(location) ;
+        dbConn.isValid = false;
+        //dbConn.datasetGraph = null;
+        //dbConn.datasetGraphSwitchable = null;
+        StoreConnection.expel(location, force);
+        
+        //    if (!force && sConn.transactionManager.activeTransactions()) 
+        //        throw new TDBTransactionException("Can't expel: Active transactions for location: " + location) ;
+
+        cache.remove(location) ;
+        // Release the lock after the cache is emptied.
+        if (SystemTDB.DiskLocationMultiJvmUsagePrevention && ! location.isMem() ) {
+            if ( ! dbConn.lock.isLockedHere() )
+                SystemTDB.errlog.warn("Location " + location.getDirectoryPath() + " was not locked by this process.");
+            dbConn.lock.unlock();
+            ProcessFileLock.release(dbConn.lock);
+        }
+    }
+    
+    // One of the other.
+    private final DatasetGraphSwitchable   datasetGraphSwitchable;
+    private final DatasetGraph             datasetGraph;
+    // This is the location of the TDB2 container directory. 
+    private final Location          location ;
+    private final ProcessFileLock   lock ;
+    private boolean                 isValid = true ;
+    
+    private DatabaseConnection(DatasetGraph dsg, Location location, ProcessFileLock fileLock)
+    {
+        this.datasetGraph = dsg;
+        this.datasetGraphSwitchable =  ( dsg instanceof DatasetGraphSwitchable ) ? (DatasetGraphSwitchable )dsg : null;
+        this.location = location ;
+        this.lock = fileLock;
+    }
+
+    public DatasetGraph getDatasetGraph() {
+        return datasetGraph;
+    }
+
+    public DatasetGraphSwitchable getDatasetGraphSwitchable() {
+        return datasetGraphSwitchable;
+    }
+    
+    public Location getLocation() {
+        return location ;
+    }
+    
+    public ProcessFileLock getLock() {
+        return lock ;
+    }
+}