ankane/pghero · error · PgHero::Error

Invalid query hash: #{query_hash}

Error message

Invalid query hash: #{query_hash}

What it means

reset_query_stats(query_hash:) converts the argument with to_i and passes it as the queryid to pg_stat_statements_reset. In Postgres, 0 means "all queries", so PgHero refuses a converted value of 0 - which is what you get from nil, a non-numeric string, or a literal 0 - and raises Error "Invalid query hash: {value}" rather than silently resetting every statement's stats.

Source

Thrown at lib/pghero/methods/query_stats.rb:109

      end

      def reset_query_stats(user: nil, query_hash: nil, raise_errors: false)
        database = database_name
        database_id = select_one("SELECT oid FROM pg_database WHERE datname = :database", {database: database})
        raise Error, "Database not found: #{database}" unless database_id

        if user
          user_id = select_one("SELECT usesysid FROM pg_user WHERE usename = :user", {user: user})
          raise Error, "User not found: #{user}" unless user_id
        else
          user_id = 0
        end

        if query_hash
          query_id = query_hash.to_i
          # may not be needed
          # but not intuitive that all query hashes are reset with 0
          raise Error, "Invalid query hash: #{query_hash}" if query_id == 0
        else
          query_id = 0
        end

        binds = {user_id: user_id, database_id: database_id, query_id: query_id}
        # use execute to prevent "unknown OID 2278" warning
        execute("SELECT pg_stat_statements_reset(:user_id, :database_id, :query_id)", binds)
        true
      rescue ActiveRecord::StatementInvalid => e
        raise e if raise_errors
        false
      end

      # https://stackoverflow.com/questions/20582500/how-to-check-if-a-table-exists-in-a-given-schema
      def historical_query_stats_enabled?
        # TODO use schema from config
        # make sure primary database is PostgreSQL first
        queries_table_exists? && query_stats_table_exists? && capture_query_stats?

View on GitHub (pinned to 7edb57986f)

Solutions

  1. Pass the numeric queryid from the current query stats row (the query_hash value PgHero itself reports), as an Integer or numeric string
  2. Validate before calling: query_hash = Integer(value, 10) rescue nil and skip when nil or 0
  3. If you intend to reset everything, omit query_hash entirely instead of passing 0

Example fix

# before
database.reset_query_stats(query_hash: params[:query_hash])

# after
query_hash = Integer(params[:query_hash], 10) rescue nil
database.reset_query_stats(query_hash: query_hash) if query_hash && query_hash > 0
Defensive patterns

Strategy: type-guard

Validate before calling

query_hash = Integer(params[:query_hash], 10) rescue nil
database.reset_query_stats(query_hash: query_hash) if query_hash && query_hash > 0

Type guard

def valid_query_hash?(value)
  n = Integer(value, 10) rescue nil
  !n.nil? && n > 0
end

Prevention

When it happens

Trigger: database.reset_query_stats(query_hash: "abc") (to_i -> 0); query_hash: nil or "0"; passing a hash digest string from an older pghero/PG format instead of the numeric pg_stat_statements queryid; forwarding params[:query_hash] from a URL without validation.

Common situations: PG13+ / pg_stat_statements switched from md5 hash labels to bigint queryid, so old stored hashes no longer fit; hand-built admin URLs; API callers sending the wrong field.

Related errors


AI-assisted analysis of ankane/pghero@7edb57986f (2026-08-21). Data as JSON: /api/errors/4275e3bd9b07b551. Report an issue: GitHub.