/**
   * Parse the SQL statement and locate any placeholders or named parameters. Named parameters are
   * substituted for a JDBC placeholder.
   *
   * @param sql the SQL statement
   * @return the parsed statement, represented as ParsedSql instance
   */
  public static ParsedSql parseSqlStatement(String sql) {
    Set<String> namedParameters = new HashSet<String>();
    ParsedSql parsedSql = new ParsedSql(sql);

    char[] statement = sql.toCharArray();
    int namedParameterCount = 0;
    int unnamedParameterCount = 0;
    int totalParameterCount = 0;

    int i = 0;
    while (i < statement.length) {
      int skipToPosition = skipCommentsAndQuotes(statement, i);
      if (i != skipToPosition) {
        if (skipToPosition >= statement.length) {
          break;
        }
        i = skipToPosition;
      }
      char c = statement[i];
      if (c == ':' || c == '&') {
        int j = i + 1;
        if (j < statement.length && statement[j] == ':' && c == ':') {
          // Postgres-style "::" casting operator - to be skipped.
          i = i + 2;
          continue;
        }
        while (j < statement.length && !isParameterSeparator(statement[j])) {
          j++;
        }
        if (j - i > 1) {
          String parameter = sql.substring(i + 1, j);
          if (!namedParameters.contains(parameter)) {
            namedParameters.add(parameter);
            namedParameterCount++;
          }
          parsedSql.addNamedParameter(parameter, i, j);
          totalParameterCount++;
        }
        i = j - 1;
      } else {
        if (c == '?') {
          unnamedParameterCount++;
          totalParameterCount++;
        }
      }
      i++;
    }
    parsedSql.setNamedParameterCount(namedParameterCount);
    parsedSql.setUnnamedParameterCount(unnamedParameterCount);
    parsedSql.setTotalParameterCount(totalParameterCount);
    return parsedSql;
  }