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

com.scalar.db.transaction.consensuscommit.MutationConditionsValidator Maven / Gradle / Ivy

Go to download

A universal transaction manager that achieves database-agnostic transactions and distributed transactions that span multiple databases

There is a newer version: 3.14.0
Show newest version
package com.scalar.db.transaction.consensuscommit;

import com.google.common.collect.Ordering;
import com.scalar.db.api.ConditionalExpression;
import com.scalar.db.api.ConditionalExpression.Operator;
import com.scalar.db.api.Delete;
import com.scalar.db.api.DeleteIf;
import com.scalar.db.api.DeleteIfExists;
import com.scalar.db.api.MutationCondition;
import com.scalar.db.api.Put;
import com.scalar.db.api.PutIf;
import com.scalar.db.api.PutIfExists;
import com.scalar.db.api.PutIfNotExists;
import com.scalar.db.exception.transaction.UnsatisfiedConditionException;
import com.scalar.db.io.Column;
import java.util.List;
import javax.annotation.Nullable;
import javax.annotation.concurrent.ThreadSafe;

/**
 * This class checks if a record satisfies the conditions of Put and Delete operations that mutate
 * the record.
 */
@ThreadSafe
public class MutationConditionsValidator {
  private final String transactionId;

  public MutationConditionsValidator(String transactionId) {
    this.transactionId = transactionId;
  }

  /**
   * This checks if the condition of the specified Put operation is satisfied for the specified
   * record.
   *
   * @param put a Put operation
   * @param existingRecord the current value of the record targeted by the mutation, if any
   * @throws UnsatisfiedConditionException if the condition is not satisfied
   */
  public void checkIfConditionIsSatisfied(Put put, @Nullable TransactionResult existingRecord)
      throws UnsatisfiedConditionException {
    if (!put.getCondition().isPresent()) {
      return;
    }
    MutationCondition condition = put.getCondition().get();
    boolean recordExists = existingRecord != null;
    if (condition instanceof PutIf) {
      if (recordExists) {
        validateConditionalExpressions(condition.getExpressions(), existingRecord);
      } else {
        throwWhenRecordDoesNotExist(condition);
      }
    } else if (condition instanceof PutIfExists) {
      if (!recordExists) {
        throwWhenRecordDoesNotExist(condition);
      }
    } else if (condition instanceof PutIfNotExists) {
      if (recordExists) {
        throwWhenRecordExists(condition);
      }
    } else {
      throw new AssertionError();
    }
  }

  /**
   * This checks if the condition of the specified Delete operation is satisfied for the specified
   * record.
   *
   * @param delete a Delete operation
   * @param existingRecord the current value of the record targeted by the mutation, if any
   * @throws UnsatisfiedConditionException if the condition is not satisfied
   */
  public void checkIfConditionIsSatisfied(Delete delete, @Nullable TransactionResult existingRecord)
      throws UnsatisfiedConditionException {
    if (!delete.getCondition().isPresent()) {
      return;
    }
    MutationCondition condition = delete.getCondition().get();
    boolean recordExists = existingRecord != null;
    if (condition instanceof DeleteIf) {
      if (recordExists) {
        validateConditionalExpressions(condition.getExpressions(), existingRecord);
      } else {
        throwWhenRecordDoesNotExist(condition);
      }
    } else if (condition instanceof DeleteIfExists) {
      if (!recordExists) {
        throwWhenRecordDoesNotExist(condition);
      }
    } else {
      throw new AssertionError();
    }
  }

  private void throwWhenRecordDoesNotExist(MutationCondition condition)
      throws UnsatisfiedConditionException {
    throw new UnsatisfiedConditionException(
        "The record does not exist so the "
            + condition.getClass().getSimpleName()
            + " condition is not satisfied",
        transactionId);
  }

  private void throwWhenRecordExists(MutationCondition condition)
      throws UnsatisfiedConditionException {
    throw new UnsatisfiedConditionException(
        "The record exists so the "
            + condition.getClass().getSimpleName()
            + " condition is not satisfied",
        transactionId);
  }

  private void validateConditionalExpressions(
      List conditionalExpressions, TransactionResult existingRecord)
      throws UnsatisfiedConditionException {
    for (ConditionalExpression conditionalExpression : conditionalExpressions) {
      if (!shouldMutate(
          existingRecord.getColumns().get(conditionalExpression.getColumn().getName()),
          conditionalExpression.getColumn(),
          conditionalExpression.getOperator())) {
        throw new UnsatisfiedConditionException(
            "The condition on the column '"
                + conditionalExpression.getColumn().getName()
                + "' is not satisfied",
            transactionId);
      }
    }
  }

  private boolean shouldMutate(
      Column existingRecordColumn, Column conditionalExpressionColumn, Operator operator) {
    switch (operator) {
      case IS_NULL:
        return existingRecordColumn.hasNullValue();
      case IS_NOT_NULL:
        return !existingRecordColumn.hasNullValue();
      case EQ:
        return Ordering.natural().compare(existingRecordColumn, conditionalExpressionColumn) == 0;
      case NE:
        return Ordering.natural().compare(existingRecordColumn, conditionalExpressionColumn) != 0;
        // For 'greater than' and 'less than' types of conditions and when the existing record is
        // null, we consider the condition to be unsatisfied. This mimics the behavior as if
        // the condition was executed by the underlying storage
      case GT:
        return !existingRecordColumn.hasNullValue()
            && Ordering.natural().compare(existingRecordColumn, conditionalExpressionColumn) > 0;
      case GTE:
        return !existingRecordColumn.hasNullValue()
            && Ordering.natural().compare(existingRecordColumn, conditionalExpressionColumn) >= 0;
      case LT:
        return !existingRecordColumn.hasNullValue()
            && Ordering.natural().compare(existingRecordColumn, conditionalExpressionColumn) < 0;
      case LTE:
        return !existingRecordColumn.hasNullValue()
            && Ordering.natural().compare(existingRecordColumn, conditionalExpressionColumn) <= 0;
      default:
        throw new AssertionError();
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy