Added reading and writing of onUpdate and onDelete attributes from/to XML
git-svn-id: https://svn.apache.org/repos/asf/db/ddlutils/trunk@573150 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/java/org/apache/ddlutils/io/DatabaseIO.java b/src/java/org/apache/ddlutils/io/DatabaseIO.java
index 5e296b9..8a09afc 100644
--- a/src/java/org/apache/ddlutils/io/DatabaseIO.java
+++ b/src/java/org/apache/ddlutils/io/DatabaseIO.java
@@ -35,7 +35,7 @@
import javax.xml.stream.XMLStreamReader;
import org.apache.commons.lang.StringUtils;
-import org.apache.ddlutils.DdlUtilsException;
+import org.apache.ddlutils.model.CascadeActionEnum;
import org.apache.ddlutils.model.Column;
import org.apache.ddlutils.model.Database;
import org.apache.ddlutils.model.ForeignKey;
@@ -98,6 +98,10 @@
public static final QName QNAME_ATTRIBUTE_LOCAL = new QName(DDLUTILS_NAMESPACE, "local");
/** Qualified name of the name attribute. */
public static final QName QNAME_ATTRIBUTE_NAME = new QName(DDLUTILS_NAMESPACE, "name");
+ /** Qualified name of the onDelete attribute. */
+ public static final QName QNAME_ATTRIBUTE_ON_DELETE = new QName(DDLUTILS_NAMESPACE, "onDelete");
+ /** Qualified name of the onUpdate attribute. */
+ public static final QName QNAME_ATTRIBUTE_ON_UPDATE = new QName(DDLUTILS_NAMESPACE, "onUpdate");
/** Qualified name of the primaryKey attribute. */
public static final QName QNAME_ATTRIBUTE_PRIMARY_KEY = new QName(DDLUTILS_NAMESPACE, "primaryKey");
/** Qualified name of the required attribute. */
@@ -160,7 +164,7 @@
* @param filename The model file name
* @return The database model
*/
- public Database read(String filename) throws DdlUtilsException
+ public Database read(String filename) throws DdlUtilsXMLException
{
try
{
@@ -168,7 +172,7 @@
}
catch (IOException ex)
{
- throw new DdlUtilsException(ex);
+ throw new DdlUtilsXMLException(ex);
}
}
@@ -178,7 +182,7 @@
* @param file The model file
* @return The database model
*/
- public Database read(File file) throws DdlUtilsException
+ public Database read(File file) throws DdlUtilsXMLException
{
try
{
@@ -186,7 +190,7 @@
}
catch (IOException ex)
{
- throw new DdlUtilsException(ex);
+ throw new DdlUtilsXMLException(ex);
}
}
@@ -196,7 +200,7 @@
* @param reader The reader that returns the model XML
* @return The database model
*/
- public Database read(Reader reader) throws DdlUtilsException
+ public Database read(Reader reader) throws DdlUtilsXMLException
{
try
{
@@ -204,7 +208,7 @@
}
catch (XMLStreamException ex)
{
- throw new DdlUtilsException(ex);
+ throw new DdlUtilsXMLException(ex);
}
}
@@ -214,7 +218,7 @@
* @param source The input source
* @return The database model
*/
- public Database read(InputSource source) throws DdlUtilsException
+ public Database read(InputSource source) throws DdlUtilsXMLException
{
try
{
@@ -222,7 +226,7 @@
}
catch (XMLStreamException ex)
{
- throw new DdlUtilsException(ex);
+ throw new DdlUtilsXMLException(ex);
}
}
@@ -246,7 +250,7 @@
* @param xmlReader The reader
* @return The database model
*/
- private Database read(XMLStreamReader xmlReader) throws DdlUtilsException
+ private Database read(XMLStreamReader xmlReader) throws DdlUtilsXMLException
{
Database model = null;
@@ -266,11 +270,11 @@
}
catch (IOException ex)
{
- throw new DdlUtilsException(ex);
+ throw new DdlUtilsXMLException(ex);
}
catch (XMLStreamException ex)
{
- throw new DdlUtilsException(ex);
+ throw new DdlUtilsXMLException(ex);
}
if (model != null)
{
@@ -483,6 +487,14 @@
{
foreignKey.setName(xmlReader.getAttributeValue(idx));
}
+ else if (isSameAs(attrQName, QNAME_ATTRIBUTE_ON_UPDATE))
+ {
+ foreignKey.setOnUpdate(getAttributeValueAsCascadeEnum(xmlReader, idx));
+ }
+ else if (isSameAs(attrQName, QNAME_ATTRIBUTE_ON_DELETE))
+ {
+ foreignKey.setOnDelete(getAttributeValueAsCascadeEnum(xmlReader, idx));
+ }
}
readReferenceElements(xmlReader, foreignKey);
consumeRestOfElement(xmlReader);
@@ -707,7 +719,7 @@
* @param attributeIdx The index of the attribute
* @return The attribute's value as a boolean
*/
- private boolean getAttributeValueAsBoolean(XMLStreamReader xmlReader, int attributeIdx) throws DdlUtilsException
+ private boolean getAttributeValueAsBoolean(XMLStreamReader xmlReader, int attributeIdx) throws DdlUtilsXMLException
{
String value = xmlReader.getAttributeValue(attributeIdx);
@@ -721,7 +733,30 @@
}
else
{
- throw new DdlUtilsException("Illegal boolean value '" + value +"' for attribute " + xmlReader.getAttributeLocalName(attributeIdx));
+ throw new DdlUtilsXMLException("Illegal boolean value '" + value +"' for attribute " + xmlReader.getAttributeLocalName(attributeIdx));
+ }
+ }
+
+ /**
+ * Returns the value of the indicated attribute of the current element as a boolean.
+ * If the value is not a valid boolean, then an exception is thrown.
+ *
+ * @param xmlReader The xml reader
+ * @param attributeIdx The index of the attribute
+ * @return The attribute's value as a boolean
+ */
+ private CascadeActionEnum getAttributeValueAsCascadeEnum(XMLStreamReader xmlReader, int attributeIdx) throws DdlUtilsXMLException
+ {
+ String value = xmlReader.getAttributeValue(attributeIdx);
+ CascadeActionEnum enumValue = value == null ? null : CascadeActionEnum.getEnum(value.toLowerCase());
+
+ if (enumValue == null)
+ {
+ throw new DdlUtilsXMLException("Illegal boolean value '" + value +"' for attribute " + xmlReader.getAttributeLocalName(attributeIdx));
+ }
+ else
+ {
+ return enumValue;
}
}
@@ -795,7 +830,7 @@
}
catch (Exception ex)
{
- throw new DdlUtilsException(ex);
+ throw new DdlUtilsXMLException(ex);
}
}
@@ -917,6 +952,14 @@
writeElementStart(xmlWriter, QNAME_ELEMENT_FOREIGN_KEY);
writeAttribute(xmlWriter, QNAME_ATTRIBUTE_FOREIGN_TABLE, foreignKey.getForeignTableName());
writeAttribute(xmlWriter, QNAME_ATTRIBUTE_NAME, foreignKey.getName());
+ if (foreignKey.getOnUpdate() != CascadeActionEnum.NONE)
+ {
+ writeAttribute(xmlWriter, QNAME_ATTRIBUTE_ON_UPDATE, foreignKey.getOnUpdate().getName());
+ }
+ if (foreignKey.getOnDelete() != CascadeActionEnum.NONE)
+ {
+ writeAttribute(xmlWriter, QNAME_ATTRIBUTE_ON_DELETE, foreignKey.getOnDelete().getName());
+ }
if (foreignKey.getReferenceCount() > 0)
{
xmlWriter.printlnIfPrettyPrinting();
diff --git a/src/test/org/apache/ddlutils/io/TestDatabaseIO.java b/src/test/org/apache/ddlutils/io/TestDatabaseIO.java
index e84ddff..a35c7db 100644
--- a/src/test/org/apache/ddlutils/io/TestDatabaseIO.java
+++ b/src/test/org/apache/ddlutils/io/TestDatabaseIO.java
@@ -22,6 +22,7 @@
import java.io.StringReader;
import java.io.StringWriter;
import java.sql.Types;
+import java.util.Iterator;
import junit.framework.TestCase;
@@ -1037,6 +1038,344 @@
}
/**
+ * Tests a database model containing foreignkeys with onUpdate values.
+ */
+ public void testForeignkeysWithOnUpdate() throws Exception
+ {
+ StringBuffer modelXml = new StringBuffer();
+
+ modelXml.append("<database name='test'>\n");
+ modelXml.append(" <table name='SomeTable'\n");
+ modelXml.append(" description='Some table'>\n");
+ modelXml.append(" <column name='ID'\n");
+ modelXml.append(" type='VARCHAR'\n");
+ modelXml.append(" size='16'\n");
+ modelXml.append(" primaryKey='true'\n");
+ modelXml.append(" required='true'\n");
+ modelXml.append(" description='The primary key'/>\n");
+ modelXml.append(" </table>\n");
+ modelXml.append(" <table name='AnotherTable'\n");
+ modelXml.append(" description='And another table'>\n");
+ modelXml.append(" <column name='Some_ID'\n");
+ modelXml.append(" type='VARCHAR'\n");
+ modelXml.append(" size='16'\n");
+ modelXml.append(" description='The foreign key'/>\n");
+ for (Iterator it = CascadeActionEnum.iterator(); it.hasNext();)
+ {
+ CascadeActionEnum enumValue = (CascadeActionEnum)it.next();
+
+ modelXml.append(" <foreign-key name='foreignkey ");
+ modelXml.append(enumValue.getName());
+ modelXml.append("' foreignTable='SomeTable' onUpdate='");
+ modelXml.append(enumValue.getName());
+ modelXml.append("'>\n");
+ modelXml.append(" <reference local='Some_ID' foreign='ID'/>\n");
+ modelXml.append(" </foreign-key>\n");
+ }
+ modelXml.append(" </table>\n");
+ modelXml.append("</database>");
+
+ Database model = readModel(modelXml.toString());
+
+ assertEquals("test", model.getName());
+ assertEquals(2, model.getTableCount());
+
+ Table someTable = model.getTable(0);
+
+ assertEquals("SomeTable", "Some table", 1, 1, 0, 0, 0,
+ someTable);
+ assertEquals("ID", Types.VARCHAR, 16, 0, null, "The primary key", null, true, true, false,
+ someTable.getColumn(0));
+
+ Table anotherTable = model.getTable(1);
+
+ assertEquals("AnotherTable", "And another table", 1, 0, 0, CascadeActionEnum.getEnumList().size(), 0,
+ anotherTable);
+ assertEquals("Some_ID", Types.VARCHAR, 16, 0, null, "The foreign key", null, false, false, false,
+ anotherTable.getColumn(0));
+
+ int idx = 0;
+
+ for (Iterator it = CascadeActionEnum.iterator(); it.hasNext(); idx++)
+ {
+ CascadeActionEnum enumValue = (CascadeActionEnum)it.next();
+ ForeignKey fk = anotherTable.getForeignKey(idx);
+
+ assertEquals("foreignkey " + enumValue.getName(), enumValue, CascadeActionEnum.NONE, someTable, 1, fk);
+ assertEquals(anotherTable.getColumn(0), someTable.getColumn(0), fk.getReference(0));
+ }
+
+ modelXml.setLength(0);
+ modelXml.append("<?xml version='1.0' encoding='UTF-8'?>\n");
+ modelXml.append("<database name=\"test\">\n");
+ modelXml.append(" <table name=\"SomeTable\" description=\"Some table\">\n");
+ modelXml.append(" <column name=\"ID\" primaryKey=\"true\" required=\"true\" type=\"VARCHAR\" size=\"16\" autoIncrement=\"false\" description=\"The primary key\" />\n");
+ modelXml.append(" </table>\n");
+ modelXml.append(" <table name=\"AnotherTable\" description=\"And another table\">\n");
+ modelXml.append(" <column name=\"Some_ID\" primaryKey=\"false\" required=\"false\" type=\"VARCHAR\" size=\"16\" autoIncrement=\"false\" description=\"The foreign key\" />\n");
+ for (Iterator it = CascadeActionEnum.iterator(); it.hasNext(); idx++)
+ {
+ CascadeActionEnum enumValue = (CascadeActionEnum)it.next();
+
+ modelXml.append(" <foreign-key foreignTable=\"SomeTable\" name=\"foreignkey ");
+ modelXml.append(enumValue.getName());
+ if (enumValue != CascadeActionEnum.NONE)
+ {
+ modelXml.append("\" onUpdate=\"");
+ modelXml.append(enumValue.getName());
+ }
+ modelXml.append("\">\n");
+ modelXml.append(" <reference local=\"Some_ID\" foreign=\"ID\" />\n");
+ modelXml.append(" </foreign-key>\n");
+ }
+ modelXml.append(" </table>\n");
+ modelXml.append("</database>\n");
+
+ assertEquals(modelXml.toString(), model);
+ }
+
+ /**
+ * Tests a database model containing foreignkeys with onDelete values.
+ */
+ public void testForeignkeysWithOnDelete() throws Exception
+ {
+ StringBuffer modelXml = new StringBuffer();
+
+ modelXml.append("<database name='test'>\n");
+ modelXml.append(" <table name='SomeTable'\n");
+ modelXml.append(" description='Some table'>\n");
+ modelXml.append(" <column name='ID'\n");
+ modelXml.append(" type='VARCHAR'\n");
+ modelXml.append(" size='16'\n");
+ modelXml.append(" primaryKey='true'\n");
+ modelXml.append(" required='true'\n");
+ modelXml.append(" description='The primary key'/>\n");
+ modelXml.append(" </table>\n");
+ modelXml.append(" <table name='AnotherTable'\n");
+ modelXml.append(" description='And another table'>\n");
+ modelXml.append(" <column name='Some_ID'\n");
+ modelXml.append(" type='VARCHAR'\n");
+ modelXml.append(" size='16'\n");
+ modelXml.append(" description='The foreign key'/>\n");
+ for (Iterator it = CascadeActionEnum.iterator(); it.hasNext();)
+ {
+ CascadeActionEnum enumValue = (CascadeActionEnum)it.next();
+
+ modelXml.append(" <foreign-key name='foreignkey ");
+ modelXml.append(enumValue.getName());
+ modelXml.append("' foreignTable='SomeTable' onDelete='");
+ modelXml.append(enumValue.getName());
+ modelXml.append("'>\n");
+ modelXml.append(" <reference local='Some_ID' foreign='ID'/>\n");
+ modelXml.append(" </foreign-key>\n");
+ }
+ modelXml.append(" </table>\n");
+ modelXml.append("</database>");
+
+ Database model = readModel(modelXml.toString());
+
+ assertEquals("test", model.getName());
+ assertEquals(2, model.getTableCount());
+
+ Table someTable = model.getTable(0);
+
+ assertEquals("SomeTable", "Some table", 1, 1, 0, 0, 0,
+ someTable);
+ assertEquals("ID", Types.VARCHAR, 16, 0, null, "The primary key", null, true, true, false,
+ someTable.getColumn(0));
+
+ Table anotherTable = model.getTable(1);
+
+ assertEquals("AnotherTable", "And another table", 1, 0, 0, CascadeActionEnum.getEnumList().size(), 0,
+ anotherTable);
+ assertEquals("Some_ID", Types.VARCHAR, 16, 0, null, "The foreign key", null, false, false, false,
+ anotherTable.getColumn(0));
+
+ int idx = 0;
+
+ for (Iterator it = CascadeActionEnum.iterator(); it.hasNext(); idx++)
+ {
+ CascadeActionEnum enumValue = (CascadeActionEnum)it.next();
+ ForeignKey fk = anotherTable.getForeignKey(idx);
+
+ assertEquals("foreignkey " + enumValue.getName(), CascadeActionEnum.NONE, enumValue, someTable, 1, fk);
+ assertEquals(anotherTable.getColumn(0), someTable.getColumn(0), fk.getReference(0));
+ }
+
+ modelXml.setLength(0);
+ modelXml.append("<?xml version='1.0' encoding='UTF-8'?>\n");
+ modelXml.append("<database name=\"test\">\n");
+ modelXml.append(" <table name=\"SomeTable\" description=\"Some table\">\n");
+ modelXml.append(" <column name=\"ID\" primaryKey=\"true\" required=\"true\" type=\"VARCHAR\" size=\"16\" autoIncrement=\"false\" description=\"The primary key\" />\n");
+ modelXml.append(" </table>\n");
+ modelXml.append(" <table name=\"AnotherTable\" description=\"And another table\">\n");
+ modelXml.append(" <column name=\"Some_ID\" primaryKey=\"false\" required=\"false\" type=\"VARCHAR\" size=\"16\" autoIncrement=\"false\" description=\"The foreign key\" />\n");
+ for (Iterator it = CascadeActionEnum.iterator(); it.hasNext(); idx++)
+ {
+ CascadeActionEnum enumValue = (CascadeActionEnum)it.next();
+
+ modelXml.append(" <foreign-key foreignTable=\"SomeTable\" name=\"foreignkey ");
+ modelXml.append(enumValue.getName());
+ if (enumValue != CascadeActionEnum.NONE)
+ {
+ modelXml.append("\" onDelete=\"");
+ modelXml.append(enumValue.getName());
+ }
+ modelXml.append("\">\n");
+ modelXml.append(" <reference local=\"Some_ID\" foreign=\"ID\" />\n");
+ modelXml.append(" </foreign-key>\n");
+ }
+ modelXml.append(" </table>\n");
+ modelXml.append("</database>\n");
+
+ assertEquals(modelXml.toString(), model);
+ }
+
+ /**
+ * Tests a foreign key with an illegal onUpdate value.
+ */
+ public void testForeignKeyWithIllegalOnUpdateValue()
+ {
+ try
+ {
+ readModel(
+ "<database name='test'>\n" +
+ " <table name='SomeTable'\n" +
+ " description='Some table'>\n" +
+ " <column name='ID'\n" +
+ " type='VARCHAR'\n" +
+ " size='16'\n" +
+ " primaryKey='true'\n" +
+ " required='true'\n" +
+ " description='The primary key'/>\n" +
+ " </table>\n" +
+ " <table name='AnotherTable'\n" +
+ " description='And another table'>\n" +
+ " <column name='Some_ID'\n" +
+ " type='VARCHAR'\n" +
+ " size='16'\n" +
+ " description='The foreign key'/>\n" +
+ " <foreign-key foreignTable='SomeTable' onUpdate='illegal'>\n" +
+ " <reference local='Some_ID' foreign='ID'/>\n" +
+ " </foreign-key>\n" +
+ " </table>\n" +
+ "</database>");
+
+ fail();
+ }
+ catch (DdlUtilsXMLException ex)
+ {}
+ }
+
+ /**
+ * Tests a foreign key with an empty onUpdate value.
+ */
+ public void testForeignKeyWithEmptyOnUpdateValue()
+ {
+ try
+ {
+ readModel(
+ "<database name='test'>\n" +
+ " <table name='SomeTable'\n" +
+ " description='Some table'>\n" +
+ " <column name='ID'\n" +
+ " type='VARCHAR'\n" +
+ " size='16'\n" +
+ " primaryKey='true'\n" +
+ " required='true'\n" +
+ " description='The primary key'/>\n" +
+ " </table>\n" +
+ " <table name='AnotherTable'\n" +
+ " description='And another table'>\n" +
+ " <column name='Some_ID'\n" +
+ " type='VARCHAR'\n" +
+ " size='16'\n" +
+ " description='The foreign key'/>\n" +
+ " <foreign-key foreignTable='SomeTable' onUpdate=''>\n" +
+ " <reference local='Some_ID' foreign='ID'/>\n" +
+ " </foreign-key>\n" +
+ " </table>\n" +
+ "</database>");
+
+ fail();
+ }
+ catch (DdlUtilsXMLException ex)
+ {}
+ }
+
+ /**
+ * Tests a foreign key with an illegal onDelete value.
+ */
+ public void testForeignKeyWithIllegalOnDeleteValue()
+ {
+ try
+ {
+ readModel(
+ "<database name='test'>\n" +
+ " <table name='SomeTable'\n" +
+ " description='Some table'>\n" +
+ " <column name='ID'\n" +
+ " type='VARCHAR'\n" +
+ " size='16'\n" +
+ " primaryKey='true'\n" +
+ " required='true'\n" +
+ " description='The primary key'/>\n" +
+ " </table>\n" +
+ " <table name='AnotherTable'\n" +
+ " description='And another table'>\n" +
+ " <column name='Some_ID'\n" +
+ " type='VARCHAR'\n" +
+ " size='16'\n" +
+ " description='The foreign key'/>\n" +
+ " <foreign-key foreignTable='SomeTable' onDelete='illegal'>\n" +
+ " <reference local='Some_ID' foreign='ID'/>\n" +
+ " </foreign-key>\n" +
+ " </table>\n" +
+ "</database>");
+
+ fail();
+ }
+ catch (DdlUtilsXMLException ex)
+ {}
+ }
+
+ /**
+ * Tests a foreign key with an empty onDelete value.
+ */
+ public void testForeignKeyWithEmptyOnDeleteValue()
+ {
+ try
+ {
+ readModel(
+ "<database name='test'>\n" +
+ " <table name='SomeTable'\n" +
+ " description='Some table'>\n" +
+ " <column name='ID'\n" +
+ " type='VARCHAR'\n" +
+ " size='16'\n" +
+ " primaryKey='true'\n" +
+ " required='true'\n" +
+ " description='The primary key'/>\n" +
+ " </table>\n" +
+ " <table name='AnotherTable'\n" +
+ " description='And another table'>\n" +
+ " <column name='Some_ID'\n" +
+ " type='VARCHAR'\n" +
+ " size='16'\n" +
+ " description='The foreign key'/>\n" +
+ " <foreign-key foreignTable='SomeTable' onDelete=''>\n" +
+ " <reference local='Some_ID' foreign='ID'/>\n" +
+ " </foreign-key>\n" +
+ " </table>\n" +
+ "</database>");
+
+ fail();
+ }
+ catch (DdlUtilsXMLException ex)
+ {}
+ }
+
+ /**
* Tests a foreign key referencing a non-existing table.
*/
public void testForeignKeyReferencingUndefinedTable()