summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJames Ahlborn <jtahlborn@yahoo.com>2008-07-20 03:47:49 +0000
committerJames Ahlborn <jtahlborn@yahoo.com>2008-07-20 03:47:49 +0000
commit7afa998954f52053f68d7bee96b674e43153e6af (patch)
tree31fc6f5cbefa5fec659d02d9d72f0a8970fc1c24
parent16d611821a3b6cba866ab596cd5a53b3533e68f8 (diff)
downloadjackcess-7afa998954f52053f68d7bee96b674e43153e6af.tar.gz
jackcess-7afa998954f52053f68d7bee96b674e43153e6af.zip
Share out-of-line long value pages in order to generate more compact database files
git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@362 f203690c-595d-4dc9-a70b-905162fa7fd2
-rw-r--r--src/changes/changes.xml4
-rw-r--r--src/java/com/healthmarketscience/jackcess/Column.java84
-rw-r--r--src/java/com/healthmarketscience/jackcess/Table.java30
-rw-r--r--test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java70
4 files changed, 148 insertions, 40 deletions
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 9f164e2..2aa3336 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -18,6 +18,10 @@
Add some more limit checking into table creation based on what access
supports (max rows per table, max identifier lengths).
</action>
+ <action dev="jahlborn" type="fix" issue="2019244">
+ Share out-of-line long value pages in order to generate more compact
+ database files.
+ </action>
</release>
<release version="1.1.15" date="2008-06-27">
<action dev="jahlborn" type="fix" issue="1998225">
diff --git a/src/java/com/healthmarketscience/jackcess/Column.java b/src/java/com/healthmarketscience/jackcess/Column.java
index 76012ef..528ce12 100644
--- a/src/java/com/healthmarketscience/jackcess/Column.java
+++ b/src/java/com/healthmarketscience/jackcess/Column.java
@@ -771,20 +771,20 @@ public class Column implements Comparable<Column> {
def.put(value);
} else {
+ TempPageHolder lvalBufferH = getTable().getLongValueBuffer();
+ ByteBuffer lvalPage = null;
int firstLvalPageNum = PageChannel.INVALID_PAGE_NUMBER;
byte firstLvalRow = 0;
- ByteBuffer lvalPage = getPageChannel().createPageBuffer();
-
// write other page(s)
switch(type) {
case LONG_VALUE_TYPE_OTHER_PAGE:
- writeLongValueHeader(lvalPage);
- firstLvalRow = (byte)Table.addDataPageRow(lvalPage,
- value.length,
+ lvalPage = getLongValuePage(value.length, lvalBufferH);
+ firstLvalPageNum = lvalBufferH.getPageNumber();
+ firstLvalRow = (byte)Table.addDataPageRow(lvalPage, value.length,
getFormat());
lvalPage.put(value);
- firstLvalPageNum = getPageChannel().writeNewPage(lvalPage);
+ getPageChannel().writePage(lvalPage, firstLvalPageNum);
break;
case LONG_VALUE_TYPE_OTHER_PAGES:
@@ -792,22 +792,35 @@ public class Column implements Comparable<Column> {
ByteBuffer buffer = ByteBuffer.wrap(value);
int remainingLen = buffer.remaining();
buffer.limit(0);
- int lvalPageNum = getPageChannel().allocateNewPage();
- byte lvalRow = 0;
+ lvalPage = getLongValuePage(getFormat().MAX_LONG_VALUE_ROW_SIZE,
+ lvalBufferH);
+ firstLvalPageNum = lvalBufferH.getPageNumber();
+ int lvalPageNum = firstLvalPageNum;
+ ByteBuffer nextLvalPage = null;
int nextLvalPageNum = 0;
while(remainingLen > 0) {
lvalPage.clear();
- writeLongValueHeader(lvalPage);
- // figure out how much we will put in this page
+ // figure out how much we will put in this page (we need 4 bytes for
+ // the next page pointer)
int chunkLength = Math.min(getFormat().MAX_LONG_VALUE_ROW_SIZE - 4,
remainingLen);
- nextLvalPageNum = ((chunkLength < remainingLen) ?
- getPageChannel().allocateNewPage() : 0);
+
+ // figure out if we will need another page, and if so, allocate it
+ if(chunkLength < remainingLen) {
+ // force a new page to be allocated
+ lvalBufferH.clear();
+ nextLvalPage = getLongValuePage(
+ getFormat().MAX_LONG_VALUE_ROW_SIZE, lvalBufferH);
+ nextLvalPageNum = lvalBufferH.getPageNumber();
+ } else {
+ nextLvalPage = null;
+ nextLvalPageNum = 0;
+ }
// add row to this page
- lvalRow = (byte)Table.addDataPageRow(lvalPage, chunkLength + 4,
- getFormat());
+ byte lvalRow = (byte)Table.addDataPageRow(lvalPage, chunkLength + 4,
+ getFormat());
// write next page info (we'll always be writing into row 0 for
// newly created pages)
@@ -821,14 +834,20 @@ public class Column implements Comparable<Column> {
// write new page to database
getPageChannel().writePage(lvalPage, lvalPageNum);
-
- // hang onto first page info
- if(firstLvalPageNum == PageChannel.INVALID_PAGE_NUMBER) {
- firstLvalPageNum = lvalPageNum;
+
+ if(lvalPageNum == firstLvalPageNum) {
+ // save initial row info
firstLvalRow = lvalRow;
+ } else {
+ // check assertion that we wrote to row 0 for all subsequent pages
+ if(lvalRow != (byte)0) {
+ throw new IllegalStateException("Expected row 0, but was " +
+ lvalRow);
+ }
}
-
+
// move to next page
+ lvalPage = nextLvalPage;
lvalPageNum = nextLvalPageNum;
}
break;
@@ -855,14 +874,35 @@ public class Column implements Comparable<Column> {
{
lvalPage.put(PageTypes.DATA); //Page type
lvalPage.put((byte) 1); //Unknown
- lvalPage.putShort((short) (getFormat().PAGE_SIZE -
- getFormat().OFFSET_ROW_START)); //Free space
+ lvalPage.putShort((short)getFormat().PAGE_INITIAL_FREE_SPACE); //Free space
lvalPage.put((byte) 'L');
lvalPage.put((byte) 'V');
lvalPage.put((byte) 'A');
lvalPage.put((byte) 'L');
- lvalPage.putShort((short)0); // num rows in page
lvalPage.putInt(0); //unknown
+ lvalPage.putShort((short)0); // num rows in page
+ }
+
+ /**
+ * Returns a long value data page with space for data of the given length.
+ */
+ private ByteBuffer getLongValuePage(int dataLength,
+ TempPageHolder lvalBufferH)
+ throws IOException
+ {
+ ByteBuffer lvalPage = null;
+ if(lvalBufferH.getPageNumber() != PageChannel.INVALID_PAGE_NUMBER) {
+ lvalPage = lvalBufferH.getPage(getPageChannel());
+ if(Table.rowFitsOnDataPage(dataLength, lvalPage, getFormat())) {
+ // the current page has space
+ return lvalPage;
+ }
+ }
+
+ // need new page
+ lvalPage = lvalBufferH.setNewPage(getPageChannel());
+ writeLongValueHeader(lvalPage);
+ return lvalPage;
}
/**
diff --git a/src/java/com/healthmarketscience/jackcess/Table.java b/src/java/com/healthmarketscience/jackcess/Table.java
index 35c9e72..b4424dc 100644
--- a/src/java/com/healthmarketscience/jackcess/Table.java
+++ b/src/java/com/healthmarketscience/jackcess/Table.java
@@ -122,6 +122,9 @@ public class Table
every call) */
private final TempBufferHolder _multiRowBufferH =
TempBufferHolder.newHolder(TempBufferHolder.Type.NONE, true);
+ /** page buffer used to write out-of-line "long value" data */
+ private final TempPageHolder _longValueBufferH =
+ TempPageHolder.newHolder(TempBufferHolder.Type.SOFT);
/** for now, "big index support" is optional */
private final boolean _useBigIndex;
@@ -224,6 +227,10 @@ public class Table
return _ownedPages.cursor();
}
+ protected TempPageHolder getLongValueBuffer() {
+ return _longValueBufferH;
+ }
+
/**
* @return All of the columns in this table (unmodifiable List)
*/
@@ -1217,11 +1224,7 @@ public class Table
for (int i = 0; i < rowData.length; i++) {
int rowSize = rowData[i].remaining();
- int rowSpaceUsage = getRowSpaceUsage(rowSize, getFormat());
- short freeSpaceInPage = dataPage.getShort(getFormat().OFFSET_FREE_SPACE);
- int rowsOnPage = getRowsOnDataPage(dataPage, getFormat());
- if((freeSpaceInPage < rowSpaceUsage) ||
- (rowsOnPage >= getFormat().MAX_NUM_ROWS_ON_DATA_PAGE)) {
+ if(!rowFitsOnDataPage(rowSize, dataPage, getFormat())) {
// Last data page is full. Create a new one.
writeDataPage(dataPage, pageNumber);
@@ -1229,8 +1232,6 @@ public class Table
dataPage = newDataPage();
pageNumber = _addRowBufferH.getPageNumber();
-
- freeSpaceInPage = dataPage.getShort(getFormat().OFFSET_FREE_SPACE);
}
// write out the row data
@@ -1643,6 +1644,21 @@ public class Table
}
/**
+ * Returns {@code true} if a row of the given size will fit on the given
+ * data page, {@code false} otherwise.
+ */
+ public static boolean rowFitsOnDataPage(
+ int rowLength, ByteBuffer dataPage, JetFormat format)
+ throws IOException
+ {
+ int rowSpaceUsage = getRowSpaceUsage(rowLength, format);
+ short freeSpaceInPage = dataPage.getShort(format.OFFSET_FREE_SPACE);
+ int rowsOnPage = getRowsOnDataPage(dataPage, format);
+ return ((rowSpaceUsage <= freeSpaceInPage) &&
+ (rowsOnPage < format.MAX_NUM_ROWS_ON_DATA_PAGE));
+ }
+
+ /**
* Duplicates and returns a row of data, optionally with a longer length
* filled with {@code null}.
*/
diff --git a/test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java b/test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java
index 6102c6c..19078ee 100644
--- a/test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java
+++ b/test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java
@@ -46,6 +46,7 @@ import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -121,6 +122,16 @@ public class DatabaseTest extends TestCase {
// success
}
+ try {
+ new TableBuilder("test")
+ .addColumn(new ColumnBuilder("A_" + createString(70), DataType.TEXT)
+ .toColumn())
+ .toTable(db);
+ fail("created table with too long column name?");
+ } catch(IllegalArgumentException e) {
+ // success
+ }
+
new TableBuilder("test")
.addColumn(new ColumnBuilder("A", DataType.TEXT).toColumn())
.toTable(db);
@@ -352,12 +363,7 @@ public class DatabaseTest extends TestCase {
.toTable(db);
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();
+ String longMemo = createString(2030);
byte[] oleValue = toByteArray(new File("test/data/test2BinData.dat"));
@@ -383,7 +389,7 @@ public class DatabaseTest extends TestCase {
public void testManyMemos() throws Exception {
final int numColumns = 126;
Database db = create();
- TableBuilder bigTableBuilder = new TableBuilder("myBigTable");
+ TableBuilder bigTableBuilder = new TableBuilder("test");
for (int i = 0; i < numColumns; i++)
{
@@ -394,15 +400,58 @@ public class DatabaseTest extends TestCase {
Table bigTable = bigTableBuilder.toTable(db);
- for (int j = 999; j < 1010; j++)
+ List<Object[]> expectedRows = new ArrayList<Object[]>();
+
+ for (int j = 0; j < 3; j++)
+ {
+ Object[] rowData = new String[numColumns];
+ for (int i = 0; i < numColumns; i++)
+ {
+ rowData[i] = "v_" + i + ";" + (j + 999);
+ }
+ expectedRows.add(rowData);
+ bigTable.addRow(rowData);
+ }
+
+ String extra1 = createString(100);
+ String extra2 = createString(2050);
+
+ for (int j = 0; j < 1; j++)
{
Object[] rowData = new String[numColumns];
for (int i = 0; i < numColumns; i++)
{
- rowData[i] = "v_" + i + ";" + j;
+ rowData[i] = "v_" + i + ";" + (j + 999) + extra2;
}
+ expectedRows.add(rowData);
bigTable.addRow(rowData);
}
+
+ for (int j = 0; j < 2; j++)
+ {
+ Object[] rowData = new String[numColumns];
+ for (int i = 0; i < numColumns; i++)
+ {
+ String tmp = "v_" + i + ";" + (j + 999);
+ if((i % 3) == 0) {
+ tmp += extra1;
+ } else if((i % 7) == 0) {
+ tmp += extra2;
+ }
+ rowData[i] = tmp;
+ }
+ expectedRows.add(rowData);
+ bigTable.addRow(rowData);
+ }
+
+ bigTable.reset();
+ Iterator<Object[]> expIter = expectedRows.iterator();
+ for(Map<?,?> row : bigTable) {
+ Object[] expectedRow = expIter.next();
+ assertEquals(Arrays.asList(expectedRow),
+ new ArrayList<Object>(row.values()));
+ }
+
db.close();
}
@@ -876,8 +925,7 @@ public class DatabaseTest extends TestCase {
for(int i = 0; i < len; ++i) {
builder.append((char)('a' + (i % 26)));
}
- String str = builder.toString();
- return str;
+ return builder.toString();
}
static void assertRowCount(int expectedRowCount, Table table)