
# Copyright (c) 2025, PostgreSQL Global Development Group

# Test recursive catalog cache invalidation, i.e. invalidation while a
# catalog cache entry is being built.

use strict;
use warnings FATAL => 'all';

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

if ($ENV{enable_injection_points} ne 'yes')
{
	plan skip_all => 'Injection points not supported by this build';
}

# Node initialization
my $node = PostgreSQL::Test::Cluster->new('node');
$node->init();
$node->start;

# Check if the extension injection_points is available, as it may be
# possible that this script is run with installcheck, where the module
# would not be installed by default.
my $result = $node->safe_psql('postgres',
	"SELECT count(*) > 0 FROM pg_available_extensions WHERE name = 'injection_points';"
);
if ($result eq 'f')
{
	plan skip_all => 'Extension injection_points not installed';
}

$node->safe_psql('postgres', 'CREATE EXTENSION injection_points;');


sub randStr
{
	my $len = shift;
	my @chars = ("A" .. "Z", "a" .. "z", "0" .. "9");
	return join '', map { @chars[ rand @chars ] } 1 .. $len;
}

# Create a function with a large body, so that it is toasted.
my $longtext = randStr(10000);
$node->safe_psql(
	'postgres', qq[
    CREATE FUNCTION foofunc(dummy integer) RETURNS integer AS \$\$ SELECT 1; /* $longtext */ \$\$ LANGUAGE SQL
]);

my $psql_session = $node->background_psql('postgres');
my $psql_session2 = $node->background_psql('postgres');

# Set injection point in the session, to pause while populating the
# catcache list
$psql_session->query_safe(
	qq[
    SELECT injection_points_set_local();
    SELECT injection_points_attach('catcache-list-miss-systable-scan-started', 'wait');
]);

# This pauses on the injection point while populating catcache list
# for functions with name "foofunc"
$psql_session->query_until(
	qr/starting_bg_psql/, q(
   \echo starting_bg_psql
   SELECT foofunc(1);
));

# While the first session is building the catcache list, create a new
# function that overloads the same name. This sends a catcache
# invalidation.
$node->safe_psql(
	'postgres', qq[
    CREATE FUNCTION foofunc() RETURNS integer AS \$\$ SELECT 123 \$\$ LANGUAGE SQL
]);

# Continue the paused session. It will continue to construct the
# catcache list, and will accept invalidations while doing that.
#
# (The fact that the first function has a large body is crucial,
# because the cache invalidation is accepted during detoasting.  If
# the body is not toasted, the invalidation is processed after
# building the catcache list, which avoids the recursion that we are
# trying to exercise here.)
#
# The "SELECT foofunc(1)" query will now finish.
$psql_session2->query_safe(
	qq[
    SELECT injection_points_wakeup('catcache-list-miss-systable-scan-started');
    SELECT injection_points_detach('catcache-list-miss-systable-scan-started');
]);

# Test that the new function is visible to the session.
$psql_session->query_safe("SELECT foofunc();");

ok($psql_session->quit);
ok($psql_session2->quit);

done_testing();
