Repository: eclipse-vertx/vertx-sql-client
Issue: error using prepared statement cache with DB2

When I enable the prepared statement cache with DB2 in the HR test suite, I get an error.

I *think* the root cause is this:

```
[2020-06-02 20:24:03] SEVERE io.vertx.db2client.impl.codec.DB2Encoder                FATAL: Unable to encode command: io.vertx.sqlclient.impl.command.CloseStatementCommand@501e333e
java.util.ConcurrentModificationException
	at java.util.HashMap$ValueSpliterator.forEachRemaining(HashMap.java:1625)
	at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:580)
	at io.vertx.db2client.impl.codec.DB2PreparedStatement.close(DB2PreparedStatement.java:101)
	at io.vertx.db2client.impl.codec.CloseStatementCommandCodec.encode(CloseStatementCommandCodec.java:31)
	at io.vertx.db2client.impl.codec.DB2Encoder.write(DB2Encoder.java:79)
	at io.vertx.db2client.impl.codec.DB2Encoder.write(DB2Encoder.java:63)
	at io.netty.channel.CombinedChannelDuplexHandler.write(CombinedChannelDuplexHandler.java:346)
	at io.netty.channel.AbstractChannelHandlerContext.invokeWrite0(AbstractChannelHandlerContext.java:717)
	at io.netty.channel.AbstractChannelHandlerContext.invokeWrite(AbstractChannelHandlerContext.java:709)
	at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:792)
	at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:702)
	at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:697)
	at io.vertx.sqlclient.impl.SocketConnectionBase.checkPending(SocketConnectionBase.java:177)
	at io.vertx.sqlclient.impl.SocketConnectionBase.schedule(SocketConnectionBase.java:165)
	at io.vertx.db2client.impl.DB2SocketConnection.schedule(DB2SocketConnection.java:70)
	at io.vertx.sqlclient.impl.Connection.schedule(Connection.java:34)
	at io.vertx.sqlclient.impl.cache.PreparedStatementCache.lambda$new$0(PreparedStatementCache.java:38)
	at io.vertx.sqlclient.impl.cache.LruCache.removeEldestEntry(LruCache.java:38)
	at java.util.LinkedHashMap.afterNodeInsertion(LinkedHashMap.java:299)
	at java.util.HashMap.putVal(HashMap.java:663)
	at java.util.HashMap.put(HashMap.java:611)
	at io.vertx.sqlclient.impl.cache.InflightCachingStmtEntry.handle(InflightCachingStmtEntry.java:40)
	at io.vertx.sqlclient.impl.cache.InflightCachingStmtEntry.handle(InflightCachingStmtEntry.java:21)
	at io.vertx.sqlclient.impl.SocketConnectionBase.handleMessage(SocketConnectionBase.java:188)
	at io.vertx.sqlclient.impl.SocketConnectionBase.lambda$init$0(SocketConnectionBase.java:78)
	at io.vertx.core.net.impl.NetSocketImpl.lambda$new$2(NetSocketImpl.java:101)
	at io.vertx.core.streams.impl.InboundBuffer.handleEvent(InboundBuffer.java:237)
	at io.vertx.core.streams.impl.InboundBuffer.write(InboundBuffer.java:127)
	at io.vertx.core.net.impl.NetSocketImpl.handleMessage(NetSocketImpl.java:357)
	at io.vertx.core.impl.ContextImpl.executeTask(ContextImpl.java:366)
	at io.vertx.core.impl.EventLoopContext.execute(EventLoopContext.java:43)
	at io.vertx.core.impl.ContextImpl.executeFromIO(ContextImpl.java:229)
	at io.vertx.core.net.impl.VertxHandler.channelRead(VertxHandler.java:173)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
	at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)
	at io.vertx.db2client.impl.codec.DB2Encoder.lambda$write$0(DB2Encoder.java:75)
	at io.vertx.db2client.impl.codec.PrepareStatementCodec.handleReadyForQuery(PrepareStatementCodec.java:89)
	at io.vertx.db2client.impl.codec.PrepareStatementCodec.handleColumnDefinitionsDecodingCompleted(PrepareStatementCodec.java:101)
	at io.vertx.db2client.impl.codec.PrepareStatementCodec.decodePayload(PrepareStatementCodec.java:80)
	at io.vertx.db2client.impl.codec.DB2Decoder.decodePayload(DB2Decoder.java:80)
	at io.vertx.db2client.impl.codec.DB2Decoder.decode(DB2Decoder.java:53)
	at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:501)
	at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:440)
	at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
	at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
	at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
	at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
	at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
	at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
	at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:714)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:650)
	at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:576)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
	at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
	at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.lang.Thread.run(Thread.java:745)
```

Changed files:
vertx-db2-client/src/main/java/io/vertx/db2client/impl/drda/SqlCode.java
vertx-db2-client/src/main/java/io/vertx/db2client/impl/drda/SectionManager.java
vertx-db2-client/src/main/java/io/vertx/db2client/DB2Exception.java
vertx-db2-client/src/main/java/io/vertx/db2client/impl/drda/NetSqlca.java
vertx-db2-client/src/main/java/io/vertx/db2client/impl/codec/DB2Decoder.java
vertx-db2-client/src/main/java/io/vertx/db2client/impl/codec/DB2PreparedStatement.java
vertx-db2-client/src/main/java/io/vertx/db2client/impl/codec/ExtendedQueryCommandBaseCodec.java

diff --git a/vertx-db2-client/src/main/java/io/vertx/db2client/DB2Exception.java b/vertx-db2-client/src/main/java/io/vertx/db2client/DB2Exception.java
index ec5ac85c..633bd1cd 100644
--- a/vertx-db2-client/src/main/java/io/vertx/db2client/DB2Exception.java
+++ b/vertx-db2-client/src/main/java/io/vertx/db2client/DB2Exception.java
@@ -23,6 +23,12 @@ public class DB2Exception extends RuntimeException {
 
   private final int errorCode;
   private final String sqlState;
+  
+  public DB2Exception(int errorCode, String sqlState) {
+    super("An error occurred with a DB2 operation. SQLCODE=" + errorCode + "  SQLSTATE=" + sqlState);
+    this.errorCode = errorCode;
+    this.sqlState = sqlState;
+  }
 
   public DB2Exception(String message, int errorCode, String sqlState) {
     super(message);
diff --git a/vertx-db2-client/src/main/java/io/vertx/db2client/impl/codec/DB2Decoder.java b/vertx-db2-client/src/main/java/io/vertx/db2client/impl/codec/DB2Decoder.java
index 3dcae962..51850d7f 100644
--- a/vertx-db2-client/src/main/java/io/vertx/db2client/impl/codec/DB2Decoder.java
+++ b/vertx-db2-client/src/main/java/io/vertx/db2client/impl/codec/DB2Decoder.java
@@ -77,17 +77,22 @@ class DB2Decoder extends ByteToMessageDecoder {
       if (LOG.isDebugEnabled())
         LOG.debug("<<< DECODE " + ctx + " (" + payloadLength + " bytes)");
       ctx.decodePayload(payload, payloadLength);
-    } catch (DB2Exception connex) {
-      // A common connection error occurred, so don't bother with a hex dump and
-      // generic error message
-      ctx.completionHandler.handle(CommandResponse.failure(connex));
     } catch (Throwable t) {
-      int i = payload.readerIndex();
-      payload.readerIndex(startIndex);
-      StringBuilder sb = new StringBuilder(
-          "FATAL: Error parsing buffer at index " + i + " / 0x" + Integer.toHexString(i) + "\\n");
-      ByteBufUtil.appendPrettyHexDump(sb, payload);
-      LOG.error(sb.toString(), t);
+      if (LOG.isDebugEnabled()) {
+        if (!(t instanceof DB2Exception) ||
+            (t.getMessage() != null && 
+             t.getMessage().startsWith("An error occurred with a DB2 operation."))) {
+          int i = payload.readerIndex();
+          payload.readerIndex(startIndex);
+          StringBuilder sb = new StringBuilder(
+              "FATAL: Error parsing buffer at index " + i + " / 0x" + Integer.toHexString(i) + "\\n" +
+              "For command " + ctx + "\\n");
+          ByteBufUtil.appendPrettyHexDump(sb, payload);
+          LOG.error(sb.toString(), t);
+        } else {
+          LOG.error("Command failed with: " + t.getMessage() + "\\nCommand=" + ctx, t);
+        }
+      }
       ctx.completionHandler.handle(CommandResponse.failure(t));
     } finally {
       payload.clear();
diff --git a/vertx-db2-client/src/main/java/io/vertx/db2client/impl/codec/DB2PreparedStatement.java b/vertx-db2-client/src/main/java/io/vertx/db2client/impl/codec/DB2PreparedStatement.java
index be55fd86..b9a85f1f 100644
--- a/vertx-db2-client/src/main/java/io/vertx/db2client/impl/codec/DB2PreparedStatement.java
+++ b/vertx-db2-client/src/main/java/io/vertx/db2client/impl/codec/DB2PreparedStatement.java
@@ -15,9 +15,9 @@
  */
 package io.vertx.db2client.impl.codec;
 
-import java.util.HashMap;
 import java.util.Map;
 import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
 
 import io.vertx.core.impl.logging.Logger;
 import io.vertx.core.impl.logging.LoggerFactory;
@@ -37,7 +37,7 @@ class DB2PreparedStatement implements PreparedStatement {
   final DB2RowDesc rowDesc;
   final Section section;
 
-  private final Map<String, QueryInstance> activeQueries = new HashMap<>(4);
+  private final Map<String, QueryInstance> activeQueries = new ConcurrentHashMap<>(4);
 
   public static class QueryInstance {
     final String cursorId;
diff --git a/vertx-db2-client/src/main/java/io/vertx/db2client/impl/codec/ExtendedQueryCommandBaseCodec.java b/vertx-db2-client/src/main/java/io/vertx/db2client/impl/codec/ExtendedQueryCommandBaseCodec.java
index 156d7342..20e9faf8 100644
--- a/vertx-db2-client/src/main/java/io/vertx/db2client/impl/codec/ExtendedQueryCommandBaseCodec.java
+++ b/vertx-db2-client/src/main/java/io/vertx/db2client/impl/codec/ExtendedQueryCommandBaseCodec.java
@@ -114,9 +114,9 @@ abstract class ExtendedQueryCommandBaseCodec<R, C extends ExtendedQueryCommand<R
   @Override
   public String toString() {
     if (isQuery)
-      return super.toString() + ", fetch=" + cmd.fetch();
+      return super.toString() + ", ps=" + statement + ", fetch=" + cmd.fetch();
     else
-      return super.toString();
+      return super.toString() + ", ps=" + statement;
   }
 
 }
diff --git a/vertx-db2-client/src/main/java/io/vertx/db2client/impl/drda/NetSqlca.java b/vertx-db2-client/src/main/java/io/vertx/db2client/impl/drda/NetSqlca.java
index e21dc948..877f3298 100644
--- a/vertx-db2-client/src/main/java/io/vertx/db2client/impl/drda/NetSqlca.java
+++ b/vertx-db2-client/src/main/java/io/vertx/db2client/impl/drda/NetSqlca.java
@@ -156,6 +156,9 @@ public class NetSqlca {
                 throw new DB2Exception("The column '" + errMsg + "' provided does not exist", sqlca.sqlCode_, sqlca.sqlState_);
               else
                 throw new DB2Exception("A column provided does not exist", sqlca.sqlCode_, sqlca.sqlState_);
+            case SqlCode.INCORRECT_NUM_HOST_VARIABLES:
+              throw new DB2Exception("The number of variables that were passed into an OPEN or EXECUTE statement do not match " +
+                                     "the number of parameter markers that were defined in the statement.", sqlca.sqlCode_, sqlca.sqlState_);
             case SqlCode.NULL_CONSTRAINT_VIOLATION:
                 throw new DB2Exception("An attempt was made to INSERT or UPDATE a column that was declared as not nullable with the NULL value: " + errMsg,
                     sqlca.sqlCode_, sqlca.sqlState_);
@@ -200,7 +203,7 @@ public class NetSqlca {
                else
                    throw new DB2Exception("Duplicate keys were detected on the updated table", sqlca.sqlCode_, sqlca.sqlState_);
             default:
-                throw new IllegalStateException("ERROR: " + sqlca.toString());
+                throw new DB2Exception(sqlca.sqlCode_, sqlca.sqlState_);
      }
    }
 
diff --git a/vertx-db2-client/src/main/java/io/vertx/db2client/impl/drda/SectionManager.java b/vertx-db2-client/src/main/java/io/vertx/db2client/impl/drda/SectionManager.java
index ff05fe2d..369a7b03 100644
--- a/vertx-db2-client/src/main/java/io/vertx/db2client/impl/drda/SectionManager.java
+++ b/vertx-db2-client/src/main/java/io/vertx/db2client/impl/drda/SectionManager.java
@@ -49,15 +49,9 @@ public class SectionManager {
     public int sectionsInUse() {
       return pkgs.stream().mapToInt(DB2Package::sectionsInUse).sum();
     }
-
+    
     public Section getSection(String sql) {
-      if (DRDAQueryRequest.isQuery(sql) ||
-          "COMMIT".equalsIgnoreCase(sql) ||
-          "ROLLBACK".equalsIgnoreCase(sql)) {
-        return getDynamicSection();
-      } else {
-        return staticSection;
-      }
+      return getDynamicSection();
     }
 
     private Section getDynamicSection() {
diff --git a/vertx-db2-client/src/main/java/io/vertx/db2client/impl/drda/SqlCode.java b/vertx-db2-client/src/main/java/io/vertx/db2client/impl/drda/SqlCode.java
index 268b205f..f0f99619 100644
--- a/vertx-db2-client/src/main/java/io/vertx/db2client/impl/drda/SqlCode.java
+++ b/vertx-db2-client/src/main/java/io/vertx/db2client/impl/drda/SqlCode.java
@@ -50,6 +50,8 @@ public class SqlCode {
     public static final int OBJECT_NOT_DEFINED = -204;
 
   public static final int COLUMN_DOES_NOT_EXIST = -206;
+  
+  public static final int INCORRECT_NUM_HOST_VARIABLES = -313;
 
   public static final int NULL_CONSTRAINT_VIOLATION = -407;