/* * 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.unit; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.Random; import org.h2.message.Trace; import org.h2.test.TestBase; import org.h2.util.Cache; import org.h2.util.CacheLRU; import org.h2.util.CacheObject; import org.h2.util.CacheWriter; import org.h2.util.StringUtils; import org.h2.util.Utils; import org.h2.value.Value; /** * Tests the cache. */ public class TestCache extends TestBase implements CacheWriter { private String out; /** * Run just this test. * * @param a ignored */ public static void main(String... a) throws Exception { TestBase test = TestBase.createCaller().init(); // test.config.traceTest = true; test.test(); } public void test() throws Exception { testTQ(); testMemoryUsage(); testCache(); testCacheDb(false); testCacheDb(true); } private void testTQ() throws Exception { if (config.memory || config.reopen) { return; } deleteDb("cache"); Connection conn = getConnection("cache;LOG=0;UNDO_LOG=0"); Statement stat = conn.createStatement(); stat.execute("create table if not exists lob(id int primary key, data blob)"); PreparedStatement prep = conn.prepareStatement("insert into lob values(?, ?)"); Random r = new Random(1); byte[] buff = new byte[2 * 1024 * 1024]; for (int i = 0; i < 10; i++) { prep.setInt(1, i); r.nextBytes(buff); prep.setBinaryStream(2, new ByteArrayInputStream(buff), -1); prep.execute(); } stat.execute("create table if not exists test(id int primary key, data varchar)"); prep = conn.prepareStatement("insert into test values(?, ?)"); for (int i = 0; i < 20000; i++) { prep.setInt(1, i); prep.setString(2, "Hello"); prep.execute(); } conn.close(); testTQ("LRU", false); testTQ("TQ", true); } private void testTQ(String cacheType, boolean scanResistant) throws Exception { Connection conn = getConnection("cache;CACHE_TYPE=" + cacheType + ";CACHE_SIZE=4096"); Statement stat = conn.createStatement(); PreparedStatement prep; for (int k = 0; k < 10; k++) { int rc; prep = conn.prepareStatement("select * from test where id = ?"); rc = getReadCount(stat); for (int x = 0; x < 2; x++) { for (int i = 0; i < 15000; i++) { prep.setInt(1, i); prep.executeQuery(); } } int rcData = getReadCount(stat) - rc; if (scanResistant && k > 0) { // TQ is expected to keep the data rows in the cache // even if the LOB is read once in a while assertEquals(0, rcData); } else { assertTrue(rcData > 0); } rc = getReadCount(stat); ResultSet rs = stat.executeQuery("select * from lob where id = " + k); rs.next(); InputStream in = rs.getBinaryStream(2); while (in.read() >= 0) { // ignore } in.close(); int rcLob = getReadCount(stat) - rc; assertTrue(rcLob > 0); } conn.close(); } private static int getReadCount(Statement stat) throws Exception { ResultSet rs; rs = stat.executeQuery("select value from information_schema.settings where name = 'info.FILE_READ'"); rs.next(); return rs.getInt(1); } private void testMemoryUsage() throws SQLException { if (!config.traceTest) { return; } if (config.memory) { return; } deleteDb("cache"); Connection conn; Statement stat; ResultSet rs; conn = getConnection("cache;CACHE_SIZE=16384"); stat = conn.createStatement(); // test DataOverflow stat.execute("create table test(id int primary key, data varchar)"); stat.execute("set max_memory_undo 10000"); conn.close(); stat = null; conn = null; long before = getRealMemory(); conn = getConnection("cache;CACHE_SIZE=16384;DB_CLOSE_ON_EXIT=FALSE"); stat = conn.createStatement(); // -XX:+HeapDumpOnOutOfMemoryError stat.execute("insert into test select x, random_uuid() || space(1) from system_range(1, 10000)"); // stat.execute("create index idx_test_n on test(data)"); // stat.execute("select data from test where data >= ''"); rs = stat.executeQuery("select value from information_schema.settings where name = 'info.CACHE_SIZE'"); rs.next(); int calculated = rs.getInt(1); rs = null; long afterInsert = getRealMemory(); conn.close(); stat = null; conn = null; long afterClose = getRealMemory(); trace("Used memory: " + (afterInsert - afterClose) + " calculated cache size: " + calculated); trace("Before: " + before + " after: " + afterInsert + " after closing: " + afterClose); } private int getRealMemory() { StringUtils.clearCache(); Value.clearCache(); eatMemory(100); freeMemory(); System.gc(); return Utils.getMemoryUsed(); } private void testCache() { out = ""; Cache c = CacheLRU.getCache(this, "LRU", 16); for (int i = 0; i < 20; i++) { c.put(new Obj(i)); } assertEquals("flush 0 flush 1 flush 2 flush 3 ", out); } /** * A simple cache object */ static class Obj extends CacheObject { Obj(int pos) { setPos(pos); } public int getMemory() { return 1024; } public boolean canRemove() { return true; } public boolean isChanged() { return true; } public String toString() { return "[" + getPos() + "]"; } } public void flushLog() { out += "flush "; } public Trace getTrace() { return null; } public void writeBack(CacheObject entry) { out += entry.getPos() + " "; } private void testCacheDb(boolean lru) throws SQLException { if (config.memory) { return; } deleteDb("cache"); Connection conn = getConnection("cache;CACHE_TYPE=" + (lru ? "LRU" : "SOFT_LRU")); Statement stat = conn.createStatement(); stat.execute("SET CACHE_SIZE 1024"); stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)"); stat.execute("CREATE TABLE MAIN(ID INT PRIMARY KEY, NAME VARCHAR)"); PreparedStatement prep = conn.prepareStatement("INSERT INTO TEST VALUES(?, ?)"); PreparedStatement prep2 = conn.prepareStatement("INSERT INTO MAIN VALUES(?, ?)"); int max = 10000; for (int i = 0; i < max; i++) { prep.setInt(1, i); prep.setString(2, "Hello " + i); prep.execute(); prep2.setInt(1, i); prep2.setString(2, "World " + i); prep2.execute(); } conn.close(); conn = getConnection("cache"); stat = conn.createStatement(); stat.execute("SET CACHE_SIZE 1024"); Random random = new Random(1); for (int i = 0; i < 100; i++) { stat.executeQuery("SELECT * FROM MAIN WHERE ID BETWEEN 40 AND 50"); stat.executeQuery("SELECT * FROM MAIN WHERE ID = " + random.nextInt(max)); if ((i % 10) == 0) { stat.executeQuery("SELECT * FROM TEST"); } } conn.close(); deleteDb("cache"); } }