summaryrefslogtreecommitdiffstats
path: root/src/bin/psql/t/001_basic.pl
blob: 9ac27db212046f9e7627539e570c4410b6eb9364 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
# Copyright (c) 2021-2023, PostgreSQL Global Development Group

use strict;
use warnings;
use locale;

use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;

program_help_ok('psql');
program_version_ok('psql');
program_options_handling_ok('psql');

# Execute a psql command and check its output.
sub psql_like
{
	local $Test::Builder::Level = $Test::Builder::Level + 1;

	my ($node, $sql, $expected_stdout, $test_name) = @_;

	my ($ret, $stdout, $stderr) = $node->psql('postgres', $sql);

	is($ret, 0, "$test_name: exit code 0");
	is($stderr, '', "$test_name: no stderr");
	like($stdout, $expected_stdout, "$test_name: matches");

	return;
}

# Execute a psql command and check that it fails and check the stderr.
sub psql_fails_like
{
	local $Test::Builder::Level = $Test::Builder::Level + 1;

	my ($node, $sql, $expected_stderr, $test_name) = @_;

	# Use the context of a WAL sender, some of the tests rely on that.
	my ($ret, $stdout, $stderr) =
	  $node->psql('postgres', $sql, replication => 'database');

	isnt($ret, 0, "$test_name: exit code not 0");
	like($stderr, $expected_stderr, "$test_name: matches");

	return;
}

# test --help=foo, analogous to program_help_ok()
foreach my $arg (qw(commands variables))
{
	my ($stdout, $stderr);
	my $result;

	$result = IPC::Run::run [ 'psql', "--help=$arg" ], '>', \$stdout, '2>',
	  \$stderr;
	ok($result, "psql --help=$arg exit code 0");
	isnt($stdout, '', "psql --help=$arg goes to stdout");
	is($stderr, '', "psql --help=$arg nothing to stderr");
}

my $node = PostgreSQL::Test::Cluster->new('main');
$node->init(extra => [ '--locale=C', '--encoding=UTF8' ]);
$node->append_conf(
	'postgresql.conf', q{
wal_level = 'logical'
max_replication_slots = 4
max_wal_senders = 4
});
$node->start;

psql_like($node, '\copyright', qr/Copyright/, '\copyright');
psql_like($node, '\help', qr/ALTER/, '\help without arguments');
psql_like($node, '\help SELECT', qr/SELECT/, '\help with argument');

# Test clean handling of unsupported replication command responses
psql_fails_like(
	$node,
	'START_REPLICATION 0/0',
	qr/unexpected PQresultStatus: 8$/,
	'handling of unexpected PQresultStatus');

# test \timing
psql_like(
	$node,
	'\timing on
SELECT 1',
	qr/^1$
^Time: \d+[.,]\d\d\d ms/m,
	'\timing with successful query');

# test \timing with query that fails
{
	my ($ret, $stdout, $stderr) =
	  $node->psql('postgres', "\\timing on\nSELECT error");
	isnt($ret, 0, '\timing with query error: query failed');
	like(
		$stdout,
		qr/^Time: \d+[.,]\d\d\d ms/m,
		'\timing with query error: timing output appears');
	unlike(
		$stdout,
		qr/^Time: 0[.,]000 ms/m,
		'\timing with query error: timing was updated');
}

# test that ENCODING variable is set and that it is updated when
# client encoding is changed
psql_like(
	$node,
	'\echo :ENCODING
set client_encoding = LATIN1;
\echo :ENCODING',
	qr/^UTF8$
^LATIN1$/m,
	'ENCODING variable is set and updated');

# test LISTEN/NOTIFY
psql_like(
	$node,
	'LISTEN foo;
NOTIFY foo;',
	qr/^Asynchronous notification "foo" received from server process with PID \d+\.$/,
	'notification');

psql_like(
	$node,
	"LISTEN foo;
NOTIFY foo, 'bar';",
	qr/^Asynchronous notification "foo" with payload "bar" received from server process with PID \d+\.$/,
	'notification with payload');

# test behavior and output on server crash
my ($ret, $out, $err) = $node->psql('postgres',
		"SELECT 'before' AS running;\n"
	  . "SELECT pg_terminate_backend(pg_backend_pid());\n"
	  . "SELECT 'AFTER' AS not_running;\n");

is($ret, 2, 'server crash: psql exit code');
like($out, qr/before/, 'server crash: output before crash');
ok($out !~ qr/AFTER/, 'server crash: no output after crash');
is( $err,
	'psql:<stdin>:2: FATAL:  terminating connection due to administrator command
psql:<stdin>:2: server closed the connection unexpectedly
	This probably means the server terminated abnormally
	before or while processing the request.
psql:<stdin>:2: error: connection to server was lost',
	'server crash: error message');

# test \errverbose
#
# (This is not in the regular regression tests because the output
# contains the source code location and we don't want to have to
# update that every time it changes.)

psql_like(
	$node,
	'SELECT 1;
\errverbose',
	qr/^1\nThere is no previous error\.$/,
	'\errverbose with no previous error');

# There are three main ways to run a query that might affect
# \errverbose: The normal way, using a cursor by setting FETCH_COUNT,
# and using \gdesc.  Test them all.

like(
	(   $node->psql(
			'postgres',
			"SELECT error;\n\\errverbose",
			on_error_stop => 0))[2],
	qr/\A^psql:<stdin>:1: ERROR:  .*$
^LINE 1: SELECT error;$
^ *^.*$
^psql:<stdin>:2: error: ERROR:  [0-9A-Z]{5}: .*$
^LINE 1: SELECT error;$
^ *^.*$
^LOCATION: .*$/m,
	'\errverbose after normal query with error');

like(
	(   $node->psql(
			'postgres',
			"\\set FETCH_COUNT 1\nSELECT error;\n\\errverbose",
			on_error_stop => 0))[2],
	qr/\A^psql:<stdin>:2: ERROR:  .*$
^LINE 2: SELECT error;$
^ *^.*$
^psql:<stdin>:3: error: ERROR:  [0-9A-Z]{5}: .*$
^LINE 2: SELECT error;$
^ *^.*$
^LOCATION: .*$/m,
	'\errverbose after FETCH_COUNT query with error');

like(
	(   $node->psql(
			'postgres',
			"SELECT error\\gdesc\n\\errverbose",
			on_error_stop => 0))[2],
	qr/\A^psql:<stdin>:1: ERROR:  .*$
^LINE 1: SELECT error$
^ *^.*$
^psql:<stdin>:2: error: ERROR:  [0-9A-Z]{5}: .*$
^LINE 1: SELECT error$
^ *^.*$
^LOCATION: .*$/m,
	'\errverbose after \gdesc with error');

# Check behavior when using multiple -c and -f switches.
# Note that we cannot test backend-side errors as tests are unstable in this
# case: IPC::Run can complain about a SIGPIPE if psql quits before reading a
# query result.
my $tempdir = PostgreSQL::Test::Utils::tempdir;
$node->safe_psql('postgres', "CREATE TABLE tab_psql_single (a int);");

# Tests with ON_ERROR_STOP.
$node->command_ok(
	[
		'psql', '-X',
		'--single-transaction', '-v',
		'ON_ERROR_STOP=1', '-c',
		'INSERT INTO tab_psql_single VALUES (1)', '-c',
		'INSERT INTO tab_psql_single VALUES (2)'
	],
	'ON_ERROR_STOP, --single-transaction and multiple -c switches');
my $row_count =
  $node->safe_psql('postgres', 'SELECT count(*) FROM tab_psql_single');
is($row_count, '2',
	'--single-transaction commits transaction, ON_ERROR_STOP and multiple -c switches'
);

$node->command_fails(
	[
		'psql', '-X',
		'--single-transaction', '-v',
		'ON_ERROR_STOP=1', '-c',
		'INSERT INTO tab_psql_single VALUES (3)', '-c',
		"\\copy tab_psql_single FROM '$tempdir/nonexistent'"
	],
	'ON_ERROR_STOP, --single-transaction and multiple -c switches, error');
$row_count =
  $node->safe_psql('postgres', 'SELECT count(*) FROM tab_psql_single');
is($row_count, '2',
	'client-side error rolls back transaction, ON_ERROR_STOP and multiple -c switches'
);

# Tests mixing files and commands.
my $copy_sql_file = "$tempdir/tab_copy.sql";
my $insert_sql_file = "$tempdir/tab_insert.sql";
append_to_file($copy_sql_file,
	"\\copy tab_psql_single FROM '$tempdir/nonexistent';");
append_to_file($insert_sql_file, 'INSERT INTO tab_psql_single VALUES (4);');
$node->command_ok(
	[
		'psql', '-X', '--single-transaction', '-v',
		'ON_ERROR_STOP=1', '-f', $insert_sql_file, '-f',
		$insert_sql_file
	],
	'ON_ERROR_STOP, --single-transaction and multiple -f switches');
$row_count =
  $node->safe_psql('postgres', 'SELECT count(*) FROM tab_psql_single');
is($row_count, '4',
	'--single-transaction commits transaction, ON_ERROR_STOP and multiple -f switches'
);

$node->command_fails(
	[
		'psql', '-X', '--single-transaction', '-v',
		'ON_ERROR_STOP=1', '-f', $insert_sql_file, '-f',
		$copy_sql_file
	],
	'ON_ERROR_STOP, --single-transaction and multiple -f switches, error');
$row_count =
  $node->safe_psql('postgres', 'SELECT count(*) FROM tab_psql_single');
is($row_count, '4',
	'client-side error rolls back transaction, ON_ERROR_STOP and multiple -f switches'
);

# Tests without ON_ERROR_STOP.
# The last switch fails on \copy.  The command returns a failure and the
# transaction commits.
$node->command_fails(
	[
		'psql', '-X',
		'--single-transaction', '-f',
		$insert_sql_file, '-f',
		$insert_sql_file, '-c',
		"\\copy tab_psql_single FROM '$tempdir/nonexistent'"
	],
	'no ON_ERROR_STOP, --single-transaction and multiple -f/-c switches');
$row_count =
  $node->safe_psql('postgres', 'SELECT count(*) FROM tab_psql_single');
is($row_count, '6',
	'client-side error commits transaction, no ON_ERROR_STOP and multiple -f/-c switches'
);

# The last switch fails on \copy coming from an input file.  The command
# returns a success and the transaction commits.
$node->command_ok(
	[
		'psql', '-X', '--single-transaction', '-f',
		$insert_sql_file, '-f', $insert_sql_file, '-f',
		$copy_sql_file
	],
	'no ON_ERROR_STOP, --single-transaction and multiple -f switches');
$row_count =
  $node->safe_psql('postgres', 'SELECT count(*) FROM tab_psql_single');
is($row_count, '8',
	'client-side error commits transaction, no ON_ERROR_STOP and multiple -f switches'
);

# The last switch makes the command return a success, and the contents of
# the transaction commit even if there is a failure in-between.
$node->command_ok(
	[
		'psql', '-X',
		'--single-transaction', '-c',
		'INSERT INTO tab_psql_single VALUES (5)', '-f',
		$copy_sql_file, '-c',
		'INSERT INTO tab_psql_single VALUES (6)'
	],
	'no ON_ERROR_STOP, --single-transaction and multiple -c switches');
$row_count =
  $node->safe_psql('postgres', 'SELECT count(*) FROM tab_psql_single');
is($row_count, '10',
	'client-side error commits transaction, no ON_ERROR_STOP and multiple -c switches'
);

# Test \copy from with DEFAULT option
$node->safe_psql(
	'postgres',
	"CREATE TABLE copy_default (
		id integer PRIMARY KEY,
		text_value text NOT NULL DEFAULT 'test',
		ts_value timestamp without time zone NOT NULL DEFAULT '2022-07-05'
	)"
);

my $copy_default_sql_file = "$tempdir/copy_default.csv";
append_to_file($copy_default_sql_file, "1,value,2022-07-04\n");
append_to_file($copy_default_sql_file, "2,placeholder,2022-07-03\n");
append_to_file($copy_default_sql_file, "3,placeholder,placeholder\n");

psql_like(
	$node,
	"\\copy copy_default from $copy_default_sql_file with (format 'csv', default 'placeholder');
	SELECT * FROM copy_default",
	qr/1\|value\|2022-07-04 00:00:00
2|test|2022-07-03 00:00:00
3|test|2022-07-05 00:00:00/,
	'\copy from with DEFAULT');

# Check \watch
# Note: the interval value is parsed with locale-aware strtod()
psql_like($node, sprintf('SELECT 1 \watch c=3 i=%g', 0.01),
	qr/1\n1\n1/, '\watch with 3 iterations');

# Check \watch errors
psql_fails_like(
	$node,
	'SELECT 1 \watch -10',
	qr/incorrect interval value "-10"/,
	'\watch, negative interval');
psql_fails_like(
	$node,
	'SELECT 1 \watch 10ab',
	qr/incorrect interval value "10ab"/,
	'\watch, incorrect interval');
psql_fails_like(
	$node,
	'SELECT 1 \watch 10e400',
	qr/incorrect interval value "10e400"/,
	'\watch, out-of-range interval');
psql_fails_like(
	$node,
	'SELECT 1 \watch 1 1',
	qr/interval value is specified more than once/,
	'\watch, interval value is specified more than once');
psql_fails_like(
	$node,
	'SELECT 1 \watch c=1 c=1',
	qr/iteration count is specified more than once/,
	'\watch, iteration count is specified more than once');

done_testing();