diff --git a/community/kernel/src/main/java/org/neo4j/kernel/api/AccessMode.java b/community/kernel/src/main/java/org/neo4j/kernel/api/AccessMode.java index 3c08d35a9b23c..c56ffa756af98 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/api/AccessMode.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/api/AccessMode.java @@ -66,7 +66,29 @@ public boolean allowsSchemaWrites() } }, - /** Allows reading data and schema, but not writing. */ + /** Allows writing data */ + WRITE_ONLY + { + @Override + public boolean allowsReads() + { + return false; + } + + @Override + public boolean allowsWrites() + { + return true; + } + + @Override + public boolean allowsSchemaWrites() + { + return false; + } + }, + + /** Allows reading and writing data, but not schema. */ WRITE { @Override diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/KernelStatement.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/KernelStatement.java index 7c6c0fc558c26..44cdf5e0563ca 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/KernelStatement.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/KernelStatement.java @@ -58,6 +58,7 @@ public KernelStatement( KernelTransactionImplementation transaction, @Override public ReadOperations readOperations() { + transaction.verifyReadTransaction(); return facade; } @@ -71,7 +72,7 @@ public TokenWriteOperations tokenWriteOperations() public DataWriteOperations dataWriteOperations() throws InvalidTransactionTypeKernelException { - transaction.upgradeToDataTransaction(); + transaction.verifyDataWriteTransaction(); return facade; } @@ -79,7 +80,7 @@ public DataWriteOperations dataWriteOperations() public SchemaWriteOperations schemaWriteOperations() throws InvalidTransactionTypeKernelException { - transaction.upgradeToSchemaTransaction(); + transaction.verifySchemaWriteTransaction(); return facade; } diff --git a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/KernelTransactionImplementation.java b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/KernelTransactionImplementation.java index 17e25535c7b30..8c153776a2de7 100644 --- a/community/kernel/src/main/java/org/neo4j/kernel/impl/api/KernelTransactionImplementation.java +++ b/community/kernel/src/main/java/org/neo4j/kernel/impl/api/KernelTransactionImplementation.java @@ -74,34 +74,52 @@ public class KernelTransactionImplementation implements KernelTransaction, TxSta private enum TransactionType { - READ, - DATA + NONE, + READ_ONLY, + READ_AND_DATA_WRITE { @Override - TransactionType upgradeToSchemaTransaction() throws InvalidTransactionTypeKernelException + TransactionType enableSchemaWriteTransaction() throws InvalidTransactionTypeKernelException { throw new InvalidTransactionTypeKernelException( "Cannot perform schema updates in a transaction that has performed data updates." ); } + + @Override + TransactionType enableReadTransaction() + { + return READ_AND_DATA_WRITE; + } }, - SCHEMA + READ_AND_SCHEMA_WRITE { @Override - TransactionType upgradeToDataTransaction() throws InvalidTransactionTypeKernelException + TransactionType enableDataWriteTransaction() throws InvalidTransactionTypeKernelException { throw new InvalidTransactionTypeKernelException( "Cannot perform data updates in a transaction that has performed schema updates." ); } + + @Override + TransactionType enableReadTransaction() + { + return READ_AND_SCHEMA_WRITE; + } }; - TransactionType upgradeToDataTransaction() throws InvalidTransactionTypeKernelException + TransactionType enableReadTransaction() throws IllegalStateException { - return DATA; + return READ_ONLY; } - TransactionType upgradeToSchemaTransaction() throws InvalidTransactionTypeKernelException + TransactionType enableDataWriteTransaction() throws InvalidTransactionTypeKernelException { - return SCHEMA; + return READ_AND_DATA_WRITE; + } + + TransactionType enableSchemaWriteTransaction() throws InvalidTransactionTypeKernelException + { + return READ_AND_SCHEMA_WRITE; } } @@ -127,11 +145,11 @@ TransactionType upgradeToSchemaTransaction() throws InvalidTransactionTypeKernel // whereas others, such as timestamp or txId when transaction starts, even locks, needs to be set in #initialize(). private TransactionState txState; private LegacyIndexTransactionState legacyIndexTransactionState; - private TransactionType transactionType; // Tracks current state of transaction, which will upgrade to WRITE or SCHEMA mode when necessary + private TransactionType transactionType; // Tracks current state of transaction, which will upgrade to WRITE or READ_AND_SCHEMA_WRITE mode when necessary private TransactionHooks.TransactionHooksState hooksState; private KernelStatement currentStatement; private CloseListener closeListener; - private AccessMode accessMode; // Defines whether a transaction is allowed to upgrade to WRITE or SCHEMA mode + private AccessMode accessMode; // Defines whether a transaction is allowed to upgrade to WRITE or READ_AND_SCHEMA_WRITE mode private Locks.Client locks; private boolean beforeHookInvoked; private boolean closing, closed; @@ -180,7 +198,7 @@ public KernelTransactionImplementation initialize( this.type = type; this.locks = locks; this.closing = closed = failure = success = terminated = beforeHookInvoked = false; - this.transactionType = TransactionType.READ; + this.transactionType = TransactionType.NONE; this.startTimeMillis = clock.currentTimeMillis(); this.lastTransactionIdWhenStarted = lastCommittedTx; this.transactionEvent = tracer.beginTransaction(); @@ -252,17 +270,28 @@ public void releaseStatement( Statement statement ) currentStatement = null; } - public void upgradeToDataTransaction() throws InvalidTransactionTypeKernelException + public void verifyReadTransaction() + { + if( !accessMode.allowsReads() ) + { + throw new IllegalStateException( + String.format( "Read operations are not allowed for `%s` transactions.", accessMode.name() ) ); + } + + transactionType.enableReadTransaction(); + } + + public void verifyDataWriteTransaction() throws InvalidTransactionTypeKernelException { if( !accessMode.allowsWrites() ) { throw new InvalidTransactionTypeKernelException( String.format( "Write operations are not allowed for `%s` transactions.", accessMode.name() ) ); } - transactionType = transactionType.upgradeToDataTransaction(); + transactionType = transactionType.enableDataWriteTransaction(); } - public void upgradeToSchemaTransaction() throws InvalidTransactionTypeKernelException + public void verifySchemaWriteTransaction() throws InvalidTransactionTypeKernelException { if( !accessMode.allowsSchemaWrites() ) { @@ -270,7 +299,7 @@ public void upgradeToSchemaTransaction() throws InvalidTransactionTypeKernelExce String.format( "Schema write operations are not allowed for `%s` transactions.", accessMode.name() ) ); } doUpgradeToSchemaTransaction(); - transactionType = transactionType.upgradeToSchemaTransaction(); + transactionType = transactionType.enableSchemaWriteTransaction(); } public void doUpgradeToSchemaTransaction() throws InvalidTransactionTypeKernelException diff --git a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/KernelTransactionAccessModeTest.java b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/KernelTransactionAccessModeTest.java index 5ed50b430d0e2..4da15f2e0048b 100644 --- a/community/kernel/src/test/java/org/neo4j/kernel/impl/api/KernelTransactionAccessModeTest.java +++ b/community/kernel/src/test/java/org/neo4j/kernel/impl/api/KernelTransactionAccessModeTest.java @@ -26,6 +26,7 @@ import org.neo4j.kernel.api.AccessMode; import org.neo4j.kernel.api.DataWriteOperations; import org.neo4j.kernel.api.KernelTransactionTestBase; +import org.neo4j.kernel.api.ReadOperations; import org.neo4j.kernel.api.SchemaWriteOperations; import org.neo4j.kernel.api.exceptions.KernelException; @@ -35,6 +36,19 @@ public class KernelTransactionAccessModeTest extends KernelTransactionTestBase { @Rule public ExpectedException exception = ExpectedException.none(); + @Test + public void shouldAllowReadsInReadMode() throws Throwable + { + // Given + KernelTransactionImplementation tx = newTransaction( AccessMode.READ ); + + // When + ReadOperations reads = tx.acquireStatement().readOperations(); + + // Then + assertNotNull( reads ); + } + @Test public void shouldNotAllowWriteAccessInReadMode() throws Throwable { @@ -62,10 +76,36 @@ public void shouldNotAllowSchemaWriteAccessInReadMode() throws Throwable } @Test - public void shouldNotAllowSchemaWriteAccessInWriteMode() throws Throwable + public void shouldNotAllowReadAccessInWriteOnlyMode() throws Throwable { // Given - KernelTransactionImplementation tx = newTransaction( AccessMode.WRITE ); + KernelTransactionImplementation tx = newTransaction( AccessMode.WRITE_ONLY ); + + // Expect + exception.expect( IllegalStateException.class ); + + // When + tx.acquireStatement().readOperations(); + } + + @Test + public void shouldAllowWriteAccessInWriteOnlyMode() throws Throwable + { + // Given + KernelTransactionImplementation tx = newTransaction( AccessMode.WRITE_ONLY ); + + // When + DataWriteOperations writes = tx.acquireStatement().dataWriteOperations(); + + // Then + assertNotNull( writes ); + } + + @Test + public void shouldNotAllowSchemaWriteAccessInWriteOnlyMode() throws Throwable + { + // Given + KernelTransactionImplementation tx = newTransaction( AccessMode.WRITE_ONLY ); // Expect exception.expect( KernelException.class ); @@ -74,6 +114,19 @@ public void shouldNotAllowSchemaWriteAccessInWriteMode() throws Throwable tx.acquireStatement().schemaWriteOperations(); } + @Test + public void shouldAllowReadsInWriteMode() throws Throwable + { + // Given + KernelTransactionImplementation tx = newTransaction( AccessMode.WRITE ); + + // When + ReadOperations reads = tx.acquireStatement().readOperations(); + + // Then + assertNotNull( reads ); + } + @Test public void shouldAllowWritesInWriteMode() throws Throwable { @@ -87,6 +140,32 @@ public void shouldAllowWritesInWriteMode() throws Throwable assertNotNull( writes ); } + @Test + public void shouldNotAllowSchemaWriteAccessInWriteMode() throws Throwable + { + // Given + KernelTransactionImplementation tx = newTransaction( AccessMode.WRITE ); + + // Expect + exception.expect( KernelException.class ); + + // When + tx.acquireStatement().schemaWriteOperations(); + } + + @Test + public void shouldAllowReadsInFullMode() throws Throwable + { + // Given + KernelTransactionImplementation tx = newTransaction( AccessMode.FULL ); + + // When + ReadOperations reads = tx.acquireStatement().readOperations(); + + // Then + assertNotNull( reads ); + } + @Test public void shouldAllowWritesInFullMode() throws Throwable {