From: James Ahlborn Date: Mon, 10 Mar 2008 03:30:14 +0000 (+0000) Subject: modify Index update support so that it honors the unique and ignoreNulls properties... X-Git-Tag: rel_1_1_13~35 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=b72b6e933630b848966d9e4ca8f4dba3f0a21bdb;p=jackcess.git modify Index update support so that it honors the unique and ignoreNulls properties for the Index git-svn-id: https://svn.code.sf.net/p/jackcess/code/jackcess/trunk@257 f203690c-595d-4dc9-a70b-905162fa7fd2 --- diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 767da5f..4c5b113 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -21,6 +21,10 @@ Fix bug caused by sign extension when reading single-byte row numbers. + + Modify Index update support so that it honors the "unique" and + "ignoreNulls" properties for the Index. + diff --git a/src/java/com/healthmarketscience/jackcess/Index.java b/src/java/com/healthmarketscience/jackcess/Index.java index 8ba49f8..c87f766 100644 --- a/src/java/com/healthmarketscience/jackcess/Index.java +++ b/src/java/com/healthmarketscience/jackcess/Index.java @@ -260,7 +260,10 @@ public class Index implements Comparable { initialize(); return _entries.size(); } - + + /** + * Whether or not the complete index state has been read. + */ public boolean isInitialized() { return _initialized; } @@ -555,11 +558,15 @@ public class Index implements Comparable { public void addRow(Object[] row, RowId rowId) throws IOException { + if(shouldIgnoreNulls() && isNullEntry(row)) { + // nothing to do + return; + } + // make sure we've parsed the entries initialize(); - Entry newEntry = new Entry(row, rowId, _columns, - _table.getMaxColumnCount()); + Entry newEntry = new Entry(row, rowId, this); if(addEntry(newEntry)) { ++_rowCount; ++_modCount; @@ -580,11 +587,15 @@ public class Index implements Comparable { public void deleteRow(Object[] row, RowId rowId) throws IOException { + if(shouldIgnoreNulls() && isNullEntry(row)) { + // nothing to do + return; + } + // make sure we've parsed the entries initialize(); - Entry oldEntry = new Entry(row, rowId, _columns, - _table.getMaxColumnCount()); + Entry oldEntry = new Entry(row, rowId, this); if(removeEntry(oldEntry)) { --_rowCount; ++_modCount; @@ -630,7 +641,7 @@ public class Index implements Comparable { Entry startEntry = new Entry(startRow, (startInclusive ? RowId.FIRST_ROW_ID : RowId.LAST_ROW_ID), - _columns, _table.getMaxColumnCount()); + this); startPos = new Position(FIRST_ENTRY_IDX, startEntry); } Position endPos = LAST_POSITION; @@ -638,7 +649,7 @@ public class Index implements Comparable { Entry endEntry = new Entry(endRow, (endInclusive ? RowId.LAST_ROW_ID : RowId.FIRST_ROW_ID), - _columns, _table.getMaxColumnCount()); + this); endPos = new Position(LAST_ENTRY_IDX, endEntry); } return new EntryCursor(startPos, endPos); @@ -663,11 +674,27 @@ public class Index implements Comparable { /** * Adds an entry to the _entries list, maintaining the order. */ - private boolean addEntry(Entry newEntry) { + private boolean addEntry(Entry newEntry) + throws IOException + { int idx = findEntry(newEntry); if(idx < 0) { // this is a new entry idx = missingIndexToInsertionPoint(idx); + + // determine if the addition of this entry would break the uniqueness + // constraint + if(isUnique() && + (((idx > 0) && + newEntry.equalsEntryBytes(_entries.get(idx - 1))) || + ((idx < _entries.size()) && + newEntry.equalsEntryBytes(_entries.get(idx))))) + { + throw new IOException( + "New entry " + newEntry + + " violates uniqueness constrain for index " + this); + } + _entries.add(idx, newEntry); return true; } @@ -781,6 +808,54 @@ public class Index implements Comparable { } } + /** + * Determines if all values for this index from the given row are + * {@code null}. + */ + private boolean isNullEntry(Object[] values) + { + if(values == null) { + return true; + } + + // annoyingly, the values array could come from different sources, one + // of which will make it a different size than the other. we need to + // handle both situations. + boolean useColNumber = (values.length >= _table.getMaxColumnCount()); + for(ColumnDescriptor col : _columns) { + Object value = values[ + useColNumber ? col.getColumnNumber() : col.getColumnIndex()]; + if(!col.isNullValue(value)) { + return false; + } + } + return true; + } + + /** + * Creates the entry bytes for a row of values. + */ + private byte[] createEntryBytes(Object[] values) throws IOException + { + if(values == null) { + return null; + } + + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + + // annoyingly, the values array could come from different sources, one + // of which will make it a different size than the other. we need to + // handle both situations. + boolean useColNumber = (values.length >= _table.getMaxColumnCount()); + for(ColumnDescriptor col : _columns) { + Object value = values[ + useColNumber ? col.getColumnNumber() : col.getColumnIndex()]; + col.writeValue(value, bout); + } + + return bout.toByteArray(); + } + /** * Flips the first bit in the byte at the given index. */ @@ -964,7 +1039,7 @@ public class Index implements Comparable { */ private static Entry createSpecialEntry(RowId rowId) { try { - return new Entry(null, rowId, null, 0); + return new Entry(null, rowId, null); } catch(IOException e) { // should never happen throw new IllegalStateException(e); @@ -1044,10 +1119,14 @@ public class Index implements Comparable { return getColumn().getName(); } - protected void writeValue(Object value, ByteArrayOutputStream bout) + protected boolean isNullValue(Object value) { + return (value == null); + } + + protected final void writeValue(Object value, ByteArrayOutputStream bout) throws IOException { - if(value == null) { + if(isNullValue(value)) { // write null value bout.write(getNullEntryFlag(isAscending())); return; @@ -1221,21 +1300,19 @@ public class Index implements Comparable { } @Override - protected void writeValue(Object value, ByteArrayOutputStream bout) - throws IOException - { + protected boolean isNullValue(Object value) { // null values are handled as booleans - bout.write( - Column.toBooleanValue(value) ? - (isAscending() ? ASC_BOOLEAN_TRUE : DESC_BOOLEAN_TRUE) : - (isAscending() ? ASC_BOOLEAN_FALSE : DESC_BOOLEAN_FALSE)); + return false; } - + @Override protected void writeNonNullValue(Object value, ByteArrayOutputStream bout) throws IOException { - throw new UnsupportedOperationException("should not be called"); + bout.write( + Column.toBooleanValue(value) ? + (isAscending() ? ASC_BOOLEAN_TRUE : DESC_BOOLEAN_TRUE) : + (isAscending() ? ASC_BOOLEAN_FALSE : DESC_BOOLEAN_FALSE)); } } @@ -1270,14 +1347,6 @@ public class Index implements Comparable { super(column, flags); } - @Override - protected void writeValue(Object value, ByteArrayOutputStream bout) - throws IOException - { - throw new UnsupportedOperationException( - "FIXME cannot write indexes of this type yet"); - } - @Override protected void writeNonNullValue(Object value, ByteArrayOutputStream bout) throws IOException @@ -1304,23 +1373,12 @@ public class Index implements Comparable { * @param rowId rowId in which the row is stored * @param columns map of columns for this index */ - private Entry(Object[] values, RowId rowId, List columns, - int maxTableColumnCount) + private Entry(Object[] values, RowId rowId, Index parent) throws IOException { _rowId = rowId; if(values != null) { - ByteArrayOutputStream bout = new ByteArrayOutputStream(); - // annoyingly, the values array could come from different sources, one - // of which will make it a different size than the other. we need to - // handle both situations. - boolean useColNumber = (values.length >= maxTableColumnCount); - for(ColumnDescriptor col : columns) { - Object value = values[ - useColNumber ? col.getColumnNumber() : col.getColumnIndex()]; - col.writeValue(value, bout); - } - _entryBytes = bout.toByteArray(); + _entryBytes = parent.createEntryBytes(values); _type = ((_rowId.getType() == RowId.Type.NORMAL) ? EntryType.NORMAL : ((_rowId.getType() == RowId.Type.ALWAYS_FIRST) ? @@ -1426,6 +1484,14 @@ public class Index implements Comparable { ((o != null) && (getClass() == o.getClass()) && (compareTo((Entry)o) == 0))); } + + /** + * @return {@code true} iff the entryBytes are equal between this + * Entry and the given Entry + */ + public boolean equalsEntryBytes(Entry o) { + return(BYTE_CODE_COMPARATOR.compare(_entryBytes, o._entryBytes) == 0); + } public int compareTo(Entry other) { if (this == other) { @@ -1599,8 +1665,7 @@ public class Index implements Comparable { public void beforeEntry(Object[] row) throws IOException { - restorePosition(new Entry(row, RowId.FIRST_ROW_ID, _columns, - _table.getMaxColumnCount())); + restorePosition(new Entry(row, RowId.FIRST_ROW_ID, Index.this)); } /** @@ -1610,8 +1675,7 @@ public class Index implements Comparable { public void afterEntry(Object[] row) throws IOException { - restorePosition(new Entry(row, RowId.LAST_ROW_ID, _columns, - _table.getMaxColumnCount())); + restorePosition(new Entry(row, RowId.LAST_ROW_ID, Index.this)); } /** diff --git a/src/java/com/healthmarketscience/jackcess/IndexCodes.java b/src/java/com/healthmarketscience/jackcess/IndexCodes.java index 2ed63cd..a3c254d 100644 --- a/src/java/com/healthmarketscience/jackcess/IndexCodes.java +++ b/src/java/com/healthmarketscience/jackcess/IndexCodes.java @@ -46,15 +46,11 @@ public class IndexCodes { static final byte END_EXTRA_TEXT = (byte)0x00; - static final byte[] ASC_BOOLEAN_TRUE = - new byte[]{ASC_START_FLAG, (byte)0x00}; - static final byte[] ASC_BOOLEAN_FALSE = - new byte[]{ASC_START_FLAG, (byte)0xFF}; + static final byte ASC_BOOLEAN_TRUE = (byte)0x00; + static final byte ASC_BOOLEAN_FALSE = (byte)0xFF; - static final byte[] DESC_BOOLEAN_TRUE = - new byte[]{DESC_START_FLAG, (byte)0xFF}; - static final byte[] DESC_BOOLEAN_FALSE = - new byte[]{DESC_START_FLAG, (byte)0x00}; + static final byte DESC_BOOLEAN_TRUE = ASC_BOOLEAN_FALSE; + static final byte DESC_BOOLEAN_FALSE = ASC_BOOLEAN_TRUE; // unprintable char is removed from normal text.