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

COVERAGE SUMMARY FOR SOURCE FILE [LobStorageMap.java]

nameclass, %method, %block, %line, %
LobStorageMap.java100% (1/1)93%  (13/14)93%  (681/732)94%  (159/169)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class LobStorageMap100% (1/1)93%  (13/14)93%  (681/732)94%  (159/169)
trace (String): void 0%   (0/1)0%   (0/14)0%   (0/2)
getInputStream (ValueLobDb, byte [], long): InputStream 100% (1/1)68%  (23/34)83%  (5/6)
generateLobId (): long 100% (1/1)88%  (35/40)83%  (5/6)
init (): void 100% (1/1)90%  (99/110)90%  (28/31)
createClob (Reader, long): Value 100% (1/1)93%  (98/105)92%  (23/25)
copyLob (ValueLobDb, int, long): ValueLobDb 100% (1/1)96%  (77/80)94%  (15/16)
LobStorageMap (Database): void 100% (1/1)100% (11/11)100% (4/4)
createBlob (InputStream, long): Value 100% (1/1)100% (76/76)100% (21/21)
createLob (InputStream, int): ValueLobDb 100% (1/1)100% (78/78)100% (13/13)
isReadOnly (): boolean 100% (1/1)100% (4/4)100% (1/1)
removeAllForTable (int): void 100% (1/1)100% (68/68)100% (17/17)
removeLob (ValueLobDb): void 100% (1/1)100% (13/13)100% (5/5)
removeLob (int, long): void 100% (1/1)100% (74/74)100% (16/16)
setTable (ValueLobDb, int): void 100% (1/1)100% (25/25)100% (6/6)

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.store;
7 
8import java.io.BufferedInputStream;
9import java.io.BufferedReader;
10import java.io.IOException;
11import java.io.InputStream;
12import java.io.Reader;
13import java.util.ArrayList;
14import java.util.Arrays;
15import java.util.Map.Entry;
16 
17import org.h2.api.ErrorCode;
18import org.h2.engine.Constants;
19import org.h2.engine.Database;
20import org.h2.message.DbException;
21import org.h2.mvstore.MVMap;
22import org.h2.mvstore.MVStore;
23import org.h2.mvstore.StreamStore;
24import org.h2.mvstore.db.MVTableEngine.Store;
25import org.h2.util.IOUtils;
26import org.h2.util.New;
27import org.h2.value.Value;
28import org.h2.value.ValueLobDb;
29 
30/**
31 * This class stores LOB objects in the database, in maps. This is the back-end
32 * i.e. the server side of the LOB storage.
33 */
34public class LobStorageMap implements LobStorageInterface {
35 
36    private static final boolean TRACE = false;
37 
38    private final Database database;
39 
40    private boolean init;
41 
42    private Object nextLobIdSync = new Object();
43    private long nextLobId;
44 
45    /**
46     * The lob metadata map. It contains the mapping from the lob id
47     * (which is a long) to the stream store id (which is a byte array).
48     *
49     * Key: lobId (long)
50     * Value: { streamStoreId (byte[]), tableId (int),
51     * byteCount (long), hash (long) }.
52     */
53    private MVMap<Long, Object[]> lobMap;
54 
55    /**
56     * The reference map. It is used to remove data from the stream store: if no
57     * more entries for the given streamStoreId exist, the data is removed from
58     * the stream store.
59     *
60     * Key: { streamStoreId (byte[]), lobId (long) }.
61     * Value: true (boolean).
62     */
63    private MVMap<Object[], Boolean> refMap;
64 
65    /**
66     * The stream store data map.
67     *
68     * Key: stream store block id (long).
69     * Value: data (byte[]).
70     */
71    private MVMap<Long, byte[]> dataMap;
72 
73    private StreamStore streamStore;
74 
75    public LobStorageMap(Database database) {
76        this.database = database;
77    }
78 
79    @Override
80    public void init() {
81        if (init) {
82            return;
83        }
84        init = true;
85        Store s = database.getMvStore();
86        MVStore mvStore;
87        if (s == null) {
88            // in-memory database
89            mvStore = MVStore.open(null);
90        } else {
91            mvStore = s.getStore();
92        }
93        lobMap = mvStore.openMap("lobMap");
94        refMap = mvStore.openMap("lobRef");
95        dataMap = mvStore.openMap("lobData");
96        streamStore = new StreamStore(dataMap);
97        // garbage collection of the last blocks
98        if (database.isReadOnly()) {
99            return;
100        }
101        if (dataMap.isEmpty()) {
102            return;
103        }
104        // search the last referenced block
105        // (a lob may not have any referenced blocks if data is kept inline,
106        // so we need to loop)
107        long lastUsedKey = -1;
108        Long lobId = lobMap.lastKey();
109        while (lobId != null) {
110            Object[] v = lobMap.get(lobId);
111            byte[] id = (byte[]) v[0];
112            lastUsedKey = streamStore.getMaxBlockKey(id);
113            if (lastUsedKey >= 0) {
114                break;
115            }
116            lobId = lobMap.floorKey(lobId);
117        }
118        // delete all blocks that are newer
119        while (true) {
120            Long last = dataMap.lastKey();
121            if (last == null || last <= lastUsedKey) {
122                break;
123            }
124            dataMap.remove(last);
125        }
126    }
127 
128    @Override
129    public Value createBlob(InputStream in, long maxLength) {
130        init();
131        int type = Value.BLOB;
132        if (maxLength < 0) {
133            maxLength = Long.MAX_VALUE;
134        }
135        int max = (int) Math.min(maxLength, database.getMaxLengthInplaceLob());
136        try {
137            if (max != 0 && max < Integer.MAX_VALUE) {
138                BufferedInputStream b = new BufferedInputStream(in, max);
139                b.mark(max);
140                byte[] small = new byte[max];
141                int len = IOUtils.readFully(b, small, max);
142                if (len < max) {
143                    if (len < small.length) {
144                        small = Arrays.copyOf(small, len);
145                    }
146                    return ValueLobDb.createSmallLob(type, small);
147                }
148                b.reset();
149                in = b;
150            }
151            return createLob(in, type);
152        } catch (IllegalStateException e) {
153            throw DbException.get(ErrorCode.OBJECT_CLOSED, e);
154        } catch (IOException e) {
155            throw DbException.convertIOException(e, null);
156        }
157    }
158 
159    @Override
160    public Value createClob(Reader reader, long maxLength) {
161        init();
162        int type = Value.CLOB;
163        if (maxLength < 0) {
164            maxLength = Long.MAX_VALUE;
165        }
166        int max = (int) Math.min(maxLength, database.getMaxLengthInplaceLob());
167        try {
168            if (max != 0 && max < Integer.MAX_VALUE) {
169                BufferedReader b = new BufferedReader(reader, max);
170                b.mark(max);
171                char[] small = new char[max];
172                int len = IOUtils.readFully(b, small, max);
173                if (len < max) {
174                    if (len < small.length) {
175                        small = Arrays.copyOf(small, len);
176                    }
177                    byte[] utf8 = new String(small, 0, len).getBytes(Constants.UTF8);
178                    return ValueLobDb.createSmallLob(type, utf8);
179                }
180                b.reset();
181                reader = b;
182            }
183            CountingReaderInputStream in =
184                    new CountingReaderInputStream(reader, maxLength);
185            ValueLobDb lob = createLob(in, type);
186            // the length is not correct
187            lob = ValueLobDb.create(type, database,
188                    lob.getTableId(), lob.getLobId(), null, in.getLength());
189            return lob;
190        } catch (IllegalStateException e) {
191            throw DbException.get(ErrorCode.OBJECT_CLOSED, e);
192        } catch (IOException e) {
193            throw DbException.convertIOException(e, null);
194        }
195    }
196 
197    private ValueLobDb createLob(InputStream in, int type) throws IOException {
198        byte[] streamStoreId;
199        try {
200            streamStoreId = streamStore.put(in);
201        } catch (Exception e) {
202            throw DbException.convertToIOException(e);
203        }
204        long lobId = generateLobId();
205        long length = streamStore.length(streamStoreId);
206        int tableId = LobStorageFrontend.TABLE_TEMP;
207        Object[] value = new Object[] { streamStoreId, tableId, length, 0 };
208        lobMap.put(lobId, value);
209        Object[] key = new Object[] { streamStoreId, lobId };
210        refMap.put(key, Boolean.TRUE);
211        ValueLobDb lob = ValueLobDb.create(
212                type, database, tableId, lobId, null, length);
213        if (TRACE) {
214            trace("create " + tableId + "/" + lobId);
215        }
216        return lob;
217    }
218 
219    private long generateLobId() {
220        synchronized (nextLobIdSync) {
221            if (nextLobId == 0) {
222                Long id = lobMap.lastKey();
223                nextLobId = id == null ? 1 : id + 1;
224            }
225            return nextLobId++;
226        }
227    }
228 
229    @Override
230    public boolean isReadOnly() {
231        return database.isReadOnly();
232    }
233 
234    @Override
235    public ValueLobDb copyLob(ValueLobDb old, int tableId, long length) {
236        init();
237        int type = old.getType();
238        long oldLobId = old.getLobId();
239        long oldLength = old.getPrecision();
240        if (oldLength != length) {
241            throw DbException.throwInternalError("Length is different");
242        }
243        Object[] value = lobMap.get(oldLobId);
244        value = Arrays.copyOf(value, value.length);
245        byte[] streamStoreId = (byte[]) value[0];
246        long lobId = generateLobId();
247        value[1] = tableId;
248        lobMap.put(lobId, value);
249        Object[] key = new Object[] { streamStoreId, lobId };
250        refMap.put(key, Boolean.TRUE);
251        ValueLobDb lob = ValueLobDb.create(
252                type, database, tableId, lobId, null, length);
253        if (TRACE) {
254            trace("copy " + old.getTableId() + "/" + old.getLobId() +
255                    " > " + tableId + "/" + lobId);
256        }
257        return lob;
258    }
259 
260    @Override
261    public InputStream getInputStream(ValueLobDb lob, byte[] hmac, long byteCount)
262            throws IOException {
263        init();
264        Object[] value = lobMap.get(lob.getLobId());
265        if (value == null) {
266            throw DbException.throwInternalError("Lob not found: " + lob.getLobId());
267        }
268        byte[] streamStoreId = (byte[]) value[0];
269        return streamStore.get(streamStoreId);
270    }
271 
272    @Override
273    public void setTable(ValueLobDb lob, int tableId) {
274        init();
275        long lobId = lob.getLobId();
276        Object[] value = lobMap.remove(lobId);
277        if (TRACE) {
278            trace("move " + lob.getTableId() + "/" + lob.getLobId() +
279                    " > " + tableId + "/" + lobId);
280        }
281        value[1] = tableId;
282        lobMap.put(lobId, value);
283    }
284 
285    @Override
286    public void removeAllForTable(int tableId) {
287        init();
288        if (database.getMvStore().getStore().isClosed()) {
289            return;
290        }
291        // this might not be very efficient -
292        // to speed it up, we would need yet another map
293        ArrayList<Long> list = New.arrayList();
294        for (Entry<Long, Object[]> e : lobMap.entrySet()) {
295            Object[] value = e.getValue();
296            int t = (Integer) value[1];
297            if (t == tableId) {
298                list.add(e.getKey());
299            }
300        }
301        for (long lobId : list) {
302            removeLob(tableId, lobId);
303        }
304        if (tableId == LobStorageFrontend.TABLE_ID_SESSION_VARIABLE) {
305            removeAllForTable(LobStorageFrontend.TABLE_TEMP);
306            removeAllForTable(LobStorageFrontend.TABLE_RESULT);
307        }
308    }
309 
310    @Override
311    public void removeLob(ValueLobDb lob) {
312        init();
313        int tableId = lob.getTableId();
314        long lobId = lob.getLobId();
315        removeLob(tableId, lobId);
316    }
317 
318    private void removeLob(int tableId, long lobId) {
319        if (TRACE) {
320            trace("remove " + tableId + "/" + lobId);
321        }
322        Object[] value = lobMap.remove(lobId);
323        if (value == null) {
324            // already removed
325            return;
326        }
327        byte[] streamStoreId = (byte[]) value[0];
328        Object[] key = new Object[] {streamStoreId, lobId };
329        refMap.remove(key);
330        // check if there are more entries for this streamStoreId
331        key = new Object[] {streamStoreId, 0L };
332        value = refMap.ceilingKey(key);
333        boolean hasMoreEntries = false;
334        if (value != null) {
335            byte[] s2 = (byte[]) value[0];
336            if (Arrays.equals(streamStoreId, s2)) {
337                hasMoreEntries = true;
338            }
339        }
340        if (!hasMoreEntries) {
341            streamStore.remove(streamStoreId);
342        }
343    }
344 
345    private static void trace(String op) {
346        System.out.println(Thread.currentThread().getName() + " LOB " + op);
347    }
348 
349}

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