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

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