0001: /*
0002: * $Id: BaseDiskTable.java,v 1.36 2005/12/22 09:02:31 ahimanikya Exp $
0003: * =======================================================================
0004: * Copyright (c) 2002-2005 Axion Development Team. All rights reserved.
0005: *
0006: * Redistribution and use in source and binary forms, with or without
0007: * modification, are permitted provided that the following conditions
0008: * are met:
0009: *
0010: * 1. Redistributions of source code must retain the above
0011: * copyright notice, this list of conditions and the following
0012: * disclaimer.
0013: *
0014: * 2. Redistributions in binary form must reproduce the above copyright
0015: * notice, this list of conditions and the following disclaimer in
0016: * the documentation and/or other materials provided with the
0017: * distribution.
0018: *
0019: * 3. The names "Tigris", "Axion", nor the names of its contributors may
0020: * not be used to endorse or promote products derived from this
0021: * software without specific prior written permission.
0022: *
0023: * 4. Products derived from this software may not be called "Axion", nor
0024: * may "Tigris" or "Axion" appear in their names without specific prior
0025: * written permission.
0026: *
0027: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
0028: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
0029: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
0030: * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
0031: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
0032: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
0033: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
0034: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
0035: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0036: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
0037: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0038: * =======================================================================
0039: */
0040:
0041: package org.axiondb.engine.tables;
0042:
0043: import java.io.DataInputStream;
0044: import java.io.DataOutputStream;
0045: import java.io.File;
0046: import java.io.FilenameFilter;
0047: import java.io.IOException;
0048: import java.io.ObjectInputStream;
0049: import java.io.ObjectOutputStream;
0050: import java.util.Iterator;
0051: import java.util.NoSuchElementException;
0052:
0053: import org.apache.commons.collections.primitives.ArrayIntList;
0054: import org.apache.commons.collections.primitives.ArrayUnsignedIntList;
0055: import org.apache.commons.collections.primitives.IntCollection;
0056: import org.apache.commons.collections.primitives.IntIterator;
0057: import org.apache.commons.collections.primitives.IntList;
0058: import org.apache.commons.logging.Log;
0059: import org.apache.commons.logging.LogFactory;
0060: import org.axiondb.AxionException;
0061: import org.axiondb.Column;
0062: import org.axiondb.Constraint;
0063: import org.axiondb.DataType;
0064: import org.axiondb.Database;
0065: import org.axiondb.Index;
0066: import org.axiondb.IndexLoader;
0067: import org.axiondb.Row;
0068: import org.axiondb.RowCollection;
0069: import org.axiondb.RowIterator;
0070: import org.axiondb.Sequence;
0071: import org.axiondb.Table;
0072: import org.axiondb.TableFactory;
0073: import org.axiondb.engine.rowiterators.BaseRowIterator;
0074: import org.axiondb.event.RowEvent;
0075: import org.axiondb.event.RowInsertedEvent;
0076: import org.axiondb.io.AxionFileSystem;
0077: import org.axiondb.io.BufferedDataInputStream;
0078: import org.axiondb.io.BufferedDataOutputStream;
0079: import org.axiondb.io.FileUtil;
0080: import org.axiondb.types.LOBType;
0081: import org.axiondb.util.ExceptionConverter;
0082:
0083: /**
0084: * Abstract base disk-resident implementation of {@link Table}.
0085: * <code>BaseDiskTable</code> manages the column meta-data for a disk-based table.
0086: *
0087: * @version $Revision: 1.36 $ $Date: 2005/12/22 09:02:31 $
0088: * @author Chuck Burdick
0089: * @author Rodney Waldhoff
0090: * @author Ahimanikya Satapathy
0091: */
0092: public abstract class BaseDiskTable extends BaseTable implements Table {
0093:
0094: //------------------------------------------------------------- Constructors
0095:
0096: public BaseDiskTable(String name, Database db, TableFactory factory)
0097: throws AxionException {
0098: super (name);
0099: _dbdir = db.getDBDirectory();
0100: _readOnly = db.isReadOnly();
0101: createOrLoadTableFiles(name, db, factory);
0102: }
0103:
0104: // ----------------------------------------------------------------- public
0105: public void addColumn(Column col) throws AxionException {
0106: addColumn(col, true);
0107: }
0108:
0109: public void addColumn(Column col, boolean metaUpdateNeeded)
0110: throws AxionException {
0111: resetLobColumn(col);
0112: super .addColumn(col);
0113: if (metaUpdateNeeded) {
0114: writeMetaFile();
0115: }
0116: }
0117:
0118: public void addConstraint(Constraint constraint)
0119: throws AxionException {
0120: super .addConstraint(constraint);
0121: writeMetaFile();
0122: }
0123:
0124: public void applyDeletes(IntCollection rowIds)
0125: throws AxionException {
0126: synchronized (this ) {
0127: applyDeletesToIndices(rowIds);
0128: applyDeletesToRows(rowIds);
0129: }
0130: }
0131:
0132: public void applyInserts(RowCollection rows) throws AxionException {
0133: synchronized (this ) {
0134: // apply to the indices one at a time, as its more memory-friendly
0135: for (Iterator indexIter = getIndices(); indexIter.hasNext();) {
0136: Index index = (Index) (indexIter.next());
0137: for (RowIterator iter = rows.rowIterator(); iter
0138: .hasNext();) {
0139: Row row = iter.next();
0140: RowEvent event = new RowInsertedEvent(this , null,
0141: row);
0142: index.rowInserted(event);
0143: }
0144: saveIndex(index);
0145: }
0146: applyInsertsToRows(rows.rowIterator());
0147: }
0148: }
0149:
0150: public void applyUpdates(RowCollection rows) throws AxionException {
0151: synchronized (this ) {
0152: applyUpdatesToIndices(rows);
0153: applyUpdatesToRows(rows.rowIterator());
0154: }
0155: }
0156:
0157: public void checkpoint() throws AxionException {
0158: super .checkpoint();
0159: DataOutputStream out = null;
0160: try {
0161: if (getSequence() != null) {
0162: File seqFile = getTableFile(SEQ_FILE_EXT);
0163: out = FS.createDataOutputSteam(seqFile);
0164: getSequence().write(out);
0165: }
0166:
0167: } catch (IOException e) {
0168: String msg = "Unable to persist sequence file";
0169: throw new AxionException(msg);
0170: } finally {
0171: FS.closeOutputStream(out);
0172: }
0173: }
0174:
0175: public void drop() throws AxionException {
0176: closeFiles();
0177: if (!FileUtil.delete(getRootDir())) {
0178: throw new AxionException("Unable to delete \""
0179: + getRootDir() + "\" during drop table "
0180: + getName());
0181: }
0182: }
0183:
0184: public void freeRowId(int id) {
0185: if (_freeIdPos >= 0 && id == _freeIds.get(_freeIdPos)) {
0186: _freeIdPos--;
0187: } else if (_nextFreeId > getPidxList().size() - 1) {
0188: _nextFreeId--;
0189: }
0190: }
0191:
0192: public int getNextRowId() {
0193: if (_freeIds.isEmpty() || _freeIdPos >= _freeIds.size() - 1) {
0194: return _nextFreeId = (_nextFreeId == -1 ? getPidxList()
0195: .size() : _nextFreeId + 1);
0196: } else {
0197: return _freeIds.get(++_freeIdPos);
0198: }
0199: }
0200:
0201: public Row getRow(int id) throws AxionException {
0202: long ptr = getPidxList().get(id);
0203: Row row = getRowByOffset(id, ptr);
0204: return row;
0205: }
0206:
0207: /** Migrate from older version to newer version for this table */
0208: public void migrate(Database db) throws AxionException {
0209: File metaFile = getTableFile(META_FILE_EXT);
0210: if (!metaFile.exists()) {
0211: return;
0212: }
0213:
0214: int version = CURRENT_META_VERSION;
0215: ObjectInputStream in = null;
0216: try {
0217: in = FS.openObjectInputSteam(metaFile);
0218: // read version number
0219: version = in.readInt();
0220: if (_log.isDebugEnabled()) {
0221: _log.debug("Version number for " + getName()
0222: + " in migrate() is " + version);
0223: }
0224:
0225: if (version < 0 || version > CURRENT_META_VERSION) {
0226: throw new AxionException("Unrecognized version "
0227: + version);
0228: }
0229:
0230: if (version == 0) {
0231: parseV0MetaFile(in);
0232: } else {
0233: parseV1MetaFile(in, db);
0234: }
0235: parseTableProperties(in);
0236: } catch (ClassNotFoundException e) {
0237: throw new AxionException("Unable to parse meta file "
0238: + metaFile + " for table " + getName(), e);
0239: } catch (IOException e) {
0240: throw new AxionException("Unable to parse meta file "
0241: + metaFile + " for table " + getName(), e);
0242: } finally {
0243: FS.closeInputStream(in);
0244: }
0245:
0246: if (version < 2) {
0247: _pidx = FS.parseLongPidx(getTableFile(PIDX_FILE_EXT),
0248: _readOnly);
0249: }
0250:
0251: if (version < 3) {
0252: // change col name to upper if required
0253: for (int i = 0, I = getColumnCount(); i < I; i++) {
0254: Column col = getColumn(i);
0255: col.getConfiguration().put(Column.NAME_CONFIG_KEY,
0256: col.getName().toUpperCase());
0257: }
0258: }
0259:
0260: if (version != CURRENT_META_VERSION) {
0261: writeMetaFile(); // migrating from older meta type, so update meta file
0262: }
0263: }
0264:
0265: public int getRowCount() {
0266: return _rowCount;
0267: }
0268:
0269: public void populateIndex(Index index) throws AxionException {
0270: for (int i = 0, I = getPidxList().size(); i < I; i++) {
0271: long ptr = getPidxList().get(i);
0272: if (ptr != INVALID_OFFSET) {
0273: index.rowInserted(new RowInsertedEvent(this , null,
0274: getRowByOffset(i, ptr)));
0275: }
0276: }
0277:
0278: File indexDir = new File(_indexRootDir, index.getName());
0279: if (!indexDir.exists()) {
0280: indexDir.mkdirs();
0281: }
0282: File typefile = new File(indexDir, index.getName()
0283: + TYPE_FILE_EXT);
0284: IndexLoader loader = index.getIndexLoader();
0285: writeNameToFile(typefile, loader);
0286: index.save(indexDir);
0287: }
0288:
0289: public void remount(File newdir, boolean datafilesonly)
0290: throws AxionException {
0291: closeFiles();
0292: initFiles(newdir, datafilesonly);
0293: writeMetaFile();
0294: resetLobColumns();
0295: super .remount(newdir, datafilesonly);
0296: }
0297:
0298: public void removeIndex(Index index) throws AxionException {
0299: super .removeIndex(index);
0300: File indexdir = new File(_indexRootDir, index.getName());
0301: if (!FileUtil.delete(indexdir)) {
0302: throw new AxionException("Unable to delete \"" + indexdir
0303: + "\" during remove index " + index.getName());
0304: }
0305: }
0306:
0307: public void rename(String oldName, String newName)
0308: throws AxionException {
0309: try {
0310: closeFiles();
0311: renameTableFiles(oldName, newName);
0312: super .rename(oldName, newName);
0313:
0314: clearDataFileReference();
0315: writeMetaFile(); // refresh meta file
0316: initFiles(getRootDir(), false);
0317:
0318: resetLobColumns();
0319: } catch (Exception e) {
0320: throw new AxionException("Fail to alter table: " + newName);
0321: }
0322: }
0323:
0324: public void setSequence(Sequence seq) throws AxionException {
0325: super .setSequence(seq);
0326: checkpoint();
0327: }
0328:
0329: public void shutdown() throws AxionException {
0330: closeFiles();
0331: }
0332:
0333: public void truncate() throws AxionException {
0334: // Return immediately if table is already empty.
0335: if (getRowCount() == 0) {
0336: return;
0337: }
0338:
0339: // Rename old data file as backup and replace with zero-length file.
0340: File df = getDataFile();
0341: File pidxFile = getTableFile(PIDX_FILE_EXT);
0342: File bkupFile = new File(df.getParentFile(), df.getName()
0343: + ".backup");
0344: File bkupPidxFile = new File(pidxFile + ".backup");
0345: FileUtil.assertFileNotLocked(bkupFile);
0346:
0347: try {
0348: closeFiles();
0349: FileUtil.delete(bkupFile);
0350: df.renameTo(bkupFile);
0351: pidxFile.renameTo(bkupPidxFile);
0352:
0353: reloadFilesAfterTruncate();
0354: truncateIndices();
0355: saveIndicesAfterTruncate();
0356:
0357: FileUtil.delete(bkupFile);
0358: FileUtil.delete(bkupPidxFile);
0359: } catch (Exception e) {
0360: // Restore old file and reload it.
0361: closeFiles();
0362: FileUtil.delete(df);
0363: FileUtil.delete(pidxFile);
0364: bkupFile.renameTo(df);
0365: bkupPidxFile.renameTo(pidxFile);
0366: reloadFilesAfterTruncate();
0367: recreateIndices();
0368: throw new AxionException(e);
0369: }
0370: }
0371:
0372: //--------------------------------------------------------------- Protected
0373:
0374: protected void clearDataFileReference() {
0375: _dataFile = null;
0376: }
0377:
0378: protected void closeFiles() {
0379: if (null != _readStream) {
0380: try {
0381: _readStream.close();
0382: } catch (IOException e) {
0383: // ignored
0384: }
0385: _readStream = null;
0386: }
0387:
0388: if (null != _writeStream) {
0389: try {
0390: _writeStream.close();
0391: } catch (IOException e) {
0392: // ignored
0393: }
0394: _writeStream = null;
0395: }
0396:
0397: if (null != _pidx) {
0398: try {
0399: _pidx.close();
0400: } catch (IOException e) {
0401: // ignored
0402: }
0403: _pidx = null;
0404: }
0405: }
0406:
0407: protected void createOrLoadDataFile() throws AxionException {
0408: if (!isReadOnly()) {
0409: FS.createNewFile(getDataFile());
0410: }
0411: getOutputStream();
0412: getInputStream();
0413: }
0414:
0415: protected void createOrLoadFreeIdsFile() throws AxionException {
0416: if (_freeIds == null) {
0417: File freeIdsFile = getTableFile(FRID_FILE_EXT);
0418: if (!isReadOnly()) {
0419: FS.createNewFile(freeIdsFile);
0420: }
0421: _freeIds = FS.parseIntFile(freeIdsFile);
0422: }
0423: }
0424:
0425: protected void loadOrMigrateMetaFile(Database db)
0426: throws AxionException {
0427: migrate(db);
0428: }
0429:
0430: abstract protected File getDataFile();
0431:
0432: protected String getDefaultDataFileExtension() {
0433: return "DATA";
0434: }
0435:
0436: protected synchronized BufferedDataInputStream getInputStream()
0437: throws AxionException {
0438: if (null == _readStream) {
0439: _readStream = FS.openBufferedDIS(getDataFile());
0440: }
0441: return _readStream;
0442: }
0443:
0444: abstract protected File getLobDir();
0445:
0446: protected synchronized BufferedDataOutputStream getOutputStream()
0447: throws AxionException {
0448: if (!isReadOnly() && null == _writeStream) {
0449: _writeStream = FS
0450: .openBufferedDOSAppend(getDataFile(), 1024);
0451: }
0452: return _writeStream;
0453: }
0454:
0455: protected AxionFileSystem.PidxList getPidxList() {
0456: if (_pidx == null) {
0457: File pidxFile = getTableFile(PIDX_FILE_EXT);
0458: try {
0459: if (!isReadOnly()) {
0460: FS.createNewFile(pidxFile);
0461: }
0462: _pidx = parsePidxFile(pidxFile);
0463: } catch (AxionException e) {
0464: throw ExceptionConverter.convertToRuntimeException(e);
0465: }
0466: _nextFreeId = -1;
0467: }
0468: return _pidx;
0469: }
0470:
0471: protected File getRootDir() {
0472: return _dir;
0473: }
0474:
0475: protected RowIterator getRowIterator() throws AxionException {
0476: return new BaseRowIterator() {
0477: Row _current = null;
0478: int _currentId = -1;
0479: int _currentIndex = -1;
0480: int _nextId = 0;
0481: int _nextIndex = 0;
0482:
0483: public Row current() {
0484: if (!hasCurrent()) {
0485: throw new NoSuchElementException("No current row.");
0486: }
0487: return _current;
0488: }
0489:
0490: public int currentIndex() {
0491: return _currentIndex;
0492: }
0493:
0494: public boolean hasCurrent() {
0495: return (null != _current);
0496: }
0497:
0498: public boolean hasNext() {
0499: return nextIndex() < getRowCount();
0500: }
0501:
0502: public boolean hasPrevious() {
0503: return nextIndex() > 0;
0504: }
0505:
0506: public Row last() throws AxionException {
0507: if (isEmpty()) {
0508: throw new IllegalStateException("No rows in table.");
0509: }
0510: _nextIndex = getRowCount();
0511: _nextId = getPidxList().size();
0512: previous();
0513:
0514: _nextId++;
0515: _nextIndex++;
0516: return current();
0517: }
0518:
0519: public Row next() throws AxionException {
0520: if (!hasNext()) {
0521: throw new NoSuchElementException("No next row");
0522: }
0523: next(1);
0524: setCurrentRow();
0525: return current();
0526: }
0527:
0528: public int next(int count) throws AxionException {
0529: for (int start = 0; start < count;) {
0530: _currentId = _nextId++;
0531: if (!hasNext() || _currentId > getPidxList().size()) {
0532: throw new NoSuchElementException("No next row");
0533: } else if (getPidxList().get(_currentId) != INVALID_OFFSET) {
0534: _currentIndex = _nextIndex++;
0535: start++;
0536: }
0537: }
0538: _current = null;
0539: return _currentId;
0540: }
0541:
0542: public int nextIndex() {
0543: return _nextIndex;
0544: }
0545:
0546: public Row previous() throws AxionException {
0547: if (!hasPrevious()) {
0548: throw new NoSuchElementException("No previous row");
0549: }
0550: previous(1);
0551: setCurrentRow();
0552: return current();
0553: }
0554:
0555: public int previous(int count) throws AxionException {
0556: for (int start = 0; start < count;) {
0557: _currentId = --_nextId;
0558: if (!hasPrevious() || _currentId < 0) {
0559: throw new NoSuchElementException(
0560: "No Previous row");
0561: } else if (getPidxList().get(_currentId) != INVALID_OFFSET) {
0562: _currentIndex = --_nextIndex;
0563: start++;
0564: }
0565: }
0566: _current = null;
0567: return _currentId;
0568: }
0569:
0570: public int previousIndex() {
0571: return _nextIndex - 1;
0572: }
0573:
0574: public void remove() throws AxionException {
0575: if (-1 == _currentIndex) {
0576: throw new IllegalStateException("No current row.");
0577: }
0578: deleteRow(_current);
0579: _nextIndex--;
0580: _currentIndex = -1;
0581: }
0582:
0583: public void reset() {
0584: _current = null;
0585: _nextIndex = 0;
0586: _currentIndex = -1;
0587: _nextId = 0;
0588: }
0589:
0590: public void set(Row row) throws AxionException {
0591: if (-1 == _currentIndex) {
0592: throw new IllegalStateException("No current row.");
0593: }
0594: updateRow(_current, row);
0595: }
0596:
0597: public int size() throws AxionException {
0598: return getRowCount();
0599: }
0600:
0601: public String toString() {
0602: return "DiskTable(" + getName() + ")";
0603: }
0604:
0605: private Row setCurrentRow() throws AxionException {
0606: Row row = getRowByOffset(_currentId, getPidxList().get(
0607: _currentId));
0608: if (row != null) {
0609: return _current = row;
0610: }
0611: throw new IllegalStateException(
0612: "No valid row at position " + _currentIndex);
0613: }
0614: };
0615: }
0616:
0617: abstract protected Row getRowByOffset(int idToAssign, long ptr)
0618: throws AxionException;
0619:
0620: protected File getTableFile(String extension) {
0621: return new File(getRootDir(), getName().toUpperCase()
0622: + extension);
0623: }
0624:
0625: protected boolean isReadOnly() {
0626: return _readOnly;
0627: }
0628:
0629: protected void initFiles(File basedir, boolean datafilesonly)
0630: throws AxionException {
0631: if (!datafilesonly) {
0632: _dir = basedir;
0633: _indexRootDir = new File(_dir, INDICES_DIR_NAME);
0634: }
0635: clearDataFileReference();
0636: getDataFile();
0637: _readStream = null;
0638: _writeStream = null;
0639: }
0640:
0641: protected void initializeRowCount() throws AxionException {
0642: _rowCount = 0;
0643: for (int i = 0, I = getPidxList().size(); i < I; i++) {
0644: long ptr = getPidxList().get(i);
0645: if (ptr != INVALID_OFFSET) {
0646: _rowCount++;
0647: }
0648: }
0649: }
0650:
0651: protected synchronized AxionFileSystem.PidxList parsePidxFile(
0652: File pidxFile) throws AxionException {
0653: return FS.parseUnsignedIntPidx(pidxFile, _readOnly);
0654: }
0655:
0656: protected void parseTableProperties(ObjectInputStream in)
0657: throws AxionException {
0658: // Allow sub class to read extra meta info
0659: }
0660:
0661: abstract protected void reloadFilesAfterTruncate()
0662: throws AxionException;
0663:
0664: protected void renameTableFiles(String oldName, String name) {
0665: // rename table dir
0666: File olddir = new File(_dbdir, oldName);
0667: _dir = new File(_dbdir, name);
0668: olddir.renameTo(_dir);
0669:
0670: // rename table files
0671: FileUtil.renameFile(_dir, oldName, name, TYPE_FILE_EXT);
0672: FileUtil.renameFile(_dir, oldName, name, PIDX_FILE_EXT);
0673: FileUtil.renameFile(_dir, oldName, name, FRID_FILE_EXT);
0674: FileUtil.renameFile(_dir, oldName, name, META_FILE_EXT);
0675: FileUtil.renameFile(_dir, oldName, name, SEQ_FILE_EXT);
0676: }
0677:
0678: protected void saveIndicesAfterTruncate() throws AxionException {
0679: for (Iterator iter = getIndices(); iter.hasNext();) {
0680: Index index = (Index) (iter.next());
0681: saveIndexAfterTruncate(index);
0682: }
0683: }
0684:
0685: protected void tryToRemove(RowIterator iter) throws AxionException {
0686: try {
0687: iter.remove();
0688: } catch (UnsupportedOperationException e) {
0689: // ignore it, that's OK
0690: }
0691: }
0692:
0693: protected final void writeFridFile() throws AxionException {
0694: FS.writeIntFile(getTableFile(FRID_FILE_EXT), _freeIds);
0695: }
0696:
0697: protected void writeMetaFile() throws AxionException {
0698: ObjectOutputStream out = null;
0699: File metaFile = getTableFile(META_FILE_EXT);
0700: try {
0701: out = FS.createObjectOutputSteam(metaFile);
0702: out.writeInt(CURRENT_META_VERSION);
0703: writeColumns(out);
0704: out.flush();
0705: writeConstraints(out);
0706: out.flush();
0707: writeTableProperties(out);
0708: out.flush();
0709: } catch (IOException e) {
0710: throw new AxionException("Unable to write meta file "
0711: + metaFile + " for table " + getName(), e);
0712: } finally {
0713: FS.closeOutputStream(out);
0714: }
0715: }
0716:
0717: protected void writeNameToFile(File file, Object obj)
0718: throws AxionException {
0719: ObjectOutputStream out = null;
0720: try {
0721: out = FS.createObjectOutputSteam(file);
0722: out.writeUTF(obj.getClass().getName());
0723: } catch (IOException e) {
0724: throw new AxionException(e);
0725: } finally {
0726: FS.closeOutputStream(out);
0727: }
0728: }
0729:
0730: protected abstract void writeRow(BufferedDataOutputStream buffer,
0731: Row row) throws AxionException;
0732:
0733: protected void writeTableProperties(ObjectOutputStream out)
0734: throws AxionException {
0735: // Allow the subclass to write table specific metadata
0736: }
0737:
0738: private void applyDeletesToRows(IntCollection rowids)
0739: throws AxionException {
0740: try {
0741: IntIterator iter = rowids.iterator();
0742: if (iter.hasNext()) {
0743: for (int rowid; iter.hasNext();) {
0744: rowid = iter.next();
0745: if (rowid > getPidxList().size() - 1) {
0746: throw new AxionException(
0747: "Can't delete non-existent row");
0748: }
0749: getPidxList().set(rowid, INVALID_OFFSET);
0750: //_freeIds.add(rowid);
0751: _rowCount--;
0752: }
0753: _pidx.flush();
0754: _freeIds.addAll(rowids);
0755: writeFridFile();
0756: }
0757: } catch (IOException e) {
0758: throw new AxionException("Error writing data.", e);
0759: }
0760: }
0761:
0762: private void applyInsertsToRows(RowIterator rows)
0763: throws AxionException {
0764: try {
0765: // write all the rows to a buffer
0766: BufferedDataOutputStream out = getOutputStream();
0767: while (rows.hasNext()) {
0768: Row row = rows.next();
0769: _rowCount++;
0770: if (!_freeIds.isEmpty() && _freeIdPos > -1) {
0771: getPidxList().set(_freeIds.removeElementAt(0),
0772: out.getPos());
0773: _freeIdPos--;
0774: } else {
0775: getPidxList().add(out.getPos());
0776: }
0777: writeRow(out, row);
0778:
0779: // drop the reference to the row form the iterator
0780: tryToRemove(rows);
0781: }
0782:
0783: _writeStream.flush();
0784: _pidx.flush();
0785: writeFridFile();
0786: _nextFreeId = -1;
0787: } catch (IOException e) {
0788: throw new AxionException("Error writing data.", e);
0789: }
0790: }
0791:
0792: private void applyUpdatesToRows(RowIterator rows)
0793: throws AxionException {
0794: try {
0795: BufferedDataOutputStream out = getOutputStream();
0796: while (rows.hasNext()) {
0797: Row row = rows.next();
0798: if (row.getIdentifier() > getPidxList().size() - 1) {
0799: throw new AxionException(
0800: "Can't update non-existent row");
0801: }
0802: // update the slot in the pidx file to point to the new data
0803: getPidxList().set(row.getIdentifier(), out.getPos());
0804: writeRow(out, row);
0805:
0806: // drop the reference to the row form the iterator
0807: tryToRemove(rows);
0808: }
0809:
0810: _writeStream.flush();
0811: _pidx.flush();
0812: getInputStream().reset();
0813: } catch (IOException e) {
0814: throw new AxionException("Error writing data.", e);
0815: }
0816: }
0817:
0818: // ----------------------------------------------------------------- Private
0819:
0820: private void createOrLoadTableFiles(String name, Database db,
0821: TableFactory factory) throws AxionException {
0822: synchronized (BaseDiskTable.class) {
0823: _dir = new File(db.getDBDirectory(), name.toUpperCase());
0824:
0825: if (!_dir.exists()) {
0826: if (!_dir.mkdirs()) {
0827: throw new AxionException(
0828: "Unable to create directory \"" + _dir
0829: + "\" for Table \"" + name + "\".");
0830: }
0831: }
0832:
0833: // create the type file if it doesn't already exist
0834: File typefile = getTableFile(TYPE_FILE_EXT);
0835: if (!typefile.exists()) {
0836: writeNameToFile(typefile, factory);
0837: }
0838:
0839: _freeIds = new ArrayIntList();
0840:
0841: loadOrMigrateMetaFile(db); // note order is signficant here!
0842: createOrLoadFreeIdsFile();
0843:
0844: initializeRowCount();
0845:
0846: // indices - directory containing index files
0847: _indexRootDir = new File(_dir, INDICES_DIR_NAME);
0848: if (_indexRootDir.exists()) {
0849: loadIndices(_indexRootDir, db);
0850: } else {
0851: _indexRootDir.mkdirs();
0852: }
0853:
0854: File seqfile = getTableFile(SEQ_FILE_EXT);
0855: if (seqfile.exists()) {
0856: loadSequences();
0857: }
0858: }
0859: }
0860:
0861: private void loadIndices(File parentdir, Database db)
0862: throws AxionException {
0863: String[] indices = parentdir.list(DOT_TYPE_FILE_FILTER);
0864: for (int i = 0; i < indices.length; i++) {
0865: File indexdir = new File(parentdir, indices[i]);
0866: File typefile = new File(indexdir, indices[i]
0867: + TYPE_FILE_EXT);
0868:
0869: String loadername = null;
0870: ObjectInputStream in = null;
0871: try {
0872: in = FS.openObjectInputSteam(typefile);
0873: loadername = in.readUTF();
0874: } catch (IOException e) {
0875: throw new AxionException(e);
0876: } finally {
0877: FS.closeInputStream(in);
0878: }
0879:
0880: IndexLoader loader = null;
0881: try {
0882: Class clazz = Class.forName(loadername);
0883: loader = (IndexLoader) (clazz.newInstance());
0884: } catch (Exception e) {
0885: throw new AxionException(e);
0886: }
0887: Index index = loader.loadIndex(this , indexdir);
0888: db.addIndex(index, this );
0889: }
0890: }
0891:
0892: private void loadSequences() throws AxionException {
0893: File seqFile = getTableFile(SEQ_FILE_EXT);
0894: DataInputStream in = null;
0895: if (seqFile.exists()) {
0896: try {
0897: in = FS.openDataInputSteam(seqFile);
0898: Sequence seq = new Sequence();
0899: seq.read(in);
0900: super .setSequence(seq);
0901: } catch (Exception e) {
0902: throw new AxionException(
0903: "Unable to read sequence file", e);
0904: } finally {
0905: FS.closeInputStream(in);
0906: }
0907: }
0908: }
0909:
0910: private void parseV0MetaFile(ObjectInputStream in)
0911: throws IOException, AxionException {
0912: int I = in.readInt(); // read number of columns
0913: for (int i = 0; i < I; i++) {
0914: String name = in.readUTF(); // read column name
0915: String dtypename = in.readUTF(); // read data type class name
0916:
0917: // create instance of datatype
0918: DataType type = null;
0919: try {
0920: Class clazz = Class.forName(dtypename);
0921: type = (DataType) (clazz.newInstance());
0922: } catch (Exception e) {
0923: throw new AxionException("Can't load table "
0924: + getName() + ", data type " + dtypename
0925: + " not found.", e);
0926: }
0927: addColumn(new Column(name, type), false);
0928: }
0929: }
0930:
0931: private void parseV1MetaFile(ObjectInputStream in, Database db)
0932: throws AxionException, IOException, ClassNotFoundException {
0933: readColumns(in);
0934: readConstraints(in, db);
0935: }
0936:
0937: // FIXME: there ought to be a better way to do this
0938: private void resetLobColumn(Column col) throws AxionException {
0939: if (col.getDataType() instanceof LOBType) {
0940: LOBType lob = (LOBType) (col.getDataType());
0941: lob.setLobDir(new File(getLobDir(), col.getName()
0942: .toUpperCase()));
0943: }
0944: }
0945:
0946: protected void resetLobColumns() throws AxionException {
0947: for (int i = 0, I = getColumnCount(); i < I; i++) {
0948: Column col = getColumn(i);
0949: resetLobColumn(col);
0950: }
0951: }
0952:
0953: private void saveIndex(Index index) throws AxionException {
0954: File dataDir = new File(_indexRootDir, index.getName());
0955: index.save(dataDir);
0956: }
0957:
0958: private void saveIndexAfterTruncate(Index index)
0959: throws AxionException {
0960: File dataDir = new File(_indexRootDir, index.getName());
0961: index.saveAfterTruncate(dataDir);
0962: }
0963:
0964: private static final FilenameFilter DOT_TYPE_FILE_FILTER = new FilenameFilter() {
0965: public boolean accept(File dir, String name) {
0966: File file = new File(dir, name);
0967: if (file.isDirectory()) {
0968: File idx = new File(file, name + TYPE_FILE_EXT);
0969: if (idx.exists()) {
0970: return true;
0971: }
0972: }
0973: return false;
0974: }
0975: };
0976:
0977: //--------------------------------------------------------------- Attributes
0978: protected static AxionFileSystem FS = new AxionFileSystem();
0979: protected static final long INVALID_OFFSET = ArrayUnsignedIntList.MAX_VALUE;
0980: protected static final int CURRENT_META_VERSION = 3;
0981:
0982: protected static final String FRID_FILE_EXT = ".FRID";
0983: protected static final String INDICES_DIR_NAME = "INDICES";
0984: protected static final String META_FILE_EXT = ".META";
0985: protected static final String PIDX_FILE_EXT = ".PIDX";
0986: protected static final String SEQ_FILE_EXT = ".SEQ";
0987: protected static final String TYPE_FILE_EXT = ".TYPE";
0988:
0989: /** The name of my ".data" file. */
0990: protected File _dataFile = null;
0991: protected File _dbdir = null;
0992:
0993: /** List of free ids. */
0994: protected IntList _freeIds = null;
0995:
0996: private int _nextFreeId = -1;
0997: private int _freeIdPos = -1;
0998:
0999: /** List of offsets into the .data file, by row id. */
1000: private AxionFileSystem.PidxList _pidx = null;
1001:
1002: protected boolean _readOnly = false;
1003: protected int _rowCount = 0;
1004:
1005: /** The directory in which my data are stored. */
1006: private File _dir = null;
1007:
1008: /** The directory in which my indices are stored. */
1009: private File _indexRootDir = null;
1010:
1011: private BufferedDataInputStream _readStream = null;
1012: private BufferedDataOutputStream _writeStream = null;
1013:
1014: private static final Log _log = LogFactory
1015: .getLog(BaseDiskTable.class);
1016:
1017: }
|