@Override
 public JDBCClient getConnection(Handler<AsyncResult<SQLConnection>> handler) {
   Context ctx = vertx.getOrCreateContext();
   exec.execute(
       () -> {
         Future<SQLConnection> res = Future.future();
         try {
           /*
           This can block until a connection is free.
           We don't want to do that while running on a worker as we can enter a deadlock situation as the worker
           might have obtained a connection, and won't release it until it is run again
           There is a general principle here:
           *User code* should be executed on a worker and can potentially block, it's up to the *user* to deal with
           deadlocks that might occur there.
           If the *service code* internally blocks waiting for a resource that might be obtained by *user code*, then
           this can cause deadlock, so the service should ensure it never does this, by executing such code
           (e.g. getConnection) on a different thread to the worker pool.
           We don't want to use the vert.x internal pool for this as the threads might end up all blocked preventing
           other important operations from occurring (e.g. async file access)
           */
           Connection conn = ds.getConnection();
           SQLConnection sconn = new JDBCConnectionImpl(vertx, conn);
           res.complete(sconn);
         } catch (SQLException e) {
           res.fail(e);
         }
         ctx.runOnContext(v -> res.setHandler(handler));
       });
   return this;
 }