EMMA Coverage Report (generated Sun Mar 01 22:06:14 CET 2015)
[all classes][org.h2.mvstore.db]

COVERAGE SUMMARY FOR SOURCE FILE [MVTableEngine.java]

nameclass, %method, %block, %line, %
MVTableEngine.java100% (4/4)100% (28/28)94%  (674/714)95%  (163.1/172)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class MVTableEngine$MVInDoubtTransaction100% (1/1)100% (4/4)72%  (39/54)82%  (14/17)
getState (): String 100% (1/1)25%  (5/20)40%  (2/5)
MVTableEngine$MVInDoubtTransaction (MVStore, TransactionStore$Transaction): void 100% (1/1)100% (12/12)100% (5/5)
getTransactionName (): String 100% (1/1)100% (4/4)100% (1/1)
setState (int): void 100% (1/1)100% (18/18)100% (6/6)
     
class MVTableEngine$Store100% (1/1)100% (19/19)96%  (406/423)96%  (100.2/104)
statisticsStart (): void 100% (1/1)85%  (11/13)95%  (2.8/3)
removeTemporaryMaps (BitField): void 100% (1/1)87%  (69/79)83%  (12.4/15)
initTransactions (): void 100% (1/1)90%  (26/29)88%  (7/8)
statisticsEnd (): Map 100% (1/1)92%  (23/25)98%  (4.9/5)
MVTableEngine$Store (Database, MVStore$Builder): void 100% (1/1)100% (36/36)100% (8/8)
close (long): void 100% (1/1)100% (67/67)100% (18/18)
closeImmediately (): void 100% (1/1)100% (9/9)100% (4/4)
compactFile (long): void 100% (1/1)100% (32/32)100% (10/10)
flush (): void 100% (1/1)100% (21/21)100% (6/6)
getInDoubtTransactions (): ArrayList 100% (1/1)100% (32/32)100% (7/7)
getInputStream (): InputStream 100% (1/1)100% (18/18)100% (4/4)
getStore (): MVStore 100% (1/1)100% (3/3)100% (1/1)
getTables (): HashMap 100% (1/1)100% (6/6)100% (1/1)
getTransactionStore (): TransactionStore 100% (1/1)100% (3/3)100% (1/1)
nextTemporaryMapName (): String 100% (1/1)100% (15/15)100% (1/1)
prepareCommit (Session, String): void 100% (1/1)100% (13/13)100% (5/5)
removeTable (MVTable): void 100% (1/1)100% (7/7)100% (2/2)
setCacheSize (int): void 100% (1/1)100% (9/9)100% (2/2)
sync (): void 100% (1/1)100% (6/6)100% (3/3)
     
class MVTableEngine100% (1/1)100% (3/3)96%  (217/225)96%  (46.9/49)
init (Database): MVTableEngine$Store 100% (1/1)96%  (188/196)95%  (38.9/41)
MVTableEngine (): void 100% (1/1)100% (3/3)100% (2/2)
createTable (CreateTableData): TableBase 100% (1/1)100% (26/26)100% (6/6)
     
class MVTableEngine$1100% (1/1)100% (2/2)100% (12/12)100% (3/3)
MVTableEngine$1 (Database): void 100% (1/1)100% (6/6)100% (1/1)
uncaughtException (Thread, Throwable): void 100% (1/1)100% (6/6)100% (2/2)

1/*
2 * Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
3 * and the EPL 1.0 (http://h2database.com/html/license.html).
4 * Initial Developer: H2 Group
5 */
6package org.h2.mvstore.db;
7 
8import java.io.InputStream;
9import java.lang.Thread.UncaughtExceptionHandler;
10import java.nio.channels.FileChannel;
11import java.util.ArrayList;
12import java.util.HashMap;
13import java.util.List;
14import java.util.Map;
15import java.util.concurrent.ConcurrentHashMap;
16 
17import org.h2.api.ErrorCode;
18import org.h2.api.TableEngine;
19import org.h2.command.ddl.CreateTableData;
20import org.h2.engine.Constants;
21import org.h2.engine.Database;
22import org.h2.engine.Session;
23import org.h2.message.DbException;
24import org.h2.mvstore.DataUtils;
25import org.h2.mvstore.FileStore;
26import org.h2.mvstore.MVMap;
27import org.h2.mvstore.MVStore;
28import org.h2.mvstore.MVStoreTool;
29import org.h2.mvstore.db.TransactionStore.Transaction;
30import org.h2.mvstore.db.TransactionStore.TransactionMap;
31import org.h2.store.InDoubtTransaction;
32import org.h2.store.fs.FileChannelInputStream;
33import org.h2.store.fs.FileUtils;
34import org.h2.table.TableBase;
35import org.h2.util.BitField;
36import org.h2.util.New;
37 
38/**
39 * A table engine that internally uses the MVStore.
40 */
41public class MVTableEngine implements TableEngine {
42 
43    /**
44     * Initialize the MVStore.
45     *
46     * @param db the database
47     * @return the store
48     */
49    public static Store init(final Database db) {
50        Store store = db.getMvStore();
51        if (store != null) {
52            return store;
53        }
54        byte[] key = db.getFileEncryptionKey();
55        String dbPath = db.getDatabasePath();
56        MVStore.Builder builder = new MVStore.Builder();
57        if (dbPath == null) {
58            store = new Store(db, builder);
59        } else {
60            String fileName = dbPath + Constants.SUFFIX_MV_FILE;
61            MVStoreTool.compactCleanUp(fileName);
62            builder.fileName(fileName);
63            builder.pageSplitSize(db.getPageSize());
64            if (db.isReadOnly()) {
65                builder.readOnly();
66            } else {
67                // possibly create the directory
68                boolean exists = FileUtils.exists(fileName);
69                if (exists && !FileUtils.canWrite(fileName)) {
70                    // read only
71                } else {
72                    String dir = FileUtils.getParent(fileName);
73                    FileUtils.createDirectories(dir);
74                }
75            }
76            if (key != null) {
77                char[] password = new char[key.length / 2];
78                for (int i = 0; i < password.length; i++) {
79                    password[i] = (char) (((key[i + i] & 255) << 16) |
80                            ((key[i + i + 1]) & 255));
81                }
82                builder.encryptionKey(password);
83            }
84            if (db.getSettings().compressData) {
85                builder.compress();
86                // use a larger page split size to improve the compression ratio
87                builder.pageSplitSize(64 * 1024);
88            }
89            builder.backgroundExceptionHandler(new UncaughtExceptionHandler() {
90 
91                @Override
92                public void uncaughtException(Thread t, Throwable e) {
93                    db.setBackgroundException(DbException.convert(e));
94                }
95 
96            });
97            try {
98                store = new Store(db, builder);
99            } catch (IllegalStateException e) {
100                int errorCode = DataUtils.getErrorCode(e.getMessage());
101                if (errorCode == DataUtils.ERROR_FILE_CORRUPT) {
102                    if (key != null) {
103                        throw DbException.get(
104                                ErrorCode.FILE_ENCRYPTION_ERROR_1,
105                                e, fileName);
106                    }
107                } else if (errorCode == DataUtils.ERROR_FILE_LOCKED) {
108                    throw DbException.get(
109                            ErrorCode.DATABASE_ALREADY_OPEN_1,
110                            e, fileName);
111                } else if (errorCode == DataUtils.ERROR_READING_FAILED) {
112                    throw DbException.get(
113                            ErrorCode.IO_EXCEPTION_1,
114                            e, fileName);
115                }
116                throw DbException.get(
117                        ErrorCode.FILE_CORRUPTED_1,
118                        e, fileName);
119            }
120        }
121        db.setMvStore(store);
122        return store;
123    }
124 
125    @Override
126    public TableBase createTable(CreateTableData data) {
127        Database db = data.session.getDatabase();
128        Store store = init(db);
129        MVTable table = new MVTable(data, store);
130        table.init(data.session);
131        store.tableMap.put(table.getMapName(), table);
132        return table;
133    }
134 
135    /**
136     * A store with open tables.
137     */
138    public static class Store {
139 
140        /**
141         * The map of open tables.
142         * Key: the map name, value: the table.
143         */
144        final ConcurrentHashMap<String, MVTable> tableMap =
145                new ConcurrentHashMap<String, MVTable>();
146 
147        /**
148         * The store.
149         */
150        private final MVStore store;
151 
152        /**
153         * The transaction store.
154         */
155        private final TransactionStore transactionStore;
156 
157        private long statisticsStart;
158 
159        private int temporaryMapId;
160 
161        public Store(Database db, MVStore.Builder builder) {
162            this.store = builder.open();
163            if (!db.getSettings().reuseSpace) {
164                store.setReuseSpace(false);
165            }
166            this.transactionStore = new TransactionStore(
167                    store,
168                    new ValueDataType(null, db, null));
169            transactionStore.init();
170        }
171 
172        public MVStore getStore() {
173            return store;
174        }
175 
176        public TransactionStore getTransactionStore() {
177            return transactionStore;
178        }
179 
180        public HashMap<String, MVTable> getTables() {
181            return new HashMap<String, MVTable>(tableMap);
182        }
183 
184        /**
185         * Remove a table.
186         *
187         * @param table the table
188         */
189        public void removeTable(MVTable table) {
190            tableMap.remove(table.getMapName());
191        }
192 
193        /**
194         * Store all pending changes.
195         */
196        public void flush() {
197            FileStore s = store.getFileStore();
198            if (s == null || s.isReadOnly()) {
199                return;
200            }
201            if (!store.compact(50, 4 * 1024 * 1024)) {
202                store.commit();
203            }
204        }
205 
206        /**
207         * Close the store, without persisting changes.
208         */
209        public void closeImmediately() {
210            if (store.isClosed()) {
211                return;
212            }
213            store.closeImmediately();
214        }
215 
216        /**
217         * Commit all transactions that are in the committing state, and
218         * rollback all open transactions.
219         */
220        public void initTransactions() {
221            List<Transaction> list = transactionStore.getOpenTransactions();
222            for (Transaction t : list) {
223                if (t.getStatus() == Transaction.STATUS_COMMITTING) {
224                    t.commit();
225                } else if (t.getStatus() != Transaction.STATUS_PREPARED) {
226                    t.rollback();
227                }
228            }
229        }
230 
231        /**
232         * Remove all temporary maps.
233         *
234         * @param objectIds the ids of the objects to keep
235         */
236        public void removeTemporaryMaps(BitField objectIds) {
237            for (String mapName : store.getMapNames()) {
238                if (mapName.startsWith("temp.")) {
239                    MVMap<?, ?> map = store.openMap(mapName);
240                    store.removeMap(map);
241                } else if (mapName.startsWith("table.") || mapName.startsWith("index.")) {
242                    int id = Integer.parseInt(mapName.substring(1 + mapName.indexOf(".")));
243                    if (!objectIds.get(id)) {
244                        ValueDataType keyType = new ValueDataType(null, null, null);
245                        ValueDataType valueType = new ValueDataType(null, null, null);
246                        Transaction t = transactionStore.begin();
247                        TransactionMap<?, ?> m = t.openMap(mapName, keyType, valueType);
248                        transactionStore.removeMap(m);
249                        t.commit();
250                    }
251                }
252            }
253        }
254 
255        /**
256         * Get the name of the next available temporary map.
257         *
258         * @return the map name
259         */
260        public synchronized String nextTemporaryMapName() {
261            return "temp." + temporaryMapId++;
262        }
263 
264        /**
265         * Prepare a transaction.
266         *
267         * @param session the session
268         * @param transactionName the transaction name (may be null)
269         */
270        public void prepareCommit(Session session, String transactionName) {
271            Transaction t = session.getTransaction();
272            t.setName(transactionName);
273            t.prepare();
274            store.commit();
275        }
276 
277        public ArrayList<InDoubtTransaction> getInDoubtTransactions() {
278            List<Transaction> list = transactionStore.getOpenTransactions();
279            ArrayList<InDoubtTransaction> result = New.arrayList();
280            for (Transaction t : list) {
281                if (t.getStatus() == Transaction.STATUS_PREPARED) {
282                    result.add(new MVInDoubtTransaction(store, t));
283                }
284            }
285            return result;
286        }
287 
288        public void setCacheSize(int kb) {
289            store.setCacheSize(Math.max(1, kb / 1024));
290        }
291 
292        public InputStream getInputStream() {
293            FileChannel fc = store.getFileStore().getEncryptedFile();
294            if (fc == null) {
295                fc = store.getFileStore().getFile();
296            }
297            return new FileChannelInputStream(fc, false);
298        }
299 
300        /**
301         * Force the changes to disk.
302         */
303        public void sync() {
304            flush();
305            store.sync();
306        }
307 
308        /**
309         * Compact the database file, that is, compact blocks that have a low
310         * fill rate, and move chunks next to each other. This will typically
311         * shrink the database file. Changes are flushed to the file, and old
312         * chunks are overwritten.
313         *
314         * @param maxCompactTime the maximum time in milliseconds to compact
315         */
316        public void compactFile(long maxCompactTime) {
317            store.setRetentionTime(0);
318            long start = System.currentTimeMillis();
319            while (store.compact(95, 16 * 1024 * 1024)) {
320                store.sync();
321                store.compactMoveChunks(95, 16 * 1024 * 1024);
322                long time = System.currentTimeMillis() - start;
323                if (time > maxCompactTime) {
324                    break;
325                }
326            }
327        }
328 
329        /**
330         * Close the store. Pending changes are persisted. Chunks with a low
331         * fill rate are compacted, but old chunks are kept for some time, so
332         * most likely the database file will not shrink.
333         *
334         * @param maxCompactTime the maximum time in milliseconds to compact
335         */
336        public void close(long maxCompactTime) {
337            try {
338                if (!store.isClosed() && store.getFileStore() != null) {
339                    boolean compactFully = false;
340                    if (!store.getFileStore().isReadOnly()) {
341                        transactionStore.close();
342                        if (maxCompactTime == Long.MAX_VALUE) {
343                            compactFully = true;
344                        }
345                    }
346                    String fileName = store.getFileStore().getFileName();
347                    store.close();
348                    if (compactFully && FileUtils.exists(fileName)) {
349                        // the file could have been deleted concurrently,
350                        // so only compact if the file still exists
351                        MVStoreTool.compact(fileName, true);
352                    }
353                }
354            } catch (IllegalStateException e) {
355                int errorCode = DataUtils.getErrorCode(e.getMessage());
356                if (errorCode == DataUtils.ERROR_WRITING_FAILED) {
357                    // disk full - ok
358                } else if (errorCode == DataUtils.ERROR_FILE_CORRUPT) {
359                    // wrong encryption key - ok
360                }
361                store.closeImmediately();
362                throw DbException.get(ErrorCode.IO_EXCEPTION_1, e, "Closing");
363            }
364        }
365 
366        /**
367         * Start collecting statistics.
368         */
369        public void statisticsStart() {
370            FileStore fs = store.getFileStore();
371            statisticsStart = fs == null ? 0 : fs.getReadCount();
372        }
373 
374        /**
375         * Stop collecting statistics.
376         *
377         * @return the statistics
378         */
379        public Map<String, Integer> statisticsEnd() {
380            HashMap<String, Integer> map = New.hashMap();
381            FileStore fs = store.getFileStore();
382            int reads = fs == null ? 0 : (int) (fs.getReadCount() - statisticsStart);
383            map.put("reads", reads);
384            return map;
385        }
386 
387    }
388 
389    /**
390     * An in-doubt transaction.
391     */
392    private static class MVInDoubtTransaction implements InDoubtTransaction {
393 
394        private final MVStore store;
395        private final Transaction transaction;
396        private int state = InDoubtTransaction.IN_DOUBT;
397 
398        MVInDoubtTransaction(MVStore store, Transaction transaction) {
399            this.store = store;
400            this.transaction = transaction;
401        }
402 
403        @Override
404        public void setState(int state) {
405            if (state == InDoubtTransaction.COMMIT) {
406                transaction.commit();
407            } else {
408                transaction.rollback();
409            }
410            store.commit();
411            this.state = state;
412        }
413 
414        @Override
415        public String getState() {
416            switch(state) {
417            case IN_DOUBT:
418                return "IN_DOUBT";
419            case COMMIT:
420                return "COMMIT";
421            case ROLLBACK:
422                return "ROLLBACK";
423            default:
424                throw DbException.throwInternalError("state="+state);
425            }
426        }
427 
428        @Override
429        public String getTransactionName() {
430            return transaction.getName();
431        }
432 
433    }
434 
435}

[all classes][org.h2.mvstore.db]
EMMA 2.0.5312 (C) Vladimir Roubtsov