/*- * See the file LICENSE for redistribution information. * * Copyright (c) 2002, 2015 Oracle and/or its affiliates. All rights reserved. * */ package com.sleepycat.collections; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; import java.io.File; import java.io.Serializable; import java.util.Arrays; import java.util.Comparator; import org.junit.After; import org.junit.Test; import com.sleepycat.bind.ByteArrayBinding; import com.sleepycat.compat.DbCompat; import com.sleepycat.db.Database; import com.sleepycat.db.DatabaseConfig; import com.sleepycat.db.DatabaseEntry; import com.sleepycat.db.DatabaseException; import com.sleepycat.db.Environment; import com.sleepycat.db.EnvironmentConfig; import com.sleepycat.db.OperationStatus; import com.sleepycat.util.keyrange.KeyRange; import com.sleepycat.util.keyrange.KeyRangeException; import com.sleepycat.util.test.SharedTestUtils; import com.sleepycat.util.test.TestBase; /** * @author Mark Hayes */ public class KeyRangeTest extends TestBase { private static boolean VERBOSE = false; private static final byte FF = (byte) 0xFF; private static final byte[][] KEYS = { /* 0 */ {1}, /* 1 */ {FF}, /* 2 */ {FF, 0}, /* 3 */ {FF, 0x7F}, /* 4 */ {FF, FF}, /* 5 */ {FF, FF, 0}, /* 6 */ {FF, FF, 0x7F}, /* 7 */ {FF, FF, FF}, }; private static byte[][] EXTREME_KEY_BYTES = { /* 0 */ {0}, /* 1 */ {FF, FF, FF, FF}, }; private Environment env; private Database store; private DataView view; private DataCursor cursor; private void openDb(Comparator<byte []> comparator) throws Exception { File dir = SharedTestUtils.getNewDir(); ByteArrayBinding dataBinding = new ByteArrayBinding(); EnvironmentConfig envConfig = new EnvironmentConfig(); envConfig.setAllowCreate(true); DbCompat.setInitializeCache(envConfig, true); env = new Environment(dir, envConfig); DatabaseConfig dbConfig = new DatabaseConfig(); DbCompat.setTypeBtree(dbConfig); dbConfig.setAllowCreate(true); if (comparator != null) { DbCompat.setBtreeComparator(dbConfig, comparator); } store = DbCompat.testOpenDatabase (env, null, "test.db", null, dbConfig); view = new DataView(store, dataBinding, dataBinding, null, true, null); } private void closeDb() throws Exception { store.close(); store = null; env.close(); env = null; } @After public void tearDown() { try { if (store != null) { store.close(); } } catch (Exception e) { System.out.println("Exception ignored during close: " + e); } try { if (env != null) { env.close(); } } catch (Exception e) { System.out.println("Exception ignored during close: " + e); } /* Ensure that GC can cleanup. */ env = null; store = null; view = null; cursor = null; } @Test public void testScan() throws Exception { openDb(null); doScan(false); closeDb(); } @Test public void testScanComparator() throws Exception { openDb(new ReverseComparator()); doScan(true); closeDb(); } private void doScan(boolean reversed) throws Exception { byte[][] keys = new byte[KEYS.length][]; final int end = KEYS.length - 1; cursor = new DataCursor(view, true); for (int i = 0; i <= end; i++) { keys[i] = KEYS[i]; cursor.put(keys[i], KEYS[i], null, false); } cursor.close(); byte[][] extremeKeys = new byte[EXTREME_KEY_BYTES.length][]; for (int i = 0; i < extremeKeys.length; i++) { extremeKeys[i] = EXTREME_KEY_BYTES[i]; } // with empty range cursor = new DataCursor(view, false); expectRange(KEYS, 0, end, reversed); cursor.close(); // begin key only, inclusive for (int i = 0; i <= end; i++) { cursor = newCursor(view, keys[i], true, null, false, reversed); expectRange(KEYS, i, end, reversed); cursor.close(); } // begin key only, exclusive for (int i = 0; i <= end; i++) { cursor = newCursor(view, keys[i], false, null, false, reversed); expectRange(KEYS, i + 1, end, reversed); cursor.close(); } // end key only, inclusive for (int i = 0; i <= end; i++) { cursor = newCursor(view, null, false, keys[i], true, reversed); expectRange(KEYS, 0, i, reversed); cursor.close(); } // end key only, exclusive for (int i = 0; i <= end; i++) { cursor = newCursor(view, null, false, keys[i], false, reversed); expectRange(KEYS, 0, i - 1, reversed); cursor.close(); } // begin and end keys, inclusive and exclusive for (int i = 0; i <= end; i++) { for (int j = i; j <= end; j++) { // begin inclusive, end inclusive cursor = newCursor(view, keys[i], true, keys[j], true, reversed); expectRange(KEYS, i, j, reversed); cursor.close(); // begin inclusive, end exclusive cursor = newCursor(view, keys[i], true, keys[j], false, reversed); expectRange(KEYS, i, j - 1, reversed); cursor.close(); // begin exclusive, end inclusive cursor = newCursor(view, keys[i], false, keys[j], true, reversed); expectRange(KEYS, i + 1, j, reversed); cursor.close(); // begin exclusive, end exclusive cursor = newCursor(view, keys[i], false, keys[j], false, reversed); expectRange(KEYS, i + 1, j - 1, reversed); cursor.close(); } } // single key range for (int i = 0; i <= end; i++) { cursor = new DataCursor(view, false, keys[i]); expectRange(KEYS, i, i, reversed); cursor.close(); } // start with lower extreme (before any existing key) cursor = newCursor(view, extremeKeys[0], true, null, false, reversed); expectRange(KEYS, 0, end, reversed); cursor.close(); // start with higher extreme (after any existing key) cursor = newCursor(view, null, false, extremeKeys[1], true, reversed); expectRange(KEYS, 0, end, reversed); cursor.close(); } private DataCursor newCursor(DataView view, Object beginKey, boolean beginInclusive, Object endKey, boolean endInclusive, boolean reversed) throws Exception { if (reversed) { return new DataCursor(view, false, endKey, endInclusive, beginKey, beginInclusive); } else { return new DataCursor(view, false, beginKey, beginInclusive, endKey, endInclusive); } } private void expectRange(byte[][] bytes, int first, int last, boolean reversed) throws DatabaseException { int i; boolean init; for (init = true, i = first;; i++, init = false) { if (checkRange(bytes, first, last, i <= last, reversed, !reversed, init, i)) { break; } } for (init = true, i = last;; i--, init = false) { if (checkRange(bytes, first, last, i >= first, reversed, reversed, init, i)) { break; } } } private boolean checkRange(byte[][] bytes, int first, int last, boolean inRange, boolean reversed, boolean forward, boolean init, int i) throws DatabaseException { OperationStatus s; if (forward) { if (init) { s = cursor.getFirst(false); } else { s = cursor.getNext(false); } } else { if (init) { s = cursor.getLast(false); } else { s = cursor.getPrev(false); } } String msg = " " + (forward ? "next" : "prev") + " i=" + i + " first=" + first + " last=" + last + (reversed ? " reversed" : " not reversed"); // check that moving past ends doesn't move the cursor if (s == OperationStatus.SUCCESS && i == first) { OperationStatus s2 = reversed ? cursor.getNext(false) : cursor.getPrev(false); assertEquals(msg, OperationStatus.NOTFOUND, s2); } if (s == OperationStatus.SUCCESS && i == last) { OperationStatus s2 = reversed ? cursor.getPrev(false) : cursor.getNext(false); assertEquals(msg, OperationStatus.NOTFOUND, s2); } byte[] val = (s == OperationStatus.SUCCESS) ? ((byte[]) cursor.getCurrentValue()) : null; if (inRange) { assertNotNull("RangeNotFound" + msg, val); if (!Arrays.equals(val, bytes[i])){ printBytes(val); printBytes(bytes[i]); fail("RangeKeyNotEqual" + msg); } if (VERBOSE) { System.out.println("GotRange" + msg); } return false; } else { assertEquals("RangeExceeded" + msg, OperationStatus.NOTFOUND, s); return true; } } private void printBytes(byte[] bytes) { for (int i = 0; i < bytes.length; i += 1) { System.out.print(Integer.toHexString(bytes[i] & 0xFF)); System.out.print(' '); } System.out.println(); } @Test public void testSubRanges() { DatabaseEntry begin = new DatabaseEntry(); DatabaseEntry begin2 = new DatabaseEntry(); DatabaseEntry end = new DatabaseEntry(); DatabaseEntry end2 = new DatabaseEntry(); KeyRange range = new KeyRange(null); KeyRange range2 = null; /* Base range [1, 2] */ begin.setData(new byte[] { 1 }); end.setData(new byte[] { 2 }); range = range.subRange(begin, true, end, true); /* Subrange (0, 1] is invalid **. */ begin2.setData(new byte[] { 0 }); end2.setData(new byte[] { 1 }); try { range2 = range.subRange(begin2, false, end2, true); fail(); } catch (KeyRangeException expected) {} /* Subrange [1, 3) is invalid. */ begin2.setData(new byte[] { 1 }); end2.setData(new byte[] { 3 }); try { range2 = range.subRange(begin2, true, end2, false); fail(); } catch (KeyRangeException expected) {} /* Subrange [2, 2] is valid. */ begin2.setData(new byte[] { 2 }); end2.setData(new byte[] { 2 }); range2 = range.subRange(begin2, true, end2, true); /* Subrange [0, 1] is invalid. */ begin2.setData(new byte[] { 0 }); end2.setData(new byte[] { 1 }); try { range2 = range.subRange(begin2, true, end2, true); fail(); } catch (KeyRangeException expected) {} /* Subrange (0, 3] is invalid. */ begin2.setData(new byte[] { 0 }); end2.setData(new byte[] { 3 }); try { range2 = range.subRange(begin2, false, end2, true); fail(); } catch (KeyRangeException expected) {} /* Subrange [3, 3) is invalid. */ begin2.setData(new byte[] { 3 }); end2.setData(new byte[] { 3 }); try { range2 = range.subRange(begin2, true, end2, false); fail(); } catch (KeyRangeException expected) {} } @SuppressWarnings("serial") public static class ReverseComparator implements Comparator<byte[]>, Serializable { public int compare(byte[] d1, byte[] d2) { int cmp = KeyRange.compareBytes(d1, 0, d1.length, d2, 0, d2.length); if (cmp < 0) { return 1; } else if (cmp > 0) { return -1; } else { return 0; } } } }