123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406240724082409241024112412241324142415241624172418241924202421242224232424242524262427242824292430243124322433243424352436243724382439244024412442244324442445244624472448244924502451245224532454245524562457245824592460246124622463246424652466246724682469247024712472247324742475247624772478247924802481248224832484248524862487248824892490249124922493249424952496 |
- /*
- 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.nio.charset.Charset;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.Collection;
- import java.util.Collections;
- import java.util.Comparator;
- import java.util.HashMap;
- 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
- * @usage _general_class_
- */
- 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;
-
- static final int MAGIC_TABLE_NUMBER = 1625;
-
- private static final int MAX_BYTE = 256;
-
- /**
- * Table type code for system tables
- * @usage _intermediate_class_
- */
- public static final byte TYPE_SYSTEM = 0x53;
- /**
- * Table type code for user tables
- * @usage _intermediate_class_
- */
- public static final byte TYPE_USER = 0x4e;
-
- /**
- * enum which controls the ordering of the columns in a table.
- * @usage _intermediate_class_
- */
- public enum ColumnOrder {
- /** columns are ordered based on the order of the data in the table (this
- order does not change as columns are added to the table). */
- DATA,
- /** columns are ordered based on the "display" order (this order can be
- changed arbitrarily) */
- DISPLAY;
- }
-
- /** comparator which sorts variable length columns based 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));
- }
- };
-
- /** comparator which sorts columns based on their display index */
- private static final Comparator<Column> DISPLAY_ORDER_COMPARATOR =
- new Comparator<Column>() {
- public int compare(Column c1, Column c2) {
- return ((c1.getDisplayIndex() < c2.getDisplayIndex()) ? -1 :
- ((c1.getDisplayIndex() > c2.getDisplayIndex()) ? 1 :
- 0));
- }
- };
-
- /** owning database */
- private final Database _database;
- /** additional table flags from the catalog entry */
- private int _flags;
- /** Type of the table (either TYPE_SYSTEM or TYPE_USER) */
- private byte _tableType;
- /** Number of actual indexes on the table */
- private int _indexCount;
- /** Number of logical indexes for the table */
- private int _logicalIndexCount;
- /** Number of rows in the table */
- private int _rowCount;
- /** last long auto number for the table */
- private int _lastLongAutoNumber;
- /** last complex type auto number for the table */
- private int _lastComplexTypeAutoNumber;
- /** 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 autonumber columns in this table, ordered by column number */
- private List<Column> _autoNumColumns;
- /** List of indexes on this table (multiple logical indexes may be backed by
- the same index data) */
- private List<Index> _indexes = new ArrayList<Index>();
- /** List of index datas on this table (the actual backing data for an
- index) */
- private List<IndexData> _indexDatas = new ArrayList<IndexData>();
- /** 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);
- /** "big index support" is optional */
- private final boolean _useBigIndex;
- /** optional error handler to use when row errors are encountered */
- private ErrorHandler _tableErrorHandler;
- /** properties for this table */
- private PropertyMap _props;
- /** properties group for this table (and columns) */
- private PropertyMaps _propertyMaps;
-
- /** 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 = true;
- 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, int flags, boolean useBigIndex)
- throws IOException
- {
- _database = database;
- _tableDefPageNumber = pageNumber;
- _name = name;
- _flags = flags;
- _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;
- }
-
- /**
- * @return The name of the table
- * @usage _general_method_
- */
- public String getName() {
- return _name;
- }
-
- /**
- * Whether or not this table has been marked as hidden.
- * @usage _general_method_
- */
- public boolean isHidden() {
- return((_flags & Database.HIDDEN_OBJECT_FLAG) != 0);
- }
-
- /**
- * @usage _advanced_method_
- */
- public boolean doUseBigIndex() {
- return _useBigIndex;
- }
-
- /**
- * @usage _advanced_method_
- */
- public int getMaxColumnCount() {
- return _maxColumnCount;
- }
-
- /**
- * @usage _general_method_
- */
- public int getColumnCount() {
- return _columns.size();
- }
-
- /**
- * @usage _general_method_
- */
- public Database getDatabase() {
- return _database;
- }
-
- /**
- * @usage _advanced_method_
- */
- public JetFormat getFormat() {
- return getDatabase().getFormat();
- }
-
- /**
- * @usage _advanced_method_
- */
- 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.
- * @usage _intermediate_method_
- */
- 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.
- * @usage _intermediate_method_
- */
- public void setErrorHandler(ErrorHandler newErrorHandler) {
- _tableErrorHandler = newErrorHandler;
- }
-
- public int getTableDefPageNumber() {
- return _tableDefPageNumber;
- }
-
- /**
- * @usage _advanced_method_
- */
- public RowState createRowState() {
- return new RowState(TempBufferHolder.Type.HARD);
- }
-
- protected UsageMap.PageCursor getOwnedPagesCursor() {
- return _ownedPages.cursor();
- }
-
- /**
- * Returns the <i>approximate</i> number of database pages owned by this
- * table and all related indexes (this number does <i>not</i> take into
- * account pages used for large OLE/MEMO fields).
- * <p>
- * To calculate the approximate number of bytes owned by a table:
- * <code>
- * int approxTableBytes = (table.getApproximateOwnedPageCount() *
- * table.getFormat().PAGE_SIZE);
- * </code>
- * @usage _intermediate_method_
- */
- public int getApproximateOwnedPageCount() {
- // add a page for the table def (although that might actually be more than
- // one page)
- int count = _ownedPages.getPageCount() + 1;
- // note, we count owned pages from _physical_ indexes, not logical indexes
- // (otherwise we could double count pages)
- for(IndexData indexData : _indexDatas) {
- count += indexData.getOwnedPageCount();
- }
- return count;
- }
-
- protected TempPageHolder getLongValueBuffer() {
- return _longValueBufferH;
- }
-
- /**
- * @return All of the columns in this table (unmodifiable List)
- * @usage _general_method_
- */
- public List<Column> getColumns() {
- return Collections.unmodifiableList(_columns);
- }
-
- /**
- * @return the column with the given name
- * @usage _general_method_
- */
- public Column getColumn(String name) {
- for(Column column : _columns) {
- if(column.getName().equalsIgnoreCase(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();
- _autoNumColumns = getAutoNumberColumns(columns);
- }
-
- /**
- * @return the properties for this table
- * @usage _general_method_
- */
- public PropertyMap getProperties() throws IOException {
- if(_props == null) {
- _props = getPropertyMaps().getDefault();
- }
- return _props;
- }
-
- /**
- * @return all PropertyMaps for this table (and columns)
- * @usage _general_method_
- */
- protected PropertyMaps getPropertyMaps() throws IOException {
- if(_propertyMaps == null) {
- _propertyMaps = getDatabase().getPropertiesForObject(
- _tableDefPageNumber);
- }
- return _propertyMaps;
- }
-
- /**
- * @return All of the Indexes on this table (unmodifiable List)
- * @usage _intermediate_method_
- */
- public List<Index> getIndexes() {
- return Collections.unmodifiableList(_indexes);
- }
-
- /**
- * @return the index with the given name
- * @throws IllegalArgumentException if there is no index with the given name
- * @usage _intermediate_method_
- */
- public Index getIndex(String name) {
- for(Index index : _indexes) {
- if(index.getName().equalsIgnoreCase(name)) {
- return index;
- }
- }
- throw new IllegalArgumentException("Index with name " + name +
- " does not exist on this table");
- }
-
- /**
- * @return the primary key index for this table
- * @throws IllegalArgumentException if there is no primary key index on this
- * table
- * @usage _intermediate_method_
- */
- public Index getPrimaryKeyIndex() {
- for(Index index : _indexes) {
- if(index.isPrimaryKey()) {
- return index;
- }
- }
- throw new IllegalArgumentException("Table " + getName() +
- " does not have a primary key index");
- }
-
- /**
- * @return the foreign key index joining this table to the given other table
- * @throws IllegalArgumentException if there is no relationship between this
- * table and the given table
- * @usage _intermediate_method_
- */
- public Index getForeignKeyIndex(Table otherTable) {
- for(Index index : _indexes) {
- if(index.isForeignKey() && (index.getReference() != null) &&
- (index.getReference().getOtherTablePageNumber() ==
- otherTable.getTableDefPageNumber())) {
- return index;
- }
- }
- throw new IllegalArgumentException(
- "Table " + getName() + " does not have a foreign key reference to " +
- otherTable.getName());
- }
-
- /**
- * @return All of the IndexData on this table (unmodifiable List)
- */
- List<IndexData> getIndexDatas() {
- return Collections.unmodifiableList(_indexDatas);
- }
-
- /**
- * Only called by unit tests
- */
- int getLogicalIndexCount() {
- return _logicalIndexCount;
- }
-
- private Cursor getInternalCursor() {
- if(_cursor == null) {
- _cursor = Cursor.createCursor(this);
- }
- return _cursor;
- }
-
- /**
- * After calling this method, getNextRow will return the first row in the
- * table, see {@link Cursor#reset}.
- * @usage _general_method_
- */
- public void reset() {
- getInternalCursor().reset();
- }
-
- /**
- * Delete the current row (retrieved by a call to {@link #getNextRow()}).
- * @usage _general_method_
- */
- public void deleteCurrentRow() throws IOException {
- getInternalCursor().deleteCurrentRow();
- }
-
- /**
- * Delete the row on which the given rowState is currently positioned.
- * <p>
- * Note, this method is not generally meant to be used directly. You should
- * use the {@link #deleteCurrentRow} method or use the Cursor class, which
- * allows for more complex table interactions.
- * @usage _advanced_method_
- */
- 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 = (!_indexDatas.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(IndexData indexData : _indexDatas) {
- indexData.deleteRow(rowValues, rowId);
- }
-
- // make sure table def gets updated
- updateTableDefinition(-1);
- }
-
- /**
- * @return The next row in this table (Column name -> Column value)
- * @usage _general_method_
- */
- 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)
- * @usage _general_method_
- */
- public Map<String, Object> getNextRow(Collection<String> columnNames)
- throws IOException
- {
- return getInternalCursor().getNextRow(columnNames);
- }
-
- /**
- * Reads a single column from the given row.
- * <p>
- * Note, this method is not generally meant to be used directly. Instead
- * use the Cursor class, which allows for more complex table interactions,
- * e.g. {@link Cursor#getCurrentRowValue}.
- * @usage _advanced_method_
- */
- 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);
-
- return getRowColumn(getFormat(), rowBuffer, column, rowState, null);
- }
-
- /**
- * Reads some columns from the given row.
- * @param columnNames Only column names in this collection will be returned
- * @usage _advanced_method_
- */
- 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(getFormat(), rowState, 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(
- JetFormat format,
- RowState rowState,
- ByteBuffer rowBuffer,
- 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
- column.setRowValue(
- rtn, getRowColumn(format, rowBuffer, column, rowState, null));
- }
- }
- return rtn;
- }
-
- /**
- * Reads the column data from the given row buffer. Leaves limit unchanged.
- * Caches the returned value in the rowState.
- */
- private static Object getRowColumn(JetFormat format,
- ByteBuffer rowBuffer,
- Column column,
- RowState rowState,
- Map<Column,byte[]> rawVarValues)
- throws IOException
- {
- byte[] columnData = null;
- try {
-
- NullMask nullMask = rowState.getNullMask(rowBuffer);
- boolean isNull = nullMask.isNull(column);
- if(column.getType() == DataType.BOOLEAN) {
- // Boolean values are stored in the null mask. see note about
- // caching below
- return rowState.setRowValue(column.getColumnIndex(),
- Boolean.valueOf(!isNull));
- } else if(isNull) {
- // well, that's easy! (no need to update cache w/ null)
- 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 + format.OFFSET_COLUMN_FIXED_DATA_ROW_OFFSET;
- colDataPos = dataStart + column.getFixedDataOffset();
- colDataLen = column.getType().getFixedSize(column.getLength());
-
- } else {
- int varDataStart;
- int varDataEnd;
-
- if(format.SIZE_ROW_VAR_COL_OFFSET == 2) {
-
- // read simple var length value
- int varColumnOffsetPos =
- (rowBuffer.limit() - nullMask.byteSize() - 4) -
- (column.getVarLenTableIndex() * 2);
-
- varDataStart = rowBuffer.getShort(varColumnOffsetPos);
- varDataEnd = rowBuffer.getShort(varColumnOffsetPos - 2);
-
- } else {
-
- // read jump-table based var length values
- short[] varColumnOffsets = readJumpTableVarColOffsets(
- rowState, rowBuffer, rowStart, nullMask);
-
- varDataStart = varColumnOffsets[column.getVarLenTableIndex()];
- varDataEnd = varColumnOffsets[column.getVarLenTableIndex() + 1];
- }
-
- colDataPos = rowStart + varDataStart;
- colDataLen = varDataEnd - varDataStart;
- }
-
- // grab the column data
- columnData = new byte[colDataLen];
- rowBuffer.position(colDataPos);
- rowBuffer.get(columnData);
-
- if((rawVarValues != null) && column.isVariableLength()) {
- // caller wants raw value as well
- rawVarValues.put(column, columnData);
- }
-
- // parse the column data. we 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.
- return rowState.setRowValue(column.getColumnIndex(),
- column.read(columnData));
-
- } catch(Exception e) {
-
- // cache "raw" row value. see note about caching above
- rowState.setRowValue(column.getColumnIndex(),
- Column.rawDataWrapper(columnData));
-
- return rowState.handleRowError(column, columnData, e);
- }
- }
-
- private static short[] readJumpTableVarColOffsets(
- RowState rowState, ByteBuffer rowBuffer, int rowStart,
- NullMask nullMask)
- {
- short[] varColOffsets = rowState.getVarColOffsets();
- if(varColOffsets != null) {
- return varColOffsets;
- }
-
- // calculate offsets using jump-table info
- int nullMaskSize = nullMask.byteSize();
- int rowEnd = rowStart + rowBuffer.remaining() - 1;
- int numVarCols = ByteUtil.getUnsignedByte(rowBuffer,
- rowEnd - nullMaskSize);
- varColOffsets = new short[numVarCols + 1];
-
- int rowLen = rowEnd - rowStart + 1;
- int numJumps = (rowLen - 1) / MAX_BYTE;
- int colOffset = rowEnd - nullMaskSize - numJumps - 1;
-
- // If last jump is a dummy value, ignore it
- if(((colOffset - rowStart - numVarCols) / MAX_BYTE) < numJumps) {
- numJumps--;
- }
-
- int jumpsUsed = 0;
- for(int i = 0; i < numVarCols + 1; i++) {
-
- while((jumpsUsed < numJumps) &&
- (i == ByteUtil.getUnsignedByte(
- rowBuffer, rowEnd - nullMaskSize-jumpsUsed - 1))) {
- jumpsUsed++;
- }
-
- varColOffsets[i] = (short)
- (ByteUtil.getUnsignedByte(rowBuffer, colOffset - i)
- + (jumpsUsed * MAX_BYTE));
- }
-
- rowState.setVarColOffsets(varColOffsets);
- return varColOffsets;
- }
-
- /**
- * Reads the null mask from the given row buffer. Leaves limit unchanged.
- */
- private NullMask getRowNullMask(ByteBuffer rowBuffer)
- throws IOException
- {
- // reset position to row start
- rowBuffer.reset();
-
- // Number of columns in this row
- int columnCount = ByteUtil.getUnsignedVarInt(
- rowBuffer, getFormat().SIZE_ROW_COLUMN_COUNT);
-
- // 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
- * @usage _advanced_method_
- */
- 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
- * @usage _advanced_method_
- */
- 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 a modifiable
- * 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
- * @usage _general_method_
- */
- public Iterator<Map<String, Object>> iterator()
- {
- return iterator(null);
- }
-
- /**
- * Calls <code>reset</code> on this table and returns a modifiable
- * 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
- * @usage _general_method_
- */
- public Iterator<Map<String, Object>> iterator(Collection<String> columnNames)
- {
- reset();
- return getInternalCursor().iterator(columnNames);
- }
-
- /**
- * Writes a new table defined by the given columns and indexes to the
- * database.
- * @return the first page of the new table's definition
- * @usage _advanced_method_
- */
- public static int writeTableDefinition(
- List<Column> columns, List<IndexBuilder> indexes,
- PageChannel pageChannel, JetFormat format, Charset charset)
- throws IOException
- {
- int indexCount = 0;
- int logicalIndexCount = 0;
- if(!indexes.isEmpty()) {
- // sort out index numbers. for now, these values will always match
- // (until we support writing foreign key indexes)
- for(IndexBuilder idx : indexes) {
- idx.setIndexNumber(logicalIndexCount++);
- idx.setIndexDataNumber(indexCount++);
- }
- }
-
- // allocate first table def page
- int tdefPageNumber = pageChannel.allocateNewPage();
-
- // first, create the usage map page
- int usageMapPageNumber =
- createUsageMapDefinitionBuffer(indexes, pageChannel, format);
-
- // next, determine how big the table def will be (in case it will be more
- // than one page)
- int idxDataLen = (indexCount * (format.SIZE_INDEX_DEFINITION +
- format.SIZE_INDEX_COLUMN_BLOCK)) +
- (logicalIndexCount * format.SIZE_INDEX_INFO_BLOCK);
- int totalTableDefSize = format.SIZE_TDEF_HEADER +
- (format.SIZE_COLUMN_DEF_BLOCK * columns.size()) + idxDataLen +
- format.SIZE_TDEF_TRAILER;
-
- // total up the amount of space used by the column and index names (2
- // bytes per char + 2 bytes for the length)
- for(Column col : columns) {
- int nameByteLen = (col.getName().length() *
- JetFormat.TEXT_FIELD_UNIT_SIZE);
- totalTableDefSize += nameByteLen + 2;
- }
-
- for(IndexBuilder idx : indexes) {
- int nameByteLen = (idx.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, indexCount,
- logicalIndexCount, format);
-
- if(indexCount > 0) {
- // index row counts
- IndexData.writeRowCountDefinitions(buffer, indexCount, format);
- }
-
- // column definitions
- Column.writeDefinitions(buffer, columns, format, charset);
-
- if(indexCount > 0) {
- // index and index data definitions
- IndexData.writeDefinitions(buffer, columns, indexes, tdefPageNumber,
- pageChannel, format);
- Index.writeDefinitions(buffer, indexes, charset);
- }
-
- //End of tabledef
- buffer.put((byte) 0xff);
- buffer.put((byte) 0xff);
-
- // write table buffer to database
- 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.
- pageChannel.writePage(buffer, tdefPageNumber);
-
- } 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(nextTdefPageNumber == 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
- 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);
- ByteUtil.forward(buffer, 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,
- int indexCount, int logicalIndexCount, JetFormat format)
- throws IOException
- {
- //Start writing the tdef
- writeTablePageHeader(buffer);
- buffer.putInt(totalTableDefSize); //Length of table def
- buffer.putInt(MAGIC_TABLE_NUMBER); // seemingly constant magic value
- buffer.putInt(0); //Number of rows
- buffer.putInt(0); //Last Autonumber
- buffer.put((byte) 1); // this makes autonumbering work in access
- 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(logicalIndexCount); //Number of logical indexes in table
- buffer.putInt(indexCount); //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
- }
-
- /**
- * Writes the given name into the given buffer in the format as expected by
- * {@link #readName}.
- */
- static void writeName(ByteBuffer buffer, String name, Charset charset)
- {
- ByteBuffer encName = Column.encodeUncompressedText(name, charset);
- buffer.putShort((short) encName.remaining());
- buffer.put(encName);
- }
-
- /**
- * 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. Index usage maps are
- * in subsequent rows.
- */
- private static int createUsageMapDefinitionBuffer(
- List<IndexBuilder> indexes, PageChannel pageChannel, JetFormat format)
- throws IOException
- {
- // 2 table usage maps plus 1 for each index
- int umapNum = 2 + indexes.size();
-
- int usageMapRowLength = format.OFFSET_USAGE_MAP_START +
- format.USAGE_MAP_TABLE_BYTE_LENGTH;
- int freeSpace = format.DATA_PAGE_INITIAL_FREE_SPACE
- - (umapNum * getRowSpaceUsage(usageMapRowLength, format));
-
- // for now, don't handle writing that many indexes
- if(freeSpace < 0) {
- throw new IOException("FIXME attempting to write too many indexes");
- }
-
- int umapPageNumber = pageChannel.allocateNewPage();
-
- 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) umapNum); //Number of records on this page
-
- // write two rows of usage map definitions for the table
- 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;
- }
-
- if(!indexes.isEmpty()) {
-
- for(int i = 0; i < indexes.size(); ++i) {
- IndexBuilder idx = indexes.get(i);
-
- // allocate root page for the index
- int rootPageNumber = pageChannel.allocateNewPage();
- int umapRowNum = i + 2;
-
- // stash info for later use
- idx.setRootPageNumber(rootPageNumber);
- idx.setUmapRowNumber((byte)umapRowNum);
- idx.setUmapPageNumber(umapPageNumber);
-
- // index map definition, including initial root page
- rtn.putShort(getRowStartOffset(umapRowNum, format), (short)rowStart);
- rtn.put(rowStart, UsageMap.MAP_TYPE_INLINE);
- rtn.putInt(rowStart + 1, rootPageNumber);
- rtn.put(rowStart + 5, (byte)1);
-
- rowStart -= usageMapRowLength;
- }
- }
-
- pageChannel.writePage(rtn, umapPageNumber);
-
- return umapPageNumber;
- }
-
- /**
- * 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);
- _lastLongAutoNumber = tableBuffer.getInt(getFormat().OFFSET_NEXT_AUTO_NUMBER);
- if(getFormat().OFFSET_NEXT_COMPLEX_AUTO_NUMBER >= 0) {
- _lastComplexTypeAutoNumber = tableBuffer.getInt(
- getFormat().OFFSET_NEXT_COMPLEX_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);
- _logicalIndexCount = 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++) {
- _indexDatas.add(IndexData.create(this, tableBuffer, i, getFormat()));
- }
-
- int colOffset = getFormat().OFFSET_INDEX_DEF_BLOCK +
- _indexCount * getFormat().SIZE_INDEX_DEFINITION;
- int dispIndex = 0;
- for (int i = 0; i < columnCount; i++) {
- Column column = new Column(this, tableBuffer,
- colOffset + (i * getFormat().SIZE_COLUMN_HEADER), dispIndex++);
- _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);
- _autoNumColumns = getAutoNumberColumns(_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);
-
- // read index column information
- for (int i = 0; i < _indexCount; i++) {
- _indexDatas.get(i).read(tableBuffer, _columns);
- }
-
- // read logical index info (may be more logical indexes than index datas)
- for (int i = 0; i < _logicalIndexCount; i++) {
- _indexes.add(new Index(tableBuffer, _indexDatas, getFormat()));
- }
-
- // read logical index names
- for (int i = 0; i < _logicalIndexCount; i++) {
- _indexes.get(i).setName(readName(tableBuffer));
- }
-
- Collections.sort(_indexes);
-
- // re-sort columns if necessary
- if(getDatabase().getColumnOrder() != ColumnOrder.DATA) {
- Collections.sort(_columns, DISPLAY_ORDER_COMPARATOR);
- }
-
- for(Column col : _columns) {
- // some columns need to do extra work after the table is completely
- // loaded
- col.postTableLoadInit();
- }
- }
-
- /**
- * 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 followed by the name
- * encoded using the {@link JetFormat#CHARSET}
- */
- private String readName(ByteBuffer buffer) {
- int nameLength = readNameLength(buffer);
- byte[] nameBytes = new byte[nameLength];
- buffer.get(nameBytes);
- return Column.decodeUncompressedText(nameBytes,
- getDatabase().getCharset());
- }
-
- /**
- * Returns a name length read from the buffer at the current position.
- */
- private int readNameLength(ByteBuffer buffer) {
- return ByteUtil.getUnsignedVarInt(buffer, getFormat().SIZE_NAME_LENGTH);
- }
-
- /**
- * Converts a map of columnName -> columnValue to an array of row values
- * appropriate for a call to {@link #addRow(Object...)}.
- * @usage _general_method_
- */
- public Object[] asRow(Map<String,Object> rowMap) {
- return asRow(rowMap, null);
- }
-
- /**
- * Converts a map of columnName -> columnValue to an array of row values
- * appropriate for a call to {@link #updateCurrentRow(Object...)}.
- * @usage _general_method_
- */
- public Object[] asUpdateRow(Map<String,Object> rowMap) {
- return asRow(rowMap, Column.KEEP_VALUE);
- }
-
- /**
- * Converts a map of columnName -> columnValue to an array of row values.
- */
- private Object[] asRow(Map<String,Object> rowMap, Object defaultValue)
- {
- Object[] row = new Object[_columns.size()];
- if(defaultValue != null) {
- Arrays.fill(row, defaultValue);
- }
- if(rowMap == null) {
- return row;
- }
- for(Column col : _columns) {
- if(rowMap.containsKey(col.getName())) {
- col.setRowValue(row, col.getRowValue(rowMap));
- }
- }
- 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.
- * @usage _general_method_
- */
- 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.
- * @usage _general_method_
- */
- 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
- {
- if(inRows.isEmpty()) {
- return;
- }
-
- // 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);
- }
-
- // fill in autonumbers
- handleAutoNumbersForAdd(row);
-
- // write the row of data to a temporary buffer
- rowData[i] = createRow(row, 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;
-
- for (int i = 0; i < rowData.length; i++) {
- int rowSize = rowData[i].remaining();
-
- // get page with space
- dataPage = findFreeRowSpace(rowSize, dataPage, pageNumber);
- pageNumber = _addRowBufferH.getPageNumber();
-
- // write out the row data
- int rowNum = addDataPageRow(dataPage, rowSize, getFormat(), 0);
- dataPage.put(rowData[i]);
-
- // update the indexes
- RowId rowId = new RowId(pageNumber, rowNum);
- for(IndexData indexData : _indexDatas) {
- indexData.addRow(rows.get(i), rowId);
- }
- }
-
- writeDataPage(dataPage, pageNumber);
-
- // Update tdef page
- updateTableDefinition(rows.size());
- }
-
- /**
- * Updates the current row to the new values.
- * <p>
- * Note, if this table has an auto-number column(s), the existing value(s)
- * will be maintained, unchanged.
- *
- * @param row new row values for the current row.
- * @usage _general_method_
- */
- public void updateCurrentRow(Object... row) throws IOException {
- getInternalCursor().updateCurrentRow(row);
- }
-
- /**
- * Update the row on which the given rowState is currently positioned.
- * <p>
- * Note, this method is not generally meant to be used directly. You should
- * use the {@link #updateCurrentRow} method or use the Cursor class, which
- * allows for more complex table interactions, e.g.
- * {@link Cursor#setCurrentRowValue} and {@link Cursor#updateCurrentRow}.
- * @usage _advanced_method_
- */
- public void updateRow(RowState rowState, RowId rowId, Object... row)
- throws IOException
- {
- requireValidRowId(rowId);
-
- // ensure that the relevant row state is up-to-date
- ByteBuffer rowBuffer = positionAtRowData(rowState, rowId);
- int oldRowSize = rowBuffer.remaining();
-
- requireNonDeletedRow(rowState, rowId);
-
- // we need to make sure the row is the right length (fill with null).
- if(row.length < _columns.size()) {
- row = dupeRow(row, _columns.size());
- }
-
- // fill in any auto-numbers (we don't allow autonumber values to be
- // modified)
- handleAutoNumbersForUpdate(row, rowBuffer, rowState);
-
- // hang on to the raw values of var length columns we are "keeping". this
- // will allow us to re-use pre-written var length data, which can save
- // space for things like long value columns.
- Map<Column,byte[]> rawVarValues =
- (!_varColumns.isEmpty() ? new HashMap<Column,byte[]>() : null);
-
- // fill in any "keep value" fields
- for(Column column : _columns) {
- if(column.getRowValue(row) == Column.KEEP_VALUE) {
- column.setRowValue(
- row, getRowColumn(getFormat(), rowBuffer, column, rowState,
- rawVarValues));
- }
- }
-
- // generate new row bytes
- ByteBuffer newRowData = createRow(
- row, _singleRowBufferH.getPageBuffer(getPageChannel()), oldRowSize,
- rawVarValues);
-
- if (newRowData.limit() > getFormat().MAX_ROW_SIZE) {
- throw new IOException("Row size " + newRowData.limit() +
- " is too large");
- }
-
- if(!_indexDatas.isEmpty()) {
- Object[] oldRowValues = rowState.getRowValues();
-
- // delete old values from indexes
- for(IndexData indexData : _indexDatas) {
- indexData.deleteRow(oldRowValues, rowId);
- }
- }
-
- // see if we can squeeze the new row data into the existing row
- rowBuffer.reset();
- int rowSize = newRowData.remaining();
-
- ByteBuffer dataPage = null;
- int pageNumber = PageChannel.INVALID_PAGE_NUMBER;
-
- if(oldRowSize >= rowSize) {
-
- // awesome, slap it in!
- rowBuffer.put(newRowData);
-
- // grab the page we just updated
- dataPage = rowState.getFinalPage();
- pageNumber = rowState.getFinalRowId().getPageNumber();
-
- } else {
-
- // bummer, need to find a new page for the data
- dataPage = findFreeRowSpace(rowSize, null,
- PageChannel.INVALID_PAGE_NUMBER);
- pageNumber = _addRowBufferH.getPageNumber();
-
- RowId headerRowId = rowState.getHeaderRowId();
- ByteBuffer headerPage = rowState.getHeaderPage();
- if(pageNumber == headerRowId.getPageNumber()) {
- // new row is on the same page as header row, share page
- dataPage = headerPage;
- }
-
- // write out the new row data (set the deleted flag on the new data row
- // so that it is ignored during normal table traversal)
- int rowNum = addDataPageRow(dataPage, rowSize, getFormat(),
- DELETED_ROW_MASK);
- dataPage.put(newRowData);
-
- // write the overflow info into the header row and clear out the
- // remaining header data
- rowBuffer = PageChannel.narrowBuffer(
- headerPage,
- findRowStart(headerPage, headerRowId.getRowNumber(), getFormat()),
- findRowEnd(headerPage, headerRowId.getRowNumber(), getFormat()));
- rowBuffer.put((byte)rowNum);
- ByteUtil.put3ByteInt(rowBuffer, pageNumber);
- ByteUtil.clearRemaining(rowBuffer);
-
- // set the overflow flag on the header row
- int headerRowIndex = getRowStartOffset(headerRowId.getRowNumber(),
- getFormat());
- headerPage.putShort(headerRowIndex,
- (short)(headerPage.getShort(headerRowIndex)
- | OVERFLOW_ROW_MASK));
- if(pageNumber != headerRowId.getPageNumber()) {
- writeDataPage(headerPage, headerRowId.getPageNumber());
- }
- }
-
- // update the indexes
- for(IndexData indexData : _indexDatas) {
- indexData.addRow(row, rowId);
- }
-
- writeDataPage(dataPage, pageNumber);
-
- updateTableDefinition(0);
- }
-
- private ByteBuffer findFreeRowSpace(int rowSize, ByteBuffer dataPage,
- int pageNumber)
- throws IOException
- {
- if(dataPage == null) {
-
- // 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.
- return newDataPage();
- }
-
- }
-
- if(!rowFitsOnDataPage(rowSize, dataPage, getFormat())) {
-
- // Last data page is full. Create a new one.
- writeDataPage(dataPage, pageNumber);
- _freeSpacePages.removePageNumber(pageNumber);
-
- dataPage = newDataPage();
- }
-
- return dataPage;
- }
-
- /**
- * 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, _lastLongAutoNumber);
- int ctypeOff = getFormat().OFFSET_NEXT_COMPLEX_AUTO_NUMBER;
- if(ctypeOff >= 0) {
- tdefPage.putInt(ctypeOff, _lastComplexTypeAutoNumber);
- }
-
- // write any index changes
- for (IndexData indexData : _indexDatas) {
- // write the unique entry count for the index to the table definition
- // page
- tdefPage.putInt(indexData.getUniqueEntryCountOffset(),
- indexData.getUniqueEntryCount());
- // write the entry page for the index
- indexData.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().DATA_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;
- }
-
- ByteBuffer createRow(Object[] rowArray, ByteBuffer buffer)
- throws IOException
- {
- return createRow(rowArray, buffer, 0, Collections.<Column,byte[]>emptyMap());
- }
-
- /**
- * Serialize a row of Objects into a byte buffer.
- *
- * @param rowArray row data, expected to be correct length for this table
- * @param buffer buffer to which to write the row data
- * @param minRowSize min size for result row
- * @param rawVarValues optional, pre-written values for var length columns
- * (enables re-use of previously written values).
- * @return the given buffer, filled with the row data
- */
- private ByteBuffer createRow(Object[] rowArray, ByteBuffer buffer,
- int minRowSize, Map<Column,byte[]> rawVarValues)
- 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()) {
- continue;
- }
-
- Object rowValue = col.getRowValue(rowArray);
-
- if (col.getType() == DataType.BOOLEAN) {
-
- if(Column.toBooleanValue(rowValue)) {
- //Booleans are stored in the null mask
- nullMask.markNotNull(col);
- }
- rowValue = null;
- }
-
- if(rowValue != null) {
-
- // we have a value to write
- nullMask.markNotNull(col);
-
- // remainingRowLength is ignored when writing fixed length data
- buffer.position(fixedDataStart + col.getFixedDataOffset());
- buffer.put(col.write(rowValue, 0));
- }
-
- // always insert space for the entire fixed data column length
- // (including null values), access expects the row to always be at least
- // big enough to hold all fixed values
- buffer.position(fixedDataStart + col.getFixedDataOffset() +
- col.getLength());
-
- // 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) {
-
- int maxRowSize = getFormat().MAX_ROW_SIZE;
-
- // figure out how much space remains for var length data. first,
- // account for already written space
- maxRowSize -= buffer.position();
- // now, account for trailer space
- int trailerSize = (nullMask.byteSize() + 4 + (_maxVarColumnCount * 2));
- maxRowSize -= trailerSize;
-
- // 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()) &&
- (varCol.getRowValue(rowArray) != 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 = varCol.getRowValue(rowArray);
- if (rowValue != null) {
- // we have a value
- nullMask.markNotNull(varCol);
-
- byte[] rawValue = null;
- ByteBuffer varDataBuf = null;
- if(((rawValue = rawVarValues.get(varCol)) != null) &&
- (rawValue.length <= maxRowSize)) {
- // save time and potentially db space, re-use raw value
- varDataBuf = ByteBuffer.wrap(rawValue);
- } else {
- // write column value
- 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();
- }
-
- // record where we stopped writing
- int eod = buffer.position();
-
- // insert padding if necessary
- padRowBuffer(buffer, minRowSize, trailerSize);
-
- buffer.putShort((short) eod); //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
-
- } else {
-
- // insert padding for row w/ no var cols
- padRowBuffer(buffer, minRowSize, nullMask.byteSize());
- }
-
- nullMask.write(buffer); //Null mask
- buffer.flip();
- if (LOG.isDebugEnabled()) {
- LOG.debug("Creating new data block:\n" + ByteUtil.toHexString(buffer, buffer.limit()));
- }
- return buffer;
- }
-
- /**
- * Autonumber columns may not be modified on update.
- */
- private void handleAutoNumbersForUpdate(
- Object[] row, ByteBuffer rowBuffer, RowState rowState)
- throws IOException
- {
- if(_autoNumColumns.isEmpty()) {
- return;
- }
-
- for(Column col : _autoNumColumns) {
- col.setRowValue(row, getRowColumn(getFormat(), rowBuffer, col, rowState,
- null));
- }
- }
-
- /**
- * Fill in all autonumber column values.
- */
- private void handleAutoNumbersForAdd(Object[] row)
- throws IOException
- {
- if(_autoNumColumns.isEmpty()) {
- return;
- }
-
- Object complexAutoNumber = null;
- for(Column col : _autoNumColumns) {
- // ignore given row value, use next autonumber
- Column.AutoNumberGenerator autoNumGen = col.getAutoNumberGenerator();
- Object rowValue = null;
- if(autoNumGen.getType() != DataType.COMPLEX_TYPE) {
- rowValue = autoNumGen.getNext(null);
- } else {
- // complex type auto numbers are shared across all complex columns
- // in the row
- complexAutoNumber = autoNumGen.getNext(complexAutoNumber);
- rowValue = complexAutoNumber;
- }
- col.setRowValue(row, rowValue);
- }
- }
-
- private static void padRowBuffer(ByteBuffer buffer, int minRowSize,
- int trailerSize)
- {
- int pos = buffer.position();
- if((pos + trailerSize) < minRowSize) {
- // pad the row to get to the min byte size
- int padSize = minRowSize - (pos + trailerSize);
- ByteUtil.clearRange(buffer, pos, pos + padSize);
- ByteUtil.forward(buffer, padSize);
- }
- }
-
- /**
- * @usage _general_method_
- */
- public int getRowCount() {
- return _rowCount;
- }
-
- int getNextLongAutoNumber() {
- // note, the saved value is the last one handed out, so pre-increment
- return ++_lastLongAutoNumber;
- }
-
- int getLastLongAutoNumber() {
- // gets the last used auto number (does not modify)
- return _lastLongAutoNumber;
- }
-
- int getNextComplexTypeAutoNumber() {
- // note, the saved value is the last one handed out, so pre-increment
- return ++_lastComplexTypeAutoNumber;
- }
-
- int getLastComplexTypeAutoNumber() {
- // gets the last used auto number (does not modify)
- return _lastComplexTypeAutoNumber;
- }
-
- @Override
- public String toString() {
- StringBuilder rtn = new StringBuilder();
- rtn.append("Type: " + _tableType +
- ((_tableType == TYPE_USER) ? " (USER)" : " (SYSTEM)"));
- rtn.append("\nName: " + _name);
- rtn.append("\nRow count: " + _rowCount);
- rtn.append("\nColumn count: " + _columns.size());
- rtn.append("\nIndex (data) count: " + _indexCount);
- rtn.append("\nLogical Index count: " + _logicalIndexCount);
- 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
- * @usage _general_method_
- */
- 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
- * @usage _general_method_
- */
- 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
- * @usage _advanced_method_
- */
- public static int addDataPageRow(ByteBuffer dataPage,
- int rowSize,
- JetFormat format,
- int rowFlags)
- {
- 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),
- (short)(rowLocation | rowFlags));
-
- // 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);
- }
- }
-
- /**
- * @usage _advanced_method_
- */
- public static boolean isDeletedRow(short rowStart) {
- return ((rowStart & DELETED_ROW_MASK) != 0);
- }
-
- /**
- * @usage _advanced_method_
- */
- public static boolean isOverflowRow(short rowStart) {
- return ((rowStart & OVERFLOW_ROW_MASK) != 0);
- }
-
- /**
- * @usage _advanced_method_
- */
- public static short cleanRowStart(short rowStart) {
- return (short)(rowStart & OFFSET_MASK);
- }
-
- /**
- * @usage _advanced_method_
- */
- public static short findRowStart(ByteBuffer buffer, int rowNum,
- JetFormat format)
- {
- return cleanRowStart(
- buffer.getShort(getRowStartOffset(rowNum, format)));
- }
-
- /**
- * @usage _advanced_method_
- */
- public static int getRowStartOffset(int rowNum, JetFormat format)
- {
- return format.OFFSET_ROW_START + (format.SIZE_ROW_LOCATION * rowNum);
- }
-
- /**
- * @usage _advanced_method_
- */
- public static short findRowEnd(ByteBuffer buffer, int rowNum,
- JetFormat format)
- {
- return (short)((rowNum == 0) ?
- format.PAGE_SIZE :
- cleanRowStart(
- buffer.getShort(getRowEndOffset(rowNum, format))));
- }
-
- /**
- * @usage _advanced_method_
- */
- public static int getRowEndOffset(int rowNum, JetFormat format)
- {
- return format.OFFSET_ROW_START + (format.SIZE_ROW_LOCATION * (rowNum - 1));
- }
-
- /**
- * @usage _advanced_method_
- */
- public static int getRowSpaceUsage(int rowSize, JetFormat format)
- {
- return rowSize + format.SIZE_ROW_LOCATION;
- }
-
- /**
- * @return the "AutoNumber" columns in the given collection of columns.
- * @usage _advanced_method_
- */
- public static List<Column> getAutoNumberColumns(Collection<Column> columns) {
- List<Column> autoCols = new ArrayList<Column>(1);
- for(Column c : columns) {
- if(c.isAutoNumber()) {
- autoCols.add(c);
- }
- }
- return autoCols;
- }
-
- /**
- * Returns {@code true} if a row of the given size will fit on the given
- * data page, {@code false} otherwise.
- * @usage _advanced_method_
- */
- 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.
- * @usage _advanced_class_
- */
- 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;
- /** null mask for the last row */
- private NullMask _nullMask;
- /** 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;
- /** cached variable column offsets for jump-table based rows */
- private short[] _varColOffsets;
-
- 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;
- _varColOffsets = null;
- _nullMask = null;
- 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 Object setRowValue(int idx, Object value) {
- _haveRowValues = true;
- _rowValues[idx] = value;
- return value;
- }
-
- public Object[] getRowValues() {
- return dupeRow(_rowValues, _rowValues.length);
- }
-
- public NullMask getNullMask(ByteBuffer rowBuffer) throws IOException {
- if(_nullMask == null) {
- _nullMask = getRowNullMask(rowBuffer);
- }
- return _nullMask;
- }
-
- private short[] getVarColOffsets() {
- return _varColOffsets;
- }
-
- private void setVarColOffsets(short[] varColOffsets) {
- _varColOffsets = varColOffsets;
- }
-
- 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;
- }
- }
-
- }
|