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

cz.o2.proxima.direct.io.cassandra.CassandraRandomReader Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017-2024 O2 Czech Republic, a.s.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package cz.o2.proxima.direct.io.cassandra;

import com.datastax.driver.core.BoundStatement;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.Session;
import com.datastax.driver.core.Token;
import cz.o2.proxima.core.functional.Consumer;
import cz.o2.proxima.core.repository.AttributeDescriptor;
import cz.o2.proxima.core.storage.AbstractStorage;
import cz.o2.proxima.core.util.Pair;
import cz.o2.proxima.direct.core.randomaccess.KeyValue;
import cz.o2.proxima.direct.core.randomaccess.RandomAccessReader;
import cz.o2.proxima.direct.core.randomaccess.RandomOffset;
import cz.o2.proxima.direct.io.cassandra.CassandraDBAccessor.ClusterHolder;
import cz.o2.proxima.direct.io.cassandra.CqlFactory.KvIterable;
import java.nio.ByteBuffer;
import java.util.Objects;
import java.util.Optional;
import javax.annotation.Nullable;
import lombok.extern.slf4j.Slf4j;

/** A {@link RandomAccessReader} for Cassandra. */
@Slf4j
class CassandraRandomReader extends AbstractStorage implements RandomAccessReader {

  private final CassandraDBAccessor accessor;
  @Nullable private ClusterHolder clusterHolder;

  CassandraRandomReader(CassandraDBAccessor accessor) {
    super(accessor.getEntityDescriptor(), accessor.getUri());
    this.accessor = accessor;
    this.clusterHolder = Objects.requireNonNull(accessor.acquireCluster());
  }

  @Override
  public  Optional> get(
      String key, String attribute, AttributeDescriptor desc, long stamp) {

    Session session = accessor.ensureSession();
    BoundStatement statement =
        accessor.getCqlFactory().getReadStatement(key, attribute, desc, session);
    final ResultSet result;
    try {
      result = accessor.execute(statement);
    } catch (Exception ex) {
      throw new RuntimeException("Unable to execute query.", ex);
    }
    // the row has to have format (value)
    for (Row row : result) {
      ByteBuffer val = row.getBytes(0);
      if (val != null) {
        byte[] rowValue = val.array();

        try {
          return Optional.of(
              accessor
                  .getCqlFactory()
                  .toKeyValue(
                      getEntityDescriptor(),
                      desc,
                      key,
                      attribute,
                      System.currentTimeMillis(),
                      new Offsets.Raw(attribute),
                      rowValue));
        } catch (Exception ex) {
          log.warn("Failed to read data from {}.{}", key, attribute, ex);
        }
      }
    }
    return Optional.empty();
  }

  @Override
  public void scanWildcardAll(
      String key, RandomOffset offset, long stamp, int limit, Consumer> consumer) {
    try {
      Offsets.Raw off = (Offsets.Raw) offset;
      Session session = accessor.ensureSession();
      KvIterable iter = accessor.getCqlFactory().getListAllStatement(key, off, limit, session);
      for (KeyValue kv : iter.iterable(accessor)) {
        if (kv.getAttribute().compareTo(off.getRaw()) > 0) {
          if (limit-- == 0) {
            break;
          }
          consumer.accept(kv);
        }
      }
    } catch (Exception ex) {
      log.error("Failed to scan attributes of {}", key, ex);
      throw new RuntimeException(ex);
    }
  }

  @Override
  public  void scanWildcard(
      String key,
      AttributeDescriptor wildcard,
      @Nullable RandomOffset offset,
      long stamp,
      int limit,
      Consumer> consumer) {

    try {
      Session session = accessor.ensureSession();
      BoundStatement statement =
          accessor
              .getCqlFactory()
              .getListStatement(key, wildcard, (Offsets.Raw) offset, limit, session);

      ResultSet result = accessor.execute(statement);
      // the row has to have the format (attribute, value)
      for (Row row : result) {
        Object attribute = row.getObject(0);
        ByteBuffer val = row.getBytes(1);
        if (val != null) {
          byte[] rowValue = val.array();
          // by convention
          String name = wildcard.toAttributePrefix() + accessor.asString(attribute);

          Optional parsed = wildcard.getValueSerializer().deserialize(rowValue);

          if (parsed.isPresent()) {
            consumer.accept(
                accessor
                    .getCqlFactory()
                    .toKeyValue(
                        getEntityDescriptor(),
                        wildcard,
                        key,
                        name,
                        System.currentTimeMillis(),
                        new Offsets.Raw(name),
                        rowValue));
          } else {
            log.error("Failed to parse value for key {} attribute {}.{}", key, wildcard, attribute);
          }
        }
      }
    } catch (Exception ex) {
      log.error("Failed to scan wildcard attribute {}", wildcard, ex);
      throw new RuntimeException(ex);
    }
  }

  @Override
  public void listEntities(
      RandomOffset offset, int limit, Consumer> consumer) {

    Session session = accessor.ensureSession();
    BoundStatement statement =
        accessor.getCqlFactory().getListEntitiesStatement((Offsets.Token) offset, limit, session);

    try {
      ResultSet result = accessor.execute(statement);
      for (Row row : result) {
        String key = row.getString(0);
        Token token = row.getToken(1);
        consumer.accept(Pair.of(new Offsets.Token((long) token.getValue()), key));
      }
    } catch (Exception ex) {
      throw new RuntimeException(ex);
    }
  }

  @Override
  public Factory asFactory() {
    final CassandraDBAccessor accessor = this.accessor;
    return repo -> new CassandraRandomReader(accessor);
  }

  @Override
  public void close() {
    Optional.ofNullable(clusterHolder).ifPresent(ClusterHolder::close);
    clusterHolder = null;
  }

  @Override
  public RandomOffset fetchOffset(Listing type, String key) {
    try {
      switch (type) {
        case ATTRIBUTE:
          return new Offsets.Raw(key);

        case ENTITY:
          Session session = accessor.ensureSession();
          ResultSet res =
              accessor.execute(accessor.getCqlFactory().getFetchTokenStatement(key, session));
          if (res.isExhausted()) {
            return new Offsets.Token(Long.MIN_VALUE);
          }
          return new Offsets.Token(res.one().getLong(0));
        default:
          throw new IllegalArgumentException("Unknown type of listing: " + type);
      }
    } catch (Exception ex) {
      throw new RuntimeException(ex);
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy