diff --git a/accord-core/src/main/java/accord/api/Key.java b/accord-core/src/main/java/accord/api/Key.java index 1504b406c2..99804b70cc 100644 --- a/accord-core/src/main/java/accord/api/Key.java +++ b/accord-core/src/main/java/accord/api/Key.java @@ -21,6 +21,7 @@ import accord.primitives.Range; import accord.primitives.RoutableKey; import accord.primitives.Seekable; +import accord.utils.Invariants; /** * A key we can find in both the cluster and on disk @@ -31,7 +32,11 @@ public interface Key extends Seekable, RoutableKey default Key asKey() { return this; } @Override - default Key slice(Range range) { return this; } + default Key slice(Range range) + { + Invariants.checkArgument(range.contains(this)); + return this; + } @Override default Range asRange() { throw new UnsupportedOperationException(); } diff --git a/accord-core/src/main/java/accord/api/Read.java b/accord-core/src/main/java/accord/api/Read.java index c2952263ca..4f58007fc1 100644 --- a/accord-core/src/main/java/accord/api/Read.java +++ b/accord-core/src/main/java/accord/api/Read.java @@ -31,8 +31,26 @@ */ public interface Read { + /** + * Returns the scope of the transaction, that is, the keys or ranges that will be read + * // TODO maybe this method should be renamed to readScope, or just scope, or something else which would not mention keys as it can be ranges as well? + */ Seekables keys(); + + /** + * The method is called when Accord reads data. + * + * @param key denotes which data to read; it is guaranteed that the key passed there is one of the items covered by {@link #keys()}. + */ AsyncChain read(Seekable key, Txn.Kind kind, SafeCommandStore commandStore, Timestamp executeAt, DataStore store); + + /** + * Returns a new read operation whose scope is an intersection of {@link #keys()} and the provided ranges. + */ Read slice(Ranges ranges); + + /** + * Returns a new read operation whose scope is a union of the scopes of this and the provided read operations. + */ Read merge(Read other); } diff --git a/accord-core/src/main/java/accord/api/Update.java b/accord-core/src/main/java/accord/api/Update.java index b3eb387553..cba9417273 100644 --- a/accord-core/src/main/java/accord/api/Update.java +++ b/accord-core/src/main/java/accord/api/Update.java @@ -19,21 +19,34 @@ package accord.api; import accord.primitives.Ranges; -import accord.primitives.Keys; import accord.primitives.Seekables; import javax.annotation.Nullable; /** * A client-defined update operation (the write equivalent of a query). - * Takes as input the data returned by {@code Read}, and returns a {@code Write} - * representing new information to distributed to each shard's stores. */ public interface Update { + /** + * Returns the scope of the transaction, that is, the keys or ranges that will be written + */ + //TODO maybe this method should be renamed to updateScope, or just scope, or something else which would not mention keys as it can be ranges as well? Seekables keys(); - // null is provided only if nothing was read + + /** + * Takes as input the data returned by {@link Read}, and returns a {@link Write} representing new information + * to be distributed to each shard's stores. Accepts {@code null} if nothing was read. + */ Write apply(@Nullable Data data); + + /** + * Returns a new update operation whose scope is an intersection of {@link #keys()} and the provided ranges. + */ Update slice(Ranges ranges); + + /** + * Returns a new update operation whose scope is a union of the scopes of this and the provided update operations. + */ Update merge(Update other); } diff --git a/accord-core/src/main/java/accord/primitives/AbstractKeys.java b/accord-core/src/main/java/accord/primitives/AbstractKeys.java index 6e4c716c5c..72bedaac72 100644 --- a/accord-core/src/main/java/accord/primitives/AbstractKeys.java +++ b/accord-core/src/main/java/accord/primitives/AbstractKeys.java @@ -35,7 +35,7 @@ @SuppressWarnings("rawtypes") // TODO (desired, efficiency): check that foldl call-sites are inlined and optimised by HotSpot -public abstract class AbstractKeys> implements Iterable, Routables +public abstract class AbstractKeys> implements Iterable, Routables { final K[] keys; diff --git a/accord-core/src/main/java/accord/primitives/AbstractRanges.java b/accord-core/src/main/java/accord/primitives/AbstractRanges.java index 0fa91858ee..8ed332be4d 100644 --- a/accord-core/src/main/java/accord/primitives/AbstractRanges.java +++ b/accord-core/src/main/java/accord/primitives/AbstractRanges.java @@ -33,7 +33,7 @@ import static accord.utils.SortedArrays.Search.FAST; import static accord.utils.SortedArrays.swapHighLow32b; -public abstract class AbstractRanges> implements Iterable, Routables +public abstract class AbstractRanges> implements Iterable, Routables { static final Range[] NO_RANGES = new Range[0]; @@ -41,6 +41,13 @@ public abstract class AbstractRanges> implements AbstractRanges(@Nonnull Range[] ranges) { + if (ranges.length > 0) + { + // check that ranges are of the same type + for (int i = 1; i < ranges.length; i++) + ranges[0].checkRangeType(ranges[i]); + } + // TODO (simple, validation): check ranges are non-overlapping (or make sure it's safe for all methods that they aren't) this.ranges = Invariants.nonNull(ranges); } @@ -110,6 +117,7 @@ public boolean intersectsAll(AbstractRanges that) { if (this.isEmpty()) return that.isEmpty(); if (that.isEmpty()) return true; + this.ranges[0].checkRangeType(that.ranges[0]); return Routables.rangeFoldl(that, this, (p, v, from, to) -> v + (to - from), 0, 0, 0) == that.size(); } diff --git a/accord-core/src/main/java/accord/primitives/AbstractUnseekableKeys.java b/accord-core/src/main/java/accord/primitives/AbstractUnseekableKeys.java index 214190e876..b9386fcf9c 100644 --- a/accord-core/src/main/java/accord/primitives/AbstractUnseekableKeys.java +++ b/accord-core/src/main/java/accord/primitives/AbstractUnseekableKeys.java @@ -23,7 +23,7 @@ import java.util.Arrays; // TODO: do we need this class? -public abstract class AbstractUnseekableKeys> extends AbstractKeys implements Iterable, Unseekables +public abstract class AbstractUnseekableKeys> extends AbstractKeys implements Iterable, Unseekables { AbstractUnseekableKeys(RoutingKey[] keys) { diff --git a/accord-core/src/main/java/accord/primitives/FullRoute.java b/accord-core/src/main/java/accord/primitives/FullRoute.java index a9acba1048..40590b982b 100644 --- a/accord-core/src/main/java/accord/primitives/FullRoute.java +++ b/accord-core/src/main/java/accord/primitives/FullRoute.java @@ -18,7 +18,7 @@ package accord.primitives; -public interface FullRoute extends Route, Unseekables> +public interface FullRoute extends Route { @Override default FullRoute union(Route route) { return this; } @Override default Ranges sliceCovering(Ranges newRanges, Slice slice) { return newRanges; } diff --git a/accord-core/src/main/java/accord/primitives/Range.java b/accord-core/src/main/java/accord/primitives/Range.java index db6c3aa021..3edcfc16e3 100644 --- a/accord-core/src/main/java/accord/primitives/Range.java +++ b/accord-core/src/main/java/accord/primitives/Range.java @@ -30,7 +30,7 @@ import static accord.utils.SortedArrays.Search.FAST; /** - * A range of keys + * A range of keys. */ public abstract class Range implements Comparable, Unseekable, Seekable { @@ -52,13 +52,13 @@ public int compareTo(RoutableKey key) } @Override - public boolean startInclusive() + public final boolean startInclusive() { return false; } @Override - public boolean endInclusive() + public final boolean endInclusive() { return true; } @@ -94,13 +94,13 @@ public int compareTo(RoutableKey key) } @Override - public boolean startInclusive() + public final boolean startInclusive() { return true; } @Override - public boolean endInclusive() + public final boolean endInclusive() { return false; } @@ -244,8 +244,7 @@ public boolean contains(RoutableKey key) */ public int compareIntersecting(Range that) { - if (that.getClass() != this.getClass()) - throw new IllegalArgumentException("Cannot mix Range of different types"); + checkRangeType(that); if (this.start.compareTo(that.end) >= 0) return 1; if (this.end.compareTo(that.start) <= 0) @@ -258,8 +257,7 @@ public int compareIntersecting(Range that) */ public int compare(Range that) { - if (that.getClass() != this.getClass()) - throw new IllegalArgumentException("Cannot mix Range of different types"); + checkRangeType(that); int c = this.start.compareTo(that.start); if (c == 0) c = this.end.compareTo(that.end); return c; @@ -272,10 +270,9 @@ public boolean contains(Range that) public Range slice(Range truncateTo) { - int cs = start.compareTo(truncateTo.start); - int ce = end.compareTo(truncateTo.end); - if (cs >= 0 && ce <= 0) return this; - return newRange(cs >= 0 ? start : truncateTo.start, ce <= 0 ? end : truncateTo.end); + Range intersection = this.intersection(truncateTo); + Invariants.checkArgument(intersection != null); + return intersection; } public boolean intersects(AbstractKeys keys) @@ -289,11 +286,20 @@ public boolean intersects(AbstractKeys keys) */ public Range intersection(Range that) { + checkRangeType(that); + if (this.compareIntersecting(that) != 0) return null; RoutingKey start = this.start.compareTo(that.start) > 0 ? this.start : that.start; RoutingKey end = this.end.compareTo(that.end) < 0 ? this.end : that.end; + + if (start == this.start && end == this.end) + return this; + + if (start == that.start && end == that.end) + return that; + return newRange(start, end); } @@ -349,21 +355,15 @@ public RoutingKey someIntersectingRoutingKey(Ranges ranges) } } - public static Range slice(Range bound, Range toSlice) - { - Invariants.checkArgument(bound.compareIntersecting(toSlice) == 0); - if (bound.contains(toSlice)) - return toSlice; - - return toSlice.newRange( - toSlice.start().compareTo(bound.start()) >= 0 ? toSlice.start() : bound.start(), - toSlice.end().compareTo(bound.end()) <= 0 ? toSlice.end() : bound.end() - ); - } - @Override public Range toUnseekable() { return this; } + + final void checkRangeType(Range that) + { + if (that.getClass() != this.getClass()) + throw new IllegalArgumentException(String.format("Cannot mix Range of different types (%s and %s)", this.getClass().getSimpleName(), that.getClass().getSimpleName())); + } } diff --git a/accord-core/src/main/java/accord/primitives/Routables.java b/accord-core/src/main/java/accord/primitives/Routables.java index 627982191e..987c420fc0 100644 --- a/accord-core/src/main/java/accord/primitives/Routables.java +++ b/accord-core/src/main/java/accord/primitives/Routables.java @@ -24,18 +24,15 @@ import accord.utils.IndexedFoldToLong; import accord.utils.IndexedRangeFoldToLong; import accord.utils.SortedArrays; -import net.nicoulaj.compilecommand.annotations.Inline; -import java.util.function.BiFunction; -import java.util.function.Function; +import net.nicoulaj.compilecommand.annotations.Inline; -import static accord.primitives.Routables.Slice.Overlapping; import static accord.utils.SortedArrays.Search.FLOOR; /** * A collection of either Seekable or Unseekable */ -public interface Routables> extends Iterable +public interface Routables> extends Iterable { enum Slice { diff --git a/accord-core/src/main/java/accord/primitives/RoutingKeys.java b/accord-core/src/main/java/accord/primitives/RoutingKeys.java index 3db75ab3e6..bff7ad6001 100644 --- a/accord-core/src/main/java/accord/primitives/RoutingKeys.java +++ b/accord-core/src/main/java/accord/primitives/RoutingKeys.java @@ -23,7 +23,7 @@ import static accord.utils.ArrayBuffers.cachedRoutingKeys; -public class RoutingKeys extends AbstractUnseekableKeys> implements Unseekables> +public class RoutingKeys extends AbstractUnseekableKeys { public static class SerializationSupport { diff --git a/accord-core/src/main/java/accord/primitives/Seekable.java b/accord-core/src/main/java/accord/primitives/Seekable.java index a3cec1f642..90a46318ae 100644 --- a/accord-core/src/main/java/accord/primitives/Seekable.java +++ b/accord-core/src/main/java/accord/primitives/Seekable.java @@ -21,11 +21,24 @@ import accord.api.Key; /** - * Something that can be found within the cluster AND found on disk, queried and returned + * Something that can be found within the cluster AND found on disk, queried and returned - in particular, it includes + * enough information to be found on disk (in constrast to {@link Unseekable}. */ public interface Seekable extends Routable { + /** + * If this is a key, it will return itself, otherwise it throw an exception + */ Key asKey(); + + /** + * If this is a range, it will return itself, otherwise it throw an exception + */ Range asRange(); + + /** + * Returns a {@link Seekable} of the same type as this one truncated to the given range (intersection). + * If the provided range does not intersect with this {@link Seekable}, an exception is thrown. + */ Seekable slice(Range range); } diff --git a/accord-core/src/main/java/accord/primitives/Seekables.java b/accord-core/src/main/java/accord/primitives/Seekables.java index 3ae8448b57..fe4ed4d87d 100644 --- a/accord-core/src/main/java/accord/primitives/Seekables.java +++ b/accord-core/src/main/java/accord/primitives/Seekables.java @@ -25,7 +25,7 @@ /** * Either a Route or a collection of Routable */ -public interface Seekables> extends Routables +public interface Seekables> extends Routables { @Override default U slice(Ranges ranges) { return slice(ranges, Overlapping); } diff --git a/accord-core/src/main/java/accord/primitives/Unseekable.java b/accord-core/src/main/java/accord/primitives/Unseekable.java index 2c40e95d64..a86b91527d 100644 --- a/accord-core/src/main/java/accord/primitives/Unseekable.java +++ b/accord-core/src/main/java/accord/primitives/Unseekable.java @@ -19,7 +19,10 @@ package accord.primitives; /** - * Something that can only be routed, i.e. is NOT a Seekable. + * Something that can only be routed. + * In particular, it purposely does not contain enough information to found it on disk (in contrast to {@link Seekable}). + * Those two interfaces were created to explicitly distinguish what information it is intended to be serialized when + * exchanging data between nodes. {@link Seekable} contains more information, but it is not always necessary to send it. */ public interface Unseekable extends Routable { diff --git a/accord-core/src/main/java/accord/primitives/Unseekables.java b/accord-core/src/main/java/accord/primitives/Unseekables.java index 5294555c4d..4f02b50236 100644 --- a/accord-core/src/main/java/accord/primitives/Unseekables.java +++ b/accord-core/src/main/java/accord/primitives/Unseekables.java @@ -23,7 +23,7 @@ /** * Either a Route or a simple collection of keys or ranges */ -public interface Unseekables> extends Iterable, Routables +public interface Unseekables> extends Iterable, Routables { enum UnseekablesKind {