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

com.bazaarvoice.emodb.sor.db.ScanRange Maven / Gradle / Ivy

There is a newer version: 6.5.190
Show newest version
package com.bazaarvoice.emodb.sor.db;

import com.bazaarvoice.emodb.common.cassandra.nio.BufferUtils;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.primitives.UnsignedBytes;
import com.netflix.astyanax.model.ByteBufferRange;
import com.netflix.astyanax.util.ByteBufferRangeImpl;
import org.apache.cassandra.utils.ByteBufferUtil;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Hex;

import javax.annotation.Nullable;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import static java.util.Objects.hash;
import static java.util.Objects.requireNonNull;

/**
 * Defines an upper and lower bound for performing a scan of the underlying Cassandra table data column family.
 * Note that if {@link #getFrom()} is greater than {@link #getTo()}} then the scan will wrap from the high to low end
 * of the token range.
 */
public class ScanRange implements Comparable {
    public final static ByteBuffer MIN_VALUE = ByteBuffer.wrap(new byte[0]);
    public final static ByteBuffer MAX_VALUE = generateMaxValue();

    private final ByteBuffer _from;
    private final ByteBuffer _to;

    @JsonCreator
    private ScanRange(@JsonProperty ("from") String from, @JsonProperty ("to") String to) {
        try {
            _from = ByteBuffer.wrap(Hex.decodeHex(requireNonNull(from, "from").toCharArray())).asReadOnlyBuffer();
            _to = ByteBuffer.wrap(Hex.decodeHex(requireNonNull(to, "to").toCharArray())).asReadOnlyBuffer();
        } catch (DecoderException e) {
            throw new IllegalArgumentException(e);
        }
    }

    private ScanRange(ByteBuffer from, ByteBuffer to) {
        _from = requireNonNull(from, "from");
        _to = requireNonNull(to, "to");
    }

    public static ScanRange create(ByteBufferRange range) {
        return create(range.getStart(), range.getEnd());
    }

    public static ScanRange create(ByteBuffer from, ByteBuffer to) {
        return new ScanRange(from, to);
    }

    public static ScanRange all() {
        return create(MIN_VALUE, MAX_VALUE);
    }

    @JsonIgnore
    public ByteBuffer getFrom() {
        return _from;
    }

    @JsonIgnore
    public ByteBuffer getTo() {
        return _to;
    }

    @JsonProperty ("from")
    public String getFromHex() {
        return ByteBufferUtil.bytesToHex(_from);
    }

    @JsonProperty ("to")
    public String getToHex() {
        return ByteBufferUtil.bytesToHex(_to);
    }

    /**
     * If necessary splits the scan range into two range such that "from" is less than "to" in each range.
     * This is necessary if the scan range wraps the token range.
     */
    public List unwrapped() {
        if (compare(_from, _to) < 0) {
            return ImmutableList.of(this);
        }
        ImmutableList.Builder ranges = ImmutableList.builder();
        if (compare(_from, MAX_VALUE) < 0) {
            ranges.add(new ScanRange(_from, MAX_VALUE));
        }
        if (compare(_to, MIN_VALUE) > 0) {
            ranges.add(new ScanRange(MIN_VALUE, _to));
        }
        return ranges.build();
    }

    public ByteBufferRange asByteBufferRange() {
        return new ByteBufferRangeImpl(_from, _to, -1, false);
    }

    public List intersection(ScanRange other) {
        return intersection(this, other);
    }

    /**
     * Returns the intersection subranges of two ScanRanges.  Normally an intersection of two linear spaces
     * would either return nothing or the single intersecting range.  However, since a ScanRange is circular it
     * is possible to provide two ScanRanges with two intersection ranges, as the following example shows.  This
     * is why this method returns a list of ScanRanges:
     *
     * Range 1:         XXXXXXX..............XXXXXXX
     * Range 2:         .....XXXXXXXXXXXXXXXXXX.....
     * Intersection:    .....XX..............XX.....
     *
     * @return A list of intersecting ranges, or empty if there was no intersection.
     */
    public static List intersection(ScanRange left, ScanRange right) {
        List unwrappedLeft = left.unwrapped();
        List unwrappedRight = right.unwrapped();

        boolean leftWrapped = unwrappedLeft.size() > 1;
        boolean rightWrapped = unwrappedRight.size() > 1;

        if (!leftWrapped && !rightWrapped) {
            // Neither side wrapped the token range, so a simple intersection is all that is required.
            // Save some cycles by comparing the ranges directly.
            ScanRange intersection = intersectionUnwrapped(left, right);
            if (intersection == null) {
                return ImmutableList.of();
            }
            return ImmutableList.of(intersection);
        }

        List intersections = Lists.newArrayListWithExpectedSize(2);
        for (ScanRange l : unwrappedLeft) {
            for (ScanRange r : unwrappedRight) {
                ScanRange intersection = intersectionUnwrapped(l, r);
                if (intersection != null) {
                    intersections.add(intersection);
                }
            }
        }

        if (intersections.size() > 1) {
            // For consistency always return the intersections sorted from low- to high-range.
            Collections.sort(intersections);

            // If multiple ranges are contiguous then join them.  This can happen if one of the ranges is "all".
            for (int i = intersections.size() - 1; i > 0; i--) {
                if (intersections.get(i-1)._to.equals(intersections.get(i)._from)) {
                    intersections.set(i-1, ScanRange.create(intersections.get(i-1)._from, intersections.get(i)._to));
                    intersections.remove(i);
                }
            }

            // If the intersections represent a contiguous high- to low-end wrapped range then combine them.
            if (intersections.size() == 2 &&
                    intersections.get(0)._from.equals(MIN_VALUE) && intersections.get(1)._to.equals(MAX_VALUE)) {
                ScanRange combined = ScanRange.create(intersections.get(1)._from, intersections.get(0)._to);
                intersections.clear();
                intersections.add(combined);
            }
        }

        return intersections;
    }

    @Nullable
    private static ScanRange intersectionUnwrapped(ScanRange left, ScanRange right) {
        // Make sure left represents the range with the lower starting point
        if (compare(left._from, right._from) > 0) {
            ScanRange tmp = right;
            right = left;
            left = tmp;
        }

        if (compare(right._from, left._from) >= 0 && compare(right._from, left._to) < 0) {
            ByteBuffer endIntersection = compare(left._to, right._to) < 0 ? left._to : right._to;
            return ScanRange.create(right._from, endIntersection);
        }

        return null;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || !(o instanceof ScanRange)) {
            return false;
        }

        ScanRange scanRange = (ScanRange) o;
        return scanRange.getFrom().equals(_from) && scanRange.getTo().equals(_to);
    }

    @Override
    public int hashCode() {
        return hash(_from, _to);
    }

    @Override
    public int compareTo(ScanRange o) {
        // Sort by whichever has the lower "from" scan boundary
        return compare(_from, o._from);
    }

    @Override
    public String toString() {
        return String.format("ScanRange[%s-%s]", getFromHex(), getToHex());
    }

    private static ByteBuffer generateMaxValue() {
        // Shortcut to create the maximal row key, shard=0xff and tableUuid=0xffffffffffffffff
        byte[] bytes = new byte[9];
        Arrays.fill(bytes, UnsignedBytes.MAX_VALUE);
        return ByteBuffer.wrap(bytes);
    }

    private static int compare(ByteBuffer left, ByteBuffer right) {
        return BufferUtils.compareUnsigned(left, right);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy