/* * Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License, Version * 1.0, and under the Eclipse Public License, Version 1.0 * (http://h2database.com/html/license.html). Initial Developer: H2 Group */ package org.h2.test.store; import java.util.ArrayList; import java.util.Iterator; import java.util.Map; import java.util.Random; import java.util.TreeMap; import org.h2.dev.store.btree.MVMap; import org.h2.dev.store.btree.MVStore; import org.h2.store.fs.FileUtils; import org.h2.test.TestBase; import org.h2.util.New; /** * Tests the tree map store. */ public class TestMVStore extends TestBase { /** * Run just this test. * * @param a ignored */ public static void main(String... a) throws Exception { TestBase.createCaller().init().test(); } public void test() throws InterruptedException { testIterateOldVersion(); testObjects(); testExample(); testIterateOverChanges(); testOpenStoreCloseLoop(); testVersion(); testTruncateFile(); testFastDelete(); testRollbackInMemory(); testRollbackStored(); testMeta(); testInMemory(); testLargeImport(); testBtreeStore(); testDefragment(); testReuseSpace(); testRandom(); testKeyValueClasses(); testIterate(); testCloseTwice(); testSimple(); } private void testIterateOldVersion() { MVStore s; Map<Integer, Integer> map; s = MVStore.open(null, new TestMapFactory()); map = s.openMap("test"); int len = 100; for (int i = 0; i < len; i++) { map.put(i, 10 * i); } Iterator<Integer> it = map.keySet().iterator(); s.incrementVersion(); for (int i = 0; i < len; i += 2) { map.remove(i); } int count = 0; while (it.hasNext()) { it.next(); count++; } assertEquals(len, count); s.close(); } private void testObjects() { String fileName = getBaseDir() + "/testObjects.h3"; FileUtils.delete(fileName); MVStore s; Map<Object, Object> map; s = MVStore.open(fileName, new TestMapFactory()); map = s.openMap("test"); map.put(1, "Hello"); map.put("2", 200); map.put(new Object[1], new Object[]{1, "2"}); s.store(); s.close(); s = MVStore.open(fileName, new TestMapFactory()); map = s.openMap("test"); assertEquals("Hello", map.get(1).toString()); assertEquals(200, ((Integer) map.get("2")).intValue()); Object[] x = (Object[]) map.get(new Object[1]); assertEquals(2, x.length); assertEquals(1, ((Integer) x[0]).intValue()); assertEquals("2", (String) x[1]); s.close(); } private void testExample() { String fileName = getBaseDir() + "/testExample.h3"; FileUtils.delete(fileName); // open the store (in-memory if fileName is null) MVStore s = MVStore.open(fileName); // create/get the map "data" // the String.class, String.class will be optional later MVMap<String, String> map = s.openMap("data", String.class, String.class); // add some data map.put("1", "Hello"); map.put("2", "World"); // get the current version, for later use long oldVersion = s.getCurrentVersion(); // from now on, the old version is read-only s.incrementVersion(); // more changes, in the new version // changes can be rolled back if required // changes always go into 'head' (the newest version) map.put("1", "Hi"); map.remove("2"); // access the old data (before incrementVersion) MVMap<String, String> oldMap = map.openVersion(oldVersion); // store the newest data to disk s.store(); // print the old version (can be done // concurrently with further modifications) // this will print Hello World // System.out.println(oldMap.get("1")); // System.out.println(oldMap.get("2")); oldMap.close(); // print the newest version ("Hi") // System.out.println(map.get("1")); // close the store - this doesn't write to disk s.close(); } private void testOpenStoreCloseLoop() { String fileName = getBaseDir() + "/testOpenClose.h3"; FileUtils.delete(fileName); for (int k = 0; k < 1; k++) { // long t = System.currentTimeMillis(); for (int j = 0; j < 3; j++) { MVStore s = openStore(fileName); Map<String, Integer> m = s.openMap("data", String.class, Integer.class); for (int i = 0; i < 3; i++) { Integer x = m.get("value"); m.put("value", x == null ? 0 : x + 1); s.store(); } s.close(); } // System.out.println("open/close: " + (System.currentTimeMillis() - t)); // System.out.println("size: " + FileUtils.size(fileName)); } } private void testIterateOverChanges() { String fileName = getBaseDir() + "/testIterate.h3"; FileUtils.delete(fileName); MVStore s = openStore(fileName); s.setMaxPageSize(6); MVMap<Integer, String> m = s.openMap("data", Integer.class, String.class); for (int i = 0; i < 100; i++) { m.put(i, "Hi"); } s.incrementVersion(); s.store(); for (int i = 20; i < 40; i++) { assertEquals("Hi", m.put(i, "Hello")); } s.incrementVersion(); for (int i = 10; i < 15; i++) { m.put(i, "Hallo"); } m.put(50, "Hallo"); for (int i = 90; i < 100; i++) { assertEquals("Hi", m.remove(i)); } assertEquals(null, m.put(100, "Hallo")); Iterator<Integer> it = m.changeIterator(s.getCurrentVersion()); ArrayList<Integer> list = New.arrayList(); while (it.hasNext()) { list.add(it.next()); } assertEquals("[9, 10, 11, 12, 13, 14, 48, 49, 50, 87, 88, 89, 100]", list.toString()); s.close(); } private void testVersion() { String fileName = getBaseDir() + "/testVersion.h3"; FileUtils.delete(fileName); MVStore s; s = openStore(fileName); MVMap<String, String> m; s = openStore(fileName); m = s.openMap("data", String.class, String.class); long first = s.getCurrentVersion(); s.incrementVersion(); m.put("1", "Hello"); m.put("2", "World"); for (int i = 10; i < 20; i++) { m.put("" + i, "data"); } long old = s.getCurrentVersion(); s.incrementVersion(); m.put("1", "Hallo"); m.put("2", "Welt"); MVMap<String, String> mFirst; mFirst = m.openVersion(first); assertEquals(0, mFirst.size()); MVMap<String, String> mOld; assertEquals("Hallo", m.get("1")); assertEquals("Welt", m.get("2")); mOld = m.openVersion(old); assertEquals("Hello", mOld.get("1")); assertEquals("World", mOld.get("2")); assertTrue(mOld.isReadOnly()); s.getCurrentVersion(); s.setRetainChunk(0); long old2 = s.store(); // the old version is still available assertEquals("Hello", mOld.get("1")); assertEquals("World", mOld.get("2")); m.put("1", "Hi"); assertEquals("Welt", m.remove("2")); s.store(); s.close(); s = openStore(fileName); m = s.openMap("data", String.class, String.class); assertEquals("Hi", m.get("1")); assertEquals(null, m.get("2")); mOld = m.openVersion(old2); assertEquals("Hallo", mOld.get("1")); assertEquals("Welt", mOld.get("2")); s.close(); } private void testTruncateFile() { String fileName = getBaseDir() + "/testTruncate.h3"; FileUtils.delete(fileName); MVStore s; MVMap<Integer, String> m; s = openStore(fileName); m = s.openMap("data", Integer.class, String.class); for (int i = 0; i < 1000; i++) { m.put(i, "Hello World"); } s.store(); s.close(); long len = FileUtils.size(fileName); s = openStore(fileName); m = s.openMap("data", Integer.class, String.class); m.clear(); s.store(); s.compact(100); s.close(); long len2 = FileUtils.size(fileName); assertTrue(len2 < len); } private void testFastDelete() { String fileName = getBaseDir() + "/testFastDelete.h3"; FileUtils.delete(fileName); MVStore s; MVMap<Integer, String> m; s = openStore(fileName); s.setMaxPageSize(100); m = s.openMap("data", Integer.class, String.class); for (int i = 0; i < 1000; i++) { m.put(i, "Hello World"); assertEquals(i + 1, m.size()); } assertEquals(1000, m.size()); s.store(); assertEquals(3, s.getWriteCount()); s.close(); s = openStore(fileName); m = s.openMap("data", Integer.class, String.class); m.clear(); assertEquals(0, m.size()); s.store(); // ensure only nodes are read, but not leaves assertEquals(4, s.getReadCount()); assertEquals(2, s.getWriteCount()); s.close(); } private void testRollbackStored() { String fileName = getBaseDir() + "/testRollback.h3"; FileUtils.delete(fileName); MVMap<String, String> meta; MVStore s = openStore(fileName); assertEquals(-1, s.getRetainChunk()); s.setRetainChunk(0); assertEquals(0, s.getRetainChunk()); assertEquals(1, s.getCurrentVersion()); assertFalse(s.hasUnsavedChanges()); MVMap<String, String> m = s.openMap("data", String.class, String.class); assertTrue(s.hasUnsavedChanges()); MVMap<String, String> m0 = s.openMap("data0", String.class, String.class); m.put("1", "Hello"); assertEquals(1, s.incrementVersion()); s.rollbackTo(1); assertEquals("Hello", m.get("1")); long v2 = s.store(); assertEquals(2, v2); assertEquals(3, s.getCurrentVersion()); assertFalse(s.hasUnsavedChanges()); s.close(); s = openStore(fileName); assertEquals(3, s.getCurrentVersion()); s.setRetainChunk(0); meta = s.getMetaMap(); m = s.openMap("data", String.class, String.class); m0 = s.openMap("data0", String.class, String.class); MVMap<String, String> m1 = s.openMap("data1", String.class, String.class); m.put("1", "Hallo"); m0.put("1", "Hallo"); m1.put("1", "Hallo"); assertEquals("Hallo", m.get("1")); assertEquals("Hallo", m1.get("1")); assertTrue(s.hasUnsavedChanges()); s.rollbackTo(v2); assertFalse(s.hasUnsavedChanges()); assertNull(meta.get("map.data1")); assertNull(m0.get("1")); assertEquals("Hello", m.get("1")); s.store(); s.close(); s = openStore(fileName); s.setRetainChunk(0); assertEquals(3, s.getCurrentVersion()); meta = s.getMetaMap(); assertTrue(meta.get("map.data") != null); assertTrue(meta.get("map.data0") != null); assertNull(meta.get("map.data1")); m = s.openMap("data", String.class, String.class); m0 = s.openMap("data0", String.class, String.class); assertNull(m0.get("1")); assertEquals("Hello", m.get("1")); assertFalse(m0.isReadOnly()); m.put("1", "Hallo"); s.incrementVersion(); assertEquals(4, s.getCurrentVersion()); long v4 = s.store(); assertEquals(4, v4); assertEquals(5, s.getCurrentVersion()); s.close(); s = openStore(fileName); s.setRetainChunk(0); m = s.openMap("data", String.class, String.class); m.put("1", "Hello"); s.store(); s.close(); s = openStore(fileName); s.setRetainChunk(0); m = s.openMap("data", String.class, String.class); assertEquals("Hello", m.get("1")); s.rollbackTo(v4); assertEquals("Hallo", m.get("1")); s.close(); s = openStore(fileName); s.setRetainChunk(0); m = s.openMap("data", String.class, String.class); assertEquals("Hallo", m.get("1")); s.close(); } private void testRollbackInMemory() { String fileName = getBaseDir() + "/testRollback.h3"; FileUtils.delete(fileName); MVStore s = openStore(fileName); assertEquals(1, s.getCurrentVersion()); s.setMaxPageSize(5); MVMap<String, String> m = s.openMap("data", String.class, String.class); s.rollbackTo(0); assertTrue(m.isClosed()); assertEquals(1, s.getCurrentVersion()); m = s.openMap("data", String.class, String.class); MVMap<String, String> m0 = s.openMap("data0", String.class, String.class); MVMap<String, String> m2 = s.openMap("data2", String.class, String.class); m.put("1", "Hello"); for (int i = 0; i < 10; i++) { m2.put("" + i, "Test"); } long v1 = s.incrementVersion(); assertEquals(1, v1); assertEquals(2, s.getCurrentVersion()); MVMap<String, String> m1 = s.openMap("data1", String.class, String.class); assertEquals("Test", m2.get("1")); m.put("1", "Hallo"); m0.put("1", "Hallo"); m1.put("1", "Hallo"); m2.clear(); assertEquals("Hallo", m.get("1")); assertEquals("Hallo", m1.get("1")); s.rollbackTo(v1); assertEquals(2, s.getCurrentVersion()); for (int i = 0; i < 10; i++) { assertEquals("Test", m2.get("" + i)); } assertEquals("Hello", m.get("1")); assertNull(m0.get("1")); assertTrue(m1.isClosed()); assertFalse(m0.isReadOnly()); assertTrue(m1.isReadOnly()); s.close(); } private void testMeta() { String fileName = getBaseDir() + "/testMeta.h3"; FileUtils.delete(fileName); MVStore s = openStore(fileName); MVMap<String, String> m = s.getMetaMap(); s.setRetainChunk(0); MVMap<String, String> data = s.openMap("data", String.class, String.class); data.put("1", "Hello"); data.put("2", "World"); s.store(); assertEquals("1/1///", m.get("map.data")); assertTrue(m.containsKey("chunk.1")); assertEquals("Hello", data.put("1", "Hallo")); s.store(); assertEquals("1/1///", m.get("map.data")); assertTrue(m.get("root.1").length() > 0); assertTrue(m.containsKey("chunk.1")); assertTrue(m.containsKey("chunk.2")); s.rollbackTo(1); assertTrue(m.containsKey("chunk.1")); assertFalse(m.containsKey("chunk.2")); s.close(); } private void testInMemory() { for (int j = 0; j < 1; j++) { MVStore s = openStore(null); // s.setMaxPageSize(10); // long t; int len = 100; // TreeMap<Integer, String> m = new TreeMap<Integer, String>(); // HashMap<Integer, String> m = New.hashMap(); MVMap<Integer, String> m = s.openMap("data", Integer.class, String.class); // t = System.currentTimeMillis(); for (int i = 0; i < len; i++) { assertNull(m.put(i, "Hello World")); } // System.out.println("put: " + (System.currentTimeMillis() - t)); // t = System.currentTimeMillis(); for (int i = 0; i < len; i++) { assertEquals("Hello World", m.get(i)); } // System.out.println("get: " + (System.currentTimeMillis() - t)); // t = System.currentTimeMillis(); for (int i = 0; i < len; i++) { assertEquals("Hello World", m.remove(i)); } // System.out.println("remove: " + (System.currentTimeMillis() - t)); // System.out.println(); assertEquals(null, m.get(0)); assertEquals(0, m.size()); s.close(); } } private void testLargeImport() { String fileName = getBaseDir() + "/testImport.h3"; int len = 1000; for (int j = 0; j < 5; j++) { FileUtils.delete(fileName); MVStore s = openStore(fileName); // s.setCompressor(null); s.setMaxPageSize(40); MVMap<Integer, Object[]> m = s.openMap("data", "", "i", "r(i,,)"); // Profiler prof = new Profiler(); // prof.startCollecting(); // long t = System.currentTimeMillis(); for (int i = 0; i < len;) { Object[] o = new Object[3]; o[0] = i; o[1] = "Hello World"; o[2] = "World"; m.put(i, o); i++; if (i % 10000 == 0) { s.store(); } } s.store(); s.close(); // System.out.println(prof.getTop(5)); // System.out.println("store time " + (System.currentTimeMillis() - t)); // System.out.println("store size " + FileUtils.size(fileName)); } } private void testBtreeStore() { String fileName = getBaseDir() + "/testBtreeStore.h3"; FileUtils.delete(fileName); MVStore s = openStore(fileName); s.close(); s = openStore(fileName); MVMap<Integer, String> m = s.openMap("data", Integer.class, String.class); int count = 2000; // Profiler p = new Profiler(); // p.startCollecting(); // long t = System.currentTimeMillis(); for (int i = 0; i < count; i++) { assertNull(m.put(i, "hello " + i)); assertEquals("hello " + i, m.get(i)); } // System.out.println("put: " + (System.currentTimeMillis() - t)); // System.out.println(p.getTop(5)); // p = new Profiler(); //p.startCollecting(); // t = System.currentTimeMillis(); s.store(); // System.out.println("store: " + (System.currentTimeMillis() - t)); // System.out.println(p.getTop(5)); assertEquals("hello 0", m.remove(0)); assertNull(m.get(0)); for (int i = 1; i < count; i++) { assertEquals("hello " + i, m.get(i)); } s.store(); s.close(); s = openStore(fileName); m = s.openMap("data", Integer.class, String.class); assertNull(m.get(0)); for (int i = 1; i < count; i++) { assertEquals("hello " + i, m.get(i)); } for (int i = 1; i < count; i++) { m.remove(i); } s.store(); assertNull(m.get(0)); for (int i = 0; i < count; i++) { assertNull(m.get(i)); } s.close(); } private void testDefragment() { String fileName = getBaseDir() + "/testDefragment.h3"; FileUtils.delete(fileName); long initialLength = 0; for (int j = 0; j < 20; j++) { MVStore s = openStore(fileName); MVMap<Integer, String> m = s.openMap("data", Integer.class, String.class); for (int i = 0; i < 10; i++) { m.put(j + i, "Hello " + j); } s.store(); s.compact(80); s.close(); long len = FileUtils.size(fileName); // System.out.println(" len:" + len); if (initialLength == 0) { initialLength = len; } else { assertTrue("initial: " + initialLength + " len: " + len, len <= initialLength * 3); } } // long len = FileUtils.size(fileName); // System.out.println("len0: " + len); MVStore s = openStore(fileName); MVMap<Integer, String> m = s.openMap("data", Integer.class, String.class); for (int i = 0; i < 100; i++) { m.remove(i); } s.store(); s.compact(80); s.close(); // len = FileUtils.size(fileName); // System.out.println("len1: " + len); s = openStore(fileName); m = s.openMap("data", Integer.class, String.class); s.compact(80); s.close(); // len = FileUtils.size(fileName); // System.out.println("len2: " + len); } private void testReuseSpace() { String fileName = getBaseDir() + "/testReuseSpace.h3"; FileUtils.delete(fileName); long initialLength = 0; for (int j = 0; j < 20; j++) { MVStore s = openStore(fileName); MVMap<Integer, String> m = s.openMap("data", Integer.class, String.class); for (int i = 0; i < 10; i++) { m.put(i, "Hello"); } s.store(); for (int i = 0; i < 10; i++) { assertEquals("Hello", m.get(i)); assertEquals("Hello", m.remove(i)); } s.store(); s.close(); long len = FileUtils.size(fileName); if (initialLength == 0) { initialLength = len; } else { assertTrue(len <= initialLength * 2); } } } private void testRandom() { String fileName = getBaseDir() + "/testRandom.h3"; FileUtils.delete(fileName); MVStore s = openStore(fileName); MVMap<Integer, Integer> m = s.openMap("data", Integer.class, Integer.class); TreeMap<Integer, Integer> map = new TreeMap<Integer, Integer>(); Random r = new Random(1); int operationCount = 1000; int maxValue = 30; Integer expected, got; for (int i = 0; i < operationCount; i++) { int k = r.nextInt(maxValue); int v = r.nextInt(); boolean compareAll; switch (r.nextInt(3)) { case 0: log(i + ": put " + k + " = " + v); expected = map.put(k, v); got = m.put(k, v); if (expected == null) { assertNull(got); } else { assertEquals(expected, got); } compareAll = true; break; case 1: log(i + ": remove " + k); expected = map.remove(k); got = m.remove(k); if (expected == null) { assertNull(got); } else { assertEquals(expected, got); } compareAll = true; break; default: Integer a = map.get(k); Integer b = m.get(k); if (a == null || b == null) { assertTrue(a == b); } else { assertEquals(a.intValue(), b.intValue()); } compareAll = false; break; } if (compareAll) { Iterator<Integer> it = m.keyIterator(null); Iterator<Integer> itExpected = map.keySet().iterator(); while (itExpected.hasNext()) { assertTrue(it.hasNext()); expected = itExpected.next(); got = it.next(); assertEquals(expected, got); } assertFalse(it.hasNext()); } } s.close(); } private void testKeyValueClasses() { MVStore s; s = MVStore.open(null); s.openMap("test", String.class, String.class); try { s.openMap("unsupportedKey", ArrayList.class, String.class); fail(); } catch (RuntimeException e) { // expected } try { s.openMap("unsupportedValue", String.class, ArrayList.class); fail(); } catch (RuntimeException e) { // expected } s.close(); String fileName = getBaseDir() + "/testKeyValueClasses.h3"; FileUtils.delete(fileName); s = openStore(fileName); MVMap<Integer, String> is = s.openMap("intString", Integer.class, String.class); is.put(1, "Hello"); MVMap<Integer, Integer> ii = s.openMap("intInt", Integer.class, Integer.class); ii.put(1, 10); MVMap<String, Integer> si = s.openMap("stringInt", String.class, Integer.class); si.put("Test", 10); MVMap<String, String> ss = s.openMap("stringString", String.class, String.class); ss.put("Hello", "World"); s.store(); s.close(); s = openStore(fileName); is = s.openMap("intString", Integer.class, String.class); assertEquals("Hello", is.get(1)); ii = s.openMap("intInt", Integer.class, Integer.class); assertEquals(10, ii.get(1).intValue()); si = s.openMap("stringInt", String.class, Integer.class); assertEquals(10, si.get("Test").intValue()); ss = s.openMap("stringString", String.class, String.class); assertEquals("World", ss.get("Hello")); s.close(); } private void testIterate() { String fileName = getBaseDir() + "/testIterate.h3"; FileUtils.delete(fileName); MVStore s = openStore(fileName); MVMap<Integer, String> m = s.openMap("data", Integer.class, String.class); Iterator<Integer> it = m.keyIterator(null); assertFalse(it.hasNext()); for (int i = 0; i < 10; i++) { m.put(i, "hello " + i); } s.store(); it = m.keyIterator(null); it.next(); assertThrows(UnsupportedOperationException.class, it).remove(); it = m.keyIterator(null); for (int i = 0; i < 10; i++) { assertTrue(it.hasNext()); assertEquals(i, it.next().intValue()); } assertFalse(it.hasNext()); assertNull(it.next()); for (int j = 0; j < 10; j++) { it = m.keyIterator(j); for (int i = j; i < 10; i++) { assertTrue(it.hasNext()); assertEquals(i, it.next().intValue()); } assertFalse(it.hasNext()); } s.close(); } private void testCloseTwice() { String fileName = getBaseDir() + "/testCloseTwice.h3"; FileUtils.delete(fileName); MVStore s = openStore(fileName); MVMap<Integer, String> m = s.openMap("data", Integer.class, String.class); for (int i = 0; i < 3; i++) { m.put(i, "hello " + i); } s.store(); // closing twice should be fine s.close(); s.close(); } private void testSimple() { String fileName = getBaseDir() + "/testSimple.h3"; FileUtils.delete(fileName); MVStore s = openStore(fileName); MVMap<Integer, String> m = s.openMap("data", Integer.class, String.class); for (int i = 0; i < 3; i++) { m.put(i, "hello " + i); } s.store(); assertEquals("hello 0", m.remove(0)); assertNull(m.get(0)); for (int i = 1; i < 3; i++) { assertEquals("hello " + i, m.get(i)); } s.store(); s.close(); s = openStore(fileName); m = s.openMap("data", Integer.class, String.class); assertNull(m.get(0)); for (int i = 1; i < 3; i++) { assertEquals("hello " + i, m.get(i)); } s.close(); } /** * Open a store for the given file name, using a small page size. * * @param fileName the file name (null for in-memory) * @return the store */ protected static MVStore openStore(String fileName) { MVStore store = MVStore.open(fileName, new TestMapFactory()); store.setMaxPageSize(10); return store; } /** * Log the message. * * @param msg the message */ protected static void log(String msg) { // System.out.println(msg); } }