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:46 UTC

[30/50] [abbrv] jena git commit: JENA-1396: Merge //github/afs/mantis as subdirectory jena-db/

http://git-wip-us.apache.org/repos/asf/jena/blob/d9da3592/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/nodetable/NodeTableOps.java
----------------------------------------------------------------------
diff --cc jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/nodetable/NodeTableOps.java
index 0000000,0000000..378bdfc
new file mode 100644
--- /dev/null
+++ b/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/nodetable/NodeTableOps.java
@@@ -1,0 -1,0 +1,50 @@@
++/*
++ *  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.nodetable;
++
++import java.util.ArrayList ;
++import java.util.List ;
++
++import org.apache.jena.graph.Node ;
++import org.seaborne.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/d9da3592/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/nodetable/NodeTableTRDF.java
----------------------------------------------------------------------
diff --cc jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/nodetable/NodeTableTRDF.java
index 0000000,0000000..44f71d4
new file mode 100644
--- /dev/null
+++ b/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/nodetable/NodeTableTRDF.java
@@@ -1,0 -1,0 +1,108 @@@
++/*
++ *  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.nodetable ;
++
++import org.apache.jena.atlas.logging.Log ;
++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.thrift.TException ;
++import org.apache.thrift.protocol.TProtocol ;
++import org.seaborne.dboe.base.file.BinaryDataFile ;
++import org.seaborne.dboe.index.Index ;
++import org.seaborne.tdb2.TDBException ;
++import org.seaborne.tdb2.store.NodeId ;
++import org.seaborne.tdb2.store.NodeIdFactory;
++
++/** 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 ; }
++}

http://git-wip-us.apache.org/repos/asf/jena/blob/d9da3592/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/nodetable/NodeTableWrapper.java
----------------------------------------------------------------------
diff --cc jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/nodetable/NodeTableWrapper.java
index 0000000,0000000..f18203d
new file mode 100644
--- /dev/null
+++ b/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/nodetable/NodeTableWrapper.java
@@@ -1,0 -1,0 +1,93 @@@
++/*
++ *  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.nodetable ;
++
++import java.util.Iterator ;
++import java.util.List ;
++
++import org.apache.jena.atlas.lib.Pair ;
++import org.apache.jena.graph.Node ;
++import org.seaborne.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/d9da3592/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/nodetable/TReadAppendFileTransport.java
----------------------------------------------------------------------
diff --cc jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/nodetable/TReadAppendFileTransport.java
index 0000000,0000000..34eebfb
new file mode 100644
--- /dev/null
+++ b/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/nodetable/TReadAppendFileTransport.java
@@@ -1,0 -1,0 +1,85 @@@
++/*
++ *  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.nodetable;
++
++import java.util.Objects ;
++
++import org.apache.thrift.transport.TTransport ;
++import org.seaborne.dboe.base.file.BinaryDataFile ;
++
++/** 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/d9da3592/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/nodetupletable/NodeTupleTable.java
----------------------------------------------------------------------
diff --cc jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/nodetupletable/NodeTupleTable.java
index 0000000,0000000..4d48189
new file mode 100644
--- /dev/null
+++ b/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/nodetupletable/NodeTupleTable.java
@@@ -1,0 -1,0 +1,71 @@@
++/*
++ *  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.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.seaborne.tdb2.store.NodeId ;
++import org.seaborne.tdb2.store.nodetable.NodeTable ;
++import org.seaborne.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/d9da3592/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/nodetupletable/NodeTupleTableConcrete.java
----------------------------------------------------------------------
diff --cc jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/nodetupletable/NodeTupleTableConcrete.java
index 0000000,0000000..e2fc5a9
new file mode 100644
--- /dev/null
+++ b/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/nodetupletable/NodeTupleTableConcrete.java
@@@ -1,0 -1,0 +1,240 @@@
++/*
++ *  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.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.seaborne.tdb2.TDBException ;
++import org.seaborne.tdb2.lib.TupleLib ;
++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 ;
++
++/** 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/d9da3592/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/nodetupletable/NodeTupleTableView.java
----------------------------------------------------------------------
diff --cc jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/nodetupletable/NodeTupleTableView.java
index 0000000,0000000..ea24558
new file mode 100644
--- /dev/null
+++ b/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/nodetupletable/NodeTupleTableView.java
@@@ -1,0 -1,0 +1,112 @@@
++/*
++ *  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.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.seaborne.tdb2.store.NodeId ;
++import org.seaborne.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/d9da3592/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/nodetupletable/NodeTupleTableWrapper.java
----------------------------------------------------------------------
diff --cc jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/nodetupletable/NodeTupleTableWrapper.java
index 0000000,0000000..952dda9
new file mode 100644
--- /dev/null
+++ b/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/nodetupletable/NodeTupleTableWrapper.java
@@@ -1,0 -1,0 +1,97 @@@
++/*
++ *  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.nodetupletable;
++
++import java.util.Iterator ;
++
++import org.apache.jena.atlas.lib.tuple.Tuple ;
++import org.apache.jena.graph.Node ;
++import org.seaborne.tdb2.store.NodeId ;
++import org.seaborne.tdb2.store.nodetable.NodeTable ;
++import org.seaborne.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/d9da3592/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/tupletable/TupleIndex.java
----------------------------------------------------------------------
diff --cc jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/tupletable/TupleIndex.java
index 0000000,0000000..e3c8b47
new file mode 100644
--- /dev/null
+++ b/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/tupletable/TupleIndex.java
@@@ -1,0 -1,0 +1,87 @@@
++/*
++ *  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.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.seaborne.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/d9da3592/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/tupletable/TupleIndexBase.java
----------------------------------------------------------------------
diff --cc jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/tupletable/TupleIndexBase.java
index 0000000,0000000..911950b
new file mode 100644
--- /dev/null
+++ b/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/tupletable/TupleIndexBase.java
@@@ -1,0 -1,0 +1,123 @@@
++/*
++ *  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.tupletable;
++
++import java.util.Iterator ;
++
++import org.apache.jena.atlas.lib.tuple.Tuple ;
++import org.apache.jena.atlas.lib.tuple.TupleMap ;
++import org.seaborne.tdb2.TDBException ;
++import org.seaborne.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/d9da3592/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/tupletable/TupleIndexRecord.java
----------------------------------------------------------------------
diff --cc jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/tupletable/TupleIndexRecord.java
index 0000000,0000000..c723472
new file mode 100644
--- /dev/null
+++ b/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/tupletable/TupleIndexRecord.java
@@@ -1,0 -1,0 +1,237 @@@
++/*
++ *  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.tupletable;
++
++import static java.lang.String.format ;
++import static org.seaborne.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.seaborne.dboe.base.record.Record ;
++import org.seaborne.dboe.base.record.RecordFactory ;
++import org.seaborne.dboe.index.RangeIndex ;
++import org.seaborne.tdb2.TDBException ;
++import org.seaborne.tdb2.lib.TupleLib ;
++import org.seaborne.tdb2.store.NodeId ;
++import org.seaborne.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/d9da3592/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/tupletable/TupleIndexRecordAsyncBulkAdd.java
----------------------------------------------------------------------
diff --cc jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/tupletable/TupleIndexRecordAsyncBulkAdd.java
index 0000000,0000000..242232e
new file mode 100644
--- /dev/null
+++ b/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/tupletable/TupleIndexRecordAsyncBulkAdd.java
@@@ -1,0 -1,0 +1,262 @@@
++/*
++ *  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.tupletable;
++
++import static java.lang.String.format ;
++import static org.seaborne.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.seaborne.dboe.base.record.Record ;
++import org.seaborne.dboe.base.record.RecordFactory ;
++import org.seaborne.dboe.index.RangeIndex ;
++import org.seaborne.dboe.transaction.txn.Transaction ;
++import org.seaborne.tdb2.TDBException ;
++import org.seaborne.tdb2.lib.Async ;
++import org.seaborne.tdb2.lib.TupleLib ;
++import org.seaborne.tdb2.store.NodeId ;
++import org.seaborne.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/d9da3592/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/tupletable/TupleIndexWrapper.java
----------------------------------------------------------------------
diff --cc jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/tupletable/TupleIndexWrapper.java
index 0000000,0000000..43f4b72
new file mode 100644
--- /dev/null
+++ b/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/tupletable/TupleIndexWrapper.java
@@@ -1,0 -1,0 +1,117 @@@
++/*
++ *  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.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.seaborne.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/d9da3592/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/tupletable/TupleTable.java
----------------------------------------------------------------------
diff --cc jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/tupletable/TupleTable.java
index 0000000,0000000..d02df70
new file mode 100644
--- /dev/null
+++ b/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/tupletable/TupleTable.java
@@@ -1,0 -1,0 +1,218 @@@
++/*
++ *  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.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.seaborne.tdb2.TDBException ;
++import org.seaborne.tdb2.store.NodeId ;
++import org.seaborne.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/d9da3592/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/value/DateTimeNode.java
----------------------------------------------------------------------
diff --cc jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/value/DateTimeNode.java
index 0000000,0000000..0853938
new file mode 100644
--- /dev/null
+++ b/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/value/DateTimeNode.java
@@@ -1,0 -1,0 +1,253 @@@
++/*
++ *  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.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.seaborne.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/d9da3592/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/value/DecimalNode.java
----------------------------------------------------------------------
diff --cc jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/value/DecimalNode.java
index 0000000,0000000..2e03d96
new file mode 100644
--- /dev/null
+++ b/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/value/DecimalNode.java
@@@ -1,0 -1,0 +1,22 @@@
++/*
++ *  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.value;
++
++/** Placeholder for a full length decimal , using int+long.*/
++public class DecimalNode {
++}

http://git-wip-us.apache.org/repos/asf/jena/blob/d9da3592/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/value/DecimalNode56.java
----------------------------------------------------------------------
diff --cc jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/value/DecimalNode56.java
index 0000000,0000000..c36e5c5
new file mode 100644
--- /dev/null
+++ b/jena-db/tdb2/src/main/java/org/seaborne/tdb2/store/value/DecimalNode56.java
@@@ -1,0 -1,0 +1,140 @@@
++/*
++ *  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.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;
++    }
++}