mirror of https://github.com/postgres/postgres
parent
96edf0c185
commit
54f2b601ef
@ -0,0 +1,52 @@ |
||||
# -*- perl -*- |
||||
# ApplySnapshot |
||||
# Vadim Mikheev, (c) 2000, PostgreSQL Inc. |
||||
|
||||
eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' |
||||
& eval 'exec perl -S $0 $argv:q' |
||||
if 0; |
||||
|
||||
use lib "@LIBDIR@"; |
||||
use IO::File; |
||||
use RServ; |
||||
use Getopt::Long; |
||||
|
||||
$| = 1; |
||||
|
||||
$result = GetOptions("debug!", "verbose!", "help", |
||||
"host=s", "user=s", "password=s"); |
||||
|
||||
my $debug = $opt_debug || 0; |
||||
my $verbose = $opt_verbose || 0; |
||||
my $snapshot = $opt_snapshot || "__Snapshot"; |
||||
|
||||
if (defined($opt_help) || (scalar(@ARGV) < 1)) { |
||||
print "Usage: $0 --host=name --user=name --password=string slavedb\n"; |
||||
exit ((scalar(@ARGV) < 1)? 1:0); |
||||
} |
||||
|
||||
my $slave = $ARGV[0] || "slave"; |
||||
|
||||
my $sinfo = "dbname=$slave"; |
||||
$sinfo = "$sinfo host=$opt_host" if (defined($opt_host)); |
||||
$sinfo = "$sinfo user=$opt_user" if (defined($opt_user)); |
||||
$sinfo = "$sinfo password=$opt_password" if (defined($opt_password)); |
||||
|
||||
my $conn = Pg::connectdb(sinfo); |
||||
|
||||
my $inpf = new IO::File; |
||||
$inpf = STDIN; |
||||
|
||||
$res = ApplySnapshot ($conn, $inpf); |
||||
|
||||
if ($res > 0) |
||||
{ |
||||
printf STDERR "Snapshot applied\n"; |
||||
} |
||||
elsif ($res != 0) |
||||
{ |
||||
printf STDERR "ERROR\n"; |
||||
exit(1); |
||||
} |
||||
|
||||
exit(0); |
@ -0,0 +1,47 @@ |
||||
# -*- perl -*- |
||||
# CleanLog |
||||
# Vadim Mikheev, (c) 2000, PostgreSQL Inc. |
||||
|
||||
eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' |
||||
& eval 'exec perl -S $0 $argv:q' |
||||
if 0; |
||||
|
||||
use lib "@LIBDIR@"; |
||||
|
||||
use Getopt::Long; |
||||
use RServ; |
||||
|
||||
$| = 1; |
||||
|
||||
$result = GetOptions("debug!", "verbose!", "help", "snapshot=s", |
||||
"host=s", "user=s", "password=s"); |
||||
|
||||
my $debug = $opt_debug || 0; |
||||
my $verbose = $opt_verbose || 0; |
||||
my $snapshot = $opt_snapshot || "__Snapshot"; |
||||
|
||||
if (defined($opt_help) || (scalar(@ARGV) < 2) || ($ARGV[1] !~ /^\d+$/)) { |
||||
print "Usage: $PROGRAM_NAME --host=name --user=name --password=string masterdb syncid\n"; |
||||
exit ((scalar(@ARGV) < 2)? 1: 0); |
||||
} |
||||
|
||||
my $dbname = $ARGV[0]; |
||||
my $howold = $ARGV[1]; |
||||
|
||||
my $minfo = "dbname=$dbname"; |
||||
$minfo = "$minfo host=$opt_host" if (defined($opt_host)); |
||||
$minfo = "$minfo user=$opt_user" if (defined($opt_user)); |
||||
$minfo = "$minfo password=$opt_password" if (defined($opt_password)); |
||||
|
||||
print "Master connection is $minfo\n" if ($debug); |
||||
print "Slave connection is $sinfo\n" if ($debug); |
||||
|
||||
my $conn = Pg::connectdb($minfo); |
||||
|
||||
$res = CleanLog($conn, $howold); |
||||
|
||||
exit(1) if $res < 0; |
||||
|
||||
printf STDERR "Deleted %d log records\n", $res if $res > 0; |
||||
|
||||
exit(0); |
@ -0,0 +1,55 @@ |
||||
# -*- perl -*- |
||||
# GetSyncID |
||||
# Vadim Mikheev, (c) 2000, PostgreSQL Inc. |
||||
|
||||
eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' |
||||
& eval 'exec perl -S $0 $argv:q' |
||||
if 0; |
||||
|
||||
use lib "@LIBDIR@"; |
||||
|
||||
use Pg; |
||||
use Getopt::Long; |
||||
use RServ; |
||||
|
||||
$| = 1; |
||||
|
||||
my $verbose = 1; |
||||
|
||||
$result = GetOptions("debug!", "verbose!", "help", |
||||
"host=s", "user=s", "password=s"); |
||||
|
||||
my $debug = $opt_debug || 0; |
||||
my $verbose = $opt_verbose if (defined($opt_verbose)); |
||||
|
||||
if (defined($opt_help) || (scalar(@ARGV) < 1)) { |
||||
print "Usage: $0 --host=name --user=name --password=string slavedb\n"; |
||||
exit ((scalar(@ARGV) < 1)? 1: 0); |
||||
} |
||||
|
||||
my $dbname = $ARGV[0]; |
||||
|
||||
my $sinfo = "dbname=$dbname"; |
||||
$sinfo = "$sinfo host=$opt_host" if (defined($opt_host)); |
||||
$sinfo = "$sinfo user=$opt_user" if (defined($opt_user)); |
||||
$sinfo = "$sinfo password=$opt_password" if (defined($opt_password)); |
||||
|
||||
if ($verbose) { print "Connecting to '$sinfo'\n" }; |
||||
my $conn = Pg::connectdb($sinfo); |
||||
|
||||
$res = GetSyncID($conn); |
||||
|
||||
die "ERROR\n" if $res < 0; |
||||
|
||||
if (! defined $res) |
||||
{ |
||||
printf STDERR "No SyncID found\n"; |
||||
} |
||||
else |
||||
{ |
||||
print("Last SyncID applied: ") if ($verbose); |
||||
printf "%d", $res; |
||||
print("\n") if ($verbose); |
||||
} |
||||
|
||||
exit(0); |
@ -0,0 +1,259 @@ |
||||
#!/bin/sh |
||||
# InitRservTest |
||||
# erServer demonstration implementation |
||||
# (c) 2000 Vadim Mikheev, PostgreSQL Inc. |
||||
|
||||
[ -n "$RSERV_PERL" ] || RSERV_PERL=@LIBDIR@ |
||||
[ -n "$RSERV_SQL" ] || RSERV_SQL=@SQLDIR@ |
||||
[ -n "$RSERV_BIN" ] || RSERV_BIN=@BINDIR@ |
||||
export RSERV_PERL |
||||
export RSERV_SQL |
||||
export RSERV_BIN |
||||
|
||||
pargs= |
||||
|
||||
while [[ $1 == -* ]]; do |
||||
case "$1" in |
||||
--user) |
||||
shift |
||||
pargs="$pargs -U $1" |
||||
;; |
||||
--host) |
||||
shift |
||||
pargs="$pargs -h $1" |
||||
;; |
||||
*) |
||||
echo "Usage: $0 --user name --host name masterdb slavedb" |
||||
exit 1 |
||||
;; |
||||
esac |
||||
shift |
||||
done |
||||
|
||||
masterdb=$1 |
||||
slavedb=$2 |
||||
|
||||
[ "${masterdb}" != "" ] || masterdb=master |
||||
[ "${slavedb}" != "" ] || slavedb=slave |
||||
|
||||
echo "Master -> $masterdb" |
||||
echo "Slave -> $slavedb" |
||||
|
||||
############################################################################ |
||||
|
||||
fill() |
||||
{ |
||||
table="create table test (i text, k int, l int); |
||||
copy test from stdin; |
||||
Line: 1 1 1 |
||||
Line: 2 2 2 |
||||
Line: 3 3 3 |
||||
Line: 4 4 4 |
||||
Line: 5 5 5 |
||||
Line: 6 6 6 |
||||
Line: 7 7 7 |
||||
Line: 8 8 8 |
||||
Line: 9 9 9 |
||||
Line: 10 10 10 |
||||
Line: 11 11 11 |
||||
Line: 12 12 12 |
||||
Line: 13 13 13 |
||||
Line: 14 14 14 |
||||
Line: 15 15 15 |
||||
Line: 16 16 16 |
||||
Line: 17 17 17 |
||||
Line: 18 18 18 |
||||
Line: 19 19 19 |
||||
Line: 20 20 20 |
||||
Line: 21 21 21 |
||||
Line: 22 22 22 |
||||
Line: 23 23 23 |
||||
Line: 24 24 24 |
||||
Line: 25 25 25 |
||||
Line: 26 26 26 |
||||
Line: 27 27 27 |
||||
Line: 28 28 28 |
||||
Line: 29 29 29 |
||||
Line: 30 30 30 |
||||
Line: 31 31 31 |
||||
Line: 32 32 32 |
||||
Line: 33 33 33 |
||||
Line: 34 34 34 |
||||
Line: 35 35 35 |
||||
Line: 36 36 36 |
||||
Line: 37 37 37 |
||||
Line: 38 38 38 |
||||
Line: 39 39 39 |
||||
Line: 40 40 40 |
||||
Line: 41 41 41 |
||||
Line: 42 42 42 |
||||
Line: 43 43 43 |
||||
Line: 44 44 44 |
||||
Line: 45 45 45 |
||||
Line: 46 46 46 |
||||
Line: 47 47 47 |
||||
Line: 48 48 48 |
||||
Line: 49 49 49 |
||||
Line: 50 50 50 |
||||
Line: 51 51 51 |
||||
Line: 52 52 52 |
||||
Line: 53 53 53 |
||||
Line: 54 54 54 |
||||
Line: 55 55 55 |
||||
Line: 56 56 56 |
||||
Line: 57 57 57 |
||||
Line: 58 58 58 |
||||
Line: 59 59 59 |
||||
Line: 60 60 60 |
||||
Line: 61 61 61 |
||||
Line: 62 62 62 |
||||
Line: 63 63 63 |
||||
Line: 64 64 64 |
||||
Line: 65 65 65 |
||||
Line: 66 66 66 |
||||
Line: 67 67 67 |
||||
Line: 68 68 68 |
||||
Line: 69 69 69 |
||||
Line: 70 70 70 |
||||
Line: 71 71 71 |
||||
Line: 72 72 72 |
||||
Line: 73 73 73 |
||||
Line: 74 74 74 |
||||
Line: 75 75 75 |
||||
Line: 76 76 76 |
||||
Line: 77 77 77 |
||||
Line: 78 78 78 |
||||
Line: 79 79 79 |
||||
Line: 80 80 80 |
||||
Line: 81 81 81 |
||||
Line: 82 82 82 |
||||
Line: 83 83 83 |
||||
Line: 84 84 84 |
||||
Line: 85 85 85 |
||||
Line: 86 86 86 |
||||
Line: 87 87 87 |
||||
Line: 88 88 88 |
||||
Line: 89 89 89 |
||||
Line: 90 90 90 |
||||
Line: 91 91 91 |
||||
Line: 92 92 92 |
||||
Line: 93 93 93 |
||||
Line: 94 94 94 |
||||
Line: 95 95 95 |
||||
Line: 96 96 96 |
||||
Line: 97 97 97 |
||||
Line: 98 98 98 |
||||
Line: 99 99 99 |
||||
Line: 100 100 100 |
||||
\\."; |
||||
echo "$table" | psql $pargs $1 || exit |
||||
if [ "$1" = "$masterdb" ] |
||||
then |
||||
rm -rf __tmpf__ |
||||
psql $pargs -c "select * into table testoid from test" $1 || exit |
||||
psql $pargs -c "copy testoid with oids to '`pwd`/__tmpf__'" $1 || exit |
||||
psql $pargs -c "select * into table teststr from test" $1 || exit |
||||
else |
||||
psql $pargs -c "select * into table testoid from test where k < 0" $1 || exit |
||||
psql $pargs -c "copy testoid with oids from '`pwd`/__tmpf__'" $1 || exit |
||||
psql $pargs -c "select * into table teststr from test" $1 || exit |
||||
rm -rf __tmpf__ |
||||
fi |
||||
psql $pargs -c "create unique index i_test on test (k)" $1 || exit |
||||
psql $pargs -c "create unique index i_testoid on testoid (oid)" $1 || exit |
||||
psql $pargs -c "create unique index i_teststr on teststr (i)" $1 || exit |
||||
psql $pargs -c vacuum $1 || exit |
||||
} |
||||
|
||||
############################################################################ |
||||
|
||||
echo |
||||
echo |
||||
echo ' ATTENTION' |
||||
echo |
||||
echo This script will destroy databases with names MASTER and SLAVE |
||||
echo |
||||
echo -n "Are you going to continue ? [Y/N] " |
||||
|
||||
read answ |
||||
|
||||
case $answ in |
||||
Y*|y*) |
||||
;; |
||||
*) |
||||
exit |
||||
;; |
||||
esac |
||||
|
||||
echo |
||||
echo |
||||
|
||||
sql="drop database $masterdb" |
||||
echo $sql |
||||
psql $pargs -c "$sql" template1 |
||||
sql="create database $masterdb" |
||||
echo $sql |
||||
psql $pargs -c "$sql" template1 || exit |
||||
|
||||
echo Setup master system |
||||
psql $pargs $masterdb < $RSERV_SQL/master.sql || exit |
||||
|
||||
echo Wait for template1 to become available... |
||||
sleep 1 |
||||
|
||||
sql="drop database $slavedb" |
||||
echo $sql |
||||
psql $pargs -c "$sql" template1 |
||||
sql="create database $slavedb" |
||||
echo $sql |
||||
psql $pargs -c "$sql" template1 || exit |
||||
|
||||
echo Setup slave system |
||||
psql $pargs $slavedb < $RSERV_SQL/slave.sql || exit |
||||
|
||||
echo Create and fill test, testoid and teststr tables in master db |
||||
fill $masterdb |
||||
echo |
||||
echo Register test, testoid and teststr tables for replication on master |
||||
echo |
||||
$RSERV_BIN/MasterAddTable $masterdb test k |
||||
$RSERV_BIN/MasterAddTable $masterdb testoid oid |
||||
$RSERV_BIN/MasterAddTable $masterdb teststr i |
||||
|
||||
echo Create and fill test, testoid and teststr tables in slave db |
||||
fill $slavedb |
||||
echo |
||||
echo Register test, testoid and teststr tables for replication on slave |
||||
echo |
||||
$RSERV_BIN/SlaveAddTable $slavedb test k |
||||
$RSERV_BIN/SlaveAddTable $slavedb testoid oid |
||||
$RSERV_BIN/SlaveAddTable $slavedb teststr i |
||||
|
||||
echo |
||||
echo |
||||
echo |
||||
echo |
||||
echo |
||||
echo |
||||
echo |
||||
echo |
||||
echo " Now make changes in $masterdb db and run" |
||||
echo |
||||
echo " Replicate $masterdb $slavedb" |
||||
echo |
||||
echo " to replicate the master on the slave." |
||||
echo |
||||
echo " You may also use the RservTest tcl utility" |
||||
echo " to demonstrate this functionality." |
||||
echo |
||||
echo |
||||
echo |
||||
echo |
||||
echo |
||||
echo |
||||
echo |
||||
echo |
||||
|
||||
exit |
||||
|
||||
############################################################################ |
@ -0,0 +1,59 @@ |
||||
# Makefile for erServer demonstration implementation
|
||||
# (c) 2000 Vadim Mikheev, PostgreSQL Inc.
|
||||
|
||||
#vpath %.pl perl
|
||||
#vpath %.pm perl
|
||||
|
||||
subdir = contrib/rserv
|
||||
top_builddir = ../..
|
||||
include $(top_builddir)/src/Makefile.global |
||||
|
||||
NAME = rserv
|
||||
OBJS = $(NAME).o
|
||||
DOCS = README.$(NAME)
|
||||
SQLS = master.sql slave.sql
|
||||
TCLS = RservTest
|
||||
PERLS = MasterInit SlaveInit MasterAddTable SlaveAddTable Replicate CleanLog
|
||||
PERLS += PrepareSnapshot ApplySnapshot GetSyncID SyncSyncID
|
||||
LIBS = RServ.pm
|
||||
SCRIPTS = InitRservTest
|
||||
MODS = $(OBJS:.o=$(DLSUFFIX))
|
||||
|
||||
override CPPFLAGS += -I$(srcdir)
|
||||
override CFLAGS += $(CFLAGS_SL)
|
||||
|
||||
INPUTFILES = $(wildcard *.in)
|
||||
CLEANFILES = $(INPUTFILES:.in=)
|
||||
CLEANFILES += $(OBJS) $(MODS)
|
||||
|
||||
.PHONY: all install installdirs tarball |
||||
|
||||
all: $(SQLS) $(TCLS) $(PERLS) $(SCRIPTS) $(MODS) |
||||
|
||||
install: all installdirs |
||||
$(INSTALL_DATA) $(SQLS) $(libdir)/contrib
|
||||
$(INSTALL_SCRIPT) $(TCLS) $(PERLS) $(SCRIPTS) $(bindir)
|
||||
$(INSTALL_SCRIPT) $(LIBS) $(libdir)/contrib
|
||||
$(INSTALL_SHLIB) $(MODS) $(libdir)/contrib
|
||||
$(INSTALL_DATA) $(DOCS) $(docdir)/contrib/$(NAME)
|
||||
|
||||
installdirs: |
||||
$(mkinstalldirs) $(datadir)/contrib $(libdir)/contrib $(docdir)/contrib/$(NAME)
|
||||
|
||||
%.sql: %.sql.in |
||||
rm -f $@; \
|
||||
C=`pwd`; \
|
||||
sed -e "s:_OBJWD_:$(libdir)/contrib:g" \
|
||||
-e "s:_DLSUFFIX_:$(DLSUFFIX):g" < $< > $@
|
||||
|
||||
%: %.in |
||||
sed -e "s:_OBJWD_:$(libdir)/contrib:g" \
|
||||
-e "s:_DLSUFFIX_:$(DLSUFFIX):g" \
|
||||
-e "s:@SQLDIR@:$(libdir)/contrib:g" \
|
||||
-e "s:@BINDIR@:$(bindir):g" \
|
||||
-e "s:@LIBDIR@:$(libdir)/contrib:g" < $< > $@
|
||||
chmod 775 $@
|
||||
|
||||
clean: |
||||
# @echo "Removing $(CLEANFILES)"
|
||||
rm -f $(CLEANFILES)
|
@ -0,0 +1,61 @@ |
||||
# -*- perl -*- |
||||
# MasterAddTable |
||||
# Vadim Mikheev, (c) 2000, PostgreSQL Inc. |
||||
|
||||
eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' |
||||
& eval 'exec perl -S $0 $argv:q' |
||||
if 0; |
||||
|
||||
use Pg; |
||||
use Getopt::Long; |
||||
|
||||
$| = 1; |
||||
|
||||
$result = GetOptions("debug!", "verbose!", "help", |
||||
"host=s", "user=s", "password=s"); |
||||
|
||||
my $debug = $opt_debug || 0; |
||||
my $verbose = $opt_verbose || 0; |
||||
|
||||
if (defined($opt_help) || (scalar(@ARGV) < 3)) { |
||||
print "Usage: $0 --host=name --user=name --password=string masterdb table column\n"; |
||||
exit ((scalar(@ARGV) < 3)? 1: 0); |
||||
} |
||||
|
||||
my $dbname = $ARGV[0]; |
||||
my $table = $ARGV[1]; |
||||
my $keyname = $ARGV[2]; |
||||
|
||||
my $minfo = "dbname=$dbname"; |
||||
$minfo = "$minfo host=$opt_host" if (defined($opt_host)); |
||||
$minfo = "$minfo user=$opt_user" if (defined($opt_user)); |
||||
$minfo = "$minfo password=$opt_password" if (defined($opt_password)); |
||||
|
||||
my $conn = Pg::connectdb($minfo); |
||||
|
||||
my $result = $conn->exec("BEGIN"); |
||||
die $conn->errorMessage if $result->resultStatus ne PGRES_COMMAND_OK; |
||||
|
||||
$result = $conn->exec("select pgc.oid, pga.attnum from pg_class pgc" . |
||||
", pg_attribute pga where pgc.relname = '$table'" . |
||||
" and pgc.oid = pga.attrelid" . |
||||
" and pga.attname = '$keyname'"); |
||||
die $conn->errorMessage if $result->resultStatus ne PGRES_TUPLES_OK; |
||||
|
||||
my @row = $result->fetchrow; |
||||
die "Can't find table/key\n" if ! defined $row[0] || ! defined $row[1]; |
||||
|
||||
$result = $conn->exec("create trigger _RSERV_TRIGGER_T_ after" . |
||||
" insert or update or delete on $table" . |
||||
" for each row execute procedure" . |
||||
" _rserv_log_('$row[1]')"); |
||||
die $conn->errorMessage if $result->resultStatus ne PGRES_COMMAND_OK; |
||||
|
||||
$result = $conn->exec("insert into _RSERV_TABLES_ (tname, cname, reloid, key)" . |
||||
" values ('$table', '$keyname', $row[0], $row[1])"); |
||||
die $conn->errorMessage if $result->resultStatus ne PGRES_COMMAND_OK; |
||||
|
||||
$result = $conn->exec("COMMIT"); |
||||
die $conn->errorMessage if $result->resultStatus ne PGRES_COMMAND_OK; |
||||
|
||||
exit(0); |
@ -0,0 +1,107 @@ |
||||
# -*- perl -*- |
||||
# MasterInit |
||||
# Vadim Mikheev, (c) 2000, PostgreSQL Inc. |
||||
|
||||
eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' |
||||
& eval 'exec perl -S $0 $argv:q' |
||||
if 0; |
||||
|
||||
use Pg; |
||||
use Getopt::Long; |
||||
|
||||
$| = 1; |
||||
|
||||
$result = GetOptions("debug!", "verbose!", "help", |
||||
"host=s", "user=s", "password=s"); |
||||
|
||||
my $debug = $opt_debug || 0; |
||||
my $verbose = $opt_verbose || 0; |
||||
|
||||
if (defined($opt_help) || (scalar(@ARGV) < 1)) { |
||||
print "Usage: $0 --host=name --user=name --password=string masterdb\n"; |
||||
exit ((scalar(@ARGV) < 1)? 1:0); |
||||
} |
||||
|
||||
my $master = $ARGV[0] || "master"; |
||||
|
||||
my $minfo = "dbname=$master"; |
||||
$minfo = "$minfo host=$opt_host" if (defined($opt_host)); |
||||
$minfo = "$minfo user=$opt_user" if (defined($opt_user)); |
||||
$minfo = "$minfo password=$opt_password" if (defined($opt_password)); |
||||
|
||||
sub RollbackAndQuit { |
||||
$conn = shift @_; |
||||
|
||||
print STDERR "Error in query: ", $conn->errorMessage; |
||||
$conn->exec("ROLLBACK"); |
||||
exit (-1); |
||||
} |
||||
|
||||
my $conn = Pg::connectdb($minfo); |
||||
if ($conn->status != PGRES_CONNECTION_OK) { |
||||
print STDERR "Failed opening $minfo\n"; |
||||
exit 1; |
||||
} |
||||
|
||||
my $result = $conn->exec("BEGIN"); |
||||
RollbackAndQuit($conn) if ($result->resultStatus ne PGRES_COMMAND_OK); |
||||
|
||||
$result = $conn->exec("set transaction isolation level serializable"); |
||||
RollbackAndQuit($conn) if ($result->resultStatus ne PGRES_COMMAND_OK); |
||||
|
||||
# List of slave servers |
||||
$result = $conn->exec("create table _RSERV_SERVERS_" . |
||||
" (server serial, host text, post int4, dbase text)"); |
||||
RollbackAndQuit($conn) if ($result->resultStatus ne PGRES_COMMAND_OK); |
||||
|
||||
# List of replicated tables |
||||
$result = $conn->exec("create table _RSERV_TABLES_" . |
||||
" (tname name, cname name, reloid oid, key int4)"); |
||||
RollbackAndQuit($conn) if ($result->resultStatus ne PGRES_COMMAND_OK); |
||||
|
||||
# Bookkeeping log for row replication |
||||
$result = $conn->exec("create table _RSERV_LOG_" . |
||||
" (reloid oid, logid int4, logtime timestamp, deleted int4, key text)"); |
||||
RollbackAndQuit($conn) if ($result->resultStatus ne PGRES_COMMAND_OK); |
||||
|
||||
# This is to speedup lookup of deleted tuples |
||||
$result = $conn->exec("create index _RSERV_LOG_INDX_DLT_ID_ on _RSERV_LOG_ (deleted, logid)"); |
||||
RollbackAndQuit($conn) if ($result->resultStatus ne PGRES_COMMAND_OK); |
||||
|
||||
# This is to speedup cleanup |
||||
$result = $conn->exec("create index _RSERV_LOG_INDX_TM_ID_ on _RSERV_LOG_ (logtime, logid)"); |
||||
RollbackAndQuit($conn) if ($result->resultStatus ne PGRES_COMMAND_OK); |
||||
|
||||
# This is to speedup trigger and lookup of updated tuples |
||||
$result = $conn->exec("create index _RSERV_LOG_INDX_REL_KEY_ on _RSERV_LOG_ (reloid, key)"); |
||||
RollbackAndQuit($conn) if ($result->resultStatus ne PGRES_COMMAND_OK); |
||||
|
||||
# Sync point for each slave server |
||||
$result = $conn->exec("create table _RSERV_SYNC_" . |
||||
" (server int4, syncid int4, synctime timestamp" . |
||||
", status int4, minid int4, maxid int4, active text)"); |
||||
RollbackAndQuit($conn) if ($result->resultStatus ne PGRES_COMMAND_OK); |
||||
|
||||
$result = $conn->exec("create index _RSERV_SYNC_INDX_SRV_ID_ on _RSERV_SYNC_ (server, syncid)"); |
||||
RollbackAndQuit($conn) if ($result->resultStatus ne PGRES_COMMAND_OK); |
||||
|
||||
# Sync point reference numbers |
||||
$result = $conn->exec("create sequence _rserv_sync_seq_"); |
||||
RollbackAndQuit($conn) if ($result->resultStatus ne PGRES_COMMAND_OK); |
||||
|
||||
$result = $conn->exec("CREATE FUNCTION _rserv_log_() RETURNS opaque" . |
||||
" AS '_OBJWD_/rserv_DLSUFFIX_' LANGUAGE 'c'"); |
||||
RollbackAndQuit($conn) if ($result->resultStatus ne PGRES_COMMAND_OK); |
||||
|
||||
$result = $conn->exec("CREATE FUNCTION _rserv_sync_(int4) RETURNS int4" . |
||||
" AS '_OBJWD_/rserv_DLSUFFIX_' LANGUAGE 'c'"); |
||||
RollbackAndQuit($conn) if ($result->resultStatus ne PGRES_COMMAND_OK); |
||||
|
||||
$result = $conn->exec("CREATE FUNCTION _rserv_debug_(int4) RETURNS int4" . |
||||
" AS '_OBJWD_/rserv_DLSUFFIX_' LANGUAGE 'c'"); |
||||
RollbackAndQuit($conn) if ($result->resultStatus ne PGRES_COMMAND_OK); |
||||
|
||||
$result = $conn->exec("COMMIT"); |
||||
RollbackAndQuit($conn) if ($result->resultStatus ne PGRES_COMMAND_OK); |
||||
|
||||
exit (0); |
@ -0,0 +1,56 @@ |
||||
# -*- perl -*- |
||||
# PrepareSnapshot |
||||
# Vadim Mikheev, (c) 2000, PostgreSQL Inc. |
||||
|
||||
eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' |
||||
& eval 'exec perl -S $0 $argv:q' |
||||
if 0; |
||||
|
||||
use lib "@LIBDIR@"; |
||||
|
||||
use IO::File; |
||||
use Getopt::Long; |
||||
use RServ; |
||||
|
||||
$| = 1; |
||||
|
||||
$result = GetOptions("debug!", "verbose!", "help", "snapshot=s", |
||||
"host=s", "user=s", "password=s"); |
||||
|
||||
my $debug = $opt_debug || 0; |
||||
my $verbose = $opt_verbose || 0; |
||||
my $snapshot = $opt_snapshot || "__Snapshot"; |
||||
|
||||
if (defined($opt_help) || (scalar(@ARGV) < 1)) { |
||||
print "Usage: $0 --snapshot=file --host=name --user=name --password=string masterdb\n"; |
||||
exit ((scalar(@ARGV) < 1)? 1:0); |
||||
} |
||||
|
||||
my $master = $ARGV[0] || "master"; |
||||
my $server = 0; |
||||
|
||||
my $minfo = "dbname=$master"; |
||||
$minfo = "$minfo host=$opt_host" if (defined($opt_host)); |
||||
$minfo = "$minfo user=$opt_user" if (defined($opt_user)); |
||||
$minfo = "$minfo password=$opt_password" if (defined($opt_password)); |
||||
|
||||
my $conn = Pg::connectdb($minfo); |
||||
|
||||
my $outf = new IO::File; |
||||
$outf = STDOUT; |
||||
|
||||
$res = PrepareSnapshot ($conn, $outf, $server); |
||||
|
||||
if ($res == 0) |
||||
{ |
||||
printf STDERR "Sync-ed\n"; |
||||
exit(0); |
||||
} |
||||
if ($res > 0) |
||||
{ |
||||
printf STDERR "Snapshot dumped to STDOUT\n"; |
||||
exit(0); |
||||
} |
||||
|
||||
printf STDERR "ERROR\n"; |
||||
exit(1); |
@ -0,0 +1,128 @@ |
||||
erServer demonstration implementation |
||||
(c) 2000, Vadim Mikheev and Thomas Lockhart, PostgreSQL Inc. |
||||
|
||||
Version 0.1: |
||||
Replicates a master database to a single slave database. |
||||
Tested under Linux (Mandrake 7.2). |
||||
|
||||
Requirements: |
||||
|
||||
- PostgreSQL >= 7.0.X |
||||
A separate Makefile is required for PostgreSQL 7.0.x and earlier |
||||
- Perl5 and the PostgreSQL perl interface |
||||
- TCL and the PostgreSQL tcl interface (for demo only) |
||||
|
||||
|
||||
How to compile: |
||||
|
||||
- make all |
||||
- make install |
||||
|
||||
Scripts and libraries are installed in places which are consistant |
||||
with the way other contrib/ code is installed; underneath the core |
||||
items in a contrib/ directory. |
||||
|
||||
|
||||
The toolset: |
||||
|
||||
MasterInit dbname |
||||
sets up structures and user-defined functions for a master |
||||
database. |
||||
|
||||
SlaveInit dbname |
||||
sets up structures for a slave database. Does not include triggers, |
||||
but only bookkeeping tables. |
||||
|
||||
MasterAddTable dbname table column |
||||
sets up triggers for the specified table and column. Note that this |
||||
column must be updated for replication to occur. |
||||
|
||||
SlaveAddTable dbname table column |
||||
sets up bookkeeping for the specified table and column. |
||||
|
||||
Replicate masterdb slavedb |
||||
actually replicate changes from a master to single slave. Note that |
||||
this must be repeated to replicate to multiple slaves, but the |
||||
bookkeeping for each slave is handled separately so each can be |
||||
updated at different times and with different sets of changes. |
||||
|
||||
GetSyncID [--noverbose] slavedb |
||||
returns the last syncid the specified slave has seen. May be used |
||||
in conjunction with CleanLog using the --noverbose option. |
||||
|
||||
CleanLog masterdb syncid |
||||
removes obsolete entries in the master database replication log |
||||
table up to the specified replication sequence number. |
||||
|
||||
Other utilities: |
||||
|
||||
PrepareSnapshot |
||||
build a file of replication information from the specified masterdb. |
||||
|
||||
ApplySnapshot |
||||
use a file of replication information to apply to the specified |
||||
slavedb. |
||||
|
||||
|
||||
How to run a demo: |
||||
|
||||
Run the InitRservTest script. It will create two local databases |
||||
'master' & 'slave' with table 'test' in them. It accepts the following |
||||
arguments: |
||||
--help |
||||
Print a usage message and quit. |
||||
--user name |
||||
Access the database with another username. |
||||
--host name |
||||
Access a remote database. Note that the shared library *must* be |
||||
visible in the same path as installed on the build machine. |
||||
masterdb |
||||
slavedb |
||||
Names of test databases. Defaults to 'master' and 'slave', |
||||
respectively. |
||||
|
||||
Once the test databases are set up, simply updating the master table |
||||
is sufficient to log a replication event. You can update the table |
||||
test then run "Replicate master slave", or you can use the demo |
||||
program RservTest. |
||||
|
||||
Run the tcl/tk GUI demo program, RservTest. It has a single window, |
||||
which has four buttons and three data entry boxes. |
||||
|
||||
-------------------------------------------------- |
||||
| PostgreSQL Asynchronous Replication | |
||||
| Master Slave | |
||||
| < master > < slave > | |
||||
| | |
||||
| [ Update ] < > | |
||||
| [ Replicate ] | |
||||
| [ Show ] ____________ | |
||||
| | |
||||
| [ Quit ] | |
||||
| | |
||||
-------------------------------------------------- |
||||
|
||||
The demo has the following behaviors: |
||||
|
||||
If you enter a string into the data entry field to the right of |
||||
[Update], then that string will be used to either update the master |
||||
database or to query the slave database. |
||||
|
||||
If you click [Update], then the string in the data entry box will be |
||||
entered into the master database. |
||||
|
||||
If you click [Replicate], then all changes since the last replication |
||||
will be propagated to the slave database. |
||||
|
||||
If you click [Show], then the slave database will be queried to find |
||||
the string in the data entry box to the right of the [Update] |
||||
button. If the string does not (yet) exist in the slave database, then |
||||
"n/a" will appear to the right of the [Show] button. If the string |
||||
does exist in the slave database, then it will be printed to the right |
||||
of the [Show] button. |
||||
|
||||
|
||||
Todo: |
||||
1. Support for multiple slave servers. |
||||
2. Explicit support for master/slave failover. |
||||
3. More docs. |
@ -0,0 +1,761 @@ |
||||
# -*- perl -*- |
||||
# RServ.pm |
||||
# Vadim Mikheev, (c) 2000, PostgreSQL Inc. |
||||
|
||||
package RServ; |
||||
|
||||
require Exporter; |
||||
@ISA = qw(Exporter); |
||||
@EXPORT = qw(PrepareSnapshot ApplySnapshot GetSyncID SyncSyncID CleanLog); |
||||
@EXPORT_OK = qw(); |
||||
|
||||
use Pg; |
||||
|
||||
$debug = 0; |
||||
$quiet = 1; |
||||
|
||||
my %Mtables = (); |
||||
my %Stables = (); |
||||
|
||||
sub PrepareSnapshot |
||||
{ |
||||
my ($conn, $outf, $server) = @_; # (@_[0], @_[1], @_[2]); |
||||
|
||||
my $result = $conn->exec("BEGIN"); |
||||
if ($result->resultStatus ne PGRES_COMMAND_OK) |
||||
{ |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-1); |
||||
} |
||||
$result = $conn->exec("set transaction isolation level serializable"); |
||||
if ($result->resultStatus ne PGRES_COMMAND_OK) |
||||
{ |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-1); |
||||
} |
||||
|
||||
# MAP oid --> tabname, keyname |
||||
$result = $conn->exec("select pgc.oid, pgc.relname, pga.attname" . |
||||
" from _RSERV_TABLES_ rt, pg_class pgc, pg_attribute pga" . |
||||
" where pgc.oid = rt.reloid and pga.attrelid = rt.reloid" . |
||||
" and pga.attnum = rt.key"); |
||||
if ($result->resultStatus ne PGRES_TUPLES_OK) |
||||
{ |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-1); |
||||
} |
||||
|
||||
my @row; |
||||
while (@row = $result->fetchrow) |
||||
{ |
||||
# printf "$row[0], $row[1], $row[2]\n"; |
||||
push @{$Mtables{$row[0]}}, $row[1], $row[2]; |
||||
} |
||||
|
||||
# Read last succeeded sync |
||||
$sql = "select syncid, synctime, minid, maxid, active from _RSERV_SYNC_" . |
||||
" where server = $server and syncid = (select max(syncid) from" . |
||||
" _RSERV_SYNC_ where server = $server and status > 0)"; |
||||
|
||||
printf "$sql\n" if $debug; |
||||
|
||||
$result = $conn->exec($sql); |
||||
if ($result->resultStatus ne PGRES_TUPLES_OK) |
||||
{ |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-1); |
||||
} |
||||
|
||||
my @lastsync = $result->fetchrow; |
||||
|
||||
my $sinfo = ""; |
||||
if ($lastsync[3] ne '') # sync info |
||||
{ |
||||
$sinfo = "and (l.logid >= $lastsync[3]"; |
||||
$sinfo .= " or l.logid in ($lastsync[4])" if $lastsync[4] ne ''; |
||||
$sinfo .= ")"; |
||||
} |
||||
|
||||
my $havedeal = 0; |
||||
|
||||
# DELETED rows |
||||
$sql = "select l.reloid, l.key from _RSERV_LOG_ l" . |
||||
" where l.deleted = 1 $sinfo order by l.reloid"; |
||||
|
||||
printf "$sql\n" if $debug; |
||||
|
||||
$result = $conn->exec($sql); |
||||
if ($result->resultStatus ne PGRES_TUPLES_OK) |
||||
{ |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-1); |
||||
} |
||||
|
||||
$lastoid = ''; |
||||
while (@row = $result->fetchrow) |
||||
{ |
||||
next unless exists $Mtables{$row[0]}; |
||||
if ($lastoid != $row[0]) |
||||
{ |
||||
if ($lastoid eq '') |
||||
{ |
||||
my $syncid = GetSYNCID($conn, $outf); |
||||
return($syncid) if $syncid < 0; |
||||
$havedeal = 1; |
||||
} |
||||
else |
||||
{ |
||||
printf $outf "\\.\n"; |
||||
} |
||||
printf $outf "-- DELETE $Mtables{$row[0]}[0]\n"; |
||||
$lastoid = $row[0]; |
||||
} |
||||
if (! defined $row[1]) |
||||
{ |
||||
print STDERR "NULL key\n" unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-2); |
||||
} |
||||
printf $outf "%s\n", OutputValue($row[1]); |
||||
} |
||||
printf $outf "\\.\n" if $lastoid ne ''; |
||||
|
||||
# UPDATED rows |
||||
|
||||
my ($taboid, $tabname, $tabkey); |
||||
foreach $taboid (keys %Mtables) |
||||
{ |
||||
($tabname, $tabkey) = @{$Mtables{$taboid}}; |
||||
my $oidkey = ($tabkey eq 'oid') ? "_$tabname.oid," : ''; |
||||
$sql = sprintf "select $oidkey _$tabname.* from _RSERV_LOG_ l," . |
||||
" $tabname _$tabname where l.reloid = $taboid and l.deleted = 0 $sinfo" . |
||||
" and l.key = _$tabname.${tabkey}::text"; |
||||
|
||||
printf "$sql\n" if $debug; |
||||
|
||||
$result = $conn->exec($sql); |
||||
if ($result->resultStatus ne PGRES_TUPLES_OK) |
||||
{ |
||||
printf $outf "-- ERROR\n" if $havedeal; |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-1); |
||||
} |
||||
next if $result->ntuples <= 0; |
||||
if (! $havedeal) |
||||
{ |
||||
my $syncid = GetSYNCID($conn, $outf); |
||||
return($syncid) if $syncid < 0; |
||||
$havedeal = 1; |
||||
} |
||||
printf $outf "-- UPDATE $tabname\n"; |
||||
while (@row = $result->fetchrow) |
||||
{ |
||||
for ($i = 0; $i <= $#row; $i++) |
||||
{ |
||||
printf $outf " " if $i; |
||||
printf $outf "%s", OutputValue($row[$i]); |
||||
} |
||||
printf $outf "\n"; |
||||
} |
||||
printf $outf "\\.\n"; |
||||
} |
||||
|
||||
unless ($havedeal) |
||||
{ |
||||
$conn->exec("ROLLBACK"); |
||||
return(0); |
||||
} |
||||
|
||||
# Remember this snapshot info |
||||
$result = $conn->exec("select _rserv_sync_($server)"); |
||||
if ($result->resultStatus ne PGRES_TUPLES_OK) |
||||
{ |
||||
printf $outf "-- ERROR\n"; |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-1); |
||||
} |
||||
|
||||
$result = $conn->exec("COMMIT"); |
||||
if ($result->resultStatus ne PGRES_COMMAND_OK) |
||||
{ |
||||
printf $outf "-- ERROR\n"; |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-1); |
||||
} |
||||
printf $outf "-- OK\n"; |
||||
|
||||
return(1); |
||||
|
||||
} |
||||
|
||||
sub OutputValue |
||||
{ |
||||
my ($val) = @_; # @_[0]; |
||||
|
||||
return("\\N") unless defined $val; |
||||
|
||||
$val =~ s/\\/\\\\/g; |
||||
$val =~ s/ /\\011/g; |
||||
$val =~ s/\n/\\012/g; |
||||
$val =~ s/\'/\\047/g; |
||||
|
||||
return($val); |
||||
} |
||||
|
||||
# Get syncid for new snapshot |
||||
sub GetSYNCID |
||||
{ |
||||
my ($conn, $outf) = @_; # (@_[0], @_[1]); |
||||
|
||||
my $result = $conn->exec("select nextval('_rserv_sync_seq_')"); |
||||
if ($result->resultStatus ne PGRES_TUPLES_OK) |
||||
{ |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-1); |
||||
} |
||||
|
||||
my @row = $result->fetchrow; |
||||
|
||||
printf $outf "-- SYNCID $row[0]\n"; |
||||
return($row[0]); |
||||
} |
||||
|
||||
|
||||
sub CleanLog |
||||
{ |
||||
my ($conn, $howold) = @_; # (@_[0], @_[1]); |
||||
|
||||
my $result = $conn->exec("BEGIN"); |
||||
if ($result->resultStatus ne PGRES_COMMAND_OK) |
||||
{ |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-1); |
||||
} |
||||
|
||||
my $sql = "select rs.maxid, rs.active from _RSERV_SYNC_ rs" . |
||||
" where rs.syncid = (select max(rs2.syncid) from _RSERV_SYNC_ rs2" . |
||||
" where rs2.server = rs.server and rs2.status > 0) order by rs.maxid"; |
||||
|
||||
printf "$sql\n" if $debug; |
||||
|
||||
$result = $conn->exec($sql); |
||||
if ($result->resultStatus ne PGRES_TUPLES_OK) |
||||
{ |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
return(-1); |
||||
} |
||||
my $maxid = ''; |
||||
my %active = (); |
||||
while (my @row = $result->fetchrow) |
||||
{ |
||||
$maxid = $row[0] if $maxid eq ''; |
||||
last if $row[0] > $maxid; |
||||
my @ids = split(/[ ]+,[ ]+/, $row[1]); |
||||
foreach $aid (@ids) |
||||
{ |
||||
$active{$aid} = 1 unless exists $active{$aid}; |
||||
} |
||||
} |
||||
if ($maxid eq '') |
||||
{ |
||||
print STDERR "No Sync IDs\n" unless ($quiet); |
||||
return(0); |
||||
} |
||||
my $alist = join(',', keys %active); |
||||
my $sinfo = "logid < $maxid"; |
||||
$sinfo .= " and logid not in ($alist)" if $alist ne ''; |
||||
|
||||
$sql = "delete from _RSERV_LOG_ where " . |
||||
"logtime < now() - '$howold second'::interval and $sinfo"; |
||||
|
||||
printf "$sql\n" if $debug; |
||||
|
||||
$result = $conn->exec($sql); |
||||
if ($result->resultStatus ne PGRES_COMMAND_OK) |
||||
{ |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-1); |
||||
} |
||||
$maxid = $result->cmdTuples; |
||||
|
||||
$result = $conn->exec("COMMIT"); |
||||
if ($result->resultStatus ne PGRES_COMMAND_OK) |
||||
{ |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-1); |
||||
} |
||||
|
||||
return($maxid); |
||||
} |
||||
|
||||
sub ApplySnapshot |
||||
{ |
||||
my ($conn, $inpf) = @_; # (@_[0], @_[1]); |
||||
|
||||
my $result = $conn->exec("BEGIN"); |
||||
if ($result->resultStatus ne PGRES_COMMAND_OK) |
||||
{ |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-1); |
||||
} |
||||
|
||||
$result = $conn->exec("SET CONSTRAINTS ALL DEFERRED"); |
||||
if ($result->resultStatus ne PGRES_COMMAND_OK) |
||||
{ |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-1); |
||||
} |
||||
|
||||
# MAP name --> oid, keyname, keynum |
||||
my $sql = "select pgc.oid, pgc.relname, pga.attname, rt.key" . |
||||
" from _RSERV_SLAVE_TABLES_ rt, pg_class pgc, pg_attribute pga" . |
||||
" where pgc.oid = rt.reloid and pga.attrelid = rt.reloid" . |
||||
" and pga.attnum = rt.key"; |
||||
$result = $conn->exec($sql); |
||||
if ($result->resultStatus ne PGRES_TUPLES_OK) |
||||
{ |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-1); |
||||
} |
||||
|
||||
while (@row = $result->fetchrow) |
||||
{ |
||||
# printf " %s %s\n", $row[1], $row[0]; |
||||
push @{$Stables{$row[1]}}, $row[0], $row[2], $row[3]; |
||||
} |
||||
|
||||
my $ok = 0; |
||||
my $syncid = ''; |
||||
while(<$inpf>) |
||||
{ |
||||
$_ =~ s/\n//; |
||||
my ($cmt, $cmd, $prm) = split (/[ ]+/, $_, 3); |
||||
if ($cmt ne '--') |
||||
{ |
||||
printf STDERR "Invalid format\n" unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-2); |
||||
} |
||||
if ($cmd eq 'DELETE') |
||||
{ |
||||
if ($syncid eq '') |
||||
{ |
||||
printf STDERR "Sync ID unspecified\n" unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-2); |
||||
} |
||||
$result = DoDelete($conn, $inpf, $prm); |
||||
if ($result) |
||||
{ |
||||
$conn->exec("ROLLBACK"); |
||||
return($result); |
||||
} |
||||
} |
||||
elsif ($cmd eq 'UPDATE') |
||||
{ |
||||
if ($syncid eq '') |
||||
{ |
||||
printf STDERR "Sync ID unspecified\n" unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-2); |
||||
} |
||||
$result = DoUpdate($conn, $inpf, $prm); |
||||
if ($result) |
||||
{ |
||||
$conn->exec("ROLLBACK"); |
||||
return($result); |
||||
} |
||||
} |
||||
elsif ($cmd eq 'SYNCID') |
||||
{ |
||||
if ($syncid ne '') |
||||
{ |
||||
printf STDERR "Second Sync ID ?!\n" unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-2); |
||||
} |
||||
if ($prm !~ /^\d+$/) |
||||
{ |
||||
printf STDERR "Invalid Sync ID $prm\n" unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-2); |
||||
} |
||||
$syncid = $prm; |
||||
|
||||
printf STDERR "Sync ID $syncid\n" unless ($quiet); |
||||
|
||||
$result = $conn->exec("select syncid, synctime from " . |
||||
"_RSERV_SLAVE_SYNC_ where syncid = " . |
||||
"(select max(syncid) from _RSERV_SLAVE_SYNC_)"); |
||||
if ($result->resultStatus ne PGRES_TUPLES_OK) |
||||
{ |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-1); |
||||
} |
||||
my @row = $result->fetchrow; |
||||
if (! defined $row[0]) |
||||
{ |
||||
$result = $conn->exec("insert into" . |
||||
" _RSERV_SLAVE_SYNC_(syncid, synctime) values ($syncid, now())"); |
||||
} |
||||
elsif ($row[0] >= $prm) |
||||
{ |
||||
printf STDERR "Sync-ed to ID $row[0] ($row[1])\n" unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(0); |
||||
} |
||||
else |
||||
{ |
||||
$result = $conn->exec("update _RSERV_SLAVE_SYNC_" . |
||||
" set syncid = $syncid, synctime = now()"); |
||||
} |
||||
if ($result->resultStatus ne PGRES_COMMAND_OK) |
||||
{ |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-1); |
||||
} |
||||
} |
||||
elsif ($cmd eq 'OK') |
||||
{ |
||||
$ok = 1; |
||||
last; |
||||
} |
||||
elsif ($cmd eq 'ERROR') |
||||
{ |
||||
printf STDERR "ERROR signaled\n" unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-2); |
||||
} |
||||
else |
||||
{ |
||||
printf STDERR "Unknown command $cmd\n" unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-2); |
||||
} |
||||
} |
||||
|
||||
if (! $ok) |
||||
{ |
||||
printf STDERR "No OK flag in input\n" unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-2); |
||||
} |
||||
|
||||
$result = $conn->exec("COMMIT"); |
||||
if ($result->resultStatus ne PGRES_COMMAND_OK) |
||||
{ |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-1); |
||||
} |
||||
|
||||
return(1); |
||||
} |
||||
|
||||
sub DoDelete |
||||
{ |
||||
my ($conn, $inpf, $tabname) = @_; # (@_[0], @_[1], @_[2]); |
||||
|
||||
my $ok = 0; |
||||
while(<$inpf>) |
||||
{ |
||||
if ($_ !~ /\n$/) |
||||
{ |
||||
printf STDERR "Invalid format\n" unless ($quiet); |
||||
return(-2); |
||||
} |
||||
my $key = $_; |
||||
$key =~ s/\n//; |
||||
if ($key eq '\.') |
||||
{ |
||||
$ok = 1; |
||||
last; |
||||
} |
||||
|
||||
my $sql = "delete from $tabname where $Stables{$tabname}->[1] = '$key'"; |
||||
|
||||
printf "$sql\n" if $debug; |
||||
|
||||
my $result = $conn->exec($sql); |
||||
if ($result->resultStatus ne PGRES_COMMAND_OK) |
||||
{ |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
return(-1); |
||||
} |
||||
} |
||||
|
||||
if (! $ok) |
||||
{ |
||||
printf STDERR "No end of input in DELETE section\n" unless ($quiet); |
||||
return(-2); |
||||
} |
||||
|
||||
return(0); |
||||
} |
||||
|
||||
|
||||
sub DoUpdate |
||||
{ |
||||
my ($conn, $inpf, $tabname) = @_; # (@_[0], @_[1], @_[2]); |
||||
my $oidkey = ($Stables{$tabname}->[2] < 0) ? 1 : 0; |
||||
|
||||
my @CopyBuf = (); |
||||
my $CBufLen = 0; |
||||
my $CBufMax = 16 * 1024 * 1024; # max size of buf for copy |
||||
|
||||
my $sql = "select attnum, attname from pg_attribute" . |
||||
" where attrelid = $Stables{$tabname}->[0] and attnum > 0"; |
||||
|
||||
my $result = $conn->exec($sql); |
||||
if ($result->resultStatus ne PGRES_TUPLES_OK) |
||||
{ |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
return(-1); |
||||
} |
||||
|
||||
my @anames = (); |
||||
while (@row = $result->fetchrow) |
||||
{ |
||||
$anames[$row[0]] = $row[1]; |
||||
} |
||||
|
||||
my $istring; |
||||
my $ok = 0; |
||||
while(<$inpf>) |
||||
{ |
||||
if ($_ !~ /\n$/) |
||||
{ |
||||
printf STDERR "Invalid format\n" unless ($quiet); |
||||
return(-2); |
||||
} |
||||
$istring = $_; |
||||
$istring =~ s/\n//; |
||||
if ($istring eq '\.') |
||||
{ |
||||
$ok = 1; |
||||
last; |
||||
} |
||||
my @vals = split(/ /, $istring); |
||||
if ($oidkey) |
||||
{ |
||||
if ($vals[0] !~ /^\d+$/ || $vals[0] <= 0) |
||||
{ |
||||
printf STDERR "Invalid OID\n" unless ($quiet); |
||||
return(-2); |
||||
} |
||||
$oidkey = $vals[0]; |
||||
} |
||||
else |
||||
{ |
||||
unshift @vals, ''; |
||||
} |
||||
|
||||
$sql = "update $tabname set "; |
||||
my $ocnt = 0; |
||||
for (my $i = 1; $i <= $#anames; $i++) |
||||
{ |
||||
if ($vals[$i] eq '\N') |
||||
{ |
||||
if ($i == $Stables{$tabname}->[2]) |
||||
{ |
||||
printf STDERR "NULL key\n" unless ($quiet); |
||||
return(-2); |
||||
} |
||||
$vals[$i] = 'null'; |
||||
} |
||||
else |
||||
{ |
||||
$vals[$i] = "'" . $vals[$i] . "'"; |
||||
next if $i == $Stables{$tabname}->[2]; |
||||
} |
||||
$ocnt++; |
||||
$sql .= ', ' if $ocnt > 1; |
||||
$sql .= "$anames[$i] = $vals[$i]"; |
||||
} |
||||
if ($oidkey) |
||||
{ |
||||
$sql .= " where $Stables{$tabname}->[1] = $oidkey"; |
||||
} |
||||
else |
||||
{ |
||||
$sql .= " where $Stables{$tabname}->[1] = $vals[$Stables{$tabname}->[2]]"; |
||||
} |
||||
|
||||
printf "$sql\n" if $debug; |
||||
|
||||
$result = $conn->exec($sql); |
||||
if ($result->resultStatus ne PGRES_COMMAND_OK) |
||||
{ |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
return(-1); |
||||
} |
||||
next if $result->cmdTuples == 1; # updated |
||||
|
||||
if ($result->cmdTuples > 1) |
||||
{ |
||||
printf STDERR "Duplicate keys\n" unless ($quiet); |
||||
return(-2); |
||||
} |
||||
|
||||
# no key - copy |
||||
push @CopyBuf, "$istring\n"; |
||||
$CBufLen += length($istring); |
||||
|
||||
if ($CBufLen >= $CBufMax) |
||||
{ |
||||
$result = DoCopy($conn, $tabname, $oidkey, \@CopyBuf); |
||||
return($result) if $result; |
||||
@CopyBuf = (); |
||||
$CBufLen = 0; |
||||
} |
||||
} |
||||
|
||||
if (! $ok) |
||||
{ |
||||
printf STDERR "No end of input in UPDATE section\n" unless ($quiet); |
||||
return(-2); |
||||
} |
||||
|
||||
if ($CBufLen) |
||||
{ |
||||
$result = DoCopy($conn, $tabname, $oidkey, \@CopyBuf); |
||||
return($result) if $result; |
||||
} |
||||
|
||||
return(0); |
||||
} |
||||
|
||||
|
||||
sub DoCopy |
||||
{ |
||||
my ($conn, $tabname, $withoids, $CBuf) = @_; # (@_[0], @_[1], @_[2], @_[3]); |
||||
|
||||
my $sql = "COPY $tabname " . (($withoids) ? "WITH OIDS " : '') . |
||||
"FROM STDIN"; |
||||
my $result = $conn->exec($sql); |
||||
if ($result->resultStatus ne PGRES_COPY_IN) |
||||
{ |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
return(-1); |
||||
} |
||||
|
||||
foreach $str (@{$CBuf}) |
||||
{ |
||||
$conn->putline($str); |
||||
} |
||||
|
||||
$conn->putline("\\.\n"); |
||||
|
||||
if ($conn->endcopy) |
||||
{ |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
return(-1); |
||||
} |
||||
|
||||
return(0); |
||||
|
||||
} |
||||
|
||||
|
||||
# |
||||
# Returns last SyncID applied on Slave |
||||
# |
||||
sub GetSyncID |
||||
{ |
||||
my ($conn) = @_; # (@_[0]); |
||||
|
||||
my $result = $conn->exec("select max(syncid) from _RSERV_SLAVE_SYNC_"); |
||||
if ($result->resultStatus ne PGRES_TUPLES_OK) |
||||
{ |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
return(-1); |
||||
} |
||||
my @row = $result->fetchrow; |
||||
return(undef) unless defined $row[0]; # null |
||||
return($row[0]); |
||||
} |
||||
|
||||
# |
||||
# Updates _RSERV_SYNC_ on Master with Slave SyncID |
||||
# |
||||
sub SyncSyncID |
||||
{ |
||||
my ($conn, $server, $syncid) = @_; # (@_[0], @_[1], @_[2]); |
||||
|
||||
my $result = $conn->exec("BEGIN"); |
||||
if ($result->resultStatus ne PGRES_COMMAND_OK) |
||||
{ |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-1); |
||||
} |
||||
|
||||
$result = $conn->exec("select synctime, status from _RSERV_SYNC_" . |
||||
" where server = $server and syncid = $syncid" . |
||||
" for update"); |
||||
if ($result->resultStatus ne PGRES_TUPLES_OK) |
||||
{ |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-1); |
||||
} |
||||
my @row = $result->fetchrow; |
||||
if (! defined $row[0]) |
||||
{ |
||||
printf STDERR "No SyncID $syncid found for server $server\n" unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(0); |
||||
} |
||||
if ($row[1] > 0) |
||||
{ |
||||
printf STDERR "SyncID $syncid for server $server already updated\n" unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(0); |
||||
} |
||||
$result = $conn->exec("update _RSERV_SYNC_" . |
||||
" set synctime = now(), status = 1" . |
||||
" where server = $server and syncid = $syncid"); |
||||
if ($result->resultStatus ne PGRES_COMMAND_OK) |
||||
{ |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-1); |
||||
} |
||||
$result = $conn->exec("delete from _RSERV_SYNC_" . |
||||
" where server = $server and syncid < $syncid"); |
||||
if ($result->resultStatus ne PGRES_COMMAND_OK) |
||||
{ |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-1); |
||||
} |
||||
|
||||
$result = $conn->exec("COMMIT"); |
||||
if ($result->resultStatus ne PGRES_COMMAND_OK) |
||||
{ |
||||
print STDERR $conn->errorMessage unless ($quiet); |
||||
$conn->exec("ROLLBACK"); |
||||
return(-1); |
||||
} |
||||
|
||||
return(1); |
||||
} |
||||
|
||||
1; |
@ -0,0 +1,100 @@ |
||||
# -*- perl -*- |
||||
# Replicate |
||||
# Vadim Mikheev, (c) 2000, PostgreSQL Inc. |
||||
|
||||
eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' |
||||
& eval 'exec perl -S $0 $argv:q' |
||||
if 0; |
||||
|
||||
use lib "@LIBDIR@"; |
||||
|
||||
use IO::File; |
||||
use Getopt::Long; |
||||
use RServ; |
||||
|
||||
$| = 1; |
||||
|
||||
$result = GetOptions("debug!", "verbose!", "help", "snapshot=s", |
||||
"masterhost=s", "slavehost=s", "host=s", |
||||
"masteruser=s", "slaveuser=s", "user=s", |
||||
"masterpassword=s", "slavepassword=s", "password=s"); |
||||
|
||||
my $debug = $opt_debug || 0; |
||||
my $verbose = $opt_verbose || 0; |
||||
my $snapshot = $opt_snapshot || "__Snapshot"; |
||||
|
||||
if (defined($opt_help) || (scalar(@ARGV) < 2)) { |
||||
print "Usage: $0 --snapshot=file --host=name --user=name --password=string masterdb slavedb\n"; |
||||
print "\t--masterhost=name --masteruser=name --masterpassword=string\n"; |
||||
print "\t--slavehost=name --slaveuser=name --slavepassword=string\n"; |
||||
exit ((scalar(@ARGV) < 2)? 1:0); |
||||
} |
||||
|
||||
my $master = $ARGV[0] || "master"; |
||||
my $slave = $ARGV[1] || "slave"; |
||||
my $server = 0; |
||||
|
||||
my $minfo = "dbname=$master"; |
||||
$minfo = "$minfo host=$opt_masterhost" if (defined($opt_masterhost)); |
||||
$minfo = "$minfo user=$opt_masteruser" if (defined($opt_masteruser)); |
||||
$minfo = "$minfo password=$opt_masterpassword" if (defined($opt_masterpassword)); |
||||
my $sinfo = "dbname=$slave"; |
||||
$sinfo = "$sinfo host=$opt_slavehost" if (defined($opt_slavehost)); |
||||
$sinfo = "$sinfo user=$opt_slaveuser" if (defined($opt_slaveuser)); |
||||
$sinfo = "$sinfo password=$opt_slavepassword" if (defined($opt_slavepassword)); |
||||
|
||||
print "Master connection is $minfo\n" if ($debug); |
||||
print "Slave connection is $sinfo\n" if ($debug); |
||||
|
||||
my $mconn = Pg::connectdb($minfo); |
||||
my $sconn = Pg::connectdb($sinfo); |
||||
|
||||
SyncSync($mconn, $sconn); |
||||
|
||||
my $outf = new IO::File; |
||||
open $outf, ">$snapshot"; |
||||
print "\n>>>>>>>>>>>>> Prepare Snapshot\n\n" if ($verbose); |
||||
$res = PrepareSnapshot($mconn, $outf, $server); |
||||
close $outf; |
||||
die "\n>>>>>>>>>>>>> ERROR\n" if $res < 0; |
||||
if ($res == 0) |
||||
{ |
||||
print "\n>>>>>>>>>>>>> DBases are sync-ed\n" if ($verbose); |
||||
exit(0); |
||||
} |
||||
|
||||
my $inpf = new IO::File; |
||||
open $inpf, "<$snapshot"; |
||||
print "\n>>>>>>>>>>>>> Apply Snapshot\n\n" if ($verbose); |
||||
$res = ApplySnapshot($sconn, $inpf); |
||||
close $inpf; |
||||
die "\n>>>>>>>>>>>>> ERROR\n" if $res < 0; |
||||
|
||||
if ($res > 0) |
||||
{ |
||||
print "Snapshot applied\n" if ($verbose); |
||||
unlink $snapshot unless ($debug); |
||||
SyncSync($mconn, $sconn); |
||||
} |
||||
|
||||
exit(0); |
||||
|
||||
########################################################################### |
||||
|
||||
sub SyncSync |
||||
{ |
||||
($mconn, $sconn) = @_; |
||||
|
||||
print "\n>>>>>>>>>>>>> Sync SyncID\n\n" if ($verbose); |
||||
print "Get last SyncID from Slave DB\n" if ($verbose); |
||||
$syncid = GetSyncID($sconn); |
||||
if ($syncid > 0) |
||||
{ |
||||
print "Last SyncID applied: $syncid\n" if ($verbose); |
||||
print "Sync SyncID\n" if ($verbose); |
||||
|
||||
$res = SyncSyncID($mconn, $server, $syncid); |
||||
|
||||
print "Succeeded\n" if (($res > 0) && ($verbose)); |
||||
} |
||||
} |
@ -0,0 +1,313 @@ |
||||
#!/bin/sh |
||||
# pgserv.tcl |
||||
# (c) 2000 Thomas Lockhart, PostgreSQL Inc. |
||||
# The next line will reinvoke as wish *DO NOT REMOVE OR ALTER* \ |
||||
exec wish "$0" "$@" |
||||
|
||||
puts "Starting Replication Server demo" |
||||
|
||||
set RSERV_BIN "@BINDIR@" |
||||
|
||||
# Bring in the interfaces we will be using... |
||||
|
||||
#package require Pgtcl |
||||
load libpgtcl[info sharedlibextension] |
||||
|
||||
# elog |
||||
# Information or error log and exit handler |
||||
proc {elog} {level message} { |
||||
global show |
||||
switch -exact -- $level { |
||||
DEBUG { |
||||
if {$show(debug)} { |
||||
puts "DEBUG $message" |
||||
} |
||||
} |
||||
ERROR { |
||||
if {$show(error)} { |
||||
puts "ERROR $message" |
||||
} |
||||
FATAL { |
||||
if ($show(error)} { |
||||
puts "FATAL $message" |
||||
} |
||||
exit 1 |
||||
} |
||||
default { |
||||
puts "INFO $message" |
||||
} |
||||
} |
||||
} |
||||
|
||||
proc {ShowUsage} {} { |
||||
global argv0 |
||||
puts "Usage: $argv0 --host name --user name --password string masterdb slavedb" |
||||
puts "\t--masterhost name --masteruser name --masterpassword string" |
||||
puts "\t--slavehost name --slaveuser name --slavepassword string" |
||||
} |
||||
|
||||
# Initial values for database access |
||||
# master, slave variables are tied to text input boxes, |
||||
# and will be updated on user input |
||||
proc {SetDbInfo} {db name {host ""} {user ""} {pass ""}} { |
||||
global dbinfo |
||||
set dbinfo($db,name) $name |
||||
set dbinfo($db,host) $host |
||||
set dbinfo($db,user) $user |
||||
set dbinfo($db,pass) $pass |
||||
} |
||||
|
||||
# ConnInfo |
||||
# Connection information for pgtcl library |
||||
proc {ConnInfo} {{db master}} { |
||||
global dbinfo |
||||
set ci "dbname=$dbinfo($db,name)" |
||||
if {[string length $dbinfo($db,host)] > 0} { |
||||
set ci "$ci host=$dbinfo($db,host)" |
||||
} |
||||
if {[string length $dbinfo($db,user)] > 0} { |
||||
set ci "$ci user=$dbinfo($db,user)" |
||||
} |
||||
if {[string length $dbinfo($db,pass)] > 0} { |
||||
set ci "$ci password=$dbinfo($db,pass)" |
||||
} |
||||
# puts "Construct conninfo $ci" |
||||
return $ci |
||||
} |
||||
|
||||
# ConnInfoParams |
||||
# Connection information for (perl) callable programs |
||||
proc {ConnInfoParams} {{db master}} { |
||||
global dbinfo |
||||
set ci "" |
||||
if {[string length $dbinfo($db,host)] > 0} { |
||||
set ci "$ci --host=$dbinfo($db,host)" |
||||
} |
||||
if {[string length $dbinfo($db,user)] > 0} { |
||||
set ci "$ci --user=$dbinfo($db,user)" |
||||
} |
||||
if {[string length $dbinfo($db,pass)] > 0} { |
||||
set ci "$ci --password=$dbinfo($db,pass)" |
||||
} |
||||
# puts "Construct conninfo $ci" |
||||
return $ci |
||||
} |
||||
|
||||
# ConnInfoMaster |
||||
# Connection information for (perl) callable programs |
||||
proc {ConnInfoMaster} {{db master}} { |
||||
global dbinfo |
||||
set ci $dbinfo($db,name) |
||||
if {[string length $dbinfo($db,host)] > 0} { |
||||
set ci "$ci --masterhost=$dbinfo($db,host)" |
||||
} |
||||
if {[string length $dbinfo($db,user)] > 0} { |
||||
set ci "$ci --masteruser=$dbinfo($db,user)" |
||||
} |
||||
if {[string length $dbinfo($db,pass)] > 0} { |
||||
set ci "$ci --masterpassword=$dbinfo($db,pass)" |
||||
} |
||||
# puts "Construct conninfo $ci" |
||||
return $ci |
||||
} |
||||
|
||||
# ConnInfoSlave |
||||
# Connection information for (perl) callable programs |
||||
proc {ConnInfoSlave} {{db slave}} { |
||||
global dbinfo |
||||
set ci $dbinfo($db,name) |
||||
if {[string length $dbinfo($db,host)] > 0} { |
||||
set ci "$ci --slavehost=$dbinfo($db,host)" |
||||
} |
||||
if {[string length $dbinfo($db,user)] > 0} { |
||||
set ci "$ci --slaveuser=$dbinfo($db,user)" |
||||
} |
||||
if {[string length $dbinfo($db,pass)] > 0} { |
||||
set ci "$ci --slavepassword=$dbinfo($db,pass)" |
||||
} |
||||
# puts "Construct conninfo $ci" |
||||
return $ci |
||||
} |
||||
|
||||
|
||||
SetDbInfo master master localhost |
||||
SetDbInfo slave slave localhost |
||||
set dbinfo(snapshot,name) "__Snapshot" |
||||
|
||||
set update "" |
||||
|
||||
set show(debug) 1 |
||||
set show(error) 1 |
||||
|
||||
set argi 0 |
||||
while {$argi < $argc} { |
||||
# puts "argi is $argi; argc is $argc" |
||||
set arg [lindex $argv $argi] |
||||
switch -glob -- $arg { |
||||
-h - |
||||
--host { |
||||
incr argi |
||||
set dbinfo(master,host) [lindex $argv $argi] |
||||
set dbinfo(slave,host) [lindex $argv $argi] |
||||
} |
||||
--masterhost { |
||||
incr argi |
||||
set dbinfo(master,host) [lindex $argv $argi] |
||||
} |
||||
--slavehost { |
||||
incr argi |
||||
set dbinfo(slave,host) [lindex $argv $argi] |
||||
} |
||||
-u - |
||||
--user { |
||||
incr argi |
||||
set dbinfo(master,user) [lindex $argv $argi] |
||||
set dbinfo(slave,user) [lindex $argv $argi] |
||||
} |
||||
--masteruser { |
||||
incr argi |
||||
set dbinfo(master,user) [lindex $argv $argi] |
||||
} |
||||
--slaveuser { |
||||
incr argi |
||||
set dbinfo(slave,user) [lindex $argv $argi] |
||||
} |
||||
-s - |
||||
--snapshot { |
||||
incr argi |
||||
set dbinfo(snapshot,name) [lindex $argv $argi] |
||||
} |
||||
-* { |
||||
elog ERROR "$argv0: invalid parameter '$arg'" |
||||
ShowUsage |
||||
exit 1 |
||||
} |
||||
default { |
||||
break |
||||
} |
||||
} |
||||
incr argi |
||||
} |
||||
|
||||
if {$argi < $argc} { |
||||
set dbinfo(master,name) [lindex $argv $argi] |
||||
incr argi |
||||
} |
||||
if {$argi < $argc} { |
||||
set dbinfo(slave,name) [lindex $argv $argi] |
||||
incr argi |
||||
} |
||||
if {$argi < $argc} { |
||||
elog "$argv0: too many parameters" |
||||
ShowUsage |
||||
exit 1 |
||||
} |
||||
|
||||
elog DEBUG "User is $dbinfo(master,user) $dbinfo(slave,user)" |
||||
elog DEBUG "Host is $dbinfo(master,host) $dbinfo(slave,host)" |
||||
|
||||
# |
||||
# TK layout |
||||
# |
||||
|
||||
wm title . "Async Replication" |
||||
|
||||
wm geom . 400x400 |
||||
|
||||
proc {CreateResultFrame} {b l w} { |
||||
pack [frame $b -borderwidth 10] -fill x |
||||
pack [button $b.run -text $l -command $l -width $w] -side left |
||||
# pack [entry $b.e -textvariable NewRow] -side left |
||||
} |
||||
|
||||
set t .top |
||||
pack [frame $t -borderwidth 10] -fill x |
||||
pack [frame $t.h -borderwidth 10] -fill x |
||||
pack [label $t.h.h -text "PostgreSQL Async Replication Server"] |
||||
|
||||
set b .b |
||||
pack [frame $b -borderwidth 10] -fill x |
||||
pack [frame $b.l -borderwidth 10] -fill x |
||||
pack [label $b.l.ml -text "Master"] -side left |
||||
pack [label $b.l.sl -text "Slave"] -side right |
||||
pack [entry $b.m -textvariable dbinfo(master,name) -width 25] -side left |
||||
pack [entry $b.s -textvariable dbinfo(slave,name) -width 25] -side right |
||||
|
||||
set b .u |
||||
pack [frame $b -borderwidth 10] -fill x |
||||
pack [button $b.run -text update -command Update -width 10] -side left |
||||
pack [entry $b.e -textvariable update -width 50] -side left |
||||
|
||||
set r [CreateResultFrame .r Replicate 10] |
||||
|
||||
set b .s |
||||
pack [frame $b -borderwidth 10] -fill x |
||||
pack [button $b.b -text Show -command Show -width 10] -side left |
||||
pack [label $b.e -text ""] -side left |
||||
|
||||
set b .button |
||||
pack [frame $b -borderwidth 10] -fill x |
||||
|
||||
pack [button $b.quit -text "Quit" -command Shutdown] |
||||
|
||||
# |
||||
# Functions mapped to buttons |
||||
# |
||||
|
||||
proc {Update} {} { |
||||
global dbinfo |
||||
global update |
||||
|
||||
elog DEBUG "Opening database [ConnInfo master]..." |
||||
set res [catch {set db [pg_connect -conninfo "[ConnInfo master]"]} msg] |
||||
if {$res} { |
||||
elog ERROR "Database '$dbinfo(master,name)' is not available: $res ($msg)" |
||||
} else { |
||||
elog DEBUG "Insert $update into database $dbinfo(master,name)..." |
||||
set res [pg_exec $db "insert into test select '$update', max(k)+1, max(l)+1 from test"] |
||||
elog DEBUG [pg_result $res -status] |
||||
catch {pg_disconnect $db} |
||||
} |
||||
} |
||||
|
||||
proc {Replicate} {} { |
||||
global dbinfo |
||||
global RSERV_BIN |
||||
|
||||
elog DEBUG "Replicating [ConnInfoCmd master]..." |
||||
exec "$RSERV_BIN/Replicate" --snapshot=$dbinfo(snapshot,name) [ConnInfoParams] [ConnInfoMaster] [ConnInfoSlave] |
||||
} |
||||
|
||||
proc {Show} {} { |
||||
global dbinfo |
||||
global update |
||||
|
||||
elog DEBUG "Opening database [ConnInfo slave]..." |
||||
set res [catch {set db [pg_connect -conninfo "[ConnInfo slave]"]} msg] |
||||
if {$res} { |
||||
elog ERROR "DB $dbinfo(slave,name) not available: $res ($msg)" |
||||
} else { |
||||
elog DEBUG "Select $update from database $dbinfo(slave,name)..." |
||||
set res [pg_exec $db "select i from test where i='$update'"] |
||||
if {[pg_result $res -status] != "PGRES_TUPLES_OK"} { |
||||
.s.e config -text "n/a" |
||||
} else { |
||||
set ntups [pg_result $res -numTuples] |
||||
if {$ntups <= 0} { |
||||
.s.e config -text "n/a" |
||||
} else { |
||||
for {set i 0} {$i < $ntups} {incr i} { |
||||
set val [pg_result $res -getTuple $i] |
||||
.s.e config -text $val |
||||
} |
||||
} |
||||
pg_result $res -clear |
||||
} |
||||
catch {pg_disconnect $db} |
||||
} |
||||
} |
||||
|
||||
proc {Shutdown} {} { |
||||
global dbinfo |
||||
exit |
||||
} |
@ -0,0 +1,59 @@ |
||||
# -*- perl -*- |
||||
# SlaveAddTable |
||||
# Vadim Mikheev, (c) 2000, PostgreSQL Inc. |
||||
|
||||
eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' |
||||
& eval 'exec perl -S $0 $argv:q' |
||||
if 0; |
||||
|
||||
use Pg; |
||||
use Getopt::Long; |
||||
|
||||
$| = 1; |
||||
|
||||
$result = GetOptions("debug!", "verbose!", "help", |
||||
"host=s", "user=s", "password=s"); |
||||
|
||||
my $debug = $opt_debug || 0; |
||||
my $verbose = $opt_verbose || 0; |
||||
|
||||
if (defined($opt_help) || (scalar(@ARGV) < 3)) { |
||||
print "Usage: $0 --host=name --user=name --password=string slavedb table column\n"; |
||||
exit ((scalar(@ARGV) < 3)? 1: 0); |
||||
} |
||||
|
||||
my $dbname = $ARGV[0]; |
||||
my $table = $ARGV[1]; |
||||
my $keyname = $ARGV[2]; |
||||
|
||||
my $sinfo = "dbname=$dbname"; |
||||
$sinfo = "$sinfo host=$opt_host" if (defined($opt_host)); |
||||
$sinfo = "$sinfo user=$opt_user" if (defined($opt_user)); |
||||
$sinfo = "$sinfo password=$opt_password" if (defined($opt_password)); |
||||
|
||||
my $dbname = $ARGV[0]; |
||||
my $table = $ARGV[1]; |
||||
my $keyname = $ARGV[2]; |
||||
|
||||
my $conn = Pg::connectdb($sinfo); |
||||
|
||||
my $result = $conn->exec("BEGIN"); |
||||
die $conn->errorMessage if $result->resultStatus ne PGRES_COMMAND_OK; |
||||
|
||||
$result = $conn->exec("select pgc.oid, pga.attnum from pg_class pgc" . |
||||
", pg_attribute pga" . |
||||
" where pgc.relname = '$table' and pgc.oid = pga.attrelid" . |
||||
" and pga.attname = '$keyname'"); |
||||
die $conn->errorMessage if $result->resultStatus ne PGRES_TUPLES_OK; |
||||
|
||||
my @row = $result->fetchrow; |
||||
die "Can't find table/key\n" if ! defined $row[0] || ! defined $row[1]; |
||||
|
||||
$result = $conn->exec("insert into _RSERV_SLAVE_TABLES_ (tname, cname, reloid, key)" . |
||||
" values ('$table', '$keyname', $row[0], $row[1])"); |
||||
die $conn->errorMessage if $result->resultStatus ne PGRES_COMMAND_OK; |
||||
|
||||
$result = $conn->exec("COMMIT"); |
||||
die $conn->errorMessage if $result->resultStatus ne PGRES_COMMAND_OK; |
||||
|
||||
exit(0); |
@ -0,0 +1,65 @@ |
||||
# -*- perl -*- |
||||
# SlaveInit |
||||
# Vadim Mikheev, (c) 2000, PostgreSQL Inc. |
||||
|
||||
eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' |
||||
& eval 'exec perl -S $0 $argv:q' |
||||
if 0; |
||||
|
||||
use Pg; |
||||
use Getopt::Long; |
||||
|
||||
$| = 1; |
||||
|
||||
$result = GetOptions("debug!", "verbose!", "quiet!", "help", |
||||
"host=s", "user=s", "password=s"); |
||||
|
||||
my $debug = $opt_debug || 0; |
||||
my $verbose = $opt_verbose || 0; |
||||
my $quiet = $opt_quiet || 0; |
||||
|
||||
if (defined($opt_help) || (scalar(@ARGV) < 1)) { |
||||
print "Usage: $0 --host=name --user=name --password=string slavedb\n"; |
||||
exit ((scalar(@ARGV) < 1)? 1:0); |
||||
} |
||||
|
||||
my $slave = $ARGV[0] || "slave"; |
||||
|
||||
my $sinfo = "dbname=$slave"; |
||||
$sinfo = "$sinfo host=$opt_host" if (defined($opt_host)); |
||||
$sinfo = "$sinfo user=$opt_user" if (defined($opt_user)); |
||||
$sinfo = "$sinfo password=$opt_password" if (defined($opt_password)); |
||||
|
||||
sub RollbackAndQuit { |
||||
my $conn = shift @_; |
||||
|
||||
print STDERR $conn->errorMessage; |
||||
$conn->exec("ROLLBACK"); |
||||
exit (-1); |
||||
} |
||||
|
||||
print "Connecting to $sinfo\n"; |
||||
my $conn = Pg::connectdb($sinfo); |
||||
if ($conn->status != PGRES_CONNECTION_OK) { |
||||
print STDERR "Failed opening $sinfo\n"; |
||||
exit 1; |
||||
} |
||||
|
||||
my $result = $conn->exec("BEGIN"); |
||||
RollbackAndQuit($conn) if ($result->resultStatus ne PGRES_COMMAND_OK); |
||||
|
||||
$result = $conn->exec("set transaction isolation level serializable"); |
||||
RollbackAndQuit($conn) if ($result->resultStatus ne PGRES_COMMAND_OK); |
||||
|
||||
$result = $conn->exec("create table _RSERV_SLAVE_TABLES_" . |
||||
" (tname name, cname name, reloid oid, key int4)"); |
||||
RollbackAndQuit($conn) if ($result->resultStatus ne PGRES_COMMAND_OK); |
||||
|
||||
$result = $conn->exec("create table _RSERV_SLAVE_SYNC_" . |
||||
" (syncid int4, synctime timestamp)"); |
||||
RollbackAndQuit($conn) if ($result->resultStatus ne PGRES_COMMAND_OK); |
||||
|
||||
$result = $conn->exec("COMMIT"); |
||||
RollbackAndQuit($conn) if ($result->resultStatus ne PGRES_COMMAND_OK); |
||||
|
||||
exit (0); |
@ -0,0 +1,48 @@ |
||||
# -*- perl -*- |
||||
# SyncSyncID |
||||
# Vadim Mikheev, (c) 2000, PostgreSQL Inc. |
||||
|
||||
eval '(exit $?0)' && eval 'exec perl -S $0 ${1+"$@"}' |
||||
& eval 'exec perl -S $0 $argv:q' |
||||
if 0; |
||||
|
||||
use lib "@LIBDIR@"; |
||||
|
||||
use Getopt::Long; |
||||
use RServ; |
||||
|
||||
$| = 1; |
||||
|
||||
$result = GetOptions("debug!", "verbose!", "quiet!", "help", |
||||
"host=s", "user=s", "password=s"); |
||||
|
||||
my $debug = $opt_debug || 0; |
||||
my $verbose = $opt_verbose || 0; |
||||
my $quiet = $opt_quiet || 0; |
||||
|
||||
if (defined($opt_help) || (scalar(@ARGV) < 2) || ($ARGV[1] !~ /^\d+$/)) { |
||||
print "Usage: $0 --host=name --user=name --password=string masterdb syncid\n"; |
||||
exit ((scalar(@ARGV) < 2)? 1:0); |
||||
} |
||||
|
||||
my $master = $ARGV[0] || "master"; |
||||
my $server = 0; |
||||
my $syncid = $ARGV[1] || die "SyncID not specified"; |
||||
|
||||
my $minfo = "dbname=$master"; |
||||
$minfo = "$minfo host=$opt_host" if (defined($opt_host)); |
||||
$minfo = "$minfo user=$opt_user" if (defined($opt_user)); |
||||
$minfo = "$minfo password=$opt_password" if (defined($opt_password)); |
||||
|
||||
my $conn = Pg::connectdb($minfo); |
||||
|
||||
$res = SyncSyncID($conn, $server, $syncid); |
||||
|
||||
if ($res == 0) |
||||
{ |
||||
printf STDERR "SyncID updated on $master\n" if ($verbose); |
||||
exit(0); |
||||
} |
||||
|
||||
printf STDERR "ERROR\n" unless ($quiet); |
||||
exit(1); |
@ -0,0 +1,101 @@ |
||||
-- erServer |
||||
-- Master server setup for erServer demonstration implementation |
||||
-- (c) 2000 Vadim Mikheev, PostgreSQL Inc. |
||||
-- |
||||
|
||||
-- |
||||
-- Slave servers |
||||
-- |
||||
drop table _RSERV_SERVERS_; |
||||
|
||||
create table _RSERV_SERVERS_ |
||||
( |
||||
server int4, -- slave server id |
||||
host text, -- server' host |
||||
port int4, -- server' port |
||||
dbase text -- db name |
||||
); |
||||
|
||||
|
||||
-- |
||||
-- Tables to sync |
||||
-- |
||||
drop table _RSERV_TABLES_; |
||||
|
||||
create table _RSERV_TABLES_ |
||||
( |
||||
tname name, -- table name |
||||
cname name, -- column name |
||||
reloid oid, -- table oid |
||||
key int4 -- key attnum |
||||
); |
||||
|
||||
|
||||
-- |
||||
-- Log for inserts/updates/deletes to sync-ed tables |
||||
-- |
||||
drop table _RSERV_LOG_; |
||||
|
||||
create table _RSERV_LOG_ |
||||
( |
||||
reloid oid, |
||||
logid int4, -- xid of last update xaction |
||||
logtime timestamp, -- last update xaction start time |
||||
deleted int4, -- deleted or inserted/updated |
||||
key text -- |
||||
); |
||||
|
||||
-- This is to speedup lookup deleted tuples |
||||
create index _RSERV_LOG_INDX_DLT_ID_ on _RSERV_LOG_ (deleted, logid); |
||||
|
||||
-- This is to speedup cleanup |
||||
create index _RSERV_LOG_INDX_TM_ID_ on _RSERV_LOG_ (logtime, logid); |
||||
|
||||
-- This is to speedup trigger and lookup updated tuples |
||||
create index _RSERV_LOG_INDX_REL_KEY_ on _RSERV_LOG_ (reloid, key); |
||||
|
||||
|
||||
-- |
||||
-- How much each slave servers are sync-ed |
||||
-- |
||||
drop table _RSERV_SYNC_; |
||||
|
||||
create table _RSERV_SYNC_ |
||||
( |
||||
server int4, |
||||
syncid int4, -- from _rserv_sync_seq_ |
||||
synctime timestamp, -- |
||||
status int4, -- prepared (0) | applied |
||||
minid int4, -- min xid from serializable snapshot |
||||
maxid int4, -- max xid from serializable snapshot |
||||
active text -- list of active xactions |
||||
); |
||||
|
||||
create index _RSERV_SYNC_INDX_SRV_ID_ on _RSERV_SYNC_ (server, syncid); |
||||
|
||||
drop sequence _rserv_sync_seq_; |
||||
create sequence _rserv_sync_seq_; |
||||
|
||||
drop function _rserv_log_(); |
||||
|
||||
CREATE FUNCTION _rserv_log_() |
||||
RETURNS opaque |
||||
AS '_OBJWD_/rserv_DLSUFFIX_' |
||||
LANGUAGE 'c' |
||||
; |
||||
|
||||
drop function _rserv_sync_(int4); |
||||
|
||||
CREATE FUNCTION _rserv_sync_(int4) |
||||
RETURNS int4 |
||||
AS '_OBJWD_/rserv_DLSUFFIX_' |
||||
LANGUAGE 'c' |
||||
; |
||||
|
||||
drop function _rserv_debug_(int4); |
||||
|
||||
CREATE FUNCTION _rserv_debug_(int4) |
||||
RETURNS int4 |
||||
AS '_OBJWD_/rserv_DLSUFFIX_' |
||||
LANGUAGE 'c' |
||||
; |
@ -0,0 +1,32 @@ |
||||
# regress.sh |
||||
# rserv regression test script |
||||
# (c) 2000 Thomas Lockhart, PostgreSQL Inc. |
||||
|
||||
dropdb master |
||||
dropdb slave |
||||
|
||||
createdb master |
||||
createdb slave |
||||
|
||||
MasterInit master |
||||
SlaveInit slave |
||||
|
||||
psql -c "create table t1 (i int, t text, d timestamp default text 'now');" master |
||||
MasterAddTable master t1 d |
||||
|
||||
psql -c "create table t1 (i int, t text, d timestamp default text 'now');" slave |
||||
SlaveAddTable slave t1 d |
||||
|
||||
psql -c "insert into t1 values (1, 'one');" master |
||||
psql -c "insert into t1 values (2, 'two');" master |
||||
|
||||
Replicate master slave |
||||
SyncSyncID master `GetSyncID --noverbose slave` |
||||
|
||||
psql -c "insert into t1 values (3, 'three');" master |
||||
psql -c "insert into t1 values (4, 'four');" master |
||||
|
||||
Replicate master slave |
||||
SyncSyncID master `GetSyncID --noverbose slave` |
||||
|
||||
exit |
@ -0,0 +1,319 @@ |
||||
/* rserv.c
|
||||
* Support functions for erServer replication. |
||||
* (c) 2000 Vadim Mikheev, PostgreSQL Inc. |
||||
*/ |
||||
|
||||
#include "executor/spi.h" /* this is what you need to work with SPI */ |
||||
#include "commands/trigger.h" /* -"- and triggers */ |
||||
#include "utils/tqual.h" /* -"- and SnapshotData */ |
||||
#include <ctype.h> /* tolower () */ |
||||
|
||||
#ifdef PG_FUNCTION_INFO_V1 |
||||
#define CurrentTriggerData ((TriggerData *) fcinfo->context) |
||||
#endif |
||||
|
||||
#ifdef PG_FUNCTION_INFO_V1 |
||||
PG_FUNCTION_INFO_V1(_rserv_log_); |
||||
PG_FUNCTION_INFO_V1(_rserv_sync_); |
||||
PG_FUNCTION_INFO_V1(_rserv_debug_); |
||||
Datum _rserv_log_(PG_FUNCTION_ARGS); |
||||
Datum _rserv_sync_(PG_FUNCTION_ARGS); |
||||
Datum _rserv_debug_(PG_FUNCTION_ARGS); |
||||
#else |
||||
HeapTuple _rserv_log_(void); |
||||
int32 _rserv_sync_(int32); |
||||
int32 _rserv_debug_(int32); |
||||
#endif |
||||
|
||||
static int debug = 0; |
||||
|
||||
static char* OutputValue(char *key, char *buf, int size); |
||||
|
||||
#ifdef PG_FUNCTION_INFO_V1 |
||||
Datum |
||||
_rserv_log_(PG_FUNCTION_ARGS) |
||||
#else |
||||
HeapTuple |
||||
_rserv_log_() |
||||
#endif |
||||
{ |
||||
Trigger *trigger; /* to get trigger name */ |
||||
int nargs; /* # of args specified in CREATE TRIGGER */ |
||||
char **args; /* argument: argnum */ |
||||
Relation rel; /* triggered relation */ |
||||
HeapTuple tuple; /* tuple to return */ |
||||
HeapTuple newtuple = NULL;/* tuple to return */ |
||||
TupleDesc tupdesc; /* tuple description */ |
||||
int keynum; |
||||
char *key; |
||||
char *okey; |
||||
char *newkey = NULL; |
||||
int deleted; |
||||
char sql[8192]; |
||||
char outbuf[8192]; |
||||
char oidbuf[64]; |
||||
int ret; |
||||
|
||||
/* Called by trigger manager ? */ |
||||
if (!CurrentTriggerData) |
||||
elog(ERROR, "_rserv_log_: triggers are not initialized"); |
||||
|
||||
/* Should be called for ROW trigger */ |
||||
if (TRIGGER_FIRED_FOR_STATEMENT(CurrentTriggerData->tg_event)) |
||||
elog(ERROR, "_rserv_log_: can't process STATEMENT events"); |
||||
|
||||
tuple = CurrentTriggerData->tg_trigtuple; |
||||
|
||||
trigger = CurrentTriggerData->tg_trigger; |
||||
nargs = trigger->tgnargs; |
||||
args = trigger->tgargs; |
||||
|
||||
if (nargs != 1) /* odd number of arguments! */ |
||||
elog(ERROR, "_rserv_log_: need in *one* argument"); |
||||
|
||||
keynum = atoi(args[0]); |
||||
|
||||
if (keynum < 0 && keynum != ObjectIdAttributeNumber) |
||||
elog(ERROR, "_rserv_log_: invalid keynum %d", keynum); |
||||
|
||||
rel = CurrentTriggerData->tg_relation; |
||||
tupdesc = rel->rd_att; |
||||
|
||||
deleted = (TRIGGER_FIRED_BY_DELETE(CurrentTriggerData->tg_event)) ?
|
||||
1 : 0; |
||||
|
||||
if (TRIGGER_FIRED_BY_UPDATE(CurrentTriggerData->tg_event)) |
||||
newtuple = CurrentTriggerData->tg_newtuple; |
||||
|
||||
/*
|
||||
* Setting CurrentTriggerData to NULL prevents direct calls to trigger |
||||
* functions in queries. Normally, trigger functions have to be called |
||||
* by trigger manager code only. |
||||
*/ |
||||
CurrentTriggerData = NULL; |
||||
|
||||
/* Connect to SPI manager */ |
||||
if ((ret = SPI_connect()) < 0) |
||||
elog(ERROR, "_rserv_log_: SPI_connect returned %d", ret); |
||||
|
||||
if (keynum == ObjectIdAttributeNumber) |
||||
{ |
||||
sprintf(oidbuf, "%u", tuple->t_data->t_oid); |
||||
key = oidbuf; |
||||
} |
||||
else |
||||
key = SPI_getvalue(tuple, tupdesc, keynum); |
||||
|
||||
if (key == NULL) |
||||
elog(ERROR, "_rserv_log_: key must be not null"); |
||||
|
||||
if (newtuple && keynum != ObjectIdAttributeNumber) |
||||
{ |
||||
newkey = SPI_getvalue(newtuple, tupdesc, keynum); |
||||
if (newkey == NULL) |
||||
elog(ERROR, "_rserv_log_: key must be not null"); |
||||
if (strcmp(newkey, key) == 0) |
||||
newkey = NULL; |
||||
else |
||||
deleted = 1; /* old key was deleted */ |
||||
} |
||||
|
||||
if (strpbrk(key, "\\ \n'")) |
||||
okey = OutputValue(key, outbuf, sizeof(outbuf)); |
||||
else |
||||
okey = key; |
||||
|
||||
sprintf(sql, "update _RSERV_LOG_ set logid = %d, logtime = now(), " |
||||
"deleted = %d where reloid = %u and key = '%s'",
|
||||
GetCurrentTransactionId(), deleted, rel->rd_id, okey); |
||||
|
||||
if (debug) |
||||
elog(NOTICE, sql); |
||||
|
||||
ret = SPI_exec(sql, 0); |
||||
|
||||
if (ret < 0) |
||||
elog(ERROR, "_rserv_log_: SPI_exec(update) returned %d", ret); |
||||
|
||||
/*
|
||||
* If no tuple was UPDATEd then do INSERT... |
||||
*/ |
||||
if (SPI_processed > 1) |
||||
elog(ERROR, "_rserv_log_: duplicate tuples"); |
||||
else if (SPI_processed == 0) |
||||
{ |
||||
sprintf(sql, "insert into _RSERV_LOG_ " |
||||
"(reloid, logid, logtime, deleted, key) " |
||||
"values (%u, %d, now(), %d, '%s')", |
||||
rel->rd_id, GetCurrentTransactionId(),
|
||||
deleted, okey); |
||||
|
||||
if (debug) |
||||
elog(NOTICE, sql); |
||||
|
||||
ret = SPI_exec(sql, 0); |
||||
|
||||
if (ret < 0) |
||||
elog(ERROR, "_rserv_log_: SPI_exec(insert) returned %d", ret); |
||||
} |
||||
|
||||
if (okey != key && okey != outbuf) |
||||
pfree(okey); |
||||
|
||||
if (newkey) |
||||
{ |
||||
if (strpbrk(newkey, "\\ \n'")) |
||||
okey = OutputValue(newkey, outbuf, sizeof(outbuf)); |
||||
else |
||||
okey = newkey; |
||||
|
||||
sprintf(sql, "insert into _RSERV_LOG_ " |
||||
"(reloid, logid, logtime, deleted, key) " |
||||
"values (%u, %d, now(), 0, '%s')",
|
||||
rel->rd_id, GetCurrentTransactionId(), okey); |
||||
|
||||
if (debug) |
||||
elog(NOTICE, sql); |
||||
|
||||
ret = SPI_exec(sql, 0); |
||||
|
||||
if (ret < 0) |
||||
elog(ERROR, "_rserv_log_: SPI_exec returned %d", ret); |
||||
|
||||
if (okey != newkey && okey != outbuf) |
||||
pfree(okey); |
||||
} |
||||
|
||||
SPI_finish(); |
||||
|
||||
#ifdef PG_FUNCTION_INFO_V1 |
||||
return (PointerGetDatum(tuple)); |
||||
#else |
||||
return (tuple); |
||||
#endif |
||||
} |
||||
|
||||
#ifdef PG_FUNCTION_INFO_V1 |
||||
Datum |
||||
_rserv_sync_(PG_FUNCTION_ARGS) |
||||
#else |
||||
int32 |
||||
_rserv_sync_(int32 server) |
||||
#endif |
||||
{ |
||||
#ifdef PG_FUNCTION_INFO_V1 |
||||
int32 server = PG_GETARG_INT32(0); |
||||
#endif |
||||
char sql[8192]; |
||||
char buf[8192]; |
||||
char *active = buf; |
||||
uint32 xcnt; |
||||
int ret; |
||||
|
||||
if (SerializableSnapshot == NULL) |
||||
elog(ERROR, "_rserv_sync_: SerializableSnapshot is NULL"); |
||||
|
||||
buf[0] = 0; |
||||
for (xcnt = 0; xcnt < SerializableSnapshot->xcnt; xcnt++) |
||||
{ |
||||
sprintf(buf + strlen(buf), "%s%u", (xcnt) ? ", " : "", |
||||
SerializableSnapshot->xip[xcnt]); |
||||
} |
||||
|
||||
if ((ret = SPI_connect()) < 0) |
||||
elog(ERROR, "_rserv_sync_: SPI_connect returned %d", ret); |
||||
|
||||
sprintf(sql, "insert into _RSERV_SYNC_ " |
||||
"(server, syncid, synctime, status, minid, maxid, active) " |
||||
"values (%u, currval('_rserv_sync_seq_'), now(), 0, %d, %d, '%s')",
|
||||
server, SerializableSnapshot->xmin, SerializableSnapshot->xmax, active); |
||||
|
||||
ret = SPI_exec(sql, 0); |
||||
|
||||
if (ret < 0) |
||||
elog(ERROR, "_rserv_sync_: SPI_exec returned %d", ret); |
||||
|
||||
SPI_finish(); |
||||
|
||||
return (0); |
||||
} |
||||
|
||||
#ifdef PG_FUNCTION_INFO_V1 |
||||
Datum |
||||
_rserv_debug_(PG_FUNCTION_ARGS) |
||||
#else |
||||
int32 |
||||
_rserv_debug_(int32 newval) |
||||
#endif |
||||
{ |
||||
#ifdef PG_FUNCTION_INFO_V1 |
||||
int32 newval = PG_GETARG_INT32(0); |
||||
#endif |
||||
int32 oldval = debug; |
||||
|
||||
debug = newval; |
||||
|
||||
return (oldval); |
||||
} |
||||
|
||||
#define ExtendBy 1024 |
||||
|
||||
static char* |
||||
OutputValue(char *key, char *buf, int size) |
||||
{ |
||||
int i = 0; |
||||
char *out = buf; |
||||
char *subst = NULL; |
||||
int slen = 0; |
||||
|
||||
size--; |
||||
for ( ; ; ) |
||||
{ |
||||
switch (*key) |
||||
{ |
||||
case '\\': subst ="\\\\"; |
||||
slen = 2; |
||||
break; |
||||
case ' ': subst = "\\011"; |
||||
slen = 4; |
||||
break; |
||||
case '\n': subst = "\\012"; |
||||
slen = 4; |
||||
break; |
||||
case '\'': subst = "\\047"; |
||||
slen = 4; |
||||
break; |
||||
case '\0': out[i] = 0; |
||||
return(out); |
||||
default: slen = 1; |
||||
break; |
||||
} |
||||
|
||||
if (i + slen >= size) |
||||
{ |
||||
if (out == buf) |
||||
{ |
||||
out = (char*) palloc(size + ExtendBy); |
||||
strncpy(out, buf, i); |
||||
size += ExtendBy; |
||||
} |
||||
else |
||||
{ |
||||
out = (char*) repalloc(out, size + ExtendBy); |
||||
size += ExtendBy; |
||||
} |
||||
} |
||||
|
||||
if (slen == 1) |
||||
out[i++] = *key; |
||||
else |
||||
{ |
||||
memcpy(out + i, subst, slen); |
||||
i += slen; |
||||
} |
||||
key++; |
||||
} |
||||
|
||||
return(out); |
||||
|
||||
} |
@ -0,0 +1,22 @@ |
||||
-- erServer |
||||
-- Slave server setup for erServer demonstration implementation |
||||
-- (c) 2000 Vadim Mikheev, PostgreSQL Inc. |
||||
-- |
||||
|
||||
drop table _RSERV_SLAVE_TABLES_; |
||||
|
||||
create table _RSERV_SLAVE_TABLES_ |
||||
( |
||||
tname name, -- table name |
||||
cname name, -- column name |
||||
reloid oid, -- table oid |
||||
key int4 -- key attnum |
||||
); |
||||
|
||||
drop table _RSERV_SLAVE_SYNC_; |
||||
|
||||
create table _RSERV_SLAVE_SYNC_ |
||||
( |
||||
syncid int4, |
||||
synctime timestamp |
||||
); |
Loading…
Reference in new issue