Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import java.util.Properties;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;

public class JdbcTransactionIntegrationTest extends DistributedTransactionIntegrationTestBase {

Expand Down Expand Up @@ -42,4 +44,16 @@ public void abort_forOngoingTransaction_ShouldAbortCorrectly() {}
@Override
@Test
public void rollback_forOngoingTransaction_ShouldRollbackCorrectly() {}

@Disabled("Implement later")
@Override
@Test
public void get_GetGivenForCommittedRecord_InReadOnlyMode_ShouldReturnRecord() {}

@Disabled("Implement later")
@Override
@ParameterizedTest
@EnumSource(ScanType.class)
public void scanOrGetScanner_ScanGivenForCommittedRecord_InReadOnlyMode_ShouldReturnRecords(
ScanType scanType) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,34 @@ public interface DistributedTransactionManager
DistributedTransaction begin(String txId)
throws TransactionNotFoundException, TransactionException;

/**
* Begins a new transaction in read-only mode.
*
* @return {@link DistributedTransaction}
* @throws TransactionNotFoundException if the transaction fails to begin due to transient faults.
* You can retry the transaction
* @throws TransactionException if the transaction fails to begin due to transient or nontransient
* faults. You can try retrying the transaction, but you may not be able to begin the
* transaction due to nontransient faults
*/
DistributedTransaction beginReadOnly() throws TransactionNotFoundException, TransactionException;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added several methods for the read-only mode.


/**
* Begins a new transaction with the specified transaction ID in read-only mode. It is users'
* responsibility to guarantee uniqueness of the ID, so it is not recommended to use this method
* unless you know exactly what you are doing.
*
* @param txId an user-provided unique transaction ID
* @return {@link DistributedTransaction}
* @throws TransactionNotFoundException if the transaction fails to begin due to transient faults.
* You can retry the transaction
* @throws TransactionException if the transaction fails to begin due to transient or nontransient
* faults. You can try retrying the transaction, but you may not be able to begin the
* transaction due to nontransient faults
*/
DistributedTransaction beginReadOnly(String txId)
throws TransactionNotFoundException, TransactionException;

/**
* Starts a new transaction. This method is an alias of {@link #begin()}.
*
Expand Down Expand Up @@ -112,6 +140,39 @@ default DistributedTransaction start(String txId)
return begin(txId);
}

/**
* Starts a new transaction in read-only mode. This method is an alias of {@link
* #beginReadOnly()}.
*
* @return {@link DistributedTransaction}
* @throws TransactionNotFoundException if the transaction fails to start due to transient faults.
* You can retry the transaction
* @throws TransactionException if the transaction fails to start due to transient or nontransient
* faults. You can try retrying the transaction, but you may not be able to start the
* transaction due to nontransient faults
*/
default DistributedTransaction startReadOnly()
throws TransactionNotFoundException, TransactionException {
return beginReadOnly();
}

/**
* Starts a new transaction with the specified transaction ID in read-only mode. This method is an
* alias of {@link #beginReadOnly(String)}.
*
* @param txId an user-provided unique transaction ID
* @return {@link DistributedTransaction}
* @throws TransactionNotFoundException if the transaction fails to start due to transient faults.
* You can retry the transaction
* @throws TransactionException if the transaction fails to start due to transient or nontransient
* faults. You can try retrying the transaction, but you may not be able to start the
* transaction due to nontransient faults
*/
default DistributedTransaction startReadOnly(String txId)
throws TransactionNotFoundException, TransactionException {
return beginReadOnly(txId);
}

/**
* Starts a new transaction with the specified {@link Isolation} level.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import javax.annotation.concurrent.ThreadSafe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@ThreadSafe
public class ActiveTransactionManagedDistributedTransactionManager
extends DecoratedDistributedTransactionManager {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiConsumer;
import javax.annotation.concurrent.ThreadSafe;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@ThreadSafe
public class ActiveTransactionManagedTwoPhaseCommitTransactionManager
extends DecoratedTwoPhaseCommitTransactionManager {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@ public DistributedTransaction begin(String txId) throws TransactionException {
return decorateTransactionOnBeginOrStart(transactionManager.begin(txId));
}

@Override
public DistributedTransaction beginReadOnly() throws TransactionException {
return decorateTransactionOnBeginOrStart(transactionManager.beginReadOnly());
}

@Override
public DistributedTransaction beginReadOnly(String txId) throws TransactionException {
return decorateTransactionOnBeginOrStart(transactionManager.beginReadOnly(txId));
}

@Override
public DistributedTransaction start() throws TransactionException {
return decorateTransactionOnBeginOrStart(transactionManager.start());
Expand All @@ -86,6 +96,16 @@ public DistributedTransaction start(String txId) throws TransactionException {
return decorateTransactionOnBeginOrStart(transactionManager.start(txId));
}

@Override
public DistributedTransaction startReadOnly(String txId) throws TransactionException {
return decorateTransactionOnBeginOrStart(transactionManager.startReadOnly(txId));
}

@Override
public DistributedTransaction startReadOnly() throws TransactionException {
return decorateTransactionOnBeginOrStart(transactionManager.startReadOnly());
}

/** @deprecated As of release 2.4.0. Will be removed in release 4.0.0. */
@Deprecated
@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.scalar.db.common;

import com.scalar.db.api.Delete;
import com.scalar.db.api.DistributedTransaction;
import com.scalar.db.api.Insert;
import com.scalar.db.api.Mutation;
import com.scalar.db.api.Put;
import com.scalar.db.api.Update;
import com.scalar.db.api.Upsert;
import com.scalar.db.common.error.CoreError;
import com.scalar.db.exception.transaction.CrudException;
import java.util.List;
import javax.annotation.concurrent.NotThreadSafe;

@NotThreadSafe
public class ReadOnlyDistributedTransaction extends DecoratedDistributedTransaction {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a wrapper class to enforce immutability for read-only transactions.


public ReadOnlyDistributedTransaction(DistributedTransaction transaction) {
super(transaction);
}

/** @deprecated As of release 3.13.0. Will be removed in release 5.0.0. */
@Deprecated
@Override
public void put(Put put) throws CrudException {
throw new IllegalStateException(
CoreError.MUTATION_NOT_ALLOWED_IN_READ_ONLY_TRANSACTION.buildMessage(getId()));
}

/** @deprecated As of release 3.13.0. Will be removed in release 5.0.0. */
@Deprecated
@Override
public void put(List<Put> puts) throws CrudException {
throw new IllegalStateException(
CoreError.MUTATION_NOT_ALLOWED_IN_READ_ONLY_TRANSACTION.buildMessage(getId()));
}

@Override
public void insert(Insert insert) throws CrudException {
throw new IllegalStateException(
CoreError.MUTATION_NOT_ALLOWED_IN_READ_ONLY_TRANSACTION.buildMessage(getId()));
}

@Override
public void upsert(Upsert upsert) throws CrudException {
throw new IllegalStateException(
CoreError.MUTATION_NOT_ALLOWED_IN_READ_ONLY_TRANSACTION.buildMessage(getId()));
}

@Override
public void update(Update update) throws CrudException {
throw new IllegalStateException(
CoreError.MUTATION_NOT_ALLOWED_IN_READ_ONLY_TRANSACTION.buildMessage(getId()));
}

@Override
public void delete(Delete delete) throws CrudException {
throw new IllegalStateException(
CoreError.MUTATION_NOT_ALLOWED_IN_READ_ONLY_TRANSACTION.buildMessage(getId()));
}

/** @deprecated As of release 3.13.0. Will be removed in release 5.0.0. */
@Deprecated
@Override
public void delete(List<Delete> deletes) throws CrudException {
throw new IllegalStateException(
CoreError.MUTATION_NOT_ALLOWED_IN_READ_ONLY_TRANSACTION.buildMessage(getId()));
}

@Override
public void mutate(List<? extends Mutation> mutations) throws CrudException {
throw new IllegalStateException(
CoreError.MUTATION_NOT_ALLOWED_IN_READ_ONLY_TRANSACTION.buildMessage(getId()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
import com.scalar.db.exception.transaction.UnknownTransactionStatusException;
import java.util.List;
import java.util.Optional;
import javax.annotation.concurrent.ThreadSafe;

@ThreadSafe
public class StateManagedDistributedTransactionManager
extends DecoratedDistributedTransactionManager {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
import com.scalar.db.exception.transaction.ValidationException;
import java.util.List;
import java.util.Optional;
import javax.annotation.concurrent.ThreadSafe;

@ThreadSafe
public class StateManagedTwoPhaseCommitTransactionManager
extends DecoratedTwoPhaseCommitTransactionManager {

Expand Down
6 changes: 6 additions & 0 deletions core/src/main/java/com/scalar/db/common/error/CoreError.java
Original file line number Diff line number Diff line change
Expand Up @@ -923,6 +923,12 @@ public enum CoreError implements ScalarDbError {
"Some scanners were not closed. All scanners must be closed before preparing the transaction.",
"",
""),
MUTATION_NOT_ALLOWED_IN_READ_ONLY_TRANSACTION(
Category.USER_ERROR,
"0207",
"Mutations are not allowed in read-only transactions. Transaction ID: %s",
"",
""),

//
// Errors for the concurrency error category
Expand Down
20 changes: 20 additions & 0 deletions core/src/main/java/com/scalar/db/service/TransactionService.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,16 @@ public DistributedTransaction begin(String txId) throws TransactionException {
return manager.begin(txId);
}

@Override
public DistributedTransaction beginReadOnly() throws TransactionException {
return manager.beginReadOnly();
}

@Override
public DistributedTransaction beginReadOnly(String txId) throws TransactionException {
return manager.beginReadOnly(txId);
}

@Override
public DistributedTransaction start() throws TransactionException {
return manager.start();
Expand All @@ -91,6 +101,16 @@ public DistributedTransaction start(String txId) throws TransactionException {
return manager.start(txId);
}

@Override
public DistributedTransaction startReadOnly() throws TransactionException {
return manager.startReadOnly();
}

@Override
public DistributedTransaction startReadOnly(String txId) throws TransactionException {
return manager.startReadOnly(txId);
}

/** @deprecated As of release 2.4.0. Will be removed in release 4.0.0. */
@Deprecated
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,16 @@ public DistributedTransaction begin(String txId) {
return begin(txId, config.getIsolation());
}

@Override
public DistributedTransaction beginReadOnly() {
throw new UnsupportedOperationException("implement later");
}

@Override
public DistributedTransaction beginReadOnly(String txId) {
throw new UnsupportedOperationException("implement later");
}
Comment on lines +148 to +156
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will add an implementation for Consensus Commit in a separate PR after this PR is merged.


/** @deprecated As of release 2.4.0. Will be removed in release 4.0.0. */
@Deprecated
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,16 @@ public DistributedTransaction begin(String txId) throws TransactionException {
}
}

@Override
public DistributedTransaction beginReadOnly() {
throw new UnsupportedOperationException("implement later");
Copy link

Copilot AI Jun 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider replacing the placeholder error message with a more descriptive and consistent message (or a reference to a constant error code) similar to the one used in the SingleCrudOperationTransactionManager.

Copilot uses AI. Check for mistakes.
}

@Override
public DistributedTransaction beginReadOnly(String txId) {
throw new UnsupportedOperationException("implement later");
}
Comment on lines +112 to +120
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will add an implementation for JDBC transactions in a separate PR after this PR is merged.


/** @deprecated As of release 2.4.0. Will be removed in release 4.0.0. */
@SuppressWarnings("InlineMeSuggester")
@Deprecated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,20 @@ public DistributedTransaction begin(String txId) throws TransactionException {
.buildMessage());
}

@Override
public DistributedTransaction beginReadOnly() throws TransactionException {
throw new UnsupportedOperationException(
CoreError.SINGLE_CRUD_OPERATION_TRANSACTION_BEGINNING_TRANSACTION_NOT_ALLOWED
.buildMessage());
}

@Override
public DistributedTransaction beginReadOnly(String txId) throws TransactionException {
throw new UnsupportedOperationException(
CoreError.SINGLE_CRUD_OPERATION_TRANSACTION_BEGINNING_TRANSACTION_NOT_ALLOWED
.buildMessage());
}
Comment on lines +79 to +91
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the Single Crud Operation transaction manager, just throw UnsupportedOperationException, since the Single Crud Operation transaction manager doesn't support beginning transactions.


/** @deprecated As of release 2.4.0. Will be removed in release 4.0.0. */
@Deprecated
@Override
Expand Down
Loading