[
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)