You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Table.java 28KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810
  1. /*
  2. Copyright (c) 2005 Health Market Science, Inc.
  3. This library is free software; you can redistribute it and/or
  4. modify it under the terms of the GNU Lesser General Public
  5. License as published by the Free Software Foundation; either
  6. version 2.1 of the License, or (at your option) any later version.
  7. This library is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  10. Lesser General Public License for more details.
  11. You should have received a copy of the GNU Lesser General Public
  12. License along with this library; if not, write to the Free Software
  13. Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  14. USA
  15. You can contact Health Market Science at info@healthmarketscience.com
  16. or at the following address:
  17. Health Market Science
  18. 2700 Horizon Drive
  19. Suite 200
  20. King of Prussia, PA 19406
  21. */
  22. package com.healthmarketscience.jackcess;
  23. import java.io.IOException;
  24. import java.nio.ByteBuffer;
  25. import java.nio.ByteOrder;
  26. import java.util.ArrayList;
  27. import java.util.Arrays;
  28. import java.util.Collection;
  29. import java.util.Collections;
  30. import java.util.Iterator;
  31. import java.util.LinkedHashMap;
  32. import java.util.List;
  33. import java.util.Map;
  34. import java.util.NoSuchElementException;
  35. import org.apache.commons.logging.Log;
  36. import org.apache.commons.logging.LogFactory;
  37. /**
  38. * A single database table
  39. * @author Tim McCune
  40. */
  41. public class Table
  42. implements Iterable<Map<String, Object>>
  43. {
  44. private static final Log LOG = LogFactory.getLog(Table.class);
  45. private static final short OFFSET_MASK = (short)0x1FFF;
  46. /** Table type code for system tables */
  47. public static final byte TYPE_SYSTEM = 0x53;
  48. /** Table type code for user tables */
  49. public static final byte TYPE_USER = 0x4e;
  50. /** Buffer used for reading the table */
  51. private ByteBuffer _buffer;
  52. /** Type of the table (either TYPE_SYSTEM or TYPE_USER) */
  53. private byte _tableType;
  54. /** Number of the current row in a data page */
  55. private int _currentRowInPage;
  56. /** Number of indexes on the table */
  57. private int _indexCount;
  58. /** Number of index slots for the table */
  59. private int _indexSlotCount;
  60. /** Offset index in the buffer where the last row read started */
  61. private short _lastRowStart;
  62. /** Number of rows in the table */
  63. private int _rowCount;
  64. private int _tableDefPageNumber;
  65. /** Number of rows left to be read on the current page */
  66. private short _rowsLeftOnPage = 0;
  67. /** Offset index in the buffer of the start of the current row */
  68. private short _rowStart;
  69. /** max Number of columns in the table (includes previous deletions) */
  70. private short _maxColumnCount;
  71. /** max Number of variable columns in the table */
  72. private short _maxVarColumnCount;
  73. /** Number of variable columns in the table */
  74. private short _varColumnCount;
  75. /** Number of fixed columns in the table */
  76. private short _fixedColumnCount;
  77. /** Number of columns in the table */
  78. private short _columnCount;
  79. /** Format of the database that contains this table */
  80. private JetFormat _format;
  81. /** List of columns in this table */
  82. private List<Column> _columns = new ArrayList<Column>();
  83. /** List of indexes on this table */
  84. private List<Index> _indexes = new ArrayList<Index>();
  85. /** Used to read in pages */
  86. private PageChannel _pageChannel;
  87. /** Table name as stored in Database */
  88. private String _name;
  89. /** Usage map of pages that this table owns */
  90. private UsageMap _ownedPages;
  91. /** Usage map of pages that this table owns with free space on them */
  92. private UsageMap _freeSpacePages;
  93. /**
  94. * Only used by unit tests
  95. */
  96. Table() throws IOException {
  97. _pageChannel = new PageChannel(null, JetFormat.VERSION_4);
  98. }
  99. /**
  100. * @param buffer Buffer to read the table with
  101. * @param pageChannel Page channel to get database pages from
  102. * @param format Format of the database that contains this table
  103. * @param pageNumber Page number of the table definition
  104. * @param name Table name
  105. */
  106. protected Table(ByteBuffer buffer, PageChannel pageChannel, JetFormat format, int pageNumber, String name)
  107. throws IOException
  108. {
  109. _buffer = buffer;
  110. _pageChannel = pageChannel;
  111. _format = format;
  112. _tableDefPageNumber = pageNumber;
  113. _name = name;
  114. int nextPage;
  115. ByteBuffer nextPageBuffer = null;
  116. nextPage = _buffer.getInt(_format.OFFSET_NEXT_TABLE_DEF_PAGE);
  117. while (nextPage != 0) {
  118. if (nextPageBuffer == null) {
  119. nextPageBuffer = ByteBuffer.allocate(format.PAGE_SIZE);
  120. nextPageBuffer.order(ByteOrder.LITTLE_ENDIAN);
  121. }
  122. _pageChannel.readPage(nextPageBuffer, nextPage);
  123. nextPage = nextPageBuffer.getInt(_format.OFFSET_NEXT_TABLE_DEF_PAGE);
  124. ByteBuffer newBuffer = ByteBuffer.allocate(_buffer.capacity() + format.PAGE_SIZE - 8);
  125. newBuffer.order(ByteOrder.LITTLE_ENDIAN);
  126. newBuffer.put(_buffer);
  127. newBuffer.put(nextPageBuffer.array(), 8, format.PAGE_SIZE - 8);
  128. _buffer = newBuffer;
  129. _buffer.flip();
  130. }
  131. readPage();
  132. }
  133. /**
  134. * @return The name of the table
  135. */
  136. public String getName() {
  137. return _name;
  138. }
  139. /**
  140. * @return All of the columns in this table (unmodifiable List)
  141. */
  142. public List<Column> getColumns() {
  143. return Collections.unmodifiableList(_columns);
  144. }
  145. /**
  146. * Only called by unit tests
  147. */
  148. void setColumns(List<Column> columns) {
  149. _columns = columns;
  150. }
  151. /**
  152. * @return All of the Indexes on this table (unmodifiable List)
  153. */
  154. public List<Index> getIndexes() {
  155. return Collections.unmodifiableList(_indexes);
  156. }
  157. /**
  158. * Only called by unit tests
  159. */
  160. int getIndexSlotCount() {
  161. return _indexSlotCount;
  162. }
  163. /**
  164. * After calling this method, getNextRow will return the first row in the table
  165. */
  166. public void reset() {
  167. _rowsLeftOnPage = 0;
  168. _ownedPages.reset();
  169. _currentRowInPage = 0;
  170. }
  171. /**
  172. * @return The next row in this table (Column name -> Column value)
  173. */
  174. public Map<String, Object> getNextRow() throws IOException {
  175. return getNextRow(null);
  176. }
  177. /**
  178. * Delete the current row (retrieved by a call to {@link #getNextRow}).
  179. */
  180. public void deleteCurrentRow() throws IOException {
  181. if (_currentRowInPage == 0) {
  182. throw new IllegalStateException("Must call getNextRow first");
  183. }
  184. int index = _format.OFFSET_DATA_ROW_LOCATION_BLOCK + (_currentRowInPage - 1) *
  185. _format.SIZE_ROW_LOCATION + 1;
  186. _buffer.put(index, (byte) (_buffer.get(index) | 0xc0));
  187. _pageChannel.writePage(_buffer, _ownedPages.getCurrentPageNumber());
  188. }
  189. /**
  190. * @param columnNames Only column names in this collection will be returned
  191. * @return The next row in this table (Column name -> Column value)
  192. */
  193. public Map<String, Object> getNextRow(Collection<String> columnNames)
  194. throws IOException
  195. {
  196. if (!positionAtNextRow()) {
  197. return null;
  198. }
  199. if (LOG.isDebugEnabled()) {
  200. LOG.debug("Data block at position " + Integer.toHexString(_buffer.position()) +
  201. ":\n" + ByteUtil.toHexString(_buffer, _buffer.position(),
  202. _buffer.limit() - _buffer.position()));
  203. }
  204. short columnCount = _buffer.getShort(); //Number of columns in this row
  205. Map<String, Object> rtn = new LinkedHashMap<String, Object>(columnCount);
  206. NullMask nullMask = new NullMask(columnCount);
  207. _buffer.position(_buffer.limit() - nullMask.byteSize()); //Null mask at end
  208. nullMask.read(_buffer);
  209. short rowVarColumnCount = 0;
  210. short[] varColumnOffsets = null;
  211. short lastVarColumnStart = 0;
  212. // if _maxVarColumnCount is 0, then row info does not include varcol info
  213. if(_maxVarColumnCount > 0) {
  214. _buffer.position(_buffer.limit() - nullMask.byteSize() - 2);
  215. rowVarColumnCount = _buffer.getShort(); // number of variable length columns in this row
  216. //Read in the offsets of each of the variable length columns
  217. varColumnOffsets = new short[rowVarColumnCount];
  218. _buffer.position(_buffer.position() - 2 - (rowVarColumnCount * 2) - 2);
  219. lastVarColumnStart = _buffer.getShort();
  220. for (short i = 0; i < rowVarColumnCount; i++) {
  221. varColumnOffsets[i] = _buffer.getShort();
  222. }
  223. }
  224. // compute start of fixed data
  225. int dataStart = _rowStart + 2;
  226. //Now read in the fixed length columns and populate the columnData array
  227. //with the combination of fixed length and variable length data.
  228. byte[] columnData = null;
  229. int columnNumber = 0;
  230. for (Iterator iter = _columns.iterator(); iter.hasNext(); columnNumber++) {
  231. Column column = (Column) iter.next();
  232. boolean isNull = nullMask.isNull(columnNumber);
  233. Object value = null;
  234. if((columnNames == null) || (columnNames.contains(column.getName()))) {
  235. if (column.getType() == DataType.BOOLEAN) {
  236. value = new Boolean(!isNull); //Boolean values are stored in the null mask
  237. } else {
  238. if(!isNull) {
  239. // locate the column data bytes
  240. int colDataPos = 0;
  241. int colDataLen = 0;
  242. if (!column.isVariableLength())
  243. {
  244. // find fixed length column data
  245. colDataPos = dataStart + column.getFixedDataOffset();
  246. colDataLen = column.getLength();
  247. }
  248. else
  249. {
  250. // find var length column data
  251. int varDataIdx = (rowVarColumnCount -
  252. column.getVarLenTableIndex() - 1);
  253. int varDataStart = varColumnOffsets[varDataIdx];
  254. int varDataEnd = ((varDataIdx > 0) ?
  255. varColumnOffsets[varDataIdx - 1] :
  256. lastVarColumnStart);
  257. colDataPos = _rowStart + varDataStart;
  258. colDataLen = varDataEnd - varDataStart;
  259. }
  260. // parse the column data
  261. columnData = new byte[colDataLen];
  262. _buffer.position(colDataPos);
  263. _buffer.get(columnData);
  264. value = column.read(columnData);
  265. }
  266. }
  267. //Add the value to the row data
  268. rtn.put(column.getName(), value);
  269. }
  270. }
  271. return rtn;
  272. }
  273. /**
  274. * Position the buffer at the next row in the table
  275. * @return True if another row was found, false if there are no more rows
  276. */
  277. private boolean positionAtNextRow() throws IOException {
  278. if (_rowsLeftOnPage == 0) {
  279. do {
  280. if (!_ownedPages.getNextPage(_buffer)) {
  281. //No more owned pages. No more rows.
  282. return false;
  283. }
  284. } while (_buffer.get() != PageTypes.DATA); //Only interested in data pages
  285. _rowsLeftOnPage = _buffer.getShort(_format.OFFSET_NUM_ROWS_ON_DATA_PAGE);
  286. _currentRowInPage = 0;
  287. _lastRowStart = (short) _format.PAGE_SIZE;
  288. }
  289. _rowStart = _buffer.getShort(_format.OFFSET_DATA_ROW_LOCATION_BLOCK +
  290. _currentRowInPage * _format.SIZE_ROW_LOCATION);
  291. _currentRowInPage++;
  292. _rowsLeftOnPage--;
  293. // FIXME, mdbtools seems to be confused as to which flag is which, this
  294. // code follows the actual code, which disagrees with the HACKING doc
  295. boolean deletedRow = ((_rowStart & 0x4000) != 0);
  296. boolean overflowRow = ((_rowStart & 0x8000) != 0);
  297. if(deletedRow ^ overflowRow) {
  298. if(LOG.isDebugEnabled()) {
  299. LOG.debug("Row flags: deletedRow " + deletedRow + ", overflowRow " +
  300. overflowRow);
  301. }
  302. }
  303. _rowStart = (short)(_rowStart & OFFSET_MASK);
  304. if (deletedRow) {
  305. // Deleted row. Skip.
  306. if(LOG.isDebugEnabled()) {
  307. LOG.debug("Skipping deleted row");
  308. }
  309. _lastRowStart = _rowStart;
  310. return positionAtNextRow();
  311. } else if (overflowRow) {
  312. // Overflow page.
  313. // FIXME - Currently skipping this. Need to figure out how to read it.
  314. LOG.warn("Skipping row with overflow flag");
  315. // _buffer.position(_rowStart);
  316. // int overflow = _buffer.getInt();
  317. _lastRowStart = _rowStart;
  318. return positionAtNextRow();
  319. } else {
  320. _buffer.position(_rowStart);
  321. _buffer.limit(_lastRowStart);
  322. _lastRowStart = _rowStart;
  323. return true;
  324. }
  325. }
  326. /**
  327. * Calls <code>reset</code> on this table and returns a modifiable Iterator
  328. * which will iterate through all the rows of this table. Use of the
  329. * Iterator follows the same restrictions as a call to
  330. * <code>getNextRow</code>.
  331. * @throws IllegalStateException if an IOException is thrown by one of the
  332. * operations, the actual exception will be contained within
  333. */
  334. public Iterator<Map<String, Object>> iterator()
  335. {
  336. return iterator(null);
  337. }
  338. /**
  339. * Calls <code>reset</code> on this table and returns a modifiable Iterator
  340. * which will iterate through all the rows of this table, returning only the
  341. * given columns. Use of the Iterator follows the same restrictions as a
  342. * call to <code>getNextRow</code>.
  343. * @throws IllegalStateException if an IOException is thrown by one of the
  344. * operations, the actual exception will be contained within
  345. */
  346. public Iterator<Map<String, Object>> iterator(Collection<String> columnNames)
  347. {
  348. return new RowIterator(columnNames);
  349. }
  350. /**
  351. * Read the table definition
  352. */
  353. private void readPage() throws IOException {
  354. if (LOG.isDebugEnabled()) {
  355. _buffer.rewind();
  356. LOG.debug("Table def block:\n" + ByteUtil.toHexString(_buffer,
  357. _format.SIZE_TDEF_BLOCK));
  358. }
  359. _rowCount = _buffer.getInt(_format.OFFSET_NUM_ROWS);
  360. _tableType = _buffer.get(_format.OFFSET_TABLE_TYPE);
  361. _maxColumnCount = _buffer.getShort(_format.OFFSET_MAX_COLS);
  362. _maxVarColumnCount = _buffer.getShort(_format.OFFSET_NUM_VAR_COLS);
  363. _columnCount = _buffer.getShort(_format.OFFSET_NUM_COLS);
  364. _indexSlotCount = _buffer.getInt(_format.OFFSET_NUM_INDEX_SLOTS);
  365. _indexCount = _buffer.getInt(_format.OFFSET_NUM_INDEXES);
  366. byte rowNum = _buffer.get(_format.OFFSET_OWNED_PAGES);
  367. int pageNum = ByteUtil.get3ByteInt(_buffer, _format.OFFSET_OWNED_PAGES + 1);
  368. _ownedPages = UsageMap.read(_pageChannel, pageNum, rowNum, _format);
  369. rowNum = _buffer.get(_format.OFFSET_FREE_SPACE_PAGES);
  370. pageNum = ByteUtil.get3ByteInt(_buffer, _format.OFFSET_FREE_SPACE_PAGES + 1);
  371. _freeSpacePages = UsageMap.read(_pageChannel, pageNum, rowNum, _format);
  372. for (int i = 0; i < _indexCount; i++) {
  373. Index index = new Index(_tableDefPageNumber, _pageChannel, _format);
  374. _indexes.add(index);
  375. index.setRowCount(_buffer.getInt(_format.OFFSET_INDEX_DEF_BLOCK +
  376. i * _format.SIZE_INDEX_DEFINITION + 4));
  377. }
  378. int offset = _format.OFFSET_INDEX_DEF_BLOCK +
  379. _indexCount * _format.SIZE_INDEX_DEFINITION;
  380. Column column;
  381. for (int i = 0; i < _columnCount; i++) {
  382. column = new Column(_buffer,
  383. offset + i * _format.SIZE_COLUMN_HEADER, _pageChannel, _format);
  384. if(column.isVariableLength()) {
  385. _varColumnCount++;
  386. } else {
  387. _fixedColumnCount++;
  388. }
  389. _columns.add(column);
  390. }
  391. offset += _columnCount * _format.SIZE_COLUMN_HEADER;
  392. for (int i = 0; i < _columnCount; i++) {
  393. column = (Column) _columns.get(i);
  394. short nameLength = _buffer.getShort(offset);
  395. offset += 2;
  396. byte[] nameBytes = new byte[nameLength];
  397. _buffer.position(offset);
  398. _buffer.get(nameBytes, 0, (int) nameLength);
  399. column.setName(_format.CHARSET.decode(ByteBuffer.wrap(nameBytes)).toString());
  400. offset += nameLength;
  401. }
  402. Collections.sort(_columns);
  403. int idxOffset = _buffer.position();
  404. _buffer.position(idxOffset +
  405. (_format.OFFSET_INDEX_NUMBER_BLOCK * _indexCount));
  406. // there are _indexSlotCount blocks here, we ignore any slot with an index
  407. // number greater than the number of actual indexes
  408. int curIndex = 0;
  409. for (int i = 0; i < _indexSlotCount; i++) {
  410. _buffer.getInt(); //Forward past Unknown
  411. int indexNumber = _buffer.getInt();
  412. _buffer.position(_buffer.position() + 15);
  413. byte indexType = _buffer.get();
  414. _buffer.position(_buffer.position() + 4);
  415. if(indexNumber < _indexCount) {
  416. Index index = _indexes.get(curIndex++);
  417. index.setIndexNumber(indexNumber);
  418. index.setPrimaryKey(indexType == 1);
  419. }
  420. }
  421. // for each empty index slot, there is some weird sort of name
  422. for(int i = 0; i < (_indexSlotCount - _indexCount); ++i) {
  423. int skipBytes = _buffer.getShort();
  424. _buffer.position(_buffer.position() + skipBytes);
  425. }
  426. // read actual index names
  427. // FIXME, we still are not always getting the names matched correctly with
  428. // the index info, some weird indexing we are not figuring out yet
  429. for (int i = 0; i < _indexCount; i++) {
  430. byte[] nameBytes = new byte[_buffer.getShort()];
  431. _buffer.get(nameBytes);
  432. ((Index) _indexes.get(i)).setName(_format.CHARSET.decode(ByteBuffer.wrap(
  433. nameBytes)).toString());
  434. }
  435. int idxEndOffset = _buffer.position();
  436. Collections.sort(_indexes);
  437. // go back to index column info after sorting
  438. _buffer.position(idxOffset);
  439. for (int i = 0; i < _indexCount; i++) {
  440. _buffer.getInt(); //Forward past Unknown
  441. ((Index) _indexes.get(i)).read(_buffer, _columns);
  442. }
  443. // reset to end of index info
  444. _buffer.position(idxEndOffset);
  445. }
  446. /**
  447. * Add a single row to this table and write it to disk
  448. */
  449. public void addRow(Object... row) throws IOException {
  450. addRows(Collections.singletonList(row));
  451. }
  452. /**
  453. * Add multiple rows to this table, only writing to disk after all
  454. * rows have been written, and every time a data page is filled. This
  455. * is much more efficient than calling <code>addRow</code> multiple times.
  456. * @param rows List of Object[] row values
  457. */
  458. public void addRows(List<? extends Object[]> rows) throws IOException {
  459. ByteBuffer dataPage = _pageChannel.createPageBuffer();
  460. ByteBuffer[] rowData = new ByteBuffer[rows.size()];
  461. Iterator<? extends Object[]> iter = rows.iterator();
  462. for (int i = 0; iter.hasNext(); i++) {
  463. rowData[i] = createRow((Object[]) iter.next());
  464. }
  465. List<Integer> pageNumbers = _ownedPages.getPageNumbers();
  466. int pageNumber;
  467. int rowSize;
  468. if (pageNumbers.size() == 0) {
  469. //No data pages exist. Create a new one.
  470. pageNumber = newDataPage(dataPage, rowData[0]);
  471. } else {
  472. //Get the last data page.
  473. //Not bothering to check other pages for free space.
  474. pageNumber = ((Integer) pageNumbers.get(pageNumbers.size() - 1)).intValue();
  475. _pageChannel.readPage(dataPage, pageNumber);
  476. }
  477. for (int i = 0; i < rowData.length; i++) {
  478. rowSize = rowData[i].limit();
  479. short freeSpaceInPage = dataPage.getShort(_format.OFFSET_FREE_SPACE);
  480. if (freeSpaceInPage < (rowSize + _format.SIZE_ROW_LOCATION)) {
  481. //Last data page is full. Create a new one.
  482. if (rowSize + _format.SIZE_ROW_LOCATION > _format.MAX_ROW_SIZE) {
  483. throw new IOException("Row size " + rowSize + " is too large");
  484. }
  485. _pageChannel.writePage(dataPage, pageNumber);
  486. dataPage.clear();
  487. pageNumber = newDataPage(dataPage, rowData[i]);
  488. _freeSpacePages.removePageNumber(pageNumber);
  489. freeSpaceInPage = dataPage.getShort(_format.OFFSET_FREE_SPACE);
  490. }
  491. //Decrease free space record.
  492. dataPage.putShort(_format.OFFSET_FREE_SPACE, (short) (freeSpaceInPage -
  493. rowSize - _format.SIZE_ROW_LOCATION));
  494. //Increment row count record.
  495. short rowCount = dataPage.getShort(_format.OFFSET_NUM_ROWS_ON_DATA_PAGE);
  496. dataPage.putShort(_format.OFFSET_NUM_ROWS_ON_DATA_PAGE, (short) (rowCount + 1));
  497. short rowLocation = (short) _format.PAGE_SIZE;
  498. if (rowCount > 0) {
  499. rowLocation = dataPage.getShort(_format.OFFSET_DATA_ROW_LOCATION_BLOCK +
  500. (rowCount - 1) * _format.SIZE_ROW_LOCATION);
  501. if (rowLocation < 0) {
  502. // Deleted row
  503. rowLocation &= ~0xc000;
  504. }
  505. }
  506. rowLocation -= rowSize;
  507. dataPage.putShort(_format.OFFSET_DATA_ROW_LOCATION_BLOCK +
  508. rowCount * _format.SIZE_ROW_LOCATION, rowLocation);
  509. dataPage.position(rowLocation);
  510. dataPage.put(rowData[i]);
  511. Iterator<Index> indIter = _indexes.iterator();
  512. while (indIter.hasNext()) {
  513. Index index = (Index) indIter.next();
  514. index.addRow((Object[]) rows.get(i), pageNumber, (byte) rowCount);
  515. }
  516. }
  517. _pageChannel.writePage(dataPage, pageNumber);
  518. //Update tdef page
  519. ByteBuffer tdefPage = _pageChannel.createPageBuffer();
  520. _pageChannel.readPage(tdefPage, _tableDefPageNumber);
  521. tdefPage.putInt(_format.OFFSET_NUM_ROWS, ++_rowCount);
  522. Iterator<Index> indIter = _indexes.iterator();
  523. for (int i = 0; i < _indexes.size(); i++) {
  524. tdefPage.putInt(_format.OFFSET_INDEX_DEF_BLOCK +
  525. i * _format.SIZE_INDEX_DEFINITION + 4, _rowCount);
  526. Index index = (Index) indIter.next();
  527. index.update();
  528. }
  529. _pageChannel.writePage(tdefPage, _tableDefPageNumber);
  530. }
  531. /**
  532. * Create a new data page
  533. * @return Page number of the new page
  534. */
  535. private int newDataPage(ByteBuffer dataPage, ByteBuffer rowData) throws IOException {
  536. if (LOG.isDebugEnabled()) {
  537. LOG.debug("Creating new data page");
  538. }
  539. dataPage.put(PageTypes.DATA); //Page type
  540. dataPage.put((byte) 1); //Unknown
  541. dataPage.putShort((short) (_format.PAGE_SIZE - _format.OFFSET_DATA_ROW_LOCATION_BLOCK -
  542. (rowData.limit() - 1) - _format.SIZE_ROW_LOCATION)); //Free space in this page
  543. dataPage.putInt(_tableDefPageNumber); //Page pointer to table definition
  544. dataPage.putInt(0); //Unknown
  545. dataPage.putInt(0); //Number of records on this page
  546. int pageNumber = _pageChannel.writeNewPage(dataPage);
  547. _ownedPages.addPageNumber(pageNumber);
  548. _freeSpacePages.addPageNumber(pageNumber);
  549. return pageNumber;
  550. }
  551. /**
  552. * Serialize a row of Objects into a byte buffer
  553. */
  554. ByteBuffer createRow(Object[] rowArray) throws IOException {
  555. ByteBuffer buffer = _pageChannel.createPageBuffer();
  556. buffer.putShort((short) _columns.size());
  557. NullMask nullMask = new NullMask(_columns.size());
  558. Iterator iter;
  559. int index = 0;
  560. Column col;
  561. List<Object> row = new ArrayList<Object>(Arrays.asList(rowArray));
  562. //Append null for arrays that are too small
  563. for (int i = rowArray.length; i < _columnCount; i++) {
  564. row.add(null);
  565. }
  566. for (iter = _columns.iterator(); iter.hasNext() && index < row.size(); index++) {
  567. col = (Column) iter.next();
  568. if (!col.isVariableLength()) {
  569. //Fixed length column data comes first
  570. buffer.put(col.write(row.get(index)));
  571. }
  572. if (col.getType() == DataType.BOOLEAN) {
  573. if (row.get(index) != null) {
  574. if (!((Boolean) row.get(index)).booleanValue()) {
  575. //Booleans are stored in the null mask
  576. nullMask.markNull(index);
  577. }
  578. }
  579. } else if (row.get(index) == null) {
  580. nullMask.markNull(index);
  581. }
  582. }
  583. int varLengthCount = Column.countVariableLength(_columns);
  584. short[] varColumnOffsets = new short[varLengthCount];
  585. index = 0;
  586. int varColumnOffsetsIndex = 0;
  587. //Now write out variable length column data
  588. for (iter = _columns.iterator(); iter.hasNext() && index < row.size(); index++) {
  589. col = (Column) iter.next();
  590. short offset = (short) buffer.position();
  591. if (col.isVariableLength()) {
  592. if (row.get(index) != null) {
  593. buffer.put(col.write(row.get(index)));
  594. }
  595. varColumnOffsets[varColumnOffsetsIndex++] = offset;
  596. }
  597. }
  598. buffer.putShort((short) buffer.position()); //EOD marker
  599. //Now write out variable length offsets
  600. //Offsets are stored in reverse order
  601. for (int i = varColumnOffsets.length - 1; i >= 0; i--) {
  602. buffer.putShort(varColumnOffsets[i]);
  603. }
  604. buffer.putShort((short) varLengthCount); //Number of var length columns
  605. buffer.put(nullMask.wrap()); //Null mask
  606. buffer.limit(buffer.position());
  607. buffer.flip();
  608. if (LOG.isDebugEnabled()) {
  609. LOG.debug("Creating new data block:\n" + ByteUtil.toHexString(buffer, buffer.limit()));
  610. }
  611. return buffer;
  612. }
  613. public int getRowCount() {
  614. return _rowCount;
  615. }
  616. public String toString() {
  617. StringBuilder rtn = new StringBuilder();
  618. rtn.append("Type: " + _tableType);
  619. rtn.append("\nName: " + _name);
  620. rtn.append("\nRow count: " + _rowCount);
  621. rtn.append("\nColumn count: " + _columnCount);
  622. rtn.append("\nIndex count: " + _indexCount);
  623. rtn.append("\nColumns:\n");
  624. Iterator iter = _columns.iterator();
  625. while (iter.hasNext()) {
  626. rtn.append(iter.next().toString());
  627. }
  628. rtn.append("\nIndexes:\n");
  629. iter = _indexes.iterator();
  630. while (iter.hasNext()) {
  631. rtn.append(iter.next().toString());
  632. }
  633. rtn.append("\nOwned pages: " + _ownedPages + "\n");
  634. return rtn.toString();
  635. }
  636. /**
  637. * @return A simple String representation of the entire table in tab-delimited format
  638. */
  639. public String display() throws IOException {
  640. return display(Long.MAX_VALUE);
  641. }
  642. /**
  643. * @param limit Maximum number of rows to display
  644. * @return A simple String representation of the entire table in tab-delimited format
  645. */
  646. public String display(long limit) throws IOException {
  647. reset();
  648. StringBuilder rtn = new StringBuilder();
  649. Iterator iter = _columns.iterator();
  650. while (iter.hasNext()) {
  651. Column col = (Column) iter.next();
  652. rtn.append(col.getName());
  653. if (iter.hasNext()) {
  654. rtn.append("\t");
  655. }
  656. }
  657. rtn.append("\n");
  658. Map row;
  659. int rowCount = 0;
  660. while ((rowCount++ < limit) && (row = getNextRow()) != null) {
  661. iter = row.values().iterator();
  662. while (iter.hasNext()) {
  663. Object obj = iter.next();
  664. if (obj instanceof byte[]) {
  665. byte[] b = (byte[]) obj;
  666. rtn.append(ByteUtil.toHexString(ByteBuffer.wrap(b), b.length));
  667. //This block can be used to easily dump a binary column to a file
  668. /*java.io.File f = java.io.File.createTempFile("ole", ".bin");
  669. java.io.FileOutputStream out = new java.io.FileOutputStream(f);
  670. out.write(b);
  671. out.flush();
  672. out.close();*/
  673. } else {
  674. rtn.append(String.valueOf(obj));
  675. }
  676. if (iter.hasNext()) {
  677. rtn.append("\t");
  678. }
  679. }
  680. rtn.append("\n");
  681. }
  682. return rtn.toString();
  683. }
  684. public static short findRowStart(ByteBuffer buffer, int rowNum,
  685. JetFormat format)
  686. {
  687. return (short)(buffer.getShort(format.OFFSET_ROW_START +
  688. (format.SIZE_ROW_LOCATION * rowNum))
  689. & OFFSET_MASK);
  690. }
  691. public static short findRowEnd(ByteBuffer buffer, int rowNum,
  692. JetFormat format)
  693. {
  694. return (short)((rowNum == 0) ?
  695. format.PAGE_SIZE :
  696. (buffer.getShort(format.OFFSET_ROW_START +
  697. (format.SIZE_ROW_LOCATION * (rowNum - 1)))
  698. & OFFSET_MASK));
  699. }
  700. /**
  701. * Row iterator for this table, supports modification.
  702. */
  703. private final class RowIterator implements Iterator<Map<String, Object>>
  704. {
  705. private Collection<String> _columnNames;
  706. private Map<String, Object> _next;
  707. private RowIterator(Collection<String> columnNames)
  708. {
  709. try {
  710. reset();
  711. _columnNames = columnNames;
  712. _next = getNextRow(_columnNames);
  713. } catch(IOException e) {
  714. throw new IllegalStateException(e);
  715. }
  716. }
  717. public boolean hasNext() { return _next != null; }
  718. public void remove() {
  719. try {
  720. deleteCurrentRow();
  721. } catch(IOException e) {
  722. throw new IllegalStateException(e);
  723. }
  724. }
  725. public Map<String, Object> next() {
  726. if(!hasNext()) {
  727. throw new NoSuchElementException();
  728. }
  729. try {
  730. Map<String, Object> rtn = _next;
  731. _next = getNextRow(_columnNames);
  732. return rtn;
  733. } catch(IOException e) {
  734. throw new IllegalStateException(e);
  735. }
  736. }
  737. }
  738. }