diff options
8 files changed, 85 insertions, 11 deletions
diff --git a/src/changes/changes.xml b/src/changes/changes.xml index a887ec3..d98c9b8 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -17,6 +17,12 @@ Allow null values in foreign key fields when enforcing referential integrity. </action> + <action dev="jahlborn" type="update"> + Add support for cascade null on delete relationships. + </action> + <action dev="jahlborn" type="update"> + Add support for the required flag for an index. + </action> </release> <release version="2.1.3" date="2015-12-04"> <action dev="jahlborn" type="fix" system="SourceForge2" issue="127"> diff --git a/src/main/java/com/healthmarketscience/jackcess/Index.java b/src/main/java/com/healthmarketscience/jackcess/Index.java index 0e04650..3b35b70 100644 --- a/src/main/java/com/healthmarketscience/jackcess/Index.java +++ b/src/main/java/com/healthmarketscience/jackcess/Index.java @@ -70,6 +70,11 @@ public interface Index public boolean isUnique(); /** + * Whether or not values are required for index columns. + */ + public boolean isRequired(); + + /** * Convenience method for constructing a new CursorBuilder for this Index. */ public CursorBuilder newCursor(); diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java index 00ce9c7..8db672f 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java @@ -113,10 +113,10 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { public static final byte HYPERLINK_FLAG_MASK = (byte)0x80; /** - * mask for the unknown bit (possible "can be null"?) + * mask for the "is updatable" field bit * @usage _advanced_field_ */ - public static final byte UNKNOWN_FLAG_MASK = (byte)0x02; + public static final byte UPDATABLE_FLAG_MASK = (byte)0x02; // some other flags? // 0x10: replication related field (or hidden?) @@ -1283,7 +1283,7 @@ public class ColumnImpl implements Column, Comparable<ColumnImpl> { * Constructs a byte containing the flags for this column. */ private static byte getColumnBitFlags(ColumnBuilder col) { - byte flags = UNKNOWN_FLAG_MASK; + byte flags = UPDATABLE_FLAG_MASK; if(!col.isVariableLength()) { flags |= FIXED_LEN_FLAG_MASK; } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/FKEnforcer.java b/src/main/java/com/healthmarketscience/jackcess/impl/FKEnforcer.java index 470035d..d2d4c50 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/FKEnforcer.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/FKEnforcer.java @@ -55,6 +55,7 @@ final class FKEnforcer private List<Joiner> _primaryJoinersChkDel; private List<Joiner> _primaryJoinersDoUp; private List<Joiner> _primaryJoinersDoDel; + private List<Joiner> _primaryJoinersDoNull; private List<Joiner> _secondaryJoiners; FKEnforcer(TableImpl table) { @@ -91,6 +92,7 @@ final class FKEnforcer _primaryJoinersChkDel = new ArrayList<Joiner>(1); _primaryJoinersDoUp = new ArrayList<Joiner>(1); _primaryJoinersDoDel = new ArrayList<Joiner>(1); + _primaryJoinersDoNull = new ArrayList<Joiner>(1); _secondaryJoiners = new ArrayList<Joiner>(1); for(IndexImpl idx : _table.getIndexes()) { @@ -106,6 +108,8 @@ final class FKEnforcer } if(ref.isCascadeDeletes()) { _primaryJoinersDoDel.add(joiner); + } else if(ref.isCascadeNullOnDelete()) { + _primaryJoinersDoNull.add(joiner); } else { _primaryJoinersChkDel.add(joiner); } @@ -208,11 +212,17 @@ final class FKEnforcer requireNoSecondaryValues(joiner, row); } - // lastly, delete from the tables for which we are the primary table in + // next, delete from the tables for which we are the primary table in // the relationship for(Joiner joiner : _primaryJoinersDoDel) { joiner.deleteRows(row); } + + // lastly, null the tables for which we are the primary table in + // the relationship + for(Joiner joiner : _primaryJoinersDoNull) { + nullSecondaryValues(joiner, row); + } } private static void requirePrimaryValues(Joiner joiner, Object[] row) @@ -264,6 +274,29 @@ final class FKEnforcer } } + private static void nullSecondaryValues(Joiner joiner, Object[] oldFromRow) + throws IOException + { + IndexCursor toCursor = joiner.getToCursor(); + List<? extends Index.Column> fromCols = joiner.getColumns(); + List<? extends Index.Column> toCols = joiner.getToIndex().getColumns(); + Object[] toRow = new Object[joiner.getToTable().getColumnCount()]; + + for(Iterator<Row> iter = joiner.findRows(oldFromRow) + .setColumnNames(Collections.<String>emptySet()) + .iterator(); iter.hasNext(); ) { + iter.next(); + + // create update row for "to" table + Arrays.fill(toRow, Column.KEEP_VALUE); + for(int i = 0; i < fromCols.size(); ++i) { + toCols.get(i).getColumn().setRowValue(toRow, null); + } + + toCursor.updateCurrentRow(toRow); + } + } + private boolean anyUpdates(Object[] oldRow, Object[] newRow) { for(ColumnImpl col : _cols) { if(!MATCHER.matches(_table, col.getName(), diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java b/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java index ec0712c..3cff2e7 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java @@ -79,7 +79,7 @@ public class IndexData { public static final byte UNIQUE_INDEX_FLAG = (byte)0x01; public static final byte IGNORE_NULLS_INDEX_FLAG = (byte)0x02; - public static final byte SPECIAL_INDEX_FLAG = (byte)0x08; // set on MSysACEs and MSysAccessObjects indexes, purpose unknown + public static final byte REQUIRED_INDEX_FLAG = (byte)0x08; public static final byte UNKNOWN_INDEX_FLAG = (byte)0x80; // always seems to be set on indexes in access 2000+ private static final int MAGIC_INDEX_NUMBER = 1923; @@ -317,6 +317,13 @@ public class IndexData { } /** + * Whether or not values are required in the columns. + */ + public boolean isRequired() { + return((_indexFlags & REQUIRED_INDEX_FLAG) != 0); + } + + /** * Returns the Columns for this index (unmodifiable) */ public List<ColumnDescriptor> getColumns() { @@ -569,10 +576,10 @@ public class IndexData { // nothing to do return change; } - if(isBackingPrimaryKey() && (nullCount > 0)) { + if((nullCount > 0) && (isBackingPrimaryKey() || isRequired())) { throw new ConstraintViolationException(withErrorContext( "Null value found in row " + Arrays.asList(row) + - " for primary key index")); + " for primary key or required index")); } // make sure we've parsed the entries @@ -979,6 +986,7 @@ public class IndexData { .append("isBackingPrimaryKey", isBackingPrimaryKey()) .append("isUnique", isUnique()) .append("ignoreNulls", shouldIgnoreNulls()) + .append("isRequired", isRequired()) .append("columns", _columns) .append("initialized", _initialized); if(_initialized) { diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/IndexImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/IndexImpl.java index 25556cb..f3fe868 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/IndexImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/IndexImpl.java @@ -49,6 +49,9 @@ public class IndexImpl implements Index, Comparable<IndexImpl> private static final byte CASCADE_UPDATES_FLAG = (byte)1; /** flag for indicating that deletes should cascade in a foreign key index */ private static final byte CASCADE_DELETES_FLAG = (byte)1; + /** flag for indicating that deletes should cascade a null in a foreign key + index */ + private static final byte CASCADE_NULL_FLAG = (byte)2; /** index table type for the "primary" table in a foreign key index */ private static final byte PRIMARY_TABLE_TYPE = (byte)1; @@ -90,8 +93,9 @@ public class IndexImpl implements Index, Comparable<IndexImpl> (relIndexNumber != INVALID_INDEX_NUMBER)) { _reference = new ForeignKeyReference( relIndexType, relIndexNumber, relTablePageNumber, - (cascadeUpdatesFlag == CASCADE_UPDATES_FLAG), - (cascadeDeletesFlag == CASCADE_DELETES_FLAG)); + ((cascadeUpdatesFlag & CASCADE_UPDATES_FLAG) != 0), + ((cascadeDeletesFlag & CASCADE_DELETES_FLAG) != 0), + ((cascadeDeletesFlag & CASCADE_NULL_FLAG) != 0)); } else { _reference = null; } @@ -207,6 +211,10 @@ public class IndexImpl implements Index, Comparable<IndexImpl> return getIndexData().isUnique(); } + public boolean isRequired() { + return getIndexData().isRequired(); + } + public List<IndexData.ColumnDescriptor> getColumns() { return getIndexData().getColumns(); } @@ -374,16 +382,18 @@ public class IndexImpl implements Index, Comparable<IndexImpl> private final int _otherTablePageNumber; private final boolean _cascadeUpdates; private final boolean _cascadeDeletes; + private final boolean _cascadeNull; public ForeignKeyReference( byte tableType, int otherIndexNumber, int otherTablePageNumber, - boolean cascadeUpdates, boolean cascadeDeletes) + boolean cascadeUpdates, boolean cascadeDeletes, boolean cascadeNull) { _tableType = tableType; _otherIndexNumber = otherIndexNumber; _otherTablePageNumber = otherTablePageNumber; _cascadeUpdates = cascadeUpdates; _cascadeDeletes = cascadeDeletes; + _cascadeNull = cascadeNull; } public byte getTableType() { @@ -410,6 +420,10 @@ public class IndexImpl implements Index, Comparable<IndexImpl> return _cascadeDeletes; } + public boolean isCascadeNullOnDelete() { + return _cascadeNull; + } + @Override public String toString() { return CustomToStringStyle.builder(this) @@ -418,6 +432,7 @@ public class IndexImpl implements Index, Comparable<IndexImpl> .append("isPrimaryTable", isPrimaryTable()) .append("isCascadeUpdates", isCascadeUpdates()) .append("isCascadeDeletes", isCascadeDeletes()) + .append("isCascadeNullOnDelete", isCascadeNullOnDelete()) .toString(); } } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/RelationshipImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/RelationshipImpl.java index bb1d5b9..8775448 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/RelationshipImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/RelationshipImpl.java @@ -40,6 +40,9 @@ public class RelationshipImpl implements Relationship private static final int CASCADE_UPDATES_FLAG = 0x00000100; /** flag indicating cascading deletes (requires referential integrity) */ private static final int CASCADE_DELETES_FLAG = 0x00001000; + /** flag indicating cascading null on delete (requires referential + integrity) */ + private static final int CASCADE_NULL_FLAG = 0x00002000; /** flag indicating left outer join */ private static final int LEFT_OUTER_JOIN_FLAG = 0x01000000; /** flag indicating right outer join */ @@ -113,6 +116,10 @@ public class RelationshipImpl implements Relationship return hasFlag(CASCADE_DELETES_FLAG); } + public boolean cascadeNullOnDelete() { + return hasFlag(CASCADE_NULL_FLAG); + } + public boolean isLeftOuterJoin() { return hasFlag(LEFT_OUTER_JOIN_FLAG); } diff --git a/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java b/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java index 541b27b..85991cb 100644 --- a/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java +++ b/src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java @@ -216,7 +216,7 @@ public class TableImpl implements Table * @param name Table name */ protected TableImpl(DatabaseImpl database, ByteBuffer tableBuffer, - int pageNumber, String name, int flags) + int pageNumber, String name, int flags) throws IOException { _database = database; |