2
#############################################################################
5
# Contains the Database mirroring script.
6
# This script queries the pending table off the database specified
7
# (along with the associated schema) for updates that are pending on a
8
# specific host. The database on that host is then updated with the changes.
11
# Written by Steven Singer (ssinger@navtechinc.com)
12
# (c) 2001-2002 Navtech Systems Support Inc.
13
# ALL RIGHTS RESERVED;
15
# Permission to use, copy, modify, and distribute this software and its
16
# documentation for any purpose, without fee, and without a written agreement
17
# is hereby granted, provided that the above copyright notice and this
18
# paragraph and the following two paragraphs appear in all copies.
20
# IN NO EVENT SHALL THE AUTHOR OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR
21
# DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
22
# LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
23
# DOCUMENTATION, EVEN IF THE AUTHOR OR DISTRIBUTORS HAVE BEEN ADVISED OF THE
24
# POSSIBILITY OF SUCH DAMAGE.
26
# THE AUTHOR AND DISTRIBUTORS SPECIFICALLY DISCLAIMS ANY WARRANTIES,
27
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
28
# AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
29
# ON AN "AS IS" BASIS, AND THE AUTHOR AND DISTRIBUTORS HAS NO OBLIGATIONS TO
30
# PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
35
##############################################################################
36
# $PostgreSQL: pgsql/contrib/dbmirror/DBMirror.pl,v 1.10 2004-07-02 00:58:09 joe Exp $
38
##############################################################################
42
DBMirror.pl - A Perl module to mirror database changes from a master database
48
DBMirror.pl slaveConfigfile.conf
53
This Perl script will connect to the master database and query its pending
54
table for a list of pending changes.
56
The transactions of the original changes to the master will be preserved
57
when sending things to the slave.
70
# add in a global path to files
71
# Pg should be included.
78
sub mirrorCommand($$$$$$);
79
sub mirrorInsert($$$$$);
80
sub mirrorDelete($$$$$);
81
sub mirrorUpdate($$$$$);
82
sub logErrorMessage($);
84
sub updateMirrorHostTable($$);
89
local $::masterPassword;
90
local $::errorThreshold=5;
91
local $::errorEmailAddr=undef;
92
local $::sleepInterval=60;
95
local $::slaveInfo = \%slaveInfoHash;
98
my $repeatErrorCount=0;
109
#run the configuration file.
111
die "usage: DBMirror.pl configFile\n";
113
if( ! defined do $ARGV[0]) {
114
logErrorMessage("Invalid Configuration file $ARGV[0]");
118
if (defined($::syslog))
122
import Sys::Syslog qw(openlog syslog);
123
openlog($0, 'cons,pid', 'user');
124
syslog("info", '%s', "starting $0 script with $ARGV[0]");
128
if(defined($::masterHost))
130
$connectString .= "host=$::masterHost ";
132
if(defined($::masterPort))
134
$connectString .= "port=$::masterPort ";
136
$connectString .= "dbname=$::masterDb user=$::masterUser password=$::masterPassword";
138
$masterConn = Pg::connectdb($connectString);
140
unless($masterConn->status == PGRES_CONNECTION_OK) {
141
logErrorMessage("Can't connect to master database\n" .
142
$masterConn->errorMessage);
147
$setQuery = "SET search_path = public";
148
my $setResult = $masterConn->exec($setQuery);
149
if($setResult->resultStatus!=PGRES_COMMAND_OK) {
150
logErrorMessage($masterConn->errorMessage . "\n" .
157
if($firstTime == 0) {
158
sleep $::sleepInterval;
162
setupSlave($::slaveInfo);
167
#Obtain a list of pending transactions using ordering by our approximation
168
#to the commit time. The commit time approximation is taken to be the
169
#SeqId of the last row edit in the transaction.
170
my $pendingTransQuery = "SELECT pd.XID,MAX(SeqId) FROM dbmirror_Pending pd";
171
$pendingTransQuery .= " LEFT JOIN dbmirror_MirroredTransaction mt INNER JOIN";
172
$pendingTransQuery .= " dbmirror_MirrorHost mh ON mt.MirrorHostId = ";
173
$pendingTransQuery .= " mh.MirrorHostId AND mh.SlaveName=";
174
$pendingTransQuery .= " '$::slaveInfo->{\"slaveName\"}' ";
175
$pendingTransQuery .= " ON pd.XID";
176
$pendingTransQuery .= " = mt.XID WHERE mt.XID is null ";
179
$pendingTransQuery .= " GROUP BY pd.XID";
180
$pendingTransQuery .= " ORDER BY MAX(pd.SeqId)";
183
my $pendingTransResults = $masterConn->exec($pendingTransQuery);
184
unless($pendingTransResults->resultStatus==PGRES_TUPLES_OK) {
185
logErrorMessage("Can't query pending table\n" . $masterConn->errorMessage);
189
my $numPendingTrans = $pendingTransResults->ntuples;
190
my $curTransTuple = 0;
194
# This loop loops through each pending transaction in the proper order.
195
# The Pending row edits for that transaction will be queried from the
196
# master and sent + committed to the slaves.
197
while($curTransTuple < $numPendingTrans) {
198
my $XID = $pendingTransResults->getvalue($curTransTuple,0);
199
my $maxSeqId = $pendingTransResults->getvalue($curTransTuple,1);
203
if($::slaveInfo->{'status'} eq 'FileClosed')
205
openTransactionFile($::slaveInfo,$XID);
210
my $pendingQuery = "SELECT pnd.SeqId,pnd.TableName,";
211
$pendingQuery .= " pnd.Op,pnddata.IsKey, pnddata.Data AS Data ";
212
$pendingQuery .= " FROM dbmirror_Pending pnd, dbmirror_PendingData pnddata ";
213
$pendingQuery .= " WHERE pnd.SeqId = pnddata.SeqId ";
215
$pendingQuery .= " AND pnd.XID=$XID ORDER BY SeqId, IsKey DESC";
218
my $pendingResults = $masterConn->exec($pendingQuery);
219
unless($pendingResults->resultStatus==PGRES_TUPLES_OK) {
220
logErrorMessage("Can't query pending table\n" . $masterConn->errorMessage);
224
sendQueryToSlaves($XID,"BEGIN");
226
my $numPending = $pendingResults->ntuples;
228
while ($curTuple < $numPending) {
229
$seqId = $pendingResults->getvalue($curTuple,0);
230
my $tableName = $pendingResults->getvalue($curTuple,1);
231
my $op = $pendingResults->getvalue($curTuple,2);
232
$curTuple = mirrorCommand($seqId,$tableName,$op,$XID,
233
$pendingResults,$curTuple) +1;
237
if($::slaveInfo->{'status'} ne 'DBOpen' &&
238
$::slaveInfo->{'status'} ne 'FileOpen')
242
sendQueryToSlaves(undef,"COMMIT");
243
#Now commit the transaction.
244
updateMirrorHostTable($XID,$seqId);
246
$pendingResults = undef;
247
$curTransTuple = $curTransTuple +1;
249
if($::slaveInfo->{'status'} eq 'FileOpen')
251
close ($::slaveInfo->{'TransactionFile'});
252
$::slaveInfo->{"status"} = 'FileClosed';
255
elsif($::slaveInfo->{'status'} eq 'DBOpen')
257
if($commandCount > 5000) {
259
$::slaveInfo->{"status"} = 'DBClosed';
260
$::slaveInfo->{"slaveConn"}->reset;
261
#Open the connection right away.
262
openSlaveConnection($::slaveInfo);
267
}#while transactions left.
269
$pendingTransResults = undef;
276
=item mirrorCommand(SeqId,tableName,op,transId,pendingResults,curTuple)
278
Mirrors a single SQL Command(change to a single row) to the slave.
284
The id number of the change to mirror. This is the
285
primary key of the pending table.
290
The name of the table the transaction takes place on.
294
The type of operation this transaction is. 'i' for insert, 'u' for update or
299
The Transaction of of the Transaction that this command is part of.
301
=item * pendingResults
303
A Results set structure returned from Pg::execute that contains the
304
join of the Pending and PendingData tables for all of the pending row
305
edits in this transaction.
310
The tuple(or row) number of the pendingRow for the command that is about
311
to be edited. If the command is an update then this points to the row
312
with IsKey equal to true. The next row, curTuple+1 is the contains the
313
PendingData with IsKey false for the update.
319
The tuple number of last tuple for this command. This might be equal to
320
currentTuple or it might be larger (+1 in the case of an Update).
328
sub mirrorCommand($$$$$$) {
330
my $tableName = $_[1];
333
my $pendingResults = $_[4];
334
my $currentTuple = $_[5];
338
$currentTuple = mirrorInsert($seqId,$tableName,$transId,$pendingResults
342
$currentTuple = mirrorDelete($seqId,$tableName,$transId,$pendingResults,
346
$currentTuple = mirrorUpdate($seqId,$tableName,$transId,$pendingResults,
350
$currentTuple = mirrorSequence($seqId,$tableName,$transId,$pendingResults,
353
$commandCount = $commandCount +1;
354
if($commandCount % 100 == 0) {
355
# print "Sent 100 commmands on SeqId $seqId \n";
362
=item mirrorInsert(transId,tableName,transId,pendingResults,currentTuple)
364
Mirrors an INSERT operation to the slave database. A new row is placed
365
in the slave database containing the primary key from pendingKeys along with
366
the data fields contained in the row identified by sourceOid.
372
The sequence id of the INSERT operation being mirrored. This is the primary
373
key of the pending table.
378
The name of the table the transaction takes place on.
382
The OID of the row in the master database for which this transaction effects.
383
If the transaction is a delete then the operation is not valid.
387
The Transaction Id of transaction that this insert is part of.
391
=item * pendingResults
393
A Results set structure returned from Pg::execute that contains the
394
join of the Pending and PendingData tables for all of the pending row
395
edits in this transaction.
400
The tuple(or row) number of the pendingRow for the command that is about
401
to be edited. In the case of an insert this should point to the one
402
row for the row edit.
406
The tuple number of the last tuple for the row edit. This should be
415
sub mirrorInsert($$$$$) {
417
my $tableName = $_[1];
419
my $pendingResults = $_[3];
420
my $currentTuple = $_[4];
424
my $firstIteration=1;
425
my %recordValues = extractData($pendingResults,$currentTuple);
428
#Now build the insert query.
429
my $insertQuery = "INSERT INTO $tableName (";
430
my $valuesQuery = ") VALUES (";
431
foreach $column (keys (%recordValues)) {
432
if($firstIteration==0) {
433
$insertQuery .= " ,";
434
$valuesQuery .= " ,";
436
$insertQuery .= "\"$column\"";
437
if(defined $recordValues{$column}) {
438
my $quotedValue = $recordValues{$column};
439
$quotedValue =~ s/\\/\\\\/g;
440
$quotedValue =~ s/'/\\'/g;
441
$valuesQuery .= "'$quotedValue'";
444
$valuesQuery .= "null";
449
sendQueryToSlaves($transId,$insertQuery . $valuesQuery);
450
return $currentTuple;
453
=item mirrorDelete(SeqId,tableName,transId,pendingResult,currentTuple)
455
Deletes a single row from the slave database. The row is identified by the
456
primary key for the transaction in the pendingKeys table.
462
The Sequence id for this delete request.
466
The name of the table to delete the row from.
470
The Transaction Id of the transaction that this command is part of.
474
=item * pendingResults
476
A Results set structure returned from Pg::execute that contains the
477
join of the Pending and PendingData tables for all of the pending row
478
edits in this transaction.
483
The tuple(or row) number of the pendingRow for the command that is about
484
to be edited. In the case of a delete this should point to the one
485
row for the row edit.
489
The tuple number of the last tuple for the row edit. This should be
498
sub mirrorDelete($$$$$) {
500
my $tableName = $_[1];
502
my $pendingResult = $_[3];
503
my $currentTuple = $_[4];
507
%dataHash = extractData($pendingResult,$currentTuple);
510
my $deleteQuery = "DELETE FROM $tableName WHERE ";
511
foreach $currentField (keys %dataHash) {
513
$deleteQuery .= " AND ";
515
my $currentValue = $dataHash{$currentField};
516
$deleteQuery .= "\"";
517
$deleteQuery .= $currentField;
518
if(defined $currentValue) {
519
$deleteQuery .= "\"='";
520
$deleteQuery .= $currentValue;
524
$deleteQuery .= " is null ";
529
sendQueryToSlaves($transId,$deleteQuery);
530
return $currentTuple;
534
=item mirrorUpdate(seqId,tableName,transId,pendingResult,currentTuple)
536
Mirrors over an edit request to a single row of the database.
537
The primary key from before the edit is used to determine which row in the
538
slave should be changed.
540
After the edit takes place on the slave its primary key will match the primary
541
key the master had immediatly following the edit. All other fields will be set
542
to the current values.
544
Data integrity is maintained because the mirroring is performed in an
545
SQL transcation so either all pending changes are made or none are.
551
The Sequence id of the update.
555
The name of the table to perform the update on.
559
The transaction Id for the transaction that this command is part of.
562
=item * pendingResults
564
A Results set structure returned from Pg::execute that contains the
565
join of the Pending and PendingData tables for all of the pending row
566
edits in this transaction.
571
The tuple(or row) number of the pendingRow for the command that is about
572
to be edited. In the case of a delete this should point to the one
573
row for the row edit.
577
The tuple number of the last tuple for the row edit. This should be
578
currentTuple +1. Which points to the non key row of the update.
585
sub mirrorUpdate($$$$$) {
587
my $tableName = $_[1];
589
my $pendingResult = $_[3];
590
my $currentTuple = $_[4];
594
my $updateQuery = "UPDATE $tableName SET ";
599
my $firstIteration=1;
601
#Extract the Key values. This row contains the values of the
602
# key fields before the update occours(the WHERE clause)
603
%keyValueHash = extractData($pendingResult,$currentTuple);
606
#Extract the data values. This is a SET clause that contains
607
#values for the entire row AFTER the update.
608
%dataValueHash = extractData($pendingResult,$currentTuple+1);
611
foreach $currentField (keys (%dataValueHash)) {
612
if($firstIteration==0) {
613
$updateQuery .= ", ";
615
$updateQuery .= " \"$currentField\"=";
616
my $currentValue = $dataValueHash{$currentField};
617
if(defined $currentValue ) {
618
$quotedValue = $currentValue;
619
$quotedValue =~ s/\\/\\\\/g;
620
$quotedValue =~ s/'/\\'/g;
621
$updateQuery .= "'$quotedValue'";
624
$updateQuery .= "null ";
630
$updateQuery .= " WHERE ";
632
foreach $currentField (keys (%keyValueHash)) {
634
if($firstIteration==0) {
635
$updateQuery .= " AND ";
637
$updateQuery .= "\"$currentField\"=";
638
$currentValue = $keyValueHash{$currentField};
639
if(defined $currentValue) {
640
$quotedValue = $currentValue;
641
$quotedValue =~ s/\\/\\\\/g;
642
$quotedValue =~ s/'/\\'/g;
643
$updateQuery .= "'$quotedValue'";
646
$updateQuery .= " null ";
650
sendQueryToSlaves($transId,$updateQuery);
651
return $currentTuple+1;
655
sub mirrorSequence($$$$$) {
657
my $sequenceName = $_[1];
659
my $pendingResult = $_[3];
660
my $currentTuple = $_[4];
664
my $sequenceValue = $pendingResult->getvalue($currentTuple,4);
665
$query = sprintf("select setval('%s',%s)",$sequenceName,$sequenceValue);
667
sendQueryToSlaves($transId,$query);
668
return $currentTuple;
672
=item sendQueryToSlaves(seqId,sqlQuery)
674
Sends an SQL query to the slave.
681
The sequence Id of the command being sent. Undef if no command is associated
682
with the query being sent.
687
SQL operation to perform on the slave.
693
sub sendQueryToSlaves($$) {
695
my $sqlQuery = $_[1];
697
if($::slaveInfo->{"status"} eq 'DBOpen') {
698
my $queryResult = $::slaveInfo->{"slaveConn"}->exec($sqlQuery);
699
unless($queryResult->resultStatus == PGRES_COMMAND_OK) {
701
$errorMessage = "Error sending query $seqId to " ;
702
$errorMessage .= $::slaveInfo->{"slaveHost"};
703
$errorMessage .=$::slaveInfo->{"slaveConn"}->errorMessage;
704
$errorMessage .= "\n" . $sqlQuery;
705
logErrorMessage($errorMessage);
706
$::slaveInfo->{"slaveConn"}->exec("ROLLBACK");
707
$::slaveInfo->{"status"} = -1;
710
elsif($::slaveInfo->{"status"} eq 'FileOpen' ) {
711
my $xfile = $::slaveInfo->{'TransactionFile'};
712
print $xfile $sqlQuery . ";\n";
722
=item logErrorMessage(error)
724
Mails an error message to the users specified $errorEmailAddr
725
The error message is also printed to STDERR.
731
The error message to log.
737
sub logErrorMessage($) {
740
if(defined $lastErrorMsg and $error eq $lastErrorMsg) {
741
if($repeatErrorCount<$::errorThreshold) {
749
if(defined $::errorEmailAddr) {
751
open (mailPipe, "|/bin/mail -s DBMirror.pl $::errorEmailAddr");
752
print mailPipe "=====================================================\n";
753
print mailPipe " DBMirror.pl \n";
755
print mailPipe " The DBMirror.pl script has encountred an error. \n";
756
print mailPipe " It might indicate that either the master database has\n";
757
print mailPipe " gone down or that the connection to a slave database can\n";
758
print mailPipe " not be made. \n";
759
print mailPipe " Process-Id: $$ on $::masterHost database $::masterDb\n";
761
print mailPipe $error;
762
print mailPipe "\n\n\n=================================================\n";
766
if (defined($::syslog))
768
syslog('err', '%s (%m)', $error);
773
$lastErrorMsg = $error;
778
my $slavePtr = $_[0];
781
$slavePtr->{"status"} = 0;
782
#Determine the MirrorHostId for the slave from the master's database
783
my $resultSet = $masterConn->exec('SELECT MirrorHostId FROM '
784
. ' dbmirror_MirrorHost WHERE SlaveName'
785
. '=\'' . $slavePtr->{"slaveName"}
787
if($resultSet->ntuples !=1) {
788
my $errorMessage .= $slavePtr->{"slaveName"} ."\n";
789
$errorMessage .= "Has no MirrorHost entry on master\n";
790
logErrorMessage($errorMessage);
791
$slavePtr->{"status"}=-1;
795
$slavePtr->{"MirrorHostId"} = $resultSet->getvalue(0,0);
797
if(defined($::slaveInfo->{'slaveDb'})) {
798
# We talk directly to a slave database.
800
if($::slaveInfo->{"status"} ne 'DBOpen')
802
openSlaveConnection($::slaveInfo);
804
sendQueryToSlaves(undef,"SET TRANSACTION ISOLATION LEVEL SERIALIZABLE");
805
sendQueryToSlaves(undef,"SET CONSTRAINTS ALL DEFERRED");
808
$::slaveInfo->{"status"} = 'FileClosed';
814
=item updateMirrorHostTable(lastTransId,lastSeqId)
816
Updates the MirroredTransaction table to reflect the fact that
817
this transaction has been sent to the current slave.
823
The Transaction id for the last transaction that has been succesfully mirrored to
824
the currently open slaves.
828
The Sequence Id of the last command that has been succefully mirrored
836
sub updateMirrorHostTable($$) {
837
my $lastTransId = shift;
838
my $lastSeqId = shift;
842
my $deleteTransactionQuery;
844
my $updateMasterQuery = "INSERT INTO dbmirror_MirroredTransaction ";
845
$updateMasterQuery .= " (XID,LastSeqId,MirrorHostId)";
846
$updateMasterQuery .= " VALUES ($lastTransId,$lastSeqId,$::slaveInfo->{\"MirrorHostId\"}) ";
848
my $updateResult = $masterConn->exec($updateMasterQuery);
849
unless($updateResult->resultStatus == PGRES_COMMAND_OK) {
850
my $errorMessage = $masterConn->errorMessage . "\n";
851
$errorMessage .= $updateMasterQuery;
852
logErrorMessage($errorMessage);
855
# print "Updated slaves to transaction $lastTransId\n" ;
858
#If this transaction has now been mirrored to all mirror hosts
859
#then it can be deleted.
860
$deleteTransactionQuery = 'DELETE FROM dbmirror_Pending WHERE XID='
861
. $lastTransId . ' AND (SELECT COUNT(*) FROM dbmirror_MirroredTransaction'
862
. ' WHERE XID=' . $lastTransId . ')=(SELECT COUNT(*) FROM'
863
. ' dbmirror_MirrorHost)';
865
$deleteResult = $masterConn->exec($deleteTransactionQuery);
866
if($deleteResult->resultStatus!=PGRES_COMMAND_OK) {
867
logErrorMessage($masterConn->errorMessage . "\n" .
868
$deleteTransactionQuery);
877
sub extractData($$) {
878
my $pendingResult = $_[0];
879
my $currentTuple = $_[1];
883
my $dataField = $pendingResult->getvalue($currentTuple,$fnumber);
885
while(length($dataField)>0) {
886
# Extract the field name that is surronded by double quotes
887
$dataField =~ m/(\".*?\")/s;
889
$dataField = substr $dataField ,length($fieldName);
890
$fieldName =~ s/\"//g; #Remove the surronding " signs.
892
if($dataField =~ m/(^= )/s) {
894
$dataField = substr $dataField , length($1);
895
$valuesHash{$fieldName}=undef;
897
elsif ($dataField =~ m/(^=\')/s) {
900
$dataField = substr $dataField ,2; #Skip the ='
901
LOOP: { #This is to allow us to use last from a do loop.
902
#Recommended in perlsyn manpage.
905
#Find the substring ending with the first ' or first \
906
$dataField =~ m/(.*?[\'\\])?/s;
908
$value .= substr $matchString,0,length($matchString)-1;
910
if($matchString =~ m/(\'$)/s) {
911
# $1 runs to the end of the field value.
912
$dataField = substr $dataField,length($matchString)+1;
917
#deal with the escape character.
918
#It The character following the escape gets appended.
919
$dataField = substr $dataField,length($matchString);
920
$dataField =~ s/(^.)//s;
928
} until(length($dataField)==0);
930
$valuesHash{$fieldName} = $value;
936
logErrorMessage "Error in PendingData Sequence Id " .
937
$pendingResult->getvalue($currentTuple,0);
949
sub openTransactionFile($$)
951
my $slaveInfo = shift;
953
# my $now_str = localtime;
963
($nowsec,$nowmin,$nowhour,$nowmday,$nowmon,$nowyear,$nowwday,$nowyday,$nowisdst) =
965
my $fileName=sprintf(">%s/%s_%02d-%02d-%02d_%02d:%02d:%dXID%d.sql", $::slaveInfo->{'TransactionFileDirectory'},
966
$::slaveInfo->{"MirrorHostId"},($nowyear+1900),($nowmon+1),$nowmday,$nowhour,$nowmin,
970
open($xfile,$fileName) or die "Can't open $fileName : $!";
972
$slaveInfo->{'TransactionFile'} = $xfile;
973
$slaveInfo->{'status'} = 'FileOpen';
978
sub openSlaveConnection($) {
979
my $slavePtr = $_[0];
984
if(defined($slavePtr->{"slaveHost"}))
986
$slaveConnString .= "host=" . $slavePtr->{"slaveHost"} . " ";
988
if(defined($slavePtr->{"slavePort"}))
990
$slaveConnString .= "port=" . $slavePtr->{"slavePort"} . " ";
993
$slaveConnString .= " dbname=" . $slavePtr->{"slaveDb"};
994
$slaveConnString .= " user=" . $slavePtr->{"slaveUser"};
995
$slaveConnString .= " password=" . $slavePtr->{"slavePassword"};
997
$slaveConn = Pg::connectdb($slaveConnString);
999
if($slaveConn->status != PGRES_CONNECTION_OK) {
1000
my $errorMessage = "Can't connect to slave database " ;
1001
$errorMessage .= $slavePtr->{"slaveHost"} . "\n";
1002
$errorMessage .= $slaveConn->errorMessage;
1003
logErrorMessage($errorMessage);
1004
$slavePtr->{"status"} = 'DBFailed';
1007
$slavePtr->{"slaveConn"} = $slaveConn;
1008
$slavePtr->{"status"} = 'DBOpen';