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

io.permazen.kv.fdb.FoundationKVStore Maven / Gradle / Ivy


/*
 * Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
 */

package io.permazen.kv.fdb;

import com.apple.foundationdb.KeyValue;
import com.apple.foundationdb.MutationType;
import com.apple.foundationdb.Range;
import com.apple.foundationdb.ReadTransaction;
import com.apple.foundationdb.Transaction;
import com.apple.foundationdb.async.AsyncIterator;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterators;
import com.google.common.primitives.Bytes;

import io.permazen.kv.KVPair;
import io.permazen.kv.KVStore;
import io.permazen.util.ByteUtil;
import io.permazen.util.CloseableIterator;

import java.util.concurrent.ExecutionException;

import javax.annotation.concurrent.ThreadSafe;

/**
 * A {@link KVStore} view of a FoundationDB {@link Transaction}.
 */
@ThreadSafe
public class FoundationKVStore implements KVStore {

    private static final byte[] MIN_KEY = ByteUtil.EMPTY;                   // minimum possible key (inclusive)
    private static final byte[] MAX_KEY = new byte[] { (byte)0xff };        // maximum possible key (exclusive)

    private final Transaction tx;
    private final byte[] keyPrefix;

    /**
     * Constructor.
     *
     * @param tx FDB transaction; note, caller is responsible for closing this
     * @param keyPrefix key prefix, or null for none
     */
    public FoundationKVStore(Transaction tx, byte[] keyPrefix) {
        Preconditions.checkArgument(tx != null, "null tx");
        this.tx = tx;
        this.keyPrefix = keyPrefix != null ? keyPrefix.clone() : null;
    }

    /**
     * Get the underlying {@link Transaction} associated with this instance.
     *
     * @return the associated transaction
     */
    public Transaction getTransaction() {
        return this.tx;
    }

    /**
     * Get the key prefix associated with this instance, if any.
     *
     * @return the associated key prefix, or null for none
     */
    public byte[] getKeyPrefix() {
        return this.keyPrefix != null ? this.keyPrefix.clone() : null;
    }

// KVStore

    @Override
    public byte[] get(byte[] key) {
        Preconditions.checkArgument(key.length == 0 || key[0] != (byte)0xff, "key starts with 0xff");
        try {
            return this.tx.get(this.addPrefix(key)).get();
        } catch (ExecutionException e) {
            throw e.getCause() instanceof RuntimeException ? (RuntimeException)e.getCause() : new RuntimeException(e.getCause());
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public KVPair getAtLeast(byte[] minKey, byte[] maxKey) {
        if (minKey != null && minKey.length > 0 && minKey[0] == (byte)0xff)
            return null;
        return this.getFirstInRange(minKey, maxKey, false);
    }

    @Override
    public KVPair getAtMost(byte[] maxKey, byte[] minKey) {
        if (maxKey != null && maxKey.length > 0 && maxKey[0] == (byte)0xff)
            maxKey = null;
        return this.getFirstInRange(minKey, maxKey, true);
    }

    @Override
    public CloseableIterator getRange(byte[] minKey, byte[] maxKey, boolean reverse) {
        if (minKey != null && minKey.length > 0 && minKey[0] == (byte)0xff)
            minKey = MAX_KEY;
        if (maxKey != null && maxKey.length > 0 && maxKey[0] == (byte)0xff)
            maxKey = null;
        Preconditions.checkArgument(minKey == null || maxKey == null || ByteUtil.compare(minKey, maxKey) <= 0, "minKey > maxKey");
        final AsyncIterator i = this.tx.getRange(
          this.addPrefix(minKey, maxKey), ReadTransaction.ROW_LIMIT_UNLIMITED, reverse).iterator();
        return CloseableIterator.wrap(
          Iterators.transform(i, kv -> new KVPair(this.removePrefix(kv.getKey()), kv.getValue())),
          i instanceof AutoCloseable ? (AutoCloseable)i : (AutoCloseable)i::cancel);
    }

    private KVPair getFirstInRange(byte[] minKey, byte[] maxKey, boolean reverse) {
        final AsyncIterator i = this.tx.getRange(
          this.addPrefix(minKey, maxKey), ReadTransaction.ROW_LIMIT_UNLIMITED /* 1? */, reverse).iterator();
        try {
            if (!i.hasNext())
                return null;
            final KeyValue kv = i.next();
            return new KVPair(this.removePrefix(kv.getKey()), kv.getValue());
        } finally {
            i.cancel();
        }
    }

    @Override
    public void put(byte[] key, byte[] value) {
        Preconditions.checkArgument(key.length == 0 || key[0] != (byte)0xff, "key starts with 0xff");
        this.tx.set(this.addPrefix(key), value);
    }

    @Override
    public void remove(byte[] key) {
        Preconditions.checkArgument(key.length == 0 || key[0] != (byte)0xff, "key starts with 0xff");
        this.tx.clear(this.addPrefix(key));
    }

    @Override
    public void removeRange(byte[] minKey, byte[] maxKey) {
        if (minKey != null && minKey.length > 0 && minKey[0] == (byte)0xff)
            return;
        if (maxKey != null && maxKey.length > 0 && maxKey[0] == (byte)0xff)
            maxKey = null;
        Preconditions.checkArgument(minKey == null || maxKey == null || ByteUtil.compare(minKey, maxKey) <= 0, "minKey > maxKey");
        this.tx.clear(this.addPrefix(minKey, maxKey));
    }

    @Override
    public byte[] encodeCounter(long value) {
        return FoundationKVDatabase.encodeCounter(value);
    }

    @Override
    public long decodeCounter(byte[] bytes) {
        return FoundationKVDatabase.decodeCounter(bytes);
    }

    @Override
    public void adjustCounter(byte[] key, long amount) {
        this.tx.mutate(MutationType.ADD, this.addPrefix(key), this.encodeCounter(amount));
    }

// Key prefixing

    byte[] addPrefix(byte[] key) {
        return this.keyPrefix != null ? Bytes.concat(this.keyPrefix, key) : key;
    }

    Range addPrefix(byte[] minKey, byte[] maxKey) {
        if (this.keyPrefix == null && maxKey != null && maxKey.length > 1 && maxKey[0] == (byte)0xff)
            maxKey = MAX_KEY;
        return new Range(this.addPrefix(minKey != null ? minKey : MIN_KEY), this.addPrefix(maxKey != null ? maxKey : MAX_KEY));
    }

    byte[] removePrefix(byte[] key) {
        if (this.keyPrefix == null)
            return key;
        if (!ByteUtil.isPrefixOf(this.keyPrefix, key)) {
            throw new IllegalArgumentException("read key " + ByteUtil.toString(key) + " not having "
              + ByteUtil.toString(this.keyPrefix) + " as a prefix");
        }
        final byte[] stripped = new byte[key.length - this.keyPrefix.length];
        System.arraycopy(key, this.keyPrefix.length, stripped, 0, stripped.length);
        return stripped;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy