aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/changes/changes.xml6
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/Index.java5
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/ColumnImpl.java6
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/FKEnforcer.java35
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/IndexData.java14
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/IndexImpl.java21
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/RelationshipImpl.java7
-rw-r--r--src/main/java/com/healthmarketscience/jackcess/impl/TableImpl.java2
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;