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

com.scalar.db.storage.cassandra.CassandraAdmin 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.storage.cassandra;

import static com.datastax.driver.core.Metadata.quoteIfNecessary;
import static com.scalar.db.util.ScalarDbUtils.getFullTableName;

import com.datastax.driver.core.ClusteringOrder;
import com.datastax.driver.core.ColumnMetadata;
import com.datastax.driver.core.KeyspaceMetadata;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.schemabuilder.Create;
import com.datastax.driver.core.schemabuilder.CreateKeyspace;
import com.datastax.driver.core.schemabuilder.SchemaBuilder;
import com.datastax.driver.core.schemabuilder.SchemaBuilder.Direction;
import com.datastax.driver.core.schemabuilder.SchemaStatement;
import com.datastax.driver.core.schemabuilder.TableOptions.CompactionOptions;
import com.google.common.annotations.VisibleForTesting;
import com.google.inject.Inject;
import com.scalar.db.api.DistributedStorageAdmin;
import com.scalar.db.api.Scan;
import com.scalar.db.api.Scan.Ordering.Order;
import com.scalar.db.api.TableMetadata;
import com.scalar.db.config.DatabaseConfig;
import com.scalar.db.exception.storage.ExecutionException;
import com.scalar.db.io.DataType;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.annotation.concurrent.ThreadSafe;

@ThreadSafe
public class CassandraAdmin implements DistributedStorageAdmin {

  public static final String REPLICATION_STRATEGY = "replication-strategy";
  public static final String COMPACTION_STRATEGY = "compaction-strategy";
  public static final String REPLICATION_FACTOR = "replication-factor";
  @VisibleForTesting static final String INDEX_NAME_PREFIX = "index";

  private final ClusterManager clusterManager;

  @Inject
  public CassandraAdmin(DatabaseConfig config) {
    clusterManager = new ClusterManager(config);
  }

  CassandraAdmin(ClusterManager clusterManager) {
    this.clusterManager = clusterManager;
  }

  @Override
  public void createTable(
      String namespace, String table, TableMetadata metadata, Map options)
      throws ExecutionException {
    createTableInternal(namespace, table, metadata, options);
    createSecondaryIndexes(namespace, table, metadata.getSecondaryIndexNames(), options);
  }

  @Override
  public void createNamespace(String namespace, Map options)
      throws ExecutionException {
    CreateKeyspace query = SchemaBuilder.createKeyspace(quoteIfNecessary(namespace));
    String replicationFactor = options.getOrDefault(REPLICATION_FACTOR, "1");
    ReplicationStrategy replicationStrategy =
        options.containsKey(REPLICATION_STRATEGY)
            ? ReplicationStrategy.fromString(options.get(REPLICATION_STRATEGY))
            : ReplicationStrategy.SIMPLE_STRATEGY;
    Map replicationOptions = new LinkedHashMap<>();
    if (replicationStrategy == ReplicationStrategy.SIMPLE_STRATEGY) {
      replicationOptions.put("class", ReplicationStrategy.SIMPLE_STRATEGY.toString());
      replicationOptions.put("replication_factor", replicationFactor);
    } else if (replicationStrategy == ReplicationStrategy.NETWORK_TOPOLOGY_STRATEGY) {
      replicationOptions.put("class", ReplicationStrategy.NETWORK_TOPOLOGY_STRATEGY.toString());
      replicationOptions.put("dc1", replicationFactor);
    }
    try {
      clusterManager
          .getSession()
          .execute(query.with().replication(replicationOptions).getQueryString());
    } catch (RuntimeException e) {
      throw new ExecutionException(String.format("creating the keyspace %s failed", namespace), e);
    }
  }

  @Override
  public void dropTable(String namespace, String table) throws ExecutionException {
    String dropTableQuery =
        SchemaBuilder.dropTable(quoteIfNecessary(namespace), quoteIfNecessary(table))
            .getQueryString();
    try {
      clusterManager.getSession().execute(dropTableQuery);
    } catch (RuntimeException e) {
      throw new ExecutionException(
          String.format("dropping the %s table failed", getFullTableName(namespace, table)), e);
    }
  }

  @Override
  public void dropNamespace(String namespace) throws ExecutionException {
    String dropKeyspace = SchemaBuilder.dropKeyspace(quoteIfNecessary(namespace)).getQueryString();
    try {
      clusterManager.getSession().execute(dropKeyspace);
    } catch (RuntimeException e) {
      throw new ExecutionException(String.format("dropping the %s keyspace failed", namespace), e);
    }
  }

  @Override
  public void truncateTable(String namespace, String table) throws ExecutionException {
    String truncateTableQuery =
        QueryBuilder.truncate(quoteIfNecessary(namespace), quoteIfNecessary(table))
            .getQueryString();
    try {
      clusterManager.getSession().execute(truncateTableQuery);
    } catch (RuntimeException e) {
      throw new ExecutionException(
          String.format("truncating the %s table failed", getFullTableName(namespace, table)), e);
    }
  }

  @Override
  public void createIndex(
      String namespace, String table, String columnName, Map options)
      throws ExecutionException {
    String indexName = getIndexName(table, columnName);
    SchemaStatement createIndex =
        SchemaBuilder.createIndex(indexName)
            .onTable(quoteIfNecessary(namespace), quoteIfNecessary(table))
            .andColumn(quoteIfNecessary(columnName));
    try {
      clusterManager.getSession().execute(createIndex.getQueryString());
    } catch (RuntimeException e) {
      throw new ExecutionException(
          String.format(
              "creating the secondary index for %s.%s.%s failed", namespace, table, columnName),
          e);
    }
  }

  @Override
  public void dropIndex(String namespace, String table, String columnName)
      throws ExecutionException {
    String indexName = getIndexName(table, columnName);
    SchemaStatement dropIndex = SchemaBuilder.dropIndex(quoteIfNecessary(namespace), indexName);
    try {
      clusterManager.getSession().execute(dropIndex.getQueryString());
    } catch (RuntimeException e) {
      throw new ExecutionException(
          String.format(
              "dropping the secondary index for %s.%s.%s failed", namespace, table, columnName),
          e);
    }
  }

  private String getIndexName(String table, String columnName) {
    return String.format("%s_%s_%s", table, INDEX_NAME_PREFIX, columnName);
  }

  @Override
  public TableMetadata getTableMetadata(String namespace, String table) throws ExecutionException {
    try {
      com.datastax.driver.core.TableMetadata metadata =
          clusterManager.getMetadata(namespace, table);
      if (metadata == null) {
        return null;
      }
      return createTableMetadata(metadata);
    } catch (RuntimeException e) {
      throw new ExecutionException("getting a table metadata failed", e);
    }
  }

  private TableMetadata createTableMetadata(com.datastax.driver.core.TableMetadata metadata)
      throws ExecutionException {
    TableMetadata.Builder builder = TableMetadata.newBuilder();
    for (ColumnMetadata column : metadata.getColumns()) {
      builder.addColumn(column.getName(), fromCassandraDataType(column.getType().getName()));
    }
    metadata.getPartitionKey().forEach(c -> builder.addPartitionKey(c.getName()));
    for (int i = 0; i < metadata.getClusteringColumns().size(); i++) {
      String clusteringColumnName = metadata.getClusteringColumns().get(i).getName();
      ClusteringOrder clusteringOrder = metadata.getClusteringOrder().get(i);
      builder.addClusteringKey(clusteringColumnName, convertOrder(clusteringOrder));
    }
    metadata.getIndexes().forEach(i -> builder.addSecondaryIndex(i.getTarget()));
    return builder.build();
  }

  @Override
  public TableMetadata getImportTableMetadata(String namespace, String table) {
    throw new UnsupportedOperationException(
        "import-related functionality is not supported in Cassandra");
  }

  @Override
  public void addRawColumnToTable(
      String namespace, String table, String columnName, DataType columnType) {
    throw new UnsupportedOperationException(
        "import-related functionality is not supported in Cassandra");
  }

  @Override
  public void importTable(String namespace, String table) {
    throw new UnsupportedOperationException(
        "import-related functionality is not supported in Cassandra");
  }

  private Scan.Ordering.Order convertOrder(ClusteringOrder clusteringOrder) {
    switch (clusteringOrder) {
      case ASC:
        return Scan.Ordering.Order.ASC;
      case DESC:
        return Scan.Ordering.Order.DESC;
      default:
        throw new AssertionError();
    }
  }

  @Override
  public Set getNamespaceTableNames(String namespace) throws ExecutionException {
    try {
      KeyspaceMetadata keyspace =
          clusterManager
              .getSession()
              .getCluster()
              .getMetadata()
              .getKeyspace(quoteIfNecessary(namespace));
      if (keyspace == null) {
        return Collections.emptySet();
      }
      return keyspace.getTables().stream()
          .map(com.datastax.driver.core.TableMetadata::getName)
          .collect(Collectors.toSet());
    } catch (RuntimeException e) {
      throw new ExecutionException("retrieving the table names of the namespace failed", e);
    }
  }

  @Override
  public boolean namespaceExists(String namespace) throws ExecutionException {
    try {
      KeyspaceMetadata keyspace =
          clusterManager
              .getSession()
              .getCluster()
              .getMetadata()
              .getKeyspace(quoteIfNecessary(namespace));
      return keyspace != null;
    } catch (RuntimeException e) {
      throw new ExecutionException("checking if the namespace exists failed", e);
    }
  }

  @Override
  public void repairTable(
      String namespace, String table, TableMetadata metadata, Map options)
      throws ExecutionException {
    // We have this check to stay consistent with the behavior of the other admins classes
    if (!tableExists(namespace, table)) {
      throw new IllegalArgumentException(
          "The table " + getFullTableName(namespace, table) + "  does not exist");
    }
    // The table metadata are not managed by ScalarDB, so we don't need to do anything here
  }

  @Override
  public void addNewColumnToTable(
      String namespace, String table, String columnName, DataType columnType)
      throws ExecutionException {
    try {
      String alterTableQuery =
          SchemaBuilder.alterTable(namespace, table)
              .addColumn(columnName)
              .type(toCassandraDataType(columnType))
              .getQueryString();

      clusterManager.getSession().execute(alterTableQuery);
    } catch (IllegalArgumentException e) {
      throw e;
    } catch (RuntimeException e) {
      throw new ExecutionException(
          String.format(
              "Adding the new column %s to the %s.%s table failed", columnName, namespace, table),
          e);
    }
  }

  @VisibleForTesting
  void createTableInternal(
      String keyspace, String table, TableMetadata metadata, Map options)
      throws ExecutionException {
    Create createTable =
        SchemaBuilder.createTable(quoteIfNecessary(keyspace), quoteIfNecessary(table));
    // Add columns
    for (String pk : metadata.getPartitionKeyNames()) {
      createTable =
          createTable.addPartitionKey(
              quoteIfNecessary(pk), toCassandraDataType(metadata.getColumnDataType(pk)));
    }
    for (String ck : metadata.getClusteringKeyNames()) {
      createTable =
          createTable.addClusteringColumn(
              quoteIfNecessary(ck), toCassandraDataType(metadata.getColumnDataType(ck)));
    }
    for (String column : metadata.getColumnNames()) {
      if (metadata.getPartitionKeyNames().contains(column)
          || metadata.getClusteringKeyNames().contains(column)) {
        continue;
      }
      createTable =
          createTable.addColumn(
              quoteIfNecessary(column), toCassandraDataType(metadata.getColumnDataType(column)));
    }
    // Add clustering order
    Create.Options createTableWithOptions = createTable.withOptions();
    for (String ck : metadata.getClusteringKeyNames()) {
      Direction direction =
          metadata.getClusteringOrder(ck) == Order.ASC ? Direction.ASC : Direction.DESC;
      createTableWithOptions =
          createTableWithOptions.clusteringOrder(quoteIfNecessary(ck), direction);
    }
    // Add compaction strategy
    CompactionStrategy compactionStrategy =
        CompactionStrategy.valueOf(
            options
                .getOrDefault(COMPACTION_STRATEGY, CompactionStrategy.STCS.toString())
                .toUpperCase(Locale.ROOT));

    CompactionOptions strategy;
    switch (compactionStrategy) {
      case LCS:
        strategy = SchemaBuilder.leveledStrategy();
        break;
      case TWCS:
        strategy = SchemaBuilder.timeWindowCompactionStrategy();
        break;
      default:
        strategy = SchemaBuilder.sizedTieredStategy();
    }
    createTableWithOptions = createTableWithOptions.compactionOptions(strategy);

    try {
      clusterManager.getSession().execute(createTableWithOptions.getQueryString());
    } catch (RuntimeException e) {
      throw new ExecutionException(
          String.format("creating the table %s.%s failed", keyspace, table), e);
    }
  }

  @VisibleForTesting
  void createSecondaryIndexes(
      String keyspace, String table, Set secondaryIndexNames, Map options)
      throws ExecutionException {
    for (String name : secondaryIndexNames) {
      createIndex(keyspace, table, name, options);
    }
  }

  @Override
  public void close() {
    clusterManager.close();
  }

  /**
   * Return the ScalarDB datatype value that is equivalent to {@link
   * com.datastax.driver.core.DataType}
   *
   * @return ScalarDB datatype that is equivalent {@link com.datastax.driver.core.DataType}
   */
  private DataType fromCassandraDataType(
      com.datastax.driver.core.DataType.Name cassandraDataTypeName) throws ExecutionException {
    switch (cassandraDataTypeName) {
      case INT:
        return DataType.INT;
      case BIGINT:
        return DataType.BIGINT;
      case FLOAT:
        return DataType.FLOAT;
      case DOUBLE:
        return DataType.DOUBLE;
      case TEXT:
        return DataType.TEXT;
      case BOOLEAN:
        return DataType.BOOLEAN;
      case BLOB:
        return DataType.BLOB;
      default:
        throw new ExecutionException(
            String.format("%s is not yet supported", cassandraDataTypeName));
    }
  }

  /**
   * Returns the equivalent {@link com.datastax.driver.core.DataType}
   *
   * @return the equivalent {@link com.datastax.driver.core.DataType} to the ScalarDB data type
   */
  private com.datastax.driver.core.DataType toCassandraDataType(DataType dataType) {
    switch (dataType) {
      case BOOLEAN:
        return com.datastax.driver.core.DataType.cboolean();
      case INT:
        return com.datastax.driver.core.DataType.cint();
      case BIGINT:
        return com.datastax.driver.core.DataType.bigint();
      case FLOAT:
        return com.datastax.driver.core.DataType.cfloat();
      case DOUBLE:
        return com.datastax.driver.core.DataType.cdouble();
      case TEXT:
        return com.datastax.driver.core.DataType.text();
      case BLOB:
        return com.datastax.driver.core.DataType.blob();
      default:
        throw new AssertionError();
    }
  }

  public enum CompactionStrategy {
    STCS,
    LCS,
    TWCS
  }

  public enum ReplicationStrategy {
    SIMPLE_STRATEGY("SimpleStrategy"),
    NETWORK_TOPOLOGY_STRATEGY("NetworkTopologyStrategy");
    private final String strategyName;

    ReplicationStrategy(final String strategyName) {
      this.strategyName = strategyName;
    }

    public static ReplicationStrategy fromString(String text) {
      for (ReplicationStrategy strategy : ReplicationStrategy.values()) {
        if (strategy.strategyName.equalsIgnoreCase(text)) {
          return strategy;
        }
      }
      throw new IllegalArgumentException(
          String.format("The network strategy %s does not exist", text));
    }

    @Override
    public String toString() {
      return strategyName;
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy