001: /*
002: * Copyright 2004-2008 H2 Group. Licensed under the H2 License, Version 1.0
003: * (http://h2database.com/html/license.html).
004: * Initial Developer: H2 Group
005: */
006: package org.h2.table;
007:
008: import java.sql.SQLException;
009: import java.util.Comparator;
010: import java.util.HashSet;
011: import org.h2.api.DatabaseEventListener;
012: import org.h2.constant.ErrorCode;
013: import org.h2.constant.SysProperties;
014: import org.h2.constraint.Constraint;
015: import org.h2.constraint.ConstraintReferential;
016: import org.h2.engine.Constants;
017: import org.h2.engine.DbObject;
018: import org.h2.engine.Session;
019: import org.h2.index.BtreeIndex;
020: import org.h2.index.Cursor;
021: import org.h2.index.HashIndex;
022: import org.h2.index.Index;
023: import org.h2.index.IndexType;
024: import org.h2.index.LinearHashIndex;
025: import org.h2.index.MultiVersionIndex;
026: import org.h2.index.ScanIndex;
027: import org.h2.index.TreeIndex;
028: import org.h2.message.Message;
029: import org.h2.message.Trace;
030: import org.h2.result.Row;
031: import org.h2.schema.Schema;
032: import org.h2.store.DataPage;
033: import org.h2.store.Record;
034: import org.h2.store.RecordReader;
035: import org.h2.util.MathUtils;
036: import org.h2.util.ObjectArray;
037: import org.h2.util.StringUtils;
038: import org.h2.value.DataType;
039: import org.h2.value.Value;
040:
041: /**
042: * Most tables are an instance of this class. For this table, the data is stored
043: * in the database. The actual data is not kept here, instead it is kept in the
044: * indexes. There is at least one index, the scan index.
045: */
046: public class TableData extends Table implements RecordReader {
047: private final boolean clustered;
048: private ScanIndex scanIndex;
049: private long rowCount;
050: private Session lockExclusive;
051: private HashSet lockShared = new HashSet();
052: private Trace traceLock;
053: private boolean globalTemporary;
054: private final ObjectArray indexes = new ObjectArray();
055: private long lastModificationId;
056: private boolean containsLargeObject;
057:
058: public TableData(Schema schema, String tableName, int id,
059: ObjectArray columns, boolean persistent, boolean clustered)
060: throws SQLException {
061: super (schema, id, tableName, persistent);
062: Column[] cols = new Column[columns.size()];
063: columns.toArray(cols);
064: setColumns(cols);
065: this .clustered = clustered;
066: if (!clustered) {
067: scanIndex = new ScanIndex(this , id, IndexColumn.wrap(cols),
068: IndexType.createScan(persistent));
069: indexes.add(scanIndex);
070: }
071: for (int i = 0; i < cols.length; i++) {
072: if (DataType.isLargeObject(cols[i].getType())) {
073: containsLargeObject = true;
074: memoryPerRow = Row.MEMORY_CALCULATE;
075: }
076: }
077: traceLock = database.getTrace(Trace.LOCK);
078: }
079:
080: public void close(Session session) throws SQLException {
081: for (int i = 0; i < indexes.size(); i++) {
082: Index index = (Index) indexes.get(i);
083: index.close(session);
084: }
085: }
086:
087: public Row getRow(Session session, int key) throws SQLException {
088: return scanIndex.getRow(session, key);
089: }
090:
091: public void addRow(Session session, Row row) throws SQLException {
092: int i = 0;
093: lastModificationId = database.getNextModificationDataId();
094: if (database.isMultiVersion()) {
095: row.setSessionId(session.getId());
096: }
097: try {
098: for (; i < indexes.size(); i++) {
099: Index index = (Index) indexes.get(i);
100: index.add(session, row);
101: checkRowCount(session, index, 1);
102: }
103: rowCount++;
104: } catch (Throwable e) {
105: try {
106: while (--i >= 0) {
107: Index index = (Index) indexes.get(i);
108: index.remove(session, row);
109: checkRowCount(session, index, 0);
110: }
111: } catch (SQLException e2) {
112: // this could happen, for example on failure in the storage
113: // but if that is not the case it means there is something wrong
114: // with the database
115: // TODO log this problem
116: throw e2;
117: }
118: throw Message.convert(e);
119: }
120: }
121:
122: private void checkRowCount(Session session, Index index, int offset) {
123: if (SysProperties.CHECK && !database.isMultiVersion()) {
124: long rc = index.getRowCount(session);
125: if (rc != rowCount + offset) {
126: throw Message.getInternalError("rowCount expected "
127: + (rowCount + offset) + " got " + rc + " "
128: + getName() + "." + index.getName());
129: }
130: }
131: }
132:
133: public Index getScanIndex(Session session) {
134: return (Index) indexes.get(0);
135: }
136:
137: public Index getUniqueIndex() {
138: for (int i = 0; i < indexes.size(); i++) {
139: Index idx = (Index) indexes.get(i);
140: if (idx.getIndexType().isUnique()) {
141: return idx;
142: }
143: }
144: return null;
145: }
146:
147: public ObjectArray getIndexes() {
148: return indexes;
149: }
150:
151: public Index addIndex(Session session, String indexName,
152: int indexId, IndexColumn[] cols, IndexType indexType,
153: int headPos, String indexComment) throws SQLException {
154: if (indexType.isPrimaryKey()) {
155: for (int i = 0; i < cols.length; i++) {
156: Column column = cols[i].column;
157: if (column.getNullable()) {
158: throw Message.getSQLException(
159: ErrorCode.COLUMN_MUST_NOT_BE_NULLABLE_1,
160: column.getName());
161: }
162: column.setPrimaryKey(true);
163: }
164: }
165: Index index;
166: if (isPersistent() && indexType.isPersistent()) {
167: if (indexType.isHash()) {
168: index = new LinearHashIndex(session, this , indexId,
169: indexName, cols, indexType);
170: } else {
171: index = new BtreeIndex(session, this , indexId,
172: indexName, cols, indexType, headPos);
173: }
174: } else {
175: if (indexType.isHash()) {
176: index = new HashIndex(this , indexId, indexName, cols,
177: indexType);
178: } else {
179: index = new TreeIndex(this , indexId, indexName, cols,
180: indexType);
181: }
182: }
183: if (database.isMultiVersion()) {
184: index = new MultiVersionIndex(index, this );
185: }
186: if (index.needRebuild() && rowCount > 0) {
187: try {
188: Index scan = getScanIndex(session);
189: long remaining = scan.getRowCount(session);
190: long total = remaining;
191: Cursor cursor = scan.find(session, null, null);
192: long i = 0;
193: int bufferSize = Constants.DEFAULT_MAX_MEMORY_ROWS;
194: ObjectArray buffer = new ObjectArray(bufferSize);
195: while (cursor.next()) {
196: database.setProgress(
197: DatabaseEventListener.STATE_CREATE_INDEX,
198: getName(), MathUtils.convertLongToInt(i++),
199: MathUtils.convertLongToInt(total));
200: Row row = cursor.get();
201: // index.add(session, row);
202: buffer.add(row);
203: if (buffer.size() >= bufferSize) {
204: addRowsToIndex(session, buffer, index);
205: }
206: remaining--;
207: }
208: addRowsToIndex(session, buffer, index);
209: if (SysProperties.CHECK && remaining != 0) {
210: throw Message
211: .getInternalError("rowcount remaining="
212: + remaining + " " + getName());
213: }
214: } catch (SQLException e) {
215: getSchema().freeUniqueName(indexName);
216: try {
217: index.remove(session);
218: } catch (SQLException e2) {
219: // this could happen, for example on failure in the storage
220: // but if that is not the case it means
221: // there is something wrong with the database
222: // TODO log this problem
223: throw e2;
224: }
225: throw e;
226: }
227: }
228: boolean temporary = getTemporary();
229: index.setTemporary(temporary);
230: if (index.getCreateSQL() != null) {
231: index.setComment(indexComment);
232: database.addSchemaObject(session, index);
233: // Need to update, because maybe the index is rebuilt at startup,
234: // and so the head pos may have changed, which needs to be stored now.
235: // addSchemaObject doesn't update the sys table at startup
236: if (index.getIndexType().isPersistent()
237: && !database.getReadOnly()
238: && !database.getLog().containsInDoubtTransactions()) {
239: // can not save anything in the log file if it contains in-doubt transactions
240: database.update(session, index);
241: }
242: }
243: indexes.add(index);
244: setModified();
245: return index;
246: }
247:
248: public boolean canGetRowCount() {
249: return true;
250: }
251:
252: private void addRowsToIndex(Session session, ObjectArray list,
253: Index index) throws SQLException {
254: final Index idx = index;
255: try {
256: list.sort(new Comparator() {
257: public int compare(Object o1, Object o2) {
258: Row r1 = (Row) o1;
259: Row r2 = (Row) o2;
260: try {
261: return idx.compareRows(r1, r2);
262: } catch (SQLException e) {
263: throw Message.convertToInternal(e);
264: }
265: }
266: });
267: } catch (Exception e) {
268: throw Message.convert(e);
269: }
270: for (int i = 0; i < list.size(); i++) {
271: Row row = (Row) list.get(i);
272: index.add(session, row);
273: }
274: list.clear();
275: }
276:
277: public boolean canDrop() {
278: return true;
279: }
280:
281: public long getRowCount(Session session) {
282: if (database.isMultiVersion()) {
283: return getScanIndex(session).getRowCount(session);
284: }
285: return rowCount;
286: }
287:
288: public void removeRow(Session session, Row row) throws SQLException {
289: lastModificationId = database.getNextModificationDataId();
290: if (database.isMultiVersion()) {
291: if (row.getDeleted()) {
292: throw Message.getSQLException(
293: ErrorCode.CONCURRENT_UPDATE_1, getName());
294: }
295: int old = row.getSessionId();
296: int newId = session.getId();
297: if (old == 0) {
298: row.setSessionId(newId);
299: } else if (old != newId) {
300: throw Message.getSQLException(
301: ErrorCode.CONCURRENT_UPDATE_1, getName());
302: }
303: }
304: int i = indexes.size() - 1;
305: try {
306: for (; i >= 0; i--) {
307: Index index = (Index) indexes.get(i);
308: index.remove(session, row);
309: checkRowCount(session, index, -1);
310: }
311: rowCount--;
312: } catch (Throwable e) {
313: try {
314: while (++i < indexes.size()) {
315: Index index = (Index) indexes.get(i);
316: index.add(session, row);
317: checkRowCount(session, index, 0);
318: }
319: } catch (SQLException e2) {
320: // this could happen, for example on failure in the storage
321: // but if that is not the case it means there is something wrong
322: // with the database
323: // TODO log this problem
324: throw e2;
325: }
326: throw Message.convert(e);
327: }
328: }
329:
330: public void truncate(Session session) throws SQLException {
331: lastModificationId = database.getNextModificationDataId();
332: for (int i = indexes.size() - 1; i >= 0; i--) {
333: Index index = (Index) indexes.get(i);
334: index.truncate(session);
335: if (SysProperties.CHECK) {
336: long rc = index.getRowCount(session);
337: if (rc != 0) {
338: throw Message
339: .getInternalError("rowCount expected 0 got "
340: + rc);
341: }
342: }
343: }
344: rowCount = 0;
345: }
346:
347: public boolean isLockExclusive(Session s) {
348: return lockExclusive == s;
349: }
350:
351: public void lock(Session session, boolean exclusive, boolean force)
352: throws SQLException {
353: int lockMode = database.getLockMode();
354: if (lockMode == Constants.LOCK_MODE_OFF) {
355: return;
356: }
357: long max = System.currentTimeMillis()
358: + session.getLockTimeout();
359: if (!force && database.isMultiVersion()) {
360: // MVCC: update, delete, and insert use a shared lock
361: // select doesn't lock
362: if (exclusive) {
363: exclusive = false;
364: } else {
365: return;
366: }
367: }
368: synchronized (database) {
369: while (true) {
370: if (lockExclusive == session) {
371: return;
372: }
373: if (exclusive) {
374: if (lockExclusive == null) {
375: if (lockShared.isEmpty()) {
376: traceLock(session, exclusive, "added for");
377: session.addLock(this );
378: lockExclusive = session;
379: return;
380: } else if (lockShared.size() == 1
381: && lockShared.contains(session)) {
382: traceLock(session, exclusive,
383: "add (upgraded) for ");
384: lockExclusive = session;
385: return;
386: }
387: }
388: } else {
389: if (lockExclusive == null) {
390: if (lockMode == Constants.LOCK_MODE_READ_COMMITTED
391: && !database.getMultiThreaded()
392: && !database.isMultiVersion()) {
393: // READ_COMMITTED read locks are acquired but they
394: // are released immediately
395: // when allowing only one thread, no read locks are
396: // required
397: return;
398: } else if (!lockShared.contains(session)) {
399: traceLock(session, exclusive, "ok");
400: session.addLock(this );
401: lockShared.add(session);
402: }
403: return;
404: }
405: }
406: long now = System.currentTimeMillis();
407: if (now >= max) {
408: traceLock(session, exclusive, "timeout after "
409: + session.getLockTimeout());
410: throw Message.getSQLException(
411: ErrorCode.LOCK_TIMEOUT_1, getName());
412: }
413: try {
414: traceLock(session, exclusive, "waiting for");
415: if (database.getLockMode() == Constants.LOCK_MODE_TABLE_GC) {
416: for (int i = 0; i < 20; i++) {
417: long free = Runtime.getRuntime()
418: .freeMemory();
419: System.gc();
420: long free2 = Runtime.getRuntime()
421: .freeMemory();
422: if (free == free2) {
423: break;
424: }
425: }
426: }
427: database.wait(max - now);
428: } catch (InterruptedException e) {
429: // ignore
430: }
431: }
432: }
433: }
434:
435: private void traceLock(Session session, boolean exclusive, String s) {
436: if (traceLock.debug()) {
437: traceLock.debug(session.getId()
438: + " "
439: + (exclusive ? "exclusive write lock"
440: : "shared read lock") + " " + s + " "
441: + getName());
442: }
443: }
444:
445: public String getDropSQL() {
446: return "DROP TABLE IF EXISTS " + getSQL();
447: }
448:
449: public String getCreateSQL() {
450: StringBuffer buff = new StringBuffer();
451: buff.append("CREATE ");
452: if (getTemporary()) {
453: if (globalTemporary) {
454: buff.append("GLOBAL ");
455: } else {
456: buff.append("LOCAL ");
457: }
458: buff.append("TEMPORARY ");
459: } else if (isPersistent()) {
460: buff.append("CACHED ");
461: } else {
462: buff.append("MEMORY ");
463: }
464: buff.append("TABLE ");
465: buff.append(getSQL());
466: if (comment != null) {
467: buff.append(" COMMENT ");
468: buff.append(StringUtils.quoteStringSQL(comment));
469: }
470: buff.append("(\n ");
471: for (int i = 0; i < columns.length; i++) {
472: Column column = columns[i];
473: if (i > 0) {
474: buff.append(",\n ");
475: }
476: buff.append(column.getCreateSQL());
477: }
478: buff.append("\n)");
479: return buff.toString();
480: }
481:
482: public boolean isLockedExclusively() {
483: return lockExclusive != null;
484: }
485:
486: public void unlock(Session s) {
487: if (database != null) {
488: traceLock(s, lockExclusive == s, "unlock");
489: if (lockExclusive == s) {
490: lockExclusive = null;
491: }
492: if (lockShared.size() > 0) {
493: lockShared.remove(s);
494: }
495: // TODO lock: maybe we need we fifo-queue to make sure nobody
496: // starves. check what other databases do
497: synchronized (database) {
498: if (database.getSessionCount() > 1) {
499: database.notifyAll();
500: }
501: }
502: }
503: }
504:
505: public Record read(Session session, DataPage s) throws SQLException {
506: int len = s.readInt();
507: Value[] data = new Value[len];
508: for (int i = 0; i < len; i++) {
509: data[i] = s.readValue();
510: }
511: Row row = new Row(data, memoryPerRow);
512: return row;
513: }
514:
515: public void setRowCount(int count) {
516: this .rowCount = count;
517: }
518:
519: public void removeChildrenAndResources(Session session)
520: throws SQLException {
521: super .removeChildrenAndResources(session);
522: // go backwards because database.removeIndex will call table.removeIndex
523: while (indexes.size() > 1) {
524: Index index = (Index) indexes.get(1);
525: if (index.getName() != null) {
526: database.removeSchemaObject(session, index);
527: }
528: }
529: if (SysProperties.CHECK) {
530: ObjectArray list = database
531: .getAllSchemaObjects(DbObject.INDEX);
532: for (int i = 0; i < list.size(); i++) {
533: Index index = (Index) list.get(i);
534: if (index.getTable() == this ) {
535: throw Message
536: .getInternalError("index not dropped: "
537: + index.getName());
538: }
539: }
540: }
541: scanIndex.remove(session);
542: database.removeMeta(session, getId());
543: scanIndex = null;
544: lockExclusive = null;
545: lockShared = null;
546: invalidate();
547: }
548:
549: public void checkRename() throws SQLException {
550: }
551:
552: public void checkSupportAlter() throws SQLException {
553: }
554:
555: public boolean canTruncate() {
556: ObjectArray constraints = getConstraints();
557: for (int i = 0; constraints != null && i < constraints.size(); i++) {
558: Constraint c = (Constraint) constraints.get(i);
559: if (!(c.getConstraintType().equals(Constraint.REFERENTIAL))) {
560: continue;
561: }
562: ConstraintReferential ref = (ConstraintReferential) c;
563: if (ref.getRefTable() == this ) {
564: return false;
565: }
566: }
567: return true;
568: }
569:
570: public String getTableType() {
571: return Table.TABLE;
572: }
573:
574: public void setGlobalTemporary(boolean globalTemporary) {
575: this .globalTemporary = globalTemporary;
576: }
577:
578: public boolean getGlobalTemporary() {
579: return globalTemporary;
580: }
581:
582: public long getMaxDataModificationId() {
583: return lastModificationId;
584: }
585:
586: public boolean isClustered() {
587: return clustered;
588: }
589:
590: public boolean getContainsLargeObject() {
591: return containsLargeObject;
592: }
593:
594: }
|