1
0
mirror of https://github.com/postgres/postgres.git synced 2025-05-28 05:21:27 +03:00
postgres/src/bin/pgbench/t/001_pgbench_with_server.pl
Michael Paquier 7668e85a40 Revert "Handle better implicit transaction state of pipeline mode"
This reverts commit d77f91214fb7 on all stable branches, due to concerns
regarding the compatility side effects this could create in a minor
release.  The change still exists on HEAD.

Discussion: https://postgr.es/m/CA+TgmoZqRgeFTg4+Yf_CMRRXiHuNz1u6ZC4FvVk+rxw0RmOPnw@mail.gmail.com
Backpatch-through: 13
2024-11-28 09:43:21 +09:00

1596 lines
43 KiB
Perl

# Copyright (c) 2021-2024, PostgreSQL Global Development Group
use strict;
use warnings FATAL => 'all';
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
# Check the initial state of the data generated. Tables for tellers and
# branches use NULL for their filler attribute. The table accounts uses
# a non-NULL filler. The history table should have no data.
sub check_data_state
{
local $Test::Builder::Level = $Test::Builder::Level + 1;
my $node = shift;
my $type = shift;
my $sql_result = $node->safe_psql('postgres',
'SELECT count(*) AS null_count FROM pgbench_accounts WHERE filler IS NULL LIMIT 10;'
);
is($sql_result, '0',
"$type: filler column of pgbench_accounts has no NULL data");
$sql_result = $node->safe_psql('postgres',
'SELECT count(*) AS null_count FROM pgbench_branches WHERE filler IS NULL;'
);
is($sql_result, '1',
"$type: filler column of pgbench_branches has only NULL data");
$sql_result = $node->safe_psql('postgres',
'SELECT count(*) AS null_count FROM pgbench_tellers WHERE filler IS NULL;'
);
is($sql_result, '10',
"$type: filler column of pgbench_tellers has only NULL data");
$sql_result = $node->safe_psql('postgres',
'SELECT count(*) AS data_count FROM pgbench_history;');
is($sql_result, '0', "$type: pgbench_history has no data");
}
# start a pgbench specific server
my $node = PostgreSQL::Test::Cluster->new('main');
# Set to untranslated messages, to be able to compare program output with
# expected strings.
$node->init(extra => [ '--locale', 'C' ]);
$node->start;
# tablespace for testing, because partitioned tables cannot use pg_default
# explicitly and we want to test that table creation with tablespace works
# for partitioned tables.
my $ts = $node->basedir . '/regress_pgbench_tap_1_ts_dir';
mkdir $ts or die "cannot create directory $ts";
# the next commands will issue a syntax error if the path contains a "'"
$node->safe_psql('postgres',
"CREATE TABLESPACE regress_pgbench_tap_1_ts LOCATION '$ts';");
# Test concurrent OID generation via pg_enum_oid_index. This indirectly
# exercises LWLock and spinlock concurrency.
my $labels = join ',', map { "'l$_'" } 1 .. 1000;
$node->pgbench(
'--no-vacuum --client=5 --protocol=prepared --transactions=25',
0,
[qr{processed: 125/125}],
[qr{^$}],
'concurrent OID generation',
{
'001_pgbench_concurrent_insert' =>
"CREATE TYPE pg_temp.e AS ENUM ($labels); DROP TYPE pg_temp.e;"
});
# Test inplace updates from VACUUM concurrent with heap_update from GRANT.
# The PROC_IN_VACUUM environment can't finish MVCC table scans consistently,
# so this fails rarely. To reproduce consistently, add a sleep after
# GetCatalogSnapshot(non-catalog-rel).
Test::More->builder->todo_start('PROC_IN_VACUUM scan breakage');
$node->safe_psql('postgres', 'CREATE TABLE ddl_target ()');
$node->pgbench(
'--no-vacuum --client=5 --protocol=prepared --transactions=50',
0,
[qr{processed: 250/250}],
[qr{^$}],
'concurrent GRANT/VACUUM',
{
'001_pgbench_grant@9' => q(
DO $$
BEGIN
PERFORM pg_advisory_xact_lock(42);
FOR i IN 1 .. 10 LOOP
GRANT SELECT ON ddl_target TO PUBLIC;
REVOKE SELECT ON ddl_target FROM PUBLIC;
END LOOP;
END
$$;
),
'001_pgbench_vacuum_ddl_target@1' => "VACUUM ddl_target;",
});
Test::More->builder->todo_end;
# Trigger various connection errors
$node->pgbench(
'no-such-database',
1,
[qr{^$}],
[
qr{connection to server .* failed},
qr{FATAL: database "no-such-database" does not exist}
],
'no such database');
$node->pgbench(
'-S -t 1', 1, [],
[qr{Perhaps you need to do initialization}],
'run without init');
# Initialize pgbench tables scale 1
$node->pgbench(
'-i', 0,
[qr{^$}],
[
qr{creating tables},
qr{vacuuming},
qr{creating primary keys},
qr{done in \d+\.\d\d s }
],
'pgbench scale 1 initialization',);
# Check data state, after client-side data generation.
check_data_state($node, 'client-side');
# Again, with all possible options
$node->pgbench(
'--initialize --init-steps=dtpvg --scale=1 --unlogged-tables --fillfactor=98 --foreign-keys --quiet --tablespace=regress_pgbench_tap_1_ts --index-tablespace=regress_pgbench_tap_1_ts --partitions=2 --partition-method=hash',
0,
[qr{^$}i],
[
qr{dropping old tables},
qr{creating tables},
qr{creating 2 partitions},
qr{vacuuming},
qr{creating primary keys},
qr{creating foreign keys},
qr{(?!vacuuming)}, # no vacuum
qr{done in \d+\.\d\d s }
],
'pgbench scale 1 initialization');
# Test interaction of --init-steps with legacy step-selection options
$node->pgbench(
'--initialize --init-steps=dtpvGvv --no-vacuum --foreign-keys --unlogged-tables --partitions=3',
0,
[qr{^$}],
[
qr{dropping old tables},
qr{creating tables},
qr{creating 3 partitions},
qr{creating primary keys},
qr{generating data \(server-side\)},
qr{creating foreign keys},
qr{(?!vacuuming)}, # no vacuum
qr{done in \d+\.\d\d s }
],
'pgbench --init-steps');
# Check data state, after server-side data generation.
check_data_state($node, 'server-side');
# Run all builtin scripts, for a few transactions each
$node->pgbench(
'--transactions=5 -Dfoo=bla --client=2 --protocol=simple --builtin=t'
. ' --connect -n -v -n',
0,
[
qr{builtin: TPC-B},
qr{clients: 2\b},
qr{processed: 10/10},
qr{mode: simple},
qr{maximum number of tries: 1}
],
[qr{^$}],
'pgbench tpcb-like');
$node->pgbench(
'--transactions=20 --client=5 -M extended --builtin=si -C --no-vacuum -s 1',
0,
[
qr{builtin: simple update},
qr{clients: 5\b},
qr{threads: 1\b},
qr{processed: 100/100},
qr{mode: extended}
],
[qr{scale option ignored}],
'pgbench simple update');
$node->pgbench(
'-t 100 -c 7 -M prepared -b se --debug',
0,
[
qr{builtin: select only},
qr{clients: 7\b},
qr{threads: 1\b},
qr{processed: 700/700},
qr{mode: prepared}
],
[
qr{vacuum}, qr{client 0}, qr{client 1}, qr{sending},
qr{receiving}, qr{executing}
],
'pgbench select only');
# check if threads are supported
my $nthreads = 2;
{
my ($stderr);
run_log([ 'pgbench', '-j', '2', '--bad-option' ], '2>', \$stderr);
$nthreads = 1 if $stderr =~ m/threads are not supported on this platform/;
}
# run custom scripts
$node->pgbench(
"-t 100 -c 1 -j $nthreads -M prepared -n",
0,
[
qr{type: multiple scripts},
qr{mode: prepared},
qr{script 1: .*/001_pgbench_custom_script_1},
qr{weight: 2},
qr{script 2: .*/001_pgbench_custom_script_2},
qr{weight: 1},
qr{processed: 100/100}
],
[qr{^$}],
'pgbench custom scripts',
{
'001_pgbench_custom_script_1@1' => q{-- select only
\set aid random(1, :scale * 100000)
SELECT abalance::INTEGER AS balance
FROM pgbench_accounts
WHERE aid=:aid;
},
'001_pgbench_custom_script_2@2' => q{-- special variables
BEGIN;
\set foo 1
-- cast are needed for typing under -M prepared
SELECT :foo::INT + :scale::INT * :client_id::INT AS bla;
COMMIT;
}
});
$node->pgbench(
'-n -t 10 -c 1 -M simple',
0,
[
qr{type: .*/001_pgbench_custom_script_3},
qr{processed: 10/10},
qr{mode: simple}
],
[qr{^$}],
'pgbench custom script',
{
'001_pgbench_custom_script_3' => q{-- select only variant
\set aid random(1, :scale * 100000)
BEGIN;
SELECT abalance::INTEGER AS balance
FROM pgbench_accounts
WHERE aid=:aid;
COMMIT;
}
});
$node->pgbench(
'-n -t 10 -c 2 -M extended',
0,
[
qr{type: .*/001_pgbench_custom_script_4},
qr{processed: 20/20},
qr{mode: extended}
],
[qr{^$}],
'pgbench custom script',
{
'001_pgbench_custom_script_4' => q{-- select only variant
\set aid random(1, :scale * 100000)
BEGIN;
SELECT abalance::INTEGER AS balance
FROM pgbench_accounts
WHERE aid=:aid;
COMMIT;
}
});
# Verify server logging of query parameters.
# (This doesn't really belong here, but pgbench is a convenient way
# to issue commands using extended query mode with parameters.)
# 1. Logging neither with errors nor with statements
$node->append_conf('postgresql.conf',
"log_min_duration_statement = 0\n"
. "log_parameter_max_length = 0\n"
. "log_parameter_max_length_on_error = 0");
$node->reload;
$node->pgbench(
'-n -t1 -c1 -M prepared',
2,
[],
[
qr{ERROR: invalid input syntax for type json},
qr{(?!unnamed portal with parameters)}
],
'server parameter logging',
{
'001_param_1' => q[select '{ invalid ' as value \gset
select $$'Valame Dios!' dijo Sancho; 'no le dije yo a vuestra merced que mirase bien lo que hacia?'$$ as long \gset
select column1::jsonb from (values (:value), (:long)) as q;
]
});
my $log = PostgreSQL::Test::Utils::slurp_file($node->logfile);
unlike(
$log,
qr[DETAIL: Parameters: \$1 = '\{ invalid ',],
"no parameters logged");
$log = undef;
# 2. Logging truncated parameters on error, full with statements
$node->append_conf('postgresql.conf',
"log_parameter_max_length = -1\n"
. "log_parameter_max_length_on_error = 64");
$node->reload;
$node->pgbench(
'-n -t1 -c1 -M prepared',
2,
[],
[
qr{ERROR: division by zero},
qr{CONTEXT: unnamed portal with parameters: \$1 = '1', \$2 = NULL}
],
'server parameter logging',
{
'001_param_2' => q{select '1' as one \gset
SELECT 1 / (random() / 2)::int, :one::int, :two::int;
}
});
$node->pgbench(
'-n -t1 -c1 -M prepared',
2,
[],
[
qr{ERROR: invalid input syntax for type json},
qr[CONTEXT: JSON data, line 1: \{ invalid\.\.\.[\r\n]+unnamed portal with parameters: \$1 = '\{ invalid ', \$2 = '''Valame Dios!'' dijo Sancho; ''no le dije yo a vuestra merced que \.\.\.']m
],
'server parameter logging',
{
'001_param_3' => q[select '{ invalid ' as value \gset
select $$'Valame Dios!' dijo Sancho; 'no le dije yo a vuestra merced que mirase bien lo que hacia?'$$ as long \gset
select column1::jsonb from (values (:value), (:long)) as q;
]
});
$log = PostgreSQL::Test::Utils::slurp_file($node->logfile);
like(
$log,
qr[DETAIL: Parameters: \$1 = '\{ invalid ', \$2 = '''Valame Dios!'' dijo Sancho; ''no le dije yo a vuestra merced que mirase bien lo que hacia\?'''],
"parameter report does not truncate");
$log = undef;
# 3. Logging full parameters on error, truncated with statements
$node->append_conf('postgresql.conf',
"log_min_duration_statement = -1\n"
. "log_parameter_max_length = 7\n"
. "log_parameter_max_length_on_error = -1");
$node->reload;
$node->pgbench(
'-n -t1 -c1 -M prepared',
2,
[],
[
qr{ERROR: division by zero},
qr{CONTEXT: unnamed portal with parameters: \$1 = '1', \$2 = NULL}
],
'server parameter logging',
{
'001_param_4' => q{select '1' as one \gset
SELECT 1 / (random() / 2)::int, :one::int, :two::int;
}
});
$node->append_conf('postgresql.conf', "log_min_duration_statement = 0");
$node->reload;
$node->pgbench(
'-n -t1 -c1 -M prepared',
2,
[],
[
qr{ERROR: invalid input syntax for type json},
qr[CONTEXT: JSON data, line 1: \{ invalid\.\.\.[\r\n]+unnamed portal with parameters: \$1 = '\{ invalid ', \$2 = '''Valame Dios!'' dijo Sancho; ''no le dije yo a vuestra merced que mirase bien lo que hacia\?']m
],
'server parameter logging',
{
'001_param_5' => q[select '{ invalid ' as value \gset
select $$'Valame Dios!' dijo Sancho; 'no le dije yo a vuestra merced que mirase bien lo que hacia?'$$ as long \gset
select column1::jsonb from (values (:value), (:long)) as q;
]
});
$log = PostgreSQL::Test::Utils::slurp_file($node->logfile);
like(
$log,
qr[DETAIL: Parameters: \$1 = '\{ inval\.\.\.', \$2 = '''Valame\.\.\.'],
"parameter report truncates");
$log = undef;
# Check that bad parameters are reported during typinput phase of BIND
$node->pgbench(
'-n -t1 -c1 -M prepared',
2,
[],
[
qr{ERROR: invalid input syntax for type smallint: "1a"},
qr{CONTEXT: unnamed portal parameter \$2 = '1a'}
],
'server parameter logging',
{
'001_param_6' => q{select 42 as value1, '1a' as value2 \gset
select :value1::smallint, :value2::smallint;
}
});
# Restore default logging config
$node->append_conf('postgresql.conf',
"log_min_duration_statement = -1\n"
. "log_parameter_max_length_on_error = 0\n"
. "log_parameter_max_length = -1");
$node->reload;
# test expressions
$node->pgbench(
'--random-seed=5432 -t 1 -Dfoo=-10.1 -Dbla=false -Di=+3 -Dn=null -Dt=t -Df=of -Dd=1.0',
0,
[ qr{type: .*/001_pgbench_expressions}, qr{processed: 1/1} ],
[
qr{setting random seed to 5432\b},
# After explicit seeding, the four random checks (1-3,20) are
# deterministic; but see also magic values in checks 111,113.
qr{command=1.: int 17\b}, # uniform random
qr{command=2.: int 104\b}, # exponential random
qr{command=3.: int 1498\b}, # gaussian random
qr{command=4.: int 4\b},
qr{command=5.: int 5\b},
qr{command=6.: int 6\b},
qr{command=7.: int 7\b},
qr{command=8.: int 8\b},
qr{command=9.: int 9\b},
qr{command=10.: int 10\b},
qr{command=11.: int 11\b},
qr{command=12.: int 12\b},
qr{command=15.: double 15\b},
qr{command=16.: double 16\b},
qr{command=17.: double 17\b},
qr{command=20.: int 3\b}, # zipfian random
qr{command=21.: double -27\b},
qr{command=22.: double 1024\b},
qr{command=23.: double 1\b},
qr{command=24.: double 1\b},
qr{command=25.: double -0.125\b},
qr{command=26.: double -0.125\b},
qr{command=27.: double -0.00032\b},
qr{command=28.: double 8.50705917302346e\+0?37\b},
qr{command=29.: double 1e\+0?30\b},
qr{command=30.: boolean false\b},
qr{command=31.: boolean true\b},
qr{command=32.: int 32\b},
qr{command=33.: int 33\b},
qr{command=34.: double 34\b},
qr{command=35.: int 35\b},
qr{command=36.: int 36\b},
qr{command=37.: double 37\b},
qr{command=38.: int 38\b},
qr{command=39.: int 39\b},
qr{command=40.: boolean true\b},
qr{command=41.: null\b},
qr{command=42.: null\b},
qr{command=43.: boolean true\b},
qr{command=44.: boolean true\b},
qr{command=45.: boolean true\b},
qr{command=46.: int 46\b},
qr{command=47.: boolean true\b},
qr{command=48.: boolean true\b},
qr{command=49.: int -5817877081768721676\b},
qr{command=50.: boolean true\b},
qr{command=51.: int -7793829335365542153\b},
qr{command=52.: int -?\d+\b},
qr{command=53.: boolean true\b},
qr{command=65.: int 65\b},
qr{command=74.: int 74\b},
qr{command=83.: int 83\b},
qr{command=86.: int 86\b},
qr{command=93.: int 93\b},
qr{command=95.: int 0\b},
qr{command=96.: int 1\b}, # :scale
qr{command=97.: int 0\b}, # :client_id
qr{command=98.: int 5432\b}, # :random_seed
qr{command=99.: int -9223372036854775808\b}, # min int
qr{command=100.: int 9223372036854775807\b}, # max int
# pseudorandom permutation tests
qr{command=101.: boolean true\b},
qr{command=102.: boolean true\b},
qr{command=103.: boolean true\b},
qr{command=104.: boolean true\b},
qr{command=105.: boolean true\b},
qr{command=109.: boolean true\b},
qr{command=110.: boolean true\b},
qr{command=111.: boolean true\b},
qr{command=113.: boolean true\b},
],
'pgbench expressions',
{
'001_pgbench_expressions' => q{-- integer functions
\set i1 debug(random(10, 19))
\set i2 debug(random_exponential(100, 199, 10.0))
\set i3 debug(random_gaussian(1000, 1999, 10.0))
\set i4 debug(abs(-4))
\set i5 debug(greatest(5, 4, 3, 2))
\set i6 debug(11 + least(-5, -4, -3, -2))
\set i7 debug(int(7.3))
-- integer arithmetic and bit-wise operators
\set i8 debug(17 / (4|1) + ( 4 + (7 >> 2)))
\set i9 debug(- (3 * 4 - (-(~ 1) + -(~ 0))) / -1 + 3 % -1)
\set ia debug(10 + (0 + 0 * 0 - 0 / 1))
\set ib debug(:ia + :scale)
\set ic debug(64 % (((2 + 1 * 2 + (1 # 2) | 4 * (2 & 11)) - (1 << 2)) + 2))
-- double functions and operators
\set d1 debug(sqrt(+1.5 * 2.0) * abs(-0.8E1))
\set d2 debug(double(1 + 1) * (-75.0 / :foo))
\set pi debug(pi() * 4.9)
\set d4 debug(greatest(4, 2, -1.17) * 4.0 * Ln(Exp(1.0)))
\set d5 debug(least(-5.18, .0E0, 1.0/0) * -3.3)
-- reset variables
\set i1 0
\set d1 false
-- yet another integer function
\set id debug(random_zipfian(1, 9, 1.3))
--- pow and power
\set poweri debug(pow(-3,3))
\set powerd debug(pow(2.0,10))
\set poweriz debug(pow(0,0))
\set powerdz debug(pow(0.0,0.0))
\set powernegi debug(pow(-2,-3))
\set powernegd debug(pow(-2.0,-3.0))
\set powernegd2 debug(power(-5.0,-5.0))
\set powerov debug(pow(9223372036854775807, 2))
\set powerov2 debug(pow(10,30))
-- comparisons and logical operations
\set c0 debug(1.0 = 0.0 and 1.0 != 0.0)
\set c1 debug(0 = 1 Or 1.0 = 1)
\set c4 debug(case when 0 < 1 then 32 else 0 end)
\set c5 debug(case when true then 33 else 0 end)
\set c6 debug(case when false THEN -1 when 1 = 1 then 13 + 19 + 2.0 end )
\set c7 debug(case when (1 > 0) and (1 >= 0) and (0 < 1) and (0 <= 1) and (0 != 1) and (0 = 0) and (0 <> 1) then 35 else 0 end)
\set c8 debug(CASE \
WHEN (1.0 > 0.0) AND (1.0 >= 0.0) AND (0.0 < 1.0) AND (0.0 <= 1.0) AND \
(0.0 != 1.0) AND (0.0 = 0.0) AND (0.0 <> 1.0) AND (0.0 = 0.0) \
THEN 36 \
ELSE 0 \
END)
\set c9 debug(CASE WHEN NOT FALSE THEN 3 * 12.3333334 END)
\set ca debug(case when false then 0 when 1-1 <> 0 then 1 else 38 end)
\set cb debug(10 + mod(13 * 7 + 12, 13) - mod(-19 * 11 - 17, 19))
\set cc debug(NOT (0 > 1) AND (1 <= 1) AND NOT (0 >= 1) AND (0 < 1) AND \
NOT (false and true) AND (false OR TRUE) AND (NOT :f) AND (NOT FALSE) AND \
NOT (NOT TRUE))
-- NULL value and associated operators
\set n0 debug(NULL + NULL * exp(NULL))
\set n1 debug(:n0)
\set n2 debug(NOT (:n0 IS NOT NULL OR :d1 IS NULL))
\set n3 debug(:n0 IS NULL AND :d1 IS NOT NULL AND :d1 NOTNULL)
\set n4 debug(:n0 ISNULL AND NOT :n0 IS TRUE AND :n0 IS NOT FALSE)
\set n5 debug(CASE WHEN :n IS NULL THEN 46 ELSE NULL END)
-- use a variables of all types
\set n6 debug(:n IS NULL AND NOT :f AND :t)
-- conditional truth
\set cs debug(CASE WHEN 1 THEN TRUE END AND CASE WHEN 1.0 THEN TRUE END AND CASE WHEN :n THEN NULL ELSE TRUE END)
-- hash functions
\set h0 debug(hash(10, 5432))
\set h1 debug(:h0 = hash_murmur2(10, 5432))
\set h3 debug(hash_fnv1a(10, 5432))
\set h4 debug(hash(10))
\set h5 debug(hash(10) = hash(10, :default_seed))
-- lazy evaluation
\set zy 0
\set yz debug(case when :zy = 0 then -1 else (1 / :zy) end)
\set yz debug(case when :zy = 0 or (1 / :zy) < 0 then -1 else (1 / :zy) end)
\set yz debug(case when :zy > 0 and (1 / :zy) < 0 then (1 / :zy) else 1 end)
-- substitute variables of all possible types
\set v0 NULL
\set v1 TRUE
\set v2 5432
\set v3 -54.21E-2
SELECT :v0, :v1, :v2, :v3;
-- if tests
\set nope 0
\if 1 > 0
\set id debug(65)
\elif 0
\set nope 1
\else
\set nope 1
\endif
\if 1 < 0
\set nope 1
\elif 1 > 0
\set ie debug(74)
\else
\set nope 1
\endif
\if 1 < 0
\set nope 1
\elif 1 < 0
\set nope 1
\else
\set if debug(83)
\endif
\if 1 = 1
\set ig debug(86)
\elif 0
\set nope 1
\endif
\if 1 = 0
\set nope 1
\elif 1 <> 0
\set ih debug(93)
\endif
-- must be zero if false branches where skipped
\set nope debug(:nope)
-- check automatic variables
\set sc debug(:scale)
\set ci debug(:client_id)
\set rs debug(:random_seed)
-- minint constant parsing
\set min debug(-9223372036854775808)
\set max debug(-(:min + 1))
-- parametric pseudorandom permutation function
\set t debug(permute(0, 2) + permute(1, 2) = 1)
\set t debug(permute(0, 3) + permute(1, 3) + permute(2, 3) = 3)
\set t debug(permute(0, 4) + permute(1, 4) + permute(2, 4) + permute(3, 4) = 6)
\set t debug(permute(0, 5) + permute(1, 5) + permute(2, 5) + permute(3, 5) + permute(4, 5) = 10)
\set t debug(permute(0, 16) + permute(1, 16) + permute(2, 16) + permute(3, 16) + \
permute(4, 16) + permute(5, 16) + permute(6, 16) + permute(7, 16) + \
permute(8, 16) + permute(9, 16) + permute(10, 16) + permute(11, 16) + \
permute(12, 16) + permute(13, 16) + permute(14, 16) + permute(15, 16) = 120)
-- random sanity checks
\set size random(2, 1000)
\set v random(0, :size - 1)
\set p permute(:v, :size)
\set t debug(0 <= :p and :p < :size and :p = permute(:v + :size, :size) and :p <> permute(:v + 1, :size))
-- actual values
\set t debug(permute(:v, 1) = 0)
\set t debug(permute(0, 2, 5431) = 0 and permute(1, 2, 5431) = 1 and \
permute(0, 2, 5433) = 1 and permute(1, 2, 5433) = 0)
-- check permute's portability across architectures
\set size debug(:max - 10)
\set t debug(permute(:size-1, :size, 5432) = 520382784483822430 and \
permute(:size-2, :size, 5432) = 1143715004660802862 and \
permute(:size-3, :size, 5432) = 447293596416496998 and \
permute(:size-4, :size, 5432) = 916527772266572956 and \
permute(:size-5, :size, 5432) = 2763809008686028849 and \
permute(:size-6, :size, 5432) = 8648551549198294572 and \
permute(:size-7, :size, 5432) = 4542876852200565125)
}
});
# random determinism when seeded
$node->safe_psql('postgres',
'CREATE UNLOGGED TABLE seeded_random(seed INT8 NOT NULL, rand TEXT NOT NULL, val INTEGER NOT NULL);'
);
# same value to check for determinism
my $seed = int(rand(1000000000));
for my $i (1, 2)
{
$node->pgbench(
"--random-seed=$seed -t 1",
0,
[qr{processed: 1/1}],
[qr{setting random seed to $seed\b}],
"random seeded with $seed",
{
"001_pgbench_random_seed_$i" => q{-- test random functions
\set ur random(1000, 1999)
\set er random_exponential(2000, 2999, 2.0)
\set gr random_gaussian(3000, 3999, 3.0)
\set zr random_zipfian(4000, 4999, 1.5)
INSERT INTO seeded_random(seed, rand, val) VALUES
(:random_seed, 'uniform', :ur),
(:random_seed, 'exponential', :er),
(:random_seed, 'gaussian', :gr),
(:random_seed, 'zipfian', :zr);
}
});
}
# check that all runs generated the same 4 values
my ($ret, $out, $err) = $node->psql('postgres',
'SELECT seed, rand, val, COUNT(*) FROM seeded_random GROUP BY seed, rand, val'
);
ok($ret == 0, "psql seeded_random count ok");
ok($err eq '', "psql seeded_random count stderr is empty");
ok($out =~ /\b$seed\|uniform\|1\d\d\d\|2/,
"psql seeded_random count uniform");
ok( $out =~ /\b$seed\|exponential\|2\d\d\d\|2/,
"psql seeded_random count exponential");
ok( $out =~ /\b$seed\|gaussian\|3\d\d\d\|2/,
"psql seeded_random count gaussian");
ok($out =~ /\b$seed\|zipfian\|4\d\d\d\|2/,
"psql seeded_random count zipfian");
$node->safe_psql('postgres', 'DROP TABLE seeded_random;');
# backslash commands
$node->pgbench(
'-t 1', 0,
[
qr{type: .*/001_pgbench_backslash_commands},
qr{processed: 1/1},
qr{shell-echo-output}
],
[qr{command=8.: int 1\b}],
'pgbench backslash commands',
{
'001_pgbench_backslash_commands' => q{-- run set
\set zero 0
\set one 1.0
-- sleep
\sleep :one ms
\sleep 100 us
\sleep 0 s
\sleep :zero
-- setshell and continuation
\setshell another_one\
echo \
:one
\set n debug(:another_one)
-- shell
\shell echo shell-echo-output
}
});
# working \gset
$node->pgbench(
'-t 1', 0,
[ qr{type: .*/001_pgbench_gset}, qr{processed: 1/1} ],
[
qr{command=3.: int 0\b},
qr{command=5.: int 1\b},
qr{command=6.: int 2\b},
qr{command=8.: int 3\b},
qr{command=10.: int 4\b},
qr{command=12.: int 5\b}
],
'pgbench gset command',
{
'001_pgbench_gset' => q{-- test gset
-- no columns
SELECT \gset
-- one value
SELECT 0 AS i0 \gset
\set i debug(:i0)
-- two values
SELECT 1 AS i1, 2 AS i2 \gset
\set i debug(:i1)
\set i debug(:i2)
-- with prefix
SELECT 3 AS i3 \gset x_
\set i debug(:x_i3)
-- overwrite existing variable
SELECT 0 AS i4, 4 AS i4 \gset
\set i debug(:i4)
-- work on the last SQL command under \;
\; \; SELECT 0 AS i5 \; SELECT 5 AS i5 \; \; \gset
\set i debug(:i5)
}
});
# \gset cannot accept more than one row, causing command to fail.
$node->pgbench(
'-t 1', 2,
[ qr{type: .*/001_pgbench_gset_two_rows}, qr{processed: 0/1} ],
[qr{expected one row, got 2\b}],
'pgbench gset command with two rows',
{
'001_pgbench_gset_two_rows' => q{
SELECT 5432 AS fail UNION SELECT 5433 ORDER BY 1 \gset
}
});
# working \aset
# Valid cases.
$node->pgbench(
'-t 1', 0,
[ qr{type: .*/001_pgbench_aset}, qr{processed: 1/1} ],
[ qr{command=3.: int 8\b}, qr{command=4.: int 7\b} ],
'pgbench aset command',
{
'001_pgbench_aset' => q{
-- test aset, which applies to a combined query
\; SELECT 6 AS i6 \; SELECT 7 AS i7 \; \aset
-- unless it returns more than one row, last is kept
SELECT 8 AS i6 UNION SELECT 9 ORDER BY 1 DESC \aset
\set i debug(:i6)
\set i debug(:i7)
}
});
# Empty result set with \aset, causing command to fail.
$node->pgbench(
'-t 1', 2,
[ qr{type: .*/001_pgbench_aset_empty}, qr{processed: 0/1} ],
[
qr{undefined variable \"i8\"},
qr{evaluation of meta-command failed\b}
],
'pgbench aset command with empty result',
{
'001_pgbench_aset_empty' => q{
-- empty result
\; SELECT 5432 AS i8 WHERE FALSE \; \aset
\set i debug(:i8)
}
});
# Working \startpipeline
$node->pgbench(
'-t 1 -n -M extended',
0,
[ qr{type: .*/001_pgbench_pipeline}, qr{actually processed: 1/1} ],
[],
'working \startpipeline',
{
'001_pgbench_pipeline' => q{
-- test startpipeline
\startpipeline
} . "select 1;\n" x 10 . q{
\endpipeline
}
});
# Working \startpipeline with \syncpipeline
$node->pgbench(
'-t 1 -n -M extended',
0,
[ qr{type: .*/001_pgbench_pipeline_sync}, qr{actually processed: 1/1} ],
[],
'working \startpipeline with \syncpipeline',
{
'001_pgbench_pipeline_sync' => q{
-- test startpipeline
\startpipeline
select 1;
\syncpipeline
\syncpipeline
select 2;
\syncpipeline
select 3;
\endpipeline
}
});
# Working \startpipeline in prepared query mode
$node->pgbench(
'-t 1 -n -M prepared',
0,
[ qr{type: .*/001_pgbench_pipeline_prep}, qr{actually processed: 1/1} ],
[],
'working \startpipeline',
{
'001_pgbench_pipeline_prep' => q{
-- test startpipeline
\startpipeline
\endpipeline
\startpipeline
} . "select 1;\n" x 10 . q{
\endpipeline
}
});
# Try \startpipeline twice
$node->pgbench(
'-t 1 -n -M extended',
2,
[],
[qr{already in pipeline mode}],
'error: call \startpipeline twice',
{
'001_pgbench_pipeline_2' => q{
-- startpipeline twice
\startpipeline
\startpipeline
}
});
# Try to end a pipeline that hasn't started
$node->pgbench(
'-t 1 -n -M extended',
2,
[],
[qr{not in pipeline mode}],
'error: \endpipeline with no start',
{
'001_pgbench_pipeline_3' => q{
-- pipeline not started
\endpipeline
}
});
# Try \gset in pipeline mode
$node->pgbench(
'-t 1 -n -M extended',
2,
[],
[qr{gset is not allowed in pipeline mode}],
'error: \gset not allowed in pipeline mode',
{
'001_pgbench_pipeline_4' => q{
\startpipeline
select 1 \gset f
\endpipeline
}
});
# Try \startpipeline without \endpipeline in a single transaction
$node->pgbench(
'-t 1 -n -M extended',
2,
[],
[qr{end of script reached with pipeline open}],
'error: call \startpipeline without \endpipeline in a single transaction',
{
'001_pgbench_pipeline_5' => q{
-- startpipeline only with single transaction
\startpipeline
}
});
# Try \startpipeline without \endpipeline
$node->pgbench(
'-t 2 -n -M extended',
2,
[],
[qr{end of script reached with pipeline open}],
'error: call \startpipeline without \endpipeline',
{
'001_pgbench_pipeline_6' => q{
-- startpipeline only
\startpipeline
}
});
# Try \startpipeline with \syncpipeline without \endpipeline
$node->pgbench(
'-t 2 -n -M extended',
2,
[],
[qr{end of script reached with pipeline open}],
'error: call \startpipeline and \syncpipeline without \endpipeline',
{
'001_pgbench_pipeline_7' => q{
-- startpipeline with \syncpipeline only
\startpipeline
\syncpipeline
}
});
# Working \startpipeline in prepared query mode with serializable
$node->pgbench(
'-c4 -t 10 -n -M prepared',
0,
[
qr{type: .*/001_pgbench_pipeline_serializable},
qr{actually processed: (\d+)/\1}
],
[],
'working \startpipeline with serializable',
{
'001_pgbench_pipeline_serializable' => q{
-- test startpipeline with serializable
\startpipeline
BEGIN ISOLATION LEVEL SERIALIZABLE;
} . "select 1;\n" x 10 . q{
END;
\endpipeline
}
});
# trigger many expression errors
my @errors = (
# [ test name, expected status, expected stderr, script ]
# SQL
[
'sql syntax error',
2,
[
qr{ERROR: syntax error},
qr{prepared statement .* does not exist}
],
q{-- SQL syntax error
SELECT 1 + ;
}
],
[
'sql too many args', 1,
[qr{statement has too many arguments.*\b255\b}],
q{-- MAX_ARGS=256 for prepared
\set i 0
SELECT LEAST(} . join(', ', (':i') x 256) . q{)}
],
# SHELL
[
'shell bad command', 2,
[qr{\(shell\) .* meta-command failed}], q{\shell no-such-command}
],
[
'shell undefined variable', 2,
[qr{undefined variable ":nosuchvariable"}],
q{-- undefined variable in shell
\shell echo ::foo :nosuchvariable
}
],
[ 'shell missing command', 1, [qr{missing command }], q{\shell} ],
[
'shell too many args', 1, [qr{too many arguments in command "shell"}],
q{-- 256 arguments to \shell
\shell echo } . join(' ', ('arg') x 255)
],
# SET
[
'set syntax error', 1,
[qr{syntax error in command "set"}], q{\set i 1 +}
],
[
'set no such function', 1,
[qr{unexpected function name}], q{\set i noSuchFunction()}
],
[
'set invalid variable name', 2,
[qr{invalid variable name}], q{\set . 1}
],
[ 'set division by zero', 2, [qr{division by zero}], q{\set i 1/0} ],
[
'set undefined variable',
2,
[qr{undefined variable "nosuchvariable"}],
q{\set i :nosuchvariable}
],
[ 'set unexpected char', 1, [qr{unexpected character .;.}], q{\set i ;} ],
[
'set too many args',
2,
[qr{too many function arguments}],
q{\set i least(0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16)}
],
[
'set empty random range', 2,
[qr{empty range given to random}], q{\set i random(5,3)}
],
[
'set random range too large', 2,
[qr{random range is too large}], q{\set i random(:minint, :maxint)}
],
[
'set gaussian param too small',
2,
[qr{gaussian param.* at least 2}],
q{\set i random_gaussian(0, 10, 1.0)}
],
[
'set exponential param greater 0',
2,
[qr{exponential parameter must be greater }],
q{\set i random_exponential(0, 10, 0.0)}
],
[
'set zipfian param to 1',
2,
[qr{zipfian parameter must be in range \[1\.001, 1000\]}],
q{\set i random_zipfian(0, 10, 1)}
],
[
'set zipfian param too large',
2,
[qr{zipfian parameter must be in range \[1\.001, 1000\]}],
q{\set i random_zipfian(0, 10, 1000000)}
],
[
'set non numeric value', 2,
[qr{malformed variable "foo" value: "bla"}], q{\set i :foo + 1}
],
[ 'set no expression', 1, [qr{syntax error}], q{\set i} ],
[ 'set missing argument', 1, [qr{missing argument}i], q{\set} ],
[
'set not a bool', 2,
[qr{cannot coerce double to boolean}], q{\set b NOT 0.0}
],
[
'set not an int', 2,
[qr{cannot coerce boolean to int}], q{\set i TRUE + 2}
],
[
'set not a double', 2,
[qr{cannot coerce boolean to double}], q{\set d ln(TRUE)}
],
[
'set case error',
1,
[qr{syntax error in command "set"}],
q{\set i CASE TRUE THEN 1 ELSE 0 END}
],
[
'set random error', 2,
[qr{cannot coerce boolean to int}], q{\set b random(FALSE, TRUE)}
],
[
'set number of args mismatch', 1,
[qr{unexpected number of arguments}], q{\set d ln(1.0, 2.0))}
],
[
'set at least one arg', 1,
[qr{at least one argument expected}], q{\set i greatest())}
],
# SET: ARITHMETIC OVERFLOW DETECTION
[
'set double to int overflow', 2,
[qr{double to int overflow for 100}], q{\set i int(1E32)}
],
[
'set bigint add overflow', 2,
[qr{int add out}], q{\set i (1<<62) + (1<<62)}
],
[
'set bigint sub overflow',
2, [qr{int sub out}], q{\set i 0 - (1<<62) - (1<<62) - (1<<62)}
],
[
'set bigint mul overflow', 2,
[qr{int mul out}], q{\set i 2 * (1<<62)}
],
[
'set bigint div out of range', 2,
[qr{bigint div out of range}], q{\set i :minint / -1}
],
# SETSHELL
[
'setshell not an int', 2,
[qr{command must return an integer}], q{\setshell i echo -n one}
],
[ 'setshell missing arg', 1, [qr{missing argument }], q{\setshell var} ],
[
'setshell no such command', 2,
[qr{could not read result }], q{\setshell var no-such-command}
],
# SLEEP
[
'sleep undefined variable', 2,
[qr{sleep: undefined variable}], q{\sleep :nosuchvariable}
],
[
'sleep too many args', 1,
[qr{too many arguments}], q{\sleep too many args}
],
[
'sleep missing arg', 1,
[ qr{missing argument}, qr{\\sleep} ], q{\sleep}
],
[
'sleep unknown unit', 1,
[qr{unrecognized time unit}], q{\sleep 1 week}
],
# MISC
[
'misc invalid backslash command', 1,
[qr{invalid command .* "nosuchcommand"}], q{\nosuchcommand}
],
[ 'misc empty script', 1, [qr{empty command list for script}], q{} ],
[
'bad boolean', 2,
[qr{malformed variable.*trueXXX}], q{\set b :badtrue or true}
],
[
'invalid permute size',
2,
[qr{permute size parameter must be greater than zero}],
q{\set i permute(0, 0)}
],
# GSET
[
'gset no row', 2,
[qr{expected one row, got 0\b}], q{SELECT WHERE FALSE \gset}
],
[ 'gset alone', 1, [qr{gset must follow an SQL command}], q{\gset} ],
[
'gset no SQL', 1,
[qr{gset must follow an SQL command}], q{\set i +1
\gset}
],
[
'gset too many arguments', 1,
[qr{too many arguments}], q{SELECT 1 \gset a b}
],
[
'gset after gset', 1,
[qr{gset must follow an SQL command}], q{SELECT 1 AS i \gset
\gset}
],
[
'gset non SELECT',
2,
[qr{expected one row, got 0}],
q{DROP TABLE IF EXISTS no_such_table \gset}
],
[
'gset bad default name', 2,
[qr{error storing into variable \?column\?}], q{SELECT 1 \gset}
],
[
'gset bad name',
2,
[qr{error storing into variable bad name!}],
q{SELECT 1 AS "bad name!" \gset}
],);
for my $e (@errors)
{
my ($name, $status, $re, $script, $no_prepare) = @$e;
$status != 0 or die "invalid expected status for test \"$name\"";
my $n = '001_pgbench_error_' . $name;
$n =~ s/ /_/g;
$node->pgbench(
'-n -t 1 -Dfoo=bla -Dnull=null -Dtrue=true -Done=1 -Dzero=0.0 -Dbadtrue=trueXXX'
. ' -Dmaxint=9223372036854775807 -Dminint=-9223372036854775808'
. ($no_prepare ? '' : ' -M prepared'),
$status,
[ $status == 1 ? qr{^$} : qr{processed: 0/1} ],
$re,
'pgbench script error: ' . $name,
{ $n => $script });
}
# throttling
$node->pgbench(
'-t 100 -S --rate=100000 --latency-limit=1000000 -c 2 -n -r',
0,
[ qr{processed: 200/200}, qr{builtin: select only} ],
[qr{^$}],
'pgbench throttling');
$node->pgbench(
# given the expected rate and the 2 ms tx duration, at most one is executed
'-t 10 --rate=100000 --latency-limit=1 -n -r',
0,
[
qr{processed: [01]/10},
qr{type: .*/001_pgbench_sleep},
qr{above the 1.0 ms latency limit: [01]/}
],
[qr{^$}],
'pgbench late throttling',
{ '001_pgbench_sleep' => q{\sleep 2ms} });
# return a list of files from directory $dir matching regexpr $re
# this works around glob portability and escaping issues
sub list_files
{
my ($dir, $re) = @_;
opendir my $dh, $dir or die "cannot opendir $dir: $!";
my @files = grep /$re/, readdir $dh;
closedir $dh or die "cannot closedir $dir: $!";
return map { $dir . '/' . $_ } @files;
}
# Check log contents and clean them up:
# $dir: directory holding logs
# $prefix: file prefix for per-thread logs
# $nb: number of expected files
# $min/$max: minimum and maximum number of lines in log files
# $re: regular expression each log line has to match
sub check_pgbench_logs
{
local $Test::Builder::Level = $Test::Builder::Level + 1;
my ($dir, $prefix, $nb, $min, $max, $re) = @_;
# $prefix is simple enough, thus does not need escaping
my @logs = list_files($dir, qr{^$prefix\..*$});
ok(@logs == $nb, "number of log files");
ok(grep(/\/$prefix\.\d+(\.\d+)?$/, @logs) == $nb, "file name format");
my $log_number = 0;
for my $log (sort @logs)
{
# Check the contents of each log file.
my $contents_raw = slurp_file($log);
my @contents = split(/\n/, $contents_raw);
my $clen = @contents;
ok( $min <= $clen && $clen <= $max,
"transaction count for $log ($clen)");
my $clen_match = grep(/$re/, @contents);
ok($clen_match == $clen, "transaction format for $prefix");
# Show more information if some logs don't match
# to help with debugging.
if ($clen_match != $clen)
{
foreach my $log (@contents)
{
print "# Log entry not matching: $log\n"
unless $log =~ /$re/;
}
}
}
return;
}
my $bdir = $node->basedir;
# Run with sampling rate, 2 clients with 50 transactions each.
$node->pgbench(
"-n -S -t 50 -c 2 --log --sampling-rate=0.5", 0,
[ qr{select only}, qr{processed: 100/100} ], [qr{^$}],
'pgbench logs', undef,
"--log-prefix=$bdir/001_pgbench_log_2");
# The IDs of the clients (1st field) in the logs should be either 0 or 1.
check_pgbench_logs($bdir, '001_pgbench_log_2', 1, 8, 92,
qr{^[01] \d{1,2} \d+ \d \d+ \d+$});
# Run with different read-only option pattern, 1 client with 10 transactions.
$node->pgbench(
"-n -b select-only -t 10 -l", 0,
[ qr{select only}, qr{processed: 10/10} ], [qr{^$}],
'pgbench logs contents', undef,
"--log-prefix=$bdir/001_pgbench_log_3");
# The ID of a single client (1st field) should match 0.
check_pgbench_logs($bdir, '001_pgbench_log_3', 1, 10, 10,
qr{^0 \d{1,2} \d+ \d \d+ \d+$});
# abortion of the client if the script contains an incomplete transaction block
$node->pgbench(
'--no-vacuum',
2,
[qr{processed: 1/10}],
[
qr{client 0 aborted: end of script reached without completing the last transaction}
],
'incomplete transaction block',
{ '001_pgbench_incomplete_transaction_block' => q{BEGIN;SELECT 1;} });
# Test the concurrent update in the table row and deadlocks.
$node->safe_psql('postgres',
'CREATE UNLOGGED TABLE first_client_table (value integer); '
. 'CREATE UNLOGGED TABLE xy (x integer, y integer); '
. 'INSERT INTO xy VALUES (1, 2);');
# Serialization error and retry
local $ENV{PGOPTIONS} = "-c default_transaction_isolation=repeatable\\ read";
# Check that we have a serialization error and the same random value of the
# delta variable in the next try
my $err_pattern =
"(client (0|1) sending UPDATE xy SET y = y \\+ -?\\d+\\b).*"
. "client \\2 got an error in command 3 \\(SQL\\) of script 0; "
. "ERROR: could not serialize access due to concurrent update\\b.*"
. "\\1";
$node->pgbench(
"-n -c 2 -t 1 --debug --verbose-errors --max-tries 2",
0,
[
qr{processed: 2/2\b},
qr{number of transactions retried: 1\b},
qr{total number of retries: 1\b}
],
[qr/$err_pattern/s],
'concurrent update with retrying',
{
'001_pgbench_serialization' => q{
-- What's happening:
-- The first client starts the transaction with the isolation level Repeatable
-- Read:
--
-- BEGIN;
-- UPDATE xy SET y = ... WHERE x = 1;
--
-- The second client starts a similar transaction with the same isolation level:
--
-- BEGIN;
-- UPDATE xy SET y = ... WHERE x = 1;
-- <waiting for the first client>
--
-- The first client commits its transaction, and the second client gets a
-- serialization error.
\set delta random(-5000, 5000)
-- The second client will stop here
SELECT pg_advisory_lock(0);
-- Start transaction with concurrent update
BEGIN;
UPDATE xy SET y = y + :delta WHERE x = 1 AND pg_advisory_lock(1) IS NOT NULL;
-- Wait for the second client
DO $$
DECLARE
exists boolean;
waiters integer;
BEGIN
-- The second client always comes in second, and the number of rows in the
-- table first_client_table reflect this. Here the first client inserts a row,
-- so the second client will see a non-empty table when repeating the
-- transaction after the serialization error.
SELECT EXISTS (SELECT * FROM first_client_table) INTO STRICT exists;
IF NOT exists THEN
-- Let the second client begin
PERFORM pg_advisory_unlock(0);
-- And wait until the second client tries to get the same lock
LOOP
SELECT COUNT(*) INTO STRICT waiters FROM pg_locks WHERE
locktype = 'advisory' AND objsubid = 1 AND
((classid::bigint << 32) | objid::bigint = 1::bigint) AND NOT granted;
IF waiters = 1 THEN
INSERT INTO first_client_table VALUES (1);
-- Exit loop
EXIT;
END IF;
END LOOP;
END IF;
END$$;
COMMIT;
SELECT pg_advisory_unlock_all();
}
});
# Clean up
$node->safe_psql('postgres', 'DELETE FROM first_client_table;');
local $ENV{PGOPTIONS} = "-c default_transaction_isolation=read\\ committed";
# Deadlock error and retry
# Check that we have a deadlock error
$err_pattern =
"client (0|1) got an error in command (3|5) \\(SQL\\) of script 0; "
. "ERROR: deadlock detected\\b";
$node->pgbench(
"-n -c 2 -t 1 --max-tries 2 --verbose-errors",
0,
[
qr{processed: 2/2\b},
qr{number of transactions retried: 1\b},
qr{total number of retries: 1\b}
],
[qr{$err_pattern}],
'deadlock with retrying',
{
'001_pgbench_deadlock' => q{
-- What's happening:
-- The first client gets the lock 2.
-- The second client gets the lock 3 and tries to get the lock 2.
-- The first client tries to get the lock 3 and one of them gets a deadlock
-- error.
--
-- A client that does not get a deadlock error must hold a lock at the
-- transaction start. Thus in the end it releases all of its locks before the
-- client with the deadlock error starts a retry (we do not want any errors
-- again).
-- Since the client with the deadlock error has not released the blocking locks,
-- let's do this here.
SELECT pg_advisory_unlock_all();
-- The second client and the client with the deadlock error stop here
SELECT pg_advisory_lock(0);
SELECT pg_advisory_lock(1);
-- The second client and the client with the deadlock error always come after
-- the first and the number of rows in the table first_client_table reflects
-- this. Here the first client inserts a row, so in the future the table is
-- always non-empty.
DO $$
DECLARE
exists boolean;
BEGIN
SELECT EXISTS (SELECT * FROM first_client_table) INTO STRICT exists;
IF exists THEN
-- We are the second client or the client with the deadlock error
-- The first client will take care by itself of this lock (see below)
PERFORM pg_advisory_unlock(0);
PERFORM pg_advisory_lock(3);
-- The second client can get a deadlock here
PERFORM pg_advisory_lock(2);
ELSE
-- We are the first client
-- This code should not be used in a new transaction after an error
INSERT INTO first_client_table VALUES (1);
PERFORM pg_advisory_lock(2);
END IF;
END$$;
DO $$
DECLARE
num_rows integer;
waiters integer;
BEGIN
-- Check if we are the first client
SELECT COUNT(*) FROM first_client_table INTO STRICT num_rows;
IF num_rows = 1 THEN
-- This code should not be used in a new transaction after an error
INSERT INTO first_client_table VALUES (2);
-- Let the second client begin
PERFORM pg_advisory_unlock(0);
PERFORM pg_advisory_unlock(1);
-- Make sure the second client is ready for deadlock
LOOP
SELECT COUNT(*) INTO STRICT waiters FROM pg_locks WHERE
locktype = 'advisory' AND
objsubid = 1 AND
((classid::bigint << 32) | objid::bigint = 2::bigint) AND
NOT granted;
IF waiters = 1 THEN
-- Exit loop
EXIT;
END IF;
END LOOP;
PERFORM pg_advisory_lock(0);
-- And the second client took care by itself of the lock 1
END IF;
END$$;
-- The first client can get a deadlock here
SELECT pg_advisory_lock(3);
SELECT pg_advisory_unlock_all();
}
});
# Clean up
$node->safe_psql('postgres', 'DROP TABLE first_client_table, xy;');
# Test --exit-on-abort
$node->safe_psql('postgres',
'CREATE TABLE counter(i int); ' . 'INSERT INTO counter VALUES (0);');
$node->pgbench(
'-t 10 -c 2 -j 2 --exit-on-abort',
2,
[],
[ qr{division by zero}, qr{Run was aborted due to an error in thread} ],
'test --exit-on-abort',
{
'001_exit_on_abort' => q{
update counter set i = i+1 returning i \gset
\if :i = 5
\set y 1/0
\endif
}
});
# Clean up
$node->safe_psql('postgres', 'DROP TABLE counter;');
# done
$node->safe_psql('postgres', 'DROP TABLESPACE regress_pgbench_tap_1_ts');
$node->stop;
done_testing();