0001: /*
0002: Copyright (c) 2005 Health Market Science, Inc.
0003:
0004: This library is free software; you can redistribute it and/or
0005: modify it under the terms of the GNU Lesser General Public
0006: License as published by the Free Software Foundation; either
0007: version 2.1 of the License, or (at your option) any later version.
0008:
0009: This library is distributed in the hope that it will be useful,
0010: but WITHOUT ANY WARRANTY; without even the implied warranty of
0011: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0012: Lesser General Public License for more details.
0013:
0014: You should have received a copy of the GNU Lesser General Public
0015: License along with this library; if not, write to the Free Software
0016: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
0017: USA
0018:
0019: You can contact Health Market Science at info@healthmarketscience.com
0020: or at the following address:
0021:
0022: Health Market Science
0023: 2700 Horizon Drive
0024: Suite 200
0025: King of Prussia, PA 19406
0026: */
0027:
0028: package com.healthmarketscience.jackcess;
0029:
0030: import java.io.IOException;
0031: import java.nio.ByteBuffer;
0032: import java.util.ArrayList;
0033: import java.util.Arrays;
0034: import java.util.Collection;
0035: import java.util.Collections;
0036: import java.util.Comparator;
0037: import java.util.Iterator;
0038: import java.util.LinkedHashMap;
0039: import java.util.List;
0040: import java.util.Map;
0041:
0042: import org.apache.commons.logging.Log;
0043: import org.apache.commons.logging.LogFactory;
0044:
0045: /**
0046: * A single database table
0047: * <p>
0048: * Is not thread-safe.
0049: *
0050: * @author Tim McCune
0051: */
0052: public class Table implements Iterable<Map<String, Object>> {
0053:
0054: private static final Log LOG = LogFactory.getLog(Table.class);
0055:
0056: private static final short OFFSET_MASK = (short) 0x1FFF;
0057:
0058: private static final short DELETED_ROW_MASK = (short) 0x8000;
0059:
0060: private static final short OVERFLOW_ROW_MASK = (short) 0x4000;
0061:
0062: /** Table type code for system tables */
0063: public static final byte TYPE_SYSTEM = 0x53;
0064: /** Table type code for user tables */
0065: public static final byte TYPE_USER = 0x4e;
0066:
0067: /** comparator which sorts variable length columns vased on their index into
0068: the variable length offset table */
0069: private static final Comparator<Column> VAR_LEN_COLUMN_COMPARATOR = new Comparator<Column>() {
0070: public int compare(Column c1, Column c2) {
0071: return ((c1.getVarLenTableIndex() < c2
0072: .getVarLenTableIndex()) ? -1
0073: : ((c1.getVarLenTableIndex() > c2
0074: .getVarLenTableIndex()) ? 1 : 0));
0075: }
0076: };
0077:
0078: /** owning database */
0079: private final Database _database;
0080: /** Type of the table (either TYPE_SYSTEM or TYPE_USER) */
0081: private byte _tableType;
0082: /** Number of indexes on the table */
0083: private int _indexCount;
0084: /** Number of index slots for the table */
0085: private int _indexSlotCount;
0086: /** Number of rows in the table */
0087: private int _rowCount;
0088: /** last auto number for the table */
0089: private int _lastAutoNumber;
0090: /** page number of the definition of this table */
0091: private final int _tableDefPageNumber;
0092: /** max Number of columns in the table (includes previous deletions) */
0093: private short _maxColumnCount;
0094: /** max Number of variable columns in the table */
0095: private short _maxVarColumnCount;
0096: /** List of columns in this table, ordered by column number */
0097: private List<Column> _columns = new ArrayList<Column>();
0098: /** List of variable length columns in this table, ordered by offset */
0099: private List<Column> _varColumns = new ArrayList<Column>();
0100: /** List of indexes on this table */
0101: private List<Index> _indexes = new ArrayList<Index>();
0102: /** Table name as stored in Database */
0103: private final String _name;
0104: /** Usage map of pages that this table owns */
0105: private UsageMap _ownedPages;
0106: /** Usage map of pages that this table owns with free space on them */
0107: private UsageMap _freeSpacePages;
0108: /** modification count for the table, keeps row-states up-to-date */
0109: private int _modCount;
0110: /** page buffer used to update data pages when adding rows */
0111: private final TempPageHolder _addRowBufferH = TempPageHolder
0112: .newHolder(TempBufferHolder.Type.SOFT);
0113: /** page buffer used to update the table def page */
0114: private final TempPageHolder _tableDefBufferH = TempPageHolder
0115: .newHolder(TempBufferHolder.Type.SOFT);
0116: /** buffer used to writing single rows of data */
0117: private final TempBufferHolder _singleRowBufferH = TempBufferHolder
0118: .newHolder(TempBufferHolder.Type.SOFT, true);
0119: /** "buffer" used to writing multi rows of data (will create new buffer on
0120: every call) */
0121: private final TempBufferHolder _multiRowBufferH = TempBufferHolder
0122: .newHolder(TempBufferHolder.Type.NONE, true);
0123:
0124: /** common cursor for iterating through the table, kept here for historic
0125: reasons */
0126: private Cursor _cursor;
0127:
0128: /**
0129: * Only used by unit tests
0130: */
0131: Table(boolean testing, List<Column> columns) throws IOException {
0132: if (!testing) {
0133: throw new IllegalArgumentException();
0134: }
0135: _database = null;
0136: _tableDefPageNumber = PageChannel.INVALID_PAGE_NUMBER;
0137: _name = null;
0138: setColumns(columns);
0139: }
0140:
0141: /**
0142: * @param database database which owns this table
0143: * @param tableBuffer Buffer to read the table with
0144: * @param pageNumber Page number of the table definition
0145: * @param name Table name
0146: */
0147: protected Table(Database database, ByteBuffer tableBuffer,
0148: int pageNumber, String name) throws IOException {
0149: _database = database;
0150: _tableDefPageNumber = pageNumber;
0151: _name = name;
0152: int nextPage = tableBuffer
0153: .getInt(getFormat().OFFSET_NEXT_TABLE_DEF_PAGE);
0154: ByteBuffer nextPageBuffer = null;
0155: while (nextPage != 0) {
0156: if (nextPageBuffer == null) {
0157: nextPageBuffer = getPageChannel().createPageBuffer();
0158: }
0159: getPageChannel().readPage(nextPageBuffer, nextPage);
0160: nextPage = nextPageBuffer
0161: .getInt(getFormat().OFFSET_NEXT_TABLE_DEF_PAGE);
0162: ByteBuffer newBuffer = getPageChannel().createBuffer(
0163: tableBuffer.capacity() + getFormat().PAGE_SIZE - 8);
0164: newBuffer.put(tableBuffer);
0165: newBuffer.put(nextPageBuffer.array(), 8,
0166: getFormat().PAGE_SIZE - 8);
0167: tableBuffer = newBuffer;
0168: tableBuffer.flip();
0169: }
0170: readTableDefinition(tableBuffer);
0171: tableBuffer = null;
0172:
0173: // setup common cursor
0174: _cursor = Cursor.createCursor(this );
0175: }
0176:
0177: /**
0178: * @return The name of the table
0179: */
0180: public String getName() {
0181: return _name;
0182: }
0183:
0184: public int getMaxColumnCount() {
0185: return _maxColumnCount;
0186: }
0187:
0188: public int getColumnCount() {
0189: return _columns.size();
0190: }
0191:
0192: public Database getDatabase() {
0193: return _database;
0194: }
0195:
0196: public JetFormat getFormat() {
0197: return getDatabase().getFormat();
0198: }
0199:
0200: public PageChannel getPageChannel() {
0201: return getDatabase().getPageChannel();
0202: }
0203:
0204: protected int getTableDefPageNumber() {
0205: return _tableDefPageNumber;
0206: }
0207:
0208: public RowState createRowState() {
0209: return new RowState(TempBufferHolder.Type.HARD);
0210: }
0211:
0212: protected UsageMap.PageCursor getOwnedPagesCursor() {
0213: return _ownedPages.cursor();
0214: }
0215:
0216: /**
0217: * @return All of the columns in this table (unmodifiable List)
0218: */
0219: public List<Column> getColumns() {
0220: return Collections.unmodifiableList(_columns);
0221: }
0222:
0223: /**
0224: * @return the column with the given name
0225: */
0226: public Column getColumn(String name) {
0227: for (Column column : _columns) {
0228: if (column.getName().equals(name)) {
0229: return column;
0230: }
0231: }
0232: throw new IllegalArgumentException("Column with name " + name
0233: + " does not exist in this table");
0234: }
0235:
0236: /**
0237: * Only called by unit tests
0238: */
0239: private void setColumns(List<Column> columns) {
0240: _columns = columns;
0241: int colIdx = 0;
0242: int varLenIdx = 0;
0243: int fixedOffset = 0;
0244: for (Column col : _columns) {
0245: col.setColumnNumber((short) colIdx);
0246: col.setColumnIndex(colIdx++);
0247: if (col.isVariableLength()) {
0248: col.setVarLenTableIndex(varLenIdx++);
0249: _varColumns.add(col);
0250: } else {
0251: col.setFixedDataOffset(fixedOffset);
0252: fixedOffset += col.getType().getFixedSize();
0253: }
0254: }
0255: _maxColumnCount = (short) _columns.size();
0256: _maxVarColumnCount = (short) _varColumns.size();
0257: }
0258:
0259: /**
0260: * @return All of the Indexes on this table (unmodifiable List)
0261: */
0262: public List<Index> getIndexes() {
0263: return Collections.unmodifiableList(_indexes);
0264: }
0265:
0266: /**
0267: * @return the index with the given name
0268: */
0269: public Index getIndex(String name) {
0270: for (Index index : _indexes) {
0271: if (index.getName().equals(name)) {
0272: return index;
0273: }
0274: }
0275: throw new IllegalArgumentException("Index with name " + name
0276: + " does not exist on this table");
0277: }
0278:
0279: /**
0280: * Only called by unit tests
0281: */
0282: int getIndexSlotCount() {
0283: return _indexSlotCount;
0284: }
0285:
0286: /**
0287: * After calling this method, getNextRow will return the first row in the
0288: * table
0289: */
0290: public void reset() {
0291: _cursor.reset();
0292: }
0293:
0294: /**
0295: * Delete the current row (retrieved by a call to {@link #getNextRow()}).
0296: */
0297: public void deleteCurrentRow() throws IOException {
0298: _cursor.deleteCurrentRow();
0299: }
0300:
0301: /**
0302: * Delete the row on which the given rowState is currently positioned.
0303: */
0304: public void deleteRow(RowState rowState, RowId rowId)
0305: throws IOException {
0306: requireValidRowId(rowId);
0307:
0308: // ensure that the relevant row state is up-to-date
0309: ByteBuffer rowBuffer = positionAtRowHeader(rowState, rowId);
0310:
0311: requireNonDeletedRow(rowState, rowId);
0312:
0313: // delete flag always gets set in the "header" row (even if data is on
0314: // overflow row)
0315: int pageNumber = rowState.getHeaderRowId().getPageNumber();
0316: int rowNumber = rowState.getHeaderRowId().getRowNumber();
0317:
0318: // use any read rowValues to help update the indexes
0319: Object[] rowValues = (!_indexes.isEmpty() ? rowState
0320: .getRowValues() : null);
0321:
0322: int rowIndex = getRowStartOffset(rowNumber, getFormat());
0323: rowBuffer.putShort(rowIndex, (short) (rowBuffer
0324: .getShort(rowIndex)
0325: | DELETED_ROW_MASK | OVERFLOW_ROW_MASK));
0326: writeDataPage(rowBuffer, pageNumber);
0327:
0328: // update the indexes
0329: for (Index index : _indexes) {
0330: index.deleteRow(rowValues, rowId);
0331: }
0332:
0333: // make sure table def gets updated
0334: updateTableDefinition(-1);
0335: }
0336:
0337: /**
0338: * @return The next row in this table (Column name -> Column value)
0339: */
0340: public Map<String, Object> getNextRow() throws IOException {
0341: return getNextRow(null);
0342: }
0343:
0344: /**
0345: * @param columnNames Only column names in this collection will be returned
0346: * @return The next row in this table (Column name -> Column value)
0347: */
0348: public Map<String, Object> getNextRow(Collection<String> columnNames)
0349: throws IOException {
0350: return _cursor.getNextRow(columnNames);
0351: }
0352:
0353: /**
0354: * Reads a single column from the given row.
0355: */
0356: public Object getRowValue(RowState rowState, RowId rowId,
0357: Column column) throws IOException {
0358: if (this != column.getTable()) {
0359: throw new IllegalArgumentException("Given column " + column
0360: + " is not from this table");
0361: }
0362: requireValidRowId(rowId);
0363:
0364: // position at correct row
0365: ByteBuffer rowBuffer = positionAtRowData(rowState, rowId);
0366: requireNonDeletedRow(rowState, rowId);
0367:
0368: Object value = getRowColumn(rowBuffer,
0369: getRowNullMask(rowBuffer), column);
0370:
0371: // cache the row values in order to be able to update the index on row
0372: // deletion. note, most of the returned values are immutable, except
0373: // for binary data (returned as byte[]), but binary data shouldn't be
0374: // indexed anyway.
0375: rowState.setRowValue(column.getColumnIndex(), value);
0376:
0377: return value;
0378: }
0379:
0380: /**
0381: * Reads some columns from the given row.
0382: * @param columnNames Only column names in this collection will be returned
0383: */
0384: public Map<String, Object> getRow(RowState rowState, RowId rowId,
0385: Collection<String> columnNames) throws IOException {
0386: requireValidRowId(rowId);
0387:
0388: // position at correct row
0389: ByteBuffer rowBuffer = positionAtRowData(rowState, rowId);
0390: requireNonDeletedRow(rowState, rowId);
0391:
0392: return getRow(rowState, rowBuffer, getRowNullMask(rowBuffer),
0393: _columns, columnNames);
0394: }
0395:
0396: /**
0397: * Reads the row data from the given row buffer. Leaves limit unchanged.
0398: * Saves parsed row values to the given rowState.
0399: */
0400: private static Map<String, Object> getRow(RowState rowState,
0401: ByteBuffer rowBuffer, NullMask nullMask,
0402: Collection<Column> columns, Collection<String> columnNames)
0403: throws IOException {
0404: Map<String, Object> rtn = new LinkedHashMap<String, Object>(
0405: columns.size());
0406: for (Column column : columns) {
0407:
0408: if ((columnNames == null)
0409: || (columnNames.contains(column.getName()))) {
0410: // Add the value to the row data
0411: Object value = getRowColumn(rowBuffer, nullMask, column);
0412: rtn.put(column.getName(), value);
0413:
0414: // cache the row values in order to be able to update the index on row
0415: // deletion. note, most of the returned values are immutable, except
0416: // for binary data (returned as byte[]), but binary data shouldn't be
0417: // indexed anyway.
0418: rowState.setRowValue(column.getColumnIndex(), value);
0419: }
0420: }
0421: return rtn;
0422: }
0423:
0424: /**
0425: * Reads the column data from the given row buffer. Leaves limit unchanged.
0426: */
0427: private static Object getRowColumn(ByteBuffer rowBuffer,
0428: NullMask nullMask, Column column) throws IOException {
0429: boolean isNull = nullMask.isNull(column);
0430: if (column.getType() == DataType.BOOLEAN) {
0431: return Boolean.valueOf(!isNull); //Boolean values are stored in the null mask
0432: } else if (isNull) {
0433: // well, that's easy!
0434: return null;
0435: }
0436:
0437: // reset position to row start
0438: rowBuffer.reset();
0439:
0440: // locate the column data bytes
0441: int rowStart = rowBuffer.position();
0442: int colDataPos = 0;
0443: int colDataLen = 0;
0444: if (!column.isVariableLength()) {
0445:
0446: // read fixed length value (non-boolean at this point)
0447: int dataStart = rowStart + 2;
0448: colDataPos = dataStart + column.getFixedDataOffset();
0449: colDataLen = column.getType().getFixedSize();
0450:
0451: } else {
0452:
0453: // read var length value
0454: int varColumnOffsetPos = (rowBuffer.limit()
0455: - nullMask.byteSize() - 4)
0456: - (column.getVarLenTableIndex() * 2);
0457:
0458: short varDataStart = rowBuffer.getShort(varColumnOffsetPos);
0459: short varDataEnd = rowBuffer
0460: .getShort(varColumnOffsetPos - 2);
0461: colDataPos = rowStart + varDataStart;
0462: colDataLen = varDataEnd - varDataStart;
0463: }
0464:
0465: // grab the column data
0466: byte[] columnData = new byte[colDataLen];
0467: rowBuffer.position(colDataPos);
0468: rowBuffer.get(columnData);
0469:
0470: // parse the column data
0471: return column.read(columnData);
0472: }
0473:
0474: /**
0475: * Reads the null mask from the given row buffer. Leaves limit unchanged.
0476: */
0477: private static NullMask getRowNullMask(ByteBuffer rowBuffer)
0478: throws IOException {
0479: // reset position to row start
0480: rowBuffer.reset();
0481:
0482: short columnCount = rowBuffer.getShort(); // Number of columns in this row
0483:
0484: // read null mask
0485: NullMask nullMask = new NullMask(columnCount);
0486: rowBuffer.position(rowBuffer.limit() - nullMask.byteSize()); //Null mask at end
0487: nullMask.read(rowBuffer);
0488:
0489: return nullMask;
0490: }
0491:
0492: /**
0493: * Sets a new buffer to the correct row header page using the given rowState
0494: * according to the given rowId. Deleted state is
0495: * determined, but overflow row pointers are not followed.
0496: *
0497: * @return a ByteBuffer of the relevant page, or null if row was invalid
0498: */
0499: public static ByteBuffer positionAtRowHeader(RowState rowState,
0500: RowId rowId) throws IOException {
0501: ByteBuffer rowBuffer = rowState.setHeaderRow(rowId);
0502:
0503: if (rowState.isAtHeaderRow()) {
0504: // this task has already been accomplished
0505: return rowBuffer;
0506: }
0507:
0508: if (!rowState.isValid()) {
0509: // this was an invalid page/row
0510: rowState.setStatus(RowStateStatus.AT_HEADER);
0511: return null;
0512: }
0513:
0514: // note, we don't use findRowStart here cause we need the unmasked value
0515: short rowStart = rowBuffer.getShort(getRowStartOffset(rowId
0516: .getRowNumber(), rowState.getTable().getFormat()));
0517:
0518: // check the deleted, overflow flags for the row (the "real" flags are
0519: // always set on the header row)
0520: RowStatus rowStatus = RowStatus.NORMAL;
0521: if (isDeletedRow(rowStart)) {
0522: rowStatus = RowStatus.DELETED;
0523: } else if (isOverflowRow(rowStart)) {
0524: rowStatus = RowStatus.OVERFLOW;
0525: }
0526:
0527: rowState.setRowStatus(rowStatus);
0528: rowState.setStatus(RowStateStatus.AT_HEADER);
0529: return rowBuffer;
0530: }
0531:
0532: /**
0533: * Sets the position and limit in a new buffer using the given rowState
0534: * according to the given row number and row end, following overflow row
0535: * pointers as necessary.
0536: *
0537: * @return a ByteBuffer narrowed to the actual row data, or null if row was
0538: * invalid or deleted
0539: */
0540: public static ByteBuffer positionAtRowData(RowState rowState,
0541: RowId rowId) throws IOException {
0542: positionAtRowHeader(rowState, rowId);
0543: if (!rowState.isValid() || rowState.isDeleted()) {
0544: // row is invalid or deleted
0545: rowState.setStatus(RowStateStatus.AT_FINAL);
0546: return null;
0547: }
0548:
0549: ByteBuffer rowBuffer = rowState.getFinalPage();
0550: int rowNum = rowState.getFinalRowId().getRowNumber();
0551: JetFormat format = rowState.getTable().getFormat();
0552:
0553: if (rowState.isAtFinalRow()) {
0554: // we've already found the final row data
0555: return PageChannel.narrowBuffer(rowBuffer, findRowStart(
0556: rowBuffer, rowNum, format), findRowEnd(rowBuffer,
0557: rowNum, format));
0558: }
0559:
0560: while (true) {
0561:
0562: // note, we don't use findRowStart here cause we need the unmasked value
0563: short rowStart = rowBuffer.getShort(getRowStartOffset(
0564: rowNum, format));
0565: short rowEnd = findRowEnd(rowBuffer, rowNum, format);
0566:
0567: // note, at this point we know the row is not deleted, so ignore any
0568: // subsequent deleted flags (as overflow rows are always marked deleted
0569: // anyway)
0570: boolean overflowRow = isOverflowRow(rowStart);
0571:
0572: // now, strip flags from rowStart offset
0573: rowStart = (short) (rowStart & OFFSET_MASK);
0574:
0575: if (overflowRow) {
0576:
0577: if ((rowEnd - rowStart) < 4) {
0578: throw new IOException("invalid overflow row info");
0579: }
0580:
0581: // Overflow page. the "row" data in the current page points to
0582: // another page/row
0583: int overflowRowNum = ByteUtil.getUnsignedByte(
0584: rowBuffer, rowStart);
0585: int overflowPageNum = ByteUtil.get3ByteInt(rowBuffer,
0586: rowStart + 1);
0587: rowBuffer = rowState.setOverflowRow(new RowId(
0588: overflowPageNum, overflowRowNum));
0589: rowNum = overflowRowNum;
0590:
0591: } else {
0592:
0593: rowState.setStatus(RowStateStatus.AT_FINAL);
0594: return PageChannel.narrowBuffer(rowBuffer, rowStart,
0595: rowEnd);
0596: }
0597: }
0598: }
0599:
0600: /**
0601: * Calls <code>reset</code> on this table and returns an unmodifiable
0602: * Iterator which will iterate through all the rows of this table. Use of
0603: * the Iterator follows the same restrictions as a call to
0604: * <code>getNextRow</code>.
0605: * @throws IllegalStateException if an IOException is thrown by one of the
0606: * operations, the actual exception will be contained within
0607: */
0608: public Iterator<Map<String, Object>> iterator() {
0609: return iterator(null);
0610: }
0611:
0612: /**
0613: * Calls <code>reset</code> on this table and returns an unmodifiable
0614: * Iterator which will iterate through all the rows of this table, returning
0615: * only the given columns. Use of the Iterator follows the same
0616: * restrictions as a call to <code>getNextRow</code>.
0617: * @throws IllegalStateException if an IOException is thrown by one of the
0618: * operations, the actual exception will be contained within
0619: */
0620: public Iterator<Map<String, Object>> iterator(
0621: Collection<String> columnNames) {
0622: reset();
0623: return _cursor.iterator(columnNames);
0624: }
0625:
0626: /**
0627: * Writes a new table defined by the given columns to the database.
0628: * @return the first page of the new table's definition
0629: */
0630: public static int writeTableDefinition(List<Column> columns,
0631: PageChannel pageChannel, JetFormat format)
0632: throws IOException {
0633: // first, create the usage map page
0634: int usageMapPageNumber = pageChannel
0635: .writeNewPage(createUsageMapDefinitionBuffer(
0636: pageChannel, format));
0637:
0638: // next, determine how big the table def will be (in case it will be more
0639: // than one page)
0640: int totalTableDefSize = format.SIZE_TDEF_HEADER
0641: + (format.SIZE_COLUMN_DEF_BLOCK * columns.size())
0642: + format.SIZE_TDEF_TRAILER;
0643: for (Column col : columns) {
0644: // we add the number of bytes for the column name and 2 bytes for the
0645: // length of the column name
0646: int nameByteLen = (col.getName().length() * JetFormat.TEXT_FIELD_UNIT_SIZE);
0647: totalTableDefSize += nameByteLen + 2;
0648: }
0649:
0650: // now, create the table definition
0651: ByteBuffer buffer = pageChannel.createBuffer(Math.max(
0652: totalTableDefSize, format.PAGE_SIZE));
0653: writeTableDefinitionHeader(buffer, columns, usageMapPageNumber,
0654: totalTableDefSize, format);
0655: writeColumnDefinitions(buffer, columns, format);
0656:
0657: //End of tabledef
0658: buffer.put((byte) 0xff);
0659: buffer.put((byte) 0xff);
0660:
0661: // write table buffer to database
0662: int tdefPageNumber = PageChannel.INVALID_PAGE_NUMBER;
0663: if (totalTableDefSize <= format.PAGE_SIZE) {
0664:
0665: // easy case, fits on one page
0666: buffer.putShort(format.OFFSET_FREE_SPACE, (short) (buffer
0667: .remaining() - 8)); // overwrite page free space
0668: // Write the tdef page to disk.
0669: tdefPageNumber = pageChannel.writeNewPage(buffer);
0670:
0671: } else {
0672:
0673: // need to split across multiple pages
0674: ByteBuffer partialTdef = pageChannel.createPageBuffer();
0675: buffer.rewind();
0676: int nextTdefPageNumber = PageChannel.INVALID_PAGE_NUMBER;
0677: while (buffer.hasRemaining()) {
0678:
0679: // reset for next write
0680: partialTdef.clear();
0681:
0682: if (tdefPageNumber == PageChannel.INVALID_PAGE_NUMBER) {
0683:
0684: // this is the first page. note, the first page already has the
0685: // page header, so no need to write it here
0686: tdefPageNumber = pageChannel.allocateNewPage();
0687: nextTdefPageNumber = tdefPageNumber;
0688:
0689: } else {
0690:
0691: // write page header
0692: writeTablePageHeader(partialTdef);
0693: }
0694:
0695: // copy the next page of tdef bytes
0696: int curTdefPageNumber = nextTdefPageNumber;
0697: int writeLen = Math.min(partialTdef.remaining(), buffer
0698: .remaining());
0699: partialTdef.put(buffer.array(), buffer.position(),
0700: writeLen);
0701: buffer.position(buffer.position() + writeLen);
0702:
0703: if (buffer.hasRemaining()) {
0704: // need a next page
0705: nextTdefPageNumber = pageChannel.allocateNewPage();
0706: partialTdef.putInt(
0707: format.OFFSET_NEXT_TABLE_DEF_PAGE,
0708: nextTdefPageNumber);
0709: }
0710:
0711: // update page free space
0712: partialTdef.putShort(format.OFFSET_FREE_SPACE,
0713: (short) (partialTdef.remaining() - 8)); // overwrite page free space
0714:
0715: // write partial page to disk
0716: pageChannel.writePage(partialTdef, curTdefPageNumber);
0717: }
0718:
0719: }
0720:
0721: return tdefPageNumber;
0722: }
0723:
0724: /**
0725: * @param buffer Buffer to write to
0726: * @param columns List of Columns in the table
0727: */
0728: private static void writeTableDefinitionHeader(ByteBuffer buffer,
0729: List<Column> columns, int usageMapPageNumber,
0730: int totalTableDefSize, JetFormat format) throws IOException {
0731: //Start writing the tdef
0732: writeTablePageHeader(buffer);
0733: buffer.putInt(totalTableDefSize); //Length of table def
0734: buffer.put((byte) 0x59); //Unknown
0735: buffer.put((byte) 0x06); //Unknown
0736: buffer.putShort((short) 0); //Unknown
0737: buffer.putInt(0); //Number of rows
0738: buffer.putInt(0); //Last Autonumber
0739: if (countAutoNumberColumns(columns) > 0) {
0740: buffer.put((byte) 1);
0741: } else {
0742: buffer.put((byte) 0);
0743: }
0744: for (int i = 0; i < 15; i++) { //Unknown
0745: buffer.put((byte) 0);
0746: }
0747: buffer.put(Table.TYPE_USER); //Table type
0748: buffer.putShort((short) columns.size()); //Max columns a row will have
0749: buffer.putShort(Column.countVariableLength(columns)); //Number of variable columns in table
0750: buffer.putShort((short) columns.size()); //Number of columns in table
0751: buffer.putInt(0); //Number of indexes in table
0752: buffer.putInt(0); //Number of indexes in table
0753: buffer.put((byte) 0); //Usage map row number
0754: ByteUtil.put3ByteInt(buffer, usageMapPageNumber); //Usage map page number
0755: buffer.put((byte) 1); //Free map row number
0756: ByteUtil.put3ByteInt(buffer, usageMapPageNumber); //Free map page number
0757: if (LOG.isDebugEnabled()) {
0758: int position = buffer.position();
0759: buffer.rewind();
0760: LOG.debug("Creating new table def block:\n"
0761: + ByteUtil.toHexString(buffer,
0762: format.SIZE_TDEF_HEADER));
0763: buffer.position(position);
0764: }
0765: }
0766:
0767: /**
0768: * Writes the page header for a table definition page
0769: * @param buffer Buffer to write to
0770: */
0771: private static void writeTablePageHeader(ByteBuffer buffer) {
0772: buffer.put(PageTypes.TABLE_DEF); //Page type
0773: buffer.put((byte) 0x01); //Unknown
0774: buffer.put((byte) 0); //Unknown
0775: buffer.put((byte) 0); //Unknown
0776: buffer.putInt(0); //Next TDEF page pointer
0777: }
0778:
0779: /**
0780: * @param buffer Buffer to write to
0781: * @param columns List of Columns to write definitions for
0782: */
0783: private static void writeColumnDefinitions(ByteBuffer buffer,
0784: List<Column> columns, JetFormat format) throws IOException {
0785: short columnNumber = (short) 0;
0786: short fixedOffset = (short) 0;
0787: short variableOffset = (short) 0;
0788: // we specifically put the "long variable" values after the normal
0789: // variable length values so that we have a better chance of fitting it
0790: // all (because "long variable" values can go in separate pages)
0791: short longVariableOffset = Column
0792: .countNonLongVariableLength(columns);
0793: for (Column col : columns) {
0794: int position = buffer.position();
0795: buffer.put(col.getType().getValue());
0796: buffer.put((byte) 0x59); //Unknown
0797: buffer.put((byte) 0x06); //Unknown
0798: buffer.putShort((short) 0); //Unknown
0799: buffer.putShort(columnNumber); //Column Number
0800: if (col.isVariableLength()) {
0801: if (!col.getType().isLongValue()) {
0802: buffer.putShort(variableOffset++);
0803: } else {
0804: buffer.putShort(longVariableOffset++);
0805: }
0806: } else {
0807: buffer.putShort((short) 0);
0808: }
0809: buffer.putShort(columnNumber); //Column Number again
0810: if (col.getType().getHasScalePrecision()) {
0811: buffer.put(col.getPrecision()); // numeric precision
0812: buffer.put(col.getScale()); // numeric scale
0813: } else {
0814: buffer.put((byte) 0x00); //unused
0815: buffer.put((byte) 0x00); //unused
0816: }
0817: buffer.putShort((short) 0); //Unknown
0818: buffer.put(getColumnBitFlags(col)); // misc col flags
0819: if (col.isCompressedUnicode()) { //Compressed
0820: buffer.put((byte) 1);
0821: } else {
0822: buffer.put((byte) 0);
0823: }
0824: buffer.putInt(0); //Unknown, but always 0.
0825: //Offset for fixed length columns
0826: if (col.isVariableLength()) {
0827: buffer.putShort((short) 0);
0828: } else {
0829: buffer.putShort(fixedOffset);
0830: fixedOffset += col.getType().getFixedSize();
0831: }
0832: if (!col.getType().isLongValue()) {
0833: buffer.putShort(col.getLength()); //Column length
0834: } else {
0835: buffer.putShort((short) 0x0000); // unused
0836: }
0837: columnNumber++;
0838: if (LOG.isDebugEnabled()) {
0839: LOG.debug("Creating new column def block\n"
0840: + ByteUtil.toHexString(buffer, position,
0841: format.SIZE_COLUMN_DEF_BLOCK));
0842: }
0843: }
0844: for (Column col : columns) {
0845: writeName(buffer, col.getName(), format);
0846: }
0847: }
0848:
0849: /**
0850: * Writes the given name into the given buffer in the format as expected by
0851: * {@link #readName}.
0852: */
0853: private static void writeName(ByteBuffer buffer, String name,
0854: JetFormat format) {
0855: ByteBuffer encName = Column
0856: .encodeUncompressedText(name, format);
0857: buffer.putShort((short) encName.remaining());
0858: buffer.put(encName);
0859: }
0860:
0861: /**
0862: * Constructs a byte containing the flags for the given column.
0863: */
0864: private static byte getColumnBitFlags(Column col) {
0865: byte flags = Column.UNKNOWN_FLAG_MASK;
0866: if (!col.isVariableLength()) {
0867: flags |= Column.FIXED_LEN_FLAG_MASK;
0868: }
0869: if (col.isAutoNumber()) {
0870: flags |= Column.AUTO_NUMBER_FLAG_MASK;
0871: }
0872: return flags;
0873: }
0874:
0875: /**
0876: * Create the usage map definition page buffer. The "used pages" map is in
0877: * row 0, the "pages with free space" map is in row 1.
0878: */
0879: private static ByteBuffer createUsageMapDefinitionBuffer(
0880: PageChannel pageChannel, JetFormat format)
0881: throws IOException {
0882: // USAGE_MAP_DEF_FREE_SPACE = 3940;
0883: int usageMapRowLength = format.OFFSET_USAGE_MAP_START
0884: + format.USAGE_MAP_TABLE_BYTE_LENGTH;
0885: int freeSpace = getRowSpaceUsage(format.MAX_ROW_SIZE, format)
0886: - (2 * getRowSpaceUsage(usageMapRowLength, format));
0887:
0888: ByteBuffer rtn = pageChannel.createPageBuffer();
0889: rtn.put(PageTypes.DATA);
0890: rtn.put((byte) 0x1); //Unknown
0891: rtn.putShort((short) freeSpace); //Free space in page
0892: rtn.putInt(0); //Table definition
0893: rtn.putInt(0); //Unknown
0894: rtn.putShort((short) 2); //Number of records on this page
0895:
0896: // write two rows of usage map definitions
0897: int rowStart = findRowEnd(rtn, 0, format) - usageMapRowLength;
0898: for (int i = 0; i < 2; ++i) {
0899: rtn
0900: .putShort(getRowStartOffset(i, format),
0901: (short) rowStart);
0902: if (i == 0) {
0903: // initial "usage pages" map definition
0904: rtn.put(rowStart, UsageMap.MAP_TYPE_REFERENCE);
0905: } else {
0906: // initial "pages with free space" map definition
0907: rtn.put(rowStart, UsageMap.MAP_TYPE_INLINE);
0908: }
0909: rowStart -= usageMapRowLength;
0910: }
0911:
0912: return rtn;
0913: }
0914:
0915: /**
0916: * Read the table definition
0917: */
0918: private void readTableDefinition(ByteBuffer tableBuffer)
0919: throws IOException {
0920: if (LOG.isDebugEnabled()) {
0921: tableBuffer.rewind();
0922: LOG.debug("Table def block:\n"
0923: + ByteUtil.toHexString(tableBuffer,
0924: getFormat().SIZE_TDEF_HEADER));
0925: }
0926: _rowCount = tableBuffer.getInt(getFormat().OFFSET_NUM_ROWS);
0927: _lastAutoNumber = tableBuffer
0928: .getInt(getFormat().OFFSET_NEXT_AUTO_NUMBER);
0929: _tableType = tableBuffer.get(getFormat().OFFSET_TABLE_TYPE);
0930: _maxColumnCount = tableBuffer
0931: .getShort(getFormat().OFFSET_MAX_COLS);
0932: _maxVarColumnCount = tableBuffer
0933: .getShort(getFormat().OFFSET_NUM_VAR_COLS);
0934: short columnCount = tableBuffer
0935: .getShort(getFormat().OFFSET_NUM_COLS);
0936: _indexSlotCount = tableBuffer
0937: .getInt(getFormat().OFFSET_NUM_INDEX_SLOTS);
0938: _indexCount = tableBuffer
0939: .getInt(getFormat().OFFSET_NUM_INDEXES);
0940:
0941: int rowNum = ByteUtil.getUnsignedByte(tableBuffer,
0942: getFormat().OFFSET_OWNED_PAGES);
0943: int pageNum = ByteUtil.get3ByteInt(tableBuffer,
0944: getFormat().OFFSET_OWNED_PAGES + 1);
0945: _ownedPages = UsageMap.read(getDatabase(), pageNum, rowNum,
0946: false);
0947: rowNum = ByteUtil.getUnsignedByte(tableBuffer,
0948: getFormat().OFFSET_FREE_SPACE_PAGES);
0949: pageNum = ByteUtil.get3ByteInt(tableBuffer,
0950: getFormat().OFFSET_FREE_SPACE_PAGES + 1);
0951: _freeSpacePages = UsageMap.read(getDatabase(), pageNum, rowNum,
0952: false);
0953:
0954: for (int i = 0; i < _indexCount; i++) {
0955: int uniqueEntryCountOffset = (getFormat().OFFSET_INDEX_DEF_BLOCK
0956: + (i * getFormat().SIZE_INDEX_DEFINITION) + 4);
0957: int uniqueEntryCount = tableBuffer
0958: .getInt(uniqueEntryCountOffset);
0959: _indexes.add(new Index(this , uniqueEntryCount,
0960: uniqueEntryCountOffset));
0961: }
0962:
0963: int colOffset = getFormat().OFFSET_INDEX_DEF_BLOCK
0964: + _indexCount * getFormat().SIZE_INDEX_DEFINITION;
0965: for (int i = 0; i < columnCount; i++) {
0966: Column column = new Column(this , tableBuffer, colOffset
0967: + (i * getFormat().SIZE_COLUMN_HEADER));
0968: _columns.add(column);
0969: if (column.isVariableLength()) {
0970: // also shove it in the variable columns list, which is ordered
0971: // differently from the _columns list
0972: _varColumns.add(column);
0973: }
0974: }
0975: tableBuffer.position(colOffset
0976: + (columnCount * getFormat().SIZE_COLUMN_HEADER));
0977: for (int i = 0; i < columnCount; i++) {
0978: Column column = _columns.get(i);
0979: column.setName(readName(tableBuffer));
0980: }
0981: Collections.sort(_columns);
0982:
0983: // setup the data index for the columns
0984: int colIdx = 0;
0985: for (Column col : _columns) {
0986: col.setColumnIndex(colIdx++);
0987: }
0988:
0989: // sort variable length columns based on their index into the variable
0990: // length offset table, because we will write the columns in this order
0991: Collections.sort(_varColumns, VAR_LEN_COLUMN_COMPARATOR);
0992:
0993: int idxOffset = tableBuffer.position();
0994: tableBuffer
0995: .position(idxOffset
0996: + (getFormat().OFFSET_INDEX_NUMBER_BLOCK * _indexCount));
0997:
0998: // if there are more index slots than indexes, the initial slots are
0999: // always empty/invalid, so we skip that data
1000: int firstRealIdx = (_indexSlotCount - _indexCount);
1001:
1002: for (int i = 0; i < _indexSlotCount; i++) {
1003:
1004: tableBuffer.getInt(); //Forward past Unknown
1005: tableBuffer.getInt(); //Forward past alternate index number
1006: int indexNumber = tableBuffer.getInt();
1007: tableBuffer.position(tableBuffer.position() + 11);
1008: byte indexType = tableBuffer.get();
1009: tableBuffer.position(tableBuffer.position() + 4);
1010:
1011: if (i < firstRealIdx) {
1012: // ignore this info
1013: continue;
1014: }
1015:
1016: Index index = _indexes.get(i - firstRealIdx);
1017: index.setIndexNumber(indexNumber);
1018: index.setIndexType(indexType);
1019: }
1020:
1021: // read actual index names
1022: for (int i = 0; i < _indexSlotCount; i++) {
1023: if (i < firstRealIdx) {
1024: // for each empty index slot, there is some weird sort of name, skip
1025: // it
1026: skipName(tableBuffer);
1027: continue;
1028: }
1029:
1030: _indexes.get(i - firstRealIdx).setName(
1031: readName(tableBuffer));
1032: }
1033: int idxEndOffset = tableBuffer.position();
1034:
1035: Collections.sort(_indexes);
1036:
1037: // go back to index column info after sorting
1038: tableBuffer.position(idxOffset);
1039: for (int i = 0; i < _indexCount; i++) {
1040: tableBuffer.getInt(); //Forward past Unknown
1041: _indexes.get(i).read(tableBuffer, _columns);
1042: }
1043:
1044: // reset to end of index info
1045: tableBuffer.position(idxEndOffset);
1046: }
1047:
1048: /**
1049: * Writes the given page data to the given page number, clears any other
1050: * relevant buffers.
1051: */
1052: private void writeDataPage(ByteBuffer pageBuffer, int pageNumber)
1053: throws IOException {
1054: // write the page data
1055: getPageChannel().writePage(pageBuffer, pageNumber);
1056:
1057: // possibly invalidate the add row buffer if a different data buffer is
1058: // being written (e.g. this happens during deleteRow)
1059: _addRowBufferH.possiblyInvalidate(pageNumber, pageBuffer);
1060:
1061: // update modification count so any active RowStates can keep themselves
1062: // up-to-date
1063: ++_modCount;
1064: }
1065:
1066: /**
1067: * Returns a name read from the buffer at the current position. The
1068: * expected name format is the name length as a short followed by (length *
1069: * 2) bytes encoded using the {@link JetFormat#CHARSET}
1070: */
1071: private String readName(ByteBuffer buffer) {
1072: short nameLength = buffer.getShort();
1073: byte[] nameBytes = new byte[nameLength];
1074: buffer.get(nameBytes);
1075: return Column.decodeUncompressedText(nameBytes, getFormat());
1076: }
1077:
1078: /**
1079: * Skips past a name int the buffer at the current position. The
1080: * expected name format is the same as that for {@link #readName}.
1081: */
1082: private void skipName(ByteBuffer buffer) {
1083: short nameLength = buffer.getShort();
1084: buffer.position(buffer.position() + nameLength);
1085: }
1086:
1087: /**
1088: * Converts a map of columnName -> columnValue to an array of row values
1089: * appropriate for a call to {@link #addRow(Object...)}.
1090: */
1091: public Object[] asRow(Map<String, Object> rowMap) {
1092: Object[] row = new Object[_columns.size()];
1093: if (rowMap == null) {
1094: return row;
1095: }
1096: for (Column col : _columns) {
1097: row[col.getColumnIndex()] = rowMap.get(col.getName());
1098: }
1099: return row;
1100: }
1101:
1102: /**
1103: * Add a single row to this table and write it to disk
1104: */
1105: public void addRow(Object... row) throws IOException {
1106: addRows(Collections.singletonList(row), _singleRowBufferH);
1107: }
1108:
1109: /**
1110: * Add multiple rows to this table, only writing to disk after all
1111: * rows have been written, and every time a data page is filled. This
1112: * is much more efficient than calling <code>addRow</code> multiple times.
1113: * @param rows List of Object[] row values
1114: */
1115: public void addRows(List<? extends Object[]> rows)
1116: throws IOException {
1117: addRows(rows, _multiRowBufferH);
1118: }
1119:
1120: /**
1121: * Add multiple rows to this table, only writing to disk after all
1122: * rows have been written, and every time a data page is filled. This
1123: * is much more efficient than calling <code>addRow</code> multiple times.
1124: * @param rows List of Object[] row values
1125: * @param writeRowBufferH TempBufferHolder used to generate buffers for
1126: * writing the row data
1127: */
1128: private void addRows(List<? extends Object[]> rows,
1129: TempBufferHolder writeRowBufferH) throws IOException {
1130: ByteBuffer[] rowData = new ByteBuffer[rows.size()];
1131: Iterator<? extends Object[]> iter = rows.iterator();
1132: for (int i = 0; iter.hasNext(); i++) {
1133: rowData[i] = createRow(iter.next(),
1134: getFormat().MAX_ROW_SIZE, writeRowBufferH
1135: .getPageBuffer(getPageChannel()));
1136: if (rowData[i].limit() > getFormat().MAX_ROW_SIZE) {
1137: throw new IOException("Row size " + rowData[i].limit()
1138: + " is too large");
1139: }
1140: }
1141:
1142: ByteBuffer dataPage = null;
1143: int pageNumber = PageChannel.INVALID_PAGE_NUMBER;
1144:
1145: // find last data page (Not bothering to check other pages for free
1146: // space.)
1147: UsageMap.PageCursor revPageCursor = _ownedPages.cursor();
1148: revPageCursor.afterLast();
1149: while (true) {
1150: int tmpPageNumber = revPageCursor.getPreviousPage();
1151: if (tmpPageNumber < 0) {
1152: break;
1153: }
1154: dataPage = _addRowBufferH.setPage(getPageChannel(),
1155: tmpPageNumber);
1156: if (dataPage.get() == PageTypes.DATA) {
1157: // found last data page, only use if actually listed in free space
1158: // pages
1159: if (_freeSpacePages.containsPageNumber(tmpPageNumber)) {
1160: pageNumber = tmpPageNumber;
1161: }
1162: break;
1163: }
1164: }
1165:
1166: if (pageNumber == PageChannel.INVALID_PAGE_NUMBER) {
1167: // No data pages exist (with free space). Create a new one.
1168: dataPage = newDataPage();
1169: pageNumber = _addRowBufferH.getPageNumber();
1170: }
1171:
1172: for (int i = 0; i < rowData.length; i++) {
1173: int rowSize = rowData[i].remaining();
1174: int rowSpaceUsage = getRowSpaceUsage(rowSize, getFormat());
1175: short freeSpaceInPage = dataPage
1176: .getShort(getFormat().OFFSET_FREE_SPACE);
1177: if (freeSpaceInPage < rowSpaceUsage) {
1178:
1179: // Last data page is full. Create a new one.
1180: writeDataPage(dataPage, pageNumber);
1181: _freeSpacePages.removePageNumber(pageNumber);
1182:
1183: dataPage = newDataPage();
1184: pageNumber = _addRowBufferH.getPageNumber();
1185:
1186: freeSpaceInPage = dataPage
1187: .getShort(getFormat().OFFSET_FREE_SPACE);
1188: }
1189:
1190: // write out the row data
1191: int rowNum = addDataPageRow(dataPage, rowSize, getFormat());
1192: dataPage.put(rowData[i]);
1193:
1194: // update the indexes
1195: for (Index index : _indexes) {
1196: index
1197: .addRow(rows.get(i), new RowId(pageNumber,
1198: rowNum));
1199: }
1200: }
1201: writeDataPage(dataPage, pageNumber);
1202:
1203: // Update tdef page
1204: updateTableDefinition(rows.size());
1205: }
1206:
1207: /**
1208: * Updates the table definition after rows are modified.
1209: */
1210: private void updateTableDefinition(int rowCountInc)
1211: throws IOException {
1212: // load table definition
1213: ByteBuffer tdefPage = _tableDefBufferH.setPage(
1214: getPageChannel(), _tableDefPageNumber);
1215:
1216: // make sure rowcount and autonumber are up-to-date
1217: _rowCount += rowCountInc;
1218: tdefPage.putInt(getFormat().OFFSET_NUM_ROWS, _rowCount);
1219: tdefPage.putInt(getFormat().OFFSET_NEXT_AUTO_NUMBER,
1220: _lastAutoNumber);
1221:
1222: // write any index changes
1223: Iterator<Index> indIter = _indexes.iterator();
1224: for (int i = 0; i < _indexes.size(); i++) {
1225: Index index = indIter.next();
1226: // write the unique entry count for the index to the table definition
1227: // page
1228: tdefPage.putInt(index.getUniqueEntryCountOffset(), index
1229: .getUniqueEntryCount());
1230: // write the entry page for the index
1231: index.update();
1232: }
1233:
1234: // write modified table definition
1235: getPageChannel().writePage(tdefPage, _tableDefPageNumber);
1236: }
1237:
1238: /**
1239: * Create a new data page
1240: * @return Page number of the new page
1241: */
1242: private ByteBuffer newDataPage() throws IOException {
1243: if (LOG.isDebugEnabled()) {
1244: LOG.debug("Creating new data page");
1245: }
1246: ByteBuffer dataPage = _addRowBufferH
1247: .setNewPage(getPageChannel());
1248: dataPage.put(PageTypes.DATA); //Page type
1249: dataPage.put((byte) 1); //Unknown
1250: dataPage.putShort((short) getRowSpaceUsage(
1251: getFormat().MAX_ROW_SIZE, getFormat())); //Free space in this page
1252: dataPage.putInt(_tableDefPageNumber); //Page pointer to table definition
1253: dataPage.putInt(0); //Unknown
1254: dataPage.putInt(0); //Number of records on this page
1255: int pageNumber = _addRowBufferH.getPageNumber();
1256: getPageChannel().writePage(dataPage, pageNumber);
1257: _ownedPages.addPageNumber(pageNumber);
1258: _freeSpacePages.addPageNumber(pageNumber);
1259: return dataPage;
1260: }
1261:
1262: /**
1263: * Serialize a row of Objects into a byte buffer
1264: */
1265: ByteBuffer createRow(Object[] rowArray, int maxRowSize,
1266: ByteBuffer buffer) throws IOException {
1267: buffer.putShort(_maxColumnCount);
1268: NullMask nullMask = new NullMask(_maxColumnCount);
1269:
1270: List<Object> row = new ArrayList<Object>(_columns.size());
1271: for (Object rowValue : rowArray) {
1272: row.add(rowValue);
1273: }
1274: //Append null for arrays that are too small
1275: for (int i = rowArray.length; i < _columns.size(); i++) {
1276: row.add(null);
1277: }
1278:
1279: //Fixed length column data comes first
1280: int fixedDataStart = buffer.position();
1281: int fixedDataEnd = fixedDataStart;
1282: for (Column col : _columns) {
1283:
1284: if (!col.isVariableLength()) {
1285:
1286: Object rowValue = row.get(col.getColumnIndex());
1287:
1288: if (col.getType() == DataType.BOOLEAN) {
1289:
1290: if (Column.toBooleanValue(rowValue)) {
1291: //Booleans are stored in the null mask
1292: nullMask.markNotNull(col);
1293: }
1294:
1295: } else {
1296:
1297: if (col.isAutoNumber()) {
1298: // ignore given row value, use next autonumber
1299: rowValue = getNextAutoNumber();
1300: }
1301:
1302: if (rowValue != null) {
1303:
1304: // we have a value
1305: nullMask.markNotNull(col);
1306:
1307: //remainingRowLength is ignored when writing fixed length data
1308: buffer.position(fixedDataStart
1309: + col.getFixedDataOffset());
1310: buffer.put(col.write(rowValue, 0));
1311:
1312: // keep track of the end of fixed data
1313: if (buffer.position() > fixedDataEnd) {
1314: fixedDataEnd = buffer.position();
1315: }
1316: }
1317: }
1318: }
1319: }
1320:
1321: // reposition at end of fixed data
1322: buffer.position(fixedDataEnd);
1323:
1324: // only need this info if this table contains any var length data
1325: if (_maxVarColumnCount > 0) {
1326:
1327: // figure out how much space remains for var length data. first,
1328: // account for already written space
1329: maxRowSize -= buffer.position();
1330: // now, account for trailer space
1331: maxRowSize -= (nullMask.byteSize() + 4 + (_maxVarColumnCount * 2));
1332:
1333: //Now write out variable length column data
1334: short[] varColumnOffsets = new short[_maxVarColumnCount];
1335: int varColumnOffsetsIndex = 0;
1336: for (Column varCol : _varColumns) {
1337: short offset = (short) buffer.position();
1338: Object rowValue = row.get(varCol.getColumnIndex());
1339: if (rowValue != null) {
1340: // we have a value
1341: nullMask.markNotNull(varCol);
1342:
1343: ByteBuffer varDataBuf = varCol.write(rowValue,
1344: maxRowSize);
1345: maxRowSize -= varDataBuf.remaining();
1346: buffer.put(varDataBuf);
1347: }
1348:
1349: // we do a loop here so that we fill in offsets for deleted columns
1350: while (varColumnOffsetsIndex <= varCol
1351: .getVarLenTableIndex()) {
1352: varColumnOffsets[varColumnOffsetsIndex++] = offset;
1353: }
1354: }
1355:
1356: // fill in offsets for any remaining deleted columns
1357: while (varColumnOffsetsIndex < varColumnOffsets.length) {
1358: varColumnOffsets[varColumnOffsetsIndex++] = (short) buffer
1359: .position();
1360: }
1361:
1362: buffer.putShort((short) buffer.position()); //EOD marker
1363: //Now write out variable length offsets
1364: //Offsets are stored in reverse order
1365: for (int i = _maxVarColumnCount - 1; i >= 0; i--) {
1366: buffer.putShort(varColumnOffsets[i]);
1367: }
1368: buffer.putShort(_maxVarColumnCount); //Number of var length columns
1369: }
1370:
1371: nullMask.write(buffer); //Null mask
1372: buffer.limit(buffer.position());
1373: buffer.flip();
1374: if (LOG.isDebugEnabled()) {
1375: LOG.debug("Creating new data block:\n"
1376: + ByteUtil.toHexString(buffer, buffer.limit()));
1377: }
1378: return buffer;
1379: }
1380:
1381: public int getRowCount() {
1382: return _rowCount;
1383: }
1384:
1385: private int getNextAutoNumber() {
1386: // note, the saved value is the last one handed out, so pre-increment
1387: return ++_lastAutoNumber;
1388: }
1389:
1390: int getLastAutoNumber() {
1391: // gets the last used auto number (does not modify)
1392: return _lastAutoNumber;
1393: }
1394:
1395: @Override
1396: public String toString() {
1397: StringBuilder rtn = new StringBuilder();
1398: rtn.append("Type: " + _tableType);
1399: rtn.append("\nName: " + _name);
1400: rtn.append("\nRow count: " + _rowCount);
1401: rtn.append("\nColumn count: " + _columns.size());
1402: rtn.append("\nIndex count: " + _indexCount);
1403: rtn.append("\nColumns:\n");
1404: for (Column col : _columns) {
1405: rtn.append(col);
1406: }
1407: rtn.append("\nIndexes:\n");
1408: for (Index index : _indexes) {
1409: rtn.append(index);
1410: }
1411: rtn.append("\nOwned pages: " + _ownedPages + "\n");
1412: return rtn.toString();
1413: }
1414:
1415: /**
1416: * @return A simple String representation of the entire table in tab-delimited format
1417: */
1418: public String display() throws IOException {
1419: return display(Long.MAX_VALUE);
1420: }
1421:
1422: /**
1423: * @param limit Maximum number of rows to display
1424: * @return A simple String representation of the entire table in tab-delimited format
1425: */
1426: public String display(long limit) throws IOException {
1427: reset();
1428: StringBuilder rtn = new StringBuilder();
1429: for (Iterator<Column> iter = _columns.iterator(); iter
1430: .hasNext();) {
1431: Column col = iter.next();
1432: rtn.append(col.getName());
1433: if (iter.hasNext()) {
1434: rtn.append("\t");
1435: }
1436: }
1437: rtn.append("\n");
1438: Map<String, Object> row;
1439: int rowCount = 0;
1440: while ((rowCount++ < limit) && (row = getNextRow()) != null) {
1441: for (Iterator<Object> iter = row.values().iterator(); iter
1442: .hasNext();) {
1443: Object obj = iter.next();
1444: if (obj instanceof byte[]) {
1445: byte[] b = (byte[]) obj;
1446: rtn.append(ByteUtil.toHexString(b));
1447: //This block can be used to easily dump a binary column to a file
1448: /*java.io.File f = java.io.File.createTempFile("ole", ".bin");
1449: java.io.FileOutputStream out = new java.io.FileOutputStream(f);
1450: out.write(b);
1451: out.flush();
1452: out.close();*/
1453: } else {
1454: rtn.append(String.valueOf(obj));
1455: }
1456: if (iter.hasNext()) {
1457: rtn.append("\t");
1458: }
1459: }
1460: rtn.append("\n");
1461: }
1462: return rtn.toString();
1463: }
1464:
1465: /**
1466: * Updates free space and row info for a new row of the given size in the
1467: * given data page. Positions the page for writing the row data.
1468: * @return the row number of the new row
1469: */
1470: public static int addDataPageRow(ByteBuffer dataPage, int rowSize,
1471: JetFormat format) {
1472: int rowSpaceUsage = getRowSpaceUsage(rowSize, format);
1473:
1474: // Decrease free space record.
1475: short freeSpaceInPage = dataPage
1476: .getShort(format.OFFSET_FREE_SPACE);
1477: dataPage.putShort(format.OFFSET_FREE_SPACE,
1478: (short) (freeSpaceInPage - rowSpaceUsage));
1479:
1480: // Increment row count record.
1481: short rowCount = dataPage
1482: .getShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE);
1483: dataPage.putShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE,
1484: (short) (rowCount + 1));
1485:
1486: // determine row position
1487: short rowLocation = findRowEnd(dataPage, rowCount, format);
1488: rowLocation -= rowSize;
1489:
1490: // write row position
1491: dataPage.putShort(getRowStartOffset(rowCount, format),
1492: rowLocation);
1493:
1494: // set position for row data
1495: dataPage.position(rowLocation);
1496:
1497: return rowCount;
1498: }
1499:
1500: /**
1501: * Returns the row count for the current page. If the page is invalid
1502: * ({@code null}) or the page is not a DATA page, 0 is returned.
1503: */
1504: private static int getRowsOnDataPage(ByteBuffer rowBuffer,
1505: JetFormat format) throws IOException {
1506: int rowsOnPage = 0;
1507: if ((rowBuffer != null) && (rowBuffer.get(0) == PageTypes.DATA)) {
1508: rowsOnPage = rowBuffer
1509: .getShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE);
1510: }
1511: return rowsOnPage;
1512: }
1513:
1514: /**
1515: * @throws IllegalStateException if the given rowId is invalid
1516: */
1517: private static void requireValidRowId(RowId rowId) {
1518: if (!rowId.isValid()) {
1519: throw new IllegalArgumentException(
1520: "Given rowId is invalid: " + rowId);
1521: }
1522: }
1523:
1524: /**
1525: * @throws IllegalStateException if the given row is invalid or deleted
1526: */
1527: private static void requireNonDeletedRow(RowState rowState,
1528: RowId rowId) {
1529: if (!rowState.isValid()) {
1530: throw new IllegalArgumentException(
1531: "Given rowId is invalid for this table: " + rowId);
1532: }
1533: if (rowState.isDeleted()) {
1534: throw new IllegalStateException("Row is deleted: " + rowId);
1535: }
1536: }
1537:
1538: public static boolean isDeletedRow(short rowStart) {
1539: return ((rowStart & DELETED_ROW_MASK) != 0);
1540: }
1541:
1542: public static boolean isOverflowRow(short rowStart) {
1543: return ((rowStart & OVERFLOW_ROW_MASK) != 0);
1544: }
1545:
1546: public static short cleanRowStart(short rowStart) {
1547: return (short) (rowStart & OFFSET_MASK);
1548: }
1549:
1550: public static short findRowStart(ByteBuffer buffer, int rowNum,
1551: JetFormat format) {
1552: return cleanRowStart(buffer.getShort(getRowStartOffset(rowNum,
1553: format)));
1554: }
1555:
1556: public static int getRowStartOffset(int rowNum, JetFormat format) {
1557: return format.OFFSET_ROW_START
1558: + (format.SIZE_ROW_LOCATION * rowNum);
1559: }
1560:
1561: public static short findRowEnd(ByteBuffer buffer, int rowNum,
1562: JetFormat format) {
1563: return (short) ((rowNum == 0) ? format.PAGE_SIZE
1564: : cleanRowStart(buffer.getShort(getRowEndOffset(rowNum,
1565: format))));
1566: }
1567:
1568: public static int getRowEndOffset(int rowNum, JetFormat format) {
1569: return format.OFFSET_ROW_START
1570: + (format.SIZE_ROW_LOCATION * (rowNum - 1));
1571: }
1572:
1573: public static int getRowSpaceUsage(int rowSize, JetFormat format) {
1574: return rowSize + format.SIZE_ROW_LOCATION;
1575: }
1576:
1577: /**
1578: * @return the number of "AutoNumber" columns in the given collection of
1579: * columns.
1580: */
1581: public static int countAutoNumberColumns(Collection<Column> columns) {
1582: int numAutoNumCols = 0;
1583: for (Column c : columns) {
1584: if (c.isAutoNumber()) {
1585: ++numAutoNumCols;
1586: }
1587: }
1588: return numAutoNumCols;
1589: }
1590:
1591: /** various statuses for the row data */
1592: private enum RowStatus {
1593: INIT, INVALID_PAGE, INVALID_ROW, VALID, DELETED, NORMAL, OVERFLOW;
1594: }
1595:
1596: /** the phases the RowState moves through as the data is parsed */
1597: private enum RowStateStatus {
1598: INIT, AT_HEADER, AT_FINAL;
1599: }
1600:
1601: /**
1602: * Maintains the state of reading a row of data.
1603: */
1604: public final class RowState {
1605: /** Buffer used for reading the header row data pages */
1606: private final TempPageHolder _headerRowBufferH;
1607: /** the header rowId */
1608: private RowId _headerRowId = RowId.FIRST_ROW_ID;
1609: /** the number of rows on the header page */
1610: private int _rowsOnHeaderPage;
1611: /** the rowState status */
1612: private RowStateStatus _status = RowStateStatus.INIT;
1613: /** the row status */
1614: private RowStatus _rowStatus = RowStatus.INIT;
1615: /** buffer used for reading overflow pages */
1616: private final TempPageHolder _overflowRowBufferH = TempPageHolder
1617: .newHolder(TempBufferHolder.Type.SOFT);
1618: /** the row buffer which contains the final data (after following any
1619: overflow pointers) */
1620: private ByteBuffer _finalRowBuffer;
1621: /** the rowId which contains the final data (after following any overflow
1622: pointers) */
1623: private RowId _finalRowId = null;
1624: /** true if the row values array has data */
1625: private boolean _haveRowValues;
1626: /** values read from the last row */
1627: private final Object[] _rowValues;
1628: /** last modification count seen on the table we track this so that the
1629: rowState can detect updates to the table and re-read any buffered
1630: data */
1631: private int _lastModCount;
1632:
1633: private RowState(TempBufferHolder.Type headerType) {
1634: _headerRowBufferH = TempPageHolder.newHolder(headerType);
1635: _rowValues = new Object[Table.this .getColumnCount()];
1636: _lastModCount = Table.this ._modCount;
1637: }
1638:
1639: public Table getTable() {
1640: return Table.this ;
1641: }
1642:
1643: public void reset() {
1644: _finalRowId = null;
1645: _finalRowBuffer = null;
1646: _rowsOnHeaderPage = 0;
1647: _status = RowStateStatus.INIT;
1648: _rowStatus = RowStatus.INIT;
1649: if (_haveRowValues) {
1650: Arrays.fill(_rowValues, null);
1651: _haveRowValues = false;
1652: }
1653: }
1654:
1655: public boolean isUpToDate() {
1656: return (Table.this ._modCount == _lastModCount);
1657: }
1658:
1659: private void checkForModification() {
1660: if (!isUpToDate()) {
1661: reset();
1662: _headerRowBufferH.invalidate();
1663: _overflowRowBufferH.invalidate();
1664: _lastModCount = Table.this ._modCount;
1665: }
1666: }
1667:
1668: private ByteBuffer getFinalPage() throws IOException {
1669: if (_finalRowBuffer == null) {
1670: // (re)load current page
1671: _finalRowBuffer = getHeaderPage();
1672: }
1673: return _finalRowBuffer;
1674: }
1675:
1676: public RowId getFinalRowId() {
1677: if (_finalRowId == null) {
1678: _finalRowId = getHeaderRowId();
1679: }
1680: return _finalRowId;
1681: }
1682:
1683: private void setRowStatus(RowStatus rowStatus) {
1684: _rowStatus = rowStatus;
1685: }
1686:
1687: public boolean isValid() {
1688: return (_rowStatus.ordinal() >= RowStatus.VALID.ordinal());
1689: }
1690:
1691: public boolean isDeleted() {
1692: return (_rowStatus == RowStatus.DELETED);
1693: }
1694:
1695: public boolean isOverflow() {
1696: return (_rowStatus == RowStatus.OVERFLOW);
1697: }
1698:
1699: public boolean isHeaderPageNumberValid() {
1700: return (_rowStatus.ordinal() > RowStatus.INVALID_PAGE
1701: .ordinal());
1702: }
1703:
1704: public boolean isHeaderRowNumberValid() {
1705: return (_rowStatus.ordinal() > RowStatus.INVALID_ROW
1706: .ordinal());
1707: }
1708:
1709: private void setStatus(RowStateStatus status) {
1710: _status = status;
1711: }
1712:
1713: public boolean isAtHeaderRow() {
1714: return (_status.ordinal() >= RowStateStatus.AT_HEADER
1715: .ordinal());
1716: }
1717:
1718: public boolean isAtFinalRow() {
1719: return (_status.ordinal() >= RowStateStatus.AT_FINAL
1720: .ordinal());
1721: }
1722:
1723: private void setRowValue(int idx, Object value) {
1724: _haveRowValues = true;
1725: _rowValues[idx] = value;
1726: }
1727:
1728: public Object[] getRowValues() {
1729: Object[] copy = new Object[_rowValues.length];
1730: System.arraycopy(_rowValues, 0, copy, 0, _rowValues.length);
1731: return copy;
1732: }
1733:
1734: public RowId getHeaderRowId() {
1735: return _headerRowId;
1736: }
1737:
1738: public int getRowsOnHeaderPage() {
1739: return _rowsOnHeaderPage;
1740: }
1741:
1742: private ByteBuffer getHeaderPage() throws IOException {
1743: checkForModification();
1744: return _headerRowBufferH.getPage(getPageChannel());
1745: }
1746:
1747: private ByteBuffer setHeaderRow(RowId rowId) throws IOException {
1748: checkForModification();
1749:
1750: // don't do any work if we are already positioned correctly
1751: if (isAtHeaderRow() && (getHeaderRowId().equals(rowId))) {
1752: return (isValid() ? getHeaderPage() : null);
1753: }
1754:
1755: // rejigger everything
1756: reset();
1757: _headerRowId = rowId;
1758: _finalRowId = rowId;
1759:
1760: int pageNumber = rowId.getPageNumber();
1761: int rowNumber = rowId.getRowNumber();
1762: if ((pageNumber < 0)
1763: || !_ownedPages.containsPageNumber(pageNumber)) {
1764: setRowStatus(RowStatus.INVALID_PAGE);
1765: return null;
1766: }
1767:
1768: _finalRowBuffer = _headerRowBufferH.setPage(
1769: getPageChannel(), pageNumber);
1770: _rowsOnHeaderPage = getRowsOnDataPage(_finalRowBuffer,
1771: getFormat());
1772:
1773: if ((rowNumber < 0) || (rowNumber >= _rowsOnHeaderPage)) {
1774: setRowStatus(RowStatus.INVALID_ROW);
1775: return null;
1776: }
1777:
1778: setRowStatus(RowStatus.VALID);
1779: return _finalRowBuffer;
1780: }
1781:
1782: private ByteBuffer setOverflowRow(RowId rowId)
1783: throws IOException {
1784: // this should never see modifications because it only happens within
1785: // the positionAtRowData method
1786: if (!isUpToDate()) {
1787: throw new IllegalStateException(
1788: "Table modified while searching?");
1789: }
1790: if (_rowStatus != RowStatus.OVERFLOW) {
1791: throw new IllegalStateException(
1792: "Row is not an overflow row?");
1793: }
1794: _finalRowId = rowId;
1795: _finalRowBuffer = _overflowRowBufferH.setPage(
1796: getPageChannel(), rowId.getPageNumber());
1797: return _finalRowBuffer;
1798: }
1799:
1800: }
1801:
1802: }
|