From b9378914c86f118caa452a2cdb83f21527172320 Mon Sep 17 00:00:00 2001 From: James Ahlborn Date: Mon, 2 Oct 2006 15:57:57 +0000 Subject: [PATCH] implement multi-page long value writing git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@127 f203690c-595d-4dc9-a70b-905162fa7fd2 --- .../healthmarketscience/jackcess/Column.java | 203 ++++++++++++------ .../jackcess/JetFormat.java | 8 - .../jackcess/PageChannel.java | 18 ++ .../healthmarketscience/jackcess/Table.java | 53 +++-- test/data/test2BinData.dat | Bin 0 -> 22970 bytes .../jackcess/DatabaseTest.java | 57 ++++- 6 files changed, 249 insertions(+), 90 deletions(-) create mode 100644 test/data/test2BinData.dat diff --git a/src/java/com/healthmarketscience/jackcess/Column.java b/src/java/com/healthmarketscience/jackcess/Column.java index 5237c2b..a8f2265 100644 --- a/src/java/com/healthmarketscience/jackcess/Column.java +++ b/src/java/com/healthmarketscience/jackcess/Column.java @@ -313,7 +313,7 @@ public class Column implements Comparable { return readCurrencyValue(buffer); } else if (_type == DataType.OLE) { if (data.length > 0) { - return readLongBinaryValue(data); + return readLongValue(data); } else { return null; } @@ -340,7 +340,7 @@ public class Column implements Comparable { * LONG_VALUE_TYPE_* * @return The LVAL data */ - private byte[] readLongBinaryValue(byte[] lvalDefinition) + private byte[] readLongValue(byte[] lvalDefinition) throws IOException { ByteBuffer def = ByteBuffer.wrap(lvalDefinition); @@ -353,18 +353,30 @@ public class Column implements Comparable { } byte[] rtn = new byte[length]; byte type = def.get(); - switch (type) { - case LONG_VALUE_TYPE_OTHER_PAGE: - if (lvalDefinition.length != _format.SIZE_LONG_VALUE_DEF) { - throw new IOException("Expected " + _format.SIZE_LONG_VALUE_DEF + - " bytes in long value definition, but found " + - lvalDefinition.length); - } + if(type == LONG_VALUE_TYPE_THIS_PAGE) { + + // inline long value + def.getInt(); //Skip over lval_dp + def.getInt(); //Skip over unknown + def.get(rtn); + + } else { + + // long value on other page(s) + if (lvalDefinition.length != _format.SIZE_LONG_VALUE_DEF) { + throw new IOException("Expected " + _format.SIZE_LONG_VALUE_DEF + + " bytes in long value definition, but found " + + lvalDefinition.length); + } + + byte rowNum = def.get(); + int pageNum = ByteUtil.get3ByteInt(def, def.position()); + ByteBuffer lvalPage = _pageChannel.createPageBuffer(); + + switch (type) { + case LONG_VALUE_TYPE_OTHER_PAGE: { - byte rowNum = def.get(); - int pageNum = ByteUtil.get3ByteInt(def, def.position()); - ByteBuffer lvalPage = _pageChannel.createPageBuffer(); _pageChannel.readPage(lvalPage, pageNum); short rowStart = Table.findRowStart(lvalPage, rowNum, _format); @@ -379,25 +391,9 @@ public class Column implements Comparable { } break; - case LONG_VALUE_TYPE_THIS_PAGE: - def.getInt(); //Skip over lval_dp - def.getInt(); //Skip over unknown - def.get(rtn); - break; - case LONG_VALUE_TYPE_OTHER_PAGES: ByteBuffer rtnBuf = ByteBuffer.wrap(rtn); - - if (lvalDefinition.length != _format.SIZE_LONG_VALUE_DEF) { - throw new IOException("Expected " + _format.SIZE_LONG_VALUE_DEF + - " bytes in long value definition, but found " + - lvalDefinition.length); - } - byte rowNum = def.get(); - int pageNum = ByteUtil.get3ByteInt(def, def.position()); - ByteBuffer lvalPage = _pageChannel.createPageBuffer(); - int remainingLen = length; while(remainingLen > 0) { lvalPage.clear(); @@ -405,7 +401,6 @@ public class Column implements Comparable { short rowStart = Table.findRowStart(lvalPage, rowNum, _format); short rowEnd = Table.findRowEnd(lvalPage, rowNum, _format); - // read next page information lvalPage.position(rowStart); @@ -428,7 +423,9 @@ public class Column implements Comparable { default: throw new IOException("Unrecognized long value type: " + type); + } } + return rtn; } @@ -439,7 +436,7 @@ public class Column implements Comparable { private String readLongStringValue(byte[] lvalDefinition) throws IOException { - byte[] binData = readLongBinaryValue(lvalDefinition); + byte[] binData = readLongValue(lvalDefinition); if(binData == null) { return null; } @@ -637,61 +634,137 @@ public class Column implements Comparable { } /** - * Write an LVAL column into a ByteBuffer inline (LONG_VALUE_TYPE_THIS_PAGE) + * Write an LVAL column into a ByteBuffer inline if it fits, otherwise in + * other data page(s). * @param value Value of the LVAL column - * @return A buffer containing the LVAL definition and the column value + * @return A buffer containing the LVAL definition and (possibly) the column + * value (unless written to other pages) */ public ByteBuffer writeLongValue(byte[] value, int remainingRowLength) throws IOException { - // FIXME, take remainingRowLength into account (don't always write inline) - if(value.length > getType().getMaxSize()) { throw new IOException("value too big for column"); } - ByteBuffer def = ByteBuffer.allocate(_format.SIZE_LONG_VALUE_DEF + value.length); + + // determine which type to write + byte type = 0; + int lvalDefLen = _format.SIZE_LONG_VALUE_DEF; + if((_format.SIZE_LONG_VALUE_DEF + value.length) <= remainingRowLength) { + type = LONG_VALUE_TYPE_THIS_PAGE; + lvalDefLen += value.length; + } else if(Table.getRowSpaceUsage(value.length, _format) <= + _format.MAX_ROW_SIZE) + { + type = LONG_VALUE_TYPE_OTHER_PAGE; + } else { + type = LONG_VALUE_TYPE_OTHER_PAGES; + } + + ByteBuffer def = ByteBuffer.allocate(lvalDefLen); def.order(ByteOrder.LITTLE_ENDIAN); ByteUtil.put3ByteInt(def, value.length); - def.put(LONG_VALUE_TYPE_THIS_PAGE); - def.putInt(0); - def.putInt(0); //Unknown - def.put(value); + def.put(type); + + if(type == LONG_VALUE_TYPE_THIS_PAGE) { + // write long value inline + def.putInt(0); + def.putInt(0); //Unknown + def.put(value); + } else { + + int firstLvalPageNum = PageChannel.INVALID_PAGE_NUMBER; + byte firstLvalRow = 0; + + ByteBuffer lvalPage = _pageChannel.createPageBuffer(); + + // write other page(s) + switch(type) { + case LONG_VALUE_TYPE_OTHER_PAGE: + writeLongValueHeader(lvalPage); + firstLvalRow = (byte)Table.addDataPageRow(lvalPage, + value.length, + _format); + lvalPage.put(value); + firstLvalPageNum = _pageChannel.writeNewPage(lvalPage); + break; + + case LONG_VALUE_TYPE_OTHER_PAGES: + + ByteBuffer buffer = ByteBuffer.wrap(value); + int remainingLen = buffer.remaining(); + buffer.limit(0); + int lvalPageNum = _pageChannel.allocateNewPage(); + byte lvalRow = 0; + int nextLvalPageNum = 0; + while(remainingLen > 0) { + lvalPage.clear(); + writeLongValueHeader(lvalPage); + + // figure out how much we will put in this page + int chunkLength = Math.min(_format.MAX_ROW_SIZE - 4, + remainingLen); + nextLvalPageNum = ((chunkLength < remainingLen) ? + _pageChannel.allocateNewPage() : 0); + + // add row to this page + lvalRow = (byte)Table.addDataPageRow(lvalPage, chunkLength + 4, + _format); + + // write next page info (we'll always be writing into row 0 for + // newly created pages) + lvalPage.put((byte)0); // row number + ByteUtil.put3ByteInt(lvalPage, nextLvalPageNum); // page number + + // write this page's chunk of data + buffer.limit(buffer.limit() + chunkLength); + lvalPage.put(buffer); + remainingLen -= chunkLength; + + // write new page to database + _pageChannel.writePage(lvalPage, lvalPageNum); + + // hang onto first page info + if(firstLvalPageNum == PageChannel.INVALID_PAGE_NUMBER) { + firstLvalPageNum = lvalPageNum; + firstLvalRow = lvalRow; + } + + // move to next page + lvalPageNum = nextLvalPageNum; + } + break; + + default: + throw new IOException("Unrecognized long value type: " + type); + } + + // update def + def.put(firstLvalRow); + ByteUtil.put3ByteInt(def, firstLvalPageNum); + def.putInt(0); //Unknown + + } + def.flip(); - return def; + return def; } - + /** - * Write an LVAL column into a ByteBuffer on another page - * (LONG_VALUE_TYPE_OTHER_PAGE) - * @param value Value of the LVAL column - * @return A buffer containing the LVAL definition + * Writes the header info for a long value page. */ - // FIXME, unused? - private ByteBuffer writeLongValueInNewPage(byte[] value) throws IOException { - ByteBuffer lvalPage = _pageChannel.createPageBuffer(); + private void writeLongValueHeader(ByteBuffer lvalPage) + { lvalPage.put(PageTypes.DATA); //Page type lvalPage.put((byte) 1); //Unknown lvalPage.putShort((short) (_format.PAGE_SIZE - - _format.OFFSET_LVAL_ROW_LOCATION_BLOCK - _format.SIZE_ROW_LOCATION - - value.length)); //Free space + _format.OFFSET_ROW_START)); //Free space lvalPage.put((byte) 'L'); lvalPage.put((byte) 'V'); lvalPage.put((byte) 'A'); lvalPage.put((byte) 'L'); - int offset = _format.PAGE_SIZE - value.length; - lvalPage.position(14); - lvalPage.putShort((short) offset); - lvalPage.position(offset); - lvalPage.put(value); - ByteBuffer def = ByteBuffer.allocate(_format.SIZE_LONG_VALUE_DEF); - def.order(ByteOrder.LITTLE_ENDIAN); - ByteUtil.put3ByteInt(def, value.length); - def.put(LONG_VALUE_TYPE_OTHER_PAGE); - def.put((byte) 0); //Row number - ByteUtil.put3ByteInt(def, _pageChannel.writeNewPage(lvalPage)); //Page # - def.putInt(0); //Unknown - def.flip(); - return def; + lvalPage.putShort((short)0); // num rows in page + lvalPage.putInt(0); //unknown } /** @@ -721,8 +794,6 @@ public class Column implements Comparable { // var length column if(!getType().isLongValue()) { - // FIXME, take remainingRowLength into account? overflow pages? - // this is an "inline" var length field switch(getType()) { case NUMERIC: diff --git a/src/java/com/healthmarketscience/jackcess/JetFormat.java b/src/java/com/healthmarketscience/jackcess/JetFormat.java index 327e96a..b1f0c25 100644 --- a/src/java/com/healthmarketscience/jackcess/JetFormat.java +++ b/src/java/com/healthmarketscience/jackcess/JetFormat.java @@ -100,8 +100,6 @@ public abstract class JetFormat { public final int OFFSET_FREE_SPACE; public final int OFFSET_NUM_ROWS_ON_DATA_PAGE; - public final int OFFSET_LVAL_ROW_LOCATION_BLOCK; - public final int OFFSET_USED_PAGES_USAGE_MAP_DEF; public final int OFFSET_FREE_PAGES_USAGE_MAP_DEF; @@ -185,8 +183,6 @@ public abstract class JetFormat { OFFSET_FREE_SPACE = defineOffsetFreeSpace(); OFFSET_NUM_ROWS_ON_DATA_PAGE = defineOffsetNumRowsOnDataPage(); - OFFSET_LVAL_ROW_LOCATION_BLOCK = defineOffsetLvalRowLocationBlock(); - OFFSET_USED_PAGES_USAGE_MAP_DEF = defineOffsetUsedPagesUsageMapDef(); OFFSET_FREE_PAGES_USAGE_MAP_DEF = defineOffsetFreePagesUsageMapDef(); @@ -249,8 +245,6 @@ public abstract class JetFormat { protected abstract int defineOffsetFreeSpace(); protected abstract int defineOffsetNumRowsOnDataPage(); - protected abstract int defineOffsetLvalRowLocationBlock(); - protected abstract int defineOffsetUsedPagesUsageMapDef(); protected abstract int defineOffsetFreePagesUsageMapDef(); @@ -314,8 +308,6 @@ public abstract class JetFormat { protected int defineOffsetFreeSpace() { return 2; } protected int defineOffsetNumRowsOnDataPage() { return 12; } - protected int defineOffsetLvalRowLocationBlock() { return 10; } - protected int defineOffsetUsedPagesUsageMapDef() { return 4027; } protected int defineOffsetFreePagesUsageMapDef() { return 3958; } diff --git a/src/java/com/healthmarketscience/jackcess/PageChannel.java b/src/java/com/healthmarketscience/jackcess/PageChannel.java index 0acaad8..3865394 100644 --- a/src/java/com/healthmarketscience/jackcess/PageChannel.java +++ b/src/java/com/healthmarketscience/jackcess/PageChannel.java @@ -44,6 +44,9 @@ public class PageChannel implements Channel { private static final Log LOG = LogFactory.getLog(PageChannel.class); static final int INVALID_PAGE_NUMBER = -1; + + /** dummy buffer used when allocating new pages */ + private static final ByteBuffer FORCE_BYTES = ByteBuffer.allocate(1); /** Global usage map always lives on page 1 */ private static final int PAGE_GLOBAL_USAGE_MAP = 1; @@ -113,6 +116,21 @@ public class PageChannel implements Channel { _globalUsageMap.removePageNumber(pageNumber); //force is done here return pageNumber; } + + /** + * Allocates a new page in the database. Data in the page is undefined + * until it is written in a call to {@link #writePage}. + */ + public int allocateNewPage() throws IOException { + long size = _channel.size(); + FORCE_BYTES.rewind(); + long offset = size + _format.PAGE_SIZE - FORCE_BYTES.remaining(); + // this will force the file to be extended with mostly undefined bytes + _channel.write(FORCE_BYTES, offset); + int pageNumber = (int) (size / _format.PAGE_SIZE); + _globalUsageMap.removePageNumber(pageNumber); //force is done here + return pageNumber; + } /** * @return Number of pages in the database diff --git a/src/java/com/healthmarketscience/jackcess/Table.java b/src/java/com/healthmarketscience/jackcess/Table.java index ba65ab7..a0ed9a6 100644 --- a/src/java/com/healthmarketscience/jackcess/Table.java +++ b/src/java/com/healthmarketscience/jackcess/Table.java @@ -701,7 +701,7 @@ public class Table } for (int i = 0; i < rowData.length; i++) { - rowSize = rowData[i].limit(); + rowSize = rowData[i].remaining(); int rowSpaceUsage = getRowSpaceUsage(rowSize, _format); short freeSpaceInPage = dataPage.getShort(_format.OFFSET_FREE_SPACE); if (freeSpaceInPage < rowSpaceUsage) { @@ -714,21 +714,16 @@ public class Table pageNumber = newDataPage(dataPage); freeSpaceInPage = dataPage.getShort(_format.OFFSET_FREE_SPACE); } - //Decrease free space record. - 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)); - short rowLocation = findRowEnd(dataPage, rowCount, _format); - rowLocation -= rowSize; - dataPage.putShort(getRowStartOffset(rowCount, _format), rowLocation); - dataPage.position(rowLocation); + + // write out the row data + int rowNum = addDataPageRow(dataPage, rowSize, _format); dataPage.put(rowData[i]); + + // update the indexes Iterator indIter = _indexes.iterator(); while (indIter.hasNext()) { Index index = (Index) indIter.next(); - index.addRow((Object[]) rows.get(i), pageNumber, (byte) rowCount); + index.addRow((Object[]) rows.get(i), pageNumber, (byte) rowNum); } } writeDataPage(dataPage, pageNumber); @@ -926,6 +921,40 @@ public class Table return rtn.toString(); } + /** + * Updates free space and row info for a new row of the given size in the + * given data page. Positions the page for writing the row data. + * @return the row number of the new row + */ + public static int addDataPageRow(ByteBuffer dataPage, + int rowSize, + JetFormat format) + { + int rowSpaceUsage = getRowSpaceUsage(rowSize, format); + + // Decrease free space record. + short freeSpaceInPage = dataPage.getShort(format.OFFSET_FREE_SPACE); + dataPage.putShort(format.OFFSET_FREE_SPACE, (short) (freeSpaceInPage - + rowSpaceUsage)); + + // Increment row count record. + short rowCount = dataPage.getShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE); + dataPage.putShort(format.OFFSET_NUM_ROWS_ON_DATA_PAGE, + (short) (rowCount + 1)); + + // determine row position + short rowLocation = findRowEnd(dataPage, rowCount, format); + rowLocation -= rowSize; + + // write row position + dataPage.putShort(getRowStartOffset(rowCount, format), rowLocation); + + // set position for row data + dataPage.position(rowLocation); + + return rowCount; + } + public static short findRowStart(ByteBuffer buffer, int rowNum, JetFormat format) { diff --git a/test/data/test2BinData.dat b/test/data/test2BinData.dat new file mode 100644 index 0000000000000000000000000000000000000000..3763155636ba71d8d95b5c40a042bfe7220060b8 GIT binary patch literal 22970 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 KU-?nw!hZnkd(mV7 literal 0 HcmV?d00001 diff --git a/test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java b/test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java index 4fc8722..51d9220 100644 --- a/test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java +++ b/test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java @@ -37,8 +37,16 @@ public class DatabaseTest extends TestCase { } static Database create() throws Exception { + return create(false); + } + + static Database create(boolean keep) throws Exception { File tmp = File.createTempFile("databaseTest", ".mdb"); - tmp.deleteOnExit(); + if(keep) { + System.out.println("Created " + tmp); + } else { + tmp.deleteOnExit(); + } return Database.create(tmp); } @@ -277,7 +285,7 @@ public class DatabaseTest extends TestCase { assertEquals(2, table.getNextRow().get("D")); } - public void testReadMemo() throws Exception { + public void testReadLongValue() throws Exception { Database db = Database.open(new File("test/data/test2.mdb")); Table table = db.getTable("MSP_PROJECTS"); @@ -286,9 +294,14 @@ public class DatabaseTest extends TestCase { assertEquals("T", row.get("PROJ_PROP_COMPANY")); assertEquals("Standard", row.get("PROJ_INFO_CAL_NAME")); assertEquals("Project1", row.get("PROJ_PROP_TITLE")); + byte[] foundBinaryData = (byte[])row.get("RESERVED_BINARY_DATA"); + System.out.println("FOO found len " + foundBinaryData.length); + byte[] expectedBinaryData = + toByteArray(new File("test/data/test2BinData.dat")); + assertTrue(Arrays.equals(expectedBinaryData, foundBinaryData)); } - public void testWriteMemo() throws Exception { + public void testWriteLongValue() throws Exception { Database db = create(); @@ -301,18 +314,39 @@ public class DatabaseTest extends TestCase { col.setName("B"); col.setType(DataType.MEMO); columns.add(col); + col = new Column(); + col.setName("C"); + col.setType(DataType.OLE); + columns.add(col); db.createTable("test", columns); String testStr = "This is a test"; + StringBuilder strBuf = new StringBuilder(); + for(int i = 0; i < 2030; ++i) { + char c = (char)('a' + (i % 26)); + strBuf.append(c); + } + String longMemo = strBuf.toString(); + byte[] oleValue = toByteArray(new File("test/data/test2BinData.dat")); + Table table = db.getTable("Test"); - table.addRow(new Object[]{testStr, testStr}); + table.addRow(testStr, testStr, null); + table.addRow(testStr, longMemo, oleValue); + table.reset(); Map row = table.getNextRow(); assertEquals(testStr, row.get("A")); assertEquals(testStr, row.get("B")); + assertNull(row.get("C")); + + row = table.getNextRow(); + + assertEquals(testStr, row.get("A")); + assertEquals(longMemo, row.get("B")); + assertTrue(Arrays.equals(oleValue, (byte[])row.get("C"))); } @@ -635,5 +669,20 @@ public class DatabaseTest extends TestCase { ostream.close(); } } + + static byte[] toByteArray(File file) + throws IOException + { + // FIXME should really be using commons io IOUtils here, but don't want + // to add dep for one simple test method + FileInputStream istream = new FileInputStream(file); + try { + byte[] bytes = new byte[(int)file.length()]; + istream.read(bytes); + return bytes; + } finally { + istream.close(); + } + } } -- 2.39.5