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

org.neo4j.bolt.v1.runtime.TransactionStateMachine Maven / Gradle / Ivy

/*
 * Copyright (c) 2002-2017 "Neo Technology,"
 * Network Engine for Objects in Lund AB [http://neotechnology.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.neo4j.bolt.v1.runtime;

import java.time.Clock;
import java.util.Map;

import org.neo4j.bolt.security.auth.AuthenticationResult;
import org.neo4j.bolt.v1.runtime.bookmarking.Bookmark;
import org.neo4j.bolt.v1.runtime.spi.BoltResult;
import org.neo4j.bolt.v1.runtime.spi.BookmarkResult;
import org.neo4j.cypher.InvalidSemanticsException;
import org.neo4j.function.ThrowingAction;
import org.neo4j.function.ThrowingConsumer;
import org.neo4j.kernel.api.KernelTransaction;
import org.neo4j.kernel.api.exceptions.KernelException;
import org.neo4j.kernel.api.exceptions.Status;
import org.neo4j.kernel.api.exceptions.TransactionFailureException;
import org.neo4j.kernel.api.security.SecurityContext;
import org.neo4j.kernel.impl.query.QueryExecutionKernelException;

public class TransactionStateMachine implements StatementProcessor
{
    private static final String BEGIN = "BEGIN";
    private static final String COMMIT = "COMMIT";
    private static final String ROLLBACK = "ROLLBACK";

    final SPI spi;
    final MutableTransactionState ctx;
    State state = State.AUTO_COMMIT;

    TransactionStateMachine( SPI spi, AuthenticationResult authenticationResult, Clock clock )
    {
        this.spi = spi;
        ctx = new MutableTransactionState( authenticationResult, clock );
    }

    public State state()
    {
        return state;
    }

    private void before()
    {
        if ( ctx.currentTransaction != null )
        {
            spi.bindTransactionToCurrentThread( ctx.currentTransaction );
        }
    }

    @Override
    public StatementMetadata run( String statement, Map params ) throws KernelException
    {
        before();
        try
        {
            state = state.run( ctx, spi, statement, params );

            return ctx.currentStatementMetadata;
        }
        finally
        {
            after();
        }
    }

    @Override
    public void streamResult( ThrowingConsumer resultConsumer ) throws Exception
    {
        before();
        try
        {
            state.streamResult( ctx, resultConsumer );
        }
        finally
        {
            after();
        }
    }

    /**
     * Rollback and close transaction. Move back to {@link State#AUTO_COMMIT}.
     * 

* Warning:This method should only be called by the bolt worker thread during it's regular message * processing. It is wrong to call it from a different thread because kernel transactions are not thread-safe. * * @throws TransactionFailureException when transaction fails to close. */ @Override public void reset() throws TransactionFailureException { state.terminateQueryAndRollbackTransaction( ctx ); state = State.AUTO_COMMIT; } private void after() { if ( ctx.currentTransaction != null ) { spi.unbindTransactionFromCurrentThread(); } } public void markCurrentTransactionForTermination() { KernelTransaction tx = ctx.currentTransaction; if ( tx != null ) { tx.markForTermination( Status.Transaction.Terminated ); } } @Override public boolean hasTransaction() { return state == State.EXPLICIT_TRANSACTION; } @Override public void setQuerySource( BoltQuerySource querySource ) { this.ctx.querySource = querySource; } enum State { AUTO_COMMIT { @Override State run( MutableTransactionState ctx, SPI spi, String statement, Map params ) throws KernelException { if ( statement.equalsIgnoreCase( BEGIN ) ) { ctx.currentTransaction = spi.beginTransaction( ctx.securityContext ); Bookmark bookmark = Bookmark.fromParamsOrNull( params ); if ( bookmark != null ) { spi.awaitUpToDate( bookmark.txId() ); ctx.currentResult = new BookmarkResult( bookmark ); } else { ctx.currentResult = BoltResult.EMPTY; } return EXPLICIT_TRANSACTION; } else if ( statement.equalsIgnoreCase( COMMIT ) ) { throw new QueryExecutionKernelException( new InvalidSemanticsException( "No current transaction to commit." ) ); } else if ( statement.equalsIgnoreCase( ROLLBACK ) ) { throw new QueryExecutionKernelException( new InvalidSemanticsException( "No current transaction to rollback." ) ); } else { if ( statement.isEmpty() ) { statement = ctx.lastStatement; } else { ctx.lastStatement = statement; } if ( spi.isPeriodicCommit( statement ) ) { BoltResultHandle resultHandle = executeQuery( ctx, spi, statement, params, () -> {} ); ctx.currentResultHandle = resultHandle; ctx.currentResult = resultHandle.start(); ctx.currentTransaction = null; // Periodic commit will change the current transaction, so // we can't trust this to point to the actual current transaction; return AUTO_COMMIT; } else { ctx.currentTransaction = spi.beginTransaction( ctx.securityContext ); BoltResultHandle resultHandle = execute( ctx, spi, statement, params ); ctx.currentResultHandle = resultHandle; ctx.currentResult = resultHandle.start(); return AUTO_COMMIT; } } } /* * In AUTO_COMMIT we must make sure to fail, close and set the current * transaction to null. */ private BoltResultHandle execute( MutableTransactionState ctx, SPI spi, String statement, Map params ) throws TransactionFailureException, QueryExecutionKernelException { return executeQuery( ctx, spi, statement, params, () -> { closeTransaction( ctx, false ); } ); } @Override void streamResult( MutableTransactionState ctx, ThrowingConsumer resultConsumer ) throws Exception { assert ctx.currentResult != null; resultConsumer.accept( ctx.currentResult ); ctx.currentResult.close(); closeTransaction( ctx, true ); } }, EXPLICIT_TRANSACTION { @Override State run( MutableTransactionState ctx, SPI spi, String statement, Map params ) throws KernelException { if ( statement.equalsIgnoreCase( BEGIN ) ) { throw new QueryExecutionKernelException( new InvalidSemanticsException( "Nested transactions are not supported." ) ); } else if ( statement.equalsIgnoreCase( COMMIT ) ) { closeTransaction( ctx, true ); long txId = spi.newestEncounteredTxId(); Bookmark bookmark = new Bookmark( txId ); ctx.currentResult = new BookmarkResult( bookmark ); return AUTO_COMMIT; } else if ( statement.equalsIgnoreCase( ROLLBACK ) ) { closeTransaction( ctx, false ); ctx.currentResult = BoltResult.EMPTY; return AUTO_COMMIT; } else { if ( statement.isEmpty() ) { statement = ctx.lastStatement; } else { ctx.lastStatement = statement; } if ( spi.isPeriodicCommit( statement ) ) { throw new QueryExecutionKernelException( new InvalidSemanticsException( "Executing queries that use periodic commit in an " + "open transaction is not possible." ) ); } else { ctx.currentResultHandle = execute( ctx, spi, statement, params ); ctx.currentResult = ctx.currentResultHandle.start(); return EXPLICIT_TRANSACTION; } } } private BoltResultHandle execute( MutableTransactionState ctx, SPI spi, String statement, Map params ) throws QueryExecutionKernelException { return executeQuery( ctx, spi, statement, params, () -> { if ( ctx.currentTransaction != null ) { ctx.currentTransaction.failure(); } } ); } @Override void streamResult( MutableTransactionState ctx, ThrowingConsumer resultConsumer ) throws Exception { assert ctx.currentResult != null; resultConsumer.accept( ctx.currentResult ); ctx.currentResult.close(); } }; abstract State run( MutableTransactionState ctx, SPI spi, String statement, Map params ) throws KernelException; abstract void streamResult( MutableTransactionState ctx, ThrowingConsumer resultConsumer ) throws Exception; void terminateQueryAndRollbackTransaction( MutableTransactionState ctx ) throws TransactionFailureException { if ( ctx.currentResultHandle != null ) { ctx.currentResultHandle.terminate(); ctx.currentResultHandle = null; } if ( ctx.currentResult != null ) { ctx.currentResult.close(); ctx.currentResult = null; } closeTransaction( ctx, false); } /* * This is overly careful about always closing and nulling the transaction since * reset can cause ctx.currentTransaction to be null we store in local variable. */ void closeTransaction(MutableTransactionState ctx, boolean success) throws TransactionFailureException { KernelTransaction tx = ctx.currentTransaction; ctx.currentTransaction = null; if (tx != null) { try { if ( success ) { tx.success(); } else { tx.failure(); } if (tx.isOpen()) { tx.close(); } } finally { ctx.currentTransaction = null; } } } } private static BoltResultHandle executeQuery( MutableTransactionState ctx, SPI spi, String statement, Map params, ThrowingAction onFail ) throws QueryExecutionKernelException { return spi.executeQuery( ctx.querySource, ctx.securityContext, statement, params, onFail ); } /** * This interface makes it possible to abort queries even before they have returned a Result object. * In some cases, creating the Result object will take as long as running the query takes. This way, we can * terminate the underlying transaction while the Result object is created. */ interface BoltResultHandle { BoltResult start() throws KernelException; void terminate(); } static class MutableTransactionState { /** The current session security context to be used for starting transactions */ final SecurityContext securityContext; /** The current transaction, if present */ KernelTransaction currentTransaction; /** Last Cypher statement executed */ String lastStatement = ""; /** The current pending result, if present */ BoltResult currentResult; final Clock clock; /** A re-usable statement metadata instance that always represents the currently running statement */ private final StatementMetadata currentStatementMetadata = new StatementMetadata() { @Override public String[] fieldNames() { return currentResult.fieldNames(); } }; BoltQuerySource querySource; BoltResultHandle currentResultHandle; private MutableTransactionState( AuthenticationResult authenticationResult, Clock clock ) { this.clock = clock; this.securityContext = authenticationResult.getSecurityContext(); } } interface SPI { void awaitUpToDate( long oldestAcceptableTxId ) throws TransactionFailureException; long newestEncounteredTxId(); KernelTransaction beginTransaction( SecurityContext securityContext ); void bindTransactionToCurrentThread( KernelTransaction tx ); void unbindTransactionFromCurrentThread(); boolean isPeriodicCommit( String query ); BoltResultHandle executeQuery( BoltQuerySource querySource, SecurityContext securityContext, String statement, Map params, ThrowingAction onFail ) throws QueryExecutionKernelException; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy