|
20 | 20 | import com.google.cloud.bigtable.data.v2.models.Range.ByteStringRange; |
21 | 21 | import com.google.protobuf.ByteString; |
22 | 22 | import com.google.protobuf.TextFormat; |
| 23 | +import java.util.ArrayList; |
| 24 | +import java.util.Collections; |
23 | 25 | import java.util.Comparator; |
24 | 26 | import java.util.List; |
25 | 27 | import java.util.stream.Collectors; |
|
29 | 31 | /** Helper functions to evaluate the completeness of collection of ByteStringRanges. */ |
30 | 32 | @Internal |
31 | 33 | public class ByteStringRangeHelper { |
32 | | - /** |
33 | | - * Returns formatted string of a partition for debugging. |
34 | | - * |
35 | | - * @param partition partition to format. |
36 | | - * @return String representation of partition. |
37 | | - */ |
38 | | - public static String formatByteStringRange(ByteStringRange partition) { |
39 | | - return "['" |
40 | | - + TextFormat.escapeBytes(partition.getStart()) |
41 | | - + "','" |
42 | | - + TextFormat.escapeBytes(partition.getEnd()) |
43 | | - + "')"; |
44 | | - } |
45 | | - |
46 | | - /** |
47 | | - * Convert partitions to a string for debugging. |
48 | | - * |
49 | | - * @param partitions to print |
50 | | - * @return string representation of partitions |
51 | | - */ |
52 | | - public static String partitionsToString(List<ByteStringRange> partitions) { |
53 | | - return partitions.stream() |
54 | | - .map(ByteStringRangeHelper::formatByteStringRange) |
55 | | - .collect(Collectors.joining(", ", "{", "}")); |
56 | | - } |
57 | 34 |
|
58 | 35 | @VisibleForTesting |
59 | 36 | static class PartitionComparator implements Comparator<ByteStringRange> { |
@@ -83,6 +60,104 @@ public int compare(ByteStringRange first, ByteStringRange second) { |
83 | 60 | } |
84 | 61 | } |
85 | 62 |
|
| 63 | + /** |
| 64 | + * Returns true if parentPartitions is a superset of childPartition. |
| 65 | + * |
| 66 | + * <p>If ordered parentPartitions row ranges form a contiguous range, and start key is before or |
| 67 | + * at childPartition's start key, and end key is at or after childPartition's end key, then |
| 68 | + * parentPartitions is a superset of childPartition. |
| 69 | + * |
| 70 | + * <p>Overlaps from parents are valid because arbitrary partitions can merge and they may overlap. |
| 71 | + * They will form a valid new partition. However, if there are any missing parent partitions, then |
| 72 | + * merge cannot happen with missing row ranges. |
| 73 | + * |
| 74 | + * @param parentPartitions list of partitions to determine if it forms a large contiguous range |
| 75 | + * @param childPartition the smaller partition |
| 76 | + * @return true if parentPartitions is a superset of childPartition, otherwise false. |
| 77 | + */ |
| 78 | + public static boolean isSuperset( |
| 79 | + List<ByteStringRange> parentPartitions, ByteStringRange childPartition) { |
| 80 | + // sort parentPartitions by starting key |
| 81 | + // iterate through, check open end key and close start key of each iteration to ensure no gaps. |
| 82 | + // first start key and last end key must be equal to or wider than child partition start and end |
| 83 | + // key. |
| 84 | + if (parentPartitions.isEmpty()) { |
| 85 | + return false; |
| 86 | + } |
| 87 | + parentPartitions.sort(new PartitionComparator()); |
| 88 | + ByteString parentStartKey = parentPartitions.get(0).getStart(); |
| 89 | + ByteString parentEndKey = parentPartitions.get(parentPartitions.size() - 1).getEnd(); |
| 90 | + |
| 91 | + return !childStartsBeforeParent(parentStartKey, childPartition.getStart()) |
| 92 | + && !childEndsAfterParent(parentEndKey, childPartition.getEnd()) |
| 93 | + && !gapsInParentPartitions(parentPartitions); |
| 94 | + } |
| 95 | + |
| 96 | + /** |
| 97 | + * Convert partitions to a string for debugging. |
| 98 | + * |
| 99 | + * @param partitions to print |
| 100 | + * @return string representation of partitions |
| 101 | + */ |
| 102 | + public static String partitionsToString(List<ByteStringRange> partitions) { |
| 103 | + return partitions.stream() |
| 104 | + .map(ByteStringRangeHelper::formatByteStringRange) |
| 105 | + .collect(Collectors.joining(", ", "{", "}")); |
| 106 | + } |
| 107 | + |
| 108 | + /** |
| 109 | + * Figure out if partitions cover the entire keyspace. If it doesn't, return a list of missing and |
| 110 | + * overlapping partitions. |
| 111 | + * |
| 112 | + * <p>partitions covers the entire key space if, when ordered, the end key is the same as the |
| 113 | + * start key of the next row range in the list, and the first start key is "" and the last end key |
| 114 | + * is "". There should be no overlap. |
| 115 | + * |
| 116 | + * @param partitions to determine if they cover entire keyspace |
| 117 | + * @return list of missing and overlapping partitions |
| 118 | + */ |
| 119 | + public static List<ByteStringRange> getMissingAndOverlappingPartitionsFromKeySpace( |
| 120 | + List<ByteStringRange> partitions) { |
| 121 | + if (partitions.isEmpty()) { |
| 122 | + return Collections.singletonList(ByteStringRange.create("", "")); |
| 123 | + } |
| 124 | + |
| 125 | + List<ByteStringRange> missingPartitions = new ArrayList<>(); |
| 126 | + |
| 127 | + // sort partitions by start key |
| 128 | + // iterate through ensuring end key is lexicographically after next start key. |
| 129 | + partitions.sort(new PartitionComparator()); |
| 130 | + |
| 131 | + ByteString prevEnd = ByteString.EMPTY; |
| 132 | + for (ByteStringRange partition : partitions) { |
| 133 | + if (!partition.getStart().equals(prevEnd)) { |
| 134 | + ByteStringRange missingPartition = ByteStringRange.create(prevEnd, partition.getStart()); |
| 135 | + missingPartitions.add(missingPartition); |
| 136 | + } |
| 137 | + prevEnd = partition.getEnd(); |
| 138 | + } |
| 139 | + // Check that the last partition ends with "", otherwise it's missing. |
| 140 | + if (!prevEnd.equals(ByteString.EMPTY)) { |
| 141 | + ByteStringRange missingPartition = ByteStringRange.create(prevEnd, ByteString.EMPTY); |
| 142 | + missingPartitions.add(missingPartition); |
| 143 | + } |
| 144 | + return missingPartitions; |
| 145 | + } |
| 146 | + |
| 147 | + /** |
| 148 | + * Returns formatted string of a partition for debugging. |
| 149 | + * |
| 150 | + * @param partition partition to format. |
| 151 | + * @return String representation of partition. |
| 152 | + */ |
| 153 | + public static String formatByteStringRange(ByteStringRange partition) { |
| 154 | + return "['" |
| 155 | + + TextFormat.escapeBytes(partition.getStart()) |
| 156 | + + "','" |
| 157 | + + TextFormat.escapeBytes(partition.getEnd()) |
| 158 | + + "')"; |
| 159 | + } |
| 160 | + |
86 | 161 | private static boolean childStartsBeforeParent( |
87 | 162 | ByteString parentStartKey, ByteString childStartKey) { |
88 | 163 | // Check if the start key of the child partition comes before the start key of the entire |
@@ -121,37 +196,4 @@ private static boolean gapsInParentPartitions(List<ByteStringRange> sortedParent |
121 | 196 | } |
122 | 197 | return false; |
123 | 198 | } |
124 | | - |
125 | | - /** |
126 | | - * Returns true if parentPartitions is a superset of childPartition. |
127 | | - * |
128 | | - * <p>If ordered parentPartitions row ranges form a contiguous range, and start key is before or |
129 | | - * at childPartition's start key, and end key is at or after childPartition's end key, then |
130 | | - * parentPartitions is a superset of childPartition. |
131 | | - * |
132 | | - * <p>Overlaps from parents are valid because arbitrary partitions can merge and they may overlap. |
133 | | - * They will form a valid new partition. However, if there are any missing parent partitions, then |
134 | | - * merge cannot happen with missing row ranges. |
135 | | - * |
136 | | - * @param parentPartitions list of partitions to determine if it forms a large contiguous range |
137 | | - * @param childPartition the smaller partition |
138 | | - * @return true if parentPartitions is a superset of childPartition, otherwise false. |
139 | | - */ |
140 | | - public static boolean isSuperset( |
141 | | - List<ByteStringRange> parentPartitions, ByteStringRange childPartition) { |
142 | | - // sort parentPartitions by starting key |
143 | | - // iterate through, check open end key and close start key of each iteration to ensure no gaps. |
144 | | - // first start key and last end key must be equal to or wider than child partition start and end |
145 | | - // key. |
146 | | - if (parentPartitions.isEmpty()) { |
147 | | - return false; |
148 | | - } |
149 | | - parentPartitions.sort(new PartitionComparator()); |
150 | | - ByteString parentStartKey = parentPartitions.get(0).getStart(); |
151 | | - ByteString parentEndKey = parentPartitions.get(parentPartitions.size() - 1).getEnd(); |
152 | | - |
153 | | - return !childStartsBeforeParent(parentStartKey, childPartition.getStart()) |
154 | | - && !childEndsAfterParent(parentEndKey, childPartition.getEnd()) |
155 | | - && !gapsInParentPartitions(parentPartitions); |
156 | | - } |
157 | 199 | } |
0 commit comments