Bladeren bron

Update table row count correctly on row deletion or bulk row addition,

        bug #1681954.


git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@174 f203690c-595d-4dc9-a70b-905162fa7fd2
tags/rel_1_1_10
James Ahlborn 16 jaren geleden
bovenliggende
commit
1fcd851a9f

+ 4
- 0
src/changes/changes.xml Bestand weergeven

</properties> </properties>
<body> <body>
<release version="1.1.10" date="TBD"> <release version="1.1.10" date="TBD">
<action dev="jahlborn" type="fix" issue="1681954">
Update table row count correctly on row deletion or bulk row addition,
bug #1681954.
</action>
<action dev="jahlborn" type="update" issue="1565216"> <action dev="jahlborn" type="update" issue="1565216">
Add experimental support for auto-number columns, feature request Add experimental support for auto-number columns, feature request
#1565216. #1565216.

+ 60
- 20
src/java/com/healthmarketscience/jackcess/Index.java Bestand weergeven

import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Map; import java.util.Map;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.TreeSet; import java.util.TreeSet;

import org.apache.commons.lang.builder.CompareToBuilder; import org.apache.commons.lang.builder.CompareToBuilder;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;






*/ */
public class Index implements Comparable<Index> { public class Index implements Comparable<Index> {
private static final Log LOG = LogFactory.getLog(Index.class);
/** Max number of columns in an index */ /** Max number of columns in an index */
private static final int MAX_COLUMNS = 10; private static final int MAX_COLUMNS = 10;
/** Page number of the index data */ /** Page number of the index data */
private int _pageNumber; private int _pageNumber;
private int _parentPageNumber; private int _parentPageNumber;
/** Number of rows in the index */
/** Number of rows in the index
NOTE: this does not actually seem to be the row count, unclear what the
value means*/
private int _rowCount; private int _rowCount;
private JetFormat _format; private JetFormat _format;
private SortedSet<Entry> _entries; private SortedSet<Entry> _entries;
buffer.put((byte) 0); //Unknown buffer.put((byte) 0); //Unknown
byte[] entryMask = new byte[_format.SIZE_INDEX_ENTRY_MASK]; byte[] entryMask = new byte[_format.SIZE_INDEX_ENTRY_MASK];
int totalSize = 0; int totalSize = 0;
Iterator iter = _entries.iterator();
while (iter.hasNext()) {
Entry entry = (Entry) iter.next();
for(Entry entry : _entries) {
int size = entry.size(); int size = entry.size();
totalSize += size; totalSize += size;
int idx = totalSize / 8; int idx = totalSize / 8;
entryMask[idx] |= (1 << (totalSize % 8)); entryMask[idx] |= (1 << (totalSize % 8));
} }
buffer.put(entryMask); buffer.put(entryMask);
iter = _entries.iterator();
while (iter.hasNext()) {
Entry entry = (Entry) iter.next();
for(Entry entry : _entries) {
entry.write(buffer); entry.write(buffer);
} }
buffer.putShort(2, (short) (_format.PAGE_SIZE - buffer.position())); buffer.putShort(2, (short) (_format.PAGE_SIZE - buffer.position()));
/** /**
* Add a row to this index
* Adds a row to this index
* <p> * <p>
* Forces index initialization. * Forces index initialization.
* *
{ {
// make sure we've parsed the entries // make sure we've parsed the entries
initialize(); initialize();

++_rowCount;
_entries.add(new Entry(row, pageNumber, rowNumber)); _entries.add(new Entry(row, pageNumber, rowNumber));
} }


/**
* Removes a row from this index
* <p>
* Forces index initialization.
*
* @param row Row to remove
* @param pageNumber Page number on which the row is removed
* @param rowNumber Row number at which the row is removed
*/
public void deleteRow(Object[] row, int pageNumber, byte rowNumber)
throws IOException
{
// make sure we've parsed the entries
initialize();

--_rowCount;
Entry oldEntry = new Entry(row, pageNumber, rowNumber);
if(!_entries.remove(oldEntry)) {
// the caller may have only read some of the row data, if this is the
// case, just search for the page/row numbers
boolean removed = false;
for(Iterator<Entry> iter = _entries.iterator(); iter.hasNext(); ) {
Entry entry = iter.next();
if((entry.getPage() == pageNumber) &&
(entry.getRow() == rowNumber)) {
iter.remove();
removed = true;
break;
}
}
if(!removed) {
LOG.warn("Failed removing index entry " + oldEntry + " for row: " +
Arrays.asList(row));
}
}
}

@Override @Override
public String toString() { public String toString() {
StringBuilder rtn = new StringBuilder(); StringBuilder rtn = new StringBuilder();
return new FixedEntryColumn(col); return new FixedEntryColumn(col);
} }
public List getEntryColumns() {
public List<EntryColumn> getEntryColumns() {
return _entryColumns; return _entryColumns;
} }
public int size() { public int size() {
int rtn = 4; int rtn = 4;
Iterator iter = _entryColumns.iterator();
while (iter.hasNext()) {
rtn += ((EntryColumn) iter.next()).size();
for(EntryColumn entryCol : _entryColumns) {
rtn += entryCol.size();
} }
return rtn; return rtn;
} }
* Write this entry into a buffer * Write this entry into a buffer
*/ */
public void write(ByteBuffer buffer) throws IOException { public void write(ByteBuffer buffer) throws IOException {
Iterator iter = _entryColumns.iterator();
while (iter.hasNext()) {
((EntryColumn) iter.next()).write(buffer);
for(EntryColumn entryCol : _entryColumns) {
entryCol.write(buffer);
} }
buffer.put((byte) (_page >>> 16)); buffer.put((byte) (_page >>> 16));
buffer.put((byte) (_page >>> 8)); buffer.put((byte) (_page >>> 8));
if (this == other) { if (this == other) {
return 0; return 0;
} }
Iterator myIter = _entryColumns.iterator();
Iterator otherIter = other.getEntryColumns().iterator();
Iterator<EntryColumn> myIter = _entryColumns.iterator();
Iterator<EntryColumn> otherIter = other.getEntryColumns().iterator();
while (myIter.hasNext()) { while (myIter.hasNext()) {
if (!otherIter.hasNext()) { if (!otherIter.hasNext()) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Trying to compare index entries with a different number of entry columns"); "Trying to compare index entries with a different number of entry columns");
} }
EntryColumn myCol = (EntryColumn) myIter.next();
EntryColumn otherCol = (EntryColumn) otherIter.next();
EntryColumn myCol = myIter.next();
EntryColumn otherCol = otherIter.next();
int i = myCol.compareTo(otherCol); int i = myCol.compareTo(otherCol);
if (i != 0) { if (i != 0) {
return i; return i;

+ 63
- 15
src/java/com/healthmarketscience/jackcess/Table.java Bestand weergeven

import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
readTableDefinition(tableBuffer); readTableDefinition(tableBuffer);
tableBuffer = null; tableBuffer = null;


_rowState = new RowState(true);
_rowState = new RowState(true, _maxColumnCount);
} }


/** /**


// FIXME, want to make this static, but writeDataPage is not static, also, this may screw up other rowstates... // FIXME, want to make this static, but writeDataPage is not static, also, this may screw up other rowstates...


// see if row was already deleted
if(_rowState.isDeleted()) {
throw new IllegalStateException("Deleting already deleted row");
}
// delete flag always gets set in the "root" page (even if overflow row) // delete flag always gets set in the "root" page (even if overflow row)
ByteBuffer rowBuffer = _rowState.getPage(_pageChannel); ByteBuffer rowBuffer = _rowState.getPage(_pageChannel);
int index = getRowStartOffset(_currentRowInPage, _format);
rowBuffer.putShort(index, (short)(rowBuffer.getShort(index)
int pageNumber = _rowState.getPageNumber();
int rowIndex = getRowStartOffset(_currentRowInPage, _format);
rowBuffer.putShort(rowIndex, (short)(rowBuffer.getShort(rowIndex)
| DELETED_ROW_MASK | OVERFLOW_ROW_MASK)); | DELETED_ROW_MASK | OVERFLOW_ROW_MASK));
writeDataPage(rowBuffer, _rowState.getPageNumber());
writeDataPage(rowBuffer, pageNumber);
_rowState.setDeleted(true); _rowState.setDeleted(true);

// update the indexes
for(Index index : _indexes) {
index.deleteRow(_rowState.getRowValues(), pageNumber,
(byte)_currentRowInPage);
}
// make sure table def gets updated
--_rowCount;
updateTableDefinition();
} }
/** /**
return null; return null;
} }


return getRow(rowBuffer, getRowNullMask(rowBuffer), _columns,
return getRow(_rowState, rowBuffer, getRowNullMask(rowBuffer), _columns,
columnNames); columnNames);
} }
return null; return null;
} }


return getRow(rowBuffer, getRowNullMask(rowBuffer), columns,
return getRow(rowState, rowBuffer, getRowNullMask(rowBuffer), columns,
columnNames); columnNames);
} }


* Reads the row data from the given row buffer. Leaves limit unchanged. * Reads the row data from the given row buffer. Leaves limit unchanged.
*/ */
private static Map<String, Object> getRow( private static Map<String, Object> getRow(
RowState rowState,
ByteBuffer rowBuffer, ByteBuffer rowBuffer,
NullMask nullMask, NullMask nullMask,
Collection<Column> columns, Collection<Column> columns,
Map<String, Object> rtn = new LinkedHashMap<String, Object>( Map<String, Object> rtn = new LinkedHashMap<String, Object>(
columns.size()); columns.size());
for(Column column : columns) { for(Column column : columns) {
Object value = null;
if((columnNames == null) || (columnNames.contains(column.getName()))) { if((columnNames == null) || (columnNames.contains(column.getName()))) {
// Add the value to the row data // Add the value to the row data
rtn.put(column.getName(), getRowColumn(rowBuffer, nullMask, column));
value = getRowColumn(rowBuffer, nullMask, column);
rtn.put(column.getName(), value);
} }

// cache the row values in order to be able to update the index on row
// deletion. note, most of the returned values are immutable, except
// for binary data (returned as byte[]), but binary data shouldn't be
// indexed anyway.
rowState._rowValues[column.getColumnNumber()] = value;
} }
return rtn; return rtn;
dataPage.put(rowData[i]); dataPage.put(rowData[i]);


// update the indexes // update the indexes
Iterator<Index> indIter = _indexes.iterator();
while (indIter.hasNext()) {
Index index = indIter.next();
index.addRow(rows.get(i), pageNumber, (byte) rowNum);
for(Index index : _indexes) {
index.addRow(rows.get(i), pageNumber, (byte)rowNum);
} }
} }
writeDataPage(dataPage, pageNumber); writeDataPage(dataPage, pageNumber);
//Update tdef page //Update tdef page
_rowCount += rows.size();
updateTableDefinition();
}

/**
* Updates the table definition after rows are modified.
*/
private void updateTableDefinition() throws IOException
{
// load table definition
ByteBuffer tdefPage = _pageChannel.createPageBuffer(); ByteBuffer tdefPage = _pageChannel.createPageBuffer();
_pageChannel.readPage(tdefPage, _tableDefPageNumber); _pageChannel.readPage(tdefPage, _tableDefPageNumber);
tdefPage.putInt(_format.OFFSET_NUM_ROWS, ++_rowCount);
// make sure rowcount and autonumber are up-to-date
tdefPage.putInt(_format.OFFSET_NUM_ROWS, _rowCount);
tdefPage.putInt(_format.OFFSET_NEXT_AUTO_NUMBER, _lastAutoNumber); tdefPage.putInt(_format.OFFSET_NEXT_AUTO_NUMBER, _lastAutoNumber);

// write any index changes
Iterator<Index> indIter = _indexes.iterator(); Iterator<Index> indIter = _indexes.iterator();
for (int i = 0; i < _indexes.size(); i++) { for (int i = 0; i < _indexes.size(); i++) {
tdefPage.putInt(_format.OFFSET_INDEX_DEF_BLOCK + tdefPage.putInt(_format.OFFSET_INDEX_DEF_BLOCK +
i * _format.SIZE_INDEX_DEFINITION + 4, _rowCount);
(i * _format.SIZE_INDEX_DEFINITION) + 4, _rowCount);
Index index = indIter.next(); Index index = indIter.next();
index.update(); index.update();
} }

// write modified table definition
_pageChannel.writePage(tdefPage, _tableDefPageNumber); _pageChannel.writePage(tdefPage, _tableDefPageNumber);
} }
/** the row buffer which contains the final data (after following any /** the row buffer which contains the final data (after following any
overflow pointers) */ overflow pointers) */
public ByteBuffer _finalRowBuffer; public ByteBuffer _finalRowBuffer;

public RowState(boolean hardRowBuffer) {
/** values read from the last row */
public Object[] _rowValues;
public RowState(boolean hardRowBuffer, int colCount) {
_rowBufferH = TempPageHolder.newHolder(hardRowBuffer); _rowBufferH = TempPageHolder.newHolder(hardRowBuffer);
_rowValues = new Object[colCount];
} }
public void reset() { public void reset() {
_finalRowBuffer = null; _finalRowBuffer = null;
_deleted = false; _deleted = false;
_overflow = false; _overflow = false;
Arrays.fill(_rowValues, null);
} }


public int getPageNumber() { public int getPageNumber() {
return _overflow; return _overflow;
} }


public Object[] getRowValues() {
return _rowValues;
}
public void possiblyInvalidate(int modifiedPageNumber, public void possiblyInvalidate(int modifiedPageNumber,
ByteBuffer modifiedBuffer) { ByteBuffer modifiedBuffer) {
_rowBufferH.possiblyInvalidate(modifiedPageNumber, _rowBufferH.possiblyInvalidate(modifiedPageNumber,

+ 18
- 9
test/src/java/com/healthmarketscience/jackcess/DatabaseTest.java Bestand weergeven

Object[] row3 = createTestRow("Tim3"); Object[] row3 = createTestRow("Tim3");
Table table = db.getTable("Test"); Table table = db.getTable("Test");
table.addRows(Arrays.asList(row1, row2, row3)); table.addRows(Arrays.asList(row1, row2, row3));

assertRowCount(3, table);
table.reset(); table.reset();
table.getNextRow(); table.getNextRow();
table.getNextRow(); table.getNextRow();
assertEquals("Tim1", outRow.get("A")); assertEquals("Tim1", outRow.get("A"));
outRow = table.getNextRow(); outRow = table.getNextRow();
assertEquals("Tim3", outRow.get("A")); assertEquals("Tim3", outRow.get("A"));
assertRowCount(2, table);


// test multi row delete/add // test multi row delete/add
db = create(); db = create();
table.addRow(row); table.addRow(row);
} }
row[3] = 1974; row[3] = 1974;
assertEquals(10, countRows(table));
assertRowCount(10, table);
table.reset(); table.reset();
table.getNextRow(); table.getNextRow();
table.deleteCurrentRow(); table.deleteCurrentRow();
assertEquals(9, countRows(table));
assertRowCount(9, table);
table.reset(); table.reset();
table.getNextRow(); table.getNextRow();
table.deleteCurrentRow(); table.deleteCurrentRow();
assertEquals(8, countRows(table));
assertRowCount(8, table);
table.reset(); table.reset();
for (int i = 0; i < 8; i++) { for (int i = 0; i < 8; i++) {
table.getNextRow(); table.getNextRow();
} }
table.deleteCurrentRow(); table.deleteCurrentRow();
assertEquals(7, countRows(table));
assertRowCount(7, table);
table.addRow(row); table.addRow(row);
assertEquals(8, countRows(table));
assertRowCount(8, table);
table.reset(); table.reset();
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
table.getNextRow(); table.getNextRow();
} }
table.deleteCurrentRow(); table.deleteCurrentRow();
assertEquals(7, countRows(table));
assertRowCount(7, table);
table.reset(); table.reset();
assertEquals(2, table.getNextRow().get("D")); assertEquals(2, table.getNextRow().get("D"));
} }
new ArrayList<Object>(row.values())); new ArrayList<Object>(row.values()));


table.reset(); table.reset();
assertEquals(7, countRows(table));
assertRowCount(7, table);
} }


String str = builder.toString(); String str = builder.toString();
return str; return str;
} }

static void assertRowCount(int expectedRowCount, Table table)
throws Exception
{
assertEquals(expectedRowCount, countRows(table));
assertEquals(expectedRowCount, table.getRowCount());
}
static int countRows(Table table) throws Exception { static int countRows(Table table) throws Exception {
int rtn = 0; int rtn = 0;
for(Map<String, Object> row : table) { for(Map<String, Object> row : table) {

+ 40
- 0
test/src/java/com/healthmarketscience/jackcess/IndexTest.java Bestand weergeven

} }
} }


public void testEntryDeletion() throws Exception {
File srcFile = new File("test/data/test.mdb");
File dbFile = File.createTempFile("databaseTest", ".mdb");
dbFile.deleteOnExit();
copyFile(srcFile, dbFile);

Table table = Database.open(dbFile).getTable("Table1");

for(int i = 0; i < 10; ++i) {
table.addRow("foo" + i, "bar" + i, (byte)42 + i, (short)53 + i, 13 * i,
(6.7d / i), null, null, true);
}
table.reset();
assertRowCount(12, table);

for(Index index : table.getIndexes()) {
assertEquals(12, index.getEntryCount());
}

table.reset();
table.getNextRow();
table.getNextRow();
table.deleteCurrentRow();
table.getNextRow();
table.deleteCurrentRow();
table.getNextRow();
table.getNextRow();
table.deleteCurrentRow();
table.getNextRow();
table.getNextRow();
table.getNextRow();
table.deleteCurrentRow();

table.reset();
assertRowCount(8, table);

for(Index index : table.getIndexes()) {
assertEquals(8, index.getEntryCount());
}
}
} }

Laden…
Annuleren
Opslaan