![]() |
|
Published 1999-01-01 Printer-friendly version
There are two basic methods for implementing replication in your Clarion For Windows applications. The first method is to use a Client/Server database, such as SQL Anywhere from Sybase. Using ODBC drivers, you can allow your Clarion application to access these databases easily. The SQL Anywhere product includes Replication Server, which can automatically replicate the tables without intervention from your application program. You can couple this product with SQL Remote, also from Sybase, which can actually use e-mail to perform the transfer without requiring a live network. Other client/server databases that can perform replication include Microsoft SQL Server, Oracle, and Lotus Notes. All of these products create databases that can be accessed in your Clarion dictionary and application via ODBC or using accelerator drivers provided with Clarion 4 Professional or Clarion 5 Enterprise Edition. These products are automatic and highly configurable, although they can be quite expensive.
The second method is to "roll your own". You can write programs using the Clarion language to handle one-way and two-way replication. I have successfully written synchronization routines using Clarion For Windows and Windows95. I will show you a simple (and inexpensive) method of performing synchronization of replicas using just your Clarion environment, Windows95, and Microsoft PLUS for Windows 95.
There are three basic steps to writing code that is "replica-aware". First, we must prepare the dictionary. Next, we must embed code into our application. Third, we must write a synchronizer program.
Actually using our program will be easy. We will connect to the network by double clicking an icon, and then synchronize the replicas while connected. Then we will disconnect from the network.
Each table to be replicated requires some fields. The first field(s) necessary is what is known as a Globally Unique ID (GUID). This forms the primary key used to access the record by the synchronizer program. If you already have a primary key, you do not require one of these, but it never hurts. This is a unique identifier for the record that is guaranteed to remain unique throughout the world. It is needed to locate the copy of this particular record in each replica. Note that between replicas, record #3833 in record number order in the CUSTOMER table will NOT necessarily point to the same customer record. This is the reason we need the GUID. By making a key from this field, our code will easily be able to locate the correct record between all the records should a modification or deletion become necessary. You can use a combination of LOCATIONID and INVOICENO, for instance, to create the GUID. A better way, in my opinion, is to generate the GUID on the fly using a simple function call. You can have your program look in your PROGRAM.INI file to determine which location the program is running from. I like to write functions to make programming easier, so you can assign the GUID by calling a function as follows:
SampleFile.NodeId = GenerateGUID()
The above function call can easily be handled by placing the GenerateGUID() function call into the "Initial Value" entry box for the GUID field within the dictionary editor. This will cause the AppGen to prime the key when the record is created automatically. I also like to keep a copy of the GenerateGUID() function as either a .TXA file on disk, or even better, as a .DLL file that can be included into the app. The code for GenerateGUID is as follows:
GenerateGUID FUNCTION()
TestGUID STRING(30)
InstanceNo += 1
If InstanceNo = 999
InstanceNo = 1
END
TestGUID = '{{' & FORMAT(GlobalSiteCode,@P####P)&'-'&|
FORMAT(TODAY(),@P######P) & '-' & |
FORMAT(CLOCK(),@P#######P) & '-' & |
FORMAT(InstanceNo,@P###P) & '}'
RETURN(CLIP(TestGUID))
In addition to the GUID set up, you must also indicate who will receive updates from your replica. This is accomplished by settting up a "Subscriber" file. Your replica "publishes" file changes to all "subscribers" defined in this file (either directly through the SubscriberID field or indirectly through the HandoffTo field). The structure of this file is as follows:
SUBSCRIBE
FILE,DRIVER('TOPSPEED'),NAME('C:.\Subscrib.TPS'),CREATE,PRE(SUBSCRIBE)
BySubscriberID KEY(+SUBSCRIBE:SubscriberID),DUP,OPT,NOCASE
ByTableName
KEY(+SUBSCRIBE:TableName,+SUBSCRIBE:SubscriberID),DUP,OPT,NOCASE
RECORD RECORD
SubscriberID STRING(10)
TableName STRING(40)
HourOffset BYTE
HandoffTo STRING(10)
. .
This file is built by the administrator and configured once for each participating replica. You will nominate those remote systems that require updates from your replica in this file and may maintain it from time to time. The Subscriber ID is a string such as "Miami", "Tampa", etc. This matches the "Publisher ID" of the remote system. The TableName field identifies the name of the database that is flagged for replication. The HourOffset is a number that represents, positive or negative, the number of hours apart from each other the two systems are (time zones).
The HandoffTo field is used when you do not want to cause the local system to be responsible for replicating the data, but instead you wish to nominate the remote computer to have the responsibility for continuing the update. For example, suppose you have a traveling salesman that works for a company that maintains one home office and three shipping warehouses. At night, when the salesman reaches his hotel room, he will have 4 different subscribers for any orders he has taken during the course of the day. The last thing the salesman will want to do is place 4 phone calls. Ideally, he should only have to dial the home office, and the Subscriber file be set up to nominate the home office to continue the process of replication to the other 3 subscribers. This is what the HandoffTo field takes care of during the synchronization process. In effect, the salesman will only be required to place one phone call and, during that call, all information is sent to the master replica at the home office, and at some later time those changes will be propagated by the home office to the shipping warehouses. This basically results in an "indirect" transfer to the participating subscriber. It still originates from the remote salesman, but transfers the responsibility of further replication to the nominated HandoffTo system.
Now, after creating a subscriber file, there must be a transaction log that indicates if a record has been added, modified, or deleted by the user or by a process. I create a transaction log file named "Replica" and include the following field set in this file:
REPLICA
FILE,DRIVER('TOPSPEED'),NAME('C:.\Replica.TPS'),CREATE,PRE(REPLICA)
GUIDKey KEY(+REPLICA:GUID),OPT,NOCASE,PRIMARY
BySubscriber
KEY(+REPLICA:SubscriberID,+REPLICA:DateStamp,+REPLICA:TimeStamp),DUP,OPT,NOCASE
RECORD RECORD
GUID STRING(40)
SubscriberID STRING(10)
TableName STRING(40)
TableGUID STRING(40)
UpdateAction BYTE
DateStamp LONG
TimeStamp REAL
ExpectedGeneration SHORT
ConflictFlag BYTE
HandoffTo STRING(10)
. .
This file will be updated for each action occurring to the file to be replicated. Note that this transaction log will record each insertion, modification, or deletion of a record in the source table. Every time a record in the replica is added, modified, or deleted, several records are added to this replica transaction log, i.e. one record for each subscriber for each affected table. During later synchronization, the sync program will select only those records from the log to which the synchronizer program is currently connected. In other words, if the synchronizer is replicating to subscriber "Miami" from publisher "Jim Q. Salesman", only those transaction log entries that have SubscriberID "Miami" will be sent across the connection during that remote session. The records that still remain in the log after synchronization will be sent when the system makes a connection to other subscribers.
The final set of fields needed in the table to be replicated is DateStamp (LONG), and TimeStamp (REAL). These two fields are set by the application program when the records are written. As the synchronizer reads the tables, it looks for records that have been modified since the last synchronization (stored in the transaction log). A comparison will be made between the DateStamp/TimeStamp in the replicated table and the DateStamp/TimeStamp field in the replica transaction log to determine if a transfer is necessary or desired. This is also needed to handle conflict resolution where two or more replicas made changes to the same record, but in different ways. For example, perhaps one user changed the phone number and saved the record. Another user may have changed the zip code and saved the record. Only one record "wins". Which one wins is determined by looking at the dates and times of modification. An exception report could be printed by the synchronizer that will advise an administrator of a conflict. It is up to the administrator to resolve this type of conflict and making corrections to the record if necessary.
Now finally, in the dictionary editor, copy the definitions for the "Replica" transaction log file, the "Subscriber" file, and any replicated tables to another identical set of files in the dictionary. This second set of files represents the "remote" copy of them. Set the file properties so that the "Full Pathname" uses something like the following:
S:\Replica\Replica.Tps (Table name would be ReplicaRemote)
S:\Replica\Subscribe.Tps (Table name would be SubscribeRemote)
S:\Replica\Customer.Tps (Table name would be CustomerRemote)
Note the use of S: as part of the path name. When dial up networking is employed to make the connection, we will map drive S: to the remote server hard disk. By placing these pointers into the dictionary we can painlessly access the remote copy of these files from within our synchronizing program as easily as we are already accessing the local copy. Drive C: will represent the local replica, and drive S: will represent the remote replica, while dial up networking is active and the sync program running. Within the source code for our synchronizer, when we reference the file label "Replica", we will be accessing the local copy of the transaction log. When we reference the file label "ReplicaRemote", we will be accessing the remote copy of the transaction log. Thus, our synchronizer will be able to handle record copy in two different directions without any reliance on anything other than dial-up networking and a remote share. The same goes for "Subscribe" vs. "SubscribeRemote", and "Customer" vs. "CustomerRemote". Get it?
The process of synchronization happens in two end-user steps. First, the end-user clicks an icon on the desktop (a dial-up networking icon), to connect to the main network. This will also cause a remote drive letter S: to be mapped. Second, the synchronizer program is run. This program accesses drive C: (the local drive) and drive S: to perform the synchronization between the two copies of the database. After synchronization, the program terminates, and the end-user disconnects from the dial-up server.
There is an easy way to handle simple networking using Microsoft PLUS for Windows 95, and simple dial-up connections. Using this type of networking, synchronization will take anywhere from a couple of minutes to a couple of hours, depending upon the quality of the connection and how many records are modified through the day. You can read the instructions that come with the Plus software (in the help screens), or study the Windows 95 or NT Resource Kits, or just follow the instructions below.
Note that the computer on the main network is termed the dial-up server. The remote client computer will "dial in" to the dial-up server. The remote client will then exchange its information with the main network using the dial-up server as the bridge to the database.
First, a computer on the main network must be designated as a dial-up server. It is possible, though not necessary, to make this the same computer as the one that holds your main database on its hard drive. If you have a local Ethernet network, then you can make any Windows 95 computer the dial-up server, and the system can go across the LAN to access the files. If you are using Windows NT for this computer, then simply configure RAS (Remote Access Service) to handle the dial-ups. If you are using a Windows 95 computer, you need to load a copy of Microsoft PLUS. This includes a free dial-up server program. After installation of the program, you set it up as follows:
At the client end, set up the Dial-Up Networking icon to dial the phone number of the dial-up server. This is done as follows:
Now the client computer will be able to call the dial-up server
computer. Now you must
"map" a drive letter that points to the shared resource on the main
network. To do this, perform the dial up by double-clicking the
icon you created under Dial-Up Networking and connect. After
connection, select Start and Run. Type \\ComputerName at the Run prompt. Now
a version of Network Neighborhood will appear that shows that
computers resources. Right-click the share name (DATA_C) of
the hard drive that contains your database files, and map a drive
letter (such as S:) to this drive. We will refer to drive S: later
in the synchronization program. When you reconnect, the dial-up
should automatically re-establish the S: connection.
At this point, any DOS or Windows application can access drive S: to copy files back and forth. We can also point our application (the synchronizer in this case) to read the data file records from the remote drive S: just as easily as it reads from drive C:.
I would also drag and drop a shortcut to this Dial-Up Networking Connection icon to the desktop. This will make it easier for your client user to get online prior to synchronization by giving them an icon they can just double-click to get online.
This completes the network step.
First, your application should check an INI file to obtain the site code for the local replica. Next, have it read the table containing the subscriber information into a queue, so it will be processed faster. Embed the following code into your Main procedure WindowEventsà OpenWindow embed:
GlobalSiteCode = GETINI('Replication','SiteCode',1)
ReadSubscriberQueue
This will read the Site Code value from the Win.Ini file, defaulting it to 1 if it is not present. Remember that each site must have a different site code value in its INI file. For example, New York could be 1, Miami could be 2, etc.
The subscriber queue is defined as:
SubscriberQueue QUEUE
SubscriberID STRING(10)
TableName STRING(40)
HandoffTo STRING(10)
END
The ReadSubscriberQueue procedure is a "Process" that uses the following code embedded at the LocalObjectsà ThisProcessà TakeRecord embed.
SubscriberQueue.SubscriberID = SUBSCRIBE:SubscriberID
SubscriberQueue.TableName = SUBSCRIBE:TableName
SubscriberQueue.HandoffTo = SUBSCRIBE:HandoffTo
ADD(SubscriberQueue)
After you have set this up, you are ready to cause the form the flag modifications. To do this, embed the following code into the Control Eventsà ?Okà Accepted embed of each data entry form (or any other file update) that needs to support replication:
UpdateReplicaLog(CONTACTS:GUID,'Contacts',SELF.Request)
CONTACTS:DateStamp = TODAY()
CONTACTS:TimeStamp = CLOCK()
Substitute the name of the GUID field for "CONTACTS:GUID", the name of the table for "Contacts", and set the correct datestamp and timestamp fields for your file.
The definition for the UpdateReplicaLog procedure is as follows:
UpdateReplicaLog PROCEDURE (AssignedGUID,FileName,Action)
CODE
Access:Replica.Open
LOOP I# = 1 to RECORDS(SubscriberQueue)
GET(SubscriberQueue,I#)
IF SubscriberQueue.TableName <> FileName Then CYCLE.
REPLICA:GUID = GenerateGUID()
REPLICA:SubscriberID = SubscriberQueue.SubscriberID
REPLICA:TableName = FileName
REPLICA:TableGUID = AssignedGUID
REPLICA:UpdateAction = Action
REPLICA:DateStamp = TODAY()
REPLICA:TimeStamp = CLOCK()
REPLICA:ExpectedGeneration = 0
REPLICA:ConflictFlag = FALSE
REPLICA:HandoffTo = SubscriberQueue.HandoffTo
IF Access:Replica.Insert() <> LEVEL:Benign
MESSAGE('Could not write to replica log.')
END
END
Access:Replica.Close
Don't forget to include the definition of the GenerateGuid function we discussed earlier somewhere in the app file. That's all that you need to do to your application.
The actual synchronization process is handled by a simple Clarion for Windows program. The source code for a basic synchronizer is shown below. The program will first open both sets of databases, presenting an error if it is unable to. It then performs synchronization in two phases. In the first phase, records from the local tables are sent to the remote. In the second phase, records from the remote tables are downloaded to the local. In each phase, we check for conflicts as well as processing the "hand-offs". The code is fairly easy to understand.
You may download this code from www.cdcaug.com/clarion, and I would recommend that you do so. Contained in the download is a sample dictionary and an application that has been set up to support replicas, along with the following synchronizer source code.
Main PROCEDURE ! Declare Procedure
AttachedSubscriber STRING(20)
PhaseString STRING(40)
Progress1 BYTE
Counter LONG
Recs LONG
LocalPublisher STRING(20)
window WINDOW('Replica Synchronizer'),AT(,,216,78),GRAY
STRING('Synchronizing Replicas ... Please wait ...'),AT(40,14),USE(?String1)
STRING(@s40),AT(38,27),USE(PhaseString),CENTER
PROGRESS,USE(Progress1),AT(38,44,140,8),RANGE(0,100)
END
CODE
OPEN(Window) ! Open status window
DISPLAY
AttachedSubscriber = COMMAND('SUBSCRIBER')
LocalPublisher = COMMAND('PUBLISHER')
SHARE(Replica) ! Open files
Do CheckLocalError
SHARE(Contacts)
Do CheckLocalError
STREAM(Replica)
STREAM(Contacts)
SHARE(ReplicaRemote)
Do CheckRemoteError
SHARE(ContactsRemote)
Do CheckRemoteError
STREAM(ReplicaRemote)
STREAM(ContactsRemote)
!---------------------------------------------------------------------------
! Now we have confirmed that we have a live connection to the remote, and
! that all necessary files on the local and remote systems are ready to
! be synchronized.
!---------------------------------------------------------------------------
Do PhaseOne ! Process Phase one of Replication
Do PhaseTwo ! Process Phase two of Replication
FLUSH(Replica) ! End of processing
FLUSH(ReplicaRemote)
FLUSH(Contacts)
FLUSH(ContactsRemote)
CLOSE(Replica)
CLOSE(ReplicaRemote)
CLOSE(Contacts)
CLOSE(ContactsRemote)
CLOSE(Window)
RETURN
PhaseOne ROUTINE
!---------------------------------------------------------------------------
! PHASE ONE:
!
! In phase one, we will send entries from the database to be
! synchronized to the remote system.
!
! We may also send copies of the log entry to the remote, if it has
! been determined that the remote computer is a "pass-thru", i.e. it
! will perform synchronization for other subscribers from the local
! publisher.
!
! Send All Log Entries that are destined for the remote system to
! the remote. As the entries are sent, determine if the log entry
! should be "handed off" to the remote (so that the remote can
! replicate the entry to other systems on its own.) If the log
! entry is not to be "handed off" it is simply erased from the
! replica log file after the primary database record is copied.
! If it is to be handed off, the local system will copy both the
! primary database record pointed to by the log, as well as the log
! entry itself to the remote replica.
!---------------------------------------------------------------------------
PhaseString = 'Phase 1 of 2 ...'
DISPLAY
Counter = 0
Recs = RECORDS(Replica)
SET(Replica) ! Read the replica log file
LOOP ! Start reading records destined for this remote
NEXT(Replica)
IF ERROR() Then Break. ! Stop reading upon end of file
! Should we synchronize the main database?
Counter += 1
Progress1 = (Counter / Recs) * 100
DISPLAY ! Display progress indicator
IF REPLICA:SubscriberID = AttachedSubscriber ! If so, then
Do SynchronizeRemote ! synchronize the main database
END
! Do we "hand off" some of the replica log entries
! to be processed by this remote?
IF REPLICA:HandoffTo = AttachedSubscriber ! If so, then
Do SendLocalReplicaLog ! send the log entries
Do SynchronizeRemote ! and synchronize the main database
END
END
PhaseTwo ROUTINE
!---------------------------------------------------------------------------
! PHASE TWO:
!
! In phase two, we will receive entries from the database to be
! synchronized to the local system.
!
! We may also receive copies of the log entry from the remote, if it has
! been determined that the local computer is a "pass-thru", i.e. it
! will perform synchronization for other subscribers from the local
! publisher.
!
! Send All Log Entries that are destined for the local system to
! the local. As the entries are sent, determine if the log entry
! should be "handed off" to the local (so that the local can
! replicate the entry to other systems on its own.) If the log
! entry is not to be "handed off" it is simply erased from the
! remote replica log file after the primary database record is copied.
! If it is to be handed off, the local system will copy both the
! primary database record pointed to by the log, as well as the log
! entry itself from the remote replica.
!---------------------------------------------------------------------------
PhaseString = 'Phase 2 of 2 ...'
DISPLAY
Counter = 0
Recs = RECORDS(ReplicaRemote)
SET(ReplicaRemote) ! Read the replica log file
LOOP ! Start reading records destined for this remote
NEXT(ReplicaRemote)
IF ERROR() Then Break. ! Stop reading upon end of file
! Should we synchronize the main database?
Counter += 1
Progress1 = (Counter / Recs) * 100
DISPLAY ! Display progress indicator
IF REPLICAREM:SubscriberID = LocalPublisher ! If so, then
Do SynchronizeLocal ! synchronize the main database
END
! Do we "hand off" some of the replica log entries
! to be processed by this remote?
IF REPLICAREM:HandoffTo = LocalPublisher ! If so, then
Do GetRemoteReplicaLog ! send the log entries
Do SynchronizeLocal ! and synchronize the main database
END
END
CheckLocalError ROUTINE
IF ERROR()
MESSAGE('A file on this local system could not be opened.|
Terminating Synchronizing Process.','Fatal Error',ICON:Exclamation,BUTTON:OK)
RETURN
END
CheckRemoteError ROUTINE
IF ERROR()
MESSAGE('A file on the remote system could not be opened.|
Terminating Synchronizing Process.','Fatal Error',ICON:Exclamation,BUTTON:OK)
RETURN
END
SynchronizeRemote ROUTINE
!---------------------------------------------------------------
! This routine will write the data record into the main database
! file that is pointed to by the current entry in the local
! replica log. Perform replication on the file named by the
! replica log entry. The log entry is removed from the local
! replica log when completed. Also, test for any conflicts
! found while synchronizing.
!---------------------------------------------------------------
IF REPLICA:TableName = 'Contacts'
IF REPLICA:UpdateAction = 3 ! If record is being deleted
CONTACTSREM:GUID = REPLICA:TableGUID ! retrieve the remote copy
GET(ContactsRemote,CONTACTSREM:GUIDKey) ! by the GUID key
IF NOT ERROR() ! If no problem retrieving
DELETE(ContactsRemote) ! Then erase the record from the remote database
DELETE(Replica) ! and remove the replica log entry
ELSE ! otherwise
REPLICA:ConflictFlag = TRUE ! set the conflict flag to true
PUT(Replica) ! and don't sync the deletion
END
ELSIF REPLICA:UpdateAction = 2 ! If record is being changed
! has another user updated record newer?
! If so, set conflict flag to true
! and don't sync the change
CONTACTSREM:GUID = REPLICA:TableGUID ! retrieve the remote copy
GET(ContactsRemote,CONTACTSREM:GUIDKey) ! by the GUID key
IF NOT ERROR() ! If the record was over there
! check for update conflict
IF (CONTACTSREM:DateStamp > REPLICA:DateStamp)|
OR ((CONTACTSREM:DateStamp = REPLICA:DateStamp)|
AND (CONTACTSREM:TimeStamp > REPLICA:TimeStamp))
REPLICA:ConflictFlag = TRUE
PUT(Replica)
ELSE
CONTACTSREM:GUID = REPLICA:TableGUID ! Make sure they don't already have it
GET(ContactsRemote,CONTACTSREM:GUIDKey) ! by looking it up with the GUID Key
IF NOT ERROR() ! If the record was already there
CONTACTSREM:Record = CONTACTS:Record ! If no conflict, update the remote copy
PUT(ContactsRemote)
DELETE(Replica) ! and remove the replica log entry
ELSE
REPLICA:ConflictFlag = TRUE
PUT(Replica)
END
END
ELSE
REPLICA:ConflictFlag = TRUE ! set the conflict flag to true
PUT(Replica) ! and don't sync the change
END
ELSIF REPLICA:UpdateAction = 1 ! If record is being added
CONTACTSREM:GUID = REPLICA:TableGUID ! Make sure they don't already have it
GET(ContactsRemote,CONTACTSREM:GUIDKey) ! by looking it up with the GUID Key
IF NOT ERROR() ! If the record was already there
REPLICA:ConflictFlag = TRUE ! set the conflict flag to true
PUT(Replica) ! and don't sync the addition
ELSE
CONTACTS:GUID = REPLICA:TableGUID ! Retrieve local copy
GET(Contacts,CONTACTS:GUIDKey)
IF NOT ERROR()
CONTACTSREM:Record = CONTACTS:Record ! If no conflict, update the remote copy
ADD(ContactsRemote)
DELETE(Replica) ! and remove the replica log entry
ELSE
REPLICA:ConflictFlag = TRUE ! set the conflict flag to true
PUT(Replica) ! and don't sync the addition
END
END
END ! End of testing for Update Action
END ! End of testing for table name
SynchronizeLocal ROUTINE
!---------------------------------------------------------------
! This routine will write the data record into the main database
! file that is pointed to by the current entry in the remote
! replica log. Perform replication on the file named by the
! replica remote log entry. The log entry is removed from the
! remote replica log when completed. Also, test for any conflicts
! found while synchronizing.
!---------------------------------------------------------------
IF REPLICAREM:TableName = 'Contacts'
IF REPLICAREM:UpdateAction = 3 ! If record is being deleted
CONTACTS:GUID = REPLICAREM:TableGUID ! retrieve the local copy
GET(Contacts,CONTACTS:GUIDKey) ! by the GUID key
IF NOT ERROR() ! If no problem retrieving
DELETE(Contacts) ! Then erase the record from the local database
DELETE(ReplicaRemote) ! and remove the replica log entry
ELSE ! otherwise
REPLICAREM:ConflictFlag = TRUE ! set the conflict flag to true
PUT(ReplicaRemote) ! and don't sync the deletion
END
ELSIF REPLICAREM:UpdateAction = 2 ! If record is being changed
! has another user updated record newer?
! If so, set conflict flag to true
! and don't sync the change
CONTACTS:GUID = REPLICAREM:TableGUID ! retrieve the remote copy
GET(Contacts,CONTACTS:GUIDKey) ! by the GUID key
IF NOT ERROR() ! If the record was over there
! check for update conflict
IF (CONTACTS:DateStamp > REPLICAREM:DateStamp)|
OR ((CONTACTS:DateStamp = REPLICAREM:DateStamp)
AND (CONTACTS:TimeStamp > REPLICAREM:TimeStamp))
REPLICAREM:ConflictFlag = TRUE
PUT(ReplicaRemote)
ELSE
CONTACTSREM:GUID = REPLICAREM:TableGUID ! Retrieve remote copy
GET(ContactsRemote,CONTACTSREM:GUIDKey)
IF NOT ERROR()
CONTACTS:Record = CONTACTSREM:Record ! If no conflict, update the local copy
PUT(Contacts)
DELETE(ReplicaRemote) ! and remove the replica log entry
ELSE
REPLICAREM:ConflictFlag = TRUE ! set the conflict flag to true
PUT(ReplicaRemote) ! and don't sync the change
END
END
ELSE
REPLICAREM:ConflictFlag = TRUE ! set the conflict flag to true
PUT(ReplicaRemote) ! and don't sync the change
END
ELSIF REPLICAREM:UpdateAction = 1 ! If record is being added
CONTACTS:GUID = REPLICAREM:TableGUID ! Make sure they don't already have it
GET(Contacts,CONTACTS:GUIDKey) ! by looking it up with the GUID Key
IF NOT ERROR() ! If the record was already there
REPLICAREM:ConflictFlag = TRUE ! set the conflict flag to true
PUT(ReplicaRemote) ! and don't sync the addition
ELSE
CONTACTSREM:GUID = REPLICAREM:TableGUID ! Retrieve remote copy
GET(ContactsRemote,CONTACTSREM:GUIDKey)
IF NOT ERROR()
CONTACTS:Record = CONTACTSREM:Record ! If no conflict, update the remote copy
ADD(Contacts)
DELETE(ReplicaRemote) ! and remove the replica log entry
ELSE
REPLICAREM:ConflictFlag = TRUE ! set the conflict flag
PUT(ReplicaRemote) ! and don't sync the addition
END
END
END ! End of testing for Update Action
END ! End of testing for table name
SendLocalReplicaLog ROUTINE
!-----------------------------------------------------------
! This routine will send the local replica log entry to the
! remote replica log. It will move the handoff information
! into the subscriber data field, so that the remote system
! appears to originate the transaction. This allows the
! remote to propagate the updates to other machines.
!-----------------------------------------------------------
REPLICAREM:Record = REPLICA:Record
REPLICAREM:SubscriberID = REPLICAREM:HandoffTo
REPLICAREM:HandoffTo = ''
ADD(ReplicaRemote)
GetRemoteReplicaLog ROUTINE
REPLICA:Record = REPLICAREM:Record
REPLICA:SubscriberID = REPLICA:HandoffTo
REPLICA:HandoffTo = ''
ADD(Replica)
Take notice of the IF statements that are used to perform conflict detection. They are simply checking the timestamps of the records to see which copy is newer. Here is where you may modify the code to perform other types of conflict detection and resolution.
The source code above is straightforward and easy to implement. Please use it to your heart's content, and send me your modifications, bug fixes (if you find any <g>), and comments to me at jerry@cdcaug.com.
See you next month!
Editor's note: The download page mentioned in the article is no longer online, but thanks to Dinko Bacun we now have most of the files. If you have any of the remaining files, or know who might, please contact the editor.
DevCon 98 Presentation Files:
Dial-Up Networking Automation for use with Replication:
Other Data Replication Strategies:
Copyright © 1999-2008 by CoveComm Inc. All Rights Reserved. Reproduction in any form without the express written consent of CoveComm Inc., except as described in the subscription agreement, is prohibited.
Clarion Magazine ISSN 1718-9942
One year: $184
(includes all back issues since '99)
Renewals from $134
Two years: $274
Renewals from $224