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/10/03 19:34:02 UTC

[16/65] [abbrv] jena git commit: JENA-1397: Rename java packages

http://git-wip-us.apache.org/repos/asf/jena/blob/3d456654/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetable/NodeTableOps.java
----------------------------------------------------------------------
diff --git a/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetable/NodeTableOps.java b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetable/NodeTableOps.java
new file mode 100644
index 0000000..0fcc559
--- /dev/null
+++ b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetable/NodeTableOps.java
@@ -0,0 +1,51 @@
+/*
+ * 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.tdb2.store.nodetable;
+
+import java.util.ArrayList ;
+import java.util.List ;
+
+import org.apache.jena.graph.Node ;
+import org.apache.jena.tdb2.store.NodeId;
+
+public class NodeTableOps {
+    
+    /** Convert a bulk operation into a loop */  
+    public static List<NodeId> bulkNodeToNodeIdImpl(NodeTable nt, List<Node> nodes, boolean withAllocation) {
+        List<NodeId> nodeIds = new ArrayList<>(nodes.size()) ;
+        for ( Node node : nodes ) {
+            NodeId nid = withAllocation ? nt.getAllocateNodeId(node) : nt.getNodeIdForNode(node) ;
+            nodeIds.add(nid) ;
+        }
+        return nodeIds ;
+    }
+    
+    /** Convert a bulk operation into a loop */  
+    public static List<Node> bulkNodeIdToNodeImpl(NodeTable nt, List<NodeId> nodeIds) {
+        List<Node> nodes = new ArrayList<>(nodeIds.size()) ;
+        for ( NodeId nodeId : nodeIds ) {
+            Node n = nt.getNodeForNodeId(nodeId) ;
+            nodes.add(n) ;
+        }
+        return nodes ;
+    }
+    
+
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/3d456654/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetable/NodeTableTRDF.java
----------------------------------------------------------------------
diff --git a/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetable/NodeTableTRDF.java b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetable/NodeTableTRDF.java
new file mode 100644
index 0000000..6dec310
--- /dev/null
+++ b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetable/NodeTableTRDF.java
@@ -0,0 +1,109 @@
+/*
+ * 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.tdb2.store.nodetable ;
+
+import org.apache.jena.atlas.logging.Log ;
+import org.apache.jena.dboe.base.file.BinaryDataFile;
+import org.apache.jena.dboe.index.Index;
+import org.apache.jena.graph.Node ;
+import org.apache.jena.riot.thrift.RiotThriftException ;
+import org.apache.jena.riot.thrift.TRDF ;
+import org.apache.jena.riot.thrift.ThriftConvert ;
+import org.apache.jena.riot.thrift.wire.RDF_Term ;
+import org.apache.jena.tdb2.TDBException;
+import org.apache.jena.tdb2.store.NodeId;
+import org.apache.jena.tdb2.store.NodeIdFactory;
+import org.apache.thrift.TException ;
+import org.apache.thrift.protocol.TProtocol ;
+
+/** NodeTable using Thrift for the I/O and storage. */
+
+public class NodeTableTRDF extends NodeTableNative {
+    // Write buffering is done in the underlying BinaryDataFile
+    BinaryDataFile diskFile ;
+    private TReadAppendFileTransport transport ;
+    private final TProtocol protocol ;
+
+    public NodeTableTRDF(Index nodeToId, BinaryDataFile objectFile) {
+        super(nodeToId) ;
+        try {
+            this.diskFile = objectFile ;
+            transport = new TReadAppendFileTransport(diskFile) ;
+            if ( ! transport.isOpen() )
+                transport.open(); 
+            this.protocol = TRDF.protocol(transport) ;
+        }
+        catch (Exception ex) {
+            throw new TDBException("NodeTableTRDF", ex) ;
+        }
+    }
+
+    @Override
+    protected NodeId writeNodeToTable(Node node) {
+        RDF_Term term = ThriftConvert.convert(node, true) ;
+        try {
+            long x = diskFile.length() ;
+            // Paired : [*]
+            NodeId nid = NodeIdFactory.createPtr(x);
+            term.write(protocol) ;
+            //transport.flush() ;
+            return nid ;
+        }
+        catch (Exception ex) {
+            throw new TDBException("NodeTableThrift/Write", ex) ;
+        }
+    }
+
+    @Override
+    protected Node readNodeFromTable(NodeId id) {
+        try {
+            // Paired : [*]
+            long x = id.getPtrLocation();
+            transport.readPosition(x) ;
+            RDF_Term term = new RDF_Term() ;
+            term.read(protocol) ;
+            Node n = ThriftConvert.convert(term) ;
+            return n ;
+        }
+        catch (TException ex) {
+            throw new TDBException("NodeTableTRDF/Read", ex) ;
+        }
+        catch (RiotThriftException ex) {
+            Log.error(this, "Bad encoding: NodeId = "+id) ;
+            throw ex ;
+        }
+    }
+
+    @Override
+    protected void syncSub() {
+        try { transport.flush(); }
+        catch (Exception ex) { throw new TDBException("NodeTableTRDF", ex) ; }
+    }
+
+    @Override
+    protected void closeSub() {
+        if ( transport.isOpen() ) {
+            try { transport.close() ; }
+            catch (Exception ex) { throw new TDBException("NodeTableTRDF", ex) ; }
+        }
+    }
+
+    public Index getIndex()             { return nodeHashToId ; }
+    public BinaryDataFile getData()     { return diskFile ; }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/jena/blob/3d456654/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetable/NodeTableWrapper.java
----------------------------------------------------------------------
diff --git a/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetable/NodeTableWrapper.java b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetable/NodeTableWrapper.java
new file mode 100644
index 0000000..c847135
--- /dev/null
+++ b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetable/NodeTableWrapper.java
@@ -0,0 +1,94 @@
+/*
+ * 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.tdb2.store.nodetable ;
+
+import java.util.Iterator ;
+import java.util.List ;
+
+import org.apache.jena.atlas.lib.Pair ;
+import org.apache.jena.graph.Node ;
+import org.apache.jena.tdb2.store.NodeId;
+
+public class NodeTableWrapper implements NodeTable {
+    protected final NodeTable nodeTable ;
+
+    @Override
+    public final NodeTable wrapped() {
+        return nodeTable ;
+    }
+
+    protected NodeTableWrapper(NodeTable nodeTable) {
+        this.nodeTable = nodeTable ;
+    }
+
+    @Override
+    public NodeId getAllocateNodeId(Node node) {
+        return nodeTable.getAllocateNodeId(node) ;
+    }
+
+    @Override
+    public NodeId getNodeIdForNode(Node node) {
+        return nodeTable.getNodeIdForNode(node) ;
+    }
+
+    @Override
+    public Node getNodeForNodeId(NodeId id) {
+        return nodeTable.getNodeForNodeId(id) ;
+    }
+
+    @Override
+    public boolean containsNode(Node node) {
+        return nodeTable.containsNode(node) ;
+    }
+
+    @Override
+    public boolean containsNodeId(NodeId nodeId) {
+        return nodeTable.containsNodeId(nodeId) ;
+    }
+
+    @Override
+    public List<NodeId> bulkNodeToNodeId(List<Node> nodes, boolean withAllocation) {
+        return nodeTable.bulkNodeToNodeId(nodes, withAllocation) ;
+    }
+
+    @Override
+    public List<Node> bulkNodeIdToNode(List<NodeId> nodeIds) {
+        return nodeTable.bulkNodeIdToNode(nodeIds) ;
+    }
+
+    @Override
+    public Iterator<Pair<NodeId, Node>> all() {
+        return nodeTable.all() ;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return nodeTable.isEmpty() ;
+    }
+
+    @Override
+    public void sync() {
+        nodeTable.sync() ;
+    }
+
+    @Override
+    public void close() {
+        nodeTable.close() ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/3d456654/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetable/TReadAppendFileTransport.java
----------------------------------------------------------------------
diff --git a/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetable/TReadAppendFileTransport.java b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetable/TReadAppendFileTransport.java
new file mode 100644
index 0000000..ba9e001
--- /dev/null
+++ b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetable/TReadAppendFileTransport.java
@@ -0,0 +1,86 @@
+/*
+ * 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.tdb2.store.nodetable;
+
+import java.util.Objects ;
+
+import org.apache.jena.dboe.base.file.BinaryDataFile;
+import org.apache.thrift.transport.TTransport ;
+
+/** A file transport that supports random access read and
+ *  buffered append write.
+ *  <p>
+ *  Adapter TTransport -&gt; BinaryDataFile
+ */
+public class TReadAppendFileTransport extends TTransport {
+    private BinaryDataFile file ;
+    private long readPosn = -1 ;
+    
+    public TReadAppendFileTransport(BinaryDataFile file) {
+        Objects.requireNonNull(file) ;
+        this.file = file ;
+    }
+    
+    @Override
+    public boolean isOpen() {
+        return file.isOpen() ;
+    }
+
+    @Override
+    public void open() {
+        file.open() ; 
+    }
+
+    @Override
+    public void close() {
+        file.close() ; 
+    }
+    
+    public void truncate(long posn) {
+        file.truncate(posn); 
+    }
+
+    public BinaryDataFile getBinaryDataFile() { return file ; }
+
+    public long readPosition() {
+        return readPosn ;
+    }
+
+    public void readPosition(long posn) {
+        readPosn = posn ;
+    }
+    
+    @Override
+    public int read(byte[] buf, int off, int len) {
+        int x = file.read(readPosn, buf, off, len) ;
+        readPosn += x ;
+        return x ;
+    }
+
+    @Override
+    public void write(byte[] buf, int off, int len) {
+        file.write(buf, off, len) ;
+    }
+    
+    @Override
+    public void flush()  {
+        file.sync(); 
+    }
+}
+

http://git-wip-us.apache.org/repos/asf/jena/blob/3d456654/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetupletable/NodeTupleTable.java
----------------------------------------------------------------------
diff --git a/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetupletable/NodeTupleTable.java b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetupletable/NodeTupleTable.java
new file mode 100644
index 0000000..ef610a1
--- /dev/null
+++ b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetupletable/NodeTupleTable.java
@@ -0,0 +1,72 @@
+/*
+ * 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.tdb2.store.nodetupletable ;
+
+import java.util.Iterator ;
+
+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.apache.jena.tdb2.store.NodeId;
+import org.apache.jena.tdb2.store.nodetable.NodeTable;
+import org.apache.jena.tdb2.store.tupletable.TupleTable;
+
+public interface NodeTupleTable extends Sync, Closeable
+{
+    public void addRow(Node... nodes) ;
+
+    public void deleteRow(Node... nodes) ;
+
+    /** Find by node. */
+    public Iterator<Tuple<Node>> find(Node... nodes) ;
+
+    /** Find by node - return an iterator of NodeIds. Can return "null" for not found as well as NullIterator */
+    public Iterator<Tuple<NodeId>> findAsNodeIds(Node... nodes) ;
+
+    /** Find by NodeId. */
+    public Iterator<Tuple<NodeId>> find(NodeId... ids) ;
+    
+    /** Find by NodeId. */
+    public Iterator<Tuple<NodeId>> find(Tuple<NodeId> ids) ;
+
+    /** Find all tuples */ 
+    public Iterator<Tuple<NodeId>> findAll() ;
+
+    /** Return the undelying tuple table - used with great care by tools
+     * that directly manipulate internal structures. 
+     */
+    public TupleTable getTupleTable() ;
+
+    /** Return the node table */
+    public NodeTable getNodeTable() ;
+
+    public boolean isEmpty() ;
+    
+    /** Clear the tuple table.  After this operation, find* will find  nothing.
+     * This does not mean all data has been removed - for example, it does not mean
+     * that any node table has been emptied.
+     */
+    public void clear() ;
+
+    // No clear operation - need to manage the tuple table 
+    // and node tables separately.
+    
+    public long size() ;
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/3d456654/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetupletable/NodeTupleTableConcrete.java
----------------------------------------------------------------------
diff --git a/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetupletable/NodeTupleTableConcrete.java b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetupletable/NodeTupleTableConcrete.java
new file mode 100644
index 0000000..55671f7
--- /dev/null
+++ b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetupletable/NodeTupleTableConcrete.java
@@ -0,0 +1,241 @@
+/*
+ * 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.tdb2.store.nodetupletable ;
+
+import static java.lang.String.format ;
+
+import java.util.Iterator ;
+
+import org.apache.jena.atlas.iterator.Iter ;
+import org.apache.jena.atlas.lib.tuple.Tuple ;
+import org.apache.jena.atlas.lib.tuple.TupleFactory ;
+import org.apache.jena.graph.Node ;
+import org.apache.jena.tdb2.TDBException;
+import org.apache.jena.tdb2.lib.TupleLib;
+import org.apache.jena.tdb2.store.NodeId;
+import org.apache.jena.tdb2.store.nodetable.NodeTable;
+import org.apache.jena.tdb2.store.tupletable.TupleIndex;
+import org.apache.jena.tdb2.store.tupletable.TupleTable;
+
+/** Group a tuple table and node table together to provide a real NodeTupleTable */
+public class NodeTupleTableConcrete implements NodeTupleTable
+{
+    protected final NodeTable  nodeTable ;
+    protected final TupleTable tupleTable ;
+
+    /*
+     * Concurrency checking: Everything goes through one of 
+     * addRow, deleteRow or find*
+     */
+
+    public NodeTupleTableConcrete(int N, TupleIndex[] indexes, NodeTable nodeTable)
+    {
+        if (indexes.length == 0 || indexes[0] == null) throw new TDBException("A primary index is required") ;
+        for (TupleIndex index : indexes)
+        {
+            if (N != index.getTupleLength())
+                throw new TDBException(format("Inconsistent: TupleTable width is %d but index %s is %d",
+                                              N, index.getMappingStr(), index.getTupleLength())) ;
+        }
+
+        this.tupleTable = new TupleTable(N, indexes) ;
+        this.nodeTable = nodeTable ;
+    }
+
+    private void startWrite()   { }
+
+    private void finishWrite()  { }
+
+    private void startRead()    { }
+
+    private void finishRead()   { }
+
+    @Override
+    public void addRow(Node... nodes)
+    {
+        try  {
+            startWrite() ;
+            NodeId n[] = new NodeId[nodes.length] ;
+            for (int i = 0; i < nodes.length; i++)
+                n[i] = nodeTable.getAllocateNodeId(nodes[i]) ;
+
+            Tuple<NodeId> t = TupleFactory.create(n) ;
+            tupleTable.add(t) ;
+        } finally 
+        {
+            finishWrite() ;
+        }
+    }
+
+    @Override
+    public void deleteRow(Node... nodes)
+    {
+        try
+        {
+            startWrite() ;
+            NodeId n[] = new NodeId[nodes.length] ;
+            for (int i = 0; i < nodes.length; i++)
+            {
+                NodeId id = idForNode(nodes[i]) ;
+                if (NodeId.isDoesNotExist(id)) return ;
+                n[i] = id ;
+            }
+
+            Tuple<NodeId> t = TupleFactory.create(n) ;
+            tupleTable.delete(t) ;
+        } finally
+        {
+            finishWrite() ;
+        }
+    }
+
+    /** Find by node. */
+    @Override
+    public Iterator<Tuple<Node>> find(Node... nodes)
+    {
+        try {
+            startRead() ;
+            Iterator<Tuple<NodeId>> iter1 = findAsNodeIds(nodes) ; // **public call
+            if (iter1 == null) 
+                return Iter.nullIterator() ;
+            Iterator<Tuple<Node>> iter2 = TupleLib.convertToNodes(nodeTable, iter1) ;
+            return iteratorControl(iter2) ;
+        } finally { finishRead() ; }
+    }
+
+    /**
+     * Find by node - return an iterator of NodeIds. 
+     * Can return "null" (when a node is known to be unknown)
+     * for not found as well as NullIterator (when
+     * no tuples are found (unknown unknown).
+     */
+    @Override
+    public Iterator<Tuple<NodeId>> findAsNodeIds(Node... nodes)
+    {
+        NodeId n[] = new NodeId[nodes.length] ;
+        try {
+            startRead() ;
+            for (int i = 0; i < nodes.length; i++)
+            {
+                NodeId id = idForNode(nodes[i]) ;
+                if (NodeId.isDoesNotExist(id)) 
+                    return Iter.nullIterator() ;
+                n[i] = id ;
+            }
+            return find(n) ; // **public call
+        } finally { finishRead() ; }
+    }
+
+    /** Find by NodeId. */
+    @Override
+    public Iterator<Tuple<NodeId>> find(NodeId... ids)
+    {
+        Tuple<NodeId> tuple = TupleFactory.create(ids) ;
+        return find(tuple) ;
+    }
+
+    /** Find by NodeId. */
+    @Override
+    public Iterator<Tuple<NodeId>> find(Tuple<NodeId> tuple)
+    {
+        // All find/*, except findAll, comes through this operation so startRead/finishRead/checkIterator only needs to happen here.
+        try {
+            startRead() ;
+            // find worker - need also protect iterators that access the node table.
+            Iterator<Tuple<NodeId>> iter = tupleTable.find(tuple) ;
+            return iteratorControl(iter) ;
+        } finally { finishRead() ; }
+    }
+
+    @Override
+    public Iterator<Tuple<NodeId>> findAll()
+    {
+        try {
+            startRead() ;
+            return iteratorControl(tupleTable.getIndex(0).all()) ;
+        } finally { finishRead() ; }
+    }
+
+    // ==== Node
+
+    protected final NodeId idForNode(Node node) {
+        if ( node == null || node == Node.ANY )
+            return NodeId.NodeIdAny ;
+        if ( node.isVariable() )
+            throw new TDBException("Can't pass variables to NodeTupleTable.find*") ;
+        return nodeTable.getNodeIdForNode(node) ;
+    }
+
+    // ==== Accessors
+
+    /**
+     * Return the undelying tuple table - used with great care by tools that directly
+     * manipulate internal structures.
+     */
+    @Override
+    public final TupleTable getTupleTable() {
+        return tupleTable ;
+    }
+
+    /** Return the node table */
+    @Override
+    public final NodeTable getNodeTable() {
+        return nodeTable ;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return tupleTable.isEmpty() ;
+    }
+
+    /** Clear the tuple table - does not clear the node table */
+    @Override
+    public void clear() {
+        try {
+            startWrite() ;
+            tupleTable.clear() ;
+        } finally { finishWrite() ; }
+    }
+
+    @Override
+    public long size() {
+        return tupleTable.size() ;
+    }
+
+    @Override
+    public final void close() {
+        try {
+            startWrite() ;
+            tupleTable.close() ;
+            nodeTable.close() ;
+        } finally { finishWrite() ; }
+    }
+
+    @Override
+    public final void sync() {
+        try {
+            startWrite() ;
+            tupleTable.sync() ;
+            nodeTable.sync() ;
+        } finally { finishWrite() ; }
+    }
+
+    // Add any iterator checking.
+    private <T> Iterator<T> iteratorControl(Iterator<T> iter) { return iter; }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/3d456654/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetupletable/NodeTupleTableView.java
----------------------------------------------------------------------
diff --git a/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetupletable/NodeTupleTableView.java b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetupletable/NodeTupleTableView.java
new file mode 100644
index 0000000..7f65221
--- /dev/null
+++ b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetupletable/NodeTupleTableView.java
@@ -0,0 +1,113 @@
+/*
+ * 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.tdb2.store.nodetupletable;
+
+import java.util.Iterator ;
+
+import org.apache.jena.atlas.lib.ArrayUtils ;
+import org.apache.jena.atlas.lib.tuple.Tuple ;
+import org.apache.jena.graph.Node ;
+import org.apache.jena.tdb2.store.NodeId;
+import org.apache.jena.tdb2.store.tupletable.TupleTable;
+
+/** (Read-only?) projection of another NodeTupleTable. 
+ * This will not reduce a N-wide tuple to N-1 when find*() used. 
+ */ 
+public class NodeTupleTableView extends NodeTupleTableWrapper
+{
+    private Node prefix ;
+    private NodeId prefixId ;
+    //private boolean readOnly = false ;
+
+    public NodeTupleTableView(NodeTupleTable ntt, Node prefix)
+    {
+        super(ntt) ;
+        this.prefix = prefix ;
+        this.prefixId = ntt.getNodeTable().getNodeIdForNode(prefix) ;
+    }
+    
+    @Override
+    public void addRow(Node... nodes)
+    { 
+        nodes = push(Node.class, prefix, nodes) ;
+        super.addRow(nodes) ;
+    }
+
+    @Override
+    public void deleteRow(Node... nodes)
+    {
+        nodes = push(Node.class, prefix, nodes) ;
+        super.deleteRow(nodes) ;
+    }
+    
+    @Override
+    public Iterator<Tuple<Node>> find(Node... nodes)
+    { 
+        nodes = push(Node.class, prefix, nodes) ;
+        return nodeTupleTable.find(nodes) ;
+    }
+    
+    private static <T> T[] push(Class<T> cls, T x,  T[] array)
+    {
+        T[] array2 = ArrayUtils.alloc(cls, array.length+1) ;
+        System.arraycopy(array, 0, array2, 1, array.length) ;
+        array2[0] = x ;
+        return array2 ;
+    }
+
+    private static <T> T[] push(Class<T> cls, T x,  Tuple<T> tuple)
+    {
+        T[] array2 = ArrayUtils.alloc(cls, tuple.len()+1) ;
+        tuple.copyInto(array2, 1);
+        array2[0] = x ;
+        return array2 ;
+    }
+
+    @Override
+    public Iterator<Tuple<NodeId>> find(NodeId... ids)
+    {
+        ids = push(NodeId.class, prefixId, ids) ;
+        return nodeTupleTable.find(ids) ;
+    }
+    
+    @Override
+    public Iterator<Tuple<NodeId>> find(Tuple<NodeId> ids)
+    {
+        NodeId[] ids2 = push(NodeId.class, prefixId, ids) ;
+        return nodeTupleTable.find(ids2) ;
+    }
+
+    @Override
+    public Iterator<Tuple<NodeId>> findAsNodeIds(Node... nodes)
+    {
+        nodes = push(Node.class, prefix, nodes) ;
+        return nodeTupleTable.findAsNodeIds(nodes) ;
+    }
+
+//    @Override
+//    public boolean isReadOnly() { return readOnly ; }
+//
+//    @Override
+//    public void setReadOnly(boolean mode)   { readOnly = mode ; }
+
+    @Override
+    public TupleTable getTupleTable()
+    // Need a projection of this?
+    { return super.getTupleTable() ; }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/3d456654/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetupletable/NodeTupleTableWrapper.java
----------------------------------------------------------------------
diff --git a/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetupletable/NodeTupleTableWrapper.java b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetupletable/NodeTupleTableWrapper.java
new file mode 100644
index 0000000..8fc88a9
--- /dev/null
+++ b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/nodetupletable/NodeTupleTableWrapper.java
@@ -0,0 +1,98 @@
+/*
+ * 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.tdb2.store.nodetupletable;
+
+import java.util.Iterator ;
+
+import org.apache.jena.atlas.lib.tuple.Tuple ;
+import org.apache.jena.graph.Node ;
+import org.apache.jena.tdb2.store.NodeId;
+import org.apache.jena.tdb2.store.nodetable.NodeTable;
+import org.apache.jena.tdb2.store.tupletable.TupleTable;
+
+public class NodeTupleTableWrapper implements NodeTupleTable
+{
+    protected NodeTupleTable nodeTupleTable ;
+
+    public NodeTupleTableWrapper(NodeTupleTable ntt) { 
+        setNodeTupleTable(ntt) ;
+    }
+    
+    protected NodeTupleTable setNodeTupleTable(NodeTupleTable ntt) {
+        NodeTupleTable old = nodeTupleTable ;
+        nodeTupleTable = ntt ;
+        return old ;
+    }
+     
+    @Override
+    public void addRow(Node... nodes)
+    { nodeTupleTable.addRow(nodes) ; }
+
+    @Override
+    public void deleteRow(Node... nodes)
+    { nodeTupleTable.deleteRow(nodes) ; }
+
+    @Override
+    public Iterator<Tuple<Node>> find(Node... nodes)
+    { return nodeTupleTable.find(nodes) ; }
+    
+    @Override
+    public Iterator<Tuple<NodeId>> find(NodeId... ids)
+    { return nodeTupleTable.find(ids) ; }
+    
+    @Override
+    public Iterator<Tuple<NodeId>> find(Tuple<NodeId> tuple)
+    { return nodeTupleTable.find(tuple) ; }
+    
+    @Override
+    public Iterator<Tuple<NodeId>> findAsNodeIds(Node... nodes)
+    { return nodeTupleTable.findAsNodeIds(nodes) ; }
+
+    @Override
+    public Iterator<Tuple<NodeId>> findAll()
+    { return nodeTupleTable.findAll() ; }
+
+    @Override
+    public NodeTable getNodeTable()
+    { return nodeTupleTable.getNodeTable() ; }
+
+    @Override
+    public TupleTable getTupleTable()
+    { return nodeTupleTable.getTupleTable() ; }
+
+    @Override
+    public boolean isEmpty()
+    { return nodeTupleTable.isEmpty() ; }
+    
+    @Override
+    public void clear()
+    { nodeTupleTable.clear(); }
+
+    @Override
+    public long size()
+    { return nodeTupleTable.size() ; }
+
+    @Override
+    public void sync()
+    { nodeTupleTable.sync() ; }
+
+    @Override
+    public void close()
+    { nodeTupleTable.close() ; }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/3d456654/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/tupletable/TupleIndex.java
----------------------------------------------------------------------
diff --git a/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/tupletable/TupleIndex.java b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/tupletable/TupleIndex.java
new file mode 100644
index 0000000..ffb3d86
--- /dev/null
+++ b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/tupletable/TupleIndex.java
@@ -0,0 +1,88 @@
+/*
+ * 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.tdb2.store.tupletable;
+
+import java.util.Collection ;
+import java.util.Iterator ;
+
+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.atlas.lib.tuple.TupleMap ;
+import org.apache.jena.tdb2.store.NodeId;
+
+public interface TupleIndex extends Sync, Closeable
+{
+    /** Insert a tuple */
+    public void add(Tuple<NodeId> tuple) ;
+
+    /** Delete a tuple */
+    public void delete(Tuple<NodeId> tuple) ; 
+
+    /** Insert tuples */
+    public void addAll(Collection<Tuple<NodeId>> tuples) ;
+    
+    /** Delete tuples */
+    public void deleteAll(Collection<Tuple<NodeId>> tuples) ;
+    
+    /** Get a convenient display string for the index - do not rely on the format */ 
+    public String getName() ;
+    
+    /** Get a convenient display string based on the details of the column map - do not rely on the format */ 
+    public String getMappingStr() ;
+    
+    /** Get the mapping of tuples used by this index */  
+    public TupleMap getMapping() ;
+    
+    /** Find all matching tuples - a slot of NodeId.NodeIdAny (or null) means match any.
+     *  Input pattern in natural order, not index order.
+     */
+    public Iterator<Tuple<NodeId>> find(Tuple<NodeId> pattern) ;
+    
+    /** return an iterator of everything */
+    public Iterator<Tuple<NodeId>> all() ;
+    
+    /** Weight a pattern - specified in normal order (not index order).
+     * Large numbers means better match. */
+    public int weight(Tuple<NodeId> pattern) ;
+
+    /** Length of tuple supported */
+    public int getTupleLength() ;
+
+    /** Size of index (number of slots). May be an estimate and not exact. -1 for unknown.  */
+    public long size() ;
+
+    /** Answer whether empty or not */
+    public boolean isEmpty() ;
+    
+    /** Clear the index */
+    public void clear() ;
+    
+    /** Return a TupleIndex if this instance wraps another, else return null */  
+    public TupleIndex wrapped() ; 
+    
+    default public TupleIndex baseTupleIndex() {
+        TupleIndex index = this ;
+        TupleIndex index2 = null ;
+        while( (index2 = index.wrapped()) != null ) {
+            index = index2 ;
+        }
+        return index ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/3d456654/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/tupletable/TupleIndexBase.java
----------------------------------------------------------------------
diff --git a/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/tupletable/TupleIndexBase.java b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/tupletable/TupleIndexBase.java
new file mode 100644
index 0000000..7784a36
--- /dev/null
+++ b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/tupletable/TupleIndexBase.java
@@ -0,0 +1,124 @@
+/*
+ * 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.tdb2.store.tupletable;
+
+import java.util.Iterator ;
+
+import org.apache.jena.atlas.lib.tuple.Tuple ;
+import org.apache.jena.atlas.lib.tuple.TupleMap ;
+import org.apache.jena.tdb2.TDBException;
+import org.apache.jena.tdb2.store.NodeId;
+
+public abstract class TupleIndexBase implements TupleIndex
+{
+    private static final boolean Check = false ;
+
+    protected final TupleMap tupleMap ;
+    protected final int tupleLength ;
+
+    private final String name ;
+    
+    protected TupleIndexBase(int N, TupleMap indexMapping, String name)
+    {
+        this.tupleLength = N ;
+        this.tupleMap = indexMapping ;
+        this.name = name ;
+    }
+    
+    @Override
+    public TupleIndex wrapped() {
+        return null ;
+    }
+    
+    /** Add tuple worker: Tuple passed in unmapped (untouched) order */
+    protected abstract void performAdd(Tuple<NodeId> tuple) ;
+    
+    /** Delete tuple worker: Tuple passed in unmapped (untouched) order */
+    protected abstract void performDelete(Tuple<NodeId> tuple) ;
+    
+    /** Find tuples worker: Tuple passed in unmaped (untouched) order */
+    protected abstract Iterator<Tuple<NodeId>> performFind(Tuple<NodeId> tuple) ;
+
+    /** Insert a tuple */
+    @Override
+    public final void add(Tuple<NodeId> tuple) 
+    { 
+        if ( Check ) {
+            if ( tupleLength != tuple.len() )
+                throw new TDBException(String.format("Mismatch: tuple length %d / index for length %d", tuple.len(), tupleLength));
+        }
+        performAdd(tuple) ;
+    }
+    /** Delete a tuple */
+    @Override
+    public final void delete(Tuple<NodeId> tuple) 
+    { 
+        if ( Check ) {
+            if ( tupleLength != tuple.len() )
+                throw new TDBException(String.format("Mismatch: tuple length %d / index for length %d", tuple.len(), tupleLength));
+        }
+
+        performDelete(tuple) ;
+    }
+
+    /** Find all matching tuples - a slot of NodeId.NodeIdAny (or null) means match any.
+     *  Input pattern in natural order, not index order.
+     */
+    @Override
+    public final Iterator<Tuple<NodeId>> find(Tuple<NodeId> pattern)
+    {
+        if ( Check ) {
+            if ( tupleLength != pattern.len() )
+                throw new TDBException(String.format("Mismatch: tuple length %d / index for length %d", pattern.len(), tupleLength));
+        }
+        // null to NodeId.NodIdAny ??
+        return performFind(pattern) ;
+    }
+    
+    @Override
+    public final int weight(Tuple<NodeId> pattern)
+    {
+        for ( int i = 0 ; i < tupleLength ; i++ )
+        {
+            NodeId X = tupleMap.mapSlot(i, pattern) ;
+            if ( undef(X) )
+                // End of fixed terms
+                return i ;
+        }
+        return tupleLength ;
+    }
+    
+    @Override
+    public final String getMappingStr()     { return tupleMap.getLabel() ; }
+
+    @Override
+    public final String getName()           { return name ; }
+
+    @Override
+    public final int getTupleLength()       { return tupleLength ; }
+
+    @Override
+    public final TupleMap getMapping()      { return tupleMap ;  }
+    
+    protected final boolean undef(NodeId x)
+    { return NodeId.isAny(x) ; }
+    
+    @Override
+    public String toString() { return "index:"+getName() ; }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/3d456654/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/tupletable/TupleIndexRecord.java
----------------------------------------------------------------------
diff --git a/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/tupletable/TupleIndexRecord.java b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/tupletable/TupleIndexRecord.java
new file mode 100644
index 0000000..15d1c40
--- /dev/null
+++ b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/tupletable/TupleIndexRecord.java
@@ -0,0 +1,238 @@
+/*
+ * 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.tdb2.store.tupletable;
+
+import static java.lang.String.format ;
+import static org.apache.jena.tdb2.sys.SystemTDB.SizeOfNodeId;
+
+import java.util.Collection ;
+import java.util.Iterator ;
+import java.util.function.Predicate ;
+
+import org.apache.jena.atlas.iterator.Iter ;
+import org.apache.jena.atlas.iterator.NullIterator ;
+import org.apache.jena.atlas.iterator.SingletonIterator ;
+import org.apache.jena.atlas.lib.tuple.Tuple ;
+import org.apache.jena.atlas.lib.tuple.TupleMap ;
+import org.apache.jena.dboe.base.record.Record;
+import org.apache.jena.dboe.base.record.RecordFactory;
+import org.apache.jena.dboe.index.RangeIndex;
+import org.apache.jena.tdb2.TDBException;
+import org.apache.jena.tdb2.lib.TupleLib;
+import org.apache.jena.tdb2.store.NodeId;
+import org.apache.jena.tdb2.store.NodeIdFactory;
+
+public class TupleIndexRecord extends TupleIndexBase
+{
+    private static final boolean Check = false ;
+    private RangeIndex index ; 
+    private RecordFactory factory ;
+    
+    public TupleIndexRecord(int N,  TupleMap tupleMapping, String name, RecordFactory factory, RangeIndex index)
+    {
+        super(N, tupleMapping, name) ;
+        this.factory = factory ;
+        this.index = index ;
+        
+        if ( factory.keyLength() != N*SizeOfNodeId)
+            throw new TDBException(format("Mismatch: TupleIndex of length %d is not comparative with a factory for key length %d", N, factory.keyLength())) ;
+    }
+    
+    /** Insert a tuple */
+    @Override
+    protected void performAdd(Tuple<NodeId> tuple) { 
+        Record r = TupleLib.record(factory, tuple, tupleMap) ;
+        index.insert(r) ;
+    }
+    
+    /** Delete a tuple */
+    @Override
+    protected void performDelete(Tuple<NodeId> tuple) { 
+        Record r = TupleLib.record(factory, tuple, tupleMap) ;
+        index.delete(r) ;
+    }
+    
+    /** Insert tuples */
+    @Override
+    public void addAll(Collection<Tuple<NodeId>> tuples) {
+        for ( Tuple<NodeId> t : tuples ) 
+            add(t) ;
+    }
+    
+    /** Delete tuples */
+    @Override
+    public void deleteAll(Collection<Tuple<NodeId>> tuples) {
+        for ( Tuple<NodeId> t : tuples ) 
+            delete(t) ;
+    }
+    
+    /** Find all matching tuples - a slot of NodeId.NodeIdAny (or null) means match any.
+     *  Input pattern in natural order, not index order.
+     */
+    
+    @Override
+    protected Iterator<Tuple<NodeId>> performFind(Tuple<NodeId> pattern) {
+        return findOrScan(pattern) ;
+    }
+
+    // Package visibility for testing.
+    final Iterator<Tuple<NodeId>> findOrScan(Tuple<NodeId> pattern) {
+        return findWorker(pattern, true, true) ;
+    }
+    
+    final Iterator<Tuple<NodeId>> findOrPartialScan(Tuple<NodeId> pattern) {
+        return findWorker(pattern, true, false) ;
+    }
+
+    final Iterator<Tuple<NodeId>> findByIndex(Tuple<NodeId> pattern) {
+        return findWorker(pattern, false, false) ;
+    }
+    
+    private Iterator<Tuple<NodeId>> findWorker(Tuple<NodeId> patternNaturalOrder, boolean partialScanAllowed, boolean fullScanAllowed) {
+        if ( Check )
+        {
+            if ( tupleLength != patternNaturalOrder.len() )
+            throw new TDBException(String.format("Mismatch: tuple length %d / index for length %d", patternNaturalOrder.len(), tupleLength)) ;
+        } 
+        
+        // Convert to index order.
+        Tuple<NodeId> pattern = tupleMap.map(patternNaturalOrder) ;
+        
+        // Canonical form.
+        int numSlots = 0 ;
+        int leadingIdx = -2;    // Index of last leading pattern NodeId.  Start less than numSlots-1
+        boolean leading = true ;
+        
+        // Records.
+        Record minRec = factory.createKeyOnly() ;
+        Record maxRec = factory.createKeyOnly() ;
+        
+        // Set the prefixes.
+        for ( int i = 0 ; i < pattern.len() ; i++ ) {
+            NodeId X = pattern.get(i) ;
+            if ( NodeId.isAny(X) ) {
+                X = null ;
+                // No longer seting leading key slots.
+                leading = false ;
+                continue ;
+            }
+//            if ( NodeId.isDoesNotExist(X) )
+//                return Iter.nullIterator();
+
+            numSlots++ ;
+            if ( leading ) {
+                leadingIdx = i ;
+                NodeIdFactory.set(X, minRec.getKey(), i*SizeOfNodeId) ;
+                NodeIdFactory.set(X, maxRec.getKey(), i*SizeOfNodeId) ;
+            }
+        }
+        
+        // Is it a simple existence test?
+        if ( numSlots == pattern.len() ) {
+            if ( index.contains(minRec) )
+                return new SingletonIterator<>(pattern) ;
+            else
+                return new NullIterator<>() ;
+        }
+        
+        Iterator<Record> iter = null ;
+        
+        if ( leadingIdx < 0 ) {
+            if ( ! fullScanAllowed )
+                return null ;
+            //System.out.println("Full scan") ;
+            // Full scan necessary
+            iter = index.iterator() ;
+        } else {
+            // Adjust the maxRec.
+            NodeId X = pattern.get(leadingIdx) ;
+            // Set the max Record to the leading NodeIds, +1.
+            // Example, SP? inclusive to S(P+1)? exclusive where ? is zero. 
+            NodeIdFactory.setNext(X, maxRec.getKey(), leadingIdx*SizeOfNodeId) ;
+            iter = index.iterator(minRec, maxRec) ;
+        }
+        
+        Iterator<Tuple<NodeId>> tuples = Iter.map(iter, item -> TupleLib.tuple(item, tupleMap)) ;
+        
+        if ( leadingIdx < numSlots-1 ) {
+            if ( ! partialScanAllowed )
+                return null ;
+            // Didn't match all defined slots in request.  
+            // Partial or full scan needed.
+            //pattern.unmap(colMap) ;
+            tuples = scan(tuples, patternNaturalOrder) ;
+        }
+        
+        return tuples ;
+    }
+    
+    @Override
+    public Iterator<Tuple<NodeId>> all()
+    {
+        Iterator<Record> iter = index.iterator() ;
+        return Iter.map(iter, item -> TupleLib.tuple(item, tupleMap)) ;
+    }
+    
+    private Iterator<Tuple<NodeId>> scan(Iterator<Tuple<NodeId>> iter, Tuple<NodeId> pattern) {
+        Predicate<Tuple<NodeId>> filter = (item) -> {
+            // Check on pattern and item (both in natural order)
+            for ( int i = 0 ; i < tupleLength ; i++ ) {
+                NodeId n = pattern.get(i) ;
+                // The pattern must be null/Any or match the tuple being tested.
+                if ( ! NodeId.isAny(n) )
+                    if ( ! item.get(i).equals(n) ) 
+                        return false ;
+            }
+            return true ;
+        } ;
+        
+        return Iter.filter(iter, filter) ;
+    }
+    
+    @Override
+    public void close() {
+        index.close() ;
+    }
+
+    @Override
+    public void sync() {
+        index.sync() ;
+    }
+
+    public final RangeIndex getRangeIndex() {
+        return index ;
+    }
+
+    //protected final RecordFactory getRecordFactory()        { return factory ; }
+    
+    @Override
+    public boolean isEmpty() {
+        return index.isEmpty() ;
+    }
+
+    @Override
+    public void clear() {
+        index.clear() ;
+    }
+
+    @Override
+    public long size() {
+        return index.size() ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/3d456654/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/tupletable/TupleIndexRecordAsyncBulkAdd.java
----------------------------------------------------------------------
diff --git a/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/tupletable/TupleIndexRecordAsyncBulkAdd.java b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/tupletable/TupleIndexRecordAsyncBulkAdd.java
new file mode 100644
index 0000000..536c935
--- /dev/null
+++ b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/tupletable/TupleIndexRecordAsyncBulkAdd.java
@@ -0,0 +1,263 @@
+/*
+ * 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.tdb2.store.tupletable;
+
+import static java.lang.String.format ;
+import static org.apache.jena.tdb2.sys.SystemTDB.SizeOfNodeId;
+
+import java.util.Collection ;
+import java.util.Iterator ;
+import java.util.function.Predicate ;
+
+import org.apache.jena.atlas.iterator.Iter ;
+import org.apache.jena.atlas.iterator.NullIterator ;
+import org.apache.jena.atlas.iterator.SingletonIterator ;
+import org.apache.jena.atlas.lib.tuple.Tuple ;
+import org.apache.jena.atlas.lib.tuple.TupleMap ;
+import org.apache.jena.dboe.base.record.Record;
+import org.apache.jena.dboe.base.record.RecordFactory;
+import org.apache.jena.dboe.index.RangeIndex;
+import org.apache.jena.dboe.transaction.txn.Transaction;
+import org.apache.jena.tdb2.TDBException;
+import org.apache.jena.tdb2.lib.Async;
+import org.apache.jena.tdb2.lib.TupleLib;
+import org.apache.jena.tdb2.store.NodeId;
+import org.apache.jena.tdb2.store.NodeIdFactory;
+
+// Async addAll 
+public class TupleIndexRecordAsyncBulkAdd extends TupleIndexBase
+{
+    private static final boolean Check = false ;
+    private RangeIndex index ; 
+    private RecordFactory factory ;
+    
+    public TupleIndexRecordAsyncBulkAdd(int N,  TupleMap tupleMapping, String name, RecordFactory factory, RangeIndex index)
+    {
+        super(N, tupleMapping, name) ;
+        this.factory = factory ;
+        this.index = index ;
+        
+        if ( factory.keyLength() != N*SizeOfNodeId)
+            throw new TDBException(format("Mismatch: TupleIndex of length %d is not comparative with a factory for key length %d", N, factory.keyLength())) ;
+    }
+    
+    /** Insert a tuple */
+    @Override
+    protected void performAdd(Tuple<NodeId> tuple) {
+        switchToSync() ;
+        Record r = TupleLib.record(factory, tuple, tupleMap) ;
+        index.insert(r) ;
+    }
+    
+    /** Delete a tuple */
+    @Override
+    protected void performDelete(Tuple<NodeId> tuple) { 
+        switchToSync() ;
+        Record r = TupleLib.record(factory, tuple, tupleMap) ;
+        index.delete(r) ;
+    }
+    
+    private void switchToSync() {
+        async.completeAsyncOperations();
+    }
+
+    Async async = new Async(1,2) ;
+    Object lock = new Object() ;
+    
+    /** Insert tuples */
+    @Override
+    public void addAll(Collection<Tuple<NodeId>> tuples) {
+        Transaction txn = null ;
+        // pass into async block.
+        
+        async.execAsync(lock, () -> {
+            // Transaction?
+            
+            System.out.println(">>Async") ;
+            for ( Tuple<NodeId> t : tuples ) {
+                Record r = TupleLib.record(factory, t, tupleMap) ;
+                index.insert(r) ;
+            }
+            System.out.println("<<Async") ;
+        }) ;
+    }
+    
+    /** Delete tuples */
+    @Override
+    public void deleteAll(Collection<Tuple<NodeId>> tuples) {
+        tuples.stream().forEach(this::delete);
+    }
+    
+    /** Find all matching tuples - a slot of NodeId.NodeIdAny (or null) means match any.
+     *  Input pattern in natural order, not index order.
+     */
+    
+    @Override
+    protected Iterator<Tuple<NodeId>> performFind(Tuple<NodeId> pattern) {
+        switchToSync() ;
+        return findOrScan(pattern) ;
+    }
+
+    // Package visibility for testing.
+    final Iterator<Tuple<NodeId>> findOrScan(Tuple<NodeId> pattern) {
+        return findWorker(pattern, true, true) ;
+    }
+    
+    final Iterator<Tuple<NodeId>> findOrPartialScan(Tuple<NodeId> pattern) {
+        return findWorker(pattern, true, false) ;
+    }
+
+    final Iterator<Tuple<NodeId>> findByIndex(Tuple<NodeId> pattern) {
+        return findWorker(pattern, false, false) ;
+    }
+    
+    private Iterator<Tuple<NodeId>> findWorker(Tuple<NodeId> patternNaturalOrder, boolean partialScanAllowed, boolean fullScanAllowed) {
+        switchToSync();
+        if ( Check && tupleLength != patternNaturalOrder.len() )
+            throw new TDBException(String.format("Mismatch: tuple length %d / index for length %d", patternNaturalOrder.len(), tupleLength));
+
+        // Convert to index order.
+        Tuple<NodeId> pattern = tupleMap.map(patternNaturalOrder) ;
+        
+        // Canonical form.
+        int numSlots = 0 ;
+        int leadingIdx = -2;    // Index of last leading pattern NodeId.  Start less than numSlots-1
+        boolean leading = true ;
+        
+        // Records.
+        Record minRec = factory.createKeyOnly() ;
+        Record maxRec = factory.createKeyOnly() ;
+        
+        // Set the prefixes.
+        for ( int i = 0 ; i < pattern.len() ; i++ ) {
+            NodeId X = pattern.get(i) ;
+            if ( NodeId.isAny(X) ) {
+                X = null ;
+                // No longer setting leading key slots.
+                leading = false ;
+                continue ;
+            }
+
+            numSlots++ ;
+            if ( leading ) {
+                leadingIdx = i ;
+                NodeIdFactory.set(X, minRec.getKey(), i*SizeOfNodeId) ;
+                NodeIdFactory.set(X, maxRec.getKey(), i*SizeOfNodeId) ;
+            }
+        }
+        
+        // Is it a simple existence test?
+        if ( numSlots == pattern.len() ) {
+            if ( index.contains(minRec) )
+                return new SingletonIterator<>(pattern) ;
+            else
+                return new NullIterator<>() ;
+        }
+        
+        Iterator<Record> iter = null ;
+        
+        if ( leadingIdx < 0 ) {
+            if ( ! fullScanAllowed )
+                return null ;
+            //System.out.println("Full scan") ;
+            // Full scan necessary
+            iter = index.iterator() ;
+        } else {
+            // Adjust the maxRec.
+            NodeId X = pattern.get(leadingIdx) ;
+            // Set the max Record to the leading NodeIds, +1.
+            // Example, SP? inclusive to S(P+1)? exclusive where ? is zero. 
+            NodeIdFactory.setNext(X, maxRec.getKey(), leadingIdx*SizeOfNodeId) ;
+            iter = index.iterator(minRec, maxRec) ;
+        }
+        
+        Iterator<Tuple<NodeId>> tuples = Iter.map(iter, item -> TupleLib.tuple(item, tupleMap)) ;
+        
+        if ( leadingIdx < numSlots-1 ) {
+            if ( ! partialScanAllowed )
+                return null ;
+            // Didn't match all defined slots in request.  
+            // Partial or full scan needed.
+            //pattern.unmap(colMap) ;
+            tuples = scan(tuples, patternNaturalOrder) ;
+        }
+        
+        return tuples ;
+    }
+    
+    @Override
+    public Iterator<Tuple<NodeId>> all()
+    {
+        switchToSync() ;
+        Iterator<Record> iter = index.iterator() ;
+        return Iter.map(iter, item -> TupleLib.tuple(item, tupleMap)) ;
+    }
+    
+    private Iterator<Tuple<NodeId>> scan(Iterator<Tuple<NodeId>> iter, Tuple<NodeId> pattern) {
+        Predicate<Tuple<NodeId>> filter = (item) -> {
+            // Check on pattern and item (both in natural order)
+            for ( int i = 0 ; i < tupleLength ; i++ ) {
+                NodeId n = pattern.get(i) ;
+                // The pattern must be null/Any or match the tuple being tested.
+                if ( ! NodeId.isAny(n) )
+                    if ( ! item.get(i).equals(n) ) 
+                        return false ;
+            }
+            return true ;
+        } ;
+        
+        return Iter.filter(iter, filter) ;
+    }
+    
+    @Override
+    public void close() {
+        switchToSync() ;
+        index.close() ;
+    }
+
+    @Override
+    public void sync() {
+        switchToSync() ;
+        index.sync() ;
+    }
+
+    public final RangeIndex getRangeIndex() {
+        return index ;
+    }
+
+    //protected final RecordFactory getRecordFactory()        { return factory ; }
+    
+    @Override
+    public boolean isEmpty() {
+        switchToSync() ;
+        return index.isEmpty() ;
+    }
+
+    @Override
+    public void clear() {
+        switchToSync() ;
+        index.clear() ;
+    }
+
+    @Override
+    public long size() {
+        switchToSync() ;
+        return index.size() ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/3d456654/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/tupletable/TupleIndexWrapper.java
----------------------------------------------------------------------
diff --git a/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/tupletable/TupleIndexWrapper.java b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/tupletable/TupleIndexWrapper.java
new file mode 100644
index 0000000..fad06a3
--- /dev/null
+++ b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/tupletable/TupleIndexWrapper.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.tdb2.store.tupletable;
+
+import java.util.Collection ;
+import java.util.Iterator ;
+
+import org.apache.jena.atlas.lib.tuple.Tuple ;
+import org.apache.jena.atlas.lib.tuple.TupleMap ;
+import org.apache.jena.tdb2.store.NodeId;
+
+public class TupleIndexWrapper implements TupleIndex
+{
+    protected final TupleIndex index ;
+
+    public TupleIndexWrapper(TupleIndex index) { this.index = index ; }
+    
+    @Override
+    public final TupleIndex wrapped() {
+        return index ;
+    }
+
+    @Override
+    public void add(Tuple<NodeId> tuple) {
+        index.add(tuple) ;
+    }
+
+    @Override
+    public void addAll(Collection<Tuple<NodeId>> tuples) {
+        index.addAll(tuples) ;
+    }
+
+    @Override
+    public void delete(Tuple<NodeId> tuple) {
+        index.delete(tuple) ;
+    }
+
+    @Override
+    public void deleteAll(Collection<Tuple<NodeId>> tuples) {
+        index.deleteAll(tuples);
+    }
+
+    @Override
+    public Iterator<Tuple<NodeId>> find(Tuple<NodeId> pattern) {
+        return index.find(pattern) ;
+    }
+
+    @Override
+    public Iterator<Tuple<NodeId>> all() {
+        return index.all() ;
+    }
+
+    @Override
+    public int getTupleLength() {
+        return index.getTupleLength() ;
+    }
+
+    @Override
+    public String getMappingStr() {
+        return index.getMappingStr() ;
+    }
+
+    @Override
+    public TupleMap getMapping() {
+        return index.getMapping() ;
+    }
+
+    @Override
+    public String getName() {
+        return index.getName() ;
+    }
+
+    @Override
+    public int weight(Tuple<NodeId> pattern) {
+        return index.weight(pattern) ;
+    }
+
+    @Override
+    public long size() {
+        return index.size() ;
+    }
+
+    @Override
+    public boolean isEmpty() {
+        return index.isEmpty() ;
+    }
+
+    @Override
+    public void clear() {
+        index.clear() ;
+    }
+
+    @Override
+    public void sync() {
+        index.sync() ;
+    }
+
+    @Override
+    public void close() {
+        index.close() ;
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/3d456654/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/tupletable/TupleTable.java
----------------------------------------------------------------------
diff --git a/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/tupletable/TupleTable.java b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/tupletable/TupleTable.java
new file mode 100644
index 0000000..42658fa
--- /dev/null
+++ b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/tupletable/TupleTable.java
@@ -0,0 +1,219 @@
+/*
+ * 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.tdb2.store.tupletable;
+
+import static java.lang.String.format ;
+
+import java.util.Iterator ;
+import java.util.List ;
+
+import org.apache.jena.atlas.iterator.Iter;
+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.atlas.logging.Log ;
+import org.apache.jena.tdb2.TDBException;
+import org.apache.jena.tdb2.store.NodeId;
+import org.apache.jena.tdb2.sys.SystemTDB;
+import org.slf4j.Logger ;
+import org.slf4j.LoggerFactory ;
+
+/** A TupleTable is a set of TupleIndexes.  The first TupleIndex is the "primary" index and must exist */
+public class TupleTable implements Sync, Closeable
+{
+    private static Logger log = LoggerFactory.getLogger(TupleTable.class) ;
+    
+    private final TupleIndex[] indexes ;
+    private final TupleIndex   scanAllIndex ;   // Use this index if a complete scan is needed.
+    private final int tupleLen ;
+    private boolean syncNeeded = false ;
+    
+    public TupleTable(int tupleLen, TupleIndex[] indexes)
+    {
+        this.tupleLen = tupleLen ;
+        this.indexes = indexes ;
+        if ( indexes[0] == null )
+            throw new TDBException("TupleTable: no primary index") ;
+        for ( TupleIndex index : indexes )
+        {
+            if ( index != null && index.getTupleLength() != tupleLen )
+                throw new TDBException("Incompatible index: "+index.getMappingStr()) ;
+        }
+        scanAllIndex = chooseScanAllIndex(tupleLen, indexes) ;
+    }
+    
+    /** Choose an index to scan in case we are asked for everything
+     * This needs to be ???G for the distinctAdjacent filter in union query to work.
+     */
+    private static TupleIndex chooseScanAllIndex(int tupleLen, TupleIndex[] indexes)
+    {
+        if ( tupleLen != 4 )
+            return indexes[0] ;
+        
+        for ( TupleIndex index : indexes )
+        {
+            // First look for SPOG
+            if ( index.getName().equals("SPOG") )
+                return index ;
+        }
+        
+        for ( TupleIndex index : indexes )
+        {
+            // Then look for any ???G
+            if ( index.getName().endsWith("G") )
+                return index ;
+        }
+        
+        Log.warn(SystemTDB.errlog, "Did not find a ???G index for full scans") ;
+        return indexes[0] ;
+    }
+
+    /** Insert a tuple */
+    public void add(Tuple<NodeId> t) {
+        // A "contains test" could be used to avoid needing to hit all
+        // the indexes when the triple is already present.
+        if ( tupleLen != t.len() )
+            throw new TDBException(format("Mismatch: inserting tuple of length %d into a table of tuples of length %d", t.len(), tupleLen)) ;
+        for ( int i = 0 ; i < indexes.length ; i++ ) {
+            if ( indexes[i] == null ) continue ;
+            indexes[i].add(t) ;
+            syncNeeded = true ;
+        }
+    }
+
+    /** Insert tuples */
+    public void addAll(List<Tuple<NodeId>> t) {
+        // Parallel.
+        for ( int i = 0 ; i < indexes.length ; i++ ) {
+            if ( indexes[i] == null ) continue ;
+            indexes[i].addAll(t) ;
+            syncNeeded = true ;
+        }
+    }
+
+    /** Delete a tuple */
+    public void delete( Tuple<NodeId> t ) { 
+        if ( tupleLen != t.len() )
+            throw new TDBException(format("Mismatch: deleting tuple of length %d from a table of tuples of length %d", t.len(), tupleLen)) ;
+
+        for ( TupleIndex index : indexes ) {
+            if ( index == null )
+                continue;
+            index.delete( t );
+        }
+    }
+    
+    /** Delete tuples */
+    public void deleteAll(List<Tuple<NodeId>> t) {
+        // Parallel.
+        for ( int i = 0 ; i < indexes.length ; i++ ) {
+            if ( indexes[i] == null ) continue ;
+            indexes[i].deleteAll(t) ;
+            syncNeeded = true ;
+        }
+    }
+
+    /** Find all matching tuples - a slot of NodeId.NodeIdAny means match any */
+    public Iterator<Tuple<NodeId>> find(Tuple<NodeId> pattern) {
+        if ( tupleLen != pattern.len() )
+            throw new TDBException(format("Mismatch: finding tuple of length %d in a table of tuples of length %d", pattern.len(), tupleLen)) ;
+        
+        int numSlots = 0 ;
+        // Canonical form. 
+        for ( int i = 0 ; i < tupleLen ; i++ ) {
+            NodeId x = pattern.get(i) ;
+            if ( ! NodeId.isAny(x) )
+                numSlots++ ;
+            if ( NodeId.isDoesNotExist(x))
+                return Iter.nullIterator();
+        }
+
+        if ( numSlots == 0 )
+            return scanAllIndex.all() ;
+        
+        int indexNumSlots = 0 ;
+        TupleIndex index = null ;
+        for ( TupleIndex idx : indexes ) {
+            if ( idx != null ) {
+                int w = idx.weight( pattern );
+                if ( w > indexNumSlots ) {
+                    indexNumSlots = w;
+                    index = idx;
+                }
+            }
+        }
+        
+        if ( index == null )
+            // No index at all.  Scan.
+            index = indexes[0] ;
+        return index.find(pattern) ;
+    }
+    
+    @Override
+    final public void close() {
+        for ( TupleIndex idx : indexes ) {
+            if ( idx != null )
+                idx.close();
+        }
+    }
+    
+    @Override
+    public void sync() {
+        if ( syncNeeded ) {
+            for ( TupleIndex idx : indexes ) {
+                if ( idx != null )
+                    idx.sync() ;
+            }
+            syncNeeded = false ;
+        }
+    }
+
+    public boolean isEmpty()        { return indexes[0].isEmpty() ; }
+    
+    public void clear() {
+        for ( TupleIndex idx : indexes ) {
+            if ( idx != null )
+                idx.clear() ;
+        }
+        syncNeeded = true ;
+    }
+    
+    public long size() {
+        return indexes[0].size() ;
+    }
+    
+    /** Get i'th index */ 
+    public TupleIndex getIndex(int i)                   { return indexes[i] ; }
+    
+    /** Get all indexes - for code that maipulates internal structures directly - use with care */ 
+    public TupleIndex[] getIndexes()                    { return indexes ; }
+    
+    /** Get the width of tuples in indexes in this table */
+    public int getTupleLen()                            { return tupleLen ; }
+
+    /** Set index - for code that manipulates internal structures directly - use with care */ 
+    public void setTupleIndex(int i, TupleIndex index) {
+        if ( index != null && index.getTupleLength() != tupleLen )
+            throw new TDBException("Incompatible index: " + index.getMappingStr()) ;
+        indexes[i] = index ;
+    }
+
+    /** Number of indexes on this tuple table */
+    public int numIndexes()                             { return indexes.length ; }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/3d456654/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/value/DateTimeNode.java
----------------------------------------------------------------------
diff --git a/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/value/DateTimeNode.java b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/value/DateTimeNode.java
new file mode 100644
index 0000000..9989414
--- /dev/null
+++ b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/value/DateTimeNode.java
@@ -0,0 +1,254 @@
+/*
+ * 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.tdb2.store.value;
+
+import java.math.BigDecimal ;
+
+import javax.xml.datatype.DatatypeConfigurationException ;
+import javax.xml.datatype.DatatypeConstants ;
+import javax.xml.datatype.DatatypeFactory ;
+import javax.xml.datatype.XMLGregorianCalendar ;
+
+import org.apache.jena.atlas.lib.BitsInt ;
+import org.apache.jena.atlas.lib.BitsLong ;
+import org.apache.jena.atlas.lib.NumberUtils ;
+import org.apache.jena.tdb2.TDBException;
+
+public class DateTimeNode
+{
+    // ---- Layout
+    // Epoch base: 0000-01-01T00:00:00
+
+    // Layout:
+    // Bits 56-63 : type
+    
+    // Bits 49-55 (7 bits)  : timezone -- 15 min precision + special for Z and no timezone.
+    // Bits 27-48 (22 bits) : date, year is 13 bits = 8000 years  (0 to 7999)
+    // Bits 0-26  (27 bits) : time, to milliseconds 
+
+    // Layout:
+    // Hi: TZ YYYY MM DD HH MM SS.sss Lo:
+    // Looses the distinction between 00:00:00 vs 24:00:00 (of the day before).
+
+    // Const-ize
+    // 13 bits year, 4 bits month, 5 bits day => 22 bits
+    static final int       DATE_LEN        = 22;
+    // 5 bits hour + 6 bits minute + 16 bits seconds (to millisecond)
+    static final int       TIME_LEN        = 27;
+
+    static final int       MILLI           = 0;
+    static final int       MILLI_LEN       = 16;
+
+    static final int       MINUTES         = MILLI_LEN;
+    static final int       MINUTES_LEN     = 6;
+
+    static final int       HOUR            = MILLI_LEN + MINUTES_LEN;
+    static final int       HOUR_LEN        = 5;
+
+    static final int       DAY             = TIME_LEN;
+    static final int       DAY_LEN         = 5;
+
+    static final int       MONTH           = TIME_LEN + DAY_LEN;
+    static final int       MONTH_LEN       = 4;
+
+    static final int       YEAR            = TIME_LEN + MONTH_LEN + DAY_LEN;
+    static final int       YEAR_LEN        = 13;
+
+    static final int       TZ              = TIME_LEN + DATE_LEN;
+    static final int       TZ_LEN          = 7;
+    // Value for Z
+    static final int       TZ_Z            = 0x7F;
+    // Value for no timezone.
+    static final int       TZ_NONE         = 0x7E;
+    
+    static DatatypeFactory datatypeFactory = null;
+    static {
+        try {
+            datatypeFactory = DatatypeFactory.newInstance();
+        }
+        catch (DatatypeConfigurationException ex) {
+            throw new TDBException("DateTimeNode", ex);
+        }
+    }
+
+    // Packed in correct place.
+    static long time(long v, int hour, int mins, int millisec) {
+        // And bit offset for direct packing?
+        // HH:MM:SS.ssss => 5 bits H, 6 bits M, 16 bits S ==> 27 bits
+        v = BitsLong.pack(v, hour, HOUR, HOUR + HOUR_LEN);
+        v = BitsLong.pack(v, mins, MINUTES, MINUTES + MINUTES_LEN);
+        v = BitsLong.pack(v, millisec, MILLI, MILLI + MILLI_LEN);
+        return v;
+    }
+
+    // Packed in correct place.
+    static long date(long v, int year, int month, int day) {
+        // YYYY:MM:DD => 13 bits year, 4 bits month, 5 bits day => 22 bits
+        v = BitsLong.pack(v, year, YEAR, YEAR + YEAR_LEN);
+        v = BitsLong.pack(v, month, MONTH, MONTH + MONTH_LEN);
+        v = BitsLong.pack(v, day, DAY, DAY + DAY_LEN);
+        return v;
+    }
+
+    static long tz(long v, int tz_in_quarters) {
+        v = BitsLong.pack(v, tz_in_quarters, TZ, TZ + TZ_LEN);
+        return v;
+    }
+
+    // From string. Assumed legal. Retains all info this way.
+    // returns -1 for unpackable.
+    public static long packDate(String lex) {
+        return packDateTime(lex);
+    }
+
+    // From string. Assumed legal.
+    // Returns -1 for unpackable.
+
+    public static long packDateTime(String lex) {
+        try {
+            return packDateTime$(lex);
+        }
+        catch (Exception ex) {
+            return -1;
+        }
+    }
+
+    private static long packDateTime$(String lex) {
+        long v = 0;
+        // Whitespace facet processing.
+        lex = lex.trim();
+
+        boolean containsZ = (lex.indexOf('Z') > 0);
+
+        // Bug in Java 1.6 (build 5 at least)
+        // T24:00:00 not accepted.
+        // See also TestNodeId.nodeId_date_time_7
+
+        XMLGregorianCalendar xcal = datatypeFactory.newXMLGregorianCalendar(lex);
+
+        if ( xcal.getFractionalSecond() != null ) {
+            BigDecimal fs = xcal.getFractionalSecond();
+            // Were there sub-millisecond resolution fractional seconds?
+            // This isn't perfect but it needs a very long fractional part to break it,
+            // less than observable quantum of time.
+            if ( fs.doubleValue() != xcal.getMillisecond() / 1000.0 )
+                return -1;
+        }
+
+        int y = xcal.getYear();
+
+        if ( y < 0 || y >= 8000 )
+            return -1;
+
+        v = date(v, xcal.getYear(), xcal.getMonth(), xcal.getDay());
+        v = time(v, xcal.getHour(), xcal.getMinute(), xcal.getSecond() * 1000 + xcal.getMillisecond());
+
+        if ( containsZ )
+            return tz(v, TZ_Z);
+
+        int tz = xcal.getTimezone();
+        if ( tz == DatatypeConstants.FIELD_UNDEFINED )
+            return tz(v, TZ_NONE);
+
+        // Timezone is weird.
+        if ( tz % 15 != 0 )
+            return -1;
+
+        tz = tz / 15;
+        return tz(v, tz);
+    }
+
+    public static String unpackDateTime(long v) {
+        return unpack(v, true);
+    }
+
+    public static String unpackDate(long v) {
+        return unpack(v, false);
+    }
+
+    // Avoid calls to String.format
+    private static String unpack(long v, boolean isDateTime) {
+        // YYYY:MM:DD => 13 bits year, 4 bits month, 5 bits day => 22 bits
+        int years = (int)BitsLong.unpack(v, YEAR, YEAR + YEAR_LEN);
+        int months = (int)BitsLong.unpack(v, MONTH, MONTH + MONTH_LEN);
+        int days = (int)BitsLong.unpack(v, DAY, DAY + DAY_LEN);
+
+        // Hours: 5, mins 6, milli 16, TZ 7 => 34 bits
+        int hours = (int)BitsLong.unpack(v, HOUR, HOUR + HOUR_LEN);
+        int minutes = (int)BitsLong.unpack(v, MINUTES, MINUTES + MINUTES_LEN);
+        int milliSeconds = (int)BitsLong.unpack(v, MILLI, MILLI + MILLI_LEN);
+
+        int tz = (int)BitsLong.unpack(v, TZ, TZ + TZ_LEN);
+
+        int sec = milliSeconds / 1000;
+        int fractionSec = milliSeconds % 1000;
+
+        StringBuilder sb = new StringBuilder(50);
+        NumberUtils.formatInt(sb, years, 4);
+        sb.append('-');
+        NumberUtils.formatInt(sb, months, 2);
+        sb.append('-');
+        NumberUtils.formatInt(sb, days, 2);
+        if ( isDateTime ) {
+            sb.append('T');
+            NumberUtils.formatInt(sb, hours, 2);
+            sb.append(':');
+            NumberUtils.formatInt(sb, minutes, 2);
+            sb.append(':');
+            NumberUtils.formatInt(sb, sec, 2);
+
+            // Formatting needed : int->any
+            if ( fractionSec != 0 ) {
+                sb.append(".");
+                if ( fractionSec % 100 == 0 )
+                    NumberUtils.formatInt(sb, fractionSec / 100, 1);
+                else if ( fractionSec % 10 == 0 )
+                    NumberUtils.formatInt(sb, fractionSec / 10, 2);
+                else
+                    NumberUtils.formatInt(sb, fractionSec, 3);
+            }
+        }
+        // tz in 15min units
+        // Special values.
+        if ( tz == TZ_Z ) {
+            sb.append("Z");
+            return sb.toString();
+        }
+
+        if ( tz == TZ_NONE )
+            return sb.toString();
+
+        // Sign extend.
+        if ( BitsLong.isSet(v, TZ + TZ_LEN - 1) )
+            tz = BitsInt.set(tz, TZ_LEN, 32);
+
+        if ( tz < 0 ) {
+            tz = -tz;
+            sb.append('-');
+        } else
+            sb.append('+');
+
+        int tzH = tz / 4;
+        int tzM = (tz % 4) * 15;
+        NumberUtils.formatUnsignedInt(sb, tzH, 2);
+        sb.append(':');
+        NumberUtils.formatUnsignedInt(sb, tzM, 2);
+        return sb.toString();
+    }
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/3d456654/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/value/DecimalNode.java
----------------------------------------------------------------------
diff --git a/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/value/DecimalNode.java b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/value/DecimalNode.java
new file mode 100644
index 0000000..acb99de
--- /dev/null
+++ b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/value/DecimalNode.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.apache.jena.tdb2.store.value;
+
+/** Placeholder for a full length decimal , using int+long.*/
+public class DecimalNode {
+}

http://git-wip-us.apache.org/repos/asf/jena/blob/3d456654/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/value/DecimalNode56.java
----------------------------------------------------------------------
diff --git a/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/value/DecimalNode56.java b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/value/DecimalNode56.java
new file mode 100644
index 0000000..7515588
--- /dev/null
+++ b/jena-db/jena-tdb2/src/main/java/org/apache/jena/tdb2/store/value/DecimalNode56.java
@@ -0,0 +1,141 @@
+/*
+ * 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.tdb2.store.value;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import org.apache.jena.atlas.lib.BitsLong ;
+
+
+// Decimal packed into 56 bits.
+public class DecimalNode56
+{
+    //private static Logger log = LoggerFactory.getLogger(DecimalNode.class) ;
+    
+    BigDecimal decimal = null ;
+    
+    // signed 8 bits of scale, signed 48 bits of value.
+    // Decimal precision is 47 bits (it's signed) or around 14 places.
+    // Not finance industry accuracy nor XSD (18 places minimum) but still useful.
+
+    static final int        SCALE_LEN = 8;
+    static final int        VALUE_LEN = 48;
+    static final int        ENC_LEN   = 48 + SCALE_LEN;
+
+    static final long       MAX_VALUE = (1L << (VALUE_LEN - 1)) - 1;
+    static final long       MIN_VALUE = -(1L << (VALUE_LEN - 1));
+
+    static final int        MAX_SCALE = (1 << (SCALE_LEN - 1)) - 1;
+    static final int        MIN_SCALE = -(1 << (SCALE_LEN - 1));
+
+    static final BigInteger MAX_I     = BigInteger.valueOf(MAX_VALUE);
+    static final BigInteger MIN_I     = BigInteger.valueOf(MIN_VALUE);
+
+    // Bits counts
+    static private int      SCALE_LO  = 56 - SCALE_LEN;
+    static private int      SCALE_HI  = 56;                               // Exclusive
+                                                                          // index
+
+    static private int      VALUE_LO  = 0;
+    static private int      VALUE_HI  = VALUE_LO + VALUE_LEN;
+
+    private int             scale;
+    private long            value;
+
+    public static DecimalNode56 valueOf(BigDecimal decimal) {
+        int scale = decimal.scale();
+        BigInteger bigInt = decimal.unscaledValue();
+        
+        //decimal.longValueExact(); // Throws exception
+        //new BigDecimal(long);
+
+        if ( bigInt.compareTo(MAX_I) > 0 || bigInt.compareTo(MIN_I) < 0 )
+            // This check makes sure that bigInt.longValue() is safe
+            return null;
+        return valueOf(bigInt.longValue(), scale);
+    }
+
+    public static DecimalNode56 valueOf(long binValue, int scale) {
+        if ( scale < MIN_SCALE || scale > MAX_SCALE ) {
+            // log.warn("Scale out of range: ("+binValue+","+scale+")") ;
+            return null;
+        }
+
+        if ( binValue < MIN_VALUE || binValue > MAX_VALUE ) {
+            // log.warn("Value out of range: ("+binValue+","+scale+")") ;
+            return null;
+        }
+
+        return new DecimalNode56(binValue, scale);
+    }
+
+    private DecimalNode56(long value, int scale) {
+        this.scale = scale;
+        this.value = value;
+    }
+
+    public long pack() {
+        return pack(value, scale);
+    }
+
+    /** Create the long value */
+    public static long pack(long value, int scale) {
+        // pack : scale, value
+        long v = 0;
+        v = BitsLong.pack(0L, scale, SCALE_LO, SCALE_HI);
+        v = BitsLong.pack(v, value, VALUE_LO, VALUE_HI);
+        // No need to do something about negative numbers
+        return v;
+    }
+
+    public static DecimalNode56 unpack(long v) {
+        int scale = (int)BitsLong.unpack(v, SCALE_LO, SCALE_HI);
+        long value = BitsLong.unpack(v, VALUE_LO, VALUE_HI);
+        return new DecimalNode56(value, scale);
+    }
+
+    public static BigDecimal unpackAsBigDecimal(long v) {
+        int scale = (int)BitsLong.unpack(v, SCALE_LO, SCALE_HI);
+        long value = BitsLong.unpack(v, VALUE_LO, VALUE_HI);
+        // Sign extend value.
+        if ( BitsLong.isSet(value, VALUE_HI - 1) )
+            value = value | -1L << (VALUE_HI);
+        return BigDecimal.valueOf(value, scale);
+    }
+
+    public BigDecimal get() {
+        if ( decimal == null )
+            decimal = BigDecimal.valueOf(value, scale);
+        return decimal;
+    }
+
+    @Override
+    public String toString() {
+        return get().toPlainString();
+    }
+
+    public int getScale() {
+        return scale;
+    }
+
+    public long getValue() {
+        return value;
+    }
+}