0001
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018 package org.apache.spark.util.kvstore;
0019
0020 import java.util.Arrays;
0021 import java.util.ArrayList;
0022 import java.util.Collections;
0023 import java.util.Comparator;
0024 import java.util.Iterator;
0025 import java.util.List;
0026 import java.util.Random;
0027
0028 import com.google.common.collect.Iterables;
0029 import com.google.common.collect.Iterators;
0030 import com.google.common.collect.Lists;
0031 import org.junit.AfterClass;
0032 import org.junit.Before;
0033 import org.junit.BeforeClass;
0034 import org.junit.Test;
0035 import org.slf4j.Logger;
0036 import org.slf4j.LoggerFactory;
0037 import static org.junit.Assert.*;
0038
0039 public abstract class DBIteratorSuite {
0040
0041 private static final Logger LOG = LoggerFactory.getLogger(DBIteratorSuite.class);
0042
0043 private static final int MIN_ENTRIES = 42;
0044 private static final int MAX_ENTRIES = 1024;
0045 private static final Random RND = new Random();
0046
0047 private static List<CustomType1> allEntries;
0048 private static List<CustomType1> clashingEntries;
0049 private static KVStore db;
0050
0051 private interface BaseComparator extends Comparator<CustomType1> {
0052
0053
0054
0055
0056 default BaseComparator fallback() {
0057 return (t1, t2) -> {
0058 int result = BaseComparator.this.compare(t1, t2);
0059 if (result != 0) {
0060 return result;
0061 }
0062
0063 return t1.key.compareTo(t2.key);
0064 };
0065 }
0066
0067
0068 default BaseComparator reverse() {
0069 return (t1, t2) -> -BaseComparator.this.compare(t1, t2);
0070 }
0071 }
0072
0073 private static final BaseComparator NATURAL_ORDER = (t1, t2) -> t1.key.compareTo(t2.key);
0074 private static final BaseComparator REF_INDEX_ORDER = (t1, t2) -> t1.id.compareTo(t2.id);
0075 private static final BaseComparator COPY_INDEX_ORDER = (t1, t2) -> t1.name.compareTo(t2.name);
0076 private static final BaseComparator NUMERIC_INDEX_ORDER = (t1, t2) -> {
0077 return Integer.valueOf(t1.num).compareTo(t2.num);
0078 };
0079 private static final BaseComparator CHILD_INDEX_ORDER = (t1, t2) -> t1.child.compareTo(t2.child);
0080
0081
0082
0083
0084
0085
0086 protected abstract KVStore createStore() throws Exception;
0087
0088 @BeforeClass
0089 public static void setupClass() {
0090 long seed = RND.nextLong();
0091 LOG.info("Random seed: {}", seed);
0092 RND.setSeed(seed);
0093 }
0094
0095 @AfterClass
0096 public static void cleanupData() throws Exception {
0097 allEntries = null;
0098 db = null;
0099 }
0100
0101 @Before
0102 public void setup() throws Exception {
0103 if (db != null) {
0104 return;
0105 }
0106
0107 db = createStore();
0108
0109 int count = RND.nextInt(MAX_ENTRIES) + MIN_ENTRIES;
0110
0111 allEntries = new ArrayList<>(count);
0112 for (int i = 0; i < count; i++) {
0113 CustomType1 t = new CustomType1();
0114 t.key = "key" + i;
0115 t.id = "id" + i;
0116 t.name = "name" + RND.nextInt(MAX_ENTRIES);
0117
0118 t.num = (i != 0) ? (int) RND.nextLong() : 0;
0119 t.child = "child" + (i % MIN_ENTRIES);
0120 allEntries.add(t);
0121 }
0122
0123
0124 Collections.shuffle(allEntries, RND);
0125 for (CustomType1 e : allEntries) {
0126 db.write(e);
0127 }
0128
0129
0130
0131
0132
0133
0134
0135 CustomType1 first = allEntries.get(0);
0136 clashingEntries = new ArrayList<>();
0137
0138 int clashCount = RND.nextInt(MIN_ENTRIES) + 1;
0139 for (int i = 0; i < clashCount; i++) {
0140 CustomType1 t = new CustomType1();
0141 t.key = "n-key" + (count + i);
0142 t.id = first.id;
0143 t.name = first.name;
0144 t.num = first.num;
0145 t.child = first.child;
0146 allEntries.add(t);
0147 clashingEntries.add(t);
0148 db.write(t);
0149 }
0150
0151
0152
0153
0154 CustomType1 t = new CustomType1();
0155 t.key = "extended-key-0";
0156 t.id = first.id;
0157 t.name = first.name + "a";
0158 t.num = first.num;
0159 t.child = first.child;
0160 allEntries.add(t);
0161 db.write(t);
0162 }
0163
0164 @Test
0165 public void naturalIndex() throws Exception {
0166 testIteration(NATURAL_ORDER, view(), null, null);
0167 }
0168
0169 @Test
0170 public void refIndex() throws Exception {
0171 testIteration(REF_INDEX_ORDER, view().index("id"), null, null);
0172 }
0173
0174 @Test
0175 public void copyIndex() throws Exception {
0176 testIteration(COPY_INDEX_ORDER, view().index("name"), null, null);
0177 }
0178
0179 @Test
0180 public void numericIndex() throws Exception {
0181 testIteration(NUMERIC_INDEX_ORDER, view().index("int"), null, null);
0182 }
0183
0184 @Test
0185 public void childIndex() throws Exception {
0186 CustomType1 any = pickLimit();
0187 testIteration(CHILD_INDEX_ORDER, view().index("child").parent(any.id), null, null);
0188 }
0189
0190 @Test
0191 public void naturalIndexDescending() throws Exception {
0192 testIteration(NATURAL_ORDER, view().reverse(), null, null);
0193 }
0194
0195 @Test
0196 public void refIndexDescending() throws Exception {
0197 testIteration(REF_INDEX_ORDER, view().index("id").reverse(), null, null);
0198 }
0199
0200 @Test
0201 public void copyIndexDescending() throws Exception {
0202 testIteration(COPY_INDEX_ORDER, view().index("name").reverse(), null, null);
0203 }
0204
0205 @Test
0206 public void numericIndexDescending() throws Exception {
0207 testIteration(NUMERIC_INDEX_ORDER, view().index("int").reverse(), null, null);
0208 }
0209
0210 @Test
0211 public void childIndexDescending() throws Exception {
0212 CustomType1 any = pickLimit();
0213 testIteration(CHILD_INDEX_ORDER, view().index("child").parent(any.id).reverse(), null, null);
0214 }
0215
0216 @Test
0217 public void naturalIndexWithStart() throws Exception {
0218 CustomType1 first = pickLimit();
0219 testIteration(NATURAL_ORDER, view().first(first.key), first, null);
0220 }
0221
0222 @Test
0223 public void refIndexWithStart() throws Exception {
0224 CustomType1 first = pickLimit();
0225 testIteration(REF_INDEX_ORDER, view().index("id").first(first.id), first, null);
0226 }
0227
0228 @Test
0229 public void copyIndexWithStart() throws Exception {
0230 CustomType1 first = pickLimit();
0231 testIteration(COPY_INDEX_ORDER, view().index("name").first(first.name), first, null);
0232 }
0233
0234 @Test
0235 public void numericIndexWithStart() throws Exception {
0236 CustomType1 first = pickLimit();
0237 testIteration(NUMERIC_INDEX_ORDER, view().index("int").first(first.num), first, null);
0238 }
0239
0240 @Test
0241 public void childIndexWithStart() throws Exception {
0242 CustomType1 any = pickLimit();
0243 testIteration(CHILD_INDEX_ORDER, view().index("child").parent(any.id).first(any.child), null,
0244 null);
0245 }
0246
0247 @Test
0248 public void naturalIndexDescendingWithStart() throws Exception {
0249 CustomType1 first = pickLimit();
0250 testIteration(NATURAL_ORDER, view().reverse().first(first.key), first, null);
0251 }
0252
0253 @Test
0254 public void refIndexDescendingWithStart() throws Exception {
0255 CustomType1 first = pickLimit();
0256 testIteration(REF_INDEX_ORDER, view().reverse().index("id").first(first.id), first, null);
0257 }
0258
0259 @Test
0260 public void copyIndexDescendingWithStart() throws Exception {
0261 CustomType1 first = pickLimit();
0262 testIteration(COPY_INDEX_ORDER, view().reverse().index("name").first(first.name), first, null);
0263 }
0264
0265 @Test
0266 public void numericIndexDescendingWithStart() throws Exception {
0267 CustomType1 first = pickLimit();
0268 testIteration(NUMERIC_INDEX_ORDER, view().reverse().index("int").first(first.num), first, null);
0269 }
0270
0271 @Test
0272 public void childIndexDescendingWithStart() throws Exception {
0273 CustomType1 any = pickLimit();
0274 testIteration(CHILD_INDEX_ORDER,
0275 view().index("child").parent(any.id).first(any.child).reverse(), null, null);
0276 }
0277
0278 @Test
0279 public void naturalIndexWithSkip() throws Exception {
0280 testIteration(NATURAL_ORDER, view().skip(pickCount()), null, null);
0281 }
0282
0283 @Test
0284 public void refIndexWithSkip() throws Exception {
0285 testIteration(REF_INDEX_ORDER, view().index("id").skip(pickCount()), null, null);
0286 }
0287
0288 @Test
0289 public void copyIndexWithSkip() throws Exception {
0290 testIteration(COPY_INDEX_ORDER, view().index("name").skip(pickCount()), null, null);
0291 }
0292
0293 @Test
0294 public void childIndexWithSkip() throws Exception {
0295 CustomType1 any = pickLimit();
0296 testIteration(CHILD_INDEX_ORDER, view().index("child").parent(any.id).skip(pickCount()),
0297 null, null);
0298 }
0299
0300 @Test
0301 public void naturalIndexWithMax() throws Exception {
0302 testIteration(NATURAL_ORDER, view().max(pickCount()), null, null);
0303 }
0304
0305 @Test
0306 public void copyIndexWithMax() throws Exception {
0307 testIteration(COPY_INDEX_ORDER, view().index("name").max(pickCount()), null, null);
0308 }
0309
0310 @Test
0311 public void childIndexWithMax() throws Exception {
0312 CustomType1 any = pickLimit();
0313 testIteration(CHILD_INDEX_ORDER, view().index("child").parent(any.id).max(pickCount()), null,
0314 null);
0315 }
0316
0317 @Test
0318 public void naturalIndexWithLast() throws Exception {
0319 CustomType1 last = pickLimit();
0320 testIteration(NATURAL_ORDER, view().last(last.key), null, last);
0321 }
0322
0323 @Test
0324 public void refIndexWithLast() throws Exception {
0325 CustomType1 last = pickLimit();
0326 testIteration(REF_INDEX_ORDER, view().index("id").last(last.id), null, last);
0327 }
0328
0329 @Test
0330 public void copyIndexWithLast() throws Exception {
0331 CustomType1 last = pickLimit();
0332 testIteration(COPY_INDEX_ORDER, view().index("name").last(last.name), null, last);
0333 }
0334
0335 @Test
0336 public void numericIndexWithLast() throws Exception {
0337 CustomType1 last = pickLimit();
0338 testIteration(NUMERIC_INDEX_ORDER, view().index("int").last(last.num), null, last);
0339 }
0340
0341 @Test
0342 public void childIndexWithLast() throws Exception {
0343 CustomType1 any = pickLimit();
0344 testIteration(CHILD_INDEX_ORDER, view().index("child").parent(any.id).last(any.child), null,
0345 null);
0346 }
0347
0348 @Test
0349 public void naturalIndexDescendingWithLast() throws Exception {
0350 CustomType1 last = pickLimit();
0351 testIteration(NATURAL_ORDER, view().reverse().last(last.key), null, last);
0352 }
0353
0354 @Test
0355 public void refIndexDescendingWithLast() throws Exception {
0356 CustomType1 last = pickLimit();
0357 testIteration(REF_INDEX_ORDER, view().reverse().index("id").last(last.id), null, last);
0358 }
0359
0360 @Test
0361 public void copyIndexDescendingWithLast() throws Exception {
0362 CustomType1 last = pickLimit();
0363 testIteration(COPY_INDEX_ORDER, view().reverse().index("name").last(last.name),
0364 null, last);
0365 }
0366
0367 @Test
0368 public void numericIndexDescendingWithLast() throws Exception {
0369 CustomType1 last = pickLimit();
0370 testIteration(NUMERIC_INDEX_ORDER, view().reverse().index("int").last(last.num),
0371 null, last);
0372 }
0373
0374 @Test
0375 public void childIndexDescendingWithLast() throws Exception {
0376 CustomType1 any = pickLimit();
0377 testIteration(CHILD_INDEX_ORDER, view().index("child").parent(any.id).last(any.child).reverse(),
0378 null, null);
0379 }
0380
0381 @Test
0382 public void testRefWithIntNaturalKey() throws Exception {
0383 LevelDBSuite.IntKeyType i = new LevelDBSuite.IntKeyType();
0384 i.key = 1;
0385 i.id = "1";
0386 i.values = Arrays.asList("1");
0387
0388 db.write(i);
0389
0390 try(KVStoreIterator<?> it = db.view(i.getClass()).closeableIterator()) {
0391 Object read = it.next();
0392 assertEquals(i, read);
0393 }
0394 }
0395
0396 private CustomType1 pickLimit() {
0397
0398 return clashingEntries.get(RND.nextInt(clashingEntries.size()));
0399 }
0400
0401 private int pickCount() {
0402 int count = RND.nextInt(allEntries.size() / 2);
0403 return Math.max(count, 1);
0404 }
0405
0406
0407
0408
0409
0410 private <T extends Comparable<T>> int compareWithFallback(
0411 T v1,
0412 T v2,
0413 CustomType1 ct1,
0414 CustomType1 ct2) {
0415 int result = v1.compareTo(v2);
0416 if (result != 0) {
0417 return result;
0418 }
0419
0420 return ct1.key.compareTo(ct2.key);
0421 }
0422
0423 private void testIteration(
0424 final BaseComparator order,
0425 final KVStoreView<CustomType1> params,
0426 final CustomType1 first,
0427 final CustomType1 last) throws Exception {
0428 List<CustomType1> indexOrder = sortBy(order.fallback());
0429 if (!params.ascending) {
0430 indexOrder = Lists.reverse(indexOrder);
0431 }
0432
0433 Iterable<CustomType1> expected = indexOrder;
0434 BaseComparator expectedOrder = params.ascending ? order : order.reverse();
0435
0436 if (params.parent != null) {
0437 expected = Iterables.filter(expected, v -> params.parent.equals(v.id));
0438 }
0439
0440 if (first != null) {
0441 expected = Iterables.filter(expected, v -> expectedOrder.compare(first, v) <= 0);
0442 }
0443
0444 if (last != null) {
0445 expected = Iterables.filter(expected, v -> expectedOrder.compare(v, last) <= 0);
0446 }
0447
0448 if (params.skip > 0) {
0449 expected = Iterables.skip(expected, (int) params.skip);
0450 }
0451
0452 if (params.max != Long.MAX_VALUE) {
0453 expected = Iterables.limit(expected, (int) params.max);
0454 }
0455
0456 List<CustomType1> actual = collect(params);
0457 compareLists(expected, actual);
0458 }
0459
0460
0461 private void compareLists(Iterable<?> expected, List<?> actual) {
0462 Iterator<?> expectedIt = expected.iterator();
0463 Iterator<?> actualIt = actual.iterator();
0464
0465 int count = 0;
0466 while (expectedIt.hasNext()) {
0467 if (!actualIt.hasNext()) {
0468 break;
0469 }
0470 count++;
0471 assertEquals(expectedIt.next(), actualIt.next());
0472 }
0473
0474 String message;
0475 Object[] remaining;
0476 int expectedCount = count;
0477 int actualCount = count;
0478
0479 if (expectedIt.hasNext()) {
0480 remaining = Iterators.toArray(expectedIt, Object.class);
0481 expectedCount += remaining.length;
0482 message = "missing";
0483 } else {
0484 remaining = Iterators.toArray(actualIt, Object.class);
0485 actualCount += remaining.length;
0486 message = "stray";
0487 }
0488
0489 assertEquals(String.format("Found %s elements: %s", message, Arrays.asList(remaining)),
0490 expectedCount, actualCount);
0491 }
0492
0493 private KVStoreView<CustomType1> view() throws Exception {
0494 return db.view(CustomType1.class);
0495 }
0496
0497 private List<CustomType1> collect(KVStoreView<CustomType1> view) throws Exception {
0498 return Arrays.asList(Iterables.toArray(view, CustomType1.class));
0499 }
0500
0501 private List<CustomType1> sortBy(Comparator<CustomType1> comp) {
0502 List<CustomType1> copy = new ArrayList<>(allEntries);
0503 Collections.sort(copy, comp);
0504 return copy;
0505 }
0506
0507 }