Added support for reading the onUpdate and onDelete settings from a live database (for DDLUTILS-75)

git-svn-id: https://svn.apache.org/repos/asf/db/ddlutils/trunk@574795 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/java/database.dtd b/src/java/database.dtd
index 7f5382a..af57679 100644
--- a/src/java/database.dtd
+++ b/src/java/database.dtd
@@ -251,6 +251,7 @@
                    <dt>cascade</dt><dd>Delete the local row.</dd>
                    <dt>setnull</dt><dd>Set the local column to <code>NULL</code> which 
                    effectively removes this specific foreign key relationship.</dd>
+                   <dt>setdefault</dt><dd>Set the local column to its default value.</dd>
                    <dt>restrict</dt><dd>Different databases may interpret this value
                    differently, but usually it is synonymous with <code>none</code>.</dd>
                    <dt>none</dt><dd>The value of the local column remains unchanged.</dd>
@@ -259,8 +260,8 @@
 <!ATTLIST foreign-key
   foreignTable CDATA #REQUIRED
   name         CDATA #IMPLIED
-  onUpdate     (cascade | setnull | restrict | none) "none"
-  onDelete     (cascade | setnull | restrict | none) "none"
+  onUpdate     (cascade | setnull | setdefault | restrict | none) "none"
+  onDelete     (cascade | setnull | setdefault | restrict | none) "none"
 >
 
 <!--
diff --git a/src/java/org/apache/ddlutils/model/CascadeActionEnum.java b/src/java/org/apache/ddlutils/model/CascadeActionEnum.java
index 22ed94b..c129f18 100644
--- a/src/java/org/apache/ddlutils/model/CascadeActionEnum.java
+++ b/src/java/org/apache/ddlutils/model/CascadeActionEnum.java
@@ -33,27 +33,34 @@
  */
 public class CascadeActionEnum extends ValuedEnum
 {
-    /** The integer value for the enum value for a cascading change. */
-    public static final int VALUE_CASCADE  = 1;
-    /** The integer value for the enum value for a set-null change. */
-    public static final int VALUE_SETNULL  = 2;
-    /** The integer value for the enum value for a restrict change. */
-    public static final int VALUE_RESTRICT = 3;
-    /** The integer value for the enum value for no-change. */
-    public static final int VALUE_NONE     = 4;
+    /** The integer value for the enum value for a cascade action. */
+    public static final int VALUE_CASCADE     = 1;
+    /** The integer value for the enum value for a set-null action. */
+    public static final int VALUE_SET_NULL    = 2;
+    /** The integer value for the enum value for a set-null action. */
+    public static final int VALUE_SET_DEFAULT = 3;
+    /** The integer value for the enum value for a restrict action. */
+    public static final int VALUE_RESTRICT    = 4;
+    /** The integer value for the enum value for no-action. */
+    public static final int VALUE_NONE        = 5;
 
-    /** The enum value for a cascade action which directs the database to change the value
-        of local column to the new value of the referenced column when it changes. */
-    public static final CascadeActionEnum CASCADE  = new CascadeActionEnum("cascade",  VALUE_CASCADE);
-    /** The enum value for a cascade action which directs the database to set the local
-        column to null when the referenced column changes. */
-    public static final CascadeActionEnum SETNULL  = new CascadeActionEnum("setnull",  VALUE_SETNULL);
+    /** The enum value for a cascade action which directs the database to apply the change to 
+        the referenced table also to this table. E.g. if the referenced row is deleted, then
+        the local one will also be deleted when this value is used for the onDelete action. */
+    public static final CascadeActionEnum CASCADE     = new CascadeActionEnum("cascade",     VALUE_CASCADE);
+    /** The enum value for a cascade action which directs the database to set the local columns
+        referenced by the foreign key to null when the referenced row changes/is deleted. */
+    public static final CascadeActionEnum SET_NULL    = new CascadeActionEnum("setnull",     VALUE_SET_NULL);
+    /** The enum value for a cascade action which directs the database to set the local columns
+        referenced by the foreign key to the default value when the referenced row changes/is deleted. */
+    public static final CascadeActionEnum SET_DEFAULT = new CascadeActionEnum("setdefault",  VALUE_SET_DEFAULT);
     /** The enum value for a cascade action which directs the database to restrict the change
-        changes to the referenced column. The interpretation of this is database-dependent. */
-    public static final CascadeActionEnum RESTRICT = new CascadeActionEnum("restrict", VALUE_RESTRICT);
-    /** The enum value for a cascade action which directs the database to take do nothing
-        to the local column when the value of the referenced column changes. */
-    public static final CascadeActionEnum NONE     = new CascadeActionEnum("none",     VALUE_NONE);
+        changes to the referenced column. The interpretation of this is database-dependent, but it is
+        usually the same as {@link #NONE}. */
+    public static final CascadeActionEnum RESTRICT    = new CascadeActionEnum("restrict",    VALUE_RESTRICT);
+    /** The enum value for the cascade action that directs the database to not change the local column
+        when the value of the referenced column changes, only check the foreign key constraint. */
+    public static final CascadeActionEnum NONE        = new CascadeActionEnum("none",        VALUE_NONE);
 
     /** Version id for this class as relevant for serialization. */
     private static final long serialVersionUID = -6378050861446415790L;
diff --git a/src/java/org/apache/ddlutils/platform/JdbcModelReader.java b/src/java/org/apache/ddlutils/platform/JdbcModelReader.java
index 8037afa..599a2bc 100644
--- a/src/java/org/apache/ddlutils/platform/JdbcModelReader.java
+++ b/src/java/org/apache/ddlutils/platform/JdbcModelReader.java
@@ -42,6 +42,7 @@
 import org.apache.commons.logging.LogFactory;
 import org.apache.ddlutils.Platform;
 import org.apache.ddlutils.PlatformInfo;
+import org.apache.ddlutils.model.CascadeActionEnum;
 import org.apache.ddlutils.model.Column;
 import org.apache.ddlutils.model.Database;
 import org.apache.ddlutils.model.ForeignKey;
@@ -228,6 +229,8 @@
         result.add(new MetaDataColumnDescriptor("FKTABLE_NAME",  Types.VARCHAR));
         result.add(new MetaDataColumnDescriptor("KEY_SEQ",       Types.TINYINT, new Short((short)0)));
         result.add(new MetaDataColumnDescriptor("FK_NAME",       Types.VARCHAR));
+        result.add(new MetaDataColumnDescriptor("UPDATE_RULE",   Types.TINYINT));
+        result.add(new MetaDataColumnDescriptor("DELETE_RULE",   Types.TINYINT));
         result.add(new MetaDataColumnDescriptor("PKCOLUMN_NAME", Types.VARCHAR));
         result.add(new MetaDataColumnDescriptor("FKCOLUMN_NAME", Types.VARCHAR));
 
@@ -779,10 +782,15 @@
         column.setName((String)values.get("COLUMN_NAME"));
         column.setDefaultValue((String)values.get("COLUMN_DEF"));
         column.setTypeCode(((Integer)values.get("DATA_TYPE")).intValue());
-        column.setPrecisionRadix(((Integer)values.get("NUM_PREC_RADIX")).intValue());
 
-        String size  = (String)values.get("COLUMN_SIZE");
-        int    scale = ((Integer)values.get("DECIMAL_DIGITS")).intValue();
+        Integer precision = (Integer)values.get("NUM_PREC_RADIX");
+
+        if (precision != null)
+        {
+            column.setPrecisionRadix(precision.intValue());
+        }
+
+        String size = (String)values.get("COLUMN_SIZE");
 
         if (size == null)
         {
@@ -791,11 +799,14 @@
         // we're setting the size after the precision and radix in case
         // the database prefers to return them in the size value
         column.setSize(size);
-        if (scale != 0)
+
+        Integer scale = (Integer)values.get("DECIMAL_DIGITS");
+
+        if (scale != null)
         {
             // if there is a scale value, set it after the size (which probably did not contain
             // a scale specification)
-            column.setScale(scale);
+            column.setScale(scale.intValue());
         }
         column.setRequired("NO".equalsIgnoreCase(((String)values.get("IS_NULLABLE")).trim()));
         column.setDescription((String)values.get("REMARKS"));
@@ -895,6 +906,8 @@
         {
             fk = new ForeignKey(fkName);
             fk.setForeignTableName((String)values.get("PKTABLE_NAME"));
+            fk.setOnUpdate(convertAction((Short)values.get("UPDATE_RULE")));
+            fk.setOnDelete(convertAction((Short)values.get("DELETE_RULE")));
             knownFks.put(fkName, fk);
         }
 
@@ -910,6 +923,38 @@
     }
 
     /**
+     * Converts the JDBC action value (one of the <code>importKey</code> constants in the
+     * {@link DatabaseMetaData} class) to a {@link CascadeActionEnum}.
+     * 
+     * @param jdbcActionValue The jdbc action value
+     * @return The enum value
+     */
+    protected CascadeActionEnum convertAction(Short jdbcActionValue)
+    {
+        CascadeActionEnum action = CascadeActionEnum.NONE;
+
+        if (jdbcActionValue != null)
+        {
+            switch (jdbcActionValue.shortValue())
+            {
+                case DatabaseMetaData.importedKeyCascade:
+                    action = CascadeActionEnum.CASCADE;
+                    break;
+                case DatabaseMetaData.importedKeySetNull:
+                    action = CascadeActionEnum.SET_NULL;
+                    break;
+                case DatabaseMetaData.importedKeySetDefault:
+                    action = CascadeActionEnum.SET_DEFAULT;
+                    break;
+                case DatabaseMetaData.importedKeyRestrict:
+                    action = CascadeActionEnum.RESTRICT;
+                    break;
+            }
+        }
+        return action;
+    }
+    
+    /**
      * Determines the indices for the indicated table.
      * 
      * @param metaData  The database meta data
diff --git a/src/java/org/apache/ddlutils/platform/MetaDataColumnDescriptor.java b/src/java/org/apache/ddlutils/platform/MetaDataColumnDescriptor.java
index 6efabf9..4a66d01 100644
--- a/src/java/org/apache/ddlutils/platform/MetaDataColumnDescriptor.java
+++ b/src/java/org/apache/ddlutils/platform/MetaDataColumnDescriptor.java
@@ -115,17 +115,28 @@
         }

         if (foundIdx > 0)

         {

+            Object result = null;

+

             switch (_jdbcType)

             {

                 case Types.BIT:

-                    return new Boolean(resultSet.getBoolean(foundIdx));

+                    result = new Boolean(resultSet.getBoolean(foundIdx));

+                    break;

                 case Types.INTEGER:

-                    return new Integer(resultSet.getInt(foundIdx));

+                    result = new Integer(resultSet.getInt(foundIdx));

+                    break;

                 case Types.TINYINT:

-                    return new Short(resultSet.getShort(foundIdx));

+                    result = new Short(resultSet.getShort(foundIdx));

+                    break;

                 default:

-                    return resultSet.getString(foundIdx);

+                    result = resultSet.getString(foundIdx);

+                break;

             }

+            if (resultSet.wasNull())

+            {

+                result = null;

+            }

+            return result;

         }

         else

         {

diff --git a/src/java/org/apache/ddlutils/platform/SqlBuilder.java b/src/java/org/apache/ddlutils/platform/SqlBuilder.java
index 327648a..cce607e 100644
--- a/src/java/org/apache/ddlutils/platform/SqlBuilder.java
+++ b/src/java/org/apache/ddlutils/platform/SqlBuilder.java
@@ -63,6 +63,7 @@
 import org.apache.ddlutils.alteration.RemovePrimaryKeyChange;
 import org.apache.ddlutils.alteration.RemoveTableChange;
 import org.apache.ddlutils.alteration.TableChange;
+import org.apache.ddlutils.model.CascadeActionEnum;
 import org.apache.ddlutils.model.Column;
 import org.apache.ddlutils.model.Database;
 import org.apache.ddlutils.model.ForeignKey;
@@ -2466,11 +2467,11 @@
     {
         for (int idx = 0; idx < table.getForeignKeyCount(); idx++)
         {
-            ForeignKey key = table.getForeignKey(idx);
+            ForeignKey foreignKey = table.getForeignKey(idx);
 
-            if (key.getForeignTableName() == null)
+            if (foreignKey.getForeignTableName() == null)
             {
-                _log.warn("Foreign key table is null for key " + key);
+                _log.warn("Foreign key table is null for key " + foreignKey);
             }
             else
             {
@@ -2478,16 +2479,18 @@
                 if (getPlatformInfo().isEmbeddedForeignKeysNamed())
                 {
                     print("CONSTRAINT ");
-                    printIdentifier(getForeignKeyName(table, key));
+                    printIdentifier(getForeignKeyName(table, foreignKey));
                     print(" ");
                 }
                 print("FOREIGN KEY (");
-                writeLocalReferences(key);
+                writeLocalReferences(foreignKey);
                 print(") REFERENCES ");
-                printIdentifier(getTableName(database.findTable(key.getForeignTableName())));
+                printIdentifier(getTableName(database.findTable(foreignKey.getForeignTableName())));
                 print(" (");
-                writeForeignReferences(key);
+                writeForeignReferences(foreignKey);
                 print(")");
+                writeForeignKeyOnDeleteAction(table, foreignKey);
+                writeForeignKeyOnUpdateAction(table, foreignKey);
             }
         }
     }
@@ -2495,29 +2498,31 @@
     /**
      * Writes a single foreign key constraint using a alter table statement.
      * 
-     * @param database The database model
-     * @param table    The table 
-     * @param key      The foreign key
+     * @param database   The database model
+     * @param table      The table 
+     * @param foreignKey The foreign key
      */
-    protected void writeExternalForeignKeyCreateStmt(Database database, Table table, ForeignKey key) throws IOException
+    protected void writeExternalForeignKeyCreateStmt(Database database, Table table, ForeignKey foreignKey) throws IOException
     {
-        if (key.getForeignTableName() == null)
+        if (foreignKey.getForeignTableName() == null)
         {
-            _log.warn("Foreign key table is null for key " + key);
+            _log.warn("Foreign key table is null for key " + foreignKey);
         }
         else
         {
             writeTableAlterStmt(table);
 
             print("ADD CONSTRAINT ");
-            printIdentifier(getForeignKeyName(table, key));
+            printIdentifier(getForeignKeyName(table, foreignKey));
             print(" FOREIGN KEY (");
-            writeLocalReferences(key);
+            writeLocalReferences(foreignKey);
             print(") REFERENCES ");
-            printIdentifier(getTableName(database.findTable(key.getForeignTableName())));
+            printIdentifier(getTableName(database.findTable(foreignKey.getForeignTableName())));
             print(" (");
-            writeForeignReferences(key);
+            writeForeignReferences(foreignKey);
             print(")");
+            writeForeignKeyOnDeleteAction(table, foreignKey);
+            writeForeignKeyOnUpdateAction(table, foreignKey);
             printEndOfStatement();
         }
     }
@@ -2557,6 +2562,70 @@
     }
 
     /**
+     * Writes the onDelete action for the given foreign key.
+     *  
+     * @param table      The table
+     * @param foreignKey The foreignkey
+     */
+    private void writeForeignKeyOnDeleteAction(Table table, ForeignKey foreignKey) throws IOException
+    {
+        if (foreignKey.getOnDelete() != CascadeActionEnum.NONE)
+        {
+            print(" ON DELETE ");
+            switch (foreignKey.getOnDelete().getValue())
+            {
+                case CascadeActionEnum.VALUE_CASCADE:
+                    print("CASCADE");
+                    break;
+                case CascadeActionEnum.VALUE_SET_NULL:
+                    print("SET NULL");
+                    break;
+                case CascadeActionEnum.VALUE_RESTRICT:
+                    print("RESTRICT");
+                    break;
+                case CascadeActionEnum.VALUE_NONE:
+                    print("NO ACTION");
+                    break;
+                default:
+                    throw new ModelException("Unsupported cascade value '" + foreignKey.getOnDelete().getValue() +
+                                             "' for onDelete in foreign key in table " + table.getName());
+            }
+        }
+    }
+
+    /**
+     * Writes the onDelete action for the given foreign key.
+     *  
+     * @param table      The table
+     * @param foreignKey The foreignkey
+     */
+    private void writeForeignKeyOnUpdateAction(Table table, ForeignKey foreignKey) throws IOException
+    {
+        if (foreignKey.getOnUpdate() != CascadeActionEnum.NONE)
+        {
+            print(" ON UPDATE ");
+            switch (foreignKey.getOnUpdate().getValue())
+            {
+                case CascadeActionEnum.VALUE_CASCADE:
+                    print("CASCADE");
+                    break;
+                case CascadeActionEnum.VALUE_SET_NULL:
+                    print("SET NULL");
+                    break;
+                case CascadeActionEnum.VALUE_RESTRICT:
+                    print("RESTRICT");
+                    break;
+                case CascadeActionEnum.VALUE_NONE:
+                    print("NO ACTION");
+                    break;
+                default:
+                    throw new ModelException("Unsupported cascade value '" + foreignKey.getOnUpdate().getValue() +
+                                             "' for onUpdate in foreign key in table " + table.getName());
+            }
+        }
+    }
+
+    /**
      * Generates the statement to drop a foreignkey constraint from the database using an
      * alter table statement.
      *
diff --git a/src/test/org/apache/ddlutils/io/RoundtripTestBase.java b/src/test/org/apache/ddlutils/io/RoundtripTestBase.java
index f6308d6..0714473 100644
--- a/src/test/org/apache/ddlutils/io/RoundtripTestBase.java
+++ b/src/test/org/apache/ddlutils/io/RoundtripTestBase.java
@@ -35,6 +35,7 @@
 import org.apache.ddlutils.dynabean.SqlDynaBean;

 import org.apache.ddlutils.dynabean.SqlDynaClass;

 import org.apache.ddlutils.dynabean.SqlDynaProperty;

+import org.apache.ddlutils.model.CascadeActionEnum;

 import org.apache.ddlutils.model.Column;

 import org.apache.ddlutils.model.Database;

 import org.apache.ddlutils.model.ForeignKey;

@@ -150,6 +151,26 @@
         getPlatform().insert(getModel(), bean);

     }

 

+

+    /**

+     * Deletes the specified row from the table.

+     * 

+     * @param tableName      The name of the table (case insensitive)

+     * @param pkColumnValues The values for the pk columns in order of definition

+     */

+    protected void deleteRow(String tableName, Object[] pkColumnValues)

+    {

+        Table    table     = getModel().findTable(tableName);

+        DynaBean bean      = getModel().createDynaBeanFor(table);

+        Column[] pkColumns = table.getPrimaryKeyColumns();

+

+        for (int idx = 0; (idx < pkColumns.length) && (idx < pkColumnValues.length); idx++)

+        {

+            bean.set(pkColumns[idx].getName(), pkColumnValues[idx]);

+        }

+        getPlatform().delete(getModel(), bean);

+    }

+

     /**

      * Returns a "SELECT * FROM [table name]" statement. It also takes

      * delimited identifier mode into account if enabled.

@@ -542,6 +563,28 @@
                          getPlatform().getSqlBuilder().shortenName(expected.getForeignTableName().toUpperCase(), getSqlBuilder().getMaxTableNameLength()),

                          getPlatform().getSqlBuilder().shortenName(actual.getForeignTableName().toUpperCase(), getSqlBuilder().getMaxTableNameLength()));

         }

+        if ((expected.getOnUpdate() == CascadeActionEnum.NONE) || (expected.getOnUpdate() == CascadeActionEnum.RESTRICT))

+        {

+            assertTrue("Not the same onUpdate setting in foreign key "+actual.getName()+".",

+                       (actual.getOnUpdate() == CascadeActionEnum.NONE) || (actual.getOnUpdate() == CascadeActionEnum.RESTRICT));

+        }

+        else

+        {

+            assertEquals("Not the same onUpdate setting in foreign key "+actual.getName()+".",

+                         expected.getOnUpdate(),

+                         actual.getOnUpdate());

+        }

+        if ((expected.getOnDelete() == CascadeActionEnum.NONE) || (expected.getOnDelete() == CascadeActionEnum.RESTRICT))

+        {

+            assertTrue("Not the same onDelete setting in foreign key "+actual.getName()+".",

+                       (actual.getOnDelete() == CascadeActionEnum.NONE) || (actual.getOnDelete() == CascadeActionEnum.RESTRICT));

+        }

+        else

+        {

+            assertEquals("Not the same onDelete setting in foreign key "+actual.getName()+".",

+                         expected.getOnDelete(),

+                         actual.getOnDelete());

+        }

         assertEquals("Not the same number of references in foreign key "+actual.getName()+".",

                      expected.getReferenceCount(),

                      actual.getReferenceCount());

diff --git a/src/test/org/apache/ddlutils/io/TestConstraints.java b/src/test/org/apache/ddlutils/io/TestConstraints.java
index e2fb5f4..a55f5e8 100644
--- a/src/test/org/apache/ddlutils/io/TestConstraints.java
+++ b/src/test/org/apache/ddlutils/io/TestConstraints.java
@@ -19,6 +19,8 @@
  * under the License.

  */

 

+import java.util.List;

+

 import org.apache.commons.lang.StringUtils;

 import org.apache.ddlutils.model.Database;

 import org.apache.ddlutils.platform.sybase.SybasePlatform;

@@ -439,4 +441,115 @@
 

         performConstraintsTest(modelXml, true);

     }

+

+    /**

+     * Tests two tables with a foreign key with a restrict onDelete action. 

+     */

+    public void testForeignKeyWithOnDeleteRestrict()

+    {

+        final String modelXml = 

+            "<?xml version='1.0' encoding='ISO-8859-1'?>\n"+

+            "<database name='roundtriptest'>\n"+

+            "  <table name='roundtrip_1'>\n"+

+            "    <column name='pk' type='INTEGER' primaryKey='true' required='true'/>\n"+

+            "  </table>\n"+

+            "  <table name='roundtrip_2'>\n"+

+            "    <column name='pk' type='INTEGER' primaryKey='true' required='true'/>\n"+

+            "    <column name='avalue' type='INTEGER' required='true'/>\n"+

+            "    <foreign-key foreignTable='roundtrip_1' onDelete='restrict'>\n"+

+            "      <reference local='avalue' foreign='pk'/>\n"+

+            "    </foreign-key>\n"+

+            "  </table>\n"+

+            "</database>";

+

+        performConstraintsTest(modelXml, true);

+    }

+

+    /**

+     * Tests two tables with a foreign key with a cascade onDelete action. 

+     */

+    public void testForeignKeyWithOnDeleteCascade()

+    {

+        final String modelXml = 

+            "<?xml version='1.0' encoding='ISO-8859-1'?>\n"+

+            "<database name='roundtriptest'>\n"+

+            "  <table name='roundtrip_1'>\n"+

+            "    <column name='pk' type='INTEGER' primaryKey='true' required='true'/>\n"+

+            "  </table>\n"+

+            "  <table name='roundtrip_2'>\n"+

+            "    <column name='pk' type='INTEGER' primaryKey='true' required='true'/>\n"+

+            "    <column name='avalue' type='INTEGER' required='true'/>\n"+

+            "    <foreign-key foreignTable='roundtrip_1' onDelete='cascade'>\n"+

+            "      <reference local='avalue' foreign='pk'/>\n"+

+            "    </foreign-key>\n"+

+            "  </table>\n"+

+            "</database>";

+

+        performConstraintsTest(modelXml, true);

+

+        insertRow("roundtrip_1", new Object[] { new Integer(1) });

+        insertRow("roundtrip_2", new Object[] { new Integer(5), new Integer(1) });

+

+        List beansTable1 = getRows("roundtrip_1");

+        List beansTable2 = getRows("roundtrip_2");

+

+        assertEquals(1, beansTable1.size());

+        assertEquals(1, beansTable2.size());

+        assertEquals(new Integer(1), beansTable1.get(0), "pk");

+        assertEquals(new Integer(5), beansTable2.get(0), "pk");

+        assertEquals(new Integer(1), beansTable2.get(0), "avalue");

+

+        deleteRow("roundtrip_1", new Object[] { new Integer(1) });

+

+        beansTable1 = getRows("roundtrip_1");

+        beansTable2 = getRows("roundtrip_2");

+

+        assertEquals(0, beansTable1.size());

+        assertEquals(0, beansTable2.size());

+    }

+

+    /**

+     * Tests two tables with a foreign key with a cascade onDelete action. 

+     */

+    public void testForeignKeyWithOnDeleteSetNull()

+    {

+        final String modelXml = 

+            "<?xml version='1.0' encoding='ISO-8859-1'?>\n"+

+            "<database name='roundtriptest'>\n"+

+            "  <table name='roundtrip_1'>\n"+

+            "    <column name='pk' type='INTEGER' primaryKey='true' required='true'/>\n"+

+            "  </table>\n"+

+            "  <table name='roundtrip_2'>\n"+

+            "    <column name='pk' type='INTEGER' primaryKey='true' required='true'/>\n"+

+            "    <column name='avalue' type='INTEGER' required='false'/>\n"+

+            "    <foreign-key foreignTable='roundtrip_1' onDelete='setnull'>\n"+

+            "      <reference local='avalue' foreign='pk'/>\n"+

+            "    </foreign-key>\n"+

+            "  </table>\n"+

+            "</database>";

+

+        performConstraintsTest(modelXml, true);

+

+        insertRow("roundtrip_1", new Object[] { new Integer(1) });

+        insertRow("roundtrip_2", new Object[] { new Integer(5), new Integer(1) });

+

+        List beansTable1 = getRows("roundtrip_1");

+        List beansTable2 = getRows("roundtrip_2");

+

+        assertEquals(1, beansTable1.size());

+        assertEquals(1, beansTable2.size());

+        assertEquals(new Integer(1), beansTable1.get(0), "pk");

+        assertEquals(new Integer(5), beansTable2.get(0), "pk");

+        assertEquals(new Integer(1), beansTable2.get(0), "avalue");

+

+        deleteRow("roundtrip_1", new Object[] { new Integer(1) });

+

+        beansTable1 = getRows("roundtrip_1");

+        beansTable2 = getRows("roundtrip_2");

+

+        assertEquals(0, beansTable1.size());

+        assertEquals(1, beansTable2.size());

+        assertEquals(new Integer(5), beansTable2.get(0), "pk");

+        assertEquals((Object)null, beansTable2.get(0), "avalue");

+    }

 }