123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935 |
- /*
- Copyright (c) 2005 Health Market Science, Inc.
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library; if not, write to the Free Software
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- USA
-
- You can contact Health Market Science at info@healthmarketscience.com
- or at the following address:
-
- Health Market Science
- 2700 Horizon Drive
- Suite 200
- King of Prussia, PA 19406
- */
-
- package com.healthmarketscience.jackcess;
-
- import java.io.IOException;
- import java.nio.ByteBuffer;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.Comparator;
- import java.util.Iterator;
- import java.util.LinkedHashMap;
- import java.util.List;
- import java.util.Map;
-
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
-
- /**
- * A single database table
- * <p>
- * Is not thread-safe.
- *
- * @author Tim McCune
- */
- public class Table
- implements Iterable<Map<String, Object>>
- {
-
- private static final Log LOG = LogFactory.getLog(Table.class);
-
- private static final short OFFSET_MASK = (short)0x1FFF;
-
- private static final short DELETED_ROW_MASK = (short)0x8000;
-
- private static final short OVERFLOW_ROW_MASK = (short)0x4000;
-
- /** Table type code for system tables */
- public static final byte TYPE_SYSTEM = 0x53;
- /** Table type code for user tables */
- public static final byte TYPE_USER = 0x4e;
-
- /** comparator which sorts variable length columns vased on their index into
- the variable length offset table */
- private static final Comparator<Column> VAR_LEN_COLUMN_COMPARATOR =
- new Comparator<Column>() {
- public int compare(Column c1, Column c2) {
- return ((c1.getVarLenTableIndex() < c2.getVarLenTableIndex()) ? -1 :
- ((c1.getVarLenTableIndex() > c2.getVarLenTableIndex()) ? 1 :
- 0));
- }
- };
-
- /** owning database */
- private final Database _database;
- /** Type of the table (either TYPE_SYSTEM or TYPE_USER) */
- private byte _tableType;
- /** Number of indexes on the table */
- private int _indexCount;
- /** Number of index slots for the table */
- private int _indexSlotCount;
- /** Number of rows in the table */
- private int _rowCount;
- /** last auto number for the table */
- private int _lastAutoNumber;
- /** page number of the definition of this table */
- private final int _tableDefPageNumber;
- /** max Number of columns in the table (includes previous deletions) */
- private short _maxColumnCount;
- /** max Number of variable columns in the table */
- private short _maxVarColumnCount;
- /** List of columns in this table, ordered by column number */
- private List<Column> _columns = new ArrayList<Column>();
- /** List of variable length columns in this table, ordered by offset */
- private List<Column> _varColumns = new ArrayList<Column>();
- /** List of indexes on this table */
- private List<Index> _indexes = new ArrayList<Index>();
- /** Table name as stored in Database */
- private final String _name;
- /** Usage map of pages that this table owns */
- private UsageMap _ownedPages;
- /** Usage map of pages that this table owns with free space on them */
- private UsageMap _freeSpacePages;
- /** modification count for the table, keeps row-states up-to-date */
- private int _modCount;
- /** page buffer used to update data pages when adding rows */
- private final TempPageHolder _addRowBufferH =
- TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
- /** page buffer used to update the table def page */
- private final TempPageHolder _tableDefBufferH =
- TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
- /** buffer used to writing single rows of data */
- private final TempBufferHolder _singleRowBufferH =
- TempBufferHolder.newHolder(TempBufferHolder.Type.SOFT, true);
- /** "buffer" used to writing multi rows of data (will create new buffer on
- every call) */
- private final TempBufferHolder _multiRowBufferH =
- TempBufferHolder.newHolder(TempBufferHolder.Type.NONE, true);
- /** page buffer used to write out-of-line "long value" data */
- private final TempPageHolder _longValueBufferH =
- TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
- /** for now, "big index support" is optional */
- private final boolean _useBigIndex;
- /** optional error handler to use when row errors are encountered */
- private ErrorHandler _tableErrorHandler;
-
- /** common cursor for iterating through the table, kept here for historic
- reasons */
- private Cursor _cursor;
-
- /**
- * Only used by unit tests
- */
- Table(boolean testing, List<Column> columns) throws IOException {
- if(!testing) {
- throw new IllegalArgumentException();
- }
- _database = null;
- _tableDefPageNumber = PageChannel.INVALID_PAGE_NUMBER;
- _name = null;
- _useBigIndex = false;
- setColumns(columns);
- }
-
- /**
- * @param database database which owns this table
- * @param tableBuffer Buffer to read the table with
- * @param pageNumber Page number of the table definition
- * @param name Table name
- * @param useBigIndex whether or not "big index support" should be enabled
- * for the table
- */
- protected Table(Database database, ByteBuffer tableBuffer,
- int pageNumber, String name, boolean useBigIndex)
- throws IOException
- {
- _database = database;
- _tableDefPageNumber = pageNumber;
- _name = name;
- _useBigIndex = useBigIndex;
- int nextPage = tableBuffer.getInt(getFormat().OFFSET_NEXT_TABLE_DEF_PAGE);
- ByteBuffer nextPageBuffer = null;
- while (nextPage != 0) {
- if (nextPageBuffer == null) {
- nextPageBuffer = getPageChannel().createPageBuffer();
- }
- getPageChannel().readPage(nextPageBuffer, nextPage);
- nextPage = nextPageBuffer.getInt(getFormat().OFFSET_NEXT_TABLE_DEF_PAGE);
- ByteBuffer newBuffer = getPageChannel().createBuffer(
- tableBuffer.capacity() + getFormat().PAGE_SIZE - 8);
- newBuffer.put(tableBuffer);
- newBuffer.put(nextPageBuffer.array(), 8, getFormat().PAGE_SIZE - 8);
- tableBuffer = newBuffer;
- tableBuffer.flip();
- }
- readTableDefinition(tableBuffer);
- tableBuffer = null;
-
- // setup common cursor
- _cursor = Cursor.createCursor(this);
- }
-
- /**
- * @return The name of the table
- */
- public String getName() {
- return _name;
- }
-
- public boolean doUseBigIndex() {
- return _useBigIndex;
- }
-
- public int getMaxColumnCount() {
- return _maxColumnCount;
- }
-
- public int getColumnCount() {
- return _columns.size();
- }
-
- public Database getDatabase() {
- return _database;
- }
-
- public JetFormat getFormat() {
- return getDatabase().getFormat();
- }
-
- public PageChannel getPageChannel() {
- return getDatabase().getPageChannel();
- }
-
- /**
- * Gets the currently configured ErrorHandler (always non-{@code null}).
- * This will be used to handle all errors unless overridden at the Cursor
- * level.
- */
- public ErrorHandler getErrorHandler() {
- return((_tableErrorHandler != null) ? _tableErrorHandler :
- getDatabase().getErrorHandler());
- }
-
- /**
- * Sets a new ErrorHandler. If {@code null}, resets to using the
- * ErrorHandler configured at the Database level.
- */
- public void setErrorHandler(ErrorHandler newErrorHandler) {
- _tableErrorHandler = newErrorHandler;
- }
-
- protected int getTableDefPageNumber() {
- return _tableDefPageNumber;
- }
-
- public RowState createRowState() {
- return new RowState(TempBufferHolder.Type.HARD);
- }
-
- protected UsageMap.PageCursor getOwnedPagesCursor() {
- return _ownedPages.cursor();
- }
-
- protected TempPageHolder getLongValueBuffer() {
- return _longValueBufferH;
- }
-
- /**
- * @return All of the columns in this table (unmodifiable List)
- */
- public List<Column> getColumns() {
- return Collections.unmodifiableList(_columns);
- }
-
- /**
- * @return the column with the given name
- */
- public Column getColumn(String name) {
- for(Column column : _columns) {
- if(column.getName().equals(name)) {
- return column;
- }
- }
- throw new IllegalArgumentException("Column with name " + name +
- " does not exist in this table");
- }
-
- /**
- * Only called by unit tests
- */
- private void setColumns(List<Column> columns) {
- _columns = columns;
- int colIdx = 0;
- int varLenIdx = 0;
- int fixedOffset = 0;
- for(Column col : _columns) {
- col.setColumnNumber((short)colIdx);
- col.setColumnIndex(colIdx++);
- if(col.isVariableLength()) {
- col.setVarLenTableIndex(varLenIdx++);
- _varColumns.add(col);
- } else {
- col.setFixedDataOffset(fixedOffset);
- fixedOffset += col.getType().getFixedSize();
- }
- }
- _maxColumnCount = (short)_columns.size();
- _maxVarColumnCount = (short)_varColumns.size();
- }
-
- /**
- * @return All of the Indexes on this table (unmodifiable List)
- */
- public List<Index> getIndexes() {
- return Collections.unmodifiableList(_indexes);
- }
-
- /**
- * @return the index with the given name
- */
- public Index getIndex(String name) {
- for(Index index : _indexes) {
- if(index.getName().equals(name)) {
- return index;
- }
- }
- throw new IllegalArgumentException("Index with name " + name +
- " does not exist on this table");
- }
-
- /**
- * Only called by unit tests
- */
- int getIndexSlotCount() {
- return _indexSlotCount;
- }
-
- /**
- * After calling this method, getNextRow will return the first row in the
- * table
- */
- public void reset() {
- _cursor.reset();
- }
-
- /**
- * Delete the current row (retrieved by a call to {@link #getNextRow()}).
- */
- public void deleteCurrentRow() throws IOException {
- _cursor.deleteCurrentRow();
- }
-
- /**
- * Delete the row on which the given rowState is currently positioned.
- */
- public void deleteRow(RowState rowState, RowId rowId) throws IOException {
- requireValidRowId(rowId);
-
- // ensure that the relevant row state is up-to-date
- ByteBuffer rowBuffer = positionAtRowHeader(rowState, rowId);
-
- requireNonDeletedRow(rowState, rowId);
-
- // delete flag always gets set in the "header" row (even if data is on
- // overflow row)
- int pageNumber = rowState.getHeaderRowId().getPageNumber();
- int rowNumber = rowState.getHeaderRowId().getRowNumber();
-
- // use any read rowValues to help update the indexes
- Object[] rowValues = (!_indexes.isEmpty() ?
- rowState.getRowValues() : null);
-
- int rowIndex = getRowStartOffset(rowNumber, getFormat());
- rowBuffer.putShort(rowIndex, (short)(rowBuffer.getShort(rowIndex)
- | DELETED_ROW_MASK | OVERFLOW_ROW_MASK));
- writeDataPage(rowBuffer, pageNumber);
-
- // update the indexes
- for(Index index : _indexes) {
- index.deleteRow(rowValues, rowId);
- }
-
- // make sure table def gets updated
- updateTableDefinition(-1);
- }
-
- /**
- * @return The next row in this table (Column name -> Column value)
- */
- public Map<String, Object> getNextRow() throws IOException {
- return getNextRow(null);
- }
-
- /**
- * @param columnNames Only column names in this collection will be returned
- * @return The next row in this table (Column name -> Column value)
- */
- public Map<String, Object> getNextRow(Collection<String> columnNames)
- throws IOException
- {
- return _cursor.getNextRow(columnNames);
- }
-
- /**
- * Reads a single column from the given row.
- */
- public Object getRowValue(RowState rowState, RowId rowId, Column column)
- throws IOException
- {
- if(this != column.getTable()) {
- throw new IllegalArgumentException(
- "Given column " + column + " is not from this table");
- }
- requireValidRowId(rowId);
-
- // position at correct row
- ByteBuffer rowBuffer = positionAtRowData(rowState, rowId);
- requireNonDeletedRow(rowState, rowId);
-
- Object value = getRowColumn(rowBuffer, getRowNullMask(rowBuffer), column,
- rowState);
-
- // cache the row values in order to be able to update the index on row
- // deletion. note, most of the returned values are immutable, except
- // for binary data (returned as byte[]), but binary data shouldn't be
- // indexed anyway.
- rowState.setRowValue(column.getColumnIndex(), value);
-
- return value;
- }
-
- /**
- * Reads some columns from the given row.
- * @param columnNames Only column names in this collection will be returned
- */
- public Map<String, Object> getRow(
- RowState rowState, RowId rowId, Collection<String> columnNames)
- throws IOException
- {
- requireValidRowId(rowId);
-
- // position at correct row
- ByteBuffer rowBuffer = positionAtRowData(rowState, rowId);
- requireNonDeletedRow(rowState, rowId);
-
- return getRow(rowState, rowBuffer, getRowNullMask(rowBuffer), _columns,
- columnNames);
- }
-
- /**
- * Reads the row data from the given row buffer. Leaves limit unchanged.
- * Saves parsed row values to the given rowState.
- */
- private static Map<String, Object> getRow(
- RowState rowState,
- ByteBuffer rowBuffer,
- NullMask nullMask,
- Collection<Column> columns,
- Collection<String> columnNames)
- throws IOException
- {
- Map<String, Object> rtn = new LinkedHashMap<String, Object>(
- columns.size());
- for(Column column : columns) {
-
- if((columnNames == null) || (columnNames.contains(column.getName()))) {
- // Add the value to the row data
- Object value = getRowColumn(rowBuffer, nullMask, column, rowState);
- rtn.put(column.getName(), value);
-
- // cache the row values in order to be able to update the index on row
- // deletion. note, most of the returned values are immutable, except
- // for binary data (returned as byte[]), but binary data shouldn't be
- // indexed anyway.
- rowState.setRowValue(column.getColumnIndex(), value);
- }
- }
- return rtn;
- }
-
- /**
- * Reads the column data from the given row buffer. Leaves limit unchanged.
- */
- private static Object getRowColumn(ByteBuffer rowBuffer,
- NullMask nullMask,
- Column column,
- RowState rowState)
- throws IOException
- {
- byte[] columnData = null;
- try {
-
- boolean isNull = nullMask.isNull(column);
- if(column.getType() == DataType.BOOLEAN) {
- return Boolean.valueOf(!isNull); //Boolean values are stored in the null mask
- } else if(isNull) {
- // well, that's easy!
- return null;
- }
-
- // reset position to row start
- rowBuffer.reset();
-
- // locate the column data bytes
- int rowStart = rowBuffer.position();
- int colDataPos = 0;
- int colDataLen = 0;
- if(!column.isVariableLength()) {
-
- // read fixed length value (non-boolean at this point)
- int dataStart = rowStart + 2;
- colDataPos = dataStart + column.getFixedDataOffset();
- colDataLen = column.getType().getFixedSize();
-
- } else {
-
- // read var length value
- int varColumnOffsetPos =
- (rowBuffer.limit() - nullMask.byteSize() - 4) -
- (column.getVarLenTableIndex() * 2);
-
- short varDataStart = rowBuffer.getShort(varColumnOffsetPos);
- short varDataEnd = rowBuffer.getShort(varColumnOffsetPos - 2);
- colDataPos = rowStart + varDataStart;
- colDataLen = varDataEnd - varDataStart;
- }
-
- // grab the column data
- columnData = new byte[colDataLen];
- rowBuffer.position(colDataPos);
- rowBuffer.get(columnData);
-
- // parse the column data
- return column.read(columnData);
-
- } catch(Exception e) {
- return rowState.handleRowError(column, columnData, e);
- }
- }
-
- /**
- * Reads the null mask from the given row buffer. Leaves limit unchanged.
- */
- private static NullMask getRowNullMask(ByteBuffer rowBuffer)
- throws IOException
- {
- // reset position to row start
- rowBuffer.reset();
-
- short columnCount = rowBuffer.getShort(); // Number of columns in this row
-
- // read null mask
- NullMask nullMask = new NullMask(columnCount);
- rowBuffer.position(rowBuffer.limit() - nullMask.byteSize()); //Null mask at end
- nullMask.read(rowBuffer);
-
- return nullMask;
- }
-
- /**
- * Sets a new buffer to the correct row header page using the given rowState
- * according to the given rowId. Deleted state is
- * determined, but overflow row pointers are not followed.
- *
- * @return a ByteBuffer of the relevant page, or null if row was invalid
- */
- public static ByteBuffer positionAtRowHeader(RowState rowState,
- RowId rowId)
- throws IOException
- {
- ByteBuffer rowBuffer = rowState.setHeaderRow(rowId);
-
- if(rowState.isAtHeaderRow()) {
- // this task has already been accomplished
- return rowBuffer;
- }
-
- if(!rowState.isValid()) {
- // this was an invalid page/row
- rowState.setStatus(RowStateStatus.AT_HEADER);
- return null;
- }
-
- // note, we don't use findRowStart here cause we need the unmasked value
- short rowStart = rowBuffer.getShort(
- getRowStartOffset(rowId.getRowNumber(),
- rowState.getTable().getFormat()));
-
- // check the deleted, overflow flags for the row (the "real" flags are
- // always set on the header row)
- RowStatus rowStatus = RowStatus.NORMAL;
- if(isDeletedRow(rowStart)) {
- rowStatus = RowStatus.DELETED;
- } else if(isOverflowRow(rowStart)) {
- rowStatus = RowStatus.OVERFLOW;
- }
-
- rowState.setRowStatus(rowStatus);
- rowState.setStatus(RowStateStatus.AT_HEADER);
- return rowBuffer;
- }
-
- /**
- * Sets the position and limit in a new buffer using the given rowState
- * according to the given row number and row end, following overflow row
- * pointers as necessary.
- *
- * @return a ByteBuffer narrowed to the actual row data, or null if row was
- * invalid or deleted
- */
- public static ByteBuffer positionAtRowData(RowState rowState,
- RowId rowId)
- throws IOException
- {
- positionAtRowHeader(rowState, rowId);
- if(!rowState.isValid() || rowState.isDeleted()) {
- // row is invalid or deleted
- rowState.setStatus(RowStateStatus.AT_FINAL);
- return null;
- }
-
- ByteBuffer rowBuffer = rowState.getFinalPage();
- int rowNum = rowState.getFinalRowId().getRowNumber();
- JetFormat format = rowState.getTable().getFormat();
-
- if(rowState.isAtFinalRow()) {
- // we've already found the final row data
- return PageChannel.narrowBuffer(
- rowBuffer,
- findRowStart(rowBuffer, rowNum, format),
- findRowEnd(rowBuffer, rowNum, format));
- }
-
- while(true) {
-
- // note, we don't use findRowStart here cause we need the unmasked value
- short rowStart = rowBuffer.getShort(getRowStartOffset(rowNum, format));
- short rowEnd = findRowEnd(rowBuffer, rowNum, format);
-
- // note, at this point we know the row is not deleted, so ignore any
- // subsequent deleted flags (as overflow rows are always marked deleted
- // anyway)
- boolean overflowRow = isOverflowRow(rowStart);
-
- // now, strip flags from rowStart offset
- rowStart = (short)(rowStart & OFFSET_MASK);
-
- if (overflowRow) {
-
- if((rowEnd - rowStart) < 4) {
- throw new IOException("invalid overflow row info");
- }
-
- // Overflow page. the "row" data in the current page points to
- // another page/row
- int overflowRowNum = ByteUtil.getUnsignedByte(rowBuffer, rowStart);
- int overflowPageNum = ByteUtil.get3ByteInt(rowBuffer, rowStart + 1);
- rowBuffer = rowState.setOverflowRow(
- new RowId(overflowPageNum, overflowRowNum));
- rowNum = overflowRowNum;
-
- } else {
-
- rowState.setStatus(RowStateStatus.AT_FINAL);
- return PageChannel.narrowBuffer(rowBuffer, rowStart, rowEnd);
- }
- }
- }
-
-
- /**
- * Calls <code>reset</code> on this table and returns an unmodifiable
- * Iterator which will iterate through all the rows of this table. Use of
- * the Iterator follows the same restrictions as a call to
- * <code>getNextRow</code>.
- * @throws IllegalStateException if an IOException is thrown by one of the
- * operations, the actual exception will be contained within
- */
- public Iterator<Map<String, Object>> iterator()
- {
- return iterator(null);
- }
-
- /**
- * Calls <code>reset</code> on this table and returns an unmodifiable
- * Iterator which will iterate through all the rows of this table, returning
- * only the given columns. Use of the Iterator follows the same
- * restrictions as a call to <code>getNextRow</code>.
- * @throws IllegalStateException if an IOException is thrown by one of the
- * operations, the actual exception will be contained within
- */
- public Iterator<Map<String, Object>> iterator(Collection<String> columnNames)
- {
- reset();
- return _cursor.iterator(columnNames);
- }
-
- /**
- * Writes a new table defined by the given columns to the database.
- * @return the first page of the new table's definition
- */
- public static int writeTableDefinition(
- List<Column> columns, PageChannel pageChannel, JetFormat format)
- throws IOException
- {
- // first, create the usage map page
- int usageMapPageNumber = pageChannel.writeNewPage(
- createUsageMapDefinitionBuffer(pageChannel, format));
-
- // next, determine how big the table def will be (in case it will be more
- // than one page)
- int totalTableDefSize = format.SIZE_TDEF_HEADER +
- (format.SIZE_COLUMN_DEF_BLOCK * columns.size()) +
- format.SIZE_TDEF_TRAILER;
- for(Column col : columns) {
- // we add the number of bytes for the column name and 2 bytes for the
- // length of the column name
- int nameByteLen = (col.getName().length() *
- JetFormat.TEXT_FIELD_UNIT_SIZE);
- totalTableDefSize += nameByteLen + 2;
- }
-
- // now, create the table definition
- ByteBuffer buffer = pageChannel.createBuffer(Math.max(totalTableDefSize,
- format.PAGE_SIZE));
- writeTableDefinitionHeader(buffer, columns, usageMapPageNumber,
- totalTableDefSize, format);
- writeColumnDefinitions(buffer, columns, format);
-
- //End of tabledef
- buffer.put((byte) 0xff);
- buffer.put((byte) 0xff);
-
- // write table buffer to database
- int tdefPageNumber = PageChannel.INVALID_PAGE_NUMBER;
- if(totalTableDefSize <= format.PAGE_SIZE) {
-
- // easy case, fits on one page
- buffer.putShort(format.OFFSET_FREE_SPACE,
- (short)(buffer.remaining() - 8)); // overwrite page free space
- // Write the tdef page to disk.
- tdefPageNumber = pageChannel.writeNewPage(buffer);
-
- } else {
-
- // need to split across multiple pages
- ByteBuffer partialTdef = pageChannel.createPageBuffer();
- buffer.rewind();
- int nextTdefPageNumber = PageChannel.INVALID_PAGE_NUMBER;
- while(buffer.hasRemaining()) {
-
- // reset for next write
- partialTdef.clear();
-
- if(tdefPageNumber == PageChannel.INVALID_PAGE_NUMBER) {
-
- // this is the first page. note, the first page already has the
- // page header, so no need to write it here
- tdefPageNumber = pageChannel.allocateNewPage();
- nextTdefPageNumber = tdefPageNumber;
-
- } else {
-
- // write page header
- writeTablePageHeader(partialTdef);
- }
-
- // copy the next page of tdef bytes
- int curTdefPageNumber = nextTdefPageNumber;
- int writeLen = Math.min(partialTdef.remaining(), buffer.remaining());
- partialTdef.put(buffer.array(), buffer.position(), writeLen);
- buffer.position(buffer.position() + writeLen);
-
- if(buffer.hasRemaining()) {
- // need a next page
- nextTdefPageNumber = pageChannel.allocateNewPage();
- partialTdef.putInt(format.OFFSET_NEXT_TABLE_DEF_PAGE,
- nextTdefPageNumber);
- }
-
- // update page free space
- partialTdef.putShort(format.OFFSET_FREE_SPACE,
- (short)(partialTdef.remaining() - 8)); // overwrite page free space
-
- // write partial page to disk
- pageChannel.writePage(partialTdef, curTdefPageNumber);
- }
-
- }
-
- return tdefPageNumber;
- }
-
- /**
- * @param buffer Buffer to write to
- * @param columns List of Columns in the table
- */
- private static void writeTableDefinitionHeader(
- ByteBuffer buffer, List<Column> columns,
- int usageMapPageNumber, int totalTableDefSize, JetFormat format)
- throws IOException
- {
- //Start writing the tdef
- writeTablePageHeader(buffer);
- buffer.putInt(totalTableDefSize); //Length of table def
- buffer.put((byte) 0x59); //Unknown
- buffer.put((byte) 0x06); //Unknown
- buffer.putShort((short) 0); //Unknown
- buffer.putInt(0); //Number of rows
- buffer.putInt(0); //Last Autonumber
- if(countAutoNumberColumns(columns) > 0) {
- buffer.put((byte) 1);
- } else {
- buffer.put((byte) 0);
- }
- for (int i = 0; i < 15; i++) { //Unknown
- buffer.put((byte) 0);
- }
- buffer.put(Table.TYPE_USER); //Table type
- buffer.putShort((short) columns.size()); //Max columns a row will have
- buffer.putShort(Column.countVariableLength(columns)); //Number of variable columns in table
- buffer.putShort((short) columns.size()); //Number of columns in table
- buffer.putInt(0); //Number of indexes in table
- buffer.putInt(0); //Number of indexes in table
- buffer.put((byte) 0); //Usage map row number
- ByteUtil.put3ByteInt(buffer, usageMapPageNumber); //Usage map page number
- buffer.put((byte) 1); //Free map row number
- ByteUtil.put3ByteInt(buffer, usageMapPageNumber); //Free map page number
- if (LOG.isDebugEnabled()) {
- int position = buffer.position();
- buffer.rewind();
- LOG.debug("Creating new table def block:\n" + ByteUtil.toHexString(
- buffer, format.SIZE_TDEF_HEADER));
- buffer.position(position);
- }
- }
-
- /**
- * Writes the page header for a table definition page
- * @param buffer Buffer to write to
- */
- private static void writeTablePageHeader(ByteBuffer buffer)
- {
- buffer.put(PageTypes.TABLE_DEF); //Page type
- buffer.put((byte) 0x01); //Unknown
- buffer.put((byte) 0); //Unknown
- buffer.put((byte) 0); //Unknown
- buffer.putInt(0); //Next TDEF page pointer
- }
-
- /**
- * @param buffer Buffer to write to
- * @param columns List of Columns to write definitions for
- */
- private static void writeColumnDefinitions(
- ByteBuffer buffer, List<Column> columns, JetFormat format)
- throws IOException
- {
- short columnNumber = (short) 0;
- short fixedOffset = (short) 0;
- short variableOffset = (short) 0;
- // we specifically put the "long variable" values after the normal
- // variable length values so that we have a better chance of fitting it
- // all (because "long variable" values can go in separate pages)
- short longVariableOffset =
- Column.countNonLongVariableLength(columns);
- for (Column col : columns) {
- int position = buffer.position();
- buffer.put(col.getType().getValue());
- buffer.put((byte) 0x59); //Unknown
- buffer.put((byte) 0x06); //Unknown
- buffer.putShort((short) 0); //Unknown
- buffer.putShort(columnNumber); //Column Number
- if (col.isVariableLength()) {
- if(!col.getType().isLongValue()) {
- buffer.putShort(variableOffset++);
- } else {
- buffer.putShort(longVariableOffset++);
- }
- } else {
- buffer.putShort((short) 0);
- }
- buffer.putShort(columnNumber); //Column Number again
- if(col.getType().getHasScalePrecision()) {
- buffer.put(col.getPrecision()); // numeric precision
- buffer.put(col.getScale()); // numeric scale
- } else {
- buffer.put((byte) 0x00); //unused
- buffer.put((byte) 0x00); //unused
- }
- buffer.putShort((short) 0); //Unknown
- buffer.put(getColumnBitFlags(col)); // misc col flags
- if (col.isCompressedUnicode()) { //Compressed
- buffer.put((byte) 1);
- } else {
- buffer.put((byte) 0);
- }
- buffer.putInt(0); //Unknown, but always 0.
- //Offset for fixed length columns
- if (col.isVariableLength()) {
- buffer.putShort((short) 0);
- } else {
- buffer.putShort(fixedOffset);
- fixedOffset += col.getType().getFixedSize();
- }
- if(!col.getType().isLongValue()) {
- buffer.putShort(col.getLength()); //Column length
- } else {
- buffer.putShort((short)0x0000); // unused
- }
- columnNumber++;
- if (LOG.isDebugEnabled()) {
- LOG.debug("Creating new column def block\n" + ByteUtil.toHexString(
- buffer, position, format.SIZE_COLUMN_DEF_BLOCK));
- }
- }
- for (Column col : columns) {
- writeName(buffer, col.getName(), format);
- }
- }
-
- /**
- * Writes the given name into the given buffer in the format as expected by
- * {@link #readName}.
- */
- private static void writeName(ByteBuffer buffer, String name,
- JetFormat format)
- {
- ByteBuffer encName = Column.encodeUncompressedText(
- name, format);
- buffer.putShort((short) encName.remaining());
- buffer.put(encName);
- }
-
- /**
- * Constructs a byte containing the flags for the given column.
- */
- private static byte getColumnBitFlags(Column col) {
- byte flags = Column.UNKNOWN_FLAG_MASK;
- if(!col.isVariableLength()) {
- flags |= Column.FIXED_LEN_FLAG_MASK;
- }
- if(col.isAutoNumber()) {
- flags |= Column.AUTO_NUMBER_FLAG_MASK;
- }
- return flags;
- }
-
- /**
- * Create the usage map definition page buffer. The "used pages" map is in
- * row 0, the "pages with free space" map is in row 1.
- */
- private static ByteBuffer createUsageMapDefinitionBuffer(
- PageChannel pageChannel, JetFormat format)
- throws IOException
- {
- int usageMapRowLength = format.OFFSET_USAGE_MAP_START +
- format.USAGE_MAP_TABLE_BYTE_LENGTH;
- int freeSpace = format.PAGE_INITIAL_FREE_SPACE
- - (2 * getRowSpaceUsage(usageMapRowLength, format));
-
- ByteBuffer rtn = pageChannel.createPageBuffer();
- rtn.put(PageTypes.DATA);
- rtn.put((byte) 0x1); //Unknown
- rtn.putShort((short)freeSpace); //Free space in page
- rtn.putInt(0); //Table definition
- rtn.putInt(0); //Unknown
- rtn.putShort((short) 2); //Number of records on this page
-
- // write two rows of usage map definitions
- int rowStart = findRowEnd(rtn, 0, format) - usageMapRowLength;
- for(int i = 0; i < 2; ++i) {
- rtn.putShort(getRowStartOffset(i, format), (short)rowStart);
- if(i == 0) {
- // initial "usage pages" map definition
- rtn.put(rowStart, UsageMap.MAP_TYPE_REFERENCE);
- } else {
- // initial "pages with free space" map definition
- rtn.put(rowStart, UsageMap.MAP_TYPE_INLINE);
- }
- rowStart -= usageMapRowLength;
- }
-
- return rtn;
- }
-
- /**
- * Read the table definition
- */
- private void readTableDefinition(ByteBuffer tableBuffer) throws IOException
- {
- if (LOG.isDebugEnabled()) {
- tableBuffer.rewind();
- LOG.debug("Table def block:\n" + ByteUtil.toHexString(tableBuffer,
- getFormat().SIZE_TDEF_HEADER));
- }
- _rowCount = tableBuffer.getInt(getFormat().OFFSET_NUM_ROWS);
- _lastAutoNumber = tableBuffer.getInt(getFormat().OFFSET_NEXT_AUTO_NUMBER);
- _tableType = tableBuffer.get(getFormat().OFFSET_TABLE_TYPE);
- _maxColumnCount = tableBuffer.getShort(getFormat().OFFSET_MAX_COLS);
- _maxVarColumnCount = tableBuffer.getShort(getFormat().OFFSET_NUM_VAR_COLS);
- short columnCount = tableBuffer.getShort(getFormat().OFFSET_NUM_COLS);
- _indexSlotCount = tableBuffer.getInt(getFormat().OFFSET_NUM_INDEX_SLOTS);
- _indexCount = tableBuffer.getInt(getFormat().OFFSET_NUM_INDEXES);
-
- int rowNum = ByteUtil.getUnsignedByte(
- tableBuffer, getFormat().OFFSET_OWNED_PAGES);
- int pageNum = ByteUtil.get3ByteInt(tableBuffer, getFormat().OFFSET_OWNED_PAGES + 1);
- _ownedPages = UsageMap.read(getDatabase(), pageNum, rowNum, false);
- rowNum = ByteUtil.getUnsignedByte(
- tableBuffer, getFormat().OFFSET_FREE_SPACE_PAGES);
- pageNum = ByteUtil.get3ByteInt(tableBuffer, getFormat().OFFSET_FREE_SPACE_PAGES + 1);
- _freeSpacePages = UsageMap.read(getDatabase(), pageNum, rowNum, false);
-
- for (int i = 0; i < _indexCount; i++) {
- int uniqueEntryCountOffset =
- (getFormat().OFFSET_INDEX_DEF_BLOCK +
- (i * getFormat().SIZE_INDEX_DEFINITION) + 4);
- int uniqueEntryCount = tableBuffer.getInt(uniqueEntryCountOffset);
- _indexes.add(createIndex(uniqueEntryCount, uniqueEntryCountOffset));
- }
-
- int colOffset = getFormat().OFFSET_INDEX_DEF_BLOCK +
- _indexCount * getFormat().SIZE_INDEX_DEFINITION;
- for (int i = 0; i < columnCount; i++) {
- Column column = new Column(this, tableBuffer,
- colOffset + (i * getFormat().SIZE_COLUMN_HEADER));
- _columns.add(column);
- if(column.isVariableLength()) {
- // also shove it in the variable columns list, which is ordered
- // differently from the _columns list
- _varColumns.add(column);
- }
- }
- tableBuffer.position(colOffset +
- (columnCount * getFormat().SIZE_COLUMN_HEADER));
- for (int i = 0; i < columnCount; i++) {
- Column column = _columns.get(i);
- column.setName(readName(tableBuffer));
- }
- Collections.sort(_columns);
-
- // setup the data index for the columns
- int colIdx = 0;
- for(Column col : _columns) {
- col.setColumnIndex(colIdx++);
- }
-
- // sort variable length columns based on their index into the variable
- // length offset table, because we will write the columns in this order
- Collections.sort(_varColumns, VAR_LEN_COLUMN_COMPARATOR);
-
- int idxOffset = tableBuffer.position();
- tableBuffer.position(idxOffset +
- (getFormat().OFFSET_INDEX_NUMBER_BLOCK * _indexCount));
-
- // if there are more index slots than indexes, the initial slots are
- // always empty/invalid, so we skip that data
- int firstRealIdx = (_indexSlotCount - _indexCount);
-
- for (int i = 0; i < _indexSlotCount; i++) {
-
- tableBuffer.getInt(); //Forward past Unknown
- tableBuffer.getInt(); //Forward past alternate index number
- int indexNumber = tableBuffer.getInt();
- tableBuffer.position(tableBuffer.position() + 11);
- byte indexType = tableBuffer.get();
- tableBuffer.position(tableBuffer.position() + 4);
-
- if(i < firstRealIdx) {
- // ignore this info
- continue;
- }
-
- Index index = _indexes.get(i - firstRealIdx);
- index.setIndexNumber(indexNumber);
- index.setIndexType(indexType);
- }
-
- // read actual index names
- for (int i = 0; i < _indexSlotCount; i++) {
- if(i < firstRealIdx) {
- // for each empty index slot, there is some weird sort of name, skip
- // it
- skipName(tableBuffer);
- continue;
- }
-
- _indexes.get(i - firstRealIdx)
- .setName(readName(tableBuffer));
- }
- int idxEndOffset = tableBuffer.position();
-
- Collections.sort(_indexes);
-
- // go back to index column info after sorting
- tableBuffer.position(idxOffset);
- for (int i = 0; i < _indexCount; i++) {
- tableBuffer.getInt(); //Forward past Unknown
- _indexes.get(i).read(tableBuffer, _columns);
- }
-
- // reset to end of index info
- tableBuffer.position(idxEndOffset);
- }
-
- /**
- * Creates an index with the given initial info.
- */
- private Index createIndex(int uniqueEntryCount, int uniqueEntryCountOffset)
- {
- return(_useBigIndex ?
- new BigIndex(this, uniqueEntryCount, uniqueEntryCountOffset) :
- new SimpleIndex(this, uniqueEntryCount, uniqueEntryCountOffset));
- }
-
- /**
- * Writes the given page data to the given page number, clears any other
- * relevant buffers.
- */
- private void writeDataPage(ByteBuffer pageBuffer, int pageNumber)
- throws IOException
- {
- // write the page data
- getPageChannel().writePage(pageBuffer, pageNumber);
-
- // possibly invalidate the add row buffer if a different data buffer is
- // being written (e.g. this happens during deleteRow)
- _addRowBufferH.possiblyInvalidate(pageNumber, pageBuffer);
-
- // update modification count so any active RowStates can keep themselves
- // up-to-date
- ++_modCount;
- }
-
- /**
- * Returns a name read from the buffer at the current position. The
- * expected name format is the name length as a short followed by (length *
- * 2) bytes encoded using the {@link JetFormat#CHARSET}
- */
- private String readName(ByteBuffer buffer) {
- int nameLength = ByteUtil.getUnsignedShort(buffer);
- byte[] nameBytes = new byte[nameLength];
- buffer.get(nameBytes);
- return Column.decodeUncompressedText(nameBytes, getFormat());
- }
-
- /**
- * Skips past a name int the buffer at the current position. The
- * expected name format is the same as that for {@link #readName}.
- */
- private void skipName(ByteBuffer buffer) {
- int nameLength = ByteUtil.getUnsignedShort(buffer);
- buffer.position(buffer.position() + nameLength);
- }
-
- /**
- * Converts a map of columnName -> columnValue to an array of row values
- * appropriate for a call to {@link #addRow(Object...)}.
- */
- public Object[] asRow(Map<String,Object> rowMap) {
- Object[] row = new Object[_columns.size()];
- if(rowMap == null) {
- return row;
- }
- for(Column col : _columns) {
- row[col.getColumnIndex()] = rowMap.get(col.getName());
- }
- return row;
- }
-
- /**
- * Add a single row to this table and write it to disk
- * <p>
- * Note, if this table has an auto-number column, the value written will be
- * put back into the given row array.
- *
- * @param row row values for a single row. the row will be modified if
- * this table contains an auto-number column, otherwise it
- * will not be modified.
- */
- public void addRow(Object... row) throws IOException {
- addRows(Collections.singletonList(row), _singleRowBufferH);
- }
-
- /**
- * Add multiple rows to this table, only writing to disk after all
- * rows have been written, and every time a data page is filled. This
- * is much more efficient than calling <code>addRow</code> multiple times.
- * <p>
- * Note, if this table has an auto-number column, the values written will be
- * put back into the given row arrays.
- *
- * @param rows List of Object[] row values. the rows will be modified if
- * this table contains an auto-number column, otherwise they
- * will not be modified.
- */
- public void addRows(List<? extends Object[]> rows) throws IOException {
- addRows(rows, _multiRowBufferH);
- }
-
- /**
- * Add multiple rows to this table, only writing to disk after all
- * rows have been written, and every time a data page is filled.
- * @param inRows List of Object[] row values
- * @param writeRowBufferH TempBufferHolder used to generate buffers for
- * writing the row data
- */
- private void addRows(List<? extends Object[]> inRows,
- TempBufferHolder writeRowBufferH)
- throws IOException
- {
- // copy the input rows to a modifiable list so we can update the elements
- List<Object[]> rows = new ArrayList<Object[]>(inRows);
- ByteBuffer[] rowData = new ByteBuffer[rows.size()];
- for (int i = 0; i < rows.size(); i++) {
-
- // we need to make sure the row is the right length (fill with null).
- // note, if the row is copied the caller will not be able to access any
- // generated auto-number value, but if they need that info they should
- // use a row array of the right size!
- Object[] row = rows.get(i);
- if(row.length < _columns.size()) {
- row = dupeRow(row, _columns.size());
- // we copied the row, so put the copy back into the rows list
- rows.set(i, row);
- }
-
- // write the row of data to a temporary buffer
- rowData[i] = createRow(row, getFormat().MAX_ROW_SIZE,
- writeRowBufferH.getPageBuffer(getPageChannel()));
-
- if (rowData[i].limit() > getFormat().MAX_ROW_SIZE) {
- throw new IOException("Row size " + rowData[i].limit() +
- " is too large");
- }
- }
-
- ByteBuffer dataPage = null;
- int pageNumber = PageChannel.INVALID_PAGE_NUMBER;
-
- // find last data page (Not bothering to check other pages for free
- // space.)
- UsageMap.PageCursor revPageCursor = _ownedPages.cursor();
- revPageCursor.afterLast();
- while(true) {
- int tmpPageNumber = revPageCursor.getPreviousPage();
- if(tmpPageNumber < 0) {
- break;
- }
- dataPage = _addRowBufferH.setPage(getPageChannel(), tmpPageNumber);
- if(dataPage.get() == PageTypes.DATA) {
- // found last data page, only use if actually listed in free space
- // pages
- if(_freeSpacePages.containsPageNumber(tmpPageNumber)) {
- pageNumber = tmpPageNumber;
- }
- break;
- }
- }
-
- if(pageNumber == PageChannel.INVALID_PAGE_NUMBER) {
- // No data pages exist (with free space). Create a new one.
- dataPage = newDataPage();
- pageNumber = _addRowBufferH.getPageNumber();
- }
-
- for (int i = 0; i < rowData.length; i++) {
- int rowSize = rowData[i].remaining();
- if(!rowFitsOnDataPage(rowSize, dataPage, getFormat())) {
-
- // Last data page is full. Create a new one.
- writeDataPage(dataPage, pageNumber);
- _freeSpacePages.removePageNumber(pageNumber);
-
- dataPage = newDataPage();
- pageNumber = _addRowBufferH.getPageNumber();
- }
-
- // write out the row data
- int rowNum = addDataPageRow(dataPage, rowSize, getFormat());
- dataPage.put(rowData[i]);
-
- // update the indexes
- for(Index index : _indexes) {
- index.addRow(rows.get(i), new RowId(pageNumber, rowNum));
- }
- }
- writeDataPage(dataPage, pageNumber);
-
- // Update tdef page
- updateTableDefinition(rows.size());
- }
-
- /**
- * Updates the table definition after rows are modified.
- */
- private void updateTableDefinition(int rowCountInc) throws IOException
- {
- // load table definition
- ByteBuffer tdefPage = _tableDefBufferH.setPage(getPageChannel(),
- _tableDefPageNumber);
-
- // make sure rowcount and autonumber are up-to-date
- _rowCount += rowCountInc;
- tdefPage.putInt(getFormat().OFFSET_NUM_ROWS, _rowCount);
- tdefPage.putInt(getFormat().OFFSET_NEXT_AUTO_NUMBER, _lastAutoNumber);
-
- // write any index changes
- Iterator<Index> indIter = _indexes.iterator();
- for (int i = 0; i < _indexes.size(); i++) {
- Index index = indIter.next();
- // write the unique entry count for the index to the table definition
- // page
- tdefPage.putInt(index.getUniqueEntryCountOffset(),
- index.getUniqueEntryCount());
- // write the entry page for the index
- index.update();
- }
-
- // write modified table definition
- getPageChannel().writePage(tdefPage, _tableDefPageNumber);
- }
-
- /**
- * Create a new data page
- * @return Page number of the new page
- */
- private ByteBuffer newDataPage() throws IOException {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Creating new data page");
- }
- ByteBuffer dataPage = _addRowBufferH.setNewPage(getPageChannel());
- dataPage.put(PageTypes.DATA); //Page type
- dataPage.put((byte) 1); //Unknown
- dataPage.putShort((short)getFormat().PAGE_INITIAL_FREE_SPACE); //Free space in this page
- dataPage.putInt(_tableDefPageNumber); //Page pointer to table definition
- dataPage.putInt(0); //Unknown
- dataPage.putShort((short)0); //Number of rows on this page
- int pageNumber = _addRowBufferH.getPageNumber();
- getPageChannel().writePage(dataPage, pageNumber);
- _ownedPages.addPageNumber(pageNumber);
- _freeSpacePages.addPageNumber(pageNumber);
- return dataPage;
- }
-
- /**
- * Serialize a row of Objects into a byte buffer.
- * <p>
- * Note, if this table has an auto-number column, the value written will be
- * put back into the given row array.
- *
- * @param rowArray row data, expected to be correct length for this table
- * @param maxRowSize max size the data can be for this row
- * @param buffer buffer to which to write the row data
- * @return the given buffer, filled with the row data
- */
- ByteBuffer createRow(Object[] rowArray, int maxRowSize, ByteBuffer buffer)
- throws IOException
- {
- buffer.putShort(_maxColumnCount);
- NullMask nullMask = new NullMask(_maxColumnCount);
-
- //Fixed length column data comes first
- int fixedDataStart = buffer.position();
- int fixedDataEnd = fixedDataStart;
- for (Column col : _columns) {
-
- if(!col.isVariableLength()) {
-
- Object rowValue = rowArray[col.getColumnIndex()];
-
- if (col.getType() == DataType.BOOLEAN) {
-
- if(Column.toBooleanValue(rowValue)) {
- //Booleans are stored in the null mask
- nullMask.markNotNull(col);
- }
-
- } else {
-
- if(col.isAutoNumber()) {
-
- // ignore given row value, use next autonumber
- rowValue = getNextAutoNumber();
-
- // we need to stick this back in the row so that the indexes get
- // updated correctly (and caller can get the generated value)
- rowArray[col.getColumnIndex()] = rowValue;
- }
-
- if(rowValue != null) {
-
- // we have a value
- nullMask.markNotNull(col);
-
- //remainingRowLength is ignored when writing fixed length data
- buffer.position(fixedDataStart + col.getFixedDataOffset());
- buffer.put(col.write(rowValue, 0));
-
- // keep track of the end of fixed data
- if(buffer.position() > fixedDataEnd) {
- fixedDataEnd = buffer.position();
- }
- }
- }
- }
- }
-
- // reposition at end of fixed data
- buffer.position(fixedDataEnd);
-
- // only need this info if this table contains any var length data
- if(_maxVarColumnCount > 0) {
-
- // figure out how much space remains for var length data. first,
- // account for already written space
- maxRowSize -= buffer.position();
- // now, account for trailer space
- maxRowSize -= (nullMask.byteSize() + 4 + (_maxVarColumnCount * 2));
-
- // for each non-null long value column we need to reserve a small
- // amount of space so that we don't end up running out of row space
- // later by being too greedy
- for (Column varCol : _varColumns) {
- if((varCol.getType().isLongValue()) &&
- (rowArray[varCol.getColumnIndex()] != null)) {
- maxRowSize -= getFormat().SIZE_LONG_VALUE_DEF;
- }
- }
-
- //Now write out variable length column data
- short[] varColumnOffsets = new short[_maxVarColumnCount];
- int varColumnOffsetsIndex = 0;
- for (Column varCol : _varColumns) {
- short offset = (short) buffer.position();
- Object rowValue = rowArray[varCol.getColumnIndex()];
- if (rowValue != null) {
- // we have a value
- nullMask.markNotNull(varCol);
-
- ByteBuffer varDataBuf = varCol.write(rowValue, maxRowSize);
- maxRowSize -= varDataBuf.remaining();
- if(varCol.getType().isLongValue()) {
- // we already accounted for some amount of the long value data
- // above. add that space back so we don't double count
- maxRowSize += getFormat().SIZE_LONG_VALUE_DEF;
- }
- buffer.put(varDataBuf);
- }
-
- // we do a loop here so that we fill in offsets for deleted columns
- while(varColumnOffsetsIndex <= varCol.getVarLenTableIndex()) {
- varColumnOffsets[varColumnOffsetsIndex++] = offset;
- }
- }
-
- // fill in offsets for any remaining deleted columns
- while(varColumnOffsetsIndex < varColumnOffsets.length) {
- varColumnOffsets[varColumnOffsetsIndex++] = (short) buffer.position();
- }
-
- buffer.putShort((short) buffer.position()); //EOD marker
- //Now write out variable length offsets
- //Offsets are stored in reverse order
- for (int i = _maxVarColumnCount - 1; i >= 0; i--) {
- buffer.putShort(varColumnOffsets[i]);
- }
- buffer.putShort(_maxVarColumnCount); //Number of var length columns
- }
-
- nullMask.write(buffer); //Null mask
- buffer.flip();
- if (LOG.isDebugEnabled()) {
- LOG.debug("Creating new data block:\n" + ByteUtil.toHexString(buffer, buffer.limit()));
- }
- return buffer;
- }
-
- public int getRowCount() {
- return _rowCount;
- }
-
- private int getNextAutoNumber() {
- // note, the saved value is the last one handed out, so pre-increment
- return ++_lastAutoNumber;
- }
-
- int getLastAutoNumber() {
- // gets the last used auto number (does not modify)
- return _lastAutoNumber;
- }
-
- @Override
- public String toString() {
- StringBuilder rtn = new StringBuilder();
- rtn.append("Type: " + _tableType);
- rtn.append("\nName: " + _name);
- rtn.append("\nRow count: " + _rowCount);
- rtn.append("\nColumn count: " + _columns.size());
- rtn.append("\nIndex count: " + _indexCount);
- rtn.append("\nColumns:\n");
- for(Column col : _columns) {
- rtn.append(col);
- }
- rtn.append("\nIndexes:\n");
- for(Index index : _indexes) {
- rtn.append(index);
- }
- rtn.append("\nOwned pages: " + _ownedPages + "\n");
- return rtn.toString();
- }
-
- /**
- * @return A simple String representation of the entire table in tab-delimited format
- */
- public String display() throws IOException {
- return display(Long.MAX_VALUE);
- }
-
- /**
- * @param limit Maximum number of rows to display
- * @return A simple String representation of the entire table in tab-delimited format
- */
- public String display(long limit) throws IOException {
- reset();
- StringBuilder rtn = new StringBuilder();
- for(Iterator<Column> iter = _columns.iterator(); iter.hasNext(); ) {
- Column col = iter.next();
- rtn.append(col.getName());
- if (iter.hasNext()) {
- rtn.append("\t");
- }
- }
- rtn.append("\n");
- Map<String, Object> row;
- int rowCount = 0;
- while ((rowCount++ < limit) && (row = getNextRow()) != null) {
- for(Iterator<Object> iter = row.values().iterator(); iter.hasNext(); ) {
- Object obj = iter.next();
- if (obj instanceof byte[]) {
- byte[] b = (byte[]) obj;
- rtn.append(ByteUtil.toHexString(b));
- //This block can be used to easily dump a binary column to a file
- /*java.io.File f = java.io.File.createTempFile("ole", ".bin");
- java.io.FileOutputStream out = new java.io.FileOutputStream(f);
- out.write(b);
- out.flush();
- out.close();*/
- } else {
- rtn.append(String.valueOf(obj));
- }
- if (iter.hasNext()) {
- rtn.append("\t");
- }
- }
- rtn.append("\n");
- }
- return rtn.toString();
- }
-
- /**
- * Updates free space and row info for a new row of the given size in the
- * given data page. Positions the page for writing the row data.
- * @return the row number of the new row
- */
- public static int addDataPageRow(ByteBuffer dataPage,
- int rowSize,
- JetFormat format)
- {
- int rowSpaceUsage = getRowSpaceUsage(rowSize, format);
-
- // Decrease free space record.
- short freeSpaceInPage = dataPage.getShort(format.OFFSET_FREE_SPACE);
- dataPage.putShort(format.OFFSET_FREE_SPACE, (short) (freeSpaceInPage -
- rowSpaceUsage));
-
- // Increment row count record.
- short rowCount = dataPage.getShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE);
- dataPage.putShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE,
- (short) (rowCount + 1));
-
- // determine row position
- short rowLocation = findRowEnd(dataPage, rowCount, format);
- rowLocation -= rowSize;
-
- // write row position
- dataPage.putShort(getRowStartOffset(rowCount, format), rowLocation);
-
- // set position for row data
- dataPage.position(rowLocation);
-
- return rowCount;
- }
-
- /**
- * Returns the row count for the current page. If the page is invalid
- * ({@code null}) or the page is not a DATA page, 0 is returned.
- */
- private static int getRowsOnDataPage(ByteBuffer rowBuffer, JetFormat format)
- throws IOException
- {
- int rowsOnPage = 0;
- if((rowBuffer != null) && (rowBuffer.get(0) == PageTypes.DATA)) {
- rowsOnPage = rowBuffer.getShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE);
- }
- return rowsOnPage;
- }
-
- /**
- * @throws IllegalStateException if the given rowId is invalid
- */
- private static void requireValidRowId(RowId rowId) {
- if(!rowId.isValid()) {
- throw new IllegalArgumentException("Given rowId is invalid: " + rowId);
- }
- }
-
- /**
- * @throws IllegalStateException if the given row is invalid or deleted
- */
- private static void requireNonDeletedRow(RowState rowState, RowId rowId) {
- if(!rowState.isValid()) {
- throw new IllegalArgumentException(
- "Given rowId is invalid for this table: " + rowId);
- }
- if(rowState.isDeleted()) {
- throw new IllegalStateException("Row is deleted: " + rowId);
- }
- }
-
- public static boolean isDeletedRow(short rowStart) {
- return ((rowStart & DELETED_ROW_MASK) != 0);
- }
-
- public static boolean isOverflowRow(short rowStart) {
- return ((rowStart & OVERFLOW_ROW_MASK) != 0);
- }
-
- public static short cleanRowStart(short rowStart) {
- return (short)(rowStart & OFFSET_MASK);
- }
-
- public static short findRowStart(ByteBuffer buffer, int rowNum,
- JetFormat format)
- {
- return cleanRowStart(
- buffer.getShort(getRowStartOffset(rowNum, format)));
- }
-
- public static int getRowStartOffset(int rowNum, JetFormat format)
- {
- return format.OFFSET_ROW_START + (format.SIZE_ROW_LOCATION * rowNum);
- }
-
- public static short findRowEnd(ByteBuffer buffer, int rowNum,
- JetFormat format)
- {
- return (short)((rowNum == 0) ?
- format.PAGE_SIZE :
- cleanRowStart(
- buffer.getShort(getRowEndOffset(rowNum, format))));
- }
-
- public static int getRowEndOffset(int rowNum, JetFormat format)
- {
- return format.OFFSET_ROW_START + (format.SIZE_ROW_LOCATION * (rowNum - 1));
- }
-
- public static int getRowSpaceUsage(int rowSize, JetFormat format)
- {
- return rowSize + format.SIZE_ROW_LOCATION;
- }
-
- /**
- * @return the number of "AutoNumber" columns in the given collection of
- * columns.
- */
- public static int countAutoNumberColumns(Collection<Column> columns) {
- int numAutoNumCols = 0;
- for(Column c : columns) {
- if(c.isAutoNumber()) {
- ++numAutoNumCols;
- }
- }
- return numAutoNumCols;
- }
-
- /**
- * Returns {@code true} if a row of the given size will fit on the given
- * data page, {@code false} otherwise.
- */
- public static boolean rowFitsOnDataPage(
- int rowLength, ByteBuffer dataPage, JetFormat format)
- throws IOException
- {
- int rowSpaceUsage = getRowSpaceUsage(rowLength, format);
- short freeSpaceInPage = dataPage.getShort(format.OFFSET_FREE_SPACE);
- int rowsOnPage = getRowsOnDataPage(dataPage, format);
- return ((rowSpaceUsage <= freeSpaceInPage) &&
- (rowsOnPage < format.MAX_NUM_ROWS_ON_DATA_PAGE));
- }
-
- /**
- * Duplicates and returns a row of data, optionally with a longer length
- * filled with {@code null}.
- */
- static Object[] dupeRow(Object[] row, int newRowLength) {
- Object[] copy = new Object[newRowLength];
- System.arraycopy(row, 0, copy, 0, row.length);
- return copy;
- }
-
- /** various statuses for the row data */
- private enum RowStatus {
- INIT, INVALID_PAGE, INVALID_ROW, VALID, DELETED, NORMAL, OVERFLOW;
- }
-
- /** the phases the RowState moves through as the data is parsed */
- private enum RowStateStatus {
- INIT, AT_HEADER, AT_FINAL;
- }
-
- /**
- * Maintains the state of reading a row of data.
- */
- public final class RowState
- {
- /** Buffer used for reading the header row data pages */
- private final TempPageHolder _headerRowBufferH;
- /** the header rowId */
- private RowId _headerRowId = RowId.FIRST_ROW_ID;
- /** the number of rows on the header page */
- private int _rowsOnHeaderPage;
- /** the rowState status */
- private RowStateStatus _status = RowStateStatus.INIT;
- /** the row status */
- private RowStatus _rowStatus = RowStatus.INIT;
- /** buffer used for reading overflow pages */
- private final TempPageHolder _overflowRowBufferH =
- TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
- /** the row buffer which contains the final data (after following any
- overflow pointers) */
- private ByteBuffer _finalRowBuffer;
- /** the rowId which contains the final data (after following any overflow
- pointers) */
- private RowId _finalRowId = null;
- /** true if the row values array has data */
- private boolean _haveRowValues;
- /** values read from the last row */
- private final Object[] _rowValues;
- /** last modification count seen on the table we track this so that the
- rowState can detect updates to the table and re-read any buffered
- data */
- private int _lastModCount;
- /** optional error handler to use when row errors are encountered */
- private ErrorHandler _errorHandler;
-
- private RowState(TempBufferHolder.Type headerType) {
- _headerRowBufferH = TempPageHolder.newHolder(headerType);
- _rowValues = new Object[Table.this.getColumnCount()];
- _lastModCount = Table.this._modCount;
- }
-
- public Table getTable() {
- return Table.this;
- }
-
- public ErrorHandler getErrorHandler() {
- return((_errorHandler != null) ? _errorHandler :
- getTable().getErrorHandler());
- }
-
- public void setErrorHandler(ErrorHandler newErrorHandler) {
- _errorHandler = newErrorHandler;
- }
-
- public void reset() {
- _finalRowId = null;
- _finalRowBuffer = null;
- _rowsOnHeaderPage = 0;
- _status = RowStateStatus.INIT;
- _rowStatus = RowStatus.INIT;
- if(_haveRowValues) {
- Arrays.fill(_rowValues, null);
- _haveRowValues = false;
- }
- }
-
- public boolean isUpToDate() {
- return(Table.this._modCount == _lastModCount);
- }
-
- private void checkForModification() {
- if(!isUpToDate()) {
- reset();
- _headerRowBufferH.invalidate();
- _overflowRowBufferH.invalidate();
- _lastModCount = Table.this._modCount;
- }
- }
-
- private ByteBuffer getFinalPage()
- throws IOException
- {
- if(_finalRowBuffer == null) {
- // (re)load current page
- _finalRowBuffer = getHeaderPage();
- }
- return _finalRowBuffer;
- }
-
- public RowId getFinalRowId() {
- if(_finalRowId == null) {
- _finalRowId = getHeaderRowId();
- }
- return _finalRowId;
- }
-
- private void setRowStatus(RowStatus rowStatus) {
- _rowStatus = rowStatus;
- }
-
- public boolean isValid() {
- return(_rowStatus.ordinal() >= RowStatus.VALID.ordinal());
- }
-
- public boolean isDeleted() {
- return(_rowStatus == RowStatus.DELETED);
- }
-
- public boolean isOverflow() {
- return(_rowStatus == RowStatus.OVERFLOW);
- }
-
- public boolean isHeaderPageNumberValid() {
- return(_rowStatus.ordinal() > RowStatus.INVALID_PAGE.ordinal());
- }
-
- public boolean isHeaderRowNumberValid() {
- return(_rowStatus.ordinal() > RowStatus.INVALID_ROW.ordinal());
- }
-
- private void setStatus(RowStateStatus status) {
- _status = status;
- }
-
- public boolean isAtHeaderRow() {
- return(_status.ordinal() >= RowStateStatus.AT_HEADER.ordinal());
- }
-
- public boolean isAtFinalRow() {
- return(_status.ordinal() >= RowStateStatus.AT_FINAL.ordinal());
- }
-
- private void setRowValue(int idx, Object value) {
- _haveRowValues = true;
- _rowValues[idx] = value;
- }
-
- public Object[] getRowValues() {
- return dupeRow(_rowValues, _rowValues.length);
- }
-
- public RowId getHeaderRowId() {
- return _headerRowId;
- }
-
- public int getRowsOnHeaderPage() {
- return _rowsOnHeaderPage;
- }
-
- private ByteBuffer getHeaderPage()
- throws IOException
- {
- checkForModification();
- return _headerRowBufferH.getPage(getPageChannel());
- }
-
- private ByteBuffer setHeaderRow(RowId rowId)
- throws IOException
- {
- checkForModification();
-
- // don't do any work if we are already positioned correctly
- if(isAtHeaderRow() && (getHeaderRowId().equals(rowId))) {
- return(isValid() ? getHeaderPage() : null);
- }
-
- // rejigger everything
- reset();
- _headerRowId = rowId;
- _finalRowId = rowId;
-
- int pageNumber = rowId.getPageNumber();
- int rowNumber = rowId.getRowNumber();
- if((pageNumber < 0) || !_ownedPages.containsPageNumber(pageNumber)) {
- setRowStatus(RowStatus.INVALID_PAGE);
- return null;
- }
-
- _finalRowBuffer = _headerRowBufferH.setPage(getPageChannel(),
- pageNumber);
- _rowsOnHeaderPage = getRowsOnDataPage(_finalRowBuffer, getFormat());
-
- if((rowNumber < 0) || (rowNumber >= _rowsOnHeaderPage)) {
- setRowStatus(RowStatus.INVALID_ROW);
- return null;
- }
-
- setRowStatus(RowStatus.VALID);
- return _finalRowBuffer;
- }
-
- private ByteBuffer setOverflowRow(RowId rowId)
- throws IOException
- {
- // this should never see modifications because it only happens within
- // the positionAtRowData method
- if(!isUpToDate()) {
- throw new IllegalStateException("Table modified while searching?");
- }
- if(_rowStatus != RowStatus.OVERFLOW) {
- throw new IllegalStateException("Row is not an overflow row?");
- }
- _finalRowId = rowId;
- _finalRowBuffer = _overflowRowBufferH.setPage(getPageChannel(),
- rowId.getPageNumber());
- return _finalRowBuffer;
- }
-
- private Object handleRowError(Column column,
- byte[] columnData,
- Exception error)
- throws IOException
- {
- return getErrorHandler().handleRowError(column, columnData,
- this, error);
- }
-
- @Override
- public String toString()
- {
- return "RowState: headerRowId = " + _headerRowId + ", finalRowId = " +
- _finalRowId;
- }
- }
-
- }
|