You are viewing a plain text version of this content. The canonical link for it is here.
Posted to derby-commits@db.apache.org by da...@apache.org on 2009/08/14 21:12:47 UTC

svn commit: r804327 - in /db/derby/code/branches/10.4/java: engine/org/apache/derby/impl/sql/execute/ testing/org/apache/derbyTesting/functionTests/tests/lang/

Author: dag
Date: Fri Aug 14 19:12:46 2009
New Revision: 804327

URL: http://svn.apache.org/viewvc?rev=804327&view=rev
Log:
DERBY-4330 NullPointerException or assert failure when re-executing PreparedStatement after lock timeout

Backported fix from trunk as:
svn merge -c 804271 https://svn.apache.org/repos/asf/db/derby/code/trunk

Patch derby-4330c fixes this issue. The problem is that when a timeout
happens (or a deadlock), the result set tree for prepared statements
for some queries is partically in a closed, partially in an open
state. (The issue was reported for a join query, but exists for others
queries as well). This causes problems when the result set tree is
being reused, i.e. when the prpared statement is attempted re-executed
after the timeout, since the tree is expected to be fully closed at
that time, cuasing the assert or NPE.

The fix ensures that the tree is left in a fully closed state in such
cases.


Modified:
    db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/DistinctScalarAggregateResultSet.java
    db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/GroupedAggregateResultSet.java
    db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/JoinResultSet.java
    db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/SetOpResultSet.java
    db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/SortResultSet.java
    db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/UnionResultSet.java
    db/derby/code/branches/10.4/java/testing/org/apache/derbyTesting/functionTests/tests/lang/ResultSetsFromPreparedStatementTest.java

Modified: db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/DistinctScalarAggregateResultSet.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/DistinctScalarAggregateResultSet.java?rev=804327&r1=804326&r2=804327&view=diff
==============================================================================
--- db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/DistinctScalarAggregateResultSet.java (original)
+++ db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/DistinctScalarAggregateResultSet.java Fri Aug 14 19:12:46 2009
@@ -138,10 +138,20 @@
 
         source.openCore();
 
-		/*
-		** Load up the sorter because we have something to sort.
-		*/
-		scanController = loadSorter();
+		try {
+			/*
+			** Load up the sorter because we have something to sort.
+			*/
+			scanController = loadSorter();
+		} catch (StandardException e) {
+			// DERBY-4330 Result set tree must be atomically open or
+			// closed for reuse to work (after DERBY-827).
+
+			isOpen = true; // to make close do its thing:
+			try { close(); } catch (StandardException ee) {}
+			throw e;
+		}
+
 		sorted = true;
 
 	    isOpen = true;

Modified: db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/GroupedAggregateResultSet.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/GroupedAggregateResultSet.java?rev=804327&r1=804326&r2=804327&view=diff
==============================================================================
--- db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/GroupedAggregateResultSet.java (original)
+++ db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/GroupedAggregateResultSet.java Fri Aug 14 19:12:46 2009
@@ -158,26 +158,35 @@
 
         source.openCore();
 
-		/* If this is an in-order group by then we do not need the sorter.
-		 * (We can do the aggregation ourselves.)
-		 * We save a clone of the first row so that subsequent next()s
-		 * do not overwrite the saved row.
-		 */
-		if (isInSortedOrder)
-		{
-			currSortedRow = getNextRowFromRS();
-			if (currSortedRow != null)
+		try {
+			/* If this is an in-order group by then we do not need the sorter.
+			 * (We can do the aggregation ourselves.)
+			 * We save a clone of the first row so that subsequent next()s
+			 * do not overwrite the saved row.
+			 */
+			if (isInSortedOrder)
 			{
-				currSortedRow = (ExecIndexRow) currSortedRow.getClone();
-				initializeVectorAggregation(currSortedRow);
+				currSortedRow = getNextRowFromRS();
+				if (currSortedRow != null)
+				{
+					currSortedRow = (ExecIndexRow) currSortedRow.getClone();
+					initializeVectorAggregation(currSortedRow);
+				}
 			}
-		}
-		else
-		{
-			/*
-			** Load up the sorter
-			*/
-			scanController = loadSorter();
+			else
+			{
+				/*
+				** Load up the sorter
+				*/
+				scanController = loadSorter();
+			}
+		} catch (StandardException e) {
+			// DERBY-4330 Result set tree must be atomically open or
+			// closed for reuse to work (after DERBY-827).
+
+			isOpen = true; // to make close do its thing:
+			try { close(); } catch (StandardException ee) {}
+			throw e;
 		}
 
 	    isOpen = true;

Modified: db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/JoinResultSet.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/JoinResultSet.java?rev=804327&r1=804326&r2=804327&view=diff
==============================================================================
--- db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/JoinResultSet.java (original)
+++ db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/JoinResultSet.java Fri Aug 14 19:12:46 2009
@@ -143,14 +143,25 @@
 		if (SanityManager.DEBUG)
 	    	SanityManager.ASSERT( ! isOpen, "JoinResultSet already open");
 
-	    isOpen = true;
 		leftResultSet.openCore();
-		leftRow = leftResultSet.getNextRowCore();
-		if (leftRow != null)
-		{
-			openRight();
-			rowsSeenLeft++;
+
+		try {
+			leftRow = leftResultSet.getNextRowCore();
+			if (leftRow != null)
+			{
+				openRight();
+				rowsSeenLeft++;
+			}
+		} catch (StandardException e) {
+			// DERBY-4330 Result set tree must be atomically open or
+			// closed for reuse to work (after DERBY-827).
+
+			isOpen = true; // to make close work:
+			try { close(); } catch (StandardException ee) {}
+			throw e;
 		}
+
+	    isOpen = true;
 		numOpens++;
 
 		openTime += getElapsedMillis(beginTime);

Modified: db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/SetOpResultSet.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/SetOpResultSet.java?rev=804327&r1=804326&r2=804327&view=diff
==============================================================================
--- db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/SetOpResultSet.java (original)
+++ db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/SetOpResultSet.java Fri Aug 14 19:12:46 2009
@@ -108,15 +108,25 @@
 		if (SanityManager.DEBUG)
 	    	SanityManager.ASSERT( ! isOpen, "SetOpResultSet already open");
 
-        isOpen = true;
         leftSource.openCore();
-        rightSource.openCore();
-        rightInputRow = rightSource.getNextRowCore();
+
+        try {
+            rightSource.openCore();
+            rightInputRow = rightSource.getNextRowCore();
+        } catch (StandardException e) {
+            // DERBY-4330 Result set tree must be atomically open or
+            // closed for reuse to work (after DERBY-827).
+            isOpen = true; // to make close work:
+            try { close(); } catch (StandardException ee) {}
+            throw e;
+        }
+
         if (rightInputRow != null)
         {
             rowsSeenRight++;
         }
 
+        isOpen = true;
 		numOpens++;
 
 		openTime += getElapsedMillis(beginTime);

Modified: db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/SortResultSet.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/SortResultSet.java?rev=804327&r1=804326&r2=804327&view=diff
==============================================================================
--- db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/SortResultSet.java (original)
+++ db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/SortResultSet.java Fri Aug 14 19:12:46 2009
@@ -247,26 +247,35 @@
 
         source.openCore();
 
-		/* If this is an in-order distinct then we do not need the sorter.
-		 * (We filter out the duplicate rows ourselves.)
-		 * We save a clone of the first row so that subsequent next()s
-		 * do not overwrite the saved row.
-		 */
-		if (isInSortedOrder && distinct)
-		{
-			currSortedRow = getNextRowFromRS();
-			if (currSortedRow != null)
+		try {
+			/* If this is an in-order distinct then we do not need the sorter.
+			 * (We filter out the duplicate rows ourselves.)  We save a clone
+			 * of the first row so that subsequent next()s do not overwrite the
+			 * saved row.
+			 */
+			if (isInSortedOrder && distinct)
+			{
+				currSortedRow = getNextRowFromRS();
+
+				if (currSortedRow != null)
+				{
+					currSortedRow = (ExecRow) currSortedRow.getClone();
+				}
+			}
+			else
 			{
-				currSortedRow = (ExecRow) currSortedRow.getClone();
+				/*
+				** Load up the sorter.
+				*/
+				scanController = loadSorter();
+				sorted = true;
 			}
-		}
-		else
-		{
-			/*
-			** Load up the sorter.
-			*/
-			scanController = loadSorter();
-			sorted = true;
+		} catch (StandardException e) {
+			// DERBY-4330 Result set tree must be atomically open or
+			// closed for reuse to work (after DERBY-827).
+			isOpen = true; // to make close do its thing:
+			try { close(); } catch (StandardException ee) {}
+			throw e;
 		}
 
 	    isOpen = true;

Modified: db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/UnionResultSet.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/UnionResultSet.java?rev=804327&r1=804326&r2=804327&view=diff
==============================================================================
--- db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/UnionResultSet.java (original)
+++ db/derby/code/branches/10.4/java/engine/org/apache/derby/impl/sql/execute/UnionResultSet.java Fri Aug 14 19:12:46 2009
@@ -99,8 +99,8 @@
 		if (SanityManager.DEBUG)
 	    	SanityManager.ASSERT( ! isOpen, "UnionResultSet already open");
 
-        isOpen = true;
         source1.openCore();
+        isOpen = true;
 		numOpens++;
 
 		openTime += getElapsedMillis(beginTime);

Modified: db/derby/code/branches/10.4/java/testing/org/apache/derbyTesting/functionTests/tests/lang/ResultSetsFromPreparedStatementTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/branches/10.4/java/testing/org/apache/derbyTesting/functionTests/tests/lang/ResultSetsFromPreparedStatementTest.java?rev=804327&r1=804326&r2=804327&view=diff
==============================================================================
--- db/derby/code/branches/10.4/java/testing/org/apache/derbyTesting/functionTests/tests/lang/ResultSetsFromPreparedStatementTest.java (original)
+++ db/derby/code/branches/10.4/java/testing/org/apache/derbyTesting/functionTests/tests/lang/ResultSetsFromPreparedStatementTest.java Fri Aug 14 19:12:46 2009
@@ -167,6 +167,10 @@
     /** Secondary connection. Used if something needs to be executed in a
      * separate transaction. */
     private Connection c2;
+    private Connection c3;
+
+
+    private static final long DERBY_DEFAULT_TIMEOUT = 60;
 
     /**
      * Creates a String containing an insert statement for the
@@ -452,6 +456,31 @@
         try { s.executeUpdate("drop table emp"); } catch (SQLException e) {}
         try { s.executeUpdate("drop table emp2"); } catch (SQLException e) {}
         try { s.executeUpdate("drop table dept"); } catch (SQLException e) {}
+
+        // DERBY-4330 tables:
+        try {
+            if (c3 != null && !c3.isClosed()) {
+                c3.rollback();
+                c3.close();
+            }
+        } catch (SQLException e) {
+        }
+
+        try { s.executeUpdate(
+                "drop table APP.FILECHANGES"); } catch (SQLException e) {}
+        try { s.executeUpdate(
+                "drop table APP.CHANGESETS"); } catch (SQLException e) {}
+        try { s.executeUpdate(
+                "drop table APP.AUTHORS"); } catch (SQLException e) {}
+        try { s.executeUpdate(
+                "drop table APP.FILES"); } catch (SQLException e) {}
+        try { s.executeUpdate(
+                "drop table APP.REPOSITORIES"); } catch (SQLException e) {}
+        try { s.executeUpdate(
+                "drop table APP.FILECHANGES_2"); } catch (SQLException e) {}
+
+        try { setTimeout(DERBY_DEFAULT_TIMEOUT); } catch (SQLException e) {}
+
         s.close();
         commit();
 
@@ -2132,4 +2161,364 @@
         ps.setMaxRows(2);
         JDBC.assertDrainResults(ps.executeQuery(), 2);
     }
+
+    public void testDerby4330_JoinResultSet()  throws SQLException {
+        setTimeout(1);
+        setSchema("APP");
+        createDerby4330_join_tables();
+
+        PreparedStatement ps = prepareStatement(
+            "SELECT CS.REVISION, A.NAME, CS.TIME, CS.MESSAGE, F.PATH " +
+            "FROM " +
+            "CHANGESETS CS, FILECHANGES FC, " +
+            "           REPOSITORIES R, FILES F, AUTHORS A " +
+            "WHERE " +
+            "F.REPOSITORY = R.ID AND A.REPOSITORY = R.ID AND " +
+            "CS.REPOSITORY = R.ID AND CS.ID = FC.CHANGESET AND " +
+            "F.ID = FC.FILE AND A.ID = CS.AUTHOR AND " +
+            "EXISTS ( " +
+            "SELECT 1 " +
+            "FROM FILES F2 " +
+            "WHERE " +
+            "F2.ID = FC.FILE AND F2.REPOSITORY = R.ID) " +
+            "ORDER BY CS.ID DESC");
+
+        c3 = openDefaultConnection();
+        c3.setAutoCommit(false);
+        Statement stm2 = c3.createStatement();
+        stm2.execute("LOCK TABLE FILECHANGES IN EXCLUSIVE MODE");
+        stm2.close();
+
+        try {
+            ps.executeQuery();
+            fail();
+        } catch (SQLException e) {
+            assertSQLState("Expected timeout", "40XL1", e);
+        }
+
+        c3.rollback();
+        c3.close();
+
+        ResultSet rs = ps.executeQuery();
+        assertTrue(rs.next());
+        assertEquals(rs.getString(2), "xyz"); // name
+        assertFalse(rs.next());
+        ps.close();
+
+    }
+
+
+    public void testDerby4330_UnionResultSet()  throws SQLException {
+        setTimeout(1);
+        setSchema("APP");
+        createDerby4330_union_tables();
+
+        PreparedStatement ps = prepareStatement(
+            "SELECT * FROM (" +
+            "SELECT * FROM FILECHANGES_2  UNION " +
+            "SELECT * FROM FILECHANGES) X"); // locked file last
+
+        PreparedStatement ps_inverse = prepareStatement(
+            "SELECT * FROM (" +
+            "SELECT * FROM FILECHANGES  UNION " + // locked file first
+            "SELECT * FROM FILECHANGES_2) X");
+
+        c3 = openDefaultConnection();
+        c3.setAutoCommit(false);
+        Statement stm2 = c3.createStatement();
+        stm2.execute("LOCK TABLE FILECHANGES IN EXCLUSIVE MODE");
+        stm2.close();
+
+        try {
+            ps.executeQuery();
+            fail();
+        } catch (SQLException e) {
+            assertSQLState("Expected timeout", "40XL1", e);
+        }
+
+        try {
+            ps_inverse.executeQuery();
+            fail();
+        } catch (SQLException e) {
+            assertSQLState("Expected timeout", "40XL1", e);
+        }
+
+        c3.rollback();
+        c3.close();
+
+        ResultSet rs = ps.executeQuery();
+        JDBC.assertFullResultSet(rs, new String[][]{{"1", "1", "1"}});
+
+        rs = ps_inverse.executeQuery();
+        JDBC.assertFullResultSet(rs, new String[][]{{"1", "1", "1"}});
+
+        ps.close();
+        ps_inverse.close();
+
+    }
+
+
+    public void testDerby4330_SetOpResultSet()  throws SQLException {
+        setTimeout(1);
+        setSchema("APP");
+        createDerby4330_union_tables();
+
+        String[] ops = {"EXCEPT", "INTERSECT"};
+        String[][][] opExpectedRs = {null, {{"1", "1", "1"}}};
+
+        for (int i=0; i < 2; i++) {
+            PreparedStatement ps = prepareStatement(
+                "SELECT * FROM (" +
+                "SELECT * FROM FILECHANGES_2 " + ops[i] + " " +
+                // locked file last
+                "SELECT * FROM FILECHANGES) X ORDER BY ID");
+
+            PreparedStatement ps_inverse = prepareStatement(
+                "SELECT * FROM (" +
+                 // locked file first:
+                "SELECT * FROM FILECHANGES " + ops[i] + " " +
+                "SELECT * FROM FILECHANGES_2) X ORDER BY ID");
+
+            c3 = openDefaultConnection();
+            c3.setAutoCommit(false);
+            Statement stm2 = c3.createStatement();
+            stm2.execute("LOCK TABLE FILECHANGES IN EXCLUSIVE MODE");
+            stm2.close();
+
+            try {
+                ps.executeQuery();
+                fail();
+            } catch (SQLException e) {
+                assertSQLState("Expected timeout", "40XL1", e);
+            }
+
+            try {
+                ps_inverse.executeQuery();
+                fail();
+            } catch (SQLException e) {
+                assertSQLState("Expected timeout", "40XL1", e);
+            }
+
+            c3.rollback();
+            c3.close();
+
+            ResultSet rs = ps.executeQuery();
+
+            if (opExpectedRs[i] != null) {
+                JDBC.assertFullResultSet(rs, opExpectedRs[i]);
+            } else {
+                JDBC.assertEmpty(rs);
+            }
+
+            rs = ps_inverse.executeQuery();
+
+            if (opExpectedRs[i] != null) {
+                JDBC.assertFullResultSet(rs, opExpectedRs[i]);
+            } else {
+                JDBC.assertEmpty(rs);
+            }
+
+            ps.close();
+            ps_inverse.close();
+
+        }
+
+    }
+
+
+    public void testDerby4330_GroupedAggregateResultSet()  throws SQLException {
+        setTimeout(1);
+        setSchema("APP");
+        createDerby4330_union_tables();
+
+        PreparedStatement ps = prepareStatement(
+            "SELECT SUM(CHANGESET) from FILECHANGES GROUP BY FILE");
+
+        c3 = openDefaultConnection();
+        c3.setAutoCommit(false);
+        Statement stm2 = c3.createStatement();
+        // Next statement gives an exclusive write lock on a row in FILECHANGES:
+        stm2.execute("INSERT INTO FILECHANGES(FILE,CHANGESET) VALUES (2,2)");
+        stm2.close();
+
+        try {
+            ps.executeQuery();
+            fail();
+        } catch (SQLException e) {
+            assertSQLState("Expected timeout", "40XL1", e);
+        }
+
+        c3.rollback();
+        c3.close();
+
+        ResultSet rs = ps.executeQuery();
+        JDBC.assertFullResultSet(rs, new String[][]{{"1"}});
+
+        ps.close();
+    }
+
+
+    public void testDerby4330_DistinctGroupedAggregateResultSet()
+            throws SQLException
+    {
+        setTimeout(1);
+        setSchema("APP");
+        createDerby4330_union_tables();
+
+        PreparedStatement ps = prepareStatement(
+            "SELECT SUM(DISTINCT CHANGESET) from FILECHANGES GROUP BY FILE");
+
+        c3 = openDefaultConnection();
+        c3.setAutoCommit(false);
+        Statement stm2 = c3.createStatement();
+        // Next statement gives an exclusive write lock on a row in FILECHANGES:
+        stm2.execute("INSERT INTO FILECHANGES(FILE,CHANGESET) VALUES (2,2)");
+        stm2.close();
+
+        try {
+            ps.executeQuery();
+            fail();
+        } catch (SQLException e) {
+            assertSQLState("Expected timeout", "40XL1", e);
+        }
+
+        c3.rollback();
+        c3.close();
+
+        ResultSet rs = ps.executeQuery();
+        JDBC.assertFullResultSet(rs, new String[][]{{"1"}});
+
+        ps.close();
+    }
+
+
+    public void testDerby4330_DistinctScalarAggregateResultSet()
+            throws SQLException
+    {
+        setTimeout(1);
+        setSchema("APP");
+        createDerby4330_union_tables();
+
+        PreparedStatement ps = prepareStatement(
+            "SELECT SUM(DISTINCT CHANGESET) from FILECHANGES");
+
+        c3 = openDefaultConnection();
+        c3.setAutoCommit(false);
+        Statement stm2 = c3.createStatement();
+        // Next statement gives an exclusive write lock on a row in FILECHANGES:
+        stm2.execute("INSERT INTO FILECHANGES(FILE,CHANGESET) VALUES (2,2)");
+        stm2.close();
+
+        try {
+            ps.executeQuery();
+            fail();
+        } catch (SQLException e) {
+            assertSQLState("Expected timeout", "40XL1", e);
+        }
+
+        c3.rollback();
+        c3.close();
+
+        ResultSet rs = ps.executeQuery();
+        JDBC.assertFullResultSet(rs, new String[][]{{"1"}});
+
+        ps.close();
+    }
+
+
+    private void setTimeout(long t) throws SQLException {
+        Statement stm = createStatement();
+        stm.execute("call syscs_util.syscs_set_database_property(" +
+                    "'derby.locks.waitTimeout', '" + t + "')");
+        stm.close();
+    }
+
+
+    private void createDerby4330_join_tables()  throws SQLException {
+        Statement stm = createStatement();
+        stm.execute(
+            "CREATE TABLE REPOSITORIES (" +
+            "ID INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY," +
+            "PATH VARCHAR(32672) UNIQUE NOT NULL)");
+
+        stm.execute(
+            "INSERT INTO REPOSITORIES(PATH) VALUES ('r')");
+
+        stm.execute(
+            "CREATE TABLE FILES (" +
+            "ID INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY," +
+            "PATH VARCHAR(32672) NOT NULL," +
+            "REPOSITORY INT NOT NULL REFERENCES REPOSITORIES" +
+            "    ON DELETE CASCADE," +
+            "UNIQUE (REPOSITORY, PATH))");
+
+        stm.execute(
+            "INSERT INTO FILES(PATH, REPOSITORY) VALUES ('/adsf',1)");
+
+        stm.execute(
+            "CREATE TABLE AUTHORS (" +
+            "ID INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY," +
+            "REPOSITORY INT NOT NULL REFERENCES REPOSITORIES " +
+            "           ON DELETE CASCADE," +
+            "NAME VARCHAR(32672) NOT NULL," +
+            "UNIQUE (REPOSITORY, NAME))");
+
+        stm.execute(
+            "INSERT INTO AUTHORS(REPOSITORY, NAME) VALUES (1, 'xyz')");
+
+        stm.execute(
+            "CREATE TABLE CHANGESETS (" +
+            "ID INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY," +
+            "REPOSITORY INT NOT NULL REFERENCES REPOSITORIES " +
+            "           ON DELETE CASCADE," +
+            "REVISION VARCHAR(1024) NOT NULL," +
+            "AUTHOR INT NOT NULL REFERENCES AUTHORS ON DELETE CASCADE," +
+            "TIME TIMESTAMP NOT NULL," +
+            "MESSAGE VARCHAR(32672) NOT NULL," +
+            "UNIQUE (REPOSITORY, REVISION))");
+
+        stm.execute(
+            "INSERT INTO CHANGESETS(REPOSITORY, REVISION, " +
+            "                       AUTHOR, TIME, MESSAGE)" +
+            " VALUES (1,'',1,CURRENT_TIMESTAMP,'')");
+
+        stm.execute(
+            "CREATE TABLE FILECHANGES (" +
+            "ID INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY," +
+            "FILE INT NOT NULL REFERENCES FILES ON DELETE CASCADE," +
+            "CHANGESET INT NOT NULL REFERENCES CHANGESETS ON DELETE CASCADE," +
+            "UNIQUE (FILE, CHANGESET))");
+
+        stm.execute("INSERT INTO FILECHANGES(FILE,CHANGESET) VALUES (1,1)");
+        stm.close();
+        commit();
+    }
+
+
+    private void createDerby4330_union_tables()  throws SQLException {
+        Statement stm = createStatement();
+        stm.execute("CREATE TABLE FILECHANGES (" +
+                    "ID INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY," +
+                    "FILE INT NOT NULL," +
+                    "CHANGESET INT NOT NULL," +
+                    "UNIQUE (FILE, CHANGESET))");
+
+        stm.execute("CREATE TABLE FILECHANGES_2 (" +
+                    "ID INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY," +
+                    "FILE INT NOT NULL," +
+                    "CHANGESET INT NOT NULL," +
+                    "UNIQUE (FILE, CHANGESET))");
+
+        stm.execute("INSERT INTO FILECHANGES(FILE,CHANGESET) VALUES (1,1)");
+        stm.execute("INSERT INTO FILECHANGES_2(FILE,CHANGESET) VALUES (1,1)");
+        stm.close();
+        commit();
+    }
+
+
+    private void setSchema(String schema) throws SQLException {
+        Statement stm = createStatement();
+        stm.execute("SET SCHEMA " + schema);
+        stm.close();
+    }
 }