# Copyright (c) 2021-2025, PostgreSQL Global Development Group use strict; use warnings FATAL => 'all'; use PostgreSQL::Test::Cluster; use PostgreSQL::Test::Utils; use Test::More; # Test set-up my ($node, $port); $node = PostgreSQL::Test::Cluster->new('test'); $node->init(auth_extra => [ '--create-role' => 'no_such_user' ]); $node->start; $port = $node->port; # Load the amcheck extension, upon which pg_amcheck depends $node->safe_psql('postgres', q(CREATE EXTENSION amcheck)); ######################################### # Test non-existent databases # Failing to connect to the initial database is an error. $node->command_checks_all( [ 'pg_amcheck', 'qqq' ], 1, [qr/^$/], [qr/FATAL: database "qqq" does not exist/], 'checking a non-existent database'); # Failing to resolve a database pattern is an error by default. $node->command_checks_all( [ 'pg_amcheck', '--database' => 'qqq', '--database' => 'postgres' ], 1, [qr/^$/], [qr/pg_amcheck: error: no connectable databases to check matching "qqq"/], 'checking an unresolvable database pattern'); # But only a warning under --no-strict-names $node->command_checks_all( [ 'pg_amcheck', '--no-strict-names', '--database' => 'qqq', '--database' => 'postgres' ], 0, [qr/^$/], [ qr/pg_amcheck: warning: no connectable databases to check matching "qqq"/ ], 'checking an unresolvable database pattern under --no-strict-names'); # Check that a substring of an existent database name does not get interpreted # as a matching pattern. $node->command_checks_all( [ 'pg_amcheck', '--database' => 'post', '--database' => 'postgres' ], 1, [qr/^$/], [ qr/pg_amcheck: error: no connectable databases to check matching "post"/ ], 'checking an unresolvable database pattern (substring of existent database)' ); # Check that a superstring of an existent database name does not get interpreted # as a matching pattern. $node->command_checks_all( [ 'pg_amcheck', '--database' => 'postgresql', '--database' => 'postgres' ], 1, [qr/^$/], [ qr/pg_amcheck: error: no connectable databases to check matching "postgresql"/ ], 'checking an unresolvable database pattern (superstring of existent database)' ); ######################################### # Test connecting with a non-existent user # Failing to connect to the initial database due to bad username is an error. $node->command_checks_all( [ 'pg_amcheck', '--username' => 'no_such_user', 'postgres' ], 1, [qr/^$/], [qr/role "no_such_user" does not exist/], 'checking with a non-existent user'); ######################################### # Test checking databases without amcheck installed # Attempting to check a database by name where amcheck is not installed should # raise a warning. If all databases are skipped, having no relations to check # raises an error. $node->command_checks_all( [ 'pg_amcheck', 'template1' ], 1, [qr/^$/], [ qr/pg_amcheck: warning: skipping database "template1": amcheck is not installed/, qr/pg_amcheck: error: no relations to check/ ], 'checking a database by name without amcheck installed, no other databases' ); # Again, but this time with another database to check, so no error is raised. $node->command_checks_all( [ 'pg_amcheck', '--database' => 'template1', '--database' => 'postgres' ], 0, [qr/^$/], [ qr/pg_amcheck: warning: skipping database "template1": amcheck is not installed/ ], 'checking a database by name without amcheck installed, with other databases' ); # Again, but by way of checking all databases $node->command_checks_all( [ 'pg_amcheck', '--all' ], 0, [qr/^$/], [ qr/pg_amcheck: warning: skipping database "template1": amcheck is not installed/ ], 'checking a database by pattern without amcheck installed, with other databases' ); ######################################### # Test unreasonable patterns # Check three-part unreasonable pattern that has zero-length names $node->command_checks_all( [ 'pg_amcheck', '--database' => 'postgres', '--table' => '..' ], 1, [qr/^$/], [ qr/pg_amcheck: error: no connectable databases to check matching "\.\."/ ], 'checking table pattern ".."'); # Again, but with non-trivial schema and relation parts $node->command_checks_all( [ 'pg_amcheck', '--database' => 'postgres', '--table' => '.foo.bar' ], 1, [qr/^$/], [ qr/pg_amcheck: error: no connectable databases to check matching "\.foo\.bar"/ ], 'checking table pattern ".foo.bar"'); # Check two-part unreasonable pattern that has zero-length names $node->command_checks_all( [ 'pg_amcheck', '--database' => 'postgres', '--table' => '.' ], 1, [qr/^$/], [qr/pg_amcheck: error: no heap tables to check matching "\."/], 'checking table pattern "."'); # Check that a multipart database name is rejected $node->command_checks_all( [ 'pg_amcheck', '--database' => 'localhost.postgres' ], 2, [qr/^$/], [ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres/ ], 'multipart database patterns are rejected'); # Check that a three-part schema name is rejected $node->command_checks_all( [ 'pg_amcheck', '--schema' => 'localhost.postgres.pg_catalog' ], 2, [qr/^$/], [ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): localhost\.postgres\.pg_catalog/ ], 'three part schema patterns are rejected'); # Check that a four-part table name is rejected $node->command_checks_all( [ 'pg_amcheck', '--table' => 'localhost.postgres.pg_catalog.pg_class' ], 2, [qr/^$/], [ qr/pg_amcheck: error: improper relation name \(too many dotted names\): localhost\.postgres\.pg_catalog\.pg_class/ ], 'four part table patterns are rejected'); # Check that too many dotted names still draws an error under --no-strict-names # That flag means that it is ok for the object to be missing, not that it is ok # for the object name to be ungrammatical $node->command_checks_all( [ 'pg_amcheck', '--no-strict-names', '--table' => 'this.is.a.really.long.dotted.string' ], 2, [qr/^$/], [ qr/pg_amcheck: error: improper relation name \(too many dotted names\): this\.is\.a\.really\.long\.dotted\.string/ ], 'ungrammatical table names still draw errors under --no-strict-names'); $node->command_checks_all( [ 'pg_amcheck', '--no-strict-names', '--schema' => 'postgres.long.dotted.string' ], 2, [qr/^$/], [ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): postgres\.long\.dotted\.string/ ], 'ungrammatical schema names still draw errors under --no-strict-names'); $node->command_checks_all( [ 'pg_amcheck', '--no-strict-names', '--database' => 'postgres.long.dotted.string' ], 2, [qr/^$/], [ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): postgres\.long\.dotted\.string/ ], 'ungrammatical database names still draw errors under --no-strict-names'); # Likewise for exclusion patterns $node->command_checks_all( [ 'pg_amcheck', '--no-strict-names', '--exclude-table' => 'a.b.c.d' ], 2, [qr/^$/], [ qr/pg_amcheck: error: improper relation name \(too many dotted names\): a\.b\.c\.d/ ], 'ungrammatical table exclusions still draw errors under --no-strict-names' ); $node->command_checks_all( [ 'pg_amcheck', '--no-strict-names', '--exclude-schema' => 'a.b.c' ], 2, [qr/^$/], [ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): a\.b\.c/ ], 'ungrammatical schema exclusions still draw errors under --no-strict-names' ); $node->command_checks_all( [ 'pg_amcheck', '--no-strict-names', '--exclude-database' => 'a.b' ], 2, [qr/^$/], [ qr/pg_amcheck: error: improper qualified name \(too many dotted names\): a\.b/ ], 'ungrammatical database exclusions still draw errors under --no-strict-names' ); ######################################### # Test checking non-existent databases, schemas, tables, and indexes # Use --no-strict-names and a single existent table so we only get warnings # about the failed pattern matches $node->command_checks_all( [ 'pg_amcheck', '--no-strict-names', '--table' => 'no_such_table', '--table' => 'no*such*table', '--index' => 'no_such_index', '--index' => 'no*such*index', '--relation' => 'no_such_relation', '--relation' => 'no*such*relation', '--database' => 'no_such_database', '--database' => 'no*such*database', '--relation' => 'none.none', '--relation' => 'none.none.none', '--relation' => 'postgres.none.none', '--relation' => 'postgres.pg_catalog.none', '--relation' => 'postgres.none.pg_class', '--table' => 'postgres.pg_catalog.pg_class', # This exists ], 0, [qr/^$/], [ qr/pg_amcheck: warning: no heap tables to check matching "no_such_table"/, qr/pg_amcheck: warning: no heap tables to check matching "no\*such\*table"/, qr/pg_amcheck: warning: no btree indexes to check matching "no_such_index"/, qr/pg_amcheck: warning: no btree indexes to check matching "no\*such\*index"/, qr/pg_amcheck: warning: no relations to check matching "no_such_relation"/, qr/pg_amcheck: warning: no relations to check matching "no\*such\*relation"/, qr/pg_amcheck: warning: no heap tables to check matching "no\*such\*table"/, qr/pg_amcheck: warning: no connectable databases to check matching "no_such_database"/, qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/, qr/pg_amcheck: warning: no relations to check matching "none\.none"/, qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/, qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.none"/, qr/pg_amcheck: warning: no relations to check matching "postgres\.pg_catalog\.none"/, qr/pg_amcheck: warning: no relations to check matching "postgres\.none\.pg_class"/, qr/pg_amcheck: warning: no connectable databases to check matching "no_such_database"/, qr/pg_amcheck: warning: no connectable databases to check matching "no\*such\*database"/, qr/pg_amcheck: warning: no connectable databases to check matching "none\.none\.none"/, ], 'many unmatched patterns and one matched pattern under --no-strict-names' ); ######################################### # Test that an invalid / partially dropped database won't be targeted $node->safe_psql( 'postgres', q( CREATE DATABASE regression_invalid; UPDATE pg_database SET datconnlimit = -2 WHERE datname = 'regression_invalid'; )); $node->command_checks_all( [ 'pg_amcheck', '--database' => 'regression_invalid' ], 1, [qr/^$/], [ qr/pg_amcheck: error: no connectable databases to check matching "regression_invalid"/, ], 'checking handling of invalid database'); $node->command_checks_all( [ 'pg_amcheck', '--database' => 'postgres', '--table' => 'regression_invalid.public.foo', ], 1, [qr/^$/], [ qr/pg_amcheck: error: no connectable databases to check matching "regression_invalid.public.foo"/, ], 'checking handling of object in invalid database'); ######################################### # Test checking otherwise existent objects but in databases where they do not exist $node->safe_psql( 'postgres', q( CREATE TABLE public.foo (f integer); CREATE INDEX foo_idx ON foo(f); )); $node->safe_psql('postgres', q(CREATE DATABASE another_db)); $node->command_checks_all( [ 'pg_amcheck', '--database' => 'postgres', '--no-strict-names', '--table' => 'template1.public.foo', '--table' => 'another_db.public.foo', '--table' => 'no_such_database.public.foo', '--index' => 'template1.public.foo_idx', '--index' => 'another_db.public.foo_idx', '--index' => 'no_such_database.public.foo_idx', ], 1, [qr/^$/], [ qr/pg_amcheck: warning: skipping database "template1": amcheck is not installed/, qr/pg_amcheck: warning: no heap tables to check matching "template1\.public\.foo"/, qr/pg_amcheck: warning: no heap tables to check matching "another_db\.public\.foo"/, qr/pg_amcheck: warning: no connectable databases to check matching "no_such_database\.public\.foo"/, qr/pg_amcheck: warning: no btree indexes to check matching "template1\.public\.foo_idx"/, qr/pg_amcheck: warning: no btree indexes to check matching "another_db\.public\.foo_idx"/, qr/pg_amcheck: warning: no connectable databases to check matching "no_such_database\.public\.foo_idx"/, qr/pg_amcheck: error: no relations to check/, ], 'checking otherwise existent objects in the wrong databases'); ######################################### # Test schema exclusion patterns # Check with only schema exclusion patterns $node->command_checks_all( [ 'pg_amcheck', '--all', '--no-strict-names', '--exclude-schema' => 'public', '--exclude-schema' => 'pg_catalog', '--exclude-schema' => 'pg_toast', '--exclude-schema' => 'information_schema', ], 1, [qr/^$/], [ qr/pg_amcheck: warning: skipping database "template1": amcheck is not installed/, qr/pg_amcheck: error: no relations to check/ ], 'schema exclusion patterns exclude all relations'); # Check with schema exclusion patterns overriding relation and schema inclusion patterns $node->command_checks_all( [ 'pg_amcheck', '--all', '--no-strict-names', '--schema' => 'public', '--schema' => 'pg_catalog', '--schema' => 'pg_toast', '--schema' => 'information_schema', '--table' => 'pg_catalog.pg_class', '--exclude-schema' => '*' ], 1, [qr/^$/], [ qr/pg_amcheck: warning: skipping database "template1": amcheck is not installed/, qr/pg_amcheck: error: no relations to check/ ], 'schema exclusion pattern overrides all inclusion patterns'); done_testing();