Author: tomdz
Date: Thu Jan 25 23:30:40 2007
New Revision: 500166
URL: http://svn.apache.org/viewvc?view=rev&rev=500166
Log:
Changedthe write-to-XML algorithm slightly to produce better organized XML
Modified:
db/ddlutils/trunk/src/java/org/apache/ddlutils/io/DatabaseDataIO.java
db/ddlutils/trunk/src/test/org/apache/ddlutils/io/RoundtripTestBase.java
db/ddlutils/trunk/src/test/org/apache/ddlutils/io/TestMisc.java
Modified: db/ddlutils/trunk/src/java/org/apache/ddlutils/io/DatabaseDataIO.java
URL:
http://svn.apache.org/viewvc/db/ddlutils/trunk/src/java/org/apache/ddlutils/io/DatabaseDataIO.java?view=diff&rev=500166&r1=500165&r2=500166
==============================================================================
--- db/ddlutils/trunk/src/java/org/apache/ddlutils/io/DatabaseDataIO.java
(original)
+++ db/ddlutils/trunk/src/java/org/apache/ddlutils/io/DatabaseDataIO.java Thu
Jan 25 23:30:40 2007
@@ -28,8 +28,12 @@
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.collections.map.ListOrderedMap;
import org.apache.ddlutils.DdlUtilsException;
import org.apache.ddlutils.Platform;
import org.apache.ddlutils.model.Column;
@@ -365,89 +369,173 @@
*/
public void writeDataToXML(Platform platform, Database model, DataWriter
writer)
{
- Table[] tables = new Table[1];
-
registerConverters(writer.getConverterConfiguration());
- // TODO: An advanced algorithm could be employed here that writes
objects
- // related by foreign keys, in the correct order
- StringBuffer query = new StringBuffer();
+ // TODO: An advanced algorithm could be employed here that writes
individual
+ // objects related by foreign keys, in the correct order
+ List tables = sortTables(model.getTables());
writer.writeDocumentStart();
- for (int tableIdx = 0; tableIdx < model.getTableCount(); tableIdx++)
+ for (Iterator it = tables.iterator(); it.hasNext();)
{
- tables[0] = (Table)model.getTable(tableIdx);
- query.setLength(0);
- query.append("SELECT ");
+ writeDataForTableToXML(platform, model, (Table)it.next(), writer);
+ }
+ writer.writeDocumentEnd();
+ }
- Connection connection = null;
- String schema = null;
+ /**
+ * Sorts the given table according to their foreign key order.
+ *
+ * @param tables The tables
+ * @return The sorted tables
+ */
+ private List sortTables(Table[] tables)
+ {
+ ArrayList result = new ArrayList();
+ HashSet processed = new HashSet();
+ ListOrderedMap pending = new ListOrderedMap();
- if (_determineSchema)
+ for (int idx = 0; idx < tables.length; idx++)
+ {
+ Table table = tables[idx];
+
+ if (table.getForeignKeyCount() == 0)
{
- try
- {
- // TODO: Remove this once we have full support for schemas
- connection = platform.borrowConnection();
- schema =
platform.getModelReader().determineSchemaOf(connection, _schemaPattern,
tables[0]);
- }
- catch (SQLException ex)
- {
- // ignored
- }
- finally
+ result.add(table);
+ processed.add(table);
+ }
+ else
+ {
+ HashSet waitedFor = new HashSet();
+
+ for (int fkIdx = 0; fkIdx < table.getForeignKeyCount();
fkIdx++)
{
- if (connection != null)
+ Table waitedForTable =
table.getForeignKey(fkIdx).getForeignTable();
+
+ if (!table.equals(waitedForTable))
{
- try
- {
- connection.close();
- }
- catch (SQLException ex)
- {
- // ignored
- }
+ waitedFor.add(waitedForTable);
}
}
+ pending.put(table, waitedFor);
}
+ }
- Column[] columns = tables[0].getColumns();
+ HashSet newProcessed = new HashSet();
- for (int columnIdx = 0; columnIdx < columns.length; columnIdx++)
+ while (!processed.isEmpty() && !pending.isEmpty())
+ {
+ newProcessed.clear();
+ for (Iterator it = pending.entrySet().iterator(); it.hasNext();)
{
- if (columnIdx > 0)
- {
- query.append(",");
- }
- if (platform.isDelimitedIdentifierModeOn())
+ Map.Entry entry = (Map.Entry)it.next();
+ Table table = (Table)entry.getKey();
+ HashSet waitedFor = (HashSet)entry.getValue();
+
+ waitedFor.removeAll(processed);
+ if (waitedFor.isEmpty())
{
-
query.append(platform.getPlatformInfo().getDelimiterToken());
+ it.remove();
+ result.add(table);
+ newProcessed.add(table);
}
- query.append(columns[columnIdx].getName());
- if (platform.isDelimitedIdentifierModeOn())
+ }
+ processed.clear();
+
+ HashSet tmp = processed;
+
+ processed = newProcessed;
+ newProcessed = tmp;
+ }
+ // the remaining are within circular dependencies
+ for (Iterator it = pending.keySet().iterator(); it.hasNext();)
+ {
+ result.add(it.next());
+ }
+ return result;
+ }
+
+ /**
+ * Writes the data contained in a single table to XML.
+ *
+ * @param platform The platform
+ * @param model The database model
+ * @param writer The data writer
+ * @param tableIdx
+ */
+ private void writeDataForTableToXML(Platform platform, Database model,
Table table, DataWriter writer)
+ {
+ Table[] tables = { table };
+ StringBuffer query = new StringBuffer();
+
+ query.append("SELECT ");
+
+ Connection connection = null;
+ String schema = null;
+
+ if (_determineSchema)
+ {
+ try
+ {
+ // TODO: Remove this once we have full support for schemas
+ connection = platform.borrowConnection();
+ schema =
platform.getModelReader().determineSchemaOf(connection, _schemaPattern,
tables[0]);
+ }
+ catch (SQLException ex)
+ {
+ // ignored
+ }
+ finally
+ {
+ if (connection != null)
{
-
query.append(platform.getPlatformInfo().getDelimiterToken());
+ try
+ {
+ connection.close();
+ }
+ catch (SQLException ex)
+ {
+ // ignored
+ }
}
}
- query.append(" FROM ");
- if (platform.isDelimitedIdentifierModeOn())
+ }
+
+ Column[] columns = tables[0].getColumns();
+
+ for (int columnIdx = 0; columnIdx < columns.length; columnIdx++)
+ {
+ if (columnIdx > 0)
{
- query.append(platform.getPlatformInfo().getDelimiterToken());
+ query.append(",");
}
- if (schema != null)
+ if (platform.isDelimitedIdentifierModeOn())
{
- query.append(schema);
- query.append(".");
+ query.append(platform.getPlatformInfo().getDelimiterToken());
}
- query.append(tables[0].getName());
+ query.append(columns[columnIdx].getName());
if (platform.isDelimitedIdentifierModeOn())
{
query.append(platform.getPlatformInfo().getDelimiterToken());
}
-
- writer.write(platform.query(model, query.toString(), tables));
}
- writer.writeDocumentEnd();
+ query.append(" FROM ");
+ if (platform.isDelimitedIdentifierModeOn())
+ {
+ query.append(platform.getPlatformInfo().getDelimiterToken());
+ }
+ if (schema != null)
+ {
+ query.append(schema);
+ query.append(".");
+ }
+ query.append(tables[0].getName());
+ if (platform.isDelimitedIdentifierModeOn())
+ {
+ query.append(platform.getPlatformInfo().getDelimiterToken());
+ }
+
+ writer.write(platform.query(model, query.toString(), tables));
}
/**
Modified:
db/ddlutils/trunk/src/test/org/apache/ddlutils/io/RoundtripTestBase.java
URL:
http://svn.apache.org/viewvc/db/ddlutils/trunk/src/test/org/apache/ddlutils/io/RoundtripTestBase.java?view=diff&rev=500166&r1=500165&r2=500166
==============================================================================
--- db/ddlutils/trunk/src/test/org/apache/ddlutils/io/RoundtripTestBase.java
(original)
+++ db/ddlutils/trunk/src/test/org/apache/ddlutils/io/RoundtripTestBase.java
Thu Jan 25 23:30:40 2007
@@ -301,7 +301,14 @@
value = new BinaryObjectsHelper().deserialize((byte[])value);
}
}
- assertEquals(expected, value);
+ if (expected == null)
+ {
+ assertNull(value);
+ }
+ else
+ {
+ assertEquals(expected, value);
+ }
}
/**
Modified: db/ddlutils/trunk/src/test/org/apache/ddlutils/io/TestMisc.java
URL:
http://svn.apache.org/viewvc/db/ddlutils/trunk/src/test/org/apache/ddlutils/io/TestMisc.java?view=diff&rev=500166&r1=500165&r2=500166
==============================================================================
--- db/ddlutils/trunk/src/test/org/apache/ddlutils/io/TestMisc.java (original)
+++ db/ddlutils/trunk/src/test/org/apache/ddlutils/io/TestMisc.java Thu Jan 25
23:30:40 2007
@@ -458,14 +458,15 @@
assertEquals(12, beans.size());
// this is the order of actual insertion (the fk order)
+ // expected insertion order is: 1, 4, 7, 10, 8, 2, 3, 5, 6, 9, 11, 12
assertEquals(new Integer(1), beans.get(0), "id");
assertNull(((DynaBean)beans.get(0)).get("parent_id"));
assertEquals(new Integer(4), beans.get(1), "id");
assertEquals(new Integer(1), beans.get(1), "parent_id");
- assertEquals(new Integer(10), beans.get(2), "id");
- assertEquals(new Integer(4), beans.get(2), "parent_id");
- assertEquals(new Integer(7), beans.get(3), "id");
- assertEquals(new Integer(1), beans.get(3), "parent_id");
+ assertEquals(new Integer(7), beans.get(2), "id");
+ assertEquals(new Integer(1), beans.get(2), "parent_id");
+ assertEquals(new Integer(10), beans.get(3), "id");
+ assertEquals(new Integer(4), beans.get(3), "parent_id");
assertEquals(new Integer(8), beans.get(4), "id");
assertEquals(new Integer(7), beans.get(4), "parent_id");
assertEquals(new Integer(2), beans.get(5), "id");
@@ -483,4 +484,154 @@
assertEquals(new Integer(12), beans.get(11), "id");
assertEquals(new Integer(11), beans.get(11), "parent_id");
}
+
+ /**
+ * Tests the backup and restore of several tables with complex
relationships with an identity column and a foreign key to
+ * itself while identity override is off.
+ */
+ public void testComplexTableModel() throws Exception
+ {
+ // A: self-reference (A1->A2)
+ // B: self- and foreign-reference (B1->B2|G1, B2->G2)
+ // C: circular reference involving more than one table (C1->D1,C2->D2)
+ // D: foreign-reference to F (D1->F1,D2)
+ // E: isolated table (E1)
+ // F: foreign-reference to C (F1->C2)
+ // G: no references (G1, G2)
+
+ final String modelXml =
+ "<?xml version='1.0' encoding='ISO-8859-1'?>\n"+
+ "<database name='roundtriptest'>\n"+
+ " <table name='A'>\n"+
+ " <column name='pk' type='INTEGER' primaryKey='true'
required='true'/>\n"+
+ " <column name='fk' type='INTEGER' required='false'/>\n"+
+ " <foreign-key name='AtoA' foreignTable='A'>\n"+
+ " <reference local='fk' foreign='pk'/>\n"+
+ " </foreign-key>\n"+
+ " </table>\n"+
+ " <table name='B'>\n"+
+ " <column name='pk' type='INTEGER' primaryKey='true'
required='true'/>\n"+
+ " <column name='fk1' type='INTEGER' required='false'/>\n"+
+ " <column name='fk2' type='INTEGER' required='false'/>\n"+
+ " <foreign-key name='BtoB' foreignTable='B'>\n"+
+ " <reference local='fk1' foreign='pk'/>\n"+
+ " </foreign-key>\n"+
+ " <foreign-key name='BtoG' foreignTable='G'>\n"+
+ " <reference local='fk2' foreign='pk'/>\n"+
+ " </foreign-key>\n"+
+ " </table>\n"+
+ " <table name='C'>\n"+
+ " <column name='pk' type='INTEGER' primaryKey='true'
required='true'/>\n"+
+ " <column name='fk' type='INTEGER' required='false'/>\n"+
+ " <foreign-key name='CtoD' foreignTable='D'>\n"+
+ " <reference local='fk' foreign='pk'/>\n"+
+ " </foreign-key>\n"+
+ " </table>\n"+
+ " <table name='D'>\n"+
+ " <column name='pk' type='INTEGER' primaryKey='true'
required='true'/>\n"+
+ " <column name='fk' type='INTEGER' required='false'/>\n"+
+ " <foreign-key name='DtoF' foreignTable='F'>\n"+
+ " <reference local='fk' foreign='pk'/>\n"+
+ " </foreign-key>\n"+
+ " </table>\n"+
+ " <table name='E'>\n"+
+ " <column name='pk' type='INTEGER' primaryKey='true'
required='true'/>\n"+
+ " </table>\n"+
+ " <table name='F'>\n"+
+ " <column name='pk' type='INTEGER' primaryKey='true'
required='true'/>\n"+
+ " <column name='fk' type='INTEGER' required='false'/>\n"+
+ " <foreign-key name='FtoC' foreignTable='C'>\n"+
+ " <reference local='fk' foreign='pk'/>\n"+
+ " </foreign-key>\n"+
+ " </table>\n"+
+ " <table name='G'>\n"+
+ " <column name='pk' type='INTEGER' primaryKey='true'
required='true'/>\n"+
+ " </table>\n"+
+ "</database>";
+
+ createDatabase(modelXml);
+
+ getPlatform().setIdentityOverrideOn(false);
+
+ // this is the optimal insertion order
+ insertRow("E", new Object[] { new Integer(1) });
+ insertRow("G", new Object[] { new Integer(1) });
+ insertRow("G", new Object[] { new Integer(2) });
+ insertRow("A", new Object[] { new Integer(2), null });
+ insertRow("A", new Object[] { new Integer(1), new Integer(2) });
+ insertRow("B", new Object[] { new Integer(2), null, new
Integer(2) });
+ insertRow("B", new Object[] { new Integer(1), new Integer(2), new
Integer(1) });
+ insertRow("D", new Object[] { new Integer(2), null });
+ insertRow("C", new Object[] { new Integer(2), new Integer(2) });
+ insertRow("F", new Object[] { new Integer(1), new Integer(2) });
+ insertRow("D", new Object[] { new Integer(1), new Integer(1) });
+ insertRow("C", new Object[] { new Integer(1), new Integer(1) });
+
+ StringWriter stringWriter = new StringWriter();
+ DatabaseDataIO dataIO = new DatabaseDataIO();
+
+ dataIO.writeDataToXML(getPlatform(), stringWriter, "UTF-8");
+
+ String dataAsXml = stringWriter.toString();
+
+ // the somewhat optimized order that DdlUtils currently generates is:
+ // E1, G1, G2, A2, A1, B2, B1, C2, C1, D2, D1, F1
+ // note that the order per table is the insertion order above
+ SAXReader reader = new SAXReader();
+ Document testDoc = reader.read(new InputSource(new
StringReader(dataAsXml)));
+ boolean uppercase = false;
+ List rows = testDoc.selectNodes("/*/*");
+ String pkColumnName = "pk";
+
+ assertEquals(12, rows.size());
+ if (!"e".equals(((Element)rows.get(0)).getName()))
+ {
+ assertEquals("E", ((Element)rows.get(0)).getName());
+ uppercase = true;
+ }
+ if (!"pk".equals(((Element)rows.get(0)).attribute(0).getName()))
+ {
+ pkColumnName = pkColumnName.toUpperCase();
+ }
+ assertEquals("1", ((Element)rows.get(0)).attributeValue(pkColumnName));
+ assertEquals(uppercase ? "G" : "g", ((Element)rows.get(1)).getName());
+ assertEquals("1", ((Element)rows.get(1)).attributeValue(pkColumnName));
+ assertEquals(uppercase ? "G" : "g", ((Element)rows.get(2)).getName());
+ assertEquals("2", ((Element)rows.get(2)).attributeValue(pkColumnName));
+ assertEquals(uppercase ? "A" : "a", ((Element)rows.get(3)).getName());
+ assertEquals("2", ((Element)rows.get(3)).attributeValue(pkColumnName));
+ assertEquals(uppercase ? "A" : "a", ((Element)rows.get(4)).getName());
+ assertEquals("1", ((Element)rows.get(4)).attributeValue(pkColumnName));
+
+ assertEquals(uppercase ? "B" : "b", ((Element)rows.get(5)).getName());
+ assertEquals("2", ((Element)rows.get(5)).attributeValue(pkColumnName));
+ assertEquals(uppercase ? "B" : "b", ((Element)rows.get(6)).getName());
+ assertEquals("1", ((Element)rows.get(6)).attributeValue(pkColumnName));
+ assertEquals(uppercase ? "C" : "c", ((Element)rows.get(7)).getName());
+ assertEquals("2", ((Element)rows.get(7)).attributeValue(pkColumnName));
+ assertEquals(uppercase ? "C" : "c", ((Element)rows.get(8)).getName());
+ assertEquals("1", ((Element)rows.get(8)).attributeValue(pkColumnName));
+ assertEquals(uppercase ? "D" : "d", ((Element)rows.get(9)).getName());
+ assertEquals("2", ((Element)rows.get(9)).attributeValue(pkColumnName));
+ assertEquals(uppercase ? "D" : "d", ((Element)rows.get(10)).getName());
+ assertEquals("1",
((Element)rows.get(10)).attributeValue(pkColumnName));
+ assertEquals(uppercase ? "F" : "f", ((Element)rows.get(11)).getName());
+ assertEquals("1",
((Element)rows.get(11)).attributeValue(pkColumnName));
+
+ dropDatabase();
+ createDatabase(modelXml);
+
+ StringReader stringReader = new StringReader(dataAsXml);
+
+ dataIO.writeDataToDatabase(getPlatform(), new Reader[] { stringReader
});
+
+ assertEquals(2, getRows("A").size());
+ assertEquals(2, getRows("B").size());
+ assertEquals(2, getRows("C").size());
+ assertEquals(2, getRows("D").size());
+ assertEquals(1, getRows("E").size());
+ assertEquals(1, getRows("F").size());
+ assertEquals(2, getRows("G").size());
+ }
+
}