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 ka...@apache.org on 2014/02/21 09:34:35 UTC

svn commit: r1570488 - in /db/derby/code/trunk/java: engine/org/apache/derby/iapi/sql/dictionary/ engine/org/apache/derby/impl/sql/catalog/ storeless/org/apache/derby/impl/storeless/ testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/

Author: kahatlen
Date: Fri Feb 21 08:34:35 2014
New Revision: 1570488

URL: http://svn.apache.org/r1570488
Log:
DERBY-4160: getMetaData().getIndexInfo crashes with "ERROR X0Y68: Column 'PARAM1' already exists."

Use a shared code path for adding parameters to SYS.SYSCOLUMNS on the
first compilation and subsequent compilations of a meta-data query.
Previously, the first compilation took a different code path, but that
caused problems if two threads compiled a meta-data query at the same
time, and both threads thought they were first.

Set a savepoint before attempting to write a stored prepared statement
to the system tables in a nested transaction, and roll back to the
savepoint if an error happens. This prevents partially stored prepared
statements from lying around in the system tables after an error.

Modified:
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java
    db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/SPSDescriptor.java
    db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java
    db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java
    db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/DatabaseMetaDataTest.java

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java?rev=1570488&r1=1570487&r2=1570488&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java Fri Feb 21 08:34:35 2014
@@ -1125,18 +1125,13 @@ public interface DataDictionary
 	 * @param spsd	The descriptor to add
 	 * @param tc			The transaction controller
 	 * @param recompile		whether to recompile or invalidate
-	 * @param updateSYSCOLUMNS indicate whether syscolumns needs to be updated
-	 *							or not.
-	 * @param firstCompilation  first time SPS is getting compiled.
 	 *
 	 * @exception StandardException		Thrown on error
 	 */
 	public void	updateSPS(
 			SPSDescriptor		spsd,
 			TransactionController	tc,
-			boolean                 recompile,
-			boolean					updateSYSCOLUMNS,
-			boolean                 firstCompilation)
+            boolean                 recompile)
 						throws StandardException;
 
 	/**

Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/SPSDescriptor.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/SPSDescriptor.java?rev=1570488&r1=1570487&r2=1570488&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/SPSDescriptor.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/SPSDescriptor.java Fri Feb 21 08:34:35 2014
@@ -137,7 +137,7 @@ public class SPSDescriptor extends Uniqu
 	 * Old code - never used.
 	 */
 	private Object			paramDefaults[];
-	private	boolean					initiallyCompilable;
+    private final boolean   initiallyCompilable;
 	private	boolean					lookedUpParams;
 	
 	private UUIDFactory				uuidFactory;
@@ -696,6 +696,8 @@ public class SPSDescriptor extends Uniqu
 
 			if (!lcc.getDataDictionary().isReadOnlyUpgrade()) {
 
+                final String savepoint = lcc.getUniqueSavepointName();
+
 				// First try compiling in a nested transaction so we can 
                 // release the locks after the compilation, and not have them
                 // sit around in the parent transaction. But if we get lock 
@@ -719,6 +721,11 @@ public class SPSDescriptor extends Uniqu
                     // When retrying in the user transaction, we'll wait for
                     // locks if necessary.
                     nestedTC.setNoLockWait(true);
+
+                    // Set a savepoint so that the work in the nested
+                    // transaction can be rolled back on error without
+                    // aborting the parent transaction.
+                    nestedTC.setSavePoint(savepoint, null);
 				}
 				catch (StandardException se)
 				{
@@ -727,12 +734,6 @@ public class SPSDescriptor extends Uniqu
 					nestedTC = null;
 				}
 
-				// DERBY-2584: If the first attempt to compile the query fails,
-				// we need to reset initiallyCompilable to make sure the
-				// prepared plan is fully stored to disk. Save the initial
-				// value here.
-				final boolean compilable = initiallyCompilable;
-
 				try
 				{
 					prepareAndRelease(lcc, null, nestedTC);
@@ -740,7 +741,16 @@ public class SPSDescriptor extends Uniqu
 				}
 				catch (StandardException se)
 				{
-					if (se.isLockTimeout())
+                    if (nestedTC != null)
+                    {
+                        // Roll back to savepoint to undo any work done by
+                        // the nested transaction. We cannot abort the nested
+                        // transaction in order to achieve the same, since
+                        // that would also abort the parent transaction.
+                        nestedTC.rollbackToSavePoint(savepoint, false, null);
+                    }
+
+                    if (nestedTC != null && se.isLockTimeout())
 					{
                         // Locks were set nowait, so a lock timeout here
                         // means that some lock request in the nested 
@@ -748,18 +758,14 @@ public class SPSDescriptor extends Uniqu
                         // with a parent lock would lead to a undetected 
                         // deadlock so must give up trying in the nested
                         // transaction and retry with parent transaction.
-						if (nestedTC != null)
-						{
-                            nestedTC.commit();
-                            nestedTC.destroy();
-                            nestedTC = null;
-						}
+                        nestedTC.commit();
+                        nestedTC.destroy();
+                        nestedTC = null;
 
 						// if we couldn't do this with a nested transaction, 
                         // retry with parent-- we need to wait this time!
                         // Lock conflicts at this point are with other 
                         // transactions, so must wait.
-						initiallyCompilable = compilable;
 						prepareAndRelease(lcc, null, null);
 						updateSYSSTATEMENTS(lcc, RECOMPILE, null);
 					}
@@ -1106,24 +1112,6 @@ public class SPSDescriptor extends Uniqu
 	private void updateSYSSTATEMENTS(LanguageConnectionContext lcc, int mode, TransactionController tc)
 		throws StandardException
 	{
-		boolean					updateSYSCOLUMNS,  recompile;
-		boolean firstCompilation = false;
-		if (mode == RECOMPILE)
-		{
-			recompile = true;
-			updateSYSCOLUMNS = true;
-			if(!initiallyCompilable)
-			{
-				firstCompilation = true;
-				initiallyCompilable = true;
-			}
-		}
-		else
-		{
-			recompile = false;
-			updateSYSCOLUMNS = false;
-		}
-
 		DataDictionary dd = getDataDictionary();
 
 		if (dd.isReadOnlyUpgrade())
@@ -1139,11 +1127,7 @@ public class SPSDescriptor extends Uniqu
 			tc = lcc.getTransactionExecute();
 		}
 
-		dd.updateSPS(this,
-					 tc, 
-					 recompile,
-					 updateSYSCOLUMNS,
-					 firstCompilation);
+        dd.updateSPS(this, tc, (mode == RECOMPILE));
 	}
 
 	/**

Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java?rev=1570488&r1=1570487&r2=1570488&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java Fri Feb 21 08:34:35 2014
@@ -4405,19 +4405,14 @@ public final class	DataDictionaryImpl
 	 * @param spsd	The descriptor to add
 	 * @param tc			The transaction controller
      * @param recompile Whether to recompile or invalidate
-	 * @param updateParamDescriptors If true, will update the
-	 *						parameter descriptors in SYS.SYSCOLUMNS.
-	 * @param firstCompilation  true, if Statement is getting compiled for first
-	 *                          time and SPS was created with NOCOMPILE option.
 	 *
 	 * @exception StandardException		Thrown on error
 	 */
+    @Override
 	public void	updateSPS(
 			SPSDescriptor			spsd,
 			TransactionController	tc,
-			boolean                 recompile,
-			boolean					updateParamDescriptors,
-			boolean                 firstCompilation)
+            boolean                 recompile)
 						throws StandardException
 	{
 		ExecIndexRow				keyRow1 = null;
@@ -4428,23 +4423,13 @@ public final class	DataDictionaryImpl
 		int[] updCols;
 		if (recompile)
 		{
-			if(firstCompilation)
-			{
-				updCols = new int[] {SYSSTATEMENTSRowFactory.SYSSTATEMENTS_VALID,
-						 SYSSTATEMENTSRowFactory.SYSSTATEMENTS_TEXT,
-									 SYSSTATEMENTSRowFactory.SYSSTATEMENTS_LASTCOMPILED,
-									 SYSSTATEMENTSRowFactory.SYSSTATEMENTS_USINGTEXT,
-									 SYSSTATEMENTSRowFactory.SYSSTATEMENTS_CONSTANTSTATE,
-									 SYSSTATEMENTSRowFactory.SYSSTATEMENTS_INITIALLY_COMPILABLE};
-			}else
-			{
-
-				updCols = new int[] {SYSSTATEMENTSRowFactory.SYSSTATEMENTS_VALID,
-						 SYSSTATEMENTSRowFactory.SYSSTATEMENTS_TEXT,
-										 SYSSTATEMENTSRowFactory.SYSSTATEMENTS_LASTCOMPILED,
-										 SYSSTATEMENTSRowFactory.SYSSTATEMENTS_USINGTEXT,
-										 SYSSTATEMENTSRowFactory.SYSSTATEMENTS_CONSTANTSTATE };
-			}
+            updCols = new int[] {
+                SYSSTATEMENTSRowFactory.SYSSTATEMENTS_VALID,
+                SYSSTATEMENTSRowFactory.SYSSTATEMENTS_TEXT,
+                SYSSTATEMENTSRowFactory.SYSSTATEMENTS_LASTCOMPILED,
+                SYSSTATEMENTSRowFactory.SYSSTATEMENTS_USINGTEXT,
+                SYSSTATEMENTSRowFactory.SYSSTATEMENTS_CONSTANTSTATE,
+            };
 		}
 		else 
 		{
@@ -4480,11 +4465,9 @@ public final class	DataDictionaryImpl
 					 tc);
 
 
-		/*
-		** If we don't need to update the parameter
-		** descriptors, we are done.
-		*/
-		if (!updateParamDescriptors)
+        // If this is an invalidation request, we don't need to update the
+        // parameter descriptors, so we are done.
+        if (!recompile)
 		{
 			return;
 		}
@@ -4499,58 +4482,11 @@ public final class	DataDictionaryImpl
 			return;
 		}
 
-		if(firstCompilation)
-		{
-			/*beetle:5119, reason for doing add here instead of update
-			 *is with NOCOMPILE option of create statement/boot time SPS,
-			 *SPS statement is not compiled to find out the parameter info.
-			 *Because of the parameter info was not inserted at SPSDescriptor 
-			 *creation time. As this is the first time we are compiling parameter
-			 *infor should be inserted instead of the update.
-			 */
-			addSPSParams(spsd, tc);
-		}
-		else
-		{
-			Object[] parameterDefaults = spsd.getParameterDefaults();
-
-			/* 
-			** Update each column with the new defaults and with
-			** the new datatypes.  It is possible that someone has
-			** done a drop/create on the underlying table and 
-			** changed the type of a column, which has changed
-			** the type of a parameter to our statement.
-			*/
-			int[] columnsToSet = new int[2];
-			columnsToSet[0] = SYSCOLUMNSRowFactory.SYSCOLUMNS_COLUMNDATATYPE;
-			columnsToSet[1] = SYSCOLUMNSRowFactory.SYSCOLUMNS_COLUMNDEFAULT;
-
-			UUID uuid = spsd.getUUID();
-
-			for (int index = 0; index < params.length; index++)
-			{
-				int parameterId = index + 1;
-
-			//RESOLVEAUTOINCREMENT
-				ColumnDescriptor cd = new ColumnDescriptor("PARAM" + parameterId,
-										  parameterId,	// position
-										  params[index],
-										  ((parameterDefaults == null) || // default
-										   (index >= parameterDefaults.length)) ? 
-										  (DataValueDescriptor)null :
-										  (DataValueDescriptor)parameterDefaults[index],
-										  (DefaultInfo) null,
-										  uuid,
-										  (UUID) null,
-										  0, 0, 0);
-										
-				updateColumnDescriptor(cd,
-									   cd.getReferencingUUID(), 
-									   cd.getColumnName(),
-									   columnsToSet, 
-									   tc);
-			}
-		}
+        // Update the parameter descriptors by dropping the existing ones
+        // and recreating them. If this is the first time the SPS is being
+        // compiled, the drop operation will be a no-op.
+        dropAllColumnDescriptors(spsd.getUUID(), tc);
+        addSPSParams(spsd, tc);
 	}
 
 	/**

Modified: db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java?rev=1570488&r1=1570487&r2=1570488&view=diff
==============================================================================
--- db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java (original)
+++ db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java Fri Feb 21 08:34:35 2014
@@ -479,8 +479,7 @@ public class EmptyDictionary implements 
 	}
 
 	public void updateSPS(SPSDescriptor spsd, TransactionController tc,
-			boolean recompile, boolean updateSYSCOLUMNS,
-			boolean firstCompilation) throws StandardException {
+            boolean recompile) throws StandardException {
 		// Auto-generated method stub
 
 	}

Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/DatabaseMetaDataTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/DatabaseMetaDataTest.java?rev=1570488&r1=1570487&r2=1570488&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/DatabaseMetaDataTest.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/jdbcapi/DatabaseMetaDataTest.java Fri Feb 21 08:34:35 2014
@@ -62,6 +62,7 @@ import org.apache.derbyTesting.junit.JDB
 import org.apache.derbyTesting.junit.TestConfiguration;
 //import org.apache.derby.shared.common.reference.JDBC40Translation;
 import org.apache.derbyTesting.functionTests.tests.upgradeTests.Version;
+import org.apache.derbyTesting.functionTests.util.Barrier;
 
 /**
  * Test the DatabaseMetaData api.
@@ -235,6 +236,11 @@ public class DatabaseMetaDataTest extend
             TestConfiguration.singleUseDatabaseDecorator(
                 new DatabaseMetaDataTest("initialCompilationTest")));
 
+        // The test for DERBY-4160 needs a fresh database to ensure that the
+        // meta-data queries haven't already been compiled.
+        suite.addTest(TestConfiguration.singleUseDatabaseDecorator(
+                new DatabaseMetaDataTest("concurrentCompilationTest")));
+
         // Test for DERBY-3693 needs a fresh database to ensure that the size
         // of SYSTABLES is so small that creating a relatively small number of
         // tables will cause the query plan for getTables() to be invalidated.
@@ -331,6 +337,67 @@ public class DatabaseMetaDataTest extend
     }
 
     /**
+     * Test that a meta-data query is compiled and stored correctly even when
+     * there's a lock conflict that causes the first attempt to store it to
+     * stop midway (DERBY-4160). This test needs a fresh database so that the
+     * meta-data calls are not already compiled.
+     */
+    public void concurrentCompilationTest() throws Exception {
+        // Create a barrier that can be used to synchronize the two threads
+        // so they perform the meta-data compilation at the same time.
+        final Barrier barrier = new Barrier(2);
+
+        // Create a thread thread that attempts to compile meta-data queries.
+        final DatabaseMetaData dmd = getDMD();
+        final Exception[] exception = new Exception[1];
+        Thread th = new Thread() {
+            @Override
+            public void run() {
+                try {
+                    concurrentCompilationTestHelper(barrier, dmd);
+                } catch (Exception e) {
+                    exception[0] = e;
+                }
+            }
+        };
+        th.start();
+
+        // At the same time, in the main thread, attempt to compile the same
+        // meta-data queries.
+        Connection c2 = openDefaultConnection();
+        concurrentCompilationTestHelper(barrier, c2.getMetaData());
+        c2.close();
+
+        // Wait until both threads are done.
+        th.join();
+
+        // Check if the helper thread got any exceptions.
+        if (exception[0] != null) {
+            fail("Exception in other thread", exception[0]);
+        }
+
+        // Finally, verify that the two meta-data methods used in the test
+        // are working.
+        testGetBestRowIdentifier();
+        testGetIndexInfo();
+    }
+
+    private void concurrentCompilationTestHelper(
+            Barrier barrier, DatabaseMetaData dmd) throws Exception {
+        // Wait until the other thread is ready to start, so that the
+        // compilation happens at the same time in both threads.
+        barrier.await();
+
+        // Often, but not always, the getIndexInfo() call would fail
+        // in one of the threads with the following error message:
+        // ERROR X0Y68: Column 'PARAM1' already exists.
+        ResultSet rs1 = dmd.getBestRowIdentifier(null, null, "", 0, true);
+        ResultSet rs2 = dmd.getIndexInfo(null, null, "", true, true);
+        JDBC.assertDrainResults(rs1);
+        JDBC.assertDrainResults(rs2);
+    }
+
+    /**
      * Tests that we don't get an internal timeout when a meta-data statement
      * is recompiled because the size of the tables it queries has changed
      * (DERBY-3693). The test must be run on a fresh database, to ensure that