docs: @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      String methodName = method.getName();
      switch (methodName) {
        case METHOD_GET_LOCK:
          return this.lock;
        case METHOD_GET_NO_BACKSLASH:
          return listener.noBackslashEscapes();
        case METHOD_GET_CATALOG:
          return listener.getCatalog();
        case METHOD_GET_TIMEOUT:
          return listener.getTimeout();
        case METHOD_VERSION_GREATER_OR_EQUAL:
          return listener.versionGreaterOrEqual((int) args[0], (int) args[1], (int) args[2]);
        case METHOD_SESSION_STATE_AWARE:
          return listener.sessionStateAware();
        case METHOD_IS_EXPLICIT_CLOSED:
          return listener.isExplicitClosed();
        case METHOD_GET_OPTIONS:
          return listener.getUrlParser().getOptions();
        case METHOD_GET_MAJOR_VERSION:
          return listener.getMajorServerVersion();
        case METHOD_GET_SERVER_THREAD_ID:
          return listener.getServerThreadId();
        case METHOD_GET_URLPARSER:
          return listener.getUrlParser();
        case METHOD_GET_PROXY:
          return this;
        case METHOD_IS_CLOSED:
          return listener.isClosed();
        case METHOD_IS_VALID:
          return listener.isValid((int) args[0]);
        case METHOD_PROLOG:
          listener.prolog((long) args[0], (MariaDbConnection) args[2], (MariaDbStatement) args[3]);
          return null;
        case METHOD_EXECUTE_QUERY:
          try {
            this.listener.preExecute();
          } catch (SQLException e) {
            //handle failover only if connection error
            //normal error can be thrown upon reconnection if there was a transaction in progress.
            if (hasToHandleFailover(e)) {
              return handleFailOver(e, method, args, listener.getCurrentProtocol());
            }
          }
          break;
        case METHOD_SET_READ_ONLY:
          this.listener.switchReadOnlyConnection((Boolean) args[0]);
          return null;
        case METHOD_GET_READ_ONLY:
          return this.listener.isReadOnly();
        case METHOD_IN_TRANSACTION:
          return this.listener.inTransaction();
        case METHOD_IS_MASTER_CONNECTION:
          return this.listener.isMasterConnection();
        case METHOD_ABORT:
          this.listener.preAbort();
          return null;
        case METHOD_CLOSED_EXPLICIT:
          this.listener.preClose();
          return null;
        case METHOD_COM_MULTI_PREPARE_EXECUTES:
        case METHOD_EXECUTE_PREPARED_QUERY:
          boolean mustBeOnMaster = (Boolean) args[0];
          ServerPrepareResult serverPrepareResult = (ServerPrepareResult) args[1];
          if (serverPrepareResult != null) {
            if (!mustBeOnMaster && serverPrepareResult.getUnProxiedProtocol().isMasterConnection()
                && !this.listener.hasHostFail()) {
              //PrepareStatement was to be executed on slave, but since a failover was running on master connection. Slave connection is up
              // again, so has to be re-prepared on slave
              try {
                logger.trace("re-prepare query \"{}\" on slave (was "
                    + "temporary on master since failover)", serverPrepareResult.getSql());
                this.listener.rePrepareOnSlave(serverPrepareResult, false);
              } catch (SQLException q) {
                //error during re-prepare, will do executed on master.
              }
            }
            try {
              return listener.invoke(method, args, serverPrepareResult.getUnProxiedProtocol());
            } catch (InvocationTargetException e) {
              if (e.getTargetException() != null) {
                if (e.getTargetException() instanceof SQLException
                    && hasToHandleFailover((SQLException) e.getTargetException())) {
                  return handleFailOver((SQLException) e.getTargetException(), method, args,
                      serverPrepareResult.getUnProxiedProtocol());
                }
                throw e.getTargetException();
              }
              throw e;
            }
          }
          break;
        case METHOD_PROLOG_PROXY:
          try {
            if (args[0] != null) {
              return listener
                  .invoke(method, args, ((ServerPrepareResult) args[0]).getUnProxiedProtocol());
            }
            return null;
          } catch (InvocationTargetException e) {
            if (e.getTargetException() != null) {
              if (e.getTargetException() instanceof SQLException
                  && hasToHandleFailover((SQLException) e.getTargetException())) {
                return handleFailOver((SQLException) e.getTargetException(), method, args,
                    ((ServerPrepareResult) args[0]).getUnProxiedProtocol());
              }
              throw e.getTargetException();
            }
            throw e;
          }
        case METHOD_RESET:
          //listener will report reset on any active connections (Master/slave)
          listener.reset();
          return null;
        default:
      }
  
      return executeInvocation(method, args, false);
  
    }