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

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

Go to download

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

The newest version!
package com.scalar.db.transaction.consensuscommit;

import com.google.common.collect.ImmutableMap;
import com.scalar.db.api.ConditionBuilder;
import com.scalar.db.api.Insert;
import com.scalar.db.api.MutationCondition;
import com.scalar.db.api.Put;
import com.scalar.db.api.PutBuilder;
import com.scalar.db.api.TableMetadata;
import com.scalar.db.api.Update;
import com.scalar.db.api.UpdateIf;
import com.scalar.db.api.UpdateIfExists;
import com.scalar.db.api.Upsert;
import com.scalar.db.common.error.CoreError;
import com.scalar.db.exception.transaction.UnsatisfiedConditionException;
import com.scalar.db.io.DataType;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public final class ConsensusCommitUtils {

  private static final ImmutableMap BEFORE_IMAGE_META_COLUMNS =
      ImmutableMap.builder()
          .put(Attribute.BEFORE_ID, DataType.TEXT)
          .put(Attribute.BEFORE_STATE, DataType.INT)
          .put(Attribute.BEFORE_VERSION, DataType.INT)
          .put(Attribute.BEFORE_PREPARED_AT, DataType.BIGINT)
          .put(Attribute.BEFORE_COMMITTED_AT, DataType.BIGINT)
          .build();

  private static final ImmutableMap AFTER_IMAGE_META_COLUMNS =
      ImmutableMap.builder()
          .put(Attribute.ID, DataType.TEXT)
          .put(Attribute.STATE, DataType.INT)
          .put(Attribute.VERSION, DataType.INT)
          .put(Attribute.PREPARED_AT, DataType.BIGINT)
          .put(Attribute.COMMITTED_AT, DataType.BIGINT)
          .build();

  private static final ImmutableMap TRANSACTION_META_COLUMNS =
      ImmutableMap.builder()
          .putAll(AFTER_IMAGE_META_COLUMNS)
          .putAll(BEFORE_IMAGE_META_COLUMNS)
          .build();

  private ConsensusCommitUtils() {}

  /**
   * Builds a transaction table metadata based on the specified table metadata.
   *
   * @param tableMetadata the base table metadata to build a transaction table metadata
   * @return a transaction table metadata based on the table metadata
   */
  public static TableMetadata buildTransactionTableMetadata(TableMetadata tableMetadata) {
    checkIsNotTransactionMetaColumn(tableMetadata.getColumnNames());
    Set nonPrimaryKeyColumns = getNonPrimaryKeyColumns(tableMetadata);
    checkBeforeColumnsDoNotAlreadyExist(nonPrimaryKeyColumns, tableMetadata);

    // Build a transaction table metadata
    TableMetadata.Builder builder = TableMetadata.newBuilder(tableMetadata);
    TRANSACTION_META_COLUMNS.forEach(builder::addColumn);
    nonPrimaryKeyColumns.forEach(
        c -> builder.addColumn(Attribute.BEFORE_PREFIX + c, tableMetadata.getColumnDataType(c)));
    return builder.build();
  }

  private static void checkIsNotTransactionMetaColumn(Set columnNames) {
    TRANSACTION_META_COLUMNS
        .keySet()
        .forEach(
            c -> {
              if (columnNames.contains(c)) {
                throw new IllegalArgumentException(
                    CoreError.CONSENSUS_COMMIT_COLUMN_RESERVED_AS_TRANSACTION_METADATA.buildMessage(
                        c));
              }
            });
  }

  private static void checkBeforeColumnsDoNotAlreadyExist(
      Set nonPrimaryKeyColumns, TableMetadata tableMetadata) {
    nonPrimaryKeyColumns.forEach(
        c -> {
          String beforePrefixed = Attribute.BEFORE_PREFIX + c;
          if (tableMetadata.getColumnNames().contains(beforePrefixed)) {
            throw new IllegalArgumentException(
                CoreError
                    .CONSENSUS_COMMIT_BEFORE_PREFIXED_COLUMN_FOR_NON_PRIMARY_KEY_RESERVED_AS_TRANSACTION_METADATA
                    .buildMessage(beforePrefixed));
          }
        });
  }

  public static String getBeforeImageColumnName(String columnName, TableMetadata tableMetadata) {
    checkIsNotTransactionMetaColumn(Collections.singleton(columnName));
    checkBeforeColumnsDoNotAlreadyExist(Collections.singleton(columnName), tableMetadata);

    return Attribute.BEFORE_PREFIX + columnName;
  }

  /**
   * Returns whether the specified table metadata is transactional.
   *
   * 

This method checks all the transaction meta columns including the before prefix column, and * if any of them is missing, it returns false. * * @param tableMetadata a table metadata * @return whether the table metadata is transactional */ public static boolean isTransactionTableMetadata(TableMetadata tableMetadata) { // if the table metadata doesn't have the transaction meta columns, it's not transactional for (String column : TRANSACTION_META_COLUMNS.keySet()) { if (!tableMetadata.getColumnNames().contains(column)) { return false; } } // if the table metadata doesn't have the before prefix columns, it's not transactional for (String nonPrimaryKeyColumn : getNonPrimaryKeyColumns(tableMetadata)) { if (TRANSACTION_META_COLUMNS.containsKey(nonPrimaryKeyColumn)) { continue; } // check if a column that has either the following name exists or not: // - "before_" + the column name // - the column name without the "before_" prefix // if both columns don't exist, the table metadata is not transactional if (!tableMetadata.getColumnNames().contains(Attribute.BEFORE_PREFIX + nonPrimaryKeyColumn) && !(nonPrimaryKeyColumn.startsWith(Attribute.BEFORE_PREFIX) && tableMetadata .getColumnNames() .contains(nonPrimaryKeyColumn.substring(Attribute.BEFORE_PREFIX.length())))) { return false; } } return true; } /** * Get non-primary key columns from the specified table metadata. * * @param tableMetadata a table metadata * @return a set of non-primary key column names */ public static Set getNonPrimaryKeyColumns(TableMetadata tableMetadata) { return tableMetadata.getColumnNames().stream() .filter(c -> !tableMetadata.getPartitionKeyNames().contains(c)) .filter(c -> !tableMetadata.getClusteringKeyNames().contains(c)) .collect(Collectors.toSet()); } /** * Get transaction meta columns. * * @return a map of transaction meta columns */ public static Map getTransactionMetaColumns() { return TRANSACTION_META_COLUMNS; } /** * Removes transaction meta columns from the specified table metadata. * * @param tableMetadata a transaction table metadata * @return a table metadata without transaction meta columns */ public static TableMetadata removeTransactionMetaColumns(TableMetadata tableMetadata) { Set transactionMetaColumns = new HashSet<>(TRANSACTION_META_COLUMNS.keySet()); transactionMetaColumns.addAll( tableMetadata.getColumnNames().stream() .filter(c -> c.startsWith(Attribute.BEFORE_PREFIX)) .filter( c -> tableMetadata .getColumnNames() .contains(c.substring(Attribute.BEFORE_PREFIX.length()))) .collect(Collectors.toSet())); TableMetadata.Builder builder = TableMetadata.newBuilder(); tableMetadata.getPartitionKeyNames().forEach(builder::addPartitionKey); tableMetadata .getClusteringKeyNames() .forEach(c -> builder.addClusteringKey(c, tableMetadata.getClusteringOrder(c))); tableMetadata.getColumnNames().stream() .filter(c -> !transactionMetaColumns.contains(c)) .forEach(c -> builder.addColumn(c, tableMetadata.getColumnDataType(c))); tableMetadata.getSecondaryIndexNames().forEach(builder::addSecondaryIndex); return builder.build(); } /** * Returns whether the specified column is a transaction meta column or not. * * @param columnName a column name * @param tableMetadata a transaction table metadata * @return whether the specified column is a transaction meta column or not */ public static boolean isTransactionMetaColumn(String columnName, TableMetadata tableMetadata) { return AFTER_IMAGE_META_COLUMNS.containsKey(columnName) || isBeforeImageColumn(columnName, tableMetadata); } /** * Returns whether the specified column is a part of the before image columns or not. * * @param columnName a column name * @param tableMetadata a transaction table metadata * @return whether the specified column is a part of the before image columns or not */ public static boolean isBeforeImageColumn(String columnName, TableMetadata tableMetadata) { if (!tableMetadata.getColumnNames().contains(columnName) || tableMetadata.getPartitionKeyNames().contains(columnName) || tableMetadata.getClusteringKeyNames().contains(columnName)) { return false; } if (BEFORE_IMAGE_META_COLUMNS.containsKey(columnName)) { return true; } if (columnName.startsWith(Attribute.BEFORE_PREFIX)) { // if the column name without the "before_" prefix exists, it's a part of the before image // columns return tableMetadata .getColumnNames() .contains(columnName.substring(Attribute.BEFORE_PREFIX.length())); } return false; } /** * Returns whether the specified column is a part of the after image columns or not. * * @param columnName a column name * @param tableMetadata a transaction table metadata * @return whether the specified column is a part of the after image columns or not */ public static boolean isAfterImageColumn(String columnName, TableMetadata tableMetadata) { if (!tableMetadata.getColumnNames().contains(columnName)) { return false; } return !isBeforeImageColumn(columnName, tableMetadata); } static Put createPutForInsert(Insert insert) { PutBuilder.Buildable buildable = Put.newBuilder() .namespace(insert.forNamespace().orElse(null)) .table(insert.forTable().orElse(null)) .partitionKey(insert.getPartitionKey()); insert.getClusteringKey().ifPresent(buildable::clusteringKey); insert.getColumns().values().forEach(buildable::value); buildable.enableInsertMode(); return buildable.build(); } static Put createPutForUpsert(Upsert upsert) { PutBuilder.Buildable buildable = Put.newBuilder() .namespace(upsert.forNamespace().orElse(null)) .table(upsert.forTable().orElse(null)) .partitionKey(upsert.getPartitionKey()); upsert.getClusteringKey().ifPresent(buildable::clusteringKey); upsert.getColumns().values().forEach(buildable::value); buildable.enableImplicitPreRead(); return buildable.build(); } static Put createPutForUpdate(Update update) { PutBuilder.Buildable buildable = Put.newBuilder() .namespace(update.forNamespace().orElse(null)) .table(update.forTable().orElse(null)) .partitionKey(update.getPartitionKey()); update.getClusteringKey().ifPresent(buildable::clusteringKey); update.getColumns().values().forEach(buildable::value); if (update.getCondition().isPresent()) { if (update.getCondition().get() instanceof UpdateIf) { update .getCondition() .ifPresent(c -> buildable.condition(ConditionBuilder.putIf(c.getExpressions()))); } else { assert update.getCondition().get() instanceof UpdateIfExists; buildable.condition(ConditionBuilder.putIfExists()); } } else { buildable.condition(ConditionBuilder.putIfExists()); } buildable.enableImplicitPreRead(); return buildable.build(); } static String convertUnsatisfiedConditionExceptionMessageForUpdate( UnsatisfiedConditionException e, MutationCondition condition) { String message = e.getMessage(); if (message.contains("PutIf") || message.contains("PutIfExists")) { return message.replaceFirst("PutIf|PutIfExists", condition.getClass().getSimpleName()); } return message; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy