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.table; |
7 | |
8 | import java.util.ArrayDeque; |
9 | import java.util.ArrayList; |
10 | import java.util.Collections; |
11 | import java.util.Comparator; |
12 | import java.util.HashSet; |
13 | import java.util.Set; |
14 | import org.h2.api.DatabaseEventListener; |
15 | import org.h2.api.ErrorCode; |
16 | import org.h2.command.ddl.Analyze; |
17 | import org.h2.command.ddl.CreateTableData; |
18 | import org.h2.constraint.Constraint; |
19 | import org.h2.constraint.ConstraintReferential; |
20 | import org.h2.engine.Constants; |
21 | import org.h2.engine.DbObject; |
22 | import org.h2.engine.Session; |
23 | import org.h2.engine.SysProperties; |
24 | import org.h2.index.Cursor; |
25 | import org.h2.index.HashIndex; |
26 | import org.h2.index.Index; |
27 | import org.h2.index.IndexType; |
28 | import org.h2.index.MultiVersionIndex; |
29 | import org.h2.index.NonUniqueHashIndex; |
30 | import org.h2.index.PageBtreeIndex; |
31 | import org.h2.index.PageDataIndex; |
32 | import org.h2.index.PageDelegateIndex; |
33 | import org.h2.index.ScanIndex; |
34 | import org.h2.index.SpatialTreeIndex; |
35 | import org.h2.index.TreeIndex; |
36 | import org.h2.message.DbException; |
37 | import org.h2.message.Trace; |
38 | import org.h2.result.Row; |
39 | import org.h2.result.SortOrder; |
40 | import org.h2.schema.SchemaObject; |
41 | import org.h2.util.MathUtils; |
42 | import org.h2.util.New; |
43 | import org.h2.value.CompareMode; |
44 | import org.h2.value.DataType; |
45 | import org.h2.value.Value; |
46 | |
47 | /** |
48 | * Most tables are an instance of this class. For this table, the data is stored |
49 | * in the database. The actual data is not kept here, instead it is kept in the |
50 | * indexes. There is at least one index, the scan index. |
51 | */ |
52 | public class RegularTable extends TableBase { |
53 | |
54 | private Index scanIndex; |
55 | private long rowCount; |
56 | private volatile Session lockExclusiveSession; |
57 | private HashSet<Session> lockSharedSessions = New.hashSet(); |
58 | |
59 | /** |
60 | * The queue of sessions waiting to lock the table. It is a FIFO queue to |
61 | * prevent starvation, since Java's synchronized locking is biased. |
62 | */ |
63 | private final ArrayDeque<Session> waitingSessions = new ArrayDeque<Session>(); |
64 | private final Trace traceLock; |
65 | private final ArrayList<Index> indexes = New.arrayList(); |
66 | private long lastModificationId; |
67 | private boolean containsLargeObject; |
68 | private final PageDataIndex mainIndex; |
69 | private int changesSinceAnalyze; |
70 | private int nextAnalyze; |
71 | private Column rowIdColumn; |
72 | |
73 | public RegularTable(CreateTableData data) { |
74 | super(data); |
75 | nextAnalyze = database.getSettings().analyzeAuto; |
76 | this.isHidden = data.isHidden; |
77 | for (Column col : getColumns()) { |
78 | if (DataType.isLargeObject(col.getType())) { |
79 | containsLargeObject = true; |
80 | } |
81 | } |
82 | if (data.persistData && database.isPersistent()) { |
83 | mainIndex = new PageDataIndex(this, data.id, |
84 | IndexColumn.wrap(getColumns()), |
85 | IndexType.createScan(data.persistData), |
86 | data.create, data.session); |
87 | scanIndex = mainIndex; |
88 | } else { |
89 | mainIndex = null; |
90 | scanIndex = new ScanIndex(this, data.id, |
91 | IndexColumn.wrap(getColumns()), IndexType.createScan(data.persistData)); |
92 | } |
93 | indexes.add(scanIndex); |
94 | traceLock = database.getTrace(Trace.LOCK); |
95 | } |
96 | |
97 | @Override |
98 | public void close(Session session) { |
99 | for (Index index : indexes) { |
100 | index.close(session); |
101 | } |
102 | } |
103 | |
104 | @Override |
105 | public Row getRow(Session session, long key) { |
106 | return scanIndex.getRow(session, key); |
107 | } |
108 | |
109 | @Override |
110 | public void addRow(Session session, Row row) { |
111 | lastModificationId = database.getNextModificationDataId(); |
112 | if (database.isMultiVersion()) { |
113 | row.setSessionId(session.getId()); |
114 | } |
115 | int i = 0; |
116 | try { |
117 | for (int size = indexes.size(); i < size; i++) { |
118 | Index index = indexes.get(i); |
119 | index.add(session, row); |
120 | checkRowCount(session, index, 1); |
121 | } |
122 | rowCount++; |
123 | } catch (Throwable e) { |
124 | try { |
125 | while (--i >= 0) { |
126 | Index index = indexes.get(i); |
127 | index.remove(session, row); |
128 | checkRowCount(session, index, 0); |
129 | } |
130 | } catch (DbException e2) { |
131 | // this could happen, for example on failure in the storage |
132 | // but if that is not the case it means there is something wrong |
133 | // with the database |
134 | trace.error(e2, "could not undo operation"); |
135 | throw e2; |
136 | } |
137 | DbException de = DbException.convert(e); |
138 | if (de.getErrorCode() == ErrorCode.DUPLICATE_KEY_1) { |
139 | for (int j = 0; j < indexes.size(); j++) { |
140 | Index index = indexes.get(j); |
141 | if (index.getIndexType().isUnique() && index instanceof MultiVersionIndex) { |
142 | MultiVersionIndex mv = (MultiVersionIndex) index; |
143 | if (mv.isUncommittedFromOtherSession(session, row)) { |
144 | throw DbException.get( |
145 | ErrorCode.CONCURRENT_UPDATE_1, index.getName()); |
146 | } |
147 | } |
148 | } |
149 | } |
150 | throw de; |
151 | } |
152 | analyzeIfRequired(session); |
153 | } |
154 | |
155 | @Override |
156 | public void commit(short operation, Row row) { |
157 | lastModificationId = database.getNextModificationDataId(); |
158 | for (int i = 0, size = indexes.size(); i < size; i++) { |
159 | Index index = indexes.get(i); |
160 | index.commit(operation, row); |
161 | } |
162 | } |
163 | |
164 | private void checkRowCount(Session session, Index index, int offset) { |
165 | if (SysProperties.CHECK && !database.isMultiVersion()) { |
166 | if (!(index instanceof PageDelegateIndex)) { |
167 | long rc = index.getRowCount(session); |
168 | if (rc != rowCount + offset) { |
169 | DbException.throwInternalError( |
170 | "rowCount expected " + (rowCount + offset) + |
171 | " got " + rc + " " + getName() + "." + index.getName()); |
172 | } |
173 | } |
174 | } |
175 | } |
176 | |
177 | @Override |
178 | public Index getScanIndex(Session session) { |
179 | return indexes.get(0); |
180 | } |
181 | |
182 | @Override |
183 | public Index getUniqueIndex() { |
184 | for (Index idx : indexes) { |
185 | if (idx.getIndexType().isUnique()) { |
186 | return idx; |
187 | } |
188 | } |
189 | return null; |
190 | } |
191 | |
192 | @Override |
193 | public ArrayList<Index> getIndexes() { |
194 | return indexes; |
195 | } |
196 | |
197 | @Override |
198 | public Index addIndex(Session session, String indexName, int indexId, |
199 | IndexColumn[] cols, IndexType indexType, boolean create, |
200 | String indexComment) { |
201 | if (indexType.isPrimaryKey()) { |
202 | for (IndexColumn c : cols) { |
203 | Column column = c.column; |
204 | if (column.isNullable()) { |
205 | throw DbException.get( |
206 | ErrorCode.COLUMN_MUST_NOT_BE_NULLABLE_1, column.getName()); |
207 | } |
208 | column.setPrimaryKey(true); |
209 | } |
210 | } |
211 | boolean isSessionTemporary = isTemporary() && !isGlobalTemporary(); |
212 | if (!isSessionTemporary) { |
213 | database.lockMeta(session); |
214 | } |
215 | Index index; |
216 | if (isPersistIndexes() && indexType.isPersistent()) { |
217 | int mainIndexColumn; |
218 | if (database.isStarting() && |
219 | database.getPageStore().getRootPageId(indexId) != 0) { |
220 | mainIndexColumn = -1; |
221 | } else if (!database.isStarting() && mainIndex.getRowCount(session) != 0) { |
222 | mainIndexColumn = -1; |
223 | } else { |
224 | mainIndexColumn = getMainIndexColumn(indexType, cols); |
225 | } |
226 | if (mainIndexColumn != -1) { |
227 | mainIndex.setMainIndexColumn(mainIndexColumn); |
228 | index = new PageDelegateIndex(this, indexId, indexName, |
229 | indexType, mainIndex, create, session); |
230 | } else if (indexType.isSpatial()) { |
231 | index = new SpatialTreeIndex(this, indexId, indexName, cols, |
232 | indexType, true, create, session); |
233 | } else { |
234 | index = new PageBtreeIndex(this, indexId, indexName, cols, |
235 | indexType, create, session); |
236 | } |
237 | } else { |
238 | if (indexType.isHash()) { |
239 | if (cols.length != 1) { |
240 | throw DbException.getUnsupportedException( |
241 | "hash indexes may index only one column"); |
242 | } |
243 | if (indexType.isUnique()) { |
244 | index = new HashIndex(this, indexId, indexName, cols, |
245 | indexType); |
246 | } else { |
247 | index = new NonUniqueHashIndex(this, indexId, indexName, |
248 | cols, indexType); |
249 | } |
250 | } else if (indexType.isSpatial()) { |
251 | index = new SpatialTreeIndex(this, indexId, indexName, cols, |
252 | indexType, false, true, session); |
253 | } else { |
254 | index = new TreeIndex(this, indexId, indexName, cols, indexType); |
255 | } |
256 | } |
257 | if (database.isMultiVersion()) { |
258 | index = new MultiVersionIndex(index, this); |
259 | } |
260 | if (index.needRebuild() && rowCount > 0) { |
261 | try { |
262 | Index scan = getScanIndex(session); |
263 | long remaining = scan.getRowCount(session); |
264 | long total = remaining; |
265 | Cursor cursor = scan.find(session, null, null); |
266 | long i = 0; |
267 | int bufferSize = (int) Math.min(rowCount, database.getMaxMemoryRows()); |
268 | ArrayList<Row> buffer = New.arrayList(bufferSize); |
269 | String n = getName() + ":" + index.getName(); |
270 | int t = MathUtils.convertLongToInt(total); |
271 | while (cursor.next()) { |
272 | database.setProgress(DatabaseEventListener.STATE_CREATE_INDEX, n, |
273 | MathUtils.convertLongToInt(i++), t); |
274 | Row row = cursor.get(); |
275 | buffer.add(row); |
276 | if (buffer.size() >= bufferSize) { |
277 | addRowsToIndex(session, buffer, index); |
278 | } |
279 | remaining--; |
280 | } |
281 | addRowsToIndex(session, buffer, index); |
282 | if (SysProperties.CHECK && remaining != 0) { |
283 | DbException.throwInternalError("rowcount remaining=" + |
284 | remaining + " " + getName()); |
285 | } |
286 | } catch (DbException e) { |
287 | getSchema().freeUniqueName(indexName); |
288 | try { |
289 | index.remove(session); |
290 | } catch (DbException e2) { |
291 | // this could happen, for example on failure in the storage |
292 | // but if that is not the case it means |
293 | // there is something wrong with the database |
294 | trace.error(e2, "could not remove index"); |
295 | throw e2; |
296 | } |
297 | throw e; |
298 | } |
299 | } |
300 | index.setTemporary(isTemporary()); |
301 | if (index.getCreateSQL() != null) { |
302 | index.setComment(indexComment); |
303 | if (isSessionTemporary) { |
304 | session.addLocalTempTableIndex(index); |
305 | } else { |
306 | database.addSchemaObject(session, index); |
307 | } |
308 | } |
309 | indexes.add(index); |
310 | setModified(); |
311 | return index; |
312 | } |
313 | |
314 | private int getMainIndexColumn(IndexType indexType, IndexColumn[] cols) { |
315 | if (mainIndex.getMainIndexColumn() != -1) { |
316 | return -1; |
317 | } |
318 | if (!indexType.isPrimaryKey() || cols.length != 1) { |
319 | return -1; |
320 | } |
321 | IndexColumn first = cols[0]; |
322 | if (first.sortType != SortOrder.ASCENDING) { |
323 | return -1; |
324 | } |
325 | switch(first.column.getType()) { |
326 | case Value.BYTE: |
327 | case Value.SHORT: |
328 | case Value.INT: |
329 | case Value.LONG: |
330 | break; |
331 | default: |
332 | return -1; |
333 | } |
334 | return first.column.getColumnId(); |
335 | } |
336 | |
337 | @Override |
338 | public boolean canGetRowCount() { |
339 | return true; |
340 | } |
341 | |
342 | private static void addRowsToIndex(Session session, ArrayList<Row> list, |
343 | Index index) { |
344 | final Index idx = index; |
345 | Collections.sort(list, new Comparator<Row>() { |
346 | @Override |
347 | public int compare(Row r1, Row r2) { |
348 | return idx.compareRows(r1, r2); |
349 | } |
350 | }); |
351 | for (Row row : list) { |
352 | index.add(session, row); |
353 | } |
354 | list.clear(); |
355 | } |
356 | |
357 | @Override |
358 | public boolean canDrop() { |
359 | return true; |
360 | } |
361 | |
362 | @Override |
363 | public long getRowCount(Session session) { |
364 | if (database.isMultiVersion()) { |
365 | return getScanIndex(session).getRowCount(session); |
366 | } |
367 | return rowCount; |
368 | } |
369 | |
370 | @Override |
371 | public void removeRow(Session session, Row row) { |
372 | if (database.isMultiVersion()) { |
373 | if (row.isDeleted()) { |
374 | throw DbException.get(ErrorCode.CONCURRENT_UPDATE_1, getName()); |
375 | } |
376 | int old = row.getSessionId(); |
377 | int newId = session.getId(); |
378 | if (old == 0) { |
379 | row.setSessionId(newId); |
380 | } else if (old != newId) { |
381 | throw DbException.get(ErrorCode.CONCURRENT_UPDATE_1, getName()); |
382 | } |
383 | } |
384 | lastModificationId = database.getNextModificationDataId(); |
385 | int i = indexes.size() - 1; |
386 | try { |
387 | for (; i >= 0; i--) { |
388 | Index index = indexes.get(i); |
389 | index.remove(session, row); |
390 | checkRowCount(session, index, -1); |
391 | } |
392 | rowCount--; |
393 | } catch (Throwable e) { |
394 | try { |
395 | while (++i < indexes.size()) { |
396 | Index index = indexes.get(i); |
397 | index.add(session, row); |
398 | checkRowCount(session, index, 0); |
399 | } |
400 | } catch (DbException e2) { |
401 | // this could happen, for example on failure in the storage |
402 | // but if that is not the case it means there is something wrong |
403 | // with the database |
404 | trace.error(e2, "could not undo operation"); |
405 | throw e2; |
406 | } |
407 | throw DbException.convert(e); |
408 | } |
409 | analyzeIfRequired(session); |
410 | } |
411 | |
412 | @Override |
413 | public void truncate(Session session) { |
414 | lastModificationId = database.getNextModificationDataId(); |
415 | for (int i = indexes.size() - 1; i >= 0; i--) { |
416 | Index index = indexes.get(i); |
417 | index.truncate(session); |
418 | } |
419 | rowCount = 0; |
420 | changesSinceAnalyze = 0; |
421 | } |
422 | |
423 | private void analyzeIfRequired(Session session) { |
424 | if (nextAnalyze == 0 || nextAnalyze > changesSinceAnalyze++) { |
425 | return; |
426 | } |
427 | changesSinceAnalyze = 0; |
428 | int n = 2 * nextAnalyze; |
429 | if (n > 0) { |
430 | nextAnalyze = n; |
431 | } |
432 | int rows = session.getDatabase().getSettings().analyzeSample / 10; |
433 | Analyze.analyzeTable(session, this, rows, false); |
434 | } |
435 | |
436 | @Override |
437 | public boolean isLockedExclusivelyBy(Session session) { |
438 | return lockExclusiveSession == session; |
439 | } |
440 | |
441 | @Override |
442 | public boolean lock(Session session, boolean exclusive, |
443 | boolean forceLockEvenInMvcc) { |
444 | int lockMode = database.getLockMode(); |
445 | if (lockMode == Constants.LOCK_MODE_OFF) { |
446 | return lockExclusiveSession != null; |
447 | } |
448 | if (!forceLockEvenInMvcc && database.isMultiVersion()) { |
449 | // MVCC: update, delete, and insert use a shared lock. |
450 | // Select doesn't lock except when using FOR UPDATE |
451 | if (exclusive) { |
452 | exclusive = false; |
453 | } else { |
454 | if (lockExclusiveSession == null) { |
455 | return false; |
456 | } |
457 | } |
458 | } |
459 | if (lockExclusiveSession == session) { |
460 | return true; |
461 | } |
462 | synchronized (database) { |
463 | if (lockExclusiveSession == session) { |
464 | return true; |
465 | } |
466 | if (!exclusive && lockSharedSessions.contains(session)) { |
467 | return true; |
468 | } |
469 | session.setWaitForLock(this, Thread.currentThread()); |
470 | waitingSessions.addLast(session); |
471 | try { |
472 | doLock1(session, lockMode, exclusive); |
473 | } finally { |
474 | session.setWaitForLock(null, null); |
475 | waitingSessions.remove(session); |
476 | } |
477 | } |
478 | return false; |
479 | } |
480 | |
481 | private void doLock1(Session session, int lockMode, boolean exclusive) { |
482 | traceLock(session, exclusive, "requesting for"); |
483 | // don't get the current time unless necessary |
484 | long max = 0; |
485 | boolean checkDeadlock = false; |
486 | while (true) { |
487 | // if I'm the next one in the queue |
488 | if (waitingSessions.getFirst() == session) { |
489 | if (doLock2(session, lockMode, exclusive)) { |
490 | return; |
491 | } |
492 | } |
493 | if (checkDeadlock) { |
494 | ArrayList<Session> sessions = checkDeadlock(session, null, null); |
495 | if (sessions != null) { |
496 | throw DbException.get(ErrorCode.DEADLOCK_1, |
497 | getDeadlockDetails(sessions, exclusive)); |
498 | } |
499 | } else { |
500 | // check for deadlocks from now on |
501 | checkDeadlock = true; |
502 | } |
503 | long now = System.currentTimeMillis(); |
504 | if (max == 0) { |
505 | // try at least one more time |
506 | max = now + session.getLockTimeout(); |
507 | } else if (now >= max) { |
508 | traceLock(session, exclusive, "timeout after " + session.getLockTimeout()); |
509 | throw DbException.get(ErrorCode.LOCK_TIMEOUT_1, getName()); |
510 | } |
511 | try { |
512 | traceLock(session, exclusive, "waiting for"); |
513 | if (database.getLockMode() == Constants.LOCK_MODE_TABLE_GC) { |
514 | for (int i = 0; i < 20; i++) { |
515 | long free = Runtime.getRuntime().freeMemory(); |
516 | System.gc(); |
517 | long free2 = Runtime.getRuntime().freeMemory(); |
518 | if (free == free2) { |
519 | break; |
520 | } |
521 | } |
522 | } |
523 | // don't wait too long so that deadlocks are detected early |
524 | long sleep = Math.min(Constants.DEADLOCK_CHECK, max - now); |
525 | if (sleep == 0) { |
526 | sleep = 1; |
527 | } |
528 | database.wait(sleep); |
529 | } catch (InterruptedException e) { |
530 | // ignore |
531 | } |
532 | } |
533 | } |
534 | |
535 | private boolean doLock2(Session session, int lockMode, boolean exclusive) { |
536 | if (exclusive) { |
537 | if (lockExclusiveSession == null) { |
538 | if (lockSharedSessions.isEmpty()) { |
539 | traceLock(session, exclusive, "added for"); |
540 | session.addLock(this); |
541 | lockExclusiveSession = session; |
542 | return true; |
543 | } else if (lockSharedSessions.size() == 1 && |
544 | lockSharedSessions.contains(session)) { |
545 | traceLock(session, exclusive, "add (upgraded) for "); |
546 | lockExclusiveSession = session; |
547 | return true; |
548 | } |
549 | } |
550 | } else { |
551 | if (lockExclusiveSession == null) { |
552 | if (lockMode == Constants.LOCK_MODE_READ_COMMITTED) { |
553 | if (!database.isMultiThreaded() && !database.isMultiVersion()) { |
554 | // READ_COMMITTED: a read lock is acquired, |
555 | // but released immediately after the operation |
556 | // is complete. |
557 | // When allowing only one thread, no lock is |
558 | // required. |
559 | // Row level locks work like read committed. |
560 | return true; |
561 | } |
562 | } |
563 | if (!lockSharedSessions.contains(session)) { |
564 | traceLock(session, exclusive, "ok"); |
565 | session.addLock(this); |
566 | lockSharedSessions.add(session); |
567 | } |
568 | return true; |
569 | } |
570 | } |
571 | return false; |
572 | } |
573 | private static String getDeadlockDetails(ArrayList<Session> sessions, boolean exclusive) { |
574 | // We add the thread details here to make it easier for customers to |
575 | // match up these error messages with their own logs. |
576 | StringBuilder buff = new StringBuilder(); |
577 | for (Session s : sessions) { |
578 | Table lock = s.getWaitForLock(); |
579 | Thread thread = s.getWaitForLockThread(); |
580 | buff.append("\nSession "). |
581 | append(s.toString()). |
582 | append(" on thread "). |
583 | append(thread.getName()). |
584 | append(" is waiting to lock "). |
585 | append(lock.toString()). |
586 | append(exclusive ? " (exclusive)" : " (shared)"). |
587 | append(" while locking "); |
588 | int i = 0; |
589 | for (Table t : s.getLocks()) { |
590 | if (i++ > 0) { |
591 | buff.append(", "); |
592 | } |
593 | buff.append(t.toString()); |
594 | if (t instanceof RegularTable) { |
595 | if (((RegularTable) t).lockExclusiveSession == s) { |
596 | buff.append(" (exclusive)"); |
597 | } else { |
598 | buff.append(" (shared)"); |
599 | } |
600 | } |
601 | } |
602 | buff.append('.'); |
603 | } |
604 | return buff.toString(); |
605 | } |
606 | |
607 | @Override |
608 | public ArrayList<Session> checkDeadlock(Session session, Session clash, |
609 | Set<Session> visited) { |
610 | // only one deadlock check at any given time |
611 | synchronized (RegularTable.class) { |
612 | if (clash == null) { |
613 | // verification is started |
614 | clash = session; |
615 | visited = New.hashSet(); |
616 | } else if (clash == session) { |
617 | // we found a circle where this session is involved |
618 | return New.arrayList(); |
619 | } else if (visited.contains(session)) { |
620 | // we have already checked this session. |
621 | // there is a circle, but the sessions in the circle need to |
622 | // find it out themselves |
623 | return null; |
624 | } |
625 | visited.add(session); |
626 | ArrayList<Session> error = null; |
627 | for (Session s : lockSharedSessions) { |
628 | if (s == session) { |
629 | // it doesn't matter if we have locked the object already |
630 | continue; |
631 | } |
632 | Table t = s.getWaitForLock(); |
633 | if (t != null) { |
634 | error = t.checkDeadlock(s, clash, visited); |
635 | if (error != null) { |
636 | error.add(session); |
637 | break; |
638 | } |
639 | } |
640 | } |
641 | if (error == null && lockExclusiveSession != null) { |
642 | Table t = lockExclusiveSession.getWaitForLock(); |
643 | if (t != null) { |
644 | error = t.checkDeadlock(lockExclusiveSession, clash, visited); |
645 | if (error != null) { |
646 | error.add(session); |
647 | } |
648 | } |
649 | } |
650 | return error; |
651 | } |
652 | } |
653 | |
654 | private void traceLock(Session session, boolean exclusive, String s) { |
655 | if (traceLock.isDebugEnabled()) { |
656 | traceLock.debug("{0} {1} {2} {3}", session.getId(), |
657 | exclusive ? "exclusive write lock" : "shared read lock", s, getName()); |
658 | } |
659 | } |
660 | |
661 | @Override |
662 | public boolean isLockedExclusively() { |
663 | return lockExclusiveSession != null; |
664 | } |
665 | |
666 | @Override |
667 | public void unlock(Session s) { |
668 | if (database != null) { |
669 | traceLock(s, lockExclusiveSession == s, "unlock"); |
670 | if (lockExclusiveSession == s) { |
671 | lockExclusiveSession = null; |
672 | } |
673 | if (lockSharedSessions.size() > 0) { |
674 | lockSharedSessions.remove(s); |
675 | } |
676 | synchronized (database) { |
677 | if (!waitingSessions.isEmpty()) { |
678 | database.notifyAll(); |
679 | } |
680 | } |
681 | } |
682 | } |
683 | |
684 | /** |
685 | * Create a row from the values. |
686 | * |
687 | * @param data the value list |
688 | * @return the row |
689 | */ |
690 | public static Row createRow(Value[] data) { |
691 | return new Row(data, Row.MEMORY_CALCULATE); |
692 | } |
693 | |
694 | /** |
695 | * Set the row count of this table. |
696 | * |
697 | * @param count the row count |
698 | */ |
699 | public void setRowCount(long count) { |
700 | this.rowCount = count; |
701 | } |
702 | |
703 | @Override |
704 | public void removeChildrenAndResources(Session session) { |
705 | if (containsLargeObject) { |
706 | // unfortunately, the data is gone on rollback |
707 | truncate(session); |
708 | database.getLobStorage().removeAllForTable(getId()); |
709 | database.lockMeta(session); |
710 | } |
711 | super.removeChildrenAndResources(session); |
712 | // go backwards because database.removeIndex will call table.removeIndex |
713 | while (indexes.size() > 1) { |
714 | Index index = indexes.get(1); |
715 | if (index.getName() != null) { |
716 | database.removeSchemaObject(session, index); |
717 | } |
718 | // needed for session temporary indexes |
719 | indexes.remove(index); |
720 | } |
721 | if (SysProperties.CHECK) { |
722 | for (SchemaObject obj : database.getAllSchemaObjects(DbObject.INDEX)) { |
723 | Index index = (Index) obj; |
724 | if (index.getTable() == this) { |
725 | DbException.throwInternalError("index not dropped: " + index.getName()); |
726 | } |
727 | } |
728 | } |
729 | scanIndex.remove(session); |
730 | database.removeMeta(session, getId()); |
731 | scanIndex = null; |
732 | lockExclusiveSession = null; |
733 | lockSharedSessions = null; |
734 | invalidate(); |
735 | } |
736 | |
737 | @Override |
738 | public String toString() { |
739 | return getSQL(); |
740 | } |
741 | |
742 | @Override |
743 | public void checkRename() { |
744 | // ok |
745 | } |
746 | |
747 | @Override |
748 | public void checkSupportAlter() { |
749 | // ok |
750 | } |
751 | |
752 | @Override |
753 | public boolean canTruncate() { |
754 | if (getCheckForeignKeyConstraints() && database.getReferentialIntegrity()) { |
755 | ArrayList<Constraint> constraints = getConstraints(); |
756 | if (constraints != null) { |
757 | for (int i = 0, size = constraints.size(); i < size; i++) { |
758 | Constraint c = constraints.get(i); |
759 | if (!(c.getConstraintType().equals(Constraint.REFERENTIAL))) { |
760 | continue; |
761 | } |
762 | ConstraintReferential ref = (ConstraintReferential) c; |
763 | if (ref.getRefTable() == this) { |
764 | return false; |
765 | } |
766 | } |
767 | } |
768 | } |
769 | return true; |
770 | } |
771 | |
772 | @Override |
773 | public String getTableType() { |
774 | return Table.TABLE; |
775 | } |
776 | |
777 | @Override |
778 | public long getMaxDataModificationId() { |
779 | return lastModificationId; |
780 | } |
781 | |
782 | public boolean getContainsLargeObject() { |
783 | return containsLargeObject; |
784 | } |
785 | |
786 | @Override |
787 | public long getRowCountApproximation() { |
788 | return scanIndex.getRowCountApproximation(); |
789 | } |
790 | |
791 | @Override |
792 | public long getDiskSpaceUsed() { |
793 | return scanIndex.getDiskSpaceUsed(); |
794 | } |
795 | |
796 | public void setCompareMode(CompareMode compareMode) { |
797 | this.compareMode = compareMode; |
798 | } |
799 | |
800 | @Override |
801 | public boolean isDeterministic() { |
802 | return true; |
803 | } |
804 | |
805 | @Override |
806 | public Column getRowIdColumn() { |
807 | if (rowIdColumn == null) { |
808 | rowIdColumn = new Column(Column.ROWID, Value.LONG); |
809 | rowIdColumn.setTable(this, -1); |
810 | } |
811 | return rowIdColumn; |
812 | } |
813 | |
814 | } |