<?xml version='1.0' encoding='UTF-8'?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/'><id>tag:blogger.com,1999:blog-5538806304499049851</id><updated>2008-06-30T14:01:00.360+01:00</updated><title type='text'>Progressions</title><link rel='alternate' type='text/html' href='http://www.wss.com/blog/blog.html'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5538806304499049851/posts/default'/><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://www.wss.com/blog/atom.xml'/><author><name>Progressions Staff</name><uri>http://www.blogger.com/profile/17463781557118102673</uri><email>noreply@blogger.com</email></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>3</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-5538806304499049851.post-5242879742234117652</id><published>2008-06-30T11:06:00.008+01:00</published><updated>2008-06-30T14:01:00.397+01:00</updated><title type='text'>Log-based replication</title><content type='html'>&lt;p&gt;&lt;span style="font-family:Courier New;font-size:78%;"&gt;&lt;/span&gt;&lt;span style="font-family:Courier New;font-size:78%;"&gt;&lt;/span&gt;&lt;span style="font-family:Courier New;font-size:78%;"&gt;&lt;/span&gt;&lt;br /&gt;Got OpenEdge Replication? Good for you!!&lt;/p&gt;&lt;p&gt;No??? But you have implemented After Imaging haven't you? &lt;/p&gt;&lt;p&gt;Seriously, you have, haven't you? If your answer is "no" then the next question has to be "Why do you care so little about your data?"&lt;/p&gt;&lt;p&gt;Implementing After Imaging is a snap. Especially if you're running OpenEdge 10.1x and later. &lt;/p&gt;&lt;p&gt;After the implementation, you can copy the images to a secondary (and even tertiary) machine and roll them forward there. All you need is a plan and a set of scripts and that's where this posting is headed.&lt;/p&gt;&lt;p&gt;The purpose of this article is to identify the initial setup of log-based database replication and to document the procedures for recovery from computer outages. You'll also find a link to all of the scripts needed.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Please (and I know everyone asks this and nobody ever does it) read the whole PDF document. Better spend the time now than later when your hair is on fire (I hope not literally) and the IT director is breathing down your neck.&lt;br /&gt;&lt;br /&gt;These programs have been implemented in High Availability/Mission Critical environments so we know they work. But your environment may be different so, you need to use caution when implementing these scripts. Please read this document completely before starting to implement these scripts.&lt;br /&gt;&lt;br /&gt;What we are saying is that these scripts are offered as is with no warranty or liability. If they cause a problem or destroy your database we are not responsible. Now that our lawyers are happy, let's get to it.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;The PDF document details all of the steps you need to implement AI in either a V9 or OpenEdge environment and explains the steps needed to implement recovery from a variety of failures.&lt;/p&gt;&lt;p&gt;There are five scripts involved; db.registry, read_registry, replicate_db, sub_replicate_db and roll_forward_ai.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;db.registry&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family:courier new;font-size:78%;"&gt;#Syntax:&lt;br /&gt;#DBHOSTSceDirBkpAIDirRMTNODERmtAIDirTgtDirRmtBkupAPWs&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:78%;"&gt;&lt;strong&gt;DB&lt;/strong&gt; The name of the database (no extensions (.db))&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;HOST&lt;/strong&gt; The name or IP address of the Primary machine&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;SceDir&lt;/strong&gt; The directory containing the Source Database&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;BkpAIDir&lt;/strong&gt; The directory on the Primary machine where the archived AI files will live.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;RMTNODE&lt;/strong&gt; The name or IP address of the Secondary machine&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;RmtAIDir&lt;/strong&gt; The directory on the Secondary machine where the AI files will be transferred to before they are rolled forward.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;TgtDir&lt;/strong&gt; The directory on the Secondary machine containing the Target database.&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;RmtBkup&lt;/strong&gt; A directory on the Secondary machine which can contain either backups transferred from Primary or backups of Target. (The initial implementation of scripts does not utilise this).&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;APWs&lt;/strong&gt; The number of APW processes to start. (The initial implementation of scripts does not utilise this).&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;read_registry&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family:courier new;font-size:78%;"&gt;#!/bin/sh&lt;br /&gt;# Program: read_registry&lt;br /&gt;# Purpose: This script sets system and database environment&lt;br /&gt;# variables for use by other scripts. A file (db.registry)&lt;br /&gt;# is read from to determine if the database is valid and&lt;br /&gt;# "registered" on the system&lt;br /&gt;# Author: WSS&lt;br /&gt;# Date: February 2008&lt;br /&gt;&lt;br /&gt;if [ x$1 = "x" ]&lt;br /&gt;then&lt;br /&gt;echo "You must enter a database name"&lt;br /&gt;exit 1&lt;br /&gt;fi&lt;br /&gt;&lt;br /&gt;# Setup generic system variables&lt;br /&gt;DLC=${DLC-/u/progress/9.1e}&lt;br /&gt;PROMSGS=$DLC/promsgs&lt;br /&gt;PROTERMCAP=$DLC/protermcap&lt;br /&gt;PROCFG=$DLC/progress.cfg&lt;br /&gt;PROPATH=$PATH:$DLC:$SCRIPTS/live&lt;br /&gt;PATH=$PATH:$DLC/bin&lt;br /&gt;RESP=$SCRIPTS/live/response&lt;br /&gt;WHOAMI=`whoami`&lt;br /&gt;HOSTNAME=`uname -n cut -f 1 -d "."`&lt;br /&gt;&lt;br /&gt;# Setup database specific variables&lt;br /&gt;DB=`grep $1 $SCRIPTS/live/db.registry cut -f 1 -d ""`&lt;br /&gt;HOST=`grep $1 $SCRIPTS/live/db.registry cut -f 2 -d ""`&lt;br /&gt;DB_DIR=`grep $1 $SCRIPTS/live/db.registry cut -f 3 -d ""`&lt;br /&gt;BKAIDIR=`grep $1 $SCRIPTS/live/db.registry cut -f 4 -d ""`&lt;br /&gt;RMTNODE=`grep $1 $SCRIPTS/live/db.registry cut -f 5 -d ""`&lt;br /&gt;RMTAIDIR=`grep $1 $SCRIPTS/live/db.registry cut -f 6 -d ""`&lt;br /&gt;RMTDBDIR=`grep $1 $SCRIPTS/live/db.registry cut -f 7 -d ""`&lt;br /&gt;&lt;br /&gt;if [ x$HOST = "x" ] # This name is not in the registry&lt;br /&gt;then&lt;br /&gt;unset DB HOST DB_DIR BKAIDIR RMTNODE RMTAIDIR RMTDBDIR&lt;br /&gt;return 2&lt;br /&gt;fi&lt;br /&gt;&lt;br /&gt;if [ $HOSTNAME != $HOST ] # This database is on a different host&lt;br /&gt;then&lt;br /&gt;export HOST&lt;br /&gt;unset DB DB_DIR BKAIDIR RMTNODE RMTAIDIR RMTDBDIR&lt;br /&gt;return 3&lt;br /&gt;fi&lt;br /&gt;# Export variables to the shell&lt;br /&gt;export DLC PROMSGS PROTERMCAP PROCFG PROPATH PATH RESP&lt;br /&gt;export DB HOST WHOAMI DB_DIR BKAIDIR RMTNODE RMTAIDIR RMTDBDIR&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;replicate_db&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Usage will be "replicate_db &lt;em&gt;dbname&lt;/em&gt;" or "replicate_db all"&lt;br /&gt;&lt;/strong&gt;&lt;br /&gt;&lt;span style="font-family:courier new;font-size:78%;"&gt;#!/bin/ksh&lt;br /&gt;# Program: replicate_db&lt;br /&gt;# Purpose: This script acts as a wrapper for the sub_replicate_db&lt;br /&gt;# script. Allowing one or all databases to be replicated&lt;br /&gt;# at the same time.&lt;br /&gt;# Syntax: replicate_db &lt;alldbname&gt;&lt;br /&gt;# Author: WSS&lt;br /&gt;# Date: February 2008&lt;br /&gt;#&lt;br /&gt;SCRIPTS=${SCRIPTS-/u/scripts}&lt;br /&gt;HOSTNAME=`uname -n cut -f 1 -d "."`&lt;br /&gt;export SCRIPTS HOSTNAME&lt;br /&gt;&lt;br /&gt;case $1 in&lt;br /&gt;&lt;br /&gt;allALLAll)&lt;br /&gt;for i in `grep $HOSTNAME $SCRIPTS/live/db.registry cut -s -f 1 -d "" `&lt;br /&gt;do&lt;br /&gt;if [ $i != "#DB" ]&lt;br /&gt;then&lt;br /&gt;. $SCRIPTS/live/read_registry $i&lt;br /&gt;&lt;br /&gt;if [ $? != "0" ]&lt;br /&gt;then&lt;br /&gt;echo "" &gt;/dev/null&lt;br /&gt;else&lt;br /&gt;echo "Working on database: $i"&lt;br /&gt;$SCRIPTS/live/sub_replicate_db $i&lt;br /&gt;fi&lt;br /&gt;fi&lt;br /&gt;done&lt;br /&gt;;;&lt;br /&gt;*)&lt;br /&gt;. $SCRIPTS/live/read_registry $1&lt;br /&gt;&lt;br /&gt;case $? in&lt;br /&gt;2)&lt;br /&gt;echo "Database $1 is not in database registry"&lt;br /&gt;exit 1&lt;br /&gt;;;&lt;br /&gt;3)&lt;br /&gt;echo "Database $1 is on $HOST not on this host"&lt;br /&gt;exit 1&lt;br /&gt;;;&lt;br /&gt;esac&lt;br /&gt;echo "Working on database: $1"&lt;br /&gt;$SCRIPTS/live/sub_replicate_db $1&lt;br /&gt;;;&lt;br /&gt;esac&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/span&gt;&lt;/span&gt;&lt;strong&gt;sub_replicate_db&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family:courier new;font-size:78%;"&gt;#!/bin/sh&lt;br /&gt;# Program: sub_replicate_db&lt;br /&gt;# Purpose: This script is called by replicate_db to do the actual&lt;br /&gt;# process of switching ai files (if possible), archiving&lt;br /&gt;# the file(s) locally, moving them over to the remote&lt;br /&gt;# machine and applying them to the replication DB and&lt;br /&gt;# lastly archinving the file(s).&lt;br /&gt;# Syntax: sub_replicate_db &lt;dbname&gt;&lt;br /&gt;#&lt;br /&gt;# Author: WSS&lt;br /&gt;# Date Written: February 2008&lt;br /&gt;#&lt;br /&gt;# Mods: Added double quotes around find command being executed via rexec&lt;br /&gt;# due to the command not running properly.&lt;br /&gt;&lt;br /&gt;# Setup the generic variables&lt;br /&gt;SCRIPTS=${SCRIPTS-/u/scripts}&lt;br /&gt;LOG=$SCRIPTS/logs&lt;br /&gt;BKLOG=$SCRIPTS/logs/archive&lt;br /&gt;INPROCESS=$SCRIPTS/tmp&lt;br /&gt;DOW=`date +%a`&lt;br /&gt;WHOAMI=`whoami`&lt;br /&gt;export SCRIPTS LOG BKLOG INPROCESS DOW WHOAMI&lt;br /&gt;&lt;br /&gt;# Do Operating system dependent aliasing&lt;br /&gt;case `uname -s` in&lt;br /&gt;HP-UX)&lt;br /&gt;alias rsh=remsh&lt;br /&gt;;;&lt;br /&gt;esac&lt;br /&gt;&lt;br /&gt;# Read the registry to setup the database specific variables&lt;br /&gt;. $SCRIPTS/live/read_registry $1&lt;br /&gt;&lt;br /&gt;case $? in&lt;br /&gt;2)&lt;br /&gt;echo "Database $1 is not in database registry"&lt;br /&gt;exit 1&lt;br /&gt;;;&lt;br /&gt;3)&lt;br /&gt;echo "Database $1 is on $HOST not on this host"&lt;br /&gt;exit 1&lt;br /&gt;;;&lt;br /&gt;esac&lt;br /&gt;&lt;br /&gt;if [ $BKAIDIR = "NO_AI" ]&lt;br /&gt;then&lt;br /&gt;exit 0&lt;br /&gt;fi&lt;br /&gt;&lt;br /&gt;LOG=$LOG/replicate$DB&lt;br /&gt;INPROCESS=$INPROCESS/replicate$DB.inprocess&lt;br /&gt;&lt;br /&gt;# Exit subroutine&lt;br /&gt;# This subroutine takes three parameters.&lt;br /&gt;# "Error code" - This gives the status the program will exit with&lt;br /&gt;# 0 - OK, not 0 - error&lt;br /&gt;# "Message" - The message you want in the log file&lt;br /&gt;# "Keep_file" - This parameter is optional. It allows you to exit&lt;br /&gt;# but leave the replicate.inprocess file intact&lt;br /&gt;exit_code()&lt;br /&gt;{&lt;br /&gt;echo &gt;&gt; $LOG&lt;br /&gt;&lt;br /&gt;if [ $1 != 0 ] # If it is a bad exit code display "ERROR" header&lt;br /&gt;then&lt;br /&gt;echo "###### ERROR ###### ERROR ###### ERROR ######" &gt;&gt; $LOG&lt;br /&gt;echo &gt;&gt; $LOG&lt;br /&gt;fi&lt;br /&gt;&lt;br /&gt;echo $2 &gt;&gt; $LOG # Regardless of the exit code display a message&lt;br /&gt;&lt;br /&gt;if [ $1 != 0 ] # If it is a bad exit code display "ERROR" footer&lt;br /&gt;then&lt;br /&gt;echo &gt;&gt; $LOG&lt;br /&gt;echo "###### ERROR ###### ERROR ###### ERROR ######" &gt;&gt; $LOG&lt;br /&gt;fi&lt;br /&gt;echo &gt;&gt; $LOG&lt;br /&gt;&lt;br /&gt;## Error mailing code&lt;br /&gt;# if [ $1 != 0 ]&lt;br /&gt;# then&lt;br /&gt;# mailx -s "Error in replication for $DB" $WHOAMI &lt; $LOG # else # mailx -s "replication for $DB Successful" $WHOAMI &lt; $LOG # fi if [ x$3 = "x" ] then rm $INPROCESS fi exit $1 } # Logging subroutine # One parameter - The message you want in the log file log_code() { echo &gt;&gt; $LOG&lt;br /&gt;echo $1 &gt;&gt; $LOG&lt;br /&gt;echo &gt;&gt; $LOG&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;# Check if another replication or AI archive process is running&lt;br /&gt;if test -s $INPROCESS # If the inprocess file exists&lt;br /&gt;then # Give a message to the screen and exit&lt;br /&gt;echo&lt;br /&gt;echo "Replication or AI archive process for "$DB" already in process"&lt;br /&gt;echo "Found $INPROCESS file - It can be removed if no previous"&lt;br /&gt;echo "AI Archive or replication for $DB is running"&lt;br /&gt;exit 1&lt;br /&gt;else # Archive the the log file and create a new one&lt;br /&gt;mv $LOG.* $BKLOG 2&gt;/dev/null&lt;br /&gt;LOG=$LOG.$DOW&lt;br /&gt;log_code "Starting replication process for "$DB&lt;br /&gt;date &gt;&gt; $LOG&lt;br /&gt;date &gt; $INPROCESS&lt;br /&gt;fi&lt;br /&gt;&lt;br /&gt;# Check to see if the backup node is available&lt;br /&gt;ping -s 1024 -c 2 $RMTNODE &gt;/dev/null&lt;br /&gt;if [ $? != 0 ]&lt;br /&gt;then&lt;br /&gt;exit_code "1" "The backup node "$RMTNODE" is not available"&lt;br /&gt;else&lt;br /&gt;log_code "Backup node is available for transfer"&lt;br /&gt;fi&lt;br /&gt;&lt;br /&gt;# If there are less than 9 full extents switch extents so that the busy&lt;br /&gt;# extent is marked as full and can be backed up&lt;br /&gt;&lt;br /&gt;FULLAIEXT=`rfutil $DB_DIR/$DB -C aimage extent list grep -i full wc -l`&lt;br /&gt;if [ $FULLAIEXT -lt 6 ]&lt;br /&gt;then&lt;br /&gt;log_code "Switching AI extents for $DB"&lt;br /&gt;_rfutil $DB_DIR/$DB -C aimage extent new&lt;br /&gt;else&lt;br /&gt;log_code "All extents full for $DB - No AI switch"&lt;br /&gt;fi&lt;br /&gt;&lt;br /&gt;if [ $? != 0 ]&lt;br /&gt;then&lt;br /&gt;exit_code "1" "Could not switch after image extents"&lt;br /&gt;else&lt;br /&gt;log_code "AI switch for $DB_DIR/$DB complete"&lt;br /&gt;fi&lt;br /&gt;&lt;br /&gt;# Copy all extents marked full to the backup directory and make&lt;br /&gt;# them as empty extents so that it can be reused&lt;br /&gt;FIRST_LOOP="yes"&lt;br /&gt;&lt;br /&gt;while true&lt;br /&gt;&lt;br /&gt;do&lt;br /&gt;&lt;br /&gt;EXTENT_NAME=`_rfutil $DB_DIR/$DB -C aimage extent full`&lt;br /&gt;&lt;br /&gt;if [ $? = 0 ]&lt;br /&gt;then # Full extent available to copy&lt;br /&gt;&lt;br /&gt;# Get the directory and file name to be copied from&lt;br /&gt;DAY_TIME=`date "+%a%H%M%S"` # Get current day and time&lt;br /&gt;&lt;br /&gt;# Copy full ai file to backup directory and then compress them&lt;br /&gt;if [ $FIRST_LOOP = "yes" ]&lt;br /&gt;then&lt;br /&gt;log_code "Removing AI file(s) older than 3 days"&lt;br /&gt;find $BKAIDIR/$DB* -mtime 3 -exec \rm {} \; 2&gt;/dev/null &gt;/dev/null&lt;br /&gt;find $BKAIDIR/$DB* -mtime 4 -exec \rm {} \; 2&gt;/dev/null &gt;/dev/null&lt;br /&gt;find $BKAIDIR/$DB* -mtime 5 -exec \rm {} \; 2&gt;/dev/null &gt;/dev/null&lt;br /&gt;fi&lt;br /&gt;&lt;br /&gt;log_code "doing copy of $EXTENT_NAME to $BKDIR/$DB.$DAY_TIME"&lt;br /&gt;cp $EXTENT_NAME $BKAIDIR/$DB.$DAY_TIME&lt;br /&gt;&lt;br /&gt;if [ $? != 0 ] # Error if copy failed&lt;br /&gt;&lt;br /&gt;then&lt;br /&gt;&lt;br /&gt;exit_code "1" "Copy of AI file to backup directory failed"&lt;br /&gt;&lt;br /&gt;fi&lt;br /&gt;&lt;br /&gt;sub_replicate_db continued&lt;br /&gt;&lt;br /&gt;# Copy the extent to the backup node&lt;br /&gt;&lt;br /&gt;if [ $FIRST_LOOP = "yes" ] # First, clean up last weeks extents&lt;br /&gt;then&lt;br /&gt;# We will keep 3 days of AI history then remove it.&lt;br /&gt;log_code "Removing AI file(s) older than 3 days from $RMTNODE:$RMTAIDIR"&lt;br /&gt;&lt;br /&gt;find $BKAIDIR/$DB* -mtime +3 -exec \rm {} \; 2&gt;&amp;amp;1 &gt;/dev/null&lt;br /&gt;&lt;br /&gt;ssh $WHOAMI@$RMTNODE "find $RMTAIDIR/$DB* -mtime +3 -exec \rm {};" 2&gt;&amp;amp;1 &gt;/dev/null&lt;br /&gt;&lt;br /&gt;FIRST_LOOP="no"&lt;br /&gt;fi&lt;br /&gt;&lt;br /&gt;# Compression REMOVED due to lack of universal compatibility&lt;br /&gt;# log_code "Compressing $BKAIDIR/$DB.$DAY_TIME in background"&lt;br /&gt;# compress $BKAIDIR/$DB.$DAY_TIME &amp;amp;&lt;br /&gt;&lt;br /&gt;log_code "Copying $EXTENT_NAME to $RMTNODE:$RMTAIDIR/$DB.$DAY_TIME"&lt;br /&gt;scp $EXTENT_NAME $WHOAMI@$RMTNODE:$RMTAIDIR/$DB.$DAY_TIME&lt;br /&gt;if [ $? != 0 ]&lt;br /&gt;then&lt;br /&gt;exit_code "1" "Remote copy of $EXTENT_NAME failed"&lt;br /&gt;else&lt;br /&gt;log_code "Remote copy of $EXTENT_NAME successful"&lt;br /&gt;fi&lt;br /&gt;&lt;br /&gt;# Roll forward AI file on the backup node&lt;br /&gt;&lt;br /&gt;ssh $WHOAMI@$RMTNODE $SCRIPTS/live/roll_forward_ai $RMTDBDIR/$DB $RMTAIDIR/$DB.$DAY_TIME&lt;br /&gt;if [ $? != 0 ]&lt;br /&gt;then&lt;br /&gt;exit_code "1" "Roll forward failed"&lt;br /&gt;else&lt;br /&gt;log_code "Roll forward of $RMTAIDIR/$DB.$DAY_TIME successful."&lt;br /&gt;fi&lt;br /&gt;&lt;br /&gt;# Compression REMOVED due to lack of universal compatibility&lt;br /&gt;# rsh $RMTNODE "compress $RMTAIDIR/$DB.$DAY_TIME &amp;amp;"&lt;br /&gt;&lt;br /&gt;# Make the extent empty for reuse&lt;br /&gt;_rfutil $DB_DIR/$DB -C aimage extent empty $EXTENT_NAME 2&gt;/dev/null 1&gt;/dev/null&lt;br /&gt;if [ $? != 0 ]&lt;br /&gt;then&lt;br /&gt;exit_code "1" "Could not empty $EXTENT_NAME"&lt;br /&gt;else&lt;br /&gt;log_code "Extent $EXTENT_NAME marked as empty"&lt;br /&gt;fi&lt;br /&gt;&lt;br /&gt;else # No more full extents&lt;br /&gt;&lt;br /&gt;date &gt;&gt; $LOG&lt;br /&gt;exit_code "0" "No full extents - procedure complete"&lt;br /&gt;break&lt;br /&gt;&lt;br /&gt;fi&lt;br /&gt;&lt;br /&gt;done # end of loop&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;roll_forward_ai&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family:courier new;font-size:78%;"&gt;#!/bin/sh&lt;br /&gt;# Program: roll_forward_ai&lt;br /&gt;# Purpose: This script rolls forward a single AI extent&lt;br /&gt;# from the backup directory&lt;br /&gt;# into the warm spare database&lt;br /&gt;#&lt;br /&gt;# Syntax: roll_forward_ai &lt;dbname&gt;&lt;ai&gt;&lt;br /&gt;#&lt;br /&gt;# Author: WSS&lt;br /&gt;# Date Written: February 2008&lt;br /&gt;#&lt;br /&gt;&lt;br /&gt;# Setup the generic variables&lt;br /&gt;SCRIPTS=${SCRIPTS-/u/scripts}&lt;br /&gt;DLC=${DLC-/u/progress/9.1e}&lt;br /&gt;PATH=$PATH:/u/progress/9.1e/bin&lt;br /&gt;export SCRIPTS DLC PATH&lt;br /&gt;&lt;br /&gt;# Roll forward AI file on the backup node&lt;br /&gt;$DLC/bin/rfutil $1 -C roll forward -a $2 -B 5000 -TB 31 -TM 32&lt;br /&gt;# 2&gt;/dev/null 1&gt;/dev/null&lt;br /&gt;if [ $? != 0 ]&lt;br /&gt;then&lt;br /&gt;echo "1"&lt;br /&gt;else&lt;br /&gt;echo "0"&lt;br /&gt;fi&lt;/span&gt;&lt;br /&gt;&lt;/p&gt;&lt;/span&gt;&lt;br /&gt;So, there you go. The Explanatory PDF and the scripts are available at the White Star Web Site: &lt;a href="http://www.wss.com/products/downloads.html"&gt;http://www.wss.com/products/downloads.html&lt;/a&gt; Again, read the PDF documentation. Read the disclaimers (again). Let us know how you get on...</content><link rel='alternate' type='text/html' href='http://www.wss.com/blog/2008/06/log-based-replication.html' title='Log-based replication'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5538806304499049851&amp;postID=5242879742234117652' title='0 Comments'/><link rel='replies' type='application/atom+xml' href='http://www.wss.com/blog/atom.xml' title='Post Comments'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5538806304499049851/posts/default/5242879742234117652'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5538806304499049851/posts/default/5242879742234117652'/><author><name>Progressions Staff</name><uri>http://www.blogger.com/profile/17463781557118102673</uri><email>noreply@blogger.com</email></author></entry><entry><id>tag:blogger.com,1999:blog-5538806304499049851.post-2479524357723223399</id><published>2007-06-30T20:14:00.000+01:00</published><updated>2007-07-01T10:59:45.383+01:00</updated><title type='text'>Calculate Working Days</title><content type='html'>&lt;p&gt;&lt;br /&gt;IF Employer = "WSS" THEN workDaysPerYear = 365.25&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;p class="style3"&gt;Earlier in the year I needed to write a routine to calculate "Working Days" for a client. Rather than reinvent the wheel (and succumbing to my lazy streak) I went out to PEG and begged help.&lt;br /&gt;&lt;br /&gt;Richard Elvin from Franklin Templeton sent me some rather elegant code to count weekends but the balance of opinion was that I'd need to have a database table to be able to count Public Holidays / Bank Holidays /Federal Holidays (substitute your own country's nomenclature).&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The weekend calculation which has been rewritten into a structured program (workDays.p)has for its main logic&lt;/p&gt;&lt;br /&gt;&lt;pre&gt;=========================================================================&lt;/pre&gt;&lt;br /&gt;&lt;pre&gt;&lt;span class="style2"&gt;/* If from date after to date swap them&lt;br /&gt; * over, and return negative answer...&lt;br /&gt; */&lt;br /&gt;IF pdtDateTo &amp;lt; pdtDateFrom&lt;br /&gt;THEN ASSIGN gdtTemp     = pdtDateFrom&lt;br /&gt;&lt;/span&gt;&lt;span class="style2"&gt;            pdtDateFrom = pdtDateTo&lt;br /&gt;&lt;/span&gt;&lt;span class="style2"&gt;            pdtDateTo   = gdtTemp&lt;br /&gt;&lt;/span&gt;&lt;span class="style2"&gt;            giSign      = -1.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;ASSIGN giDays = pdtDateTo - pdtDateFrom.&lt;br /&gt;&lt;br /&gt;/* Deduct Weekends...&lt;br /&gt; * Date - WEEKDAY( Date ) gives&lt;br /&gt; * the Saturday Preceding Date&lt;br /&gt; */&lt;br /&gt;ASSIGN giWeekEnds = TRUNCATE( ( ( pdtDateTo - WEEKDAY( pdtDateTo ) )&lt;br /&gt;   - (pdtDateFrom - WEEKDAY( pdtDateFrom ) ) ) / 7, 0 ).&lt;br /&gt;&lt;br /&gt;IF WEEKDAY( pdtDateTo ) = 7 THEN giDays = giDays - 1.&lt;br /&gt;IF WEEKDAY( pdtDateFrom ) = 1 THEN giDays = giDays - 1.&lt;br /&gt;&lt;br /&gt;ASSIGN giWorkDays = giDays - ( giWeekEnds * 2 ).&lt;/span&gt;&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;=========================================================================&lt;br /&gt;&lt;br /&gt;&lt;span class="style1"&gt;&lt;span class="style2"&gt;Pretty neat huh? This, of course will work anywhere that has Saturdays and Sundays as "days off". If your part of the world doesn't get Saturdays and Sundays off, I apologise but at least you're beginning to see what it's like working at White Star.&lt;br /&gt;&lt;br /&gt;It turned out that the company that I was writing the code for DID have a database table for holidays so all was happy. And then I got to thinking...&lt;br /&gt;&lt;br /&gt;Well, you know how it is, you can either fill in your tax returns and get your paperwork up to date or you can play with the language. Of course, being a good citizen, I &lt;i&gt;"put off until tomorrow"&lt;/i&gt; the paperwork and started researching holiday calculations (wonderful place that wikipedia!). Mostly pretty straight forward as it turns out. First Monday in May, Fourth Thursday in November - that kind of thing. And then I hit Easter.&lt;br /&gt;&lt;br /&gt;If you live in the USA then who cares? But in the UK everyone gets either one or both of Good Friday and Easter Monday off.&lt;br /&gt;&lt;br /&gt;Wikipedia gave me a choice. In the end I chose the Meeus/Jones/Butcher Gregorian algorithm because it was a straight calculation and didn't need any intermediate tables and it looks like this...&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;=========================================================================&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;DEFINE INPUT PARAMETER pYear AS INTEGER NO-UNDO.&lt;br /&gt;&lt;br /&gt;/* First, find Easter Sunday. */&lt;br /&gt;&lt;br /&gt;DEFINE VARIABLE iaa AS INTEGER NO-UNDO.&lt;br /&gt;DEFINE VARIABLE ibb AS INTEGER NO-UNDO.&lt;br /&gt;DEFINE VARIABLE icc AS INTEGER NO-UNDO.&lt;br /&gt;DEFINE VARIABLE idd AS INTEGER NO-UNDO.&lt;br /&gt;DEFINE VARIABLE iee AS INTEGER NO-UNDO.&lt;br /&gt;DEFINE VARIABLE iff AS INTEGER NO-UNDO.&lt;br /&gt;DEFINE VARIABLE igg AS INTEGER NO-UNDO.&lt;br /&gt;DEFINE VARIABLE ihh AS INTEGER NO-UNDO.&lt;br /&gt;DEFINE VARIABLE iii AS INTEGER NO-UNDO.&lt;br /&gt;DEFINE VARIABLE ijj AS INTEGER NO-UNDO.&lt;br /&gt;DEFINE VARIABLE ikk AS INTEGER NO-UNDO.&lt;br /&gt;DEFINE VARIABLE ill AS INTEGER NO-UNDO.&lt;br /&gt;DEFINE VARIABLE imm AS INTEGER NO-UNDO.&lt;br /&gt;DEFINE VARIABLE iMonth AS INTEGER NO-UNDO.&lt;br /&gt;DEFINE VARIABLE iDay AS INTEGER NO-UNDO.&lt;br /&gt;&lt;br /&gt;ASSIGN iaa = pYear MOD 19&lt;br /&gt;       ibb = TRUNCATE (pYear / 100, 0)&lt;br /&gt;       icc = pYear MOD 100&lt;br /&gt;       idd = TRUNCATE (ibb / 4, 0)&lt;br /&gt;       iee = ibb MOD 4&lt;br /&gt;       iff = TRUNCATE ( (ibb + 8) / 25, 0)&lt;br /&gt;       igg = TRUNCATE ( (ibb - iff + 1) / 3, 0)&lt;br /&gt;       ihh = (19 * iaa + ibb - idd - igg + 15) MOD 30&lt;br /&gt;       iii = TRUNCATE (icc / 4, 0)&lt;br /&gt;       ikk = icc MOD 4&lt;br /&gt;       ill = (32 + 2 * iee + 2 * iii - ihh - ikk) MOD 7&lt;br /&gt;       imm = TRUNCATE ((iaa + 11 * ihh + 22 * ill) / 451, 0)&lt;br /&gt;       iMonth = TRUNCATE ((ihh + ill - 7 * imm + 114) / 31, 0)&lt;br /&gt;       iDay = ((ihh + ill - 7 * imm + 114) MOD 31) + 1.&lt;br /&gt;&lt;br /&gt;CREATE ttBankHoliday.&lt;br /&gt;ASSIGN ttBankHoliday.dtDate = DATE (iMonth, iDay - 2, pYear).&lt;br /&gt;&lt;br /&gt;IF piRegionCode NE 3 /* Scotland */ THEN DO:&lt;br /&gt;   CREATE ttBankHoliday.&lt;br /&gt;   ASSIGN ttBankHoliday.dtDate = DATE (iMonth, iDay + 1, pYear).&lt;br /&gt;END.&lt;br /&gt;&lt;br /&gt;=========================================================================&lt;/pre&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;&lt;span class="style1"&gt;&lt;span class="style2"&gt;Notice the STRICT adherance to naming standards for all those integers!!&lt;br /&gt;&lt;br /&gt;Having got England/Wales working I did some extra coding for Northern Ireland and for Scotland and then, because it's nearly 50% of the Progress community, I had a go at the good old United States of America. Hey, mostly straightforward and then I got totally beaten by "Inauguration Day". Let's see 20th January every 4th year after an election. A Monday if the 20th's a Sunday but no time off if the 20th is a Saturday - Oh and no time off unless you're a federal employee in DC or a bunch of other places...&lt;br /&gt;&lt;br /&gt;Anyway there's three pieces of code for you on the Web Site - stubWorkDays.w ( a driver for workDays.p), workDays.p and bankHolidayTT.p which will subtract those Public / Bank / Whatever holidays. Get the zip file at &lt;a href="http://www.wss.com/products/downloads.html"&gt;http://www.wss.com/products/downloads.html&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;If you disagree strongly with the algorithms, drop me a line at alan@wss.com. Likewise, if you come up with any improvements. CAN the holidays of rest of the World be calculated?&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;</content><link rel='alternate' type='text/html' href='http://www.wss.com/blog/2007/06/calculate-working-days.html' title='Calculate Working Days'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5538806304499049851&amp;postID=2479524357723223399' title='0 Comments'/><link rel='replies' type='application/atom+xml' href='http://www.wss.com/blog/atom.xml' title='Post Comments'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5538806304499049851/posts/default/2479524357723223399'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5538806304499049851/posts/default/2479524357723223399'/><author><name>Progressions Staff</name><uri>http://www.blogger.com/profile/17463781557118102673</uri><email>noreply@blogger.com</email></author></entry><entry><id>tag:blogger.com,1999:blog-5538806304499049851.post-1557737075475846236</id><published>2007-06-08T15:04:00.000+01:00</published><updated>2007-06-16T13:00:03.000+01:00</updated><title type='text'>Welcome to Progressions on-line!</title><content type='html'>Welcome to Progressions on-line! After over 10 years of print and e-distribution, Progressions has evolved to an on-line resource, free to all. (Whoever imagined blogs when Ethan Lish wrote about the interest-group predecessor to the PEG in issue #1 of Progressions?) We're happy to make this available to everyone, and encourage all of you to submit ideas, problems, experiences, things you've learned from the PEG or from your work, so that everyone can take advantage of the collective body of knowledge that has accumulated around Progress over almost 2 ½ decades. We will be encouraging many of the contributors to Progressions to submit for this weblog as well, and intend to build it into a useful repository of ideas on all sorts of topics.&lt;br /&gt;&lt;br /&gt;Again, Welcome!&lt;br /&gt;&lt;br /&gt;John Campell</content><link rel='alternate' type='text/html' href='http://www.wss.com/blog/2007/06/welcome-to-progressions-on-line.html' title='Welcome to Progressions on-line!'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=5538806304499049851&amp;postID=1557737075475846236' title='1 Comments'/><link rel='replies' type='application/atom+xml' href='http://www.wss.com/blog/atom.xml' title='Post Comments'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5538806304499049851/posts/default/1557737075475846236'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5538806304499049851/posts/default/1557737075475846236'/><author><name>Progressions Staff</name><uri>http://www.blogger.com/profile/17463781557118102673</uri><email>noreply@blogger.com</email></author></entry></feed>