docs: public static JdbcCallParseInfo modifyJdbcCall(String jdbcSql, boolean stdStrings,
        int serverVersion, int protocolVersion) throws SQLException {
      // Mini-parser for JDBC function-call syntax (only)
      // TODO: Merge with escape processing (and parameter parsing?) so we only parse each query once.
      // RE: frequently used statements are cached (see {@link org.postgresql.jdbc.PgConnection#borrowQuery}), so this "merge" is not that important.
      String sql = jdbcSql;
      boolean isFunction = false;
      boolean outParamBeforeFunc = false;
  
      int len = jdbcSql.length();
      int state = 1;
      boolean inQuotes = false;
      boolean inEscape = false;
      int startIndex = -1;
      int endIndex = -1;
      boolean syntaxError = false;
      int i = 0;
  
      while (i < len && !syntaxError) {
        char ch = jdbcSql.charAt(i);
  
        switch (state) {
          case 1:  // Looking for { at start of query
            if (ch == '{') {
              ++i;
              ++state;
            } else if (Character.isWhitespace(ch)) {
              ++i;
            } else {
              // Not function-call syntax. Skip the rest of the string.
              i = len;
            }
            break;
  
          case 2:  // After {, looking for ? or =, skipping whitespace
            if (ch == '?') {
              outParamBeforeFunc =
                  isFunction = true;   // { ? = call ... }  -- function with one out parameter
              ++i;
              ++state;
            } else if (ch == 'c' || ch == 'C') {  // { call ... }      -- proc with no out parameters
              state += 3; // Don't increase 'i'
            } else if (Character.isWhitespace(ch)) {
              ++i;
            } else {
              // "{ foo ...", doesn't make sense, complain.
              syntaxError = true;
            }
            break;
  
          case 3:  // Looking for = after ?, skipping whitespace
            if (ch == '=') {
              ++i;
              ++state;
            } else if (Character.isWhitespace(ch)) {
              ++i;
            } else {
              syntaxError = true;
            }
            break;
  
          case 4:  // Looking for 'call' after '? =' skipping whitespace
            if (ch == 'c' || ch == 'C') {
              ++state; // Don't increase 'i'.
            } else if (Character.isWhitespace(ch)) {
              ++i;
            } else {
              syntaxError = true;
            }
            break;
  
          case 5:  // Should be at 'call ' either at start of string or after ?=
            if ((ch == 'c' || ch == 'C') && i + 4 <= len && jdbcSql.substring(i, i + 4)
                .equalsIgnoreCase("call")) {
              isFunction = true;
              i += 4;
              ++state;
            } else if (Character.isWhitespace(ch)) {
              ++i;
            } else {
              syntaxError = true;
            }
            break;
  
          case 6:  // Looking for whitespace char after 'call'
            if (Character.isWhitespace(ch)) {
              // Ok, we found the start of the real call.
              ++i;
              ++state;
              startIndex = i;
            } else {
              syntaxError = true;
            }
            break;
  
          case 7:  // In "body" of the query (after "{ [? =] call ")
            if (ch == '\'') {
              inQuotes = !inQuotes;
              ++i;
            } else if (inQuotes && ch == '\\' && !stdStrings) {
              // Backslash in string constant, skip next character.
              i += 2;
            } else if (!inQuotes && ch == '{') {
              inEscape = !inEscape;
              ++i;
            } else if (!inQuotes && ch == '}') {
              if (!inEscape) {
                // Should be end of string.
                endIndex = i;
                ++i;
                ++state;
              } else {
                inEscape = false;
              }
            } else if (!inQuotes && ch == ';') {
              syntaxError = true;
            } else {
              // Everything else is ok.
              ++i;
            }
            break;
  
          case 8:  // At trailing end of query, eating whitespace
            if (Character.isWhitespace(ch)) {
              ++i;
            } else {
              syntaxError = true;
            }
            break;
  
          default:
            throw new IllegalStateException("somehow got into bad state " + state);
        }
      }
  
      // We can only legally end in a couple of states here.
      if (i == len && !syntaxError) {
        if (state == 1) {
          // Not an escaped syntax.
          return new JdbcCallParseInfo(sql, isFunction);
        }
        if (state != 8) {
          syntaxError = true; // Ran out of query while still parsing
        }
      }
  
      if (syntaxError) {
        throw new PSQLException(
            GT.tr("Malformed function or procedure escape syntax at offset {0}.", i),
            PSQLState.STATEMENT_NOT_ALLOWED_IN_FUNCTION_CALL);
      }
  
      String prefix = "select * from ";
      String suffix = " as result";
  
      String s = jdbcSql.substring(startIndex, endIndex);
      int prefixLength = prefix.length();
      StringBuilder sb = new StringBuilder(prefixLength + jdbcSql.length() + suffix.length() + 10);
      sb.append(prefix);
      sb.append(s);
  
      int opening = s.indexOf('(') + 1;
      if (opening == 0) {
        // here the function call has no parameters declaration eg : "{ ? = call pack_getValue}"
        sb.append(outParamBeforeFunc ? "(?)" : "()");
      } else if (outParamBeforeFunc) {
        // move the single out parameter into the function call
        // so that it can be treated like all other parameters
        boolean needComma = false;
  
        // the following loop will check if the function call has parameters
        // eg "{ ? = call pack_getValue(?) }" vs "{ ? = call pack_getValue() }
        for (int j = opening + prefixLength; j < sb.length(); j++) {
          char c = sb.charAt(j);
          if (c == ')') {
            break;
          }
  
          if (!Character.isWhitespace(c)) {
            needComma = true;
            break;
          }
        }
  
        // insert the return parameter as the first parameter of the function call
        if (needComma) {
          sb.insert(opening + prefixLength, "?,");
        } else {
          sb.insert(opening + prefixLength, "?");
        }
      }
  
      sql = sb.append(suffix).toString();
      return new JdbcCallParseInfo(sql, isFunction);
    }