All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.jena.tdb.transaction.NodeTableTrans Maven / Gradle / Ivy

Go to download

TDB is a storage subsystem for Jena and ARQ, it is a native triple store providing persistent storage of triples/quads.

There is a newer version: 4.10.0
Show newest version
/*
 * 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.tdb.transaction;

import static org.apache.jena.atlas.logging.FmtLog.warn ;

import java.nio.ByteBuffer ;
import java.util.Iterator ;

import org.apache.jena.atlas.iterator.Iter ;
import org.apache.jena.atlas.lib.ByteBufferLib ;
import org.apache.jena.atlas.lib.Pair ;
import org.apache.jena.graph.Node ;
import org.apache.jena.tdb.TDBException ;
import org.apache.jena.tdb.base.objectfile.ObjectFile ;
import org.apache.jena.tdb.base.record.Record ;
import org.apache.jena.tdb.index.Index ;
import org.apache.jena.tdb.store.NodeId ;
import org.apache.jena.tdb.store.nodetable.NodeTable ;
import org.apache.jena.tdb.store.nodetable.NodeTableCache ;
import org.apache.jena.tdb.store.nodetable.NodeTableInline ;
import org.apache.jena.tdb.store.nodetable.NodeTableNative ;
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;

public class NodeTableTrans implements NodeTable, TransactionLifecycle
{
    private static Logger log = LoggerFactory.getLogger(NodeTableTrans.class) ;
    // TODO flag to note is any work is needed on commit.
    private final NodeTable base ;
    private long allocOffset ;
    
    private NodeTable nodeTableJournal = null ;
    private static int CacheSize = 10000 ;      // [TxTDB:TODO] Make configurable 
    private boolean passthrough = false ;
    
    private Index nodeIndex ;
    private ObjectFile journalObjFile ;
    // Start of the journal file for this transaction.
    // Always zero currently but allows for future  
    private long journalObjFileStartOffset ; 
    private final String label ;
    private final Transaction txn ;     // Can be null (during recovery).
    
    public NodeTableTrans(Transaction txn, String label, NodeTable sub, Index nodeIndex, ObjectFile objFile)
    {
        this.txn = txn ;
        this.base = sub ;
        this.nodeIndex = nodeIndex ;
        this.journalObjFile = objFile ;
        // Clear bytes from an old run
        // (a crash while writing means the old transaction did not commit
        //  any bytes in the file are junk)
        // This is coupled to the fact the prepare phase does the actually data writing. 
        journalObjFile.truncate(0) ;
        this.label = label ; 
    }

    public void setPassthrough(boolean v)   { passthrough = v ; }
    public NodeTable getBaseNodeTable()     { return base ; }
    public NodeTable getJournalTable()      { return nodeTableJournal ; }
    public Transaction getTransaction()     { return txn ; }
    
    @Override
    public NodeId getAllocateNodeId(Node node)
    {
        if ( passthrough ) return base.getAllocateNodeId(node) ;
        NodeId nodeId = getNodeIdForNode(node) ;
        if ( ! NodeId.isDoesNotExist(nodeId) )
            return nodeId ;
        // add to journal
        nodeId = allocate(node) ;
        return nodeId ;
    }
    
    @Override
    public NodeId getNodeIdForNode(Node node)
    {
        if ( node == Node.ANY )
            return NodeId.NodeIdAny ;
        if ( passthrough ) return base.getNodeIdForNode(node) ;
        NodeId nodeId = nodeTableJournal.getNodeIdForNode(node) ;
        if ( ! NodeId.isDoesNotExist(nodeId) )
            return mapFromJournal(nodeId) ;
        nodeId = base.getNodeIdForNode(node) ;
        return nodeId ;
    }

    @Override
    public Node getNodeForNodeId(NodeId id)
    {
        if ( NodeId.isAny(id) ) 
            return Node.ANY ;
        if ( passthrough ) return base.getNodeForNodeId(id) ;
        long x = id.getId() ;
        if ( x < allocOffset )
            return base.getNodeForNodeId(id) ;
        id = mapToJournal(id) ;
        Node node = nodeTableJournal.getNodeForNodeId(id) ;
        return node ;
    }

    @Override
    public boolean containsNode(Node node) {
        NodeId x = getNodeIdForNode(node) ;
        return NodeId.isDoesNotExist(x) ;
    }

    @Override
    public boolean containsNodeId(NodeId nodeId) {
        Node x = getNodeForNodeId(nodeId) ;
        return x == null ;
    }



    /** Convert from a id to the id in the "journal" file */ 
    private NodeId mapToJournal(NodeId id)
    { 
        if ( passthrough )
           throw new TDBTransactionException("Not in an active transaction") ;
        if ( NodeId.isInline(id) )
            return id ; 
        return NodeId.create(id.getId()-allocOffset) ;
    }
    
    /** Convert from a id in other to an external id  */ 
    private NodeId mapFromJournal(NodeId id)
    { 
        if ( passthrough )
            throw new TDBTransactionException("Not in an active transaction") ;
        if ( NodeId.isInline(id) )
            return id ; 
        return NodeId.create(id.getId()+allocOffset) ; 
    }
    
    private NodeId allocate(Node node)
    {
        NodeId nodeId = nodeTableJournal.getAllocateNodeId(node) ;
        nodeId = mapFromJournal(nodeId) ;
        return nodeId ;
    }
    
    @Override
    public NodeId allocOffset()
    {
        if ( passthrough ) return base.allocOffset() ;
        // If we have done the append stage, this is invalid as the base may change under our feet
        // Would need to track base operations.
        NodeId x1 = nodeTableJournal.allocOffset() ;
        NodeId x2 = mapFromJournal(x1) ;
        return x2 ;
    }

    @Override
    public void begin(Transaction txn)
    {
        //debug("%s begin", txn.getLabel()) ;
        
        if ( this.txn.getTxnId() != txn.getTxnId() )
            throw new TDBException(String.format("Different transactions: %s %s", this.txn.getLabel(), txn.getLabel())) ;
        if ( passthrough )
            throw new TDBException("Already active") ;
        passthrough = false ;
        
        allocOffset = base.allocOffset().getId() ;
        // base node table empty e.g. first use.
        journalObjFileStartOffset = journalObjFile.length() ;
        // Because the data is written in prepare, the journal of object data is
        // always empty at the start of a transaction.
        if ( journalObjFileStartOffset != 0 )
            warn(log, "%s journalStartOffset not zero: %d/0x%02X",txn.getLabel(), journalObjFileStartOffset, journalObjFileStartOffset) ;
        allocOffset += journalObjFileStartOffset ;
        
        this.nodeTableJournal = new NodeTableNative(nodeIndex, journalObjFile) ;
        this.nodeTableJournal = NodeTableCache.create(nodeTableJournal, CacheSize, CacheSize, 100) ;
        // This class knows about non-mappable inline values.   mapToJournal(NodeId)/mapFromJournal. 
        this.nodeTableJournal = NodeTableInline.create(nodeTableJournal) ;
    }
    
    static public boolean APPEND_LOG = false ; 
    
    /** Copy from the journal file to the real file */
    /*package*/ void append()
    {
        Iterator> iter = nodeTableJournal.all() ;
        Pair firstPair = null ;
        Pair lastPair = null ;
        
        for ( ; iter.hasNext() ; )
        {
            Pair x = iter.next() ;
            
            if ( firstPair == null )
                firstPair = x ;
            lastPair = x ;
            
            NodeId nodeId = x.getLeft() ;
            Node node = x.getRight() ;
            debug("  append: %s -> %s", x, mapFromJournal(nodeId)) ;
            // This does the write.
            NodeId nodeId2 = base.getAllocateNodeId(node) ;
            if ( ! nodeId2.equals(mapFromJournal(nodeId)) )
                inconsistent(node, nodeId, nodeId2) ;
        }
    }
    
    private void inconsistent(Node node , NodeId nodeId , NodeId nodeId2 )
    {
        String msg = String.format("Different ids for %s: allocated: expected %s, got %s", node, mapFromJournal(nodeId), nodeId2) ;
        System.err.println() ;
        System.err.println() ;
        System.err.println(msg) ;
        dump() ;   
        System.err.println() ;
        throw new TDBException(msg) ;
    }
    
    // Debugging only
    private void dump()
    {
        System.err.println(">>>>>>>>>>") ;
        System.err.println("label = "+label) ;
        System.err.println("txn = "+txn) ;
        System.err.println("offset = "+allocOffset) ;
        System.err.println("journalStartOffset = "+journalObjFileStartOffset) ;
        System.err.println("journal = "+journalObjFile.getLabel()) ;
        if ( true )
            return ;
        
        System.err.println("nodeTableJournal >>>") ;
        Iterator> iter = nodeTableJournal.all() ;
        for ( ; iter.hasNext() ; )
        {
            Pair x = iter.next() ;
            NodeId nodeId = x.getLeft() ;
            Node node = x.getRight() ;
            NodeId mapped = mapFromJournal(nodeId) ;
            //debug("append: %s -> %s", x, mapFromJournal(nodeId)) ;
            // This does the write.
            NodeId nodeId2 = base.getAllocateNodeId(node) ;
            System.err.println(x + "  mapped=" + mapped + " getAlloc="+nodeId2) ;
        }
        
        System.err.println("journal >>>") ;
        Iterator> iter1 = this.journalObjFile.all() ;
        for ( ; iter1.hasNext() ; )
        {
            Pair p = iter1.next() ;
            System.err.println(p.getLeft()+" : "+p.getRight()) ;
            ByteBufferLib.print(System.err, p.getRight()) ;
        }
        
        System.err.println("nodeIndex >>>") ;
        Iterator iter2 = this.nodeIndex.iterator() ;
        for ( ; iter2.hasNext() ; )
        {
            Record r = iter2.next() ;
            System.err.println(r) ;
        }
        System.err.println("<<<<<<<<<<") ;
    }
    
    @Override
    public void commitPrepare(Transaction txn)
    {
        debug("commitPrepare") ;
        
        // The node table is append-only so it can be written during prepare.
        // The index isn't written (via the transaction journal) until enact.
        if ( nodeTableJournal == null )
            throw new TDBTransactionException(txn.getLabel()+": Not in a transaction for a commit to happen") ;
        writeNodeJournal() ;
        
        if ( journalObjFile != null && journalObjFile.length() != 0 )
        {
            long x = journalObjFile.length() ;
            throw new TDBTransactionException(txn.getLabel()+": journalObjFile not cleared ("+x+")") ;
        }
    }
    
    @Override
    public void commitEnact(Transaction txn)
    {
        debug("commitEnact") ;
        // The work was done in commitPrepare, using the fact that node data file
        // is append only.  Until here, pointers to the extra data aren't available
        // until the index is written.
        // The index is written via the transaction journal.
        //writeJournal() ;
    }

    private void writeNodeJournal()
    {
        long expected = base.allocOffset().getId() ;
        long len = journalObjFile.length() ;
        if ( expected != allocOffset )
            warn(log, "Inconsistency: base.allocOffset() = %d : allocOffset = %d", expected, allocOffset) ;
        
        long newbase = -1 ; 
        append() ;      // Calls all() which does a buffer flish.
        // Reset (in case we use this again)
        nodeIndex.clear() ;
        journalObjFile.truncate(journalObjFileStartOffset) ;    // Side effect is a buffer flush.
        //journalObjFile.sync() ;
        journalObjFile.close() ;                                // Side effect is a buffer flush.
        journalObjFile = null ;
        base.sync() ;
        allocOffset = -99 ; // base.allocOffset().getId() ; // Will be invalid as we may write through to the base table later.
        passthrough = true ;
    }

    @Override
    public void commitClearup(Transaction txn)
    {
        debug("commitClearup") ;
        finish() ;
    }

    @Override
    public void abort(Transaction txn)
    {
        debug("abort") ;
        if ( nodeTableJournal == null )
            throw new TDBTransactionException(txn.getLabel()+": Not in a transaction for a commit to happen") ;
        // Ensure the cache does not flush.
        nodeTableJournal = null ;
        // then make sure the journal file is empty.
        if ( journalObjFile != null )
        {
            journalObjFile.truncate(journalObjFileStartOffset) ;
            journalObjFile.sync() ;
        }
        finish() ;
    }
    
    private void finish()
    {
        close() ;
        passthrough = true ;
        nodeTableJournal = null ;
        journalObjFile = null ;
   }

    @Override
    public Iterator> all()
    {
        // Better would be to convert the spill file format.
        return Iter.concat(base.all(), nodeTableJournal.all()) ;
    }

    @Override
    public boolean isEmpty()
    {
        return nodeTableJournal.isEmpty() && base.isEmpty() ;
    }

    @Override
    public void sync()
    {
        if ( passthrough )
            base.sync() ;
    }

    @Override
    public void close()
    {
        if ( nodeIndex != null )
            nodeIndex.close() ;
        nodeIndex = null ;
        // Closing the journal flushes it; i.e. disk IO. 
        if ( journalObjFile != null )
            journalObjFile.close() ;
        journalObjFile = null ;
    }

    @Override
    public String toString() { return "NodeTableTrans:"+label+"(#"+Integer.toHexString(super.hashCode())+")" ; }
    
    private void debug(String fmt, Object... args)
    {
        if ( log.isDebugEnabled() )
        {
            String x = String.format(fmt, args) ;
            log.debug(label+": "+x) ;
        }
    }

    // Return the base table, not the nodeTableJournal
    @Override
    public NodeTable wrapped() {
        return base ;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy