[ 
https://issues.apache.org/jira/browse/DERBY-4160?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=13905475#comment-13905475
 ] 

Knut Anders Hatlen commented on DERBY-4160:
-------------------------------------------

It looks like this is a race condition in the handling of the 
SYS.SYSSTATEMENTS.INITIALLY_COMPILABLE column. We have seen other race 
conditions in the handling of this column before, see for example DERBY-2584.

The column tells whether a stored prepared statement is compiled when it's 
created, or if it has to be compiled later. In practice, this means it should 
be true for trigger statements, and false for meta-data statements (because 
meta-data statements are added to the database so early in the database 
creation or upgrade that there is no LanguageConnectionContext available yet, 
and they cannot be compiled).

However, once a meta-data statement has been compiled, the value of the column 
is switched from false to true. So it doesn't actually tell whether the SPS was 
initially compilable, but rather whether it has been compiled at least once. 
This is used by DataDictionaryImpl.updateSPS() to decide whether it should 
update the existing SPS or create it from scratch.

What happens in the repro, is: Two threads attempt to execute the same 
meta-data query. They both read the SPSDescriptor from the dictionary, and they 
both see that INITIALLY_COMPILABLE is false because the meta-data query has not 
been executed before. Both of them compile the query, and both of them try to 
store the SPS. Because of locking in the system tables, one of them has to wait 
for the other to complete before it goes ahead and stores it. Since it had 
previously seen that INITIALLY_COMPILABLE was false, it gets confused when it 
finds that the SPS is already in the database, and throws the above mentioned 
exception "ERROR X0Y68: Column 'PARAM1' already exists."

I suppose we could add more logic to synchronize between the two threads to 
avoid the race condition. But I think the best solution would be to change the 
use of INITIALLY_COMPILABLE so that its value represents what its name 
suggests: whether or not the statement was initially compilable. Now, since 
this means INITIALLY_COMPILABLE won't change during the lifetime of the SPS, it 
also means that there won't be any race conditions when accessing it.

This change means that DataDictionaryImpl.updateSPS() can no longer tell 
whether or not the SPS is already stored in the database based on that column. 
But that information is not strictly needed. It is currently used in order to 
decide whether the existing SPS should be updated or a new one created. We 
could instead change the code to always replace the existing one (that is, 
delete the existing one if one exists, and create a new one). This change will 
actually simplify this part of the code significantly, since we get a shared 
code path for the first compilation of an SPS and subsequent recompilations, 
whereas they currently have two separate code paths. Additionally, the 
redefinition of INITIALLY_COMPILABLE probably fixes DERBY-2584 as well, so that 
we can remove the current workaround that we have for that bug.

> getMetaData().getIndexInfo crashes with "ERROR X0Y68: Column 'PARAM1' already 
> exists."
> --------------------------------------------------------------------------------------
>
>                 Key: DERBY-4160
>                 URL: https://issues.apache.org/jira/browse/DERBY-4160
>             Project: Derby
>          Issue Type: Bug
>          Components: SQL
>    Affects Versions: 10.4.2.0
>         Environment: FreeBSD java 1.6.0, 64-Bit Server VM; DataNucleus JDO
>            Reporter: ArtemGr
>            Assignee: Knut Anders Hatlen
>              Labels: derby_triage10_5_2
>         Attachments: D4160.java
>
>
> The following code in DataNucleus:
> rs = conn.getMetaData().getIndexInfo(catalogName, schemaName, tableName, 
> false,
> true);
> triggers an Exception (http://gist.github.com/95679):
> Caused by: java.sql.SQLException: Column 'PARAM1' already exists.
>         at 
> org.apache.derby.impl.jdbc.SQLExceptionFactory.getSQLException(SQLExceptionFactory.java:45)
>         at 
> org.apache.derby.impl.jdbc.SQLExceptionFactory40.wrapArgsForTransportAcrossDRDA(SQLExceptionFactory40.java:119)
>         at 
> org.apache.derby.impl.jdbc.SQLExceptionFactory40.getSQLException(SQLExceptionFactory40.java:70)
>         ... 105 more
> Caused by: ERROR X0Y68: Column 'PARAM1' already exists.
>         at 
> org.apache.derby.iapi.error.StandardException.newException(StandardException.java:303)
>         at 
> org.apache.derby.impl.sql.catalog.DataDictionaryImpl.duplicateDescriptorException(DataDictionaryImpl.java:1678)
>         at 
> org.apache.derby.impl.sql.catalog.DataDictionaryImpl.addDescriptor(DataDictionaryImpl.java:1662)
>         at 
> org.apache.derby.impl.sql.catalog.DataDictionaryImpl.addSPSParams(DataDictionaryImpl.java:3682)
>         at 
> org.apache.derby.impl.sql.catalog.DataDictionaryImpl.updateSPS(DataDictionaryImpl.java:3830)
>         at 
> org.apache.derby.iapi.sql.dictionary.SPSDescriptor.updateSYSSTATEMENTS(SPSDescriptor.java:1112)
>         at 
> org.apache.derby.iapi.sql.dictionary.SPSDescriptor.getPreparedStatement(SPSDescriptor.java:736)
>         at 
> org.apache.derby.iapi.sql.dictionary.SPSDescriptor.getPreparedStatement(SPSDescriptor.java:642)
>         at 
> org.apache.derby.impl.sql.compile.ExecSPSNode.generate(ExecSPSNode.java:177)
>         at 
> org.apache.derby.impl.sql.GenericStatement.prepMinion(GenericStatement.java:447)
>         at 
> org.apache.derby.impl.sql.GenericStatement.prepare(GenericStatement.java:88)
>         at 
> org.apache.derby.impl.sql.conn.GenericLanguageConnectionContext.prepareInternalStatement(GenericLanguageConnectionContext.java:794)
>         at 
> org.apache.derby.impl.jdbc.EmbedPreparedStatement.<init>(EmbedPreparedStatement.java:128)
>         ... 99 more



--
This message was sent by Atlassian JIRA
(v6.1.5#6160)

Reply via email to