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 | */ |
6 | package org.h2.engine; |
7 | |
8 | import java.io.IOException; |
9 | import java.sql.SQLException; |
10 | import java.util.ArrayList; |
11 | import java.util.Collections; |
12 | import java.util.HashMap; |
13 | import java.util.HashSet; |
14 | import java.util.Properties; |
15 | import java.util.Set; |
16 | import java.util.StringTokenizer; |
17 | import org.h2.api.DatabaseEventListener; |
18 | import org.h2.api.ErrorCode; |
19 | import org.h2.api.JavaObjectSerializer; |
20 | import org.h2.command.CommandInterface; |
21 | import org.h2.command.ddl.CreateTableData; |
22 | import org.h2.command.dml.SetTypes; |
23 | import org.h2.constraint.Constraint; |
24 | import org.h2.index.Cursor; |
25 | import org.h2.index.Index; |
26 | import org.h2.index.IndexType; |
27 | import org.h2.jdbc.JdbcConnection; |
28 | import org.h2.message.DbException; |
29 | import org.h2.message.Trace; |
30 | import org.h2.message.TraceSystem; |
31 | import org.h2.mvstore.db.MVTableEngine; |
32 | import org.h2.result.Row; |
33 | import org.h2.result.SearchRow; |
34 | import org.h2.schema.Schema; |
35 | import org.h2.schema.SchemaObject; |
36 | import org.h2.schema.Sequence; |
37 | import org.h2.schema.TriggerObject; |
38 | import org.h2.store.DataHandler; |
39 | import org.h2.store.FileLock; |
40 | import org.h2.store.FileStore; |
41 | import org.h2.store.InDoubtTransaction; |
42 | import org.h2.store.LobStorageBackend; |
43 | import org.h2.store.LobStorageFrontend; |
44 | import org.h2.store.LobStorageInterface; |
45 | import org.h2.store.LobStorageMap; |
46 | import org.h2.store.PageStore; |
47 | import org.h2.store.WriterThread; |
48 | import org.h2.store.fs.FileUtils; |
49 | import org.h2.table.Column; |
50 | import org.h2.table.IndexColumn; |
51 | import org.h2.table.MetaTable; |
52 | import org.h2.table.Table; |
53 | import org.h2.table.TableLinkConnection; |
54 | import org.h2.table.TableView; |
55 | import org.h2.tools.DeleteDbFiles; |
56 | import org.h2.tools.Server; |
57 | import org.h2.util.BitField; |
58 | import org.h2.util.JdbcUtils; |
59 | import org.h2.util.MathUtils; |
60 | import org.h2.util.NetUtils; |
61 | import org.h2.util.New; |
62 | import org.h2.util.SmallLRUCache; |
63 | import org.h2.util.SourceCompiler; |
64 | import org.h2.util.StringUtils; |
65 | import org.h2.util.TempFileDeleter; |
66 | import org.h2.util.Utils; |
67 | import org.h2.value.CaseInsensitiveMap; |
68 | import org.h2.value.CompareMode; |
69 | import org.h2.value.Value; |
70 | import org.h2.value.ValueInt; |
71 | |
72 | /** |
73 | * There is one database object per open database. |
74 | * |
75 | * The format of the meta data table is: |
76 | * id int, 0, objectType int, sql varchar |
77 | * |
78 | * @since 2004-04-15 22:49 |
79 | */ |
80 | public class Database implements DataHandler { |
81 | |
82 | private static int initialPowerOffCount; |
83 | |
84 | /** |
85 | * The default name of the system user. This name is only used as long as |
86 | * there is no administrator user registered. |
87 | */ |
88 | private static final String SYSTEM_USER_NAME = "DBA"; |
89 | |
90 | private final boolean persistent; |
91 | private final String databaseName; |
92 | private final String databaseShortName; |
93 | private final String databaseURL; |
94 | private final String cipher; |
95 | private final byte[] filePasswordHash; |
96 | private final byte[] fileEncryptionKey; |
97 | |
98 | private final HashMap<String, Role> roles = New.hashMap(); |
99 | private final HashMap<String, User> users = New.hashMap(); |
100 | private final HashMap<String, Setting> settings = New.hashMap(); |
101 | private final HashMap<String, Schema> schemas = New.hashMap(); |
102 | private final HashMap<String, Right> rights = New.hashMap(); |
103 | private final HashMap<String, UserDataType> userDataTypes = New.hashMap(); |
104 | private final HashMap<String, UserAggregate> aggregates = New.hashMap(); |
105 | private final HashMap<String, Comment> comments = New.hashMap(); |
106 | |
107 | private final Set<Session> userSessions = |
108 | Collections.synchronizedSet(new HashSet<Session>()); |
109 | private Session exclusiveSession; |
110 | private final BitField objectIds = new BitField(); |
111 | private final Object lobSyncObject = new Object(); |
112 | |
113 | private Schema mainSchema; |
114 | private Schema infoSchema; |
115 | private int nextSessionId; |
116 | private int nextTempTableId; |
117 | private User systemUser; |
118 | private Session systemSession; |
119 | private Session lobSession; |
120 | private Table meta; |
121 | private Index metaIdIndex; |
122 | private FileLock lock; |
123 | private WriterThread writer; |
124 | private boolean starting; |
125 | private TraceSystem traceSystem; |
126 | private Trace trace; |
127 | private final int fileLockMethod; |
128 | private Role publicRole; |
129 | private long modificationDataId; |
130 | private long modificationMetaId; |
131 | private CompareMode compareMode; |
132 | private String cluster = Constants.CLUSTERING_DISABLED; |
133 | private boolean readOnly; |
134 | private int writeDelay = Constants.DEFAULT_WRITE_DELAY; |
135 | private DatabaseEventListener eventListener; |
136 | private int maxMemoryRows = SysProperties.MAX_MEMORY_ROWS; |
137 | private int maxMemoryUndo = Constants.DEFAULT_MAX_MEMORY_UNDO; |
138 | private int lockMode = Constants.DEFAULT_LOCK_MODE; |
139 | private int maxLengthInplaceLob; |
140 | private int allowLiterals = Constants.ALLOW_LITERALS_ALL; |
141 | |
142 | private int powerOffCount = initialPowerOffCount; |
143 | private int closeDelay; |
144 | private DatabaseCloser delayedCloser; |
145 | private volatile boolean closing; |
146 | private boolean ignoreCase; |
147 | private boolean deleteFilesOnDisconnect; |
148 | private String lobCompressionAlgorithm; |
149 | private boolean optimizeReuseResults = true; |
150 | private final String cacheType; |
151 | private final String accessModeData; |
152 | private boolean referentialIntegrity = true; |
153 | private boolean multiVersion; |
154 | private DatabaseCloser closeOnExit; |
155 | private Mode mode = Mode.getInstance(Mode.REGULAR); |
156 | private boolean multiThreaded; |
157 | private int maxOperationMemory = |
158 | Constants.DEFAULT_MAX_OPERATION_MEMORY; |
159 | private SmallLRUCache<String, String[]> lobFileListCache; |
160 | private final boolean autoServerMode; |
161 | private final int autoServerPort; |
162 | private Server server; |
163 | private HashMap<TableLinkConnection, TableLinkConnection> linkConnections; |
164 | private final TempFileDeleter tempFileDeleter = TempFileDeleter.getInstance(); |
165 | private PageStore pageStore; |
166 | private Properties reconnectLastLock; |
167 | private volatile long reconnectCheckNext; |
168 | private volatile boolean reconnectChangePending; |
169 | private volatile int checkpointAllowed; |
170 | private volatile boolean checkpointRunning; |
171 | private final Object reconnectSync = new Object(); |
172 | private int cacheSize; |
173 | private int compactMode; |
174 | private SourceCompiler compiler; |
175 | private volatile boolean metaTablesInitialized; |
176 | private boolean flushOnEachCommit; |
177 | private LobStorageInterface lobStorage; |
178 | private final int pageSize; |
179 | private int defaultTableType = Table.TYPE_CACHED; |
180 | private final DbSettings dbSettings; |
181 | private final int reconnectCheckDelay; |
182 | private int logMode; |
183 | private MVTableEngine.Store mvStore; |
184 | private int retentionTime; |
185 | private DbException backgroundException; |
186 | private JavaObjectSerializer javaObjectSerializer; |
187 | private String javaObjectSerializerName; |
188 | private volatile boolean javaObjectSerializerInitialized; |
189 | private boolean queryStatistics; |
190 | private QueryStatisticsData queryStatisticsData; |
191 | |
192 | public Database(ConnectionInfo ci, String cipher) { |
193 | String name = ci.getName(); |
194 | this.dbSettings = ci.getDbSettings(); |
195 | this.reconnectCheckDelay = dbSettings.reconnectCheckDelay; |
196 | this.compareMode = CompareMode.getInstance(null, 0); |
197 | this.persistent = ci.isPersistent(); |
198 | this.filePasswordHash = ci.getFilePasswordHash(); |
199 | this.fileEncryptionKey = ci.getFileEncryptionKey(); |
200 | this.databaseName = name; |
201 | this.databaseShortName = parseDatabaseShortName(); |
202 | this.maxLengthInplaceLob = Constants.DEFAULT_MAX_LENGTH_INPLACE_LOB; |
203 | this.cipher = cipher; |
204 | String lockMethodName = ci.getProperty("FILE_LOCK", null); |
205 | this.accessModeData = StringUtils.toLowerEnglish( |
206 | ci.getProperty("ACCESS_MODE_DATA", "rw")); |
207 | this.autoServerMode = ci.getProperty("AUTO_SERVER", false); |
208 | this.autoServerPort = ci.getProperty("AUTO_SERVER_PORT", 0); |
209 | int defaultCacheSize = Utils.scaleForAvailableMemory( |
210 | Constants.CACHE_SIZE_DEFAULT); |
211 | this.cacheSize = |
212 | ci.getProperty("CACHE_SIZE", defaultCacheSize); |
213 | this.pageSize = ci.getProperty("PAGE_SIZE", |
214 | Constants.DEFAULT_PAGE_SIZE); |
215 | if ("r".equals(accessModeData)) { |
216 | readOnly = true; |
217 | } |
218 | if (dbSettings.mvStore && lockMethodName == null) { |
219 | if (autoServerMode) { |
220 | fileLockMethod = FileLock.LOCK_FILE; |
221 | } else { |
222 | fileLockMethod = FileLock.LOCK_FS; |
223 | } |
224 | } else { |
225 | fileLockMethod = FileLock.getFileLockMethod(lockMethodName); |
226 | } |
227 | if (dbSettings.mvStore && fileLockMethod == FileLock.LOCK_SERIALIZED) { |
228 | throw DbException.getUnsupportedException( |
229 | "MV_STORE combined with FILE_LOCK=SERIALIZED"); |
230 | } |
231 | this.databaseURL = ci.getURL(); |
232 | String listener = ci.removeProperty("DATABASE_EVENT_LISTENER", null); |
233 | if (listener != null) { |
234 | listener = StringUtils.trim(listener, true, true, "'"); |
235 | setEventListenerClass(listener); |
236 | } |
237 | String modeName = ci.removeProperty("MODE", null); |
238 | if (modeName != null) { |
239 | this.mode = Mode.getInstance(modeName); |
240 | } |
241 | this.multiVersion = |
242 | ci.getProperty("MVCC", dbSettings.mvStore); |
243 | this.logMode = |
244 | ci.getProperty("LOG", PageStore.LOG_MODE_SYNC); |
245 | this.javaObjectSerializerName = |
246 | ci.getProperty("JAVA_OBJECT_SERIALIZER", null); |
247 | this.multiThreaded = |
248 | ci.getProperty("MULTI_THREADED", false); |
249 | |
250 | boolean closeAtVmShutdown = |
251 | dbSettings.dbCloseOnExit; |
252 | int traceLevelFile = |
253 | ci.getIntProperty(SetTypes.TRACE_LEVEL_FILE, |
254 | TraceSystem.DEFAULT_TRACE_LEVEL_FILE); |
255 | int traceLevelSystemOut = |
256 | ci.getIntProperty(SetTypes.TRACE_LEVEL_SYSTEM_OUT, |
257 | TraceSystem.DEFAULT_TRACE_LEVEL_SYSTEM_OUT); |
258 | this.cacheType = StringUtils.toUpperEnglish( |
259 | ci.removeProperty("CACHE_TYPE", Constants.CACHE_TYPE_DEFAULT)); |
260 | openDatabase(traceLevelFile, traceLevelSystemOut, closeAtVmShutdown); |
261 | } |
262 | |
263 | private void openDatabase(int traceLevelFile, int traceLevelSystemOut, |
264 | boolean closeAtVmShutdown) { |
265 | try { |
266 | open(traceLevelFile, traceLevelSystemOut); |
267 | if (closeAtVmShutdown) { |
268 | try { |
269 | closeOnExit = new DatabaseCloser(this, 0, true); |
270 | Runtime.getRuntime().addShutdownHook(closeOnExit); |
271 | } catch (IllegalStateException e) { |
272 | // shutdown in progress - just don't register the handler |
273 | // (maybe an application wants to write something into a |
274 | // database at shutdown time) |
275 | } catch (SecurityException e) { |
276 | // applets may not do that - ignore |
277 | // Google App Engine doesn't allow |
278 | // to instantiate classes that extend Thread |
279 | } |
280 | } |
281 | } catch (Throwable e) { |
282 | if (e instanceof OutOfMemoryError) { |
283 | e.fillInStackTrace(); |
284 | } |
285 | if (traceSystem != null) { |
286 | if (e instanceof SQLException) { |
287 | SQLException e2 = (SQLException) e; |
288 | if (e2.getErrorCode() != ErrorCode. |
289 | DATABASE_ALREADY_OPEN_1) { |
290 | // only write if the database is not already in use |
291 | trace.error(e, "opening {0}", databaseName); |
292 | } |
293 | } |
294 | traceSystem.close(); |
295 | } |
296 | closeOpenFilesAndUnlock(false); |
297 | throw DbException.convert(e); |
298 | } |
299 | } |
300 | |
301 | public static void setInitialPowerOffCount(int count) { |
302 | initialPowerOffCount = count; |
303 | } |
304 | |
305 | public void setPowerOffCount(int count) { |
306 | if (powerOffCount == -1) { |
307 | return; |
308 | } |
309 | powerOffCount = count; |
310 | } |
311 | |
312 | public MVTableEngine.Store getMvStore() { |
313 | return mvStore; |
314 | } |
315 | |
316 | public void setMvStore(MVTableEngine.Store mvStore) { |
317 | this.mvStore = mvStore; |
318 | this.retentionTime = mvStore.getStore().getRetentionTime(); |
319 | } |
320 | |
321 | /** |
322 | * Check if two values are equal with the current comparison mode. |
323 | * |
324 | * @param a the first value |
325 | * @param b the second value |
326 | * @return true if both objects are equal |
327 | */ |
328 | public boolean areEqual(Value a, Value b) { |
329 | // can not use equals because ValueDecimal 0.0 is not equal to 0.00. |
330 | return a.compareTo(b, compareMode) == 0; |
331 | } |
332 | |
333 | /** |
334 | * Compare two values with the current comparison mode. The values may not |
335 | * be of the same type. |
336 | * |
337 | * @param a the first value |
338 | * @param b the second value |
339 | * @return 0 if both values are equal, -1 if the first value is smaller, and |
340 | * 1 otherwise |
341 | */ |
342 | public int compare(Value a, Value b) { |
343 | return a.compareTo(b, compareMode); |
344 | } |
345 | |
346 | /** |
347 | * Compare two values with the current comparison mode. The values must be |
348 | * of the same type. |
349 | * |
350 | * @param a the first value |
351 | * @param b the second value |
352 | * @return 0 if both values are equal, -1 if the first value is smaller, and |
353 | * 1 otherwise |
354 | */ |
355 | public int compareTypeSave(Value a, Value b) { |
356 | return a.compareTypeSave(b, compareMode); |
357 | } |
358 | |
359 | public long getModificationDataId() { |
360 | return modificationDataId; |
361 | } |
362 | |
363 | /** |
364 | * Set or reset the pending change flag in the .lock.db file. |
365 | * |
366 | * @param pending the new value of the flag |
367 | * @return true if the call was successful, |
368 | * false if another connection was faster |
369 | */ |
370 | private synchronized boolean reconnectModified(boolean pending) { |
371 | if (readOnly || lock == null || |
372 | fileLockMethod != FileLock.LOCK_SERIALIZED) { |
373 | return true; |
374 | } |
375 | try { |
376 | if (pending == reconnectChangePending) { |
377 | long now = System.currentTimeMillis(); |
378 | if (now > reconnectCheckNext) { |
379 | if (pending) { |
380 | String pos = pageStore == null ? |
381 | null : "" + pageStore.getWriteCountTotal(); |
382 | lock.setProperty("logPos", pos); |
383 | lock.save(); |
384 | } |
385 | reconnectCheckNext = now + reconnectCheckDelay; |
386 | } |
387 | return true; |
388 | } |
389 | Properties old = lock.load(); |
390 | if (pending) { |
391 | if (old.getProperty("changePending") != null) { |
392 | return false; |
393 | } |
394 | trace.debug("wait before writing"); |
395 | Thread.sleep((int) (reconnectCheckDelay * 1.1)); |
396 | Properties now = lock.load(); |
397 | if (!now.equals(old)) { |
398 | // somebody else was faster |
399 | return false; |
400 | } |
401 | } |
402 | String pos = pageStore == null ? |
403 | null : "" + pageStore.getWriteCountTotal(); |
404 | lock.setProperty("logPos", pos); |
405 | if (pending) { |
406 | lock.setProperty("changePending", "true-" + Math.random()); |
407 | } else { |
408 | lock.setProperty("changePending", null); |
409 | } |
410 | // ensure that the writer thread will |
411 | // not reset the flag before we are done |
412 | reconnectCheckNext = System.currentTimeMillis() + |
413 | 2 * reconnectCheckDelay; |
414 | old = lock.save(); |
415 | if (pending) { |
416 | trace.debug("wait before writing again"); |
417 | Thread.sleep((int) (reconnectCheckDelay * 1.1)); |
418 | Properties now = lock.load(); |
419 | if (!now.equals(old)) { |
420 | // somebody else was faster |
421 | return false; |
422 | } |
423 | } else { |
424 | Thread.sleep(1); |
425 | } |
426 | reconnectLastLock = old; |
427 | reconnectChangePending = pending; |
428 | reconnectCheckNext = System.currentTimeMillis() + |
429 | reconnectCheckDelay; |
430 | return true; |
431 | } catch (Exception e) { |
432 | trace.error(e, "pending {0}", pending); |
433 | return false; |
434 | } |
435 | } |
436 | |
437 | public long getNextModificationDataId() { |
438 | return ++modificationDataId; |
439 | } |
440 | |
441 | public long getModificationMetaId() { |
442 | return modificationMetaId; |
443 | } |
444 | |
445 | public long getNextModificationMetaId() { |
446 | // if the meta data has been modified, the data is modified as well |
447 | // (because MetaTable returns modificationDataId) |
448 | modificationDataId++; |
449 | return modificationMetaId++; |
450 | } |
451 | |
452 | public int getPowerOffCount() { |
453 | return powerOffCount; |
454 | } |
455 | |
456 | @Override |
457 | public void checkPowerOff() { |
458 | if (powerOffCount == 0) { |
459 | return; |
460 | } |
461 | if (powerOffCount > 1) { |
462 | powerOffCount--; |
463 | return; |
464 | } |
465 | if (powerOffCount != -1) { |
466 | try { |
467 | powerOffCount = -1; |
468 | stopWriter(); |
469 | if (mvStore != null) { |
470 | mvStore.closeImmediately(); |
471 | } |
472 | if (pageStore != null) { |
473 | try { |
474 | pageStore.close(); |
475 | } catch (DbException e) { |
476 | // ignore |
477 | } |
478 | pageStore = null; |
479 | } |
480 | if (lock != null) { |
481 | stopServer(); |
482 | if (fileLockMethod != FileLock.LOCK_SERIALIZED) { |
483 | // allow testing shutdown |
484 | lock.unlock(); |
485 | } |
486 | lock = null; |
487 | } |
488 | if (traceSystem != null) { |
489 | traceSystem.close(); |
490 | } |
491 | } catch (DbException e) { |
492 | DbException.traceThrowable(e); |
493 | } |
494 | } |
495 | Engine.getInstance().close(databaseName); |
496 | throw DbException.get(ErrorCode.DATABASE_IS_CLOSED); |
497 | } |
498 | |
499 | /** |
500 | * Check if a database with the given name exists. |
501 | * |
502 | * @param name the name of the database (including path) |
503 | * @return true if one exists |
504 | */ |
505 | static boolean exists(String name) { |
506 | if (FileUtils.exists(name + Constants.SUFFIX_PAGE_FILE)) { |
507 | return true; |
508 | } |
509 | return FileUtils.exists(name + Constants.SUFFIX_MV_FILE); |
510 | } |
511 | |
512 | /** |
513 | * Get the trace object for the given module. |
514 | * |
515 | * @param module the module name |
516 | * @return the trace object |
517 | */ |
518 | public Trace getTrace(String module) { |
519 | return traceSystem.getTrace(module); |
520 | } |
521 | |
522 | @Override |
523 | public FileStore openFile(String name, String openMode, boolean mustExist) { |
524 | if (mustExist && !FileUtils.exists(name)) { |
525 | throw DbException.get(ErrorCode.FILE_NOT_FOUND_1, name); |
526 | } |
527 | FileStore store = FileStore.open(this, name, openMode, cipher, |
528 | filePasswordHash); |
529 | try { |
530 | store.init(); |
531 | } catch (DbException e) { |
532 | store.closeSilently(); |
533 | throw e; |
534 | } |
535 | return store; |
536 | } |
537 | |
538 | /** |
539 | * Check if the file password hash is correct. |
540 | * |
541 | * @param testCipher the cipher algorithm |
542 | * @param testHash the hash code |
543 | * @return true if the cipher algorithm and the password match |
544 | */ |
545 | boolean validateFilePasswordHash(String testCipher, byte[] testHash) { |
546 | if (!StringUtils.equals(testCipher, this.cipher)) { |
547 | return false; |
548 | } |
549 | return Utils.compareSecure(testHash, filePasswordHash); |
550 | } |
551 | |
552 | private String parseDatabaseShortName() { |
553 | String n = databaseName; |
554 | if (n.endsWith(":")) { |
555 | n = null; |
556 | } |
557 | if (n != null) { |
558 | StringTokenizer tokenizer = new StringTokenizer(n, "/\\:,;"); |
559 | while (tokenizer.hasMoreTokens()) { |
560 | n = tokenizer.nextToken(); |
561 | } |
562 | } |
563 | if (n == null || n.length() == 0) { |
564 | n = "unnamed"; |
565 | } |
566 | return dbSettings.databaseToUpper ? StringUtils.toUpperEnglish(n) : n; |
567 | } |
568 | |
569 | private synchronized void open(int traceLevelFile, int traceLevelSystemOut) { |
570 | if (persistent) { |
571 | String dataFileName = databaseName + Constants.SUFFIX_OLD_DATABASE_FILE; |
572 | boolean existsData = FileUtils.exists(dataFileName); |
573 | String pageFileName = databaseName + Constants.SUFFIX_PAGE_FILE; |
574 | String mvFileName = databaseName + Constants.SUFFIX_MV_FILE; |
575 | boolean existsPage = FileUtils.exists(pageFileName); |
576 | boolean existsMv = FileUtils.exists(mvFileName); |
577 | if (existsData && (!existsPage && !existsMv)) { |
578 | throw DbException.get( |
579 | ErrorCode.FILE_VERSION_ERROR_1, "Old database: " + |
580 | dataFileName + |
581 | " - please convert the database " + |
582 | "to a SQL script and re-create it."); |
583 | } |
584 | if (existsPage && !FileUtils.canWrite(pageFileName)) { |
585 | readOnly = true; |
586 | } |
587 | if (existsMv && !FileUtils.canWrite(mvFileName)) { |
588 | readOnly = true; |
589 | } |
590 | if (existsPage && !existsMv) { |
591 | dbSettings.mvStore = false; |
592 | } |
593 | if (readOnly) { |
594 | if (traceLevelFile >= TraceSystem.DEBUG) { |
595 | String traceFile = Utils.getProperty("java.io.tmpdir", ".") + |
596 | "/" + "h2_" + System.currentTimeMillis(); |
597 | traceSystem = new TraceSystem(traceFile + |
598 | Constants.SUFFIX_TRACE_FILE); |
599 | } else { |
600 | traceSystem = new TraceSystem(null); |
601 | } |
602 | } else { |
603 | traceSystem = new TraceSystem(databaseName + |
604 | Constants.SUFFIX_TRACE_FILE); |
605 | } |
606 | traceSystem.setLevelFile(traceLevelFile); |
607 | traceSystem.setLevelSystemOut(traceLevelSystemOut); |
608 | trace = traceSystem.getTrace(Trace.DATABASE); |
609 | trace.info("opening {0} (build {1})", databaseName, Constants.BUILD_ID); |
610 | if (autoServerMode) { |
611 | if (readOnly || |
612 | fileLockMethod == FileLock.LOCK_NO || |
613 | fileLockMethod == FileLock.LOCK_SERIALIZED || |
614 | fileLockMethod == FileLock.LOCK_FS || |
615 | !persistent) { |
616 | throw DbException.getUnsupportedException( |
617 | "autoServerMode && (readOnly || fileLockMethod == NO" + |
618 | " || fileLockMethod == SERIALIZED || inMemory)"); |
619 | } |
620 | } |
621 | String lockFileName = databaseName + Constants.SUFFIX_LOCK_FILE; |
622 | if (readOnly) { |
623 | if (FileUtils.exists(lockFileName)) { |
624 | throw DbException.get(ErrorCode.DATABASE_ALREADY_OPEN_1, |
625 | "Lock file exists: " + lockFileName); |
626 | } |
627 | } |
628 | if (!readOnly && fileLockMethod != FileLock.LOCK_NO) { |
629 | if (fileLockMethod != FileLock.LOCK_FS) { |
630 | lock = new FileLock(traceSystem, lockFileName, Constants.LOCK_SLEEP); |
631 | lock.lock(fileLockMethod); |
632 | if (autoServerMode) { |
633 | startServer(lock.getUniqueId()); |
634 | } |
635 | } |
636 | } |
637 | if (SysProperties.MODIFY_ON_WRITE) { |
638 | while (isReconnectNeeded()) { |
639 | // wait until others stopped writing |
640 | } |
641 | } else { |
642 | while (isReconnectNeeded() && !beforeWriting()) { |
643 | // wait until others stopped writing and |
644 | // until we can write (the file is not yet open - |
645 | // no need to re-connect) |
646 | } |
647 | } |
648 | deleteOldTempFiles(); |
649 | starting = true; |
650 | if (SysProperties.MODIFY_ON_WRITE) { |
651 | try { |
652 | getPageStore(); |
653 | } catch (DbException e) { |
654 | if (e.getErrorCode() != ErrorCode.DATABASE_IS_READ_ONLY) { |
655 | throw e; |
656 | } |
657 | pageStore = null; |
658 | while (!beforeWriting()) { |
659 | // wait until others stopped writing and |
660 | // until we can write (the file is not yet open - |
661 | // no need to re-connect) |
662 | } |
663 | getPageStore(); |
664 | } |
665 | } else { |
666 | getPageStore(); |
667 | } |
668 | starting = false; |
669 | if (mvStore == null) { |
670 | writer = WriterThread.create(this, writeDelay); |
671 | } else { |
672 | setWriteDelay(writeDelay); |
673 | } |
674 | } else { |
675 | if (autoServerMode) { |
676 | throw DbException.getUnsupportedException( |
677 | "autoServerMode && inMemory"); |
678 | } |
679 | traceSystem = new TraceSystem(null); |
680 | trace = traceSystem.getTrace(Trace.DATABASE); |
681 | if (dbSettings.mvStore) { |
682 | getPageStore(); |
683 | } |
684 | } |
685 | systemUser = new User(this, 0, SYSTEM_USER_NAME, true); |
686 | mainSchema = new Schema(this, 0, Constants.SCHEMA_MAIN, systemUser, true); |
687 | infoSchema = new Schema(this, -1, "INFORMATION_SCHEMA", systemUser, true); |
688 | schemas.put(mainSchema.getName(), mainSchema); |
689 | schemas.put(infoSchema.getName(), infoSchema); |
690 | publicRole = new Role(this, 0, Constants.PUBLIC_ROLE_NAME, true); |
691 | roles.put(Constants.PUBLIC_ROLE_NAME, publicRole); |
692 | systemUser.setAdmin(true); |
693 | systemSession = new Session(this, systemUser, ++nextSessionId); |
694 | lobSession = new Session(this, systemUser, ++nextSessionId); |
695 | CreateTableData data = new CreateTableData(); |
696 | ArrayList<Column> cols = data.columns; |
697 | Column columnId = new Column("ID", Value.INT); |
698 | columnId.setNullable(false); |
699 | cols.add(columnId); |
700 | cols.add(new Column("HEAD", Value.INT)); |
701 | cols.add(new Column("TYPE", Value.INT)); |
702 | cols.add(new Column("SQL", Value.STRING)); |
703 | boolean create = true; |
704 | if (pageStore != null) { |
705 | create = pageStore.isNew(); |
706 | } |
707 | data.tableName = "SYS"; |
708 | data.id = 0; |
709 | data.temporary = false; |
710 | data.persistData = persistent; |
711 | data.persistIndexes = persistent; |
712 | data.create = create; |
713 | data.isHidden = true; |
714 | data.session = systemSession; |
715 | meta = mainSchema.createTable(data); |
716 | IndexColumn[] pkCols = IndexColumn.wrap(new Column[] { columnId }); |
717 | metaIdIndex = meta.addIndex(systemSession, "SYS_ID", |
718 | 0, pkCols, IndexType.createPrimaryKey( |
719 | false, false), true, null); |
720 | objectIds.set(0); |
721 | starting = true; |
722 | Cursor cursor = metaIdIndex.find(systemSession, null, null); |
723 | ArrayList<MetaRecord> records = New.arrayList(); |
724 | while (cursor.next()) { |
725 | MetaRecord rec = new MetaRecord(cursor.get()); |
726 | objectIds.set(rec.getId()); |
727 | records.add(rec); |
728 | } |
729 | Collections.sort(records); |
730 | synchronized (systemSession) { |
731 | for (MetaRecord rec : records) { |
732 | rec.execute(this, systemSession, eventListener); |
733 | } |
734 | } |
735 | if (mvStore != null) { |
736 | mvStore.initTransactions(); |
737 | mvStore.removeTemporaryMaps(objectIds); |
738 | } |
739 | recompileInvalidViews(systemSession); |
740 | starting = false; |
741 | if (!readOnly) { |
742 | // set CREATE_BUILD in a new database |
743 | String name = SetTypes.getTypeName(SetTypes.CREATE_BUILD); |
744 | if (settings.get(name) == null) { |
745 | Setting setting = new Setting(this, allocateObjectId(), name); |
746 | setting.setIntValue(Constants.BUILD_ID); |
747 | lockMeta(systemSession); |
748 | addDatabaseObject(systemSession, setting); |
749 | } |
750 | // mark all ids used in the page store |
751 | if (pageStore != null) { |
752 | BitField f = pageStore.getObjectIds(); |
753 | for (int i = 0, len = f.length(); i < len; i++) { |
754 | if (f.get(i) && !objectIds.get(i)) { |
755 | trace.info("unused object id: " + i); |
756 | objectIds.set(i); |
757 | } |
758 | } |
759 | } |
760 | } |
761 | getLobStorage().init(); |
762 | systemSession.commit(true); |
763 | |
764 | trace.info("opened {0}", databaseName); |
765 | if (checkpointAllowed > 0) { |
766 | afterWriting(); |
767 | } |
768 | } |
769 | |
770 | private void startServer(String key) { |
771 | try { |
772 | server = Server.createTcpServer( |
773 | "-tcpPort", Integer.toString(autoServerPort), |
774 | "-tcpAllowOthers", |
775 | "-tcpDaemon", |
776 | "-key", key, databaseName); |
777 | server.start(); |
778 | } catch (SQLException e) { |
779 | throw DbException.convert(e); |
780 | } |
781 | String localAddress = NetUtils.getLocalAddress(); |
782 | String address = localAddress + ":" + server.getPort(); |
783 | lock.setProperty("server", address); |
784 | String hostName = NetUtils.getHostName(localAddress); |
785 | lock.setProperty("hostName", hostName); |
786 | lock.save(); |
787 | } |
788 | |
789 | private void stopServer() { |
790 | if (server != null) { |
791 | Server s = server; |
792 | // avoid calling stop recursively |
793 | // because stopping the server will |
794 | // try to close the database as well |
795 | server = null; |
796 | s.stop(); |
797 | } |
798 | } |
799 | |
800 | private void recompileInvalidViews(Session session) { |
801 | boolean recompileSuccessful; |
802 | do { |
803 | recompileSuccessful = false; |
804 | for (Table obj : getAllTablesAndViews(false)) { |
805 | if (obj instanceof TableView) { |
806 | TableView view = (TableView) obj; |
807 | if (view.isInvalid()) { |
808 | view.recompile(session, true); |
809 | if (!view.isInvalid()) { |
810 | recompileSuccessful = true; |
811 | } |
812 | } |
813 | } |
814 | } |
815 | } while (recompileSuccessful); |
816 | // when opening a database, views are initialized before indexes, |
817 | // so they may not have the optimal plan yet |
818 | // this is not a problem, it is just nice to see the newest plan |
819 | for (Table obj : getAllTablesAndViews(false)) { |
820 | if (obj instanceof TableView) { |
821 | TableView view = (TableView) obj; |
822 | if (!view.isInvalid()) { |
823 | view.recompile(systemSession, true); |
824 | } |
825 | } |
826 | } |
827 | } |
828 | |
829 | private void initMetaTables() { |
830 | if (metaTablesInitialized) { |
831 | return; |
832 | } |
833 | synchronized (infoSchema) { |
834 | if (!metaTablesInitialized) { |
835 | for (int type = 0, count = MetaTable.getMetaTableTypeCount(); |
836 | type < count; type++) { |
837 | MetaTable m = new MetaTable(infoSchema, -1 - type, type); |
838 | infoSchema.add(m); |
839 | } |
840 | metaTablesInitialized = true; |
841 | } |
842 | } |
843 | } |
844 | |
845 | private synchronized void addMeta(Session session, DbObject obj) { |
846 | int id = obj.getId(); |
847 | if (id > 0 && !starting && !obj.isTemporary()) { |
848 | Row r = meta.getTemplateRow(); |
849 | MetaRecord rec = new MetaRecord(obj); |
850 | rec.setRecord(r); |
851 | objectIds.set(id); |
852 | if (SysProperties.CHECK) { |
853 | verifyMetaLocked(session); |
854 | } |
855 | meta.addRow(session, r); |
856 | if (isMultiVersion()) { |
857 | // TODO this should work without MVCC, but avoid risks at the |
858 | // moment |
859 | session.log(meta, UndoLogRecord.INSERT, r); |
860 | } |
861 | } |
862 | } |
863 | |
864 | /** |
865 | * Verify the meta table is locked. |
866 | * |
867 | * @param session the session |
868 | */ |
869 | public void verifyMetaLocked(Session session) { |
870 | if (meta != null && !meta.isLockedExclusivelyBy(session) |
871 | && lockMode != Constants.LOCK_MODE_OFF) { |
872 | throw DbException.throwInternalError(); |
873 | } |
874 | } |
875 | |
876 | /** |
877 | * Lock the metadata table for updates. |
878 | * |
879 | * @param session the session |
880 | * @return whether it was already locked before by this session |
881 | */ |
882 | public boolean lockMeta(Session session) { |
883 | // this method can not be synchronized on the database object, |
884 | // as unlocking is also synchronized on the database object - |
885 | // so if locking starts just before unlocking, locking could |
886 | // never be successful |
887 | if (meta == null) { |
888 | return true; |
889 | } |
890 | boolean wasLocked = meta.lock(session, true, true); |
891 | return wasLocked; |
892 | } |
893 | |
894 | /** |
895 | * Remove the given object from the meta data. |
896 | * |
897 | * @param session the session |
898 | * @param id the id of the object to remove |
899 | */ |
900 | public synchronized void removeMeta(Session session, int id) { |
901 | if (id > 0 && !starting) { |
902 | SearchRow r = meta.getTemplateSimpleRow(false); |
903 | r.setValue(0, ValueInt.get(id)); |
904 | boolean wasLocked = lockMeta(session); |
905 | Cursor cursor = metaIdIndex.find(session, r, r); |
906 | if (cursor.next()) { |
907 | if (SysProperties.CHECK) { |
908 | if (lockMode != Constants.LOCK_MODE_OFF && !wasLocked) { |
909 | throw DbException.throwInternalError(); |
910 | } |
911 | } |
912 | Row found = cursor.get(); |
913 | meta.removeRow(session, found); |
914 | if (isMultiVersion()) { |
915 | // TODO this should work without MVCC, but avoid risks at |
916 | // the moment |
917 | session.log(meta, UndoLogRecord.DELETE, found); |
918 | } |
919 | objectIds.clear(id); |
920 | if (SysProperties.CHECK) { |
921 | checkMetaFree(session, id); |
922 | } |
923 | } else if (!wasLocked) { |
924 | // must not keep the lock if it was not locked |
925 | // otherwise updating sequences may cause a deadlock |
926 | meta.unlock(session); |
927 | session.unlock(meta); |
928 | } |
929 | } |
930 | } |
931 | |
932 | @SuppressWarnings("unchecked") |
933 | private HashMap<String, DbObject> getMap(int type) { |
934 | HashMap<String, ? extends DbObject> result; |
935 | switch (type) { |
936 | case DbObject.USER: |
937 | result = users; |
938 | break; |
939 | case DbObject.SETTING: |
940 | result = settings; |
941 | break; |
942 | case DbObject.ROLE: |
943 | result = roles; |
944 | break; |
945 | case DbObject.RIGHT: |
946 | result = rights; |
947 | break; |
948 | case DbObject.SCHEMA: |
949 | result = schemas; |
950 | break; |
951 | case DbObject.USER_DATATYPE: |
952 | result = userDataTypes; |
953 | break; |
954 | case DbObject.COMMENT: |
955 | result = comments; |
956 | break; |
957 | case DbObject.AGGREGATE: |
958 | result = aggregates; |
959 | break; |
960 | default: |
961 | throw DbException.throwInternalError("type=" + type); |
962 | } |
963 | return (HashMap<String, DbObject>) result; |
964 | } |
965 | |
966 | /** |
967 | * Add a schema object to the database. |
968 | * |
969 | * @param session the session |
970 | * @param obj the object to add |
971 | */ |
972 | public synchronized void addSchemaObject(Session session, SchemaObject obj) { |
973 | int id = obj.getId(); |
974 | if (id > 0 && !starting) { |
975 | checkWritingAllowed(); |
976 | } |
977 | lockMeta(session); |
978 | obj.getSchema().add(obj); |
979 | addMeta(session, obj); |
980 | } |
981 | |
982 | /** |
983 | * Add an object to the database. |
984 | * |
985 | * @param session the session |
986 | * @param obj the object to add |
987 | */ |
988 | public synchronized void addDatabaseObject(Session session, DbObject obj) { |
989 | int id = obj.getId(); |
990 | if (id > 0 && !starting) { |
991 | checkWritingAllowed(); |
992 | } |
993 | HashMap<String, DbObject> map = getMap(obj.getType()); |
994 | if (obj.getType() == DbObject.USER) { |
995 | User user = (User) obj; |
996 | if (user.isAdmin() && systemUser.getName().equals(SYSTEM_USER_NAME)) { |
997 | systemUser.rename(user.getName()); |
998 | } |
999 | } |
1000 | String name = obj.getName(); |
1001 | if (SysProperties.CHECK && map.get(name) != null) { |
1002 | DbException.throwInternalError("object already exists"); |
1003 | } |
1004 | lockMeta(session); |
1005 | addMeta(session, obj); |
1006 | map.put(name, obj); |
1007 | } |
1008 | |
1009 | /** |
1010 | * Get the user defined aggregate function if it exists, or null if not. |
1011 | * |
1012 | * @param name the name of the user defined aggregate function |
1013 | * @return the aggregate function or null |
1014 | */ |
1015 | public UserAggregate findAggregate(String name) { |
1016 | return aggregates.get(name); |
1017 | } |
1018 | |
1019 | /** |
1020 | * Get the comment for the given database object if one exists, or null if |
1021 | * not. |
1022 | * |
1023 | * @param object the database object |
1024 | * @return the comment or null |
1025 | */ |
1026 | public Comment findComment(DbObject object) { |
1027 | if (object.getType() == DbObject.COMMENT) { |
1028 | return null; |
1029 | } |
1030 | String key = Comment.getKey(object); |
1031 | return comments.get(key); |
1032 | } |
1033 | |
1034 | /** |
1035 | * Get the role if it exists, or null if not. |
1036 | * |
1037 | * @param roleName the name of the role |
1038 | * @return the role or null |
1039 | */ |
1040 | public Role findRole(String roleName) { |
1041 | return roles.get(roleName); |
1042 | } |
1043 | |
1044 | /** |
1045 | * Get the schema if it exists, or null if not. |
1046 | * |
1047 | * @param schemaName the name of the schema |
1048 | * @return the schema or null |
1049 | */ |
1050 | public Schema findSchema(String schemaName) { |
1051 | Schema schema = schemas.get(schemaName); |
1052 | if (schema == infoSchema) { |
1053 | initMetaTables(); |
1054 | } |
1055 | return schema; |
1056 | } |
1057 | |
1058 | /** |
1059 | * Get the setting if it exists, or null if not. |
1060 | * |
1061 | * @param name the name of the setting |
1062 | * @return the setting or null |
1063 | */ |
1064 | public Setting findSetting(String name) { |
1065 | return settings.get(name); |
1066 | } |
1067 | |
1068 | /** |
1069 | * Get the user if it exists, or null if not. |
1070 | * |
1071 | * @param name the name of the user |
1072 | * @return the user or null |
1073 | */ |
1074 | public User findUser(String name) { |
1075 | return users.get(name); |
1076 | } |
1077 | |
1078 | /** |
1079 | * Get the user defined data type if it exists, or null if not. |
1080 | * |
1081 | * @param name the name of the user defined data type |
1082 | * @return the user defined data type or null |
1083 | */ |
1084 | public UserDataType findUserDataType(String name) { |
1085 | return userDataTypes.get(name); |
1086 | } |
1087 | |
1088 | /** |
1089 | * Get user with the given name. This method throws an exception if the user |
1090 | * does not exist. |
1091 | * |
1092 | * @param name the user name |
1093 | * @return the user |
1094 | * @throws DbException if the user does not exist |
1095 | */ |
1096 | public User getUser(String name) { |
1097 | User user = findUser(name); |
1098 | if (user == null) { |
1099 | throw DbException.get(ErrorCode.USER_NOT_FOUND_1, name); |
1100 | } |
1101 | return user; |
1102 | } |
1103 | |
1104 | /** |
1105 | * Create a session for the given user. |
1106 | * |
1107 | * @param user the user |
1108 | * @return the session |
1109 | * @throws DbException if the database is in exclusive mode |
1110 | */ |
1111 | synchronized Session createSession(User user) { |
1112 | if (exclusiveSession != null) { |
1113 | throw DbException.get(ErrorCode.DATABASE_IS_IN_EXCLUSIVE_MODE); |
1114 | } |
1115 | Session session = new Session(this, user, ++nextSessionId); |
1116 | userSessions.add(session); |
1117 | trace.info("connecting session #{0} to {1}", session.getId(), databaseName); |
1118 | if (delayedCloser != null) { |
1119 | delayedCloser.reset(); |
1120 | delayedCloser = null; |
1121 | } |
1122 | return session; |
1123 | } |
1124 | |
1125 | /** |
1126 | * Remove a session. This method is called after the user has disconnected. |
1127 | * |
1128 | * @param session the session |
1129 | */ |
1130 | public synchronized void removeSession(Session session) { |
1131 | if (session != null) { |
1132 | if (exclusiveSession == session) { |
1133 | exclusiveSession = null; |
1134 | } |
1135 | userSessions.remove(session); |
1136 | if (session != systemSession && session != lobSession) { |
1137 | trace.info("disconnecting session #{0}", session.getId()); |
1138 | } |
1139 | } |
1140 | if (userSessions.size() == 0 && |
1141 | session != systemSession && session != lobSession) { |
1142 | if (closeDelay == 0) { |
1143 | close(false); |
1144 | } else if (closeDelay < 0) { |
1145 | return; |
1146 | } else { |
1147 | delayedCloser = new DatabaseCloser(this, closeDelay * 1000, false); |
1148 | delayedCloser.setName("H2 Close Delay " + getShortName()); |
1149 | delayedCloser.setDaemon(true); |
1150 | delayedCloser.start(); |
1151 | } |
1152 | } |
1153 | if (session != systemSession && |
1154 | session != lobSession && session != null) { |
1155 | trace.info("disconnected session #{0}", session.getId()); |
1156 | } |
1157 | } |
1158 | |
1159 | private synchronized void closeAllSessionsException(Session except) { |
1160 | Session[] all = new Session[userSessions.size()]; |
1161 | userSessions.toArray(all); |
1162 | for (Session s : all) { |
1163 | if (s != except) { |
1164 | try { |
1165 | // must roll back, otherwise the session is removed and |
1166 | // the transaction log that contains its uncommitted |
1167 | // operations as well |
1168 | s.rollback(); |
1169 | s.close(); |
1170 | } catch (DbException e) { |
1171 | trace.error(e, "disconnecting session #{0}", s.getId()); |
1172 | } |
1173 | } |
1174 | } |
1175 | } |
1176 | |
1177 | /** |
1178 | * Close the database. |
1179 | * |
1180 | * @param fromShutdownHook true if this method is called from the shutdown |
1181 | * hook |
1182 | */ |
1183 | synchronized void close(boolean fromShutdownHook) { |
1184 | if (closing) { |
1185 | return; |
1186 | } |
1187 | throwLastBackgroundException(); |
1188 | if (fileLockMethod == FileLock.LOCK_SERIALIZED && |
1189 | !reconnectChangePending) { |
1190 | // another connection may have written something - don't write |
1191 | try { |
1192 | closeOpenFilesAndUnlock(false); |
1193 | } catch (DbException e) { |
1194 | // ignore |
1195 | } |
1196 | traceSystem.close(); |
1197 | Engine.getInstance().close(databaseName); |
1198 | return; |
1199 | } |
1200 | closing = true; |
1201 | stopServer(); |
1202 | if (userSessions.size() > 0) { |
1203 | if (!fromShutdownHook) { |
1204 | return; |
1205 | } |
1206 | trace.info("closing {0} from shutdown hook", databaseName); |
1207 | closeAllSessionsException(null); |
1208 | } |
1209 | trace.info("closing {0}", databaseName); |
1210 | if (eventListener != null) { |
1211 | // allow the event listener to connect to the database |
1212 | closing = false; |
1213 | DatabaseEventListener e = eventListener; |
1214 | // set it to null, to make sure it's called only once |
1215 | eventListener = null; |
1216 | e.closingDatabase(); |
1217 | if (userSessions.size() > 0) { |
1218 | // if a connection was opened, we can't close the database |
1219 | return; |
1220 | } |
1221 | closing = true; |
1222 | } |
1223 | removeOrphanedLobs(); |
1224 | try { |
1225 | if (systemSession != null) { |
1226 | if (powerOffCount != -1) { |
1227 | for (Table table : getAllTablesAndViews(false)) { |
1228 | if (table.isGlobalTemporary()) { |
1229 | table.removeChildrenAndResources(systemSession); |
1230 | } else { |
1231 | table.close(systemSession); |
1232 | } |
1233 | } |
1234 | for (SchemaObject obj : getAllSchemaObjects( |
1235 | DbObject.SEQUENCE)) { |
1236 | Sequence sequence = (Sequence) obj; |
1237 | sequence.close(); |
1238 | } |
1239 | } |
1240 | for (SchemaObject obj : getAllSchemaObjects( |
1241 | DbObject.TRIGGER)) { |
1242 | TriggerObject trigger = (TriggerObject) obj; |
1243 | try { |
1244 | trigger.close(); |
1245 | } catch (SQLException e) { |
1246 | trace.error(e, "close"); |
1247 | } |
1248 | } |
1249 | if (powerOffCount != -1) { |
1250 | meta.close(systemSession); |
1251 | systemSession.commit(true); |
1252 | } |
1253 | } |
1254 | } catch (DbException e) { |
1255 | trace.error(e, "close"); |
1256 | } |
1257 | tempFileDeleter.deleteAll(); |
1258 | try { |
1259 | closeOpenFilesAndUnlock(true); |
1260 | } catch (DbException e) { |
1261 | trace.error(e, "close"); |
1262 | } |
1263 | trace.info("closed"); |
1264 | traceSystem.close(); |
1265 | if (closeOnExit != null) { |
1266 | closeOnExit.reset(); |
1267 | try { |
1268 | Runtime.getRuntime().removeShutdownHook(closeOnExit); |
1269 | } catch (IllegalStateException e) { |
1270 | // ignore |
1271 | } catch (SecurityException e) { |
1272 | // applets may not do that - ignore |
1273 | } |
1274 | closeOnExit = null; |
1275 | } |
1276 | Engine.getInstance().close(databaseName); |
1277 | if (deleteFilesOnDisconnect && persistent) { |
1278 | deleteFilesOnDisconnect = false; |
1279 | try { |
1280 | String directory = FileUtils.getParent(databaseName); |
1281 | String name = FileUtils.getName(databaseName); |
1282 | DeleteDbFiles.execute(directory, name, true); |
1283 | } catch (Exception e) { |
1284 | // ignore (the trace is closed already) |
1285 | } |
1286 | } |
1287 | } |
1288 | |
1289 | private void removeOrphanedLobs() { |
1290 | // remove all session variables and temporary lobs |
1291 | if (!persistent) { |
1292 | return; |
1293 | } |
1294 | boolean lobStorageIsUsed = infoSchema.findTableOrView( |
1295 | systemSession, LobStorageBackend.LOB_DATA_TABLE) != null; |
1296 | lobStorageIsUsed |= mvStore != null; |
1297 | if (!lobStorageIsUsed) { |
1298 | return; |
1299 | } |
1300 | try { |
1301 | getLobStorage(); |
1302 | lobStorage.removeAllForTable( |
1303 | LobStorageFrontend.TABLE_ID_SESSION_VARIABLE); |
1304 | } catch (DbException e) { |
1305 | trace.error(e, "close"); |
1306 | } |
1307 | } |
1308 | |
1309 | private void stopWriter() { |
1310 | if (writer != null) { |
1311 | writer.stopThread(); |
1312 | writer = null; |
1313 | } |
1314 | } |
1315 | |
1316 | /** |
1317 | * Close all open files and unlock the database. |
1318 | * |
1319 | * @param flush whether writing is allowed |
1320 | */ |
1321 | private synchronized void closeOpenFilesAndUnlock(boolean flush) { |
1322 | stopWriter(); |
1323 | if (pageStore != null) { |
1324 | if (flush) { |
1325 | try { |
1326 | pageStore.checkpoint(); |
1327 | if (!readOnly) { |
1328 | lockMeta(pageStore.getPageStoreSession()); |
1329 | pageStore.compact(compactMode); |
1330 | } |
1331 | } catch (DbException e) { |
1332 | if (SysProperties.CHECK2) { |
1333 | int code = e.getErrorCode(); |
1334 | if (code != ErrorCode.DATABASE_IS_CLOSED && |
1335 | code != ErrorCode.LOCK_TIMEOUT_1 && |
1336 | code != ErrorCode.IO_EXCEPTION_2) { |
1337 | e.printStackTrace(); |
1338 | } |
1339 | } |
1340 | trace.error(e, "close"); |
1341 | } catch (Throwable t) { |
1342 | if (SysProperties.CHECK2) { |
1343 | t.printStackTrace(); |
1344 | } |
1345 | trace.error(t, "close"); |
1346 | } |
1347 | } |
1348 | } |
1349 | reconnectModified(false); |
1350 | if (mvStore != null) { |
1351 | long maxCompactTime = dbSettings.maxCompactTime; |
1352 | if (compactMode == CommandInterface.SHUTDOWN_COMPACT) { |
1353 | mvStore.compactFile(dbSettings.maxCompactTime); |
1354 | } else if (compactMode == CommandInterface.SHUTDOWN_DEFRAG) { |
1355 | maxCompactTime = Long.MAX_VALUE; |
1356 | } else if (getSettings().defragAlways) { |
1357 | maxCompactTime = Long.MAX_VALUE; |
1358 | } |
1359 | mvStore.close(maxCompactTime); |
1360 | } |
1361 | closeFiles(); |
1362 | if (persistent && lock == null && |
1363 | fileLockMethod != FileLock.LOCK_NO && |
1364 | fileLockMethod != FileLock.LOCK_FS) { |
1365 | // everything already closed (maybe in checkPowerOff) |
1366 | // don't delete temp files in this case because |
1367 | // the database could be open now (even from within another process) |
1368 | return; |
1369 | } |
1370 | if (persistent) { |
1371 | deleteOldTempFiles(); |
1372 | } |
1373 | if (systemSession != null) { |
1374 | systemSession.close(); |
1375 | systemSession = null; |
1376 | } |
1377 | if (lobSession != null) { |
1378 | lobSession.close(); |
1379 | lobSession = null; |
1380 | } |
1381 | if (lock != null) { |
1382 | if (fileLockMethod == FileLock.LOCK_SERIALIZED) { |
1383 | // wait before deleting the .lock file, |
1384 | // otherwise other connections can not detect that |
1385 | if (lock.load().containsKey("changePending")) { |
1386 | try { |
1387 | Thread.sleep((int) (reconnectCheckDelay * 1.1)); |
1388 | } catch (InterruptedException e) { |
1389 | trace.error(e, "close"); |
1390 | } |
1391 | } |
1392 | } |
1393 | lock.unlock(); |
1394 | lock = null; |
1395 | } |
1396 | } |
1397 | |
1398 | private synchronized void closeFiles() { |
1399 | try { |
1400 | if (mvStore != null) { |
1401 | mvStore.closeImmediately(); |
1402 | } |
1403 | if (pageStore != null) { |
1404 | pageStore.close(); |
1405 | pageStore = null; |
1406 | } |
1407 | } catch (DbException e) { |
1408 | trace.error(e, "close"); |
1409 | } |
1410 | } |
1411 | |
1412 | private void checkMetaFree(Session session, int id) { |
1413 | SearchRow r = meta.getTemplateSimpleRow(false); |
1414 | r.setValue(0, ValueInt.get(id)); |
1415 | Cursor cursor = metaIdIndex.find(session, r, r); |
1416 | if (cursor.next()) { |
1417 | DbException.throwInternalError(); |
1418 | } |
1419 | } |
1420 | |
1421 | /** |
1422 | * Allocate a new object id. |
1423 | * |
1424 | * @return the id |
1425 | */ |
1426 | public synchronized int allocateObjectId() { |
1427 | int i = objectIds.nextClearBit(0); |
1428 | objectIds.set(i); |
1429 | return i; |
1430 | } |
1431 | |
1432 | public ArrayList<UserAggregate> getAllAggregates() { |
1433 | return New.arrayList(aggregates.values()); |
1434 | } |
1435 | |
1436 | public ArrayList<Comment> getAllComments() { |
1437 | return New.arrayList(comments.values()); |
1438 | } |
1439 | |
1440 | public int getAllowLiterals() { |
1441 | if (starting) { |
1442 | return Constants.ALLOW_LITERALS_ALL; |
1443 | } |
1444 | return allowLiterals; |
1445 | } |
1446 | |
1447 | public ArrayList<Right> getAllRights() { |
1448 | return New.arrayList(rights.values()); |
1449 | } |
1450 | |
1451 | public ArrayList<Role> getAllRoles() { |
1452 | return New.arrayList(roles.values()); |
1453 | } |
1454 | |
1455 | /** |
1456 | * Get all schema objects. |
1457 | * |
1458 | * @return all objects of all types |
1459 | */ |
1460 | public ArrayList<SchemaObject> getAllSchemaObjects() { |
1461 | initMetaTables(); |
1462 | ArrayList<SchemaObject> list = New.arrayList(); |
1463 | for (Schema schema : schemas.values()) { |
1464 | list.addAll(schema.getAll()); |
1465 | } |
1466 | return list; |
1467 | } |
1468 | |
1469 | /** |
1470 | * Get all schema objects of the given type. |
1471 | * |
1472 | * @param type the object type |
1473 | * @return all objects of that type |
1474 | */ |
1475 | public ArrayList<SchemaObject> getAllSchemaObjects(int type) { |
1476 | if (type == DbObject.TABLE_OR_VIEW) { |
1477 | initMetaTables(); |
1478 | } |
1479 | ArrayList<SchemaObject> list = New.arrayList(); |
1480 | for (Schema schema : schemas.values()) { |
1481 | list.addAll(schema.getAll(type)); |
1482 | } |
1483 | return list; |
1484 | } |
1485 | |
1486 | /** |
1487 | * Get all tables and views. |
1488 | * |
1489 | * @param includeMeta whether to force including the meta data tables (if |
1490 | * true, metadata tables are always included; if false, metadata |
1491 | * tables are only included if they are already initialized) |
1492 | * @return all objects of that type |
1493 | */ |
1494 | public ArrayList<Table> getAllTablesAndViews(boolean includeMeta) { |
1495 | if (includeMeta) { |
1496 | initMetaTables(); |
1497 | } |
1498 | ArrayList<Table> list = New.arrayList(); |
1499 | for (Schema schema : schemas.values()) { |
1500 | list.addAll(schema.getAllTablesAndViews()); |
1501 | } |
1502 | return list; |
1503 | } |
1504 | |
1505 | public ArrayList<Schema> getAllSchemas() { |
1506 | initMetaTables(); |
1507 | return New.arrayList(schemas.values()); |
1508 | } |
1509 | |
1510 | public ArrayList<Setting> getAllSettings() { |
1511 | return New.arrayList(settings.values()); |
1512 | } |
1513 | |
1514 | public ArrayList<UserDataType> getAllUserDataTypes() { |
1515 | return New.arrayList(userDataTypes.values()); |
1516 | } |
1517 | |
1518 | public ArrayList<User> getAllUsers() { |
1519 | return New.arrayList(users.values()); |
1520 | } |
1521 | |
1522 | public String getCacheType() { |
1523 | return cacheType; |
1524 | } |
1525 | |
1526 | public String getCluster() { |
1527 | return cluster; |
1528 | } |
1529 | |
1530 | public CompareMode getCompareMode() { |
1531 | return compareMode; |
1532 | } |
1533 | |
1534 | @Override |
1535 | public String getDatabasePath() { |
1536 | if (persistent) { |
1537 | return FileUtils.toRealPath(databaseName); |
1538 | } |
1539 | return null; |
1540 | } |
1541 | |
1542 | public String getShortName() { |
1543 | return databaseShortName; |
1544 | } |
1545 | |
1546 | public String getName() { |
1547 | return databaseName; |
1548 | } |
1549 | |
1550 | /** |
1551 | * Get all sessions that are currently connected to the database. |
1552 | * |
1553 | * @param includingSystemSession if the system session should also be |
1554 | * included |
1555 | * @return the list of sessions |
1556 | */ |
1557 | public Session[] getSessions(boolean includingSystemSession) { |
1558 | ArrayList<Session> list; |
1559 | // need to synchronized on userSession, otherwise the list |
1560 | // may contain null elements |
1561 | synchronized (userSessions) { |
1562 | list = New.arrayList(userSessions); |
1563 | } |
1564 | // copy, to ensure the reference is stable |
1565 | Session sys = systemSession; |
1566 | Session lob = lobSession; |
1567 | if (includingSystemSession && sys != null) { |
1568 | list.add(sys); |
1569 | } |
1570 | if (includingSystemSession && lob != null) { |
1571 | list.add(lob); |
1572 | } |
1573 | Session[] array = new Session[list.size()]; |
1574 | list.toArray(array); |
1575 | return array; |
1576 | } |
1577 | |
1578 | /** |
1579 | * Update an object in the system table. |
1580 | * |
1581 | * @param session the session |
1582 | * @param obj the database object |
1583 | */ |
1584 | public synchronized void updateMeta(Session session, DbObject obj) { |
1585 | lockMeta(session); |
1586 | int id = obj.getId(); |
1587 | removeMeta(session, id); |
1588 | addMeta(session, obj); |
1589 | } |
1590 | |
1591 | /** |
1592 | * Rename a schema object. |
1593 | * |
1594 | * @param session the session |
1595 | * @param obj the object |
1596 | * @param newName the new name |
1597 | */ |
1598 | public synchronized void renameSchemaObject(Session session, |
1599 | SchemaObject obj, String newName) { |
1600 | checkWritingAllowed(); |
1601 | obj.getSchema().rename(obj, newName); |
1602 | updateMetaAndFirstLevelChildren(session, obj); |
1603 | } |
1604 | |
1605 | private synchronized void updateMetaAndFirstLevelChildren(Session session, DbObject obj) { |
1606 | ArrayList<DbObject> list = obj.getChildren(); |
1607 | Comment comment = findComment(obj); |
1608 | if (comment != null) { |
1609 | DbException.throwInternalError(); |
1610 | } |
1611 | updateMeta(session, obj); |
1612 | // remember that this scans only one level deep! |
1613 | if (list != null) { |
1614 | for (DbObject o : list) { |
1615 | if (o.getCreateSQL() != null) { |
1616 | updateMeta(session, o); |
1617 | } |
1618 | } |
1619 | } |
1620 | } |
1621 | |
1622 | /** |
1623 | * Rename a database object. |
1624 | * |
1625 | * @param session the session |
1626 | * @param obj the object |
1627 | * @param newName the new name |
1628 | */ |
1629 | public synchronized void renameDatabaseObject(Session session, |
1630 | DbObject obj, String newName) { |
1631 | checkWritingAllowed(); |
1632 | int type = obj.getType(); |
1633 | HashMap<String, DbObject> map = getMap(type); |
1634 | if (SysProperties.CHECK) { |
1635 | if (!map.containsKey(obj.getName())) { |
1636 | DbException.throwInternalError("not found: " + obj.getName()); |
1637 | } |
1638 | if (obj.getName().equals(newName) || map.containsKey(newName)) { |
1639 | DbException.throwInternalError("object already exists: " + newName); |
1640 | } |
1641 | } |
1642 | obj.checkRename(); |
1643 | int id = obj.getId(); |
1644 | lockMeta(session); |
1645 | removeMeta(session, id); |
1646 | map.remove(obj.getName()); |
1647 | obj.rename(newName); |
1648 | map.put(newName, obj); |
1649 | updateMetaAndFirstLevelChildren(session, obj); |
1650 | } |
1651 | |
1652 | /** |
1653 | * Create a temporary file in the database folder. |
1654 | * |
1655 | * @return the file name |
1656 | */ |
1657 | public String createTempFile() { |
1658 | try { |
1659 | boolean inTempDir = readOnly; |
1660 | String name = databaseName; |
1661 | if (!persistent) { |
1662 | name = "memFS:" + name; |
1663 | } |
1664 | return FileUtils.createTempFile(name, |
1665 | Constants.SUFFIX_TEMP_FILE, true, inTempDir); |
1666 | } catch (IOException e) { |
1667 | throw DbException.convertIOException(e, databaseName); |
1668 | } |
1669 | } |
1670 | |
1671 | private void deleteOldTempFiles() { |
1672 | String path = FileUtils.getParent(databaseName); |
1673 | for (String name : FileUtils.newDirectoryStream(path)) { |
1674 | if (name.endsWith(Constants.SUFFIX_TEMP_FILE) && |
1675 | name.startsWith(databaseName)) { |
1676 | // can't always delete the files, they may still be open |
1677 | FileUtils.tryDelete(name); |
1678 | } |
1679 | } |
1680 | } |
1681 | |
1682 | /** |
1683 | * Get the schema. If the schema does not exist, an exception is thrown. |
1684 | * |
1685 | * @param schemaName the name of the schema |
1686 | * @return the schema |
1687 | * @throws DbException no schema with that name exists |
1688 | */ |
1689 | public Schema getSchema(String schemaName) { |
1690 | Schema schema = findSchema(schemaName); |
1691 | if (schema == null) { |
1692 | throw DbException.get(ErrorCode.SCHEMA_NOT_FOUND_1, schemaName); |
1693 | } |
1694 | return schema; |
1695 | } |
1696 | |
1697 | /** |
1698 | * Remove the object from the database. |
1699 | * |
1700 | * @param session the session |
1701 | * @param obj the object to remove |
1702 | */ |
1703 | public synchronized void removeDatabaseObject(Session session, DbObject obj) { |
1704 | checkWritingAllowed(); |
1705 | String objName = obj.getName(); |
1706 | int type = obj.getType(); |
1707 | HashMap<String, DbObject> map = getMap(type); |
1708 | if (SysProperties.CHECK && !map.containsKey(objName)) { |
1709 | DbException.throwInternalError("not found: " + objName); |
1710 | } |
1711 | Comment comment = findComment(obj); |
1712 | lockMeta(session); |
1713 | if (comment != null) { |
1714 | removeDatabaseObject(session, comment); |
1715 | } |
1716 | int id = obj.getId(); |
1717 | obj.removeChildrenAndResources(session); |
1718 | map.remove(objName); |
1719 | removeMeta(session, id); |
1720 | } |
1721 | |
1722 | /** |
1723 | * Get the first table that depends on this object. |
1724 | * |
1725 | * @param obj the object to find |
1726 | * @param except the table to exclude (or null) |
1727 | * @return the first dependent table, or null |
1728 | */ |
1729 | public Table getDependentTable(SchemaObject obj, Table except) { |
1730 | switch (obj.getType()) { |
1731 | case DbObject.COMMENT: |
1732 | case DbObject.CONSTRAINT: |
1733 | case DbObject.INDEX: |
1734 | case DbObject.RIGHT: |
1735 | case DbObject.TRIGGER: |
1736 | case DbObject.USER: |
1737 | return null; |
1738 | default: |
1739 | } |
1740 | HashSet<DbObject> set = New.hashSet(); |
1741 | for (Table t : getAllTablesAndViews(false)) { |
1742 | if (except == t) { |
1743 | continue; |
1744 | } else if (Table.VIEW.equals(t.getTableType())) { |
1745 | continue; |
1746 | } |
1747 | set.clear(); |
1748 | t.addDependencies(set); |
1749 | if (set.contains(obj)) { |
1750 | return t; |
1751 | } |
1752 | } |
1753 | return null; |
1754 | } |
1755 | |
1756 | /** |
1757 | * Remove an object from the system table. |
1758 | * |
1759 | * @param session the session |
1760 | * @param obj the object to be removed |
1761 | */ |
1762 | public synchronized void removeSchemaObject(Session session, |
1763 | SchemaObject obj) { |
1764 | int type = obj.getType(); |
1765 | if (type == DbObject.TABLE_OR_VIEW) { |
1766 | Table table = (Table) obj; |
1767 | if (table.isTemporary() && !table.isGlobalTemporary()) { |
1768 | session.removeLocalTempTable(table); |
1769 | return; |
1770 | } |
1771 | } else if (type == DbObject.INDEX) { |
1772 | Index index = (Index) obj; |
1773 | Table table = index.getTable(); |
1774 | if (table.isTemporary() && !table.isGlobalTemporary()) { |
1775 | session.removeLocalTempTableIndex(index); |
1776 | return; |
1777 | } |
1778 | } else if (type == DbObject.CONSTRAINT) { |
1779 | Constraint constraint = (Constraint) obj; |
1780 | Table table = constraint.getTable(); |
1781 | if (table.isTemporary() && !table.isGlobalTemporary()) { |
1782 | session.removeLocalTempTableConstraint(constraint); |
1783 | return; |
1784 | } |
1785 | } |
1786 | checkWritingAllowed(); |
1787 | lockMeta(session); |
1788 | Comment comment = findComment(obj); |
1789 | if (comment != null) { |
1790 | removeDatabaseObject(session, comment); |
1791 | } |
1792 | obj.getSchema().remove(obj); |
1793 | int id = obj.getId(); |
1794 | if (!starting) { |
1795 | Table t = getDependentTable(obj, null); |
1796 | if (t != null) { |
1797 | obj.getSchema().add(obj); |
1798 | throw DbException.get(ErrorCode.CANNOT_DROP_2, obj.getSQL(), |
1799 | t.getSQL()); |
1800 | } |
1801 | obj.removeChildrenAndResources(session); |
1802 | } |
1803 | removeMeta(session, id); |
1804 | } |
1805 | |
1806 | /** |
1807 | * Check if this database disk-based. |
1808 | * |
1809 | * @return true if it is disk-based, false it it is in-memory only. |
1810 | */ |
1811 | public boolean isPersistent() { |
1812 | return persistent; |
1813 | } |
1814 | |
1815 | public TraceSystem getTraceSystem() { |
1816 | return traceSystem; |
1817 | } |
1818 | |
1819 | public synchronized void setCacheSize(int kb) { |
1820 | if (starting) { |
1821 | int max = MathUtils.convertLongToInt(Utils.getMemoryMax()) / 2; |
1822 | kb = Math.min(kb, max); |
1823 | } |
1824 | cacheSize = kb; |
1825 | if (pageStore != null) { |
1826 | pageStore.getCache().setMaxMemory(kb); |
1827 | } |
1828 | if (mvStore != null) { |
1829 | mvStore.setCacheSize(Math.max(1, kb / 1024)); |
1830 | } |
1831 | } |
1832 | |
1833 | public synchronized void setMasterUser(User user) { |
1834 | lockMeta(systemSession); |
1835 | addDatabaseObject(systemSession, user); |
1836 | systemSession.commit(true); |
1837 | } |
1838 | |
1839 | public Role getPublicRole() { |
1840 | return publicRole; |
1841 | } |
1842 | |
1843 | /** |
1844 | * Get a unique temporary table name. |
1845 | * |
1846 | * @param baseName the prefix of the returned name |
1847 | * @param session the session |
1848 | * @return a unique name |
1849 | */ |
1850 | public synchronized String getTempTableName(String baseName, Session session) { |
1851 | String tempName; |
1852 | do { |
1853 | tempName = baseName + "_COPY_" + session.getId() + |
1854 | "_" + nextTempTableId++; |
1855 | } while (mainSchema.findTableOrView(session, tempName) != null); |
1856 | return tempName; |
1857 | } |
1858 | |
1859 | public void setCompareMode(CompareMode compareMode) { |
1860 | this.compareMode = compareMode; |
1861 | } |
1862 | |
1863 | public void setCluster(String cluster) { |
1864 | this.cluster = cluster; |
1865 | } |
1866 | |
1867 | @Override |
1868 | public void checkWritingAllowed() { |
1869 | if (readOnly) { |
1870 | throw DbException.get(ErrorCode.DATABASE_IS_READ_ONLY); |
1871 | } |
1872 | if (fileLockMethod == FileLock.LOCK_SERIALIZED) { |
1873 | if (!reconnectChangePending) { |
1874 | throw DbException.get(ErrorCode.DATABASE_IS_READ_ONLY); |
1875 | } |
1876 | } |
1877 | } |
1878 | |
1879 | public boolean isReadOnly() { |
1880 | return readOnly; |
1881 | } |
1882 | |
1883 | public void setWriteDelay(int value) { |
1884 | writeDelay = value; |
1885 | if (writer != null) { |
1886 | writer.setWriteDelay(value); |
1887 | // TODO check if MIN_WRITE_DELAY is a good value |
1888 | flushOnEachCommit = writeDelay < Constants.MIN_WRITE_DELAY; |
1889 | } |
1890 | if (mvStore != null) { |
1891 | int millis = value < 0 ? 0 : value; |
1892 | mvStore.getStore().setAutoCommitDelay(millis); |
1893 | } |
1894 | } |
1895 | |
1896 | public int getRetentionTime() { |
1897 | return retentionTime; |
1898 | } |
1899 | |
1900 | public void setRetentionTime(int value) { |
1901 | retentionTime = value; |
1902 | if (mvStore != null) { |
1903 | mvStore.getStore().setRetentionTime(value); |
1904 | } |
1905 | } |
1906 | |
1907 | /** |
1908 | * Check if flush-on-each-commit is enabled. |
1909 | * |
1910 | * @return true if it is |
1911 | */ |
1912 | public boolean getFlushOnEachCommit() { |
1913 | return flushOnEachCommit; |
1914 | } |
1915 | |
1916 | /** |
1917 | * Get the list of in-doubt transactions. |
1918 | * |
1919 | * @return the list |
1920 | */ |
1921 | public ArrayList<InDoubtTransaction> getInDoubtTransactions() { |
1922 | if (mvStore != null) { |
1923 | return mvStore.getInDoubtTransactions(); |
1924 | } |
1925 | return pageStore == null ? null : pageStore.getInDoubtTransactions(); |
1926 | } |
1927 | |
1928 | /** |
1929 | * Prepare a transaction. |
1930 | * |
1931 | * @param session the session |
1932 | * @param transaction the name of the transaction |
1933 | */ |
1934 | synchronized void prepareCommit(Session session, String transaction) { |
1935 | if (readOnly) { |
1936 | return; |
1937 | } |
1938 | if (mvStore != null) { |
1939 | mvStore.prepareCommit(session, transaction); |
1940 | return; |
1941 | } |
1942 | if (pageStore != null) { |
1943 | pageStore.flushLog(); |
1944 | pageStore.prepareCommit(session, transaction); |
1945 | } |
1946 | } |
1947 | |
1948 | /** |
1949 | * Commit the current transaction of the given session. |
1950 | * |
1951 | * @param session the session |
1952 | */ |
1953 | synchronized void commit(Session session) { |
1954 | throwLastBackgroundException(); |
1955 | if (readOnly) { |
1956 | return; |
1957 | } |
1958 | if (pageStore != null) { |
1959 | pageStore.commit(session); |
1960 | } |
1961 | session.setAllCommitted(); |
1962 | } |
1963 | |
1964 | private void throwLastBackgroundException() { |
1965 | if (backgroundException != null) { |
1966 | // we don't care too much about concurrency here, |
1967 | // we just want to make sure the exception is _normally_ |
1968 | // not just logged to the .trace.db file |
1969 | DbException b = backgroundException; |
1970 | backgroundException = null; |
1971 | if (b != null) { |
1972 | throw b; |
1973 | } |
1974 | } |
1975 | } |
1976 | |
1977 | public void setBackgroundException(DbException e) { |
1978 | if (backgroundException == null) { |
1979 | backgroundException = e; |
1980 | TraceSystem t = getTraceSystem(); |
1981 | if (t != null) { |
1982 | t.getTrace(Trace.DATABASE).error(e, "flush"); |
1983 | } |
1984 | } |
1985 | } |
1986 | |
1987 | /** |
1988 | * Flush all pending changes to the transaction log. |
1989 | */ |
1990 | public synchronized void flush() { |
1991 | if (readOnly) { |
1992 | return; |
1993 | } |
1994 | if (pageStore != null) { |
1995 | pageStore.flushLog(); |
1996 | } |
1997 | if (mvStore != null) { |
1998 | try { |
1999 | mvStore.flush(); |
2000 | } catch (RuntimeException e) { |
2001 | backgroundException = DbException.convert(e); |
2002 | throw e; |
2003 | } |
2004 | } |
2005 | } |
2006 | |
2007 | public void setEventListener(DatabaseEventListener eventListener) { |
2008 | this.eventListener = eventListener; |
2009 | } |
2010 | |
2011 | public void setEventListenerClass(String className) { |
2012 | if (className == null || className.length() == 0) { |
2013 | eventListener = null; |
2014 | } else { |
2015 | try { |
2016 | eventListener = (DatabaseEventListener) |
2017 | JdbcUtils.loadUserClass(className).newInstance(); |
2018 | String url = databaseURL; |
2019 | if (cipher != null) { |
2020 | url += ";CIPHER=" + cipher; |
2021 | } |
2022 | eventListener.init(url); |
2023 | } catch (Throwable e) { |
2024 | throw DbException.get( |
2025 | ErrorCode.ERROR_SETTING_DATABASE_EVENT_LISTENER_2, e, |
2026 | className, e.toString()); |
2027 | } |
2028 | } |
2029 | } |
2030 | |
2031 | /** |
2032 | * Set the progress of a long running operation. |
2033 | * This method calls the {@link DatabaseEventListener} if one is registered. |
2034 | * |
2035 | * @param state the {@link DatabaseEventListener} state |
2036 | * @param name the object name |
2037 | * @param x the current position |
2038 | * @param max the highest value |
2039 | */ |
2040 | public void setProgress(int state, String name, int x, int max) { |
2041 | if (eventListener != null) { |
2042 | try { |
2043 | eventListener.setProgress(state, name, x, max); |
2044 | } catch (Exception e2) { |
2045 | // ignore this (user made) exception |
2046 | } |
2047 | } |
2048 | } |
2049 | |
2050 | /** |
2051 | * This method is called after an exception occurred, to inform the database |
2052 | * event listener (if one is set). |
2053 | * |
2054 | * @param e the exception |
2055 | * @param sql the SQL statement |
2056 | */ |
2057 | public void exceptionThrown(SQLException e, String sql) { |
2058 | if (eventListener != null) { |
2059 | try { |
2060 | eventListener.exceptionThrown(e, sql); |
2061 | } catch (Exception e2) { |
2062 | // ignore this (user made) exception |
2063 | } |
2064 | } |
2065 | } |
2066 | |
2067 | /** |
2068 | * Synchronize the files with the file system. This method is called when |
2069 | * executing the SQL statement CHECKPOINT SYNC. |
2070 | */ |
2071 | public synchronized void sync() { |
2072 | if (readOnly) { |
2073 | return; |
2074 | } |
2075 | if (mvStore != null) { |
2076 | mvStore.sync(); |
2077 | } |
2078 | if (pageStore != null) { |
2079 | pageStore.sync(); |
2080 | } |
2081 | } |
2082 | |
2083 | public int getMaxMemoryRows() { |
2084 | return maxMemoryRows; |
2085 | } |
2086 | |
2087 | public void setMaxMemoryRows(int value) { |
2088 | this.maxMemoryRows = value; |
2089 | } |
2090 | |
2091 | public void setMaxMemoryUndo(int value) { |
2092 | this.maxMemoryUndo = value; |
2093 | } |
2094 | |
2095 | public int getMaxMemoryUndo() { |
2096 | return maxMemoryUndo; |
2097 | } |
2098 | |
2099 | public void setLockMode(int lockMode) { |
2100 | switch (lockMode) { |
2101 | case Constants.LOCK_MODE_OFF: |
2102 | if (multiThreaded) { |
2103 | // currently the combination of LOCK_MODE=0 and MULTI_THREADED |
2104 | // is not supported. also see code in |
2105 | // JdbcDatabaseMetaData#supportsTransactionIsolationLevel(int) |
2106 | throw DbException.get( |
2107 | ErrorCode.UNSUPPORTED_SETTING_COMBINATION, |
2108 | "LOCK_MODE=0 & MULTI_THREADED"); |
2109 | } |
2110 | break; |
2111 | case Constants.LOCK_MODE_READ_COMMITTED: |
2112 | case Constants.LOCK_MODE_TABLE: |
2113 | case Constants.LOCK_MODE_TABLE_GC: |
2114 | break; |
2115 | default: |
2116 | throw DbException.getInvalidValueException("lock mode", lockMode); |
2117 | } |
2118 | this.lockMode = lockMode; |
2119 | } |
2120 | |
2121 | public int getLockMode() { |
2122 | return lockMode; |
2123 | } |
2124 | |
2125 | public synchronized void setCloseDelay(int value) { |
2126 | this.closeDelay = value; |
2127 | } |
2128 | |
2129 | public Session getSystemSession() { |
2130 | return systemSession; |
2131 | } |
2132 | |
2133 | /** |
2134 | * Check if the database is in the process of closing. |
2135 | * |
2136 | * @return true if the database is closing |
2137 | */ |
2138 | public boolean isClosing() { |
2139 | return closing; |
2140 | } |
2141 | |
2142 | public void setMaxLengthInplaceLob(int value) { |
2143 | this.maxLengthInplaceLob = value; |
2144 | } |
2145 | |
2146 | @Override |
2147 | public int getMaxLengthInplaceLob() { |
2148 | return maxLengthInplaceLob; |
2149 | } |
2150 | |
2151 | public void setIgnoreCase(boolean b) { |
2152 | ignoreCase = b; |
2153 | } |
2154 | |
2155 | public boolean getIgnoreCase() { |
2156 | if (starting) { |
2157 | // tables created at startup must not be converted to ignorecase |
2158 | return false; |
2159 | } |
2160 | return ignoreCase; |
2161 | } |
2162 | |
2163 | public synchronized void setDeleteFilesOnDisconnect(boolean b) { |
2164 | this.deleteFilesOnDisconnect = b; |
2165 | } |
2166 | |
2167 | @Override |
2168 | public String getLobCompressionAlgorithm(int type) { |
2169 | return lobCompressionAlgorithm; |
2170 | } |
2171 | |
2172 | public void setLobCompressionAlgorithm(String stringValue) { |
2173 | this.lobCompressionAlgorithm = stringValue; |
2174 | } |
2175 | |
2176 | public synchronized void setMaxLogSize(long value) { |
2177 | if (pageStore != null) { |
2178 | pageStore.setMaxLogSize(value); |
2179 | } |
2180 | } |
2181 | |
2182 | public void setAllowLiterals(int value) { |
2183 | this.allowLiterals = value; |
2184 | } |
2185 | |
2186 | public boolean getOptimizeReuseResults() { |
2187 | return optimizeReuseResults; |
2188 | } |
2189 | |
2190 | public void setOptimizeReuseResults(boolean b) { |
2191 | optimizeReuseResults = b; |
2192 | } |
2193 | |
2194 | @Override |
2195 | public Object getLobSyncObject() { |
2196 | return lobSyncObject; |
2197 | } |
2198 | |
2199 | public int getSessionCount() { |
2200 | return userSessions.size(); |
2201 | } |
2202 | |
2203 | public void setReferentialIntegrity(boolean b) { |
2204 | referentialIntegrity = b; |
2205 | } |
2206 | |
2207 | public boolean getReferentialIntegrity() { |
2208 | return referentialIntegrity; |
2209 | } |
2210 | |
2211 | public void setQueryStatistics(boolean b) { |
2212 | queryStatistics = b; |
2213 | synchronized (this) { |
2214 | queryStatisticsData = null; |
2215 | } |
2216 | } |
2217 | |
2218 | public boolean getQueryStatistics() { |
2219 | return queryStatistics; |
2220 | } |
2221 | |
2222 | public QueryStatisticsData getQueryStatisticsData() { |
2223 | if (!queryStatistics) { |
2224 | return null; |
2225 | } |
2226 | if (queryStatisticsData == null) { |
2227 | synchronized (this) { |
2228 | if (queryStatisticsData == null) { |
2229 | queryStatisticsData = new QueryStatisticsData(); |
2230 | } |
2231 | } |
2232 | } |
2233 | return queryStatisticsData; |
2234 | } |
2235 | |
2236 | /** |
2237 | * Check if the database is currently opening. This is true until all stored |
2238 | * SQL statements have been executed. |
2239 | * |
2240 | * @return true if the database is still starting |
2241 | */ |
2242 | public boolean isStarting() { |
2243 | return starting; |
2244 | } |
2245 | |
2246 | /** |
2247 | * Check if multi version concurrency is enabled for this database. |
2248 | * |
2249 | * @return true if it is enabled |
2250 | */ |
2251 | public boolean isMultiVersion() { |
2252 | return multiVersion; |
2253 | } |
2254 | |
2255 | /** |
2256 | * Called after the database has been opened and initialized. This method |
2257 | * notifies the event listener if one has been set. |
2258 | */ |
2259 | void opened() { |
2260 | if (eventListener != null) { |
2261 | eventListener.opened(); |
2262 | } |
2263 | if (writer != null) { |
2264 | writer.startThread(); |
2265 | } |
2266 | } |
2267 | |
2268 | public void setMode(Mode mode) { |
2269 | this.mode = mode; |
2270 | } |
2271 | |
2272 | public Mode getMode() { |
2273 | return mode; |
2274 | } |
2275 | |
2276 | public boolean isMultiThreaded() { |
2277 | return multiThreaded; |
2278 | } |
2279 | |
2280 | public void setMultiThreaded(boolean multiThreaded) { |
2281 | if (multiThreaded && this.multiThreaded != multiThreaded) { |
2282 | if (multiVersion && mvStore == null) { |
2283 | // currently the combination of MVCC and MULTI_THREADED is not |
2284 | // supported |
2285 | throw DbException.get( |
2286 | ErrorCode.UNSUPPORTED_SETTING_COMBINATION, |
2287 | "MVCC & MULTI_THREADED"); |
2288 | } |
2289 | if (lockMode == 0) { |
2290 | // currently the combination of LOCK_MODE=0 and MULTI_THREADED |
2291 | // is not supported |
2292 | throw DbException.get( |
2293 | ErrorCode.UNSUPPORTED_SETTING_COMBINATION, |
2294 | "LOCK_MODE=0 & MULTI_THREADED"); |
2295 | } |
2296 | } |
2297 | this.multiThreaded = multiThreaded; |
2298 | } |
2299 | |
2300 | public void setMaxOperationMemory(int maxOperationMemory) { |
2301 | this.maxOperationMemory = maxOperationMemory; |
2302 | } |
2303 | |
2304 | public int getMaxOperationMemory() { |
2305 | return maxOperationMemory; |
2306 | } |
2307 | |
2308 | public Session getExclusiveSession() { |
2309 | return exclusiveSession; |
2310 | } |
2311 | |
2312 | /** |
2313 | * Set the session that can exclusively access the database. |
2314 | * |
2315 | * @param session the session |
2316 | * @param closeOthers whether other sessions are closed |
2317 | */ |
2318 | public void setExclusiveSession(Session session, boolean closeOthers) { |
2319 | this.exclusiveSession = session; |
2320 | if (closeOthers) { |
2321 | closeAllSessionsException(session); |
2322 | } |
2323 | } |
2324 | |
2325 | @Override |
2326 | public SmallLRUCache<String, String[]> getLobFileListCache() { |
2327 | if (lobFileListCache == null) { |
2328 | lobFileListCache = SmallLRUCache.newInstance(128); |
2329 | } |
2330 | return lobFileListCache; |
2331 | } |
2332 | |
2333 | /** |
2334 | * Checks if the system table (containing the catalog) is locked. |
2335 | * |
2336 | * @return true if it is currently locked |
2337 | */ |
2338 | public boolean isSysTableLocked() { |
2339 | return meta == null || meta.isLockedExclusively(); |
2340 | } |
2341 | |
2342 | /** |
2343 | * Open a new connection or get an existing connection to another database. |
2344 | * |
2345 | * @param driver the database driver or null |
2346 | * @param url the database URL |
2347 | * @param user the user name |
2348 | * @param password the password |
2349 | * @return the connection |
2350 | */ |
2351 | public TableLinkConnection getLinkConnection(String driver, String url, |
2352 | String user, String password) { |
2353 | if (linkConnections == null) { |
2354 | linkConnections = New.hashMap(); |
2355 | } |
2356 | return TableLinkConnection.open(linkConnections, driver, url, user, |
2357 | password, dbSettings.shareLinkedConnections); |
2358 | } |
2359 | |
2360 | @Override |
2361 | public String toString() { |
2362 | return databaseShortName + ":" + super.toString(); |
2363 | } |
2364 | |
2365 | /** |
2366 | * Immediately close the database. |
2367 | */ |
2368 | public void shutdownImmediately() { |
2369 | setPowerOffCount(1); |
2370 | try { |
2371 | checkPowerOff(); |
2372 | } catch (DbException e) { |
2373 | // ignore |
2374 | } |
2375 | closeFiles(); |
2376 | } |
2377 | |
2378 | @Override |
2379 | public TempFileDeleter getTempFileDeleter() { |
2380 | return tempFileDeleter; |
2381 | } |
2382 | |
2383 | public PageStore getPageStore() { |
2384 | if (dbSettings.mvStore) { |
2385 | if (mvStore == null) { |
2386 | mvStore = MVTableEngine.init(this); |
2387 | } |
2388 | return null; |
2389 | } |
2390 | if (pageStore == null) { |
2391 | pageStore = new PageStore(this, databaseName + |
2392 | Constants.SUFFIX_PAGE_FILE, accessModeData, cacheSize); |
2393 | if (pageSize != Constants.DEFAULT_PAGE_SIZE) { |
2394 | pageStore.setPageSize(pageSize); |
2395 | } |
2396 | if (!readOnly && fileLockMethod == FileLock.LOCK_FS) { |
2397 | pageStore.setLockFile(true); |
2398 | } |
2399 | pageStore.setLogMode(logMode); |
2400 | pageStore.open(); |
2401 | } |
2402 | return pageStore; |
2403 | } |
2404 | |
2405 | /** |
2406 | * Get the first user defined table. |
2407 | * |
2408 | * @return the table or null if no table is defined |
2409 | */ |
2410 | public Table getFirstUserTable() { |
2411 | for (Table table : getAllTablesAndViews(false)) { |
2412 | if (table.getCreateSQL() != null) { |
2413 | if (table.isHidden()) { |
2414 | // LOB tables |
2415 | continue; |
2416 | } |
2417 | return table; |
2418 | } |
2419 | } |
2420 | return null; |
2421 | } |
2422 | |
2423 | /** |
2424 | * Check if the contents of the database was changed and therefore it is |
2425 | * required to re-connect. This method waits until pending changes are |
2426 | * completed. If a pending change takes too long (more than 2 seconds), the |
2427 | * pending change is broken (removed from the properties file). |
2428 | * |
2429 | * @return true if reconnecting is required |
2430 | */ |
2431 | public boolean isReconnectNeeded() { |
2432 | if (fileLockMethod != FileLock.LOCK_SERIALIZED) { |
2433 | return false; |
2434 | } |
2435 | if (reconnectChangePending) { |
2436 | return false; |
2437 | } |
2438 | long now = System.currentTimeMillis(); |
2439 | if (now < reconnectCheckNext) { |
2440 | return false; |
2441 | } |
2442 | reconnectCheckNext = now + reconnectCheckDelay; |
2443 | if (lock == null) { |
2444 | lock = new FileLock(traceSystem, databaseName + |
2445 | Constants.SUFFIX_LOCK_FILE, Constants.LOCK_SLEEP); |
2446 | } |
2447 | try { |
2448 | Properties prop = lock.load(), first = prop; |
2449 | while (true) { |
2450 | if (prop.equals(reconnectLastLock)) { |
2451 | return false; |
2452 | } |
2453 | if (prop.getProperty("changePending", null) == null) { |
2454 | break; |
2455 | } |
2456 | if (System.currentTimeMillis() > |
2457 | now + reconnectCheckDelay * 10) { |
2458 | if (first.equals(prop)) { |
2459 | // the writing process didn't update the file - |
2460 | // it may have terminated |
2461 | lock.setProperty("changePending", null); |
2462 | lock.save(); |
2463 | break; |
2464 | } |
2465 | } |
2466 | trace.debug("delay (change pending)"); |
2467 | Thread.sleep(reconnectCheckDelay); |
2468 | prop = lock.load(); |
2469 | } |
2470 | reconnectLastLock = prop; |
2471 | } catch (Exception e) { |
2472 | // DbException, InterruptedException |
2473 | trace.error(e, "readOnly {0}", readOnly); |
2474 | // ignore |
2475 | } |
2476 | return true; |
2477 | } |
2478 | |
2479 | /** |
2480 | * Flush all changes when using the serialized mode, and if there are |
2481 | * pending changes, and some time has passed. This switches to a new |
2482 | * transaction log and resets the change pending flag in |
2483 | * the .lock.db file. |
2484 | */ |
2485 | public void checkpointIfRequired() { |
2486 | if (fileLockMethod != FileLock.LOCK_SERIALIZED || |
2487 | readOnly || !reconnectChangePending || closing) { |
2488 | return; |
2489 | } |
2490 | long now = System.currentTimeMillis(); |
2491 | if (now > reconnectCheckNext + reconnectCheckDelay) { |
2492 | if (SysProperties.CHECK && checkpointAllowed < 0) { |
2493 | DbException.throwInternalError(); |
2494 | } |
2495 | synchronized (reconnectSync) { |
2496 | if (checkpointAllowed > 0) { |
2497 | return; |
2498 | } |
2499 | checkpointRunning = true; |
2500 | } |
2501 | synchronized (this) { |
2502 | trace.debug("checkpoint start"); |
2503 | flushSequences(); |
2504 | checkpoint(); |
2505 | reconnectModified(false); |
2506 | trace.debug("checkpoint end"); |
2507 | } |
2508 | synchronized (reconnectSync) { |
2509 | checkpointRunning = false; |
2510 | } |
2511 | } |
2512 | } |
2513 | |
2514 | public boolean isFileLockSerialized() { |
2515 | return fileLockMethod == FileLock.LOCK_SERIALIZED; |
2516 | } |
2517 | |
2518 | private void flushSequences() { |
2519 | for (SchemaObject obj : getAllSchemaObjects(DbObject.SEQUENCE)) { |
2520 | Sequence sequence = (Sequence) obj; |
2521 | sequence.flushWithoutMargin(); |
2522 | } |
2523 | } |
2524 | |
2525 | /** |
2526 | * Flush all changes and open a new transaction log. |
2527 | */ |
2528 | public void checkpoint() { |
2529 | if (persistent) { |
2530 | synchronized (this) { |
2531 | if (pageStore != null) { |
2532 | pageStore.checkpoint(); |
2533 | } |
2534 | } |
2535 | if (mvStore != null) { |
2536 | mvStore.flush(); |
2537 | } |
2538 | } |
2539 | getTempFileDeleter().deleteUnused(); |
2540 | } |
2541 | |
2542 | /** |
2543 | * This method is called before writing to the transaction log. |
2544 | * |
2545 | * @return true if the call was successful and writing is allowed, |
2546 | * false if another connection was faster |
2547 | */ |
2548 | public boolean beforeWriting() { |
2549 | if (fileLockMethod != FileLock.LOCK_SERIALIZED) { |
2550 | return true; |
2551 | } |
2552 | while (checkpointRunning) { |
2553 | try { |
2554 | Thread.sleep(10 + (int) (Math.random() * 10)); |
2555 | } catch (Exception e) { |
2556 | // ignore InterruptedException |
2557 | } |
2558 | } |
2559 | synchronized (reconnectSync) { |
2560 | if (reconnectModified(true)) { |
2561 | checkpointAllowed++; |
2562 | if (SysProperties.CHECK && checkpointAllowed > 20) { |
2563 | throw DbException.throwInternalError(); |
2564 | } |
2565 | return true; |
2566 | } |
2567 | } |
2568 | // make sure the next call to isReconnectNeeded() returns true |
2569 | reconnectCheckNext = System.currentTimeMillis() - 1; |
2570 | reconnectLastLock = null; |
2571 | return false; |
2572 | } |
2573 | |
2574 | /** |
2575 | * This method is called after updates are finished. |
2576 | */ |
2577 | public void afterWriting() { |
2578 | if (fileLockMethod != FileLock.LOCK_SERIALIZED) { |
2579 | return; |
2580 | } |
2581 | synchronized (reconnectSync) { |
2582 | checkpointAllowed--; |
2583 | } |
2584 | if (SysProperties.CHECK && checkpointAllowed < 0) { |
2585 | throw DbException.throwInternalError(); |
2586 | } |
2587 | } |
2588 | |
2589 | /** |
2590 | * Switch the database to read-only mode. |
2591 | * |
2592 | * @param readOnly the new value |
2593 | */ |
2594 | public void setReadOnly(boolean readOnly) { |
2595 | this.readOnly = readOnly; |
2596 | } |
2597 | |
2598 | public void setCompactMode(int compactMode) { |
2599 | this.compactMode = compactMode; |
2600 | } |
2601 | |
2602 | public SourceCompiler getCompiler() { |
2603 | if (compiler == null) { |
2604 | compiler = new SourceCompiler(); |
2605 | } |
2606 | return compiler; |
2607 | } |
2608 | |
2609 | @Override |
2610 | public LobStorageInterface getLobStorage() { |
2611 | if (lobStorage == null) { |
2612 | if (dbSettings.mvStore) { |
2613 | lobStorage = new LobStorageMap(this); |
2614 | } else { |
2615 | lobStorage = new LobStorageBackend(this); |
2616 | } |
2617 | } |
2618 | return lobStorage; |
2619 | } |
2620 | |
2621 | public JdbcConnection getLobConnectionForInit() { |
2622 | String url = Constants.CONN_URL_INTERNAL; |
2623 | JdbcConnection conn = new JdbcConnection( |
2624 | systemSession, systemUser.getName(), url); |
2625 | conn.setTraceLevel(TraceSystem.OFF); |
2626 | return conn; |
2627 | } |
2628 | |
2629 | public JdbcConnection getLobConnectionForRegularUse() { |
2630 | String url = Constants.CONN_URL_INTERNAL; |
2631 | JdbcConnection conn = new JdbcConnection( |
2632 | lobSession, systemUser.getName(), url); |
2633 | conn.setTraceLevel(TraceSystem.OFF); |
2634 | return conn; |
2635 | } |
2636 | |
2637 | public Session getLobSession() { |
2638 | return lobSession; |
2639 | } |
2640 | |
2641 | public void setLogMode(int log) { |
2642 | if (log < 0 || log > 2) { |
2643 | throw DbException.getInvalidValueException("LOG", log); |
2644 | } |
2645 | if (pageStore != null) { |
2646 | if (log != PageStore.LOG_MODE_SYNC || |
2647 | pageStore.getLogMode() != PageStore.LOG_MODE_SYNC) { |
2648 | // write the log mode in the trace file when enabling or |
2649 | // disabling a dangerous mode |
2650 | trace.error(null, "log {0}", log); |
2651 | } |
2652 | this.logMode = log; |
2653 | pageStore.setLogMode(log); |
2654 | } |
2655 | if (mvStore != null) { |
2656 | this.logMode = log; |
2657 | } |
2658 | } |
2659 | |
2660 | public int getLogMode() { |
2661 | if (pageStore != null) { |
2662 | return pageStore.getLogMode(); |
2663 | } |
2664 | if (mvStore != null) { |
2665 | return logMode; |
2666 | } |
2667 | return PageStore.LOG_MODE_OFF; |
2668 | } |
2669 | |
2670 | public int getDefaultTableType() { |
2671 | return defaultTableType; |
2672 | } |
2673 | |
2674 | public void setDefaultTableType(int defaultTableType) { |
2675 | this.defaultTableType = defaultTableType; |
2676 | } |
2677 | |
2678 | public void setMultiVersion(boolean multiVersion) { |
2679 | this.multiVersion = multiVersion; |
2680 | } |
2681 | |
2682 | public DbSettings getSettings() { |
2683 | return dbSettings; |
2684 | } |
2685 | |
2686 | /** |
2687 | * Create a new hash map. Depending on the configuration, the key is case |
2688 | * sensitive or case insensitive. |
2689 | * |
2690 | * @param <V> the value type |
2691 | * @return the hash map |
2692 | */ |
2693 | public <V> HashMap<String, V> newStringMap() { |
2694 | return dbSettings.databaseToUpper ? |
2695 | new HashMap<String, V>() : |
2696 | new CaseInsensitiveMap<V>(); |
2697 | } |
2698 | |
2699 | /** |
2700 | * Compare two identifiers (table names, column names,...) and verify they |
2701 | * are equal. Case sensitivity depends on the configuration. |
2702 | * |
2703 | * @param a the first identifier |
2704 | * @param b the second identifier |
2705 | * @return true if they match |
2706 | */ |
2707 | public boolean equalsIdentifiers(String a, String b) { |
2708 | if (a == b || a.equals(b)) { |
2709 | return true; |
2710 | } |
2711 | if (!dbSettings.databaseToUpper && a.equalsIgnoreCase(b)) { |
2712 | return true; |
2713 | } |
2714 | return false; |
2715 | } |
2716 | |
2717 | @Override |
2718 | public int readLob(long lobId, byte[] hmac, long offset, byte[] buff, |
2719 | int off, int length) { |
2720 | throw DbException.throwInternalError(); |
2721 | } |
2722 | |
2723 | public byte[] getFileEncryptionKey() { |
2724 | return fileEncryptionKey; |
2725 | } |
2726 | |
2727 | public int getPageSize() { |
2728 | return pageSize; |
2729 | } |
2730 | |
2731 | @Override |
2732 | public JavaObjectSerializer getJavaObjectSerializer() { |
2733 | initJavaObjectSerializer(); |
2734 | return javaObjectSerializer; |
2735 | } |
2736 | |
2737 | private void initJavaObjectSerializer() { |
2738 | if (javaObjectSerializerInitialized) { |
2739 | return; |
2740 | } |
2741 | synchronized (this) { |
2742 | if (javaObjectSerializerInitialized) { |
2743 | return; |
2744 | } |
2745 | String serializerName = javaObjectSerializerName; |
2746 | if (serializerName != null) { |
2747 | serializerName = serializerName.trim(); |
2748 | if (!serializerName.isEmpty() && |
2749 | !serializerName.equals("null")) { |
2750 | try { |
2751 | javaObjectSerializer = (JavaObjectSerializer) |
2752 | JdbcUtils.loadUserClass(serializerName).newInstance(); |
2753 | } catch (Exception e) { |
2754 | throw DbException.convert(e); |
2755 | } |
2756 | } |
2757 | } |
2758 | javaObjectSerializerInitialized = true; |
2759 | } |
2760 | } |
2761 | |
2762 | public void setJavaObjectSerializerName(String serializerName) { |
2763 | synchronized (this) { |
2764 | javaObjectSerializerInitialized = false; |
2765 | javaObjectSerializerName = serializerName; |
2766 | } |
2767 | } |
2768 | |
2769 | } |