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
- Pass the numeric queryid from the current query stats row (the query_hash value PgHero itself reports), as an Integer or numeric string
- Validate before calling: query_hash = Integer(value, 10) rescue nil and skip when nil or 0
- 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
- Validate with Integer(value, 10) at the request boundary; to_i silently maps garbage to 0
- Use the numeric queryid pghero reports, not md5-style hashes from older pg_stat_statements versions
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
- User not found: #{user}
- Invalid connection URL
- Invalid sort
- Database not found: #{database}
- Query hash stats not enabled
AI-assisted analysis of ankane/pghero@7edb57986f (2026-08-21).
Data as JSON: /api/errors/4275e3bd9b07b551.
Report an issue: GitHub.