Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions t/003_dump_restore.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# Check dump and restore of lolor large objects
#
# Copyright (c) 2022-2026, pgEdge, Inc.
#

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

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

my $src = PostgreSQL::Test::Cluster->new('src_node');
my $dst = PostgreSQL::Test::Cluster->new('dst_node');
my ($result, $stdout, $stderr);

# Setup source node with lolor extension
$src->init;
$src->append_conf('postgresql.conf', qq{lolor.node = 1});
$src->start;
$src->safe_psql('postgres', "CREATE EXTENSION lolor");

# Create lolor large objects with known content
$src->safe_psql('postgres',
qq(SELECT lo_from_bytea(1, 'lolor LO object - 1')));
$src->safe_psql('postgres',
qq(SELECT lo_from_bytea(2, 'lolor LO object - 2')));

# Verify content on source before dump
$result = $src->safe_psql('postgres', qq(
BEGIN;
SELECT lo_open(1, 262144) AS fd \\gset
SELECT convert_from(loread(:fd, 1024), 'UTF8');
END;
));
is($result, 'lolor LO object - 1', "Verify first LO content on source");

$result = $src->safe_psql('postgres', qq(
BEGIN;
SELECT lo_open(2, 262144) AS fd \\gset
SELECT convert_from(loread(:fd, 1024), 'UTF8');
END;
));
is($result, 'lolor LO object - 2', "Verify second LO content on source");

# Dump the source database
my $dump_file = $src->data_dir . '/dump.sql';
command_ok(
['pg_dump', '-f', $dump_file, '-d', $src->connstr('postgres')],
'pg_dump succeeds on source with lolor objects');

$src->stop;

# Setup destination node and restore
$dst->init;
$dst->append_conf('postgresql.conf', qq{lolor.node = 1});
$dst->start;

command_ok(
['psql', '-X', '-f', $dump_file, '-d', $dst->connstr('postgres')],
'restore dump on destination node succeeds');

# Verify lolor objects survived dump/restore
$result = $dst->safe_psql('postgres', qq(
BEGIN;
SELECT lo_open(1, 262144) AS fd \\gset
SELECT convert_from(loread(:fd, 1024), 'UTF8');
END;
));
is($result, 'lolor LO object - 1',
"First lolor LO preserved after dump/restore");

$result = $dst->safe_psql('postgres', qq(
BEGIN;
SELECT lo_open(2, 262144) AS fd \\gset
SELECT convert_from(loread(:fd, 1024), 'UTF8');
END;
));
is($result, 'lolor LO object - 2',
"Second lolor LO preserved after dump/restore");

# Verify new lolor objects can be created on destination
$dst->safe_psql('postgres',
qq(SELECT lo_from_bytea(3, 'new object on dst')));
$result = $dst->safe_psql('postgres', qq(
BEGIN;
SELECT lo_open(3, 262144) AS fd \\gset
SELECT convert_from(loread(:fd, 1024), 'UTF8');
END;
));
is($result, 'new object on dst',
"Can create and read new lolor objects after restore");

$dst->stop;

done_testing();
73 changes: 73 additions & 0 deletions t/004_streaming_replication.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# Check streaming replication of lolor large objects
#
# Copyright (c) 2022-2026, pgEdge, Inc.
#

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

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

my $primary = PostgreSQL::Test::Cluster->new('primary');
my ($result, $stdout, $stderr);

# Setup primary node with lolor extension
$primary->init(allows_streaming => 1);
$primary->append_conf('postgresql.conf', qq{lolor.node = 1});
$primary->start;
$primary->safe_psql('postgres', "CREATE EXTENSION lolor");

# Create a lolor large object BEFORE setting up the replica
$primary->safe_psql('postgres',
qq(SELECT lo_from_bytea(1, 'pre-backup LO object')));

$result = $primary->safe_psql('postgres', qq(
BEGIN;
SELECT lo_open(1, 262144) AS fd \\gset
SELECT convert_from(loread(:fd, 1024), 'UTF8');
END;
));
is($result, 'pre-backup LO object', "Pre-backup LO created on primary");

# Take a backup and create streaming standby
my $backup_name = 'my_backup';
$primary->backup($backup_name);

my $standby = PostgreSQL::Test::Cluster->new('standby');
$standby->init_from_backup($primary, $backup_name,
has_streaming => 1);
$standby->start;

# Create another lolor object on the primary AFTER standby is running
$primary->safe_psql('postgres',
qq(SELECT lo_from_bytea(2, 'post-backup LO object')));

# Wait for standby to catch up
$primary->wait_for_replay_catchup($standby);

# Verify the pre-backup object is available on standby
$result = $standby->safe_psql('postgres', qq(
BEGIN;
SELECT lo_open(1, 262144) AS fd \\gset
SELECT convert_from(loread(:fd, 1024), 'UTF8');
END;
));
is($result, 'pre-backup LO object',
"Pre-backup LO available on standby");

# Verify the post-backup object was streamed to standby
$result = $standby->safe_psql('postgres', qq(
BEGIN;
SELECT lo_open(2, 262144) AS fd \\gset
SELECT convert_from(loread(:fd, 1024), 'UTF8');
END;
));
is($result, 'post-backup LO object',
"Post-backup LO replicated to standby via streaming");

$standby->stop;
$primary->stop;

done_testing();
75 changes: 75 additions & 0 deletions t/005_logical_replication.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Check logical replication of lolor large objects
#
# Copyright (c) 2022-2026, pgEdge, Inc.
#

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

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

my $publisher = PostgreSQL::Test::Cluster->new('publisher');
my $subscriber = PostgreSQL::Test::Cluster->new('subscriber');
my ($result, $stdout, $stderr);

# Setup publisher with logical replication support
$publisher->init(allows_streaming => 'logical');
$publisher->append_conf('postgresql.conf', qq{lolor.node = 1});
$publisher->start;
$publisher->safe_psql('postgres', "CREATE EXTENSION lolor");

# Create a lolor large object BEFORE setting up subscription
$publisher->safe_psql('postgres',
qq(SELECT lo_from_bytea(1, 'pre-subscription LO')));

# Create publication for lolor tables
$publisher->safe_psql('postgres',
"CREATE PUBLICATION lolor_pub FOR TABLE lolor.pg_largeobject, lolor.pg_largeobject_metadata");

# Setup subscriber with lolor extension (tables must exist before subscription)
$subscriber->init;
$subscriber->append_conf('postgresql.conf', qq{lolor.node = 2});
$subscriber->start;
$subscriber->safe_psql('postgres', "CREATE EXTENSION lolor");

# Create subscription
my $publisher_connstr = $publisher->connstr . ' dbname=postgres';
$subscriber->safe_psql('postgres',
"CREATE SUBSCRIPTION lolor_sub CONNECTION '$publisher_connstr' PUBLICATION lolor_pub");

# Wait for initial table sync to complete
$subscriber->wait_for_subscription_sync($publisher, 'lolor_sub');

# Verify pre-subscription object replicated via initial sync
$result = $subscriber->safe_psql('postgres', qq(
BEGIN;
SELECT lo_open(1, 262144) AS fd \\gset
SELECT convert_from(loread(:fd, 1024), 'UTF8');
END;
));
is($result, 'pre-subscription LO',
"Pre-subscription LO replicated via initial sync");

# Create another object on publisher AFTER subscription is active
$publisher->safe_psql('postgres',
qq(SELECT lo_from_bytea(2, 'post-subscription LO')));

# Wait for subscriber to catch up with ongoing changes
$publisher->wait_for_catchup('lolor_sub');

# Verify post-subscription object replicated via streaming
$result = $subscriber->safe_psql('postgres', qq(
BEGIN;
SELECT lo_open(2, 262144) AS fd \\gset
SELECT convert_from(loread(:fd, 1024), 'UTF8');
END;
));
is($result, 'post-subscription LO',
"Post-subscription LO replicated via logical streaming");

$subscriber->stop;
$publisher->stop;

done_testing();