File D:\maven-qcr-plugin-testsunitaires\qcr4java\h2-1.3.170-sources\target\site\qc\lecode\tools\Recover.java.html

1 /*
2 * Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License,
3 * Version 1.0, and under the Eclipse Public License, Version 1.0
4 * (http://h2database.com/html/license.html).
5 * Initial Developer: H2 Group
6 */
7 package org.h2.tools;
8
9 import java.io.BufferedInputStream;
10 import java.io.BufferedReader;
11 import java.io.IOException;
12 import java.io.InputStream;
13 import java.io.InputStreamReader;
14 import java.io.OutputStream;
15 import java.io.PrintWriter;
16 import java.io.Reader;
17 import java.sql.Connection;
18 import java.sql.SQLException;
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.Map;
24 import java.util.zip.CRC32;
25 import org.h2.compress.CompressLZF;
26 import org.h2.engine.Constants;
27 import org.h2.engine.DbObject;
28 import org.h2.engine.MetaRecord;
29 import org.h2.jdbc.JdbcConnection;
30 import org.h2.message.DbException;
31 import org.h2.result.Row;
32 import org.h2.result.SimpleRow;
33 import org.h2.security.SHA256;
34 import org.h2.store.Data;
35 import org.h2.store.DataHandler;
36 import org.h2.store.DataReader;
37 import org.h2.store.FileLister;
38 import org.h2.store.FileStore;
39 import org.h2.store.FileStoreInputStream;
40 import org.h2.store.LobStorage;
41 import org.h2.store.Page;
42 import org.h2.store.PageFreeList;
43 import org.h2.store.PageLog;
44 import org.h2.store.PageStore;
45 import org.h2.store.fs.FileUtils;
46 import org.h2.util.BitField;
47 import org.h2.util.IOUtils;
48 import org.h2.util.IntArray;
49 import org.h2.util.MathUtils;
50 import org.h2.util.New;
51 import org.h2.util.SmallLRUCache;
52 import org.h2.util.StatementBuilder;
53 import org.h2.util.StringUtils;
54 import org.h2.util.TempFileDeleter;
55 import org.h2.util.Tool;
56 import org.h2.util.Utils;
57 import org.h2.value.Value;
58 import org.h2.value.ValueLob;
59 import org.h2.value.ValueLobDb;
60 import org.h2.value.ValueLong;
61
62 /**
63 * Helps recovering a corrupted database.
64 * @h2.resource
65 */
66 public class Recover extends Tool implements DataHandler {
67
68 private String databaseName;
69 private int storageId;
70 private String storageName;
71 private int recordLength;
72 private int valueId;
73 private boolean trace;
74 private boolean transactionLog;
75 private ArrayList<MetaRecord> schema;
76 private HashSet<Integer> objectIdSet;
77 private HashMap<Integer, String> tableMap;
78 private HashMap<String, String> columnTypeMap;
79 private boolean remove;
80
81 private int pageSize;
82 private FileStore store;
83 private int[] parents;
84
85 private Stats stat;
86
87 /**
88 * Statistic data
89 */
90 static class Stats {
91
92 /**
93 * The empty space in bytes in a data leaf pages.
94 */
95 long pageDataEmpty;
96
97 /**
98 * The number of bytes used for data.
99 */
100 int pageDataRows;
101
102 /**
103 * The number of bytes used for the page headers.
104 */
105 int pageDataHead;
106
107 /**
108 * The count per page type.
109 */
110 final int[] pageTypeCount = new int[Page.TYPE_STREAM_DATA + 2];
111
112 /**
113 * The number of free pages.
114 */
115 int free;
116 }
117
118 /**
119 * Options are case sensitive. Supported options are:
120 * <table>
121 * <tr><td>[-help] or [-?]</td>
122 * <td>Print the list of options</td></tr>
123 * <tr><td>[-dir &lt;dir&gt;]</td>
124 * <td>The directory (default: .)</td></tr>
125 * <tr><td>[-db &lt;database&gt;]</td>
126 * <td>The database name (all databases if not set)</td></tr>
127 * <tr><td>[-trace]</td>
128 * <td>Print additional trace information</td></tr>
129 * </table>
130 * Encrypted databases need to be decrypted first.
131 * @h2.resource
132 *
133 * @param args the command line arguments
134 */
135 public static void main(String... args) throws SQLException {
136 new Recover().runTool(args);
137 }
138
139 /**
140 * Dumps the contents of a database file to a human readable text file. This
141 * text file can be used to recover most of the data. This tool does not
142 * open the database and can be used even if the database files are
143 * corrupted. A database can get corrupted if there is a bug in the database
144 * engine or file system software, or if an application writes into the
145 * database file that doesn't understand the the file format, or if there is
146 * a hardware problem.
147 *
148 * @param args the command line arguments
149 */
150 public void runTool(String... args) throws SQLException {
151 String dir = ".";
152 String db = null;
153 for (int i = 0; args != null && i < args.length; i++) {
154 String arg = args[i];
155 if ("-dir".equals(arg)) {
156 dir = args[++i];
157 } else if ("-db".equals(arg)) {
158 db = args[++i];
159 } else if ("-removePassword".equals(arg)) {
160 remove = true;
161 } else if ("-trace".equals(arg)) {
162 trace = true;
163 } else if ("-transactionLog".equals(arg)) {
164 transactionLog = true;
165 } else if (arg.equals("-help") || arg.equals("-?")) {
166 showUsage();
167 return;
168 } else {
169 showUsageAndThrowUnsupportedOption(arg);
170 }
171 }
172 process(dir, db);
173 }
174
175 /**
176 * INTERNAL
177 */
178 public static Reader readClob(String fileName) throws IOException {
179 return new BufferedReader(new InputStreamReader(readBlob(fileName), "UTF-8"));
180 }
181
182 /**
183 * INTERNAL
184 */
185 public static InputStream readBlob(String fileName) throws IOException {
186 return new BufferedInputStream(FileUtils.newInputStream(fileName));
187 }
188
189 /**
190 * INTERNAL
191 */
192 public static Value.ValueBlob readBlobDb(Connection conn, long lobId, long precision) {
193 DataHandler h = ((JdbcConnection) conn).getSession().getDataHandler();
194 LobStorage lobStorage = h.getLobStorage();
195 return ValueLobDb.create(Value.BLOB, lobStorage, LobStorage.TABLE_TEMP, lobId, null, precision);
196 }
197
198 /**
199 * INTERNAL
200 */
201 public static Value.ValueClob readClobDb(Connection conn, long lobId, long precision) {
202 DataHandler h = ((JdbcConnection) conn).getSession().getDataHandler();
203 LobStorage lobStorage = h.getLobStorage();
204 return ValueLobDb.create(Value.CLOB, lobStorage, LobStorage.TABLE_TEMP, lobId, null, precision);
205 }
206
207 private void trace(String message) {
208 if (trace) {
209 out.println(message);
210 }
211 }
212
213 private void traceError(String message, Throwable t) {
214 out.println(message + ": " + t.toString());
215 if (trace) {
216 t.printStackTrace(out);
217 }
218 }
219
220 /**
221 * Dumps the contents of a database to a SQL script file.
222 *
223 * @param dir the directory
224 * @param db the database name (null for all databases)
225 */
226 public static void execute(String dir, String db) throws SQLException {
227 try {
228 new Recover().process(dir, db);
229 } catch (DbException e) {
230 throw DbException.toSQLException(e);
231 }
232 }
233
234 private void process(String dir, String db) {
235 ArrayList<String> list = FileLister.getDatabaseFiles(dir, db, true);
236 if (list.size() == 0) {
237 printNoDatabaseFilesFound(dir, db);
238 }
239 for (String fileName : list) {
240 if (fileName.endsWith(Constants.SUFFIX_PAGE_FILE)) {
241 dumpPageStore(fileName);
242 } else if (fileName.endsWith(Constants.SUFFIX_LOB_FILE)) {
243 dumpLob(fileName, false);
244 }
245 }
246 }
247
248 private PrintWriter getWriter(String fileName, String suffix) {
249 fileName = fileName.substring(0, fileName.length() - 3);
250 String outputFile = fileName + suffix;
251 trace("Created file: " + outputFile);
252 return new PrintWriter(IOUtils.getBufferedWriter(FileUtils.newOutputStream(outputFile, false)));
253 }
254
255 private void writeDataError(PrintWriter writer, String error, byte[] data) {
256 writer.println("-- ERROR: " + error + " storageId: "
257 + storageId + " recordLength: " + recordLength + " valueId: " + valueId);
258 StringBuilder sb = new StringBuilder();
259 for (int i = 0; i < data.length; i++) {
260 int x = data[i] & 0xff;
261 if (x >= ' ' && x < 128) {
262 sb.append((char) x);
263 } else {
264 sb.append('?');
265 }
266 }
267 writer.println("-- dump: " + sb.toString());
268 sb = new StringBuilder();
269 for (int i = 0; i < data.length; i++) {
270 int x = data[i] & 0xff;
271 sb.append(' ');
272 if (x < 16) {
273 sb.append('0');
274 }
275 sb.append(Integer.toHexString(x));
276 }
277 writer.println("-- dump: " + sb.toString());
278 }
279
280 private void dumpLob(String fileName, boolean lobCompression) {
281 OutputStream fileOut = null;
282 FileStore fileStore = null;
283 long size = 0;
284 String n = fileName + (lobCompression ? ".comp" : "") + ".txt";
285 InputStream in = null;
286 try {
287 fileOut = FileUtils.newOutputStream(n, false);
288 fileStore = FileStore.open(null, fileName, "r");
289 fileStore.init();
290 in = new FileStoreInputStream(fileStore, this, lobCompression, false);
291 size = IOUtils.copy(in, fileOut);
292 } catch (Throwable e) {
293 // this is usually not a problem, because we try both compressed and
294 // uncompressed
295 } finally {
296 IOUtils.closeSilently(fileOut);
297 IOUtils.closeSilently(in);
298 closeSilently(fileStore);
299 }
300 if (size == 0) {
301 try {
302 FileUtils.delete(n);
303 } catch (Exception e) {
304 traceError(n, e);
305 }
306 }
307 }
308
309 private String getSQL(String column, Value v) {
310 if (v instanceof ValueLob) {
311 ValueLob lob = (ValueLob) v;
312 byte[] small = lob.getSmall();
313 if (small == null) {
314 String file = lob.getFileName();
315 String type = lob.getType() == Value.BLOB ? "BLOB" : "CLOB";
316 if (lob.useCompression()) {
317 dumpLob(file, true);
318 file += ".comp";
319 }
320 return "READ_" + type + "('" + file + ".txt')";
321 }
322 } else if (v instanceof ValueLobDb) {
323 ValueLobDb lob = (ValueLobDb) v;
324 byte[] small = lob.getSmall();
325 if (small == null) {
326 int type = lob.getType();
327 long id = lob.getLobId();
328 long precision = lob.getPrecision();
329 String m;
330 String columnType;
331 if (type == Value.BLOB) {
332 columnType = "BLOB";
333 m = "READ_BLOB_DB";
334 } else {
335 columnType = "CLOB";
336 m = "READ_CLOB_DB";
337 }
338 columnTypeMap.put(column, columnType);
339 return m + "(" + id + ", " + precision + ")";
340 }
341 }
342 return v.getSQL();
343 }
344
345 private void setDatabaseName(String name) {
346 databaseName = name;
347 }
348
349 private void dumpPageStore(String fileName) {
350 setDatabaseName(fileName.substring(0, fileName.length() - Constants.SUFFIX_PAGE_FILE.length()));
351 PrintWriter writer = null;
352 stat = new Stats();
353 try {
354 writer = getWriter(fileName, ".sql");
355 writer.println("CREATE ALIAS IF NOT EXISTS READ_BLOB FOR \"" + this.getClass().getName() + ".readBlob\";");
356 writer.println("CREATE ALIAS IF NOT EXISTS READ_CLOB FOR \"" + this.getClass().getName() + ".readClob\";");
357 writer.println("CREATE ALIAS IF NOT EXISTS READ_BLOB_DB FOR \"" + this.getClass().getName() + ".readBlobDb\";");
358 writer.println("CREATE ALIAS IF NOT EXISTS READ_CLOB_DB FOR \"" + this.getClass().getName() + ".readClobDb\";");
359 resetSchema();
360 store = FileStore.open(null, fileName, remove ? "rw" : "r");
361 long length = store.length();
362 try {
363 store.init();
364 } catch (Exception e) {
365 writeError(writer, e);
366 }
367 Data s = Data.create(this, 128);
368 seek(0);
369 store.readFully(s.getBytes(), 0, 128);
370 s.setPos(48);
371 pageSize = s.readInt();
372 int writeVersion = s.readByte();
373 int readVersion = s.readByte();
374 writer.println("-- pageSize: " + pageSize +
375 " writeVersion: " + writeVersion +
376 " readVersion: " + readVersion);
377 if (pageSize < PageStore.PAGE_SIZE_MIN || pageSize > PageStore.PAGE_SIZE_MAX) {
378 pageSize = Constants.DEFAULT_PAGE_SIZE;
379 writer.println("-- ERROR: page size; using " + pageSize);
380 }
381 long pageCount = length / pageSize;
382 parents = new int[(int) pageCount];
383 s = Data.create(this, pageSize);
384 for (long i = 3; i < pageCount; i++) {
385 s.reset();
386 seek(i);
387 store.readFully(s.getBytes(), 0, 32);
388 s.readByte();
389 s.readShortInt();
390 parents[(int) i] = s.readInt();
391 }
392 int logKey = 0, logFirstTrunkPage = 0, logFirstDataPage = 0;
393 s = Data.create(this, pageSize);
394 for (long i = 1;; i++) {
395 if (i == 3) {
396 break;
397 }
398 s.reset();
399 seek(i);
400 store.readFully(s.getBytes(), 0, pageSize);
401 CRC32 crc = new CRC32();
402 crc.update(s.getBytes(), 4, pageSize - 4);
403 int expected = (int) crc.getValue();
404 int got = s.readInt();
405 long writeCounter = s.readLong();
406 int key = s.readInt();
407 int firstTrunkPage = s.readInt();
408 int firstDataPage = s.readInt();
409 if (expected == got) {
410 logKey = key;
411 logFirstTrunkPage = firstTrunkPage;
412 logFirstDataPage = firstDataPage;
413 }
414 writer.println("-- head " + i +
415 ": writeCounter: " + writeCounter +
416 " log " + key + ":" + firstTrunkPage + "/" + firstDataPage +
417 " crc " + got + " (" + (expected == got ? "ok" : ("expected: " + expected)) + ")");
418 }
419 writer.println("-- log " + logKey + ":" + logFirstTrunkPage +
420 "/" + logFirstDataPage);
421
422 PrintWriter devNull = new PrintWriter(new OutputStream() {
423 public void write(int b) {
424 // ignore
425 }
426 });
427 dumpPageStore(devNull, pageCount);
428 stat = new Stats();
429 schema.clear();
430 objectIdSet = New.hashSet();
431 dumpPageStore(writer, pageCount);
432 writeSchema(writer);
433 try {
434 dumpPageLogStream(writer, logKey, logFirstTrunkPage, logFirstDataPage, pageCount);
435 } catch (IOException e) {
436 // ignore
437 }
438 writer.println("---- Statistics ----");
439 writer.println("-- page count: " + pageCount + ", free: " + stat.free);
440 long total = Math.max(1, stat.pageDataRows + stat.pageDataEmpty + stat.pageDataHead);
441 writer.println("-- page data bytes: head " + stat.pageDataHead +
442 ", empty " + stat.pageDataEmpty +
443 ", rows " + stat.pageDataRows +
444 " (" + (100 - 100L * stat.pageDataEmpty / total) + "% full)");
445 for (int i = 0; i < stat.pageTypeCount.length; i++) {
446 int count = stat.pageTypeCount[i];
447 if (count > 0) {
448 writer.println("-- " + getPageType(i) + " " + (100 * count / pageCount) + "%, " + count + " page(s)");
449 }
450 }
451 writer.close();
452 } catch (Throwable e) {
453 writeError(writer, e);
454 } finally {
455 IOUtils.closeSilently(writer);
456 closeSilently(store);
457 }
458 }
459
460 private static String getPageType(int type) {
461 switch (type) {
462 case 0:
463 return "free";
464 case Page.TYPE_DATA_LEAF:
465 return "data leaf";
466 case Page.TYPE_DATA_NODE:
467 return "data node";
468 case Page.TYPE_DATA_OVERFLOW:
469 return "data overflow";
470 case Page.TYPE_BTREE_LEAF:
471 return "btree leaf";
472 case Page.TYPE_BTREE_NODE:
473 return "btree node";
474 case Page.TYPE_FREE_LIST:
475 return "free list";
476 case Page.TYPE_STREAM_TRUNK:
477 return "stream trunk";
478 case Page.TYPE_STREAM_DATA:
479 return "stream data";
480 }
481 return "[" + type + "]";
482 }
483
484 private void dumpPageStore(PrintWriter writer, long pageCount) {
485 Data s = Data.create(this, pageSize);
486 for (long page = 3; page < pageCount; page++) {
487 s = Data.create(this, pageSize);
488 seek(page);
489 store.readFully(s.getBytes(), 0, pageSize);
490 dumpPage(writer, s, page, pageCount);
491 }
492 }
493
494 private void dumpPage(PrintWriter writer, Data s, long page, long pageCount) {
495 try {
496 int type = s.readByte();
497 switch (type) {
498 case Page.TYPE_EMPTY:
499 stat.pageTypeCount[type]++;
500 return;
501 }
502 boolean last = (type & Page.FLAG_LAST) != 0;
503 type &= ~Page.FLAG_LAST;
504 if (!PageStore.checksumTest(s.getBytes(), (int) page, pageSize)) {
505 writeDataError(writer, "checksum mismatch type: " + type, s.getBytes());
506 }
507 s.readShortInt();
508 switch (type) {
509 // type 1
510 case Page.TYPE_DATA_LEAF: {
511 stat.pageTypeCount[type]++;
512 int parentPageId = s.readInt();
513 setStorage(s.readVarInt());
514 int columnCount = s.readVarInt();
515 int entries = s.readShortInt();
516 writer.println("-- page " + page + ": data leaf " + (last ? "(last) " : "") + "parent: " + parentPageId +
517 " table: " + storageId + " entries: " + entries + " columns: " + columnCount);
518 dumpPageDataLeaf(writer, s, last, page, columnCount, entries);
519 break;
520 }
521 // type 2
522 case Page.TYPE_DATA_NODE: {
523 stat.pageTypeCount[type]++;
524 int parentPageId = s.readInt();
525 setStorage(s.readVarInt());
526 int rowCount = s.readInt();
527 int entries = s.readShortInt();
528 writer.println("-- page " + page + ": data node " + (last ? "(last) " : "") + "parent: " + parentPageId +
529 " table: " + storageId + " entries: " + entries + " rowCount: " + rowCount);
530 dumpPageDataNode(writer, s, page, entries);
531 break;
532 }
533 // type 3
534 case Page.TYPE_DATA_OVERFLOW:
535 stat.pageTypeCount[type]++;
536 writer.println("-- page " + page + ": data overflow " + (last ? "(last) " : ""));
537 break;
538 // type 4
539 case Page.TYPE_BTREE_LEAF: {
540 stat.pageTypeCount[type]++;
541 int parentPageId = s.readInt();
542 setStorage(s.readVarInt());
543 int entries = s.readShortInt();
544 writer.println("-- page " + page + ": b-tree leaf " + (last ? "(last) " : "") + "parent: " + parentPageId +
545 " index: " + storageId + " entries: " + entries);
546 if (trace) {
547 dumpPageBtreeLeaf(writer, s, entries, !last);
548 }
549 break;
550 }
551 // type 5
552 case Page.TYPE_BTREE_NODE:
553 stat.pageTypeCount[type]++;
554 int parentPageId = s.readInt();
555 setStorage(s.readVarInt());
556 writer.println("-- page " + page + ": b-tree node " + (last ? "(last) " : "") + "parent: " + parentPageId +
557 " index: " + storageId);
558 dumpPageBtreeNode(writer, s, page, !last);
559 break;
560 // type 6
561 case Page.TYPE_FREE_LIST:
562 stat.pageTypeCount[type]++;
563 writer.println("-- page " + page + ": free list " + (last ? "(last)" : ""));
564 stat.free += dumpPageFreeList(writer, s, page, pageCount);
565 break;
566 // type 7
567 case Page.TYPE_STREAM_TRUNK:
568 stat.pageTypeCount[type]++;
569 writer.println("-- page " + page + ": log trunk");
570 break;
571 // type 8
572 case Page.TYPE_STREAM_DATA:
573 stat.pageTypeCount[type]++;
574 writer.println("-- page " + page + ": log data");
575 break;
576 default:
577 writer.println("-- ERROR page " + page + " unknown type " + type);
578 break;
579 }
580 } catch (Exception e) {
581 writeError(writer, e);
582 }
583 }
584
585 private void dumpPageLogStream(PrintWriter writer, int logKey,
586 int logFirstTrunkPage, int logFirstDataPage, long pageCount) throws IOException {
587 Data s = Data.create(this, pageSize);
588 DataReader in = new DataReader(
589 new PageInputStream(writer, this, store, logKey, logFirstTrunkPage, logFirstDataPage, pageSize)
590 );
591 writer.println("---- Transaction log ----");
592 CompressLZF compress = new CompressLZF();
593 while (true) {
594 int x = in.readByte();
595 if (x < 0) {
596 break;
597 }
598 if (x == PageLog.NOOP) {
599 // ignore
600 } else if (x == PageLog.UNDO) {
601 int pageId = in.readVarInt();
602 int size = in.readVarInt();
603 byte[] data = new byte[pageSize];
604 if (size == 0) {
605 in.readFully(data, 0, pageSize);
606 } else if (size == 1) {
607 // empty
608 } else {
609 byte[] compressBuffer = new byte[size];
610 in.readFully(compressBuffer, 0, size);
611 try {
612 compress.expand(compressBuffer, 0, size, data, 0, pageSize);
613 } catch (ArrayIndexOutOfBoundsException e) {
614 throw DbException.convertToIOException(e);
615 }
616 }
617 String typeName = "";
618 int type = data[0];
619 boolean last = (type & Page.FLAG_LAST) != 0;
620 type &= ~Page.FLAG_LAST;
621 switch (type) {
622 case Page.TYPE_EMPTY:
623 typeName = "empty";
624 break;
625 case Page.TYPE_DATA_LEAF:
626 typeName = "data leaf " + (last ? "(last)" : "");
627 break;
628 case Page.TYPE_DATA_NODE:
629 typeName = "data node " + (last ? "(last)" : "");
630 break;
631 case Page.TYPE_DATA_OVERFLOW:
632 typeName = "data overflow " + (last ? "(last)" : "");
633 break;
634 case Page.TYPE_BTREE_LEAF:
635 typeName = "b-tree leaf " + (last ? "(last)" : "");
636 break;
637 case Page.TYPE_BTREE_NODE:
638 typeName = "b-tree node " + (last ? "(last)" : "");
639 break;
640 case Page.TYPE_FREE_LIST:
641 typeName = "free list " + (last ? "(last)" : "");
642 break;
643 case Page.TYPE_STREAM_TRUNK:
644 typeName = "log trunk";
645 break;
646 case Page.TYPE_STREAM_DATA:
647 typeName = "log data";
648 break;
649 default:
650 typeName = "ERROR: unknown type " + type;
651 break;
652 }
653 writer.println("-- undo page " + pageId + " " + typeName);
654 if (trace) {
655 Data d = Data.create(null, data);
656 dumpPage(writer, d, pageId, pageCount);
657 }
658 } else if (x == PageLog.ADD) {
659 int sessionId = in.readVarInt();
660 setStorage(in.readVarInt());
661 Row row = PageLog.readRow(in, s);
662 writer.println("-- session " + sessionId +
663 " table " + storageId +
664 " + " + row.toString());
665 if (transactionLog) {
666 if (storageId == 0 && row.getColumnCount() >= 4) {
667 int tableId = (int) row.getKey();
668 String sql = row.getValue(3).getString();
669 String name = extractTableOrViewName(sql);
670 if (row.getValue(2).getInt() == DbObject.TABLE_OR_VIEW) {
671 tableMap.put(tableId, name);
672 }
673 writer.println(sql + ";");
674 } else {
675 String tableName = tableMap.get(storageId);
676 if (tableName != null) {
677 StatementBuilder buff = new StatementBuilder();
678 buff.append("INSERT INTO ").append(tableName).append(" VALUES(");
679 for (int i = 0; i < row.getColumnCount(); i++) {
680 buff.appendExceptFirst(", ");
681 buff.append(row.getValue(i).getSQL());
682 }
683 buff.append(");");
684 writer.println(buff.toString());
685 }
686 }
687 }
688 } else if (x == PageLog.REMOVE) {
689 int sessionId = in.readVarInt();
690 setStorage(in.readVarInt());
691 long key = in.readVarLong();
692 writer.println("-- session " + sessionId +
693 " table " + storageId +
694 " - " + key);
695 if (transactionLog) {
696 if (storageId == 0) {
697 int tableId = (int) key;
698 String tableName = tableMap.get(tableId);
699 if (tableName != null) {
700 writer.println("DROP TABLE IF EXISTS " + tableName + ";");
701 }
702 } else {
703 String tableName = tableMap.get(storageId);
704 if (tableName != null) {
705 String sql = "DELETE FROM " + tableName + " WHERE _ROWID_ = " + key + ";";
706 writer.println(sql);
707 }
708 }
709 }
710 } else if (x == PageLog.TRUNCATE) {
711 int sessionId = in.readVarInt();
712 setStorage(in.readVarInt());
713 writer.println("-- session " + sessionId +
714 " table " + storageId +
715 " truncate");
716 if (transactionLog) {
717 writer.println("TRUNCATE TABLE " + storageId);
718 }
719 } else if (x == PageLog.COMMIT) {
720 int sessionId = in.readVarInt();
721 writer.println("-- commit " + sessionId);
722 } else if (x == PageLog.ROLLBACK) {
723 int sessionId = in.readVarInt();
724 writer.println("-- rollback " + sessionId);
725 } else if (x == PageLog.PREPARE_COMMIT) {
726 int sessionId = in.readVarInt();
727 String transaction = in.readString();
728 writer.println("-- prepare commit " + sessionId + " " + transaction);
729 } else if (x == PageLog.NOOP) {
730 // nothing to do
731 } else if (x == PageLog.CHECKPOINT) {
732 writer.println("-- checkpoint");
733 } else if (x == PageLog.FREE_LOG) {
734 int size = in.readVarInt();
735 StringBuilder buff = new StringBuilder("-- free");
736 for (int i = 0; i < size; i++) {
737 buff.append(' ').append(in.readVarInt());
738 }
739 writer.println(buff);
740 } else {
741 writer.println("-- ERROR: unknown operation " + x);
742 break;
743 }
744 }
745 }
746
747 private String setStorage(int storageId) {
748 this.storageId = storageId;
749 this.storageName = "O_" + String.valueOf(storageId).replace('-', 'M');
750 return storageName;
751 }
752
753 /**
754 * An input stream that reads the data from a page store.
755 */
756 static class PageInputStream extends InputStream {
757
758 private final PrintWriter writer;
759 private final FileStore store;
760 private final Data page;
761 private final int pageSize;
762 private long trunkPage;
763 private long nextTrunkPage;
764 private long dataPage;
765 private final IntArray dataPages = new IntArray();
766 private boolean endOfFile;
767 private int remaining;
768 private int logKey;
769
770 public PageInputStream(PrintWriter writer, DataHandler handler,
771 FileStore store, int logKey, long firstTrunkPage, long firstDataPage, int pageSize) {
772 this.writer = writer;
773 this.store = store;
774 this.pageSize = pageSize;
775 this.logKey = logKey - 1;
776 this.nextTrunkPage = firstTrunkPage;
777 this.dataPage = firstDataPage;
778 page = Data.create(handler, pageSize);
779 }
780
781 public int read() {
782 byte[] b = { 0 };
783 int len = read(b);
784 return len < 0 ? -1 : (b[0] & 255);
785 }
786
787 public int read(byte[] b) {
788 return read(b, 0, b.length);
789 }
790
791 public int read(byte[] b, int off, int len) {
792 if (len == 0) {
793 return 0;
794 }
795 int read = 0;
796 while (len > 0) {
797 int r = readBlock(b, off, len);
798 if (r < 0) {
799 break;
800 }
801 read += r;
802 off += r;
803 len -= r;
804 }
805 return read == 0 ? -1 : read;
806 }
807
808 private int readBlock(byte[] buff, int off, int len) {
809 fillBuffer();
810 if (endOfFile) {
811 return -1;
812 }
813 int l = Math.min(remaining, len);
814 page.read(buff, off, l);
815 remaining -= l;
816 return l;
817 }
818
819 private void fillBuffer() {
820 if (remaining > 0 || endOfFile) {
821 return;
822 }
823 while (dataPages.size() == 0) {
824 if (nextTrunkPage == 0) {
825 endOfFile = true;
826 return;
827 }
828 trunkPage = nextTrunkPage;
829 store.seek(trunkPage * pageSize);
830 store.readFully(page.getBytes(), 0, pageSize);
831 page.reset();
832 if (!PageStore.checksumTest(page.getBytes(), (int) trunkPage, pageSize)) {
833 writer.println("-- ERROR: checksum mismatch page: " +trunkPage);
834 endOfFile = true;
835 return;
836 }
837 int t = page.readByte();
838 page.readShortInt();
839 if (t != Page.TYPE_STREAM_TRUNK) {
840 writer.println("-- log eof " + trunkPage + " type: " + t + " expected type: " + Page.TYPE_STREAM_TRUNK);
841 endOfFile = true;
842 return;
843 }
844 page.readInt();
845 int key = page.readInt();
846 logKey++;
847 if (key != logKey) {
848 writer.println("-- log eof " + trunkPage + " type: " + t + " expected key: " + logKey + " got: " + key);
849 }
850 nextTrunkPage = page.readInt();
851 writer.println("-- log " + key + ":" + trunkPage + " next: " + nextTrunkPage);
852 int pageCount = page.readShortInt();
853 for (int i = 0; i < pageCount; i++) {
854 int d = page.readInt();
855 if (dataPage != 0) {
856 if (d == dataPage) {
857 dataPage = 0;
858 } else {
859 // ignore the pages before the starting page
860 continue;
861 }
862 }
863 dataPages.add(d);
864 }
865 }
866 if (dataPages.size() > 0) {
867 page.reset();
868 long nextPage = dataPages.get(0);
869 dataPages.remove(0);
870 store.seek(nextPage * pageSize);
871 store.readFully(page.getBytes(), 0, pageSize);
872 page.reset();
873 int t = page.readByte();
874 if (t != 0 && !PageStore.checksumTest(page.getBytes(), (int) nextPage, pageSize)) {
875 writer.println("-- ERROR: checksum mismatch page: " +nextPage);
876 endOfFile = true;
877 return;
878 }
879 page.readShortInt();
880 int p = page.readInt();
881 int k = page.readInt();
882 writer.println("-- log " + k + ":" + trunkPage + "/" + nextPage);
883 if (t != Page.TYPE_STREAM_DATA) {
884 writer.println("-- log eof " +nextPage+ " type: " + t + " parent: " + p +
885 " expected type: " + Page.TYPE_STREAM_DATA);
886 endOfFile = true;
887 return;
888 } else if (k != logKey) {
889 writer.println("-- log eof " +nextPage+ " type: " + t + " parent: " + p +
890 " expected key: " + logKey + " got: " + k);
891 endOfFile = true;
892 return;
893 }
894 remaining = pageSize - page.length();
895 }
896 }
897 }
898
899 private void dumpPageBtreeNode(PrintWriter writer, Data s, long pageId, boolean positionOnly) {
900 int rowCount = s.readInt();
901 int entryCount = s.readShortInt();
902 int[] children = new int[entryCount + 1];
903 int[] offsets = new int[entryCount];
904 children[entryCount] = s.readInt();
905 checkParent(writer, pageId, children, entryCount);
906 int empty = Integer.MAX_VALUE;
907 for (int i = 0; i < entryCount; i++) {
908 children[i] = s.readInt();
909 checkParent(writer, pageId, children, i);
910 int off = s.readShortInt();
911 empty = Math.min(off, empty);
912 offsets[i] = off;
913 }
914 empty = empty - s.length();
915 if (!trace) {
916 return;
917 }
918 writer.println("-- empty: " + empty);
919 for (int i = 0; i < entryCount; i++) {
920 int off = offsets[i];
921 s.setPos(off);
922 long key = s.readVarLong();
923 Value data;
924 if (positionOnly) {
925 data = ValueLong.get(key);
926 } else {
927 try {
928 data = s.readValue();
929 } catch (Throwable e) {
930 writeDataError(writer, "exception " + e, s.getBytes());
931 continue;
932 }
933 }
934 writer.println("-- [" + i + "] child: " + children[i] + " key: " + key + " data: " + data);
935 }
936 writer.println("-- [" + entryCount + "] child: " + children[entryCount] + " rowCount: " + rowCount);
937 }
938
939 private int dumpPageFreeList(PrintWriter writer, Data s, long pageId, long pageCount) {
940 int pagesAddressed = PageFreeList.getPagesAddressed(pageSize);
941 BitField used = new BitField();
942 for (int i = 0; i < pagesAddressed; i += 8) {
943 int x = s.readByte() & 255;
944 for (int j = 0; j < 8; j++) {
945 if ((x & (1 << j)) != 0) {
946 used.set(i + j);
947 }
948 }
949 }
950 int free = 0;
951 for (long i = 0, j = pageId; i < pagesAddressed && j < pageCount; i++, j++) {
952 if (i == 0 || j % 100 == 0) {
953 if (i > 0) {
954 writer.println();
955 }
956 writer.print("-- " + j + " ");
957 } else if (j % 20 == 0) {
958 writer.print(" - ");
959 } else if (j % 10 == 0) {
960 writer.print(' ');
961 }
962 writer.print(used.get((int) i) ? '1' : '0');
963 if (!used.get((int) i)) {
964 free++;
965 }
966 }
967 writer.println();
968 return free;
969 }
970
971 private void dumpPageBtreeLeaf(PrintWriter writer, Data s, int entryCount, boolean positionOnly) {
972 int[] offsets = new int[entryCount];
973 int empty = Integer.MAX_VALUE;
974 for (int i = 0; i < entryCount; i++) {
975 int off = s.readShortInt();
976 empty = Math.min(off, empty);
977 offsets[i] = off;
978 }
979 empty = empty - s.length();
980 writer.println("-- empty: " + empty);
981 for (int i = 0; i < entryCount; i++) {
982 int off = offsets[i];
983 s.setPos(off);
984 long key = s.readVarLong();
985 Value data;
986 if (positionOnly) {
987 data = ValueLong.get(key);
988 } else {
989 try {
990 data = s.readValue();
991 } catch (Throwable e) {
992 writeDataError(writer, "exception " + e, s.getBytes());
993 continue;
994 }
995 }
996 writer.println("-- [" + i + "] key: " + key + " data: " + data);
997 }
998 }
999
1000 private void checkParent(PrintWriter writer, long pageId, int[] children, int index) {
1001 int child = children[index];
1002 if (child < 0 || child >= parents.length) {
1003 writer.println("-- ERROR [" + pageId + "] child[" + index + "]: " + child + " >= page count: " + parents.length);
1004 } else if (parents[child] != pageId) {
1005 writer.println("-- ERROR [" + pageId + "] child[" + index + "]: " + child + " parent: " + parents[child]);
1006 }
1007 }
1008
1009 private void dumpPageDataNode(PrintWriter writer, Data s, long pageId, int entryCount) {
1010 int[] children = new int[entryCount + 1];
1011 long[] keys = new long[entryCount];
1012 children[entryCount] = s.readInt();
1013 checkParent(writer, pageId, children, entryCount);
1014 for (int i = 0; i < entryCount; i++) {
1015 children[i] = s.readInt();
1016 checkParent(writer, pageId, children, i);
1017 keys[i] = s.readVarLong();
1018 }
1019 if (!trace) {
1020 return;
1021 }
1022 for (int i = 0; i < entryCount; i++) {
1023 writer.println("-- [" + i + "] child: " + children[i] + " key: " + keys[i]);
1024 }
1025 writer.println("-- [" + entryCount + "] child: " + children[entryCount]);
1026 }
1027
1028 private void dumpPageDataLeaf(PrintWriter writer, Data s, boolean last, long pageId, int columnCount, int entryCount) {
1029 long[] keys = new long[entryCount];
1030 int[] offsets = new int[entryCount];
1031 long next = 0;
1032 if (!last) {
1033 next = s.readInt();
1034 writer.println("-- next: " + next);
1035 }
1036 int empty = pageSize;
1037 for (int i = 0; i < entryCount; i++) {
1038 keys[i] = s.readVarLong();
1039 int off = s.readShortInt();
1040 empty = Math.min(off, empty);
1041 offsets[i] = off;
1042 }
1043 stat.pageDataRows += pageSize - empty;
1044 empty = empty - s.length();
1045 stat.pageDataHead += s.length();
1046 stat.pageDataEmpty += empty;
1047 if (trace) {
1048 writer.println("-- empty: " + empty);
1049 }
1050 if (!last) {
1051 Data s2 = Data.create(this, pageSize);
1052 s.setPos(pageSize);
1053 long parent = pageId;
1054 while (true) {
1055 checkParent(writer, parent, new int[]{(int) next}, 0);
1056 parent = next;
1057 seek(next);
1058 store.readFully(s2.getBytes(), 0, pageSize);
1059 s2.reset();
1060 int type = s2.readByte();
1061 s2.readShortInt();
1062 s2.readInt();
1063 if (type == (Page.TYPE_DATA_OVERFLOW | Page.FLAG_LAST)) {
1064 int size = s2.readShortInt();
1065 writer.println("-- chain: " + next + " type: " + type + " size: " + size);
1066 s.checkCapacity(size);
1067 s.write(s2.getBytes(), s2.length(), size);
1068 break;
1069 } else if (type == Page.TYPE_DATA_OVERFLOW) {
1070 next = s2.readInt();
1071 if (next == 0) {
1072 writeDataError(writer, "next:0", s2.getBytes());
1073 break;
1074 }
1075 int size = pageSize - s2.length();
1076 writer.println("-- chain: " + next + " type: " + type + " size: " + size + " next: " + next);
1077 s.checkCapacity(size);
1078 s.write(s2.getBytes(), s2.length(), size);
1079 } else {
1080 writeDataError(writer, "type: " + type, s2.getBytes());
1081 break;
1082 }
1083 }
1084 }
1085 for (int i = 0; i < entryCount; i++) {
1086 long key = keys[i];
1087 int off = offsets[i];
1088 if (trace) {
1089 writer.println("-- [" + i + "] storage: " + storageId + " key: " + key + " off: " + off);
1090 }
1091 s.setPos(off);
1092 Value[] data = createRecord(writer, s, columnCount);
1093 if (data != null) {
1094 createTemporaryTable(writer);
1095 writeRow(writer, s, data);
1096 if (remove && storageId == 0) {
1097 String sql = data[3].getString();
1098 if (sql.startsWith("CREATE USER ")) {
1099 int saltIndex = Utils.indexOf(s.getBytes(), "SALT ".getBytes(), off);
1100 if (saltIndex >= 0) {
1101 String userName = sql.substring("CREATE USER ".length(), sql.indexOf("SALT ") - 1);
1102 if (userName.startsWith("IF NOT EXISTS ")) {
1103 userName = userName.substring("IF NOT EXISTS ".length());
1104 }
1105 if (userName.startsWith("\"")) {
1106 // TODO doesn't work for all cases ("" inside user name)
1107 userName = userName.substring(1, userName.length() - 1);
1108 }
1109 byte[] userPasswordHash = SHA256.getKeyPasswordHash(userName, "".toCharArray());
1110 byte[] salt = MathUtils.secureRandomBytes(Constants.SALT_LEN);
1111 byte[] passwordHash = SHA256.getHashWithSalt(userPasswordHash, salt);
1112 StringBuilder buff = new StringBuilder();
1113 buff.append("SALT '").
1114 append(StringUtils.convertBytesToHex(salt)).
1115 append("' HASH '").
1116 append(StringUtils.convertBytesToHex(passwordHash)).
1117 append('\'');
1118 byte[] replacement = buff.toString().getBytes();
1119 System.arraycopy(replacement, 0, s.getBytes(), saltIndex, replacement.length);
1120 seek(pageId);
1121 store.write(s.getBytes(), 0, pageSize);
1122 if (trace) {
1123 out.println("User: " + userName);
1124 }
1125 remove = false;
1126 }
1127 }
1128 }
1129 }
1130 }
1131 }
1132
1133 private void seek(long page) {
1134 // page is long to avoid integer overflow
1135 store.seek(page * pageSize);
1136 }
1137
1138 private Value[] createRecord(PrintWriter writer, Data s, int columnCount) {
1139 recordLength = columnCount;
1140 if (columnCount <= 0) {
1141 writeDataError(writer, "columnCount<0", s.getBytes());
1142 return null;
1143 }
1144 Value[] data;
1145 try {
1146 data = new Value[columnCount];
1147 } catch (OutOfMemoryError e) {
1148 writeDataError(writer, "out of memory", s.getBytes());
1149 return null;
1150 }
1151 return data;
1152 }
1153
1154 private void writeRow(PrintWriter writer, Data s, Value[] data) {
1155 StringBuilder sb = new StringBuilder();
1156 sb.append("INSERT INTO " + storageName + " VALUES(");
1157 for (valueId = 0; valueId < recordLength; valueId++) {
1158 try {
1159 Value v = s.readValue();
1160 data[valueId] = v;
1161 if (valueId > 0) {
1162 sb.append(", ");
1163 }
1164 String columnName = storageName + "." + valueId;
1165 sb.append(getSQL(columnName, v));
1166 } catch (Exception e) {
1167 writeDataError(writer, "exception " + e, s.getBytes());
1168 continue;
1169 } catch (OutOfMemoryError e) {
1170 writeDataError(writer, "out of memory", s.getBytes());
1171 continue;
1172 }
1173 }
1174 sb.append(");");
1175 writer.println(sb.toString());
1176 if (storageId == 0) {
1177 try {
1178 SimpleRow r = new SimpleRow(data);
1179 MetaRecord meta = new MetaRecord(r);
1180 schema.add(meta);
1181 if (meta.getObjectType() == DbObject.TABLE_OR_VIEW) {
1182 String sql = data[3].getString();
1183 String name = extractTableOrViewName(sql);
1184 tableMap.put(meta.getId(), name);
1185 }
1186 } catch (Throwable t) {
1187 writeError(writer, t);
1188 }
1189 }
1190 }
1191
1192 private void resetSchema() {
1193 schema = New.arrayList();
1194 objectIdSet = New.hashSet();
1195 tableMap = New.hashMap();
1196 columnTypeMap = New.hashMap();
1197 }
1198
1199 private void writeSchema(PrintWriter writer) {
1200 writer.println("---- Schema ----");
1201 Collections.sort(schema);
1202 for (MetaRecord m : schema) {
1203 if (!isSchemaObjectTypeDelayed(m)) {
1204 // create, but not referential integrity constraints and so on
1205 // because they could fail on duplicate keys
1206 String sql = m.getSQL();
1207 writer.println(sql + ";");
1208 }
1209 }
1210 // first, copy the lob storage (if there is any)
1211 // must occur before copying data,
1212 // otherwise the lob storage may be overwritten
1213 boolean deleteLobs = false;
1214 for (Map.Entry<Integer, String> entry : tableMap.entrySet()) {
1215 Integer objectId = entry.getKey();
1216 String name = entry.getValue();
1217 if (objectIdSet.contains(objectId)) {
1218 if (name.startsWith("INFORMATION_SCHEMA.LOB")) {
1219 setStorage(objectId);
1220 writer.println("DELETE FROM " + name + ";");
1221 writer.println("INSERT INTO " + name + " SELECT * FROM " + storageName + ";");
1222 if (name.startsWith("INFORMATION_SCHEMA.LOBS")) {
1223 writer.println("UPDATE " + name + " SET TABLE = " + LobStorage.TABLE_TEMP + ";");
1224 deleteLobs = true;
1225 }
1226 }
1227 }
1228 }
1229 for (Map.Entry<Integer, String> entry : tableMap.entrySet()) {
1230 Integer objectId = entry.getKey();
1231 String name = entry.getValue();
1232 if (objectIdSet.contains(objectId)) {
1233 setStorage(objectId);
1234 if (name.startsWith("INFORMATION_SCHEMA.LOB")) {
1235 continue;
1236 }
1237 writer.println("INSERT INTO " + name + " SELECT * FROM " + storageName + ";");
1238 }
1239 }
1240 for (Integer objectId : objectIdSet) {
1241 setStorage(objectId);
1242 writer.println("DROP TABLE " + storageName + ";");
1243 }
1244 writer.println("DROP ALIAS READ_BLOB;");
1245 writer.println("DROP ALIAS READ_CLOB;");
1246 writer.println("DROP ALIAS READ_BLOB_DB;");
1247 writer.println("DROP ALIAS READ_CLOB_DB;");
1248 if (deleteLobs) {
1249 writer.println("DELETE FROM INFORMATION_SCHEMA.LOBS WHERE TABLE = " + LobStorage.TABLE_TEMP + ";");
1250 }
1251 for (MetaRecord m : schema) {
1252 if (isSchemaObjectTypeDelayed(m)) {
1253 String sql = m.getSQL();
1254 writer.println(sql + ";");
1255 }
1256 }
1257 }
1258
1259 private static boolean isSchemaObjectTypeDelayed(MetaRecord m) {
1260 switch (m.getObjectType()) {
1261 case DbObject.INDEX:
1262 case DbObject.CONSTRAINT:
1263 case DbObject.TRIGGER:
1264 return true;
1265 }
1266 return false;
1267 }
1268
1269 private void createTemporaryTable(PrintWriter writer) {
1270 if (!objectIdSet.contains(storageId)) {
1271 objectIdSet.add(storageId);
1272 StatementBuilder buff = new StatementBuilder("CREATE TABLE ");
1273 buff.append(storageName).append('(');
1274 for (int i = 0; i < recordLength; i++) {
1275 buff.appendExceptFirst(", ");
1276 buff.append('C').append(i).append(' ');
1277 String columnType = columnTypeMap.get(storageName + "." + i);
1278 if (columnType == null) {
1279 buff.append("VARCHAR");
1280 } else {
1281 buff.append(columnType);
1282 }
1283 }
1284 writer.println(buff.append(");").toString());
1285 writer.flush();
1286 }
1287 }
1288
1289 private static String extractTableOrViewName(String sql) {
1290 int indexTable = sql.indexOf(" TABLE ");
1291 int indexView = sql.indexOf(" VIEW ");
1292 if (indexTable > 0 && indexView > 0) {
1293 if (indexTable < indexView) {
1294 indexView = -1;
1295 } else {
1296 indexTable = -1;
1297 }
1298 }
1299 if (indexView > 0) {
1300 sql = sql.substring(indexView + " VIEW ".length());
1301 } else if (indexTable > 0) {
1302 sql = sql.substring(indexTable + " TABLE ".length());
1303 } else {
1304 return "UNKNOWN";
1305 }
1306 if (sql.startsWith("IF NOT EXISTS ")) {
1307 sql = sql.substring("IF NOT EXISTS ".length());
1308 }
1309 boolean ignore = false;
1310 // sql is modified in the loop
1311 for (int i = 0; i < sql.length(); i++) {
1312 char ch = sql.charAt(i);
1313 if (ch == '\"') {
1314 ignore = !ignore;
1315 } else if (!ignore && (ch <= ' ' || ch == '(')) {
1316 sql = sql.substring(0, i);
1317 return sql;
1318 }
1319 }
1320 return "UNKNOWN";
1321 }
1322
1323
1324 private static void closeSilently(FileStore fileStore) {
1325 if (fileStore != null) {
1326 fileStore.closeSilently();
1327 }
1328 }
1329
1330 private void writeError(PrintWriter writer, Throwable e) {
1331 if (writer != null) {
1332 writer.println("// error: " + e);
1333 }
1334 traceError("Error", e);
1335 }
1336
1337 /**
1338 * INTERNAL
1339 */
1340 public String getDatabasePath() {
1341 return databaseName;
1342 }
1343
1344 /**
1345 * INTERNAL
1346 */
1347 public FileStore openFile(String name, String mode, boolean mustExist) {
1348 return FileStore.open(this, name, "rw");
1349 }
1350
1351 /**
1352 * INTERNAL
1353 */
1354 public void checkPowerOff() {
1355 // nothing to do
1356 }
1357
1358 /**
1359 * INTERNAL
1360 */
1361 public void checkWritingAllowed() {
1362 // nothing to do
1363 }
1364
1365 /**
1366 * INTERNAL
1367 */
1368 public int getMaxLengthInplaceLob() {
1369 throw DbException.throwInternalError();
1370 }
1371
1372 /**
1373 * INTERNAL
1374 */
1375 public String getLobCompressionAlgorithm(int type) {
1376 return null;
1377 }
1378
1379 /**
1380 * INTERNAL
1381 */
1382 public Object getLobSyncObject() {
1383 return this;
1384 }
1385
1386 /**
1387 * INTERNAL
1388 */
1389 public SmallLRUCache<String, String[]> getLobFileListCache() {
1390 return null;
1391 }
1392
1393 /**
1394 * INTERNAL
1395 */
1396 public TempFileDeleter getTempFileDeleter() {
1397 return TempFileDeleter.getInstance();
1398 }
1399
1400 /**
1401 * INTERNAL
1402 */
1403 public LobStorage getLobStorage() {
1404 return null;
1405 }
1406
1407 /**
1408 * INTERNAL
1409 */
1410 public Connection getLobConnection() {
1411 return null;
1412 }
1413
1414 /**
1415 * INTERNAL
1416 */
1417 public int readLob(long lobId, byte[] hmac, long offset, byte[] buff, int off, int length) {
1418 throw DbException.throwInternalError();
1419 }
1420
1421 }
1422