diff --git a/src/bin/pg_basebackup/t/010_pg_basebackup.pl b/src/bin/pg_basebackup/t/010_pg_basebackup.pl index b7d36b65dd..aabc27ab6f 100644 --- a/src/bin/pg_basebackup/t/010_pg_basebackup.pl +++ b/src/bin/pg_basebackup/t/010_pg_basebackup.pl @@ -506,6 +506,7 @@ system_or_bail 'pg_ctl', '-D', $pgdata, 'start'; $node->command_checks_all( [ 'pg_basebackup', '-D', "$tempdir/backup_corrupt" ], 1, + '', [qr{^$}], [qr/^WARNING.*checksum verification failed/s], 'pg_basebackup reports checksum mismatch'); @@ -526,6 +527,7 @@ system_or_bail 'pg_ctl', '-D', $pgdata, 'start'; $node->command_checks_all( [ 'pg_basebackup', '-D', "$tempdir/backup_corrupt2" ], 1, + '', [qr{^$}], [qr/^WARNING.*further.*failures.*will.not.be.reported/s], 'pg_basebackup does not report more than 5 checksum mismatches'); @@ -542,6 +544,7 @@ system_or_bail 'pg_ctl', '-D', $pgdata, 'start'; $node->command_checks_all( [ 'pg_basebackup', '-D', "$tempdir/backup_corrupt3" ], 1, + '', [qr{^$}], [qr/^WARNING.*7 total checksum verification failures/s], 'pg_basebackup correctly report the total number of checksum mismatches'); diff --git a/src/bin/pg_checksums/t/002_actions.pl b/src/bin/pg_checksums/t/002_actions.pl index 59228b916c..cf4811d382 100644 --- a/src/bin/pg_checksums/t/002_actions.pl +++ b/src/bin/pg_checksums/t/002_actions.pl @@ -62,6 +62,7 @@ sub check_relation_corruption '--filenode', $relfilenode_corrupted ], 1, + '', [qr/Bad checksums:.*1/], [qr/checksum verification failed/], "fails with corrupted data for single relfilenode on tablespace $tablespace" @@ -71,6 +72,7 @@ sub check_relation_corruption $node->command_checks_all( [ 'pg_checksums', '--check', '-D', $pgdata ], 1, + '', [qr/Bad checksums:.*1/], [qr/checksum verification failed/], "fails with corrupted data on tablespace $tablespace"); @@ -203,6 +205,7 @@ sub fail_corrupt $node->command_checks_all( [ 'pg_checksums', '--check', '-D', $pgdata ], 1, + '', [qr/^$/], [qr/could not read block 0 in file.*$file\":/], "fails for corrupted data in $file"); diff --git a/src/bin/pg_controldata/t/001_pg_controldata.pl b/src/bin/pg_controldata/t/001_pg_controldata.pl index 3b63ad230f..ebe9b80a52 100644 --- a/src/bin/pg_controldata/t/001_pg_controldata.pl +++ b/src/bin/pg_controldata/t/001_pg_controldata.pl @@ -33,6 +33,7 @@ close $fh; command_checks_all( [ 'pg_controldata', $node->data_dir ], 0, + '', [ qr/WARNING: Calculated CRC checksum does not match value stored in file/, qr/WARNING: invalid WAL segment size/ diff --git a/src/bin/pg_resetwal/t/002_corrupted.pl b/src/bin/pg_resetwal/t/002_corrupted.pl index f9940d7fc5..1990669d26 100644 --- a/src/bin/pg_resetwal/t/002_corrupted.pl +++ b/src/bin/pg_resetwal/t/002_corrupted.pl @@ -30,6 +30,7 @@ close $fh; command_checks_all( [ 'pg_resetwal', '-n', $node->data_dir ], 0, + '', [qr/pg_control version number/], [ qr/pg_resetwal: warning: pg_control exists but is broken or wrong version; ignoring it/ @@ -46,6 +47,7 @@ close $fh; command_checks_all( [ 'pg_resetwal', '-n', $node->data_dir ], 0, + '', [qr/pg_control version number/], [ qr/\Qpg_resetwal: warning: pg_control specifies invalid WAL segment size (0 bytes); proceed with caution\E/ diff --git a/src/bin/pgbench/t/001_pgbench_with_server.pl b/src/bin/pgbench/t/001_pgbench_with_server.pl index b82d3f65c4..01010914fe 100644 --- a/src/bin/pgbench/t/001_pgbench_with_server.pl +++ b/src/bin/pgbench/t/001_pgbench_with_server.pl @@ -50,7 +50,7 @@ sub pgbench push @cmd, @args; - $node->command_checks_all(\@cmd, $stat, $out, $err, $name); + $node->command_checks_all(\@cmd, $stat, '', $out, $err, $name); # cleanup? #unlink @filenames or die "cannot unlink files (@filenames): $!"; diff --git a/src/bin/pgbench/t/002_pgbench_no_server.pl b/src/bin/pgbench/t/002_pgbench_no_server.pl index f7fa18418b..b58f3548c3 100644 --- a/src/bin/pgbench/t/002_pgbench_no_server.pl +++ b/src/bin/pgbench/t/002_pgbench_no_server.pl @@ -25,7 +25,7 @@ sub pgbench my ($opts, $stat, $out, $err, $name) = @_; print STDERR "opts=$opts, stat=$stat, out=$out, err=$err, name=$name"; command_checks_all([ 'pgbench', split(/\s+/, $opts) ], - $stat, $out, $err, $name); + $stat, '', $out, $err, $name); return; } @@ -52,7 +52,7 @@ sub pgbench_scripts push @cmd, '-f', $filename; } } - command_checks_all(\@cmd, $stat, $out, $err, $name); + command_checks_all(\@cmd, $stat, '', $out, $err, $name); return; } diff --git a/src/bin/psql/.gitignore b/src/bin/psql/.gitignore index c2862b12d6..d324c1c1fa 100644 --- a/src/bin/psql/.gitignore +++ b/src/bin/psql/.gitignore @@ -3,3 +3,4 @@ /sql_help.c /psql +/tmp_check diff --git a/src/bin/psql/Makefile b/src/bin/psql/Makefile index 69bb297fe7..9473ab01cb 100644 --- a/src/bin/psql/Makefile +++ b/src/bin/psql/Makefile @@ -60,8 +60,15 @@ uninstall: clean distclean: rm -f psql$(X) $(OBJS) lex.backup + rm -rf tmp_check # files removed here are supposed to be in the distribution tarball, # so do not clean them in the clean/distclean rules maintainer-clean: distclean rm -f sql_help.h sql_help.c psqlscanslash.c + +check: + $(prove_check) + +installcheck: + $(prove_installcheck) diff --git a/src/bin/psql/t/001_psql.pl b/src/bin/psql/t/001_psql.pl new file mode 100644 index 0000000000..f0cf0b4aeb --- /dev/null +++ b/src/bin/psql/t/001_psql.pl @@ -0,0 +1,876 @@ +use strict; +use warnings; + +use PostgresNode; +use TestLib; +use Test::More; + +my $node = get_new_node('main'); +$node->init(); +$node->start(); + +# create a file under the test directory +# return its full path +sub create_test_file +{ + my ($fname, $contents) = @_; + my $fn = $node->basedir . '/' . $fname; + #ok(not -e $fn, "$fn must not already exists"); + append_to_file($fn, $contents); + return $fn; +} + +# invoke psql +# - opts: space-separated options and arguments +# -X is appended, unless opts starts with ! +# - stat: expected exit status +# - in: input stream +# - out: list of re to check on stdout +# - err: list of re to check on stderr +# - name: of the test +# - more: more raw arguments +sub psql +{ + local $Test::Builder::Level = $Test::Builder::Level + 1; + my ($opts, $stat, $in, $out, $err, $name, @more) = @_; + $opts =~ s/^!// or push @more, '-X'; + my @cmd = ('psql', split(/\s+/, $opts), @more); + $node->command_checks_all(\@cmd, $stat, $in, $out, $err, $name); + return; +} + +# invoke psql interactively, making it believe it is connected to a tty +# - opts: space-separated options and arguments +# -X is appended, unless opts starts with ! +# - stat: expected final exit status +# - timeout: for interactions, in second +# - inout: list of input expected re +# - name: of the test +# - remove: tell to remove init/start/end checks +sub ipsql +{ + local $Test::Builder::Level = $Test::Builder::Level + 1; + my ($opts, $stat, $timeout, $inout, $name, $remove) = @_; + + # build command to run + my @more = (); + @more = ('-X') unless $opts =~ /^!/; + $opts =~ s/^!//; + my @cmd = ('psql', split(/\s+/, $opts), @more); + + # check it is running at start and end, and exit cleanly + unless ($remove) + { + unshift @$inout, [ "\\echo STARTING :VERSION_NUM\n", qr/STARTING \d+.*postgres=\# /s ]; + push @$inout, [ "\\echo ENDING :VERSION_NUM\\q\n", qr/ENDING \d+$/s ]; + } + + # call the stuff + $node->icommand_checks( + \@cmd, $stat, $timeout, + $remove ? undef : qr/psql .*Type "help" for help..*postgres=# /s, + $inout, $name); + return; +} + +# +# check whether we have readline/history capability +# +my ($out, $err); +$node->psql('postgres', "\\s\n", 'stdout' => \$out, 'stderr' => \$err); +my $has_history = $err !~ /history is not supported by this installation/; + +my $EMPTY = [ qr/^$/ ]; + +# +# check various options +# +# this cannot be tested from within a SQL script +psql('!--help', 0, '', + [ qr/interactive terminal/, qr/Usage:/, qr/Connection options:/ ], + $EMPTY, 'psql --help'); +psql('!-?', 0, '', + [ qr/interactive terminal/, qr/Usage:/, qr/Connection options:/ ], + $EMPTY, 'psql -?'); +psql('!--version', 0, '', + [ qr{^psql \(PostgreSQL\) \d+} ], $EMPTY, 'psql --version'); +psql('--help=variables', 0, '', + [ qr{VERSION_NAME}, qr{unicode_border_linestyle}, qr{PSQL_EDITOR} ], + $EMPTY, 'psql --help=variables'); +# look for plenty of backslash-commands +psql('--help=commands', 0, '', + [ qr{\\copyright}, qr{\\pset}, qr{(?!VERSION_NAME)} ], + $EMPTY, 'psql --help=commands'); + +# tested elsewhere: -a -A -d -e -h -L +psql('-b', 0, "SELECT 1/0;\n", + $EMPTY, [ qr{STATEMENT: SELECT 1/0}, qr{ERROR: division by zero} ], 'psql -b'); +psql('-c \\q', 0, '', $EMPTY, $EMPTY, 'psql -c'); +psql('-c \\l -c \\l', 0, '', [ qr{template0.*template0}s ], $EMPTY, 'psql -c -c'); +psql('', 0, '', [ qr{(?!1234)}, qr{5678}s ], $EMPTY, 'psql -c ;;', ('-c', 'SELECT 1234; SELECT 5678;')); +psql('-E', 0, "\\l\n", [ qr{\* QUERY \*}, qr{template0} ], $EMPTY, 'psql -E'); +psql('-A -F _', 0, "SELECT 54, 32;\n", [ qr{54_32} ], $EMPTY, 'psql -F _'); +psql('-H', 0, "SELECT 5432 AS pg\n", [ qr{>5432<} ], $EMPTY, 'psql -H'); +psql('-l', 0, '', [ qr{template0}, qr{template1} ], $EMPTY, 'psql -l'); +psql('-n', 0, "\\q\n", $EMPTY, $EMPTY, 'psql -n'); +psql('-P format=html', 0, "SELECT 5432 AS pg\n", [ qr{>5432<} ], $EMPTY, 'psql -P format=html'); +psql('-P tuples_only', 0, "\\pset\n", [ qr{tuples_only.*on} ], $EMPTY, 'psql -P tuples_only'); +psql('-A -R_', 0, "VALUES (1), (2);\n", [ qr{_1_2_} ], $EMPTY, 'psql -R _'); +psql('-s', 0, "SELECT 1;\n", [ qr{verify command}, qr{SELECT 1}, qr{press return to proceed} ], $EMPTY, 'psql -s'); +psql('-S', 0, "SELECT 1 AS one\nSELECT 2 AS two\n", [ qr{one.*1.*two.*2}s ], $EMPTY, 'psql -S'); +psql('-H -T width=100', 0, "SELECT 1 AS one;\n", [ qr{table.*width=100} ], $EMPTY, 'psql -T ...'); +psql('-X -V', 0, '', [ qr{^psql \(PostgreSQL\) \d+} ], $EMPTY, 'psql -X -V'); +psql('', 0, "\\echo :VERSION_NUM\n", [ qr{\d{6}} ], $EMPTY, 'psql -- show VERSION_NUM'); +psql('-v VERSION_NUM', 0, "\\echo :VERSION_NUM\n", [ qr{:VERSION_NUM} ], $EMPTY, 'psql -v ...'); +psql('-v VERSION_NUM=bla', 0, "\\echo :VERSION_NUM\n", [ qr{bla} ], $EMPTY, 'psql -v ...=.'); +psql('-w', 0, '', $EMPTY, $EMPTY, 'psql -w'); +# nope, interacts on tty +#psql('-W', 0, "foo\n", [ qr{^$} ], [ qr{^$} ], 'psql -W'); +psql('-x', 0, "SELECT 1 AS one, 2 AS two;\n", [ qr{one \| 1.*two \| 2}s ], $EMPTY, 'psql -x'); +# some issue, \0 is not welcome somewhere +#psql('-A -z', "SELECT 1 AS one, 2 AS two;\n", [ qr{one.two}s, qr{1.2}s ], $EMPTY, 'psql -z'); +#psql('-A -0', "SELECT 1 AS one, 2 AS two;\n", [ qr{two.1}s ], $EMPTY, 'psql -0'); +psql('-1', 0, "SELECT 54;\nSELECT 32;\n", [ qr{54}, qr{32} ], $EMPTY, 'psql -1'); +psql('-X -?', 0, '', + [ qr{interactive terminal}, qr{Usage:}, qr{Connection options:} ], $EMPTY, 'psql -X -?'); +psql('--csv', 0, "SELECT 1 AS one, 2 AS two\n", [ qr{one,two}, qr{1,2} ], $EMPTY, 'psql --csv'); + +# +# create some objects to help \d tests later +# +psql('', 0, '-- create some objects +CREATE USER regress_psql_tap_1; +ALTER ROLE regress_psql_tap_1 SET enable_indexscan TO off; +CREATE TABLE regress_psql_tap_1_t1(data TEXT NOT NULL); +CREATE FUNCTION regress_psql_tap_1_f1() RETURNS INTEGER AS $$ SELECT 54321 $$ LANGUAGE SQL; +CREATE VIEW regress_psql_tap_1_v1 AS SELECT 54321 AS one; +', [ qr{CREATE ROLE}, qr{ALTER ROLE} ], [ qr{^$} ], 'psql -- create stuff'); + +# +# check interactive help +# +# this is mostly for coverage, including occasional negatives. +# we certainly do not want to have an output file with the whole doc. +my @backslash_help = ( + # \h command, list of stdout matches + [ 'no-help', [ qr{No help available for} ] ], + [ '' , [ qr{ABORT}, qr{ALTER}, qr{CREATE}, qr{DROP}, qr{VALUES}, qr{WITH} ] ], + [ ' ' , [ qr{ABORT}, qr{ALTER}, qr{CREATE}, qr{DROP}, qr{VALUES}, qr{WITH} ] ], + # lists + [ 'ALTER', [ qr{AGGREGATE}, qr{ROLE}, qr{VIEW}, qr{(?!ACCESS METHOD)}, qr{(?!TRANSFORM)} ] ], + [ 'ALTER TEXT SEARCH', [ qr{CONFIGURATION}, qr{DICTIONARY}, qr{PARSER}, qr{TEMPLATE} ] ], + [ 'ALTER FOREIGN', [ qr{DATA WRAPPER}, qr{TABLE} ] ], + [ 'CREATE', [ qr{ACCESS METHOD}, qr{TRANSFORM} ] ], + [ 'DROP', [ qr{ACCESS METHOD}, qr{VIEW} ] ], + # details + [ 'ABORT', [ qr{TRANSACTION}, qr{CHAIN} ] ], + [ 'ALTER AGGREGATE', [ qr{RENAME TO}, qr{(?!CONVERSION)} ] ], + [ 'ALTER CONVERSION', [ qr{SET SCHEMA}, qr{(?!AGGREGATE)} ] ], + [ 'ALTER COLLATION', [ qr{REFRESH VERSION} ] ], + [ 'ALTER DATABASE', [ qr{CONNECTION LIMIT} ] ], + [ 'ALTER DEFAULT PRIVILEGES', [ qr{GRANT} ] ], + [ 'ALTER DOMAIN', [ qr{VALIDATE CONSTRAINT} ] ], + [ 'ALTER EVENT TRIGGER', [ qr{DISABLE} ] ], + [ 'ALTER EXTENSION', [ qr{OPERATOR FAMILY} ] ], + [ 'ALTER FOREIGN DATA WRAPPER', [ qr{VALIDATOR} ] ], + [ 'ALTER FOREIGN TABLE', [ qr{SET STORAGE} ] ], + [ 'ALTER FUNCTION', [ qr{PARALLEL} ] ], + [ 'ALTER GROUP', [ qr{DROP USER} ] ], + [ 'ALTER INDEX', [ qr{ALL IN TABLESPACE} ] ], + [ 'ALTER LANGUAGE', [ qr{PROCEDURAL}, qr{(?!INDEX)} ] ], + [ 'ALTER LARGE OBJECT', [ qr{OWNER TO} ] ], + [ 'ALTER MATERIALIZED VIEW', [ qr{SET TABLESPACE} ] ], + [ 'ALTER OPERATOR', [ qr{JOIN =} ] ], + [ 'ALTER OPERATOR CLASS', [ qr{SET SCHEMA}, qr{(?!LANGUAGE)} ] ], + [ 'ALTER OPERATOR FAMILY', [ qr{FUNCTION} ] ], + [ 'ALTER POLICY', [ qr{WITH CHECK} ] ], + [ 'ALTER PROCEDURE', [ qr{DEPENDS ON EXTENSION} ] ], + [ 'ALTER PUBLICATION', [ qr{DROP TABLE} ] ], + [ 'ALTER ROLE', [ qr{BYPASSRLS} ] ], + [ 'ALTER ROUTINE', [ qr{LEAKPROOF} ] ], + [ 'ALTER RULE', [ qr{RENAME TO}, qr{(?!OWNER TO)} ] ], + [ 'ALTER SCHEMA', [ qr{OWNER TO} ] ], + [ 'ALTER SEQUENCE', [ qr{NO MAXVALUE} ] ], + [ 'ALTER SERVER', [ qr{VERSION} ] ], + [ 'ALTER STATISTICS', [ qr{SET SCHEMA} ] ], + [ 'ALTER SUBSCRIPTION', [ qr{REFRESH PUBLICATION} ] ], + [ 'ALTER SYSTEM', [ qr{RESET ALL} ] ], + [ 'ALTER TABLE', [ qr{ALL IN TABLESPACE}, qr{DISABLE ROW LEVEL SECURITY} ] ], + [ 'ALTER TABLESPACE', [ qr{RESET} ] ], + [ 'ALTER TEXT SEARCH CONFIGURATION', [ qr{ALTER MAPPING FOR} ] ], + [ 'ALTER TEXT SEARCH DICTIONARY', [ qr{OWNER TO} ] ], + [ 'ALTER TEXT SEARCH PARSER', [ qr{RENAME TO}, qr{(?!OWNER TO)} ] ], + [ 'ALTER TEXT SEARCH TEMPLATE', [ qr{RENAME TO}, qr{(?!OWNER TO)} ] ], + [ 'ALTER TRIGGER', [ qr{DEPENDS ON EXTENSION} ] ], + [ 'ALTER TYPE', [ qr{RENAME ATTRIBUTE} ] ], + [ 'ALTER USER', [ qr{NOREPLICATION}, qr{(?!MAPPING)} ] ], + [ 'ALTER USER MAPPING', [ qr{SERVER} ] ], + [ 'ALTER VIEW', [ qr{DROP DEFAULT} ] ], + [ 'ANALYZE', [ qr{VERBOSE}, qr{SKIP_LOCKED} ] ], + [ 'BEGIN', [ qr{ISOLATION LEVEL} ] ], + [ 'CALL', [] ], + [ 'CHECKPOINT', [] ], + [ 'CLOSE', [ qr{ALL} ] ], + [ 'CLUSTER', [ qr{VERBOSE} ] ], + [ 'COMMENT', [ qr{FOREIGN DATA WRAPPER} ] ], + [ 'COMMIT', [ qr{TRANSACTION}, qr{CHAIN} ] ], + [ 'COMMIT PREPARED', [] ], + [ 'COPY', [ qr{FORMAT}, qr{FORCE_NOT_NULL} ] ], + [ 'CREATE ACCESS METHOD', [ qr{HANDLER} ] ], + [ 'CREATE AGGREGATE', [ qr{SSPACE}, qr{SORTOP}, qr{FINALFUNC} ] ], + [ 'CREATE CAST', [ qr{AS IMPLICIT} ] ], + [ 'CREATE COLLATION', [ qr{LC_CTYPE}, qr{DETERMINISTIC} ] ], + [ 'CREATE CONVERSION', [ qr{FOR} ] ], + [ 'CREATE DATABASE', [ qr{ALLOW_CONNECTIONS} ] ], + [ 'CREATE DOMAIN', [ qr{COLLATE} ] ], + [ 'CREATE EVENT TRIGGER', [ qr{EXECUTE} ] ], + [ 'CREATE EXTENSION', [ qr{FROM} ] ], + [ 'CREATE FOREIGN DATA WRAPPER', [ qr{NO VALIDATOR} ] ], + [ 'CREATE FOREIGN TABLE', [ qr{SERVER} ] ], + [ 'CREATE FUNCTION', [ qr{CALLED ON NULL INPUT}, qr{(?!PROCEDURE)} ] ], + [ 'CREATE GROUP', [ qr{ADMIN} ] ], + [ 'CREATE INDEX', [ qr{CONCURRENTLY}, qr{WHERE} ] ], + [ 'CREATE LANGUAGE', [ qr{TRUSTED} ] ], + [ 'CREATE MATERIALIZED VIEW', [ qr{DATA} ] ], + [ 'CREATE OPERATOR', [ qr{LEFTARG} ] ], + [ 'CREATE OPERATOR CLASS', [ qr{FOR ORDER BY} ] ], + [ 'CREATE OPERATOR FAMILY', [ qr{USING} ] ], + [ 'CREATE POLICY', [ qr{PERMISSIVE} ] ], + [ 'CREATE PROCEDURE', [ qr{SECURITY INVOKER}, qr{(?!FUNCTION)} ] ], + [ 'CREATE PUBLICATION', [ qr{FOR ALL TABLES} ] ], + [ 'CREATE ROLE', [ qr{SYSID} ] ], + [ 'CREATE RULE', [ qr{ALSO} ] ], + [ 'CREATE SCHEMA', [ qr{AUTHORIZATION} ] ], + [ 'CREATE SEQUENCE', [ qr{NO MINVALUE} ] ], + [ 'CREATE SERVER', [ qr{FOREIGN DATA WRAPPER} ] ], + [ 'CREATE STATISTICS', [ qr{FROM} ] ], + [ 'CREATE SUBSCRIPTION', [ qr{CONNECTION} ] ], + [ 'CREATE TABLE', [ qr{PARTITION BY} ] ], + [ 'CREATE TABLE AS', [ qr{ON COMMIT} ] ], + [ 'CREATE TABLESPACE', [ qr{LOCATION} ] ], + [ 'CREATE TEXT SEARCH CONFIGURATION', [ qr{PARSER} ] ], + [ 'CREATE TEXT SEARCH DICTIONARY', [ qr{TEMPLATE} ] ], + [ 'CREATE TEXT SEARCH PARSER', [ qr{GETTOKEN} ] ], + [ 'CREATE TEXT SEARCH TEMPLATE', [ qr{LEXIZE} ] ], + [ 'CREATE TRANSFORM', [ qr{FROM SQL WITH FUNCTION} ] ], + [ 'CREATE TRIGGER', [ qr{EXECUTE} ] ], + [ 'CREATE TYPE', [ qr{SUBTYPE} ] ], + [ 'CREATE USER', [ qr{PASSWORD NULL} ] ], + [ 'CREATE USER MAPPING', [ qr{SERVER} ] ], + [ 'CREATE VIEW', [ qr{CASCADED} ] ], + [ 'DEALLOCATE', [ qr{PREPARE} ] ], + [ 'DECLARE', [ qr{INSENSITIVE} ] ], + [ 'DELETE', [ qr{WHERE CURRENT OF} ] ], + [ 'DISCARD', [ qr{PLANS} ] ], + [ 'DO', [ qr{LANGUAGE} ] ], + [ 'DROP ACCESS METHOD', [ qr{CASCADE} ] ], + [ 'DROP AGGREGATE', [ qr{ORDER BY} ] ], + [ 'DROP CAST', [ qr{RESTRICT} ] ], + [ 'DROP COLLATION', [ qr{RESTRICT} ] ], + [ 'DROP CONVERSION', [ qr{RESTRICT} ] ], + [ 'DROP DATABASE', [ qr{IF EXISTS} ] ], + [ 'DROP DOMAIN', [ qr{RESTRICT} ] ], + [ 'DROP EVENT TRIGGER', [ qr{RESTRICT} ] ], + [ 'DROP EXTENSION', [ qr{RESTRICT} ] ], + [ 'DROP FOREIGN DATA WRAPPER', [ qr{RESTRICT} ] ], + [ 'DROP FOREIGN TABLE', [ qr{RESTRICT} ] ], + [ 'DROP FUNCTION', [ qr{RESTRICT} ] ], + [ 'DROP GROUP', [ qr{IF EXISTS} ] ], + [ 'DROP INDEX', [ qr{RESTRICT} ] ], + [ 'DROP LANGUAGE', [ qr{RESTRICT} ] ], + [ 'DROP MATERIALIZED VIEW', [ qr{RESTRICT} ] ], + [ 'DROP OPERATOR', [ qr{RESTRICT} ] ], + [ 'DROP OPERATOR CLASS', [ qr{RESTRICT} ] ], + [ 'DROP OPERATOR FAMILY', [ qr{RESTRICT} ] ], + [ 'DROP OWNED', [ qr{CURRENT_USER} ] ], + [ 'DROP POLICY', [ qr{RESTRICT} ] ], + [ 'DROP PROCEDURE', [ qr{RESTRICT} ] ], + [ 'DROP PUBLICATION', [ qr{RESTRICT} ] ], + [ 'DROP ROLE', [ qr{IF EXISTS} ] ], + [ 'DROP ROUTINE', [ qr{RESTRICT} ] ], + [ 'DROP RULE', [ qr{RESTRICT} ] ], + [ 'DROP SCHEMA', [ qr{RESTRICT} ] ], + [ 'DROP SEQUENCE', [ qr{RESTRICT} ] ], + [ 'DROP SERVER', [ qr{RESTRICT} ] ], + [ 'DROP STATISTICS', [ qr{IF EXISTS} ] ], + [ 'DROP SUBSCRIPTION', [ qr{RESTRICT} ] ], + [ 'DROP TABLE', [ qr{RESTRICT} ] ], + [ 'DROP TABLESPACE', [ qr{IF EXISTS} ] ], + [ 'DROP TEXT SEARCH CONFIGURATION', [ qr{RESTRICT} ] ], + [ 'DROP TEXT SEARCH DICTIONARY', [ qr{RESTRICT} ] ], + [ 'DROP TEXT SEARCH PARSER', [ qr{RESTRICT} ] ], + [ 'DROP TEXT SEARCH TEMPLATE', [ qr{RESTRICT} ] ], + [ 'DROP TRANSFORM', [ qr{LANGUAGE} ] ], + [ 'DROP TRIGGER', [ qr{RESTRICT} ] ], + [ 'DROP TYPE', [ qr{RESTRICT} ] ], + [ 'DROP USER', [ qr{IF EXISTS} ] ], + [ 'DROP USER MAPPING', [ qr{SERVER} ] ], + [ 'DROP VIEW', [ qr{RESTRICT} ] ], + [ 'END', [ qr{TRANSACTION}, qr{CHAIN}, qr{(?!COMMIT)} ] ], + [ 'EXECUTE', [ ] ], + [ 'EXPLAIN', [ qr{VERBOSE} ] ], + [ 'FETCH', [ qr{RELATIVE} ] ], + [ 'GRANT', [ qr{ALL TABLES} ] ], + [ 'IMPORT FOREIGN SCHEMA', [ qr{FROM SERVER} ] ], + [ 'INSERT', [ qr{ON CONFLICT} ] ], + [ 'LISTEN', [ ] ], + [ 'LOAD', [ ] ], + [ 'LOCK', [ qr{NOWAIT} ] ], + [ 'MOVE', [ qr{FORWARD} ] ], + [ 'NOTIFY', [ ] ], + [ 'PREPARE', [ qr{AS} ] ], + [ 'PREPARE TRANSACTION', [ ] ], + [ 'REASSIGN OWNED', [ qr{SESSION_USER} ] ], + [ 'REFRESH MATERIALIZED VIEW', [ qr{CONCURRENTLY} ] ], + [ 'REINDEX', [ qr{DATABASE} ] ], + [ 'RELEASE', [ qr{SAVEPOINT} ] ], + [ 'RESET', [ qr{RESET ALL} ] ], + [ 'REVOKE', [ qr{PRIVILEGES} ] ], + [ 'ROLLBACK', [ qr{CHAIN} ] ], + [ 'ROLLBACK PREPARED', [ ]], + [ 'SAVEPOINT', [ ] ], + [ 'SECURITY LABEL', [ qr{SUBSCRIPTION} ] ], + [ 'SELECT', [ qr{RECURSIVE} ] ], + [ 'SET', [ qr{SESSION} ] ], + [ 'SET CONSTRAINTS', [ qr{IMMEDIATE} ] ], + [ 'SET ROLE', [ qr{ROLE NONE} ] ], + [ 'SET SESSION AUTHORIZATION', [ qr{DEFAULT} ] ], + [ 'SET TRANSACTION', [ qr{SNAPSHOT} ] ], + [ 'SHOW', [ qr{SHOW ALL} ] ], + [ 'START TRANSACTION', [ qr{ISOLATION LEVEL}, qr{(?!BEGIN)} ] ], + [ 'TABLE', [ qr{ONLY} ] ], # hmmm... + [ 'TRUNCATE', [ qr{CONTINUE IDENTITY} ] ], + [ 'UNLISTEN', [ ] ], + [ 'UPDATE', [ qr{RETURNING} ] ], + [ 'VACUUM', [ qr{FREEZE} ] ], + [ 'VALUES', [ qr{ORDER BY} ] ], + [ 'WITH', [ qr{RECURSIVE} ] ], # SELECT duplicate? +); + +for my $h (@backslash_help) +{ + my ($cmd, $out) = @$h; + push @$out, qr{$cmd}; + psql('', 0, "\\h $cmd\n", $out, [ qr{^$} ], "psql -- \\h $cmd"); +} + +# special cases +psql('', 0, "\\h ROLLBACK TO SAVEPOINT\n", [ qr{ROLLBACK}, qr{SAVEPOINT} ], + [ qr{^$} ], "psql -- \\h ROLLBACK TO SAVEPOINT"); +psql('', 0, "\\h SELECT INTO\n", [ qr{SELECT}, qr{INTO} ], + [ qr{^$} ], "psql -- \\h SELECT INTO"); + +# +# check describe and other backslash commands +# +# the output can vary significantly, especially with + +my @backslash_out = ( + # empty + [ "\\d\n", [ qr{List of relations}, qr{regress_psql_tap_1_t1}, qr{(?!pg_locks)} ] ], + [ "\\dS\n", [ qr{List of relations}, qr{pg_locks} ] ], + # aggregates + [ "\\da\n", [ qr{List of aggregate functions}, qr{(?!Description)} ] ], + [ "\\da+\n", [ qr{List of aggregate functions}, qr{(Description)} ] ], + [ "\\daS\n", [ qr{array_agg}, qr{avg}, qr{bit_and}, qr{bit_or}, qr{count}, qr{max} ] ], + # access methods + [ "\\dA\n", [ qr{List of access methods}, qr{btree}, qr{hash}, qr{(?!Description)} ] ], + [ "\\dA+\n", [ qr{List of access methods}, qr{btree}, qr{hash}, qr{Description} ] ], + # tablespaces + [ "\\db\n", [ qr{List of tablespaces}, qr{pg_default}, qr{pg_global}, qr{(?!Size)} ] ], + [ "\\db+ pg_def*\n", [ qr{List of tablespaces}, qr{pg_default}, qr{(?!pg_global)}, qr{Size} ] ], + # functions + [ "\\df\n", [ qr{List of functions} ] ], + [ "\\df+\n", [ qr{List of functions} ] ], + [ "\\dftS\n", [ qr{RI_}, qr{Type}, qr{(?!Volatility)}, qr{(?!Parallel)} ] ], + [ "\\dftS+\n", [ qr{Volatility}, qr{Parallel} ] ], + [ "\\dfwS\n", [ qr{lag}, qr{dense_rank}, qr{(?!Owner)}, qr{(?!Language)} ] ], + [ "\\dfwS+\n", [ qr{cume_dist}, qr{Owner}, qr{Language} ] ], + [ "\\dfnS\n", [ qr{abbrev}, qr{(?!bit_or)} ] ], + [ "\\dfaS\n", [ qr{bit_and}, qr{(?!abs)} ] ], + [ "\\dfpS\n", [ qr{List of functions} ] ], + [ "\\dfwtS\n", [ qr{RI_FKey_}, qr{last_value}, qr{nth_value}, qr{(?!bit_and)} ] ], + # type + [ "\\dTS charac*\n", [ qr{List of data types}, qr{character varying}, qr{(?!double)}, qr{(?!Size)} ] ], + [ "\\dTS+\n", [ qr{aclitem}, qr{Size} ] ], + # operator + [ "\\doS\n", [ qr{List of operators}, qr{!~\*} ] ], + [ "\\doS+\n", [ qr{<->}, qr{Result type}] ], + # databases + [ "\\l\n", [ qr{List of databases}, qr{template0}, qr{template1}, qr{(?!Size)} ] ], + [ "\\l a*\n", [ qr{(?!template[01])} ] ], + [ "\\l+\n", [ qr{Size}, qr{Encoding} ] ], + [ "\\list\n", [ qr{List of databases} ] ], + # permissions: well tested + # default acls (empty) + [ "\\ddp\n", [ qr{Default access privileges} ] ], + # descriptions (empty) + [ "\\dd\n", [ qr{Object descriptions} ] ], + # tables: well tested + # roles/users/groups + [ "\\du\n", [ qr{Superuser}, qr{regress_psql_tap_1}, qr{List of roles}, qr{(?!Description)} ] ], + [ "\\du+\n", [ qr{Superuser}, qr{List of roles}, qr{Description} ] ], + [ "\\dg\n", [ qr{Superuser}, qr{List of roles} ] ], + # role settings + [ "\\drds\n", [ qr{List of settings} ] ], + [ "\\drds regress_* *\n", [ qr{List of settings} ] ], + # index/... + [ "\\diS\n", [ qr{List of relations}, qr{pg_am_oid_index} ] ], + [ "\\diS+\n", [ qr{Table} ] ], + # partition tables: well tested + # large objects + [ "\\dl", [ qr{Large objects} ] ], + # languages + [ "\\dL\n", [ qr{List of languages}, qr{plpgsql}, qr{(?!Trusted)} ] ], + [ "\\dL+\n", [ qr{plpgsql}, qr{Trusted} ] ], + # domains + [ "\\dD\n", [ qr{List of domains}, qr{(?!Access privileges)} ] ], + [ "\\dD+\n", [ qr{List of domains}, qr{Access privileges} ] ], + # conversions + [ "\\dcS\n", [ qr{List of conversions}, qr{UTF8}, qr{(?!Description)} ] ], + [ "\\dcS+\n", [ qr{List of conversions}, qr{LATIN1}, qr{Description} ] ], + # event triggers (empty) + [ "\\dy\n", [ qr{List of event triggers}, qr{(?!Description)} ] ], + [ "\\dy+\n", [ qr{List of event triggers}, qr{Description} ] ], + # casts + [ "\\dC\n", [ qr{List of casts}, qr{timestamp without time zone}, qr{(?!Description)} ] ], + [ "\\dC+\n", [ qr{Function}, qr{Implicit}, qr{Description} ] ], + # collations + [ "\\dOS\n", [ qr{List of collations}, qr{Deterministic}, qr{(?!Description)} ] ], + [ "\\dOS+\n", [ qr{POSIX}, qr{Description} ] ], + # schemas + [ "\\dn\n", [ qr{List of schemas}, qr{Owner}, qr{(?!Description)} ] ], + [ "\\dn+\n", [ qr{Access privileges}, qr{Description} ] ], + # text search misc. + [ "\\dFp\n", [ qr{List of text search parsers}, qr{default} ] ], + [ "\\dFp+\n", [ qr{hword}, qr{numword} ] ], + [ "\\dFd\n", [ qr{List of text search dictionaries}, qr{simple} ] ], + [ "\\dFd+\n", [ qr{Init options}, qr{Description} ] ], + [ "\\dFt\n", [ qr{List of text search templates}, qr{simple} ] ], + [ "\\dFt+\n", [ qr{Lexize}, qr{Description} ] ], + [ "\\dF\n", [ qr{List of text search configurations}, qr{simple} ] ], + [ "\\dF+\n", [ qr{email}, qr{url_path} ] ], + # extensions + [ "\\dx\n", [ qr{List of installed extensions}, qr{Version}, qr{plpgsql} ] ], + [ "\\dx+\n", [ qr{Objects in extension}, qr{plpgsql_call_handler} ] ], + # other backslash commands + [ "\\f\n", [ qr{Field separator is} ] ], + [ "\\H\\H\n", [ qr{Output format is html.* is aligned}s ] ], + [ "\\\?\n", [ qr{General}, qr{Help}, qr{Variables}, qr{Large Objects} ] ], + [ "\\C foobar\n", [ qr{Title is "foobar"} ] ], + [ "\\cd /\n\\cd\n", [ qr{^$} ] ], + [ "\\conninfo\n", [ qr{You are connected to database} ] ], + [ "\\copy (SELECT 5432 UNION SELECT 2345 ORDER BY 1) TO STDOUT\n", [ qr/\b2345\b.*\b5432\b/s ] ], + [ "\\copyright\n", [ qr/The Regents of the University of California/ ] ], + [ "\\encoding\n", [ qr/./ ] ], + [ "\\encoding :ENCODING\n", $EMPTY ], + [ "\\errverbose\n", [ qr{There is no previous error} ] ], + [ "\\lo_list\n", [ qr{Large objects} ] ], + [ "\\if true\\q\\endif\n", $EMPTY ], + # ??? + #[ "SELECT md5('hello world');\n\\s\n", [ qr{5eb63bbbe0}, qr{SELECT md5} ] ], + [ "\\set\n", [ qr{ENCODING = }, qr{VERSION_NUM = } ] ], + [ "\\set COMP_KEYWORD_CASE preserve-lower\n\\set COMP_KEYWORD_CASE lower\n" . + "\\set COMP_KEYWORD_CASE upper\n\\echo :COMP_KEYWORD_CASE", [ qr/upper/ ] ], + [ "\\set HISTCONTROL ignorespace\n\\set HISTCONTROL ignoredups\n" . + "\\set HISTCONTROL ignoreboth\n\\echo :HISTCONTROL", [ qr/ignoreboth/ ] ], + [ "\\set ECHO_HIDDEN on\n\\l\n", [ qr/\* QUERY \*/, qr/pg_catalog\.pg_database/, qr/template0/ ] ], + [ "\\set ECHO_HIDDEN noexec\n\\l\n", [ qr/\* QUERY \*/, qr/pg_catalog\.pg_database/, qr/(?!template0)/ ] ], + [ "\\set LAST_ERROR_MESSAGE foo bla\n\\echo :LAST_ERROR_MESSAGE\n", [ qr{foobla} ] ], + [ "\\set AUTOCOMMIT off\n\\set ON_ERROR_ROLLBACK on\nSELECT 5432 AS pg;\nSELECT 2345 AS pg;\n", + [ qr{5432}, qr{2345} ] ], + [ "\\set ON_ERROR_ROLLBACK interactive\n\\echo :ON_ERROR_ROLLBACK\n", [ qr{interactive} ] ], + [ "\\set VERBOSITY verbose\n\\echo :VERBOSITY\n", [ qr{verbose} ] ], + [ "\\set foo bla\n\\echo :foo\\unset foo\\echo :foo\n", [ qr{^bla.*:foo}s ] ], + [ "\\set foo bla\n\\echo :'foo' :\"foo\"\n", [ qr{'bla'}, qr{"bla"} ] ], + [ "\\setenv FOO bla\n\\setenv FOO\n", [ qr{^$} ] ], + [ "\\timing\nSELECT 1;\n\\timing\n", [ qr{Timing is on.*Timing is off}s, qr(Time: \d+\.\d{3} ms) ] ], + [ "\\timing ON\n\\timing OFF\n", [ qr{Timing is on.*Timing is off}s ] ], + [ "\\T foo\n", [ qr{Table attributes are "foo"} ] ], + [ "\\T\n", [ qr{Table attributes unset} ] ], + # help variants + [ "\\?\n", + [ qr{General}, qr{Help}, qr{Query}, qr{Input}, qr{Conditional}, + qr{Informational}, qr{Formatting}, qr{Connection}, + qr{Operating System}, qr{Variables}, qr{Large Objects} ] ], + [ "\\? commands\n", + [ qr{General}, qr{Help}, qr{Query}, qr{Input}, qr{Conditional}, + qr{Informational}, qr{Formatting}, qr{Connection}, + qr{Operating System}, qr{Variables}, qr{Large Objects} ] ], + [ "\\? bad\n", [ qr{General} ] ], + [ "\\? variables\n", + [ qr{psql variables:}, qr{Display settings:}, qr{Environment variables} ] ], + [ "\\? options\n", + [ qr{interactive terminal}, qr{Report bugs} ] ], + # pset format + [ "\\pset format aligned\nSELECT 1 AS one, 2 AS two;\n", + [ qr{Output format is aligned}, qr{ 1 \| 2} ] ], + [ "\\pset format asciidoc\nSELECT 1 AS one, 2 AS two;\n", + [ qr{Output format is asciidoc}, qr{\|1 \|2} ] ], + [ "\\pset format csv\nSELECT 1 AS one, 2 AS two;\n", + [ qr{Output format is csv}, qr{1,2} ] ], + [ "\\pset format html\nSELECT 1 AS one, 2 AS two;\n", + [ qr{Output format is html}, qr{