Replication from Oracle to PostgreSQL Using GoldenGate

Andy Kerber (@dbakerber), Senior Consultant

This is part three of a four-part post on Oracle Golden Gate replication. In part one, we configured Oracle Multi-Master replication with basic conflict management. In part two, we configured Oracle to MySQL replication using GoldenGate. In this third part, we will configure Oracle to PostgreSQL replication using GoldenGate.

Environment:

Oracle database server: pgorcl
Oracle version: 12.1.0.2 (not using containers)
PostgreSQL database server: pgsql
PostgreSQL: version 9.6
GoldenGate: version 12c

Installation:

GoldenGate for PostgreSQL is in a zipped tar file. Unzip the file in your GoldenGate Home directory, $GGH, and then untar it (tar –xvf).

After installation, you will need to add the GoldenGate Home directory ($GGH) and the $GGH/lib directory to your LD_LIBRARY_PATH.

A caveat: GoldenGate for PostgreSQL uses odbc for the connection to PostgreSQL. Any data types that cannot be handled by odbc will not be handled by GoldenGate.

First, in PostgreSQL, we create the schema and database:

psql

postgres=# create user andy with superuser password 'andy';

postgres=#create database andy;

postgres=#\c andy

andy=# CREATE TABLE BONUS2
    (    ENAME VARCHAR(10),
         JOB VARCHAR(9),
         SAL BIGINT,
         COMM BIGINT,
         LAST_UPDT_DT_TM TIMESTAMP  DEFAULT CURRENT_TIMESTAMP 
    )
   ;
CREATE TABLE DEPT
    (    DEPTNO SMALLINT,
         DNAME VARCHAR(14),
         LOC VARCHAR(13),
         LAST_UPDT_DT_TM TIMESTAMP DEFAULT current_timestamp
    )
   ;
CREATE TABLE DEPTTEST
    (    DEPTNO SMALLINT,
         DNAME VARCHAR(14),
         LOC VARCHAR(13),
         LAST_UPDT_DT_TM TIMESTAMP DEFAULT current_timestamp
    )
   ;
CREATE TABLE EMP
    (    EMPNO SMALLINT,
         ENAME VARCHAR(10),
         JOB VARCHAR(9),
         MGR SMALLINT,
         HIREDATE TIMESTAMP,
         SAL DECIMAL(7,2),
         COMM DECIMAL(7,2),
         DEPTNO SMALLINT,
         LAST_UPDT_DT_TM TIMESTAMP DEFAULT current_timestamp
    )
   ;
CREATE TABLE MYTEST
    (    EMPNO SMALLINT,
         ENAME VARCHAR(10),
         JOB VARCHAR(9),
         MGR SMALLINT,
         HIREDATE TIMESTAMP,
         SAL DECIMAL(7,2),
         COMM DECIMAL(7,2),
         DEPTNO SMALLINT,
         LAST_UPDT_DT_TM TIMESTAMP DEFAULT current_timestamp
    )
   ;
CREATE TABLE SALGRADE
    (    GRADE BIGINT,
         LOSAL BIGINT,
         HISAL BIGINT,
         LAST_UPDT_DT_TM TIMESTAMP DEFAULT current_timestamp
    )
   ;
CREATE TABLE SCOTTWORK
    (    EMPNO SMALLINT,
         ENAME VARCHAR(10),
         JOB VARCHAR(9),
         MGR SMALLINT,
         HIREDATE TIMESTAMP,
         SAL DECIMAL(7,2),
         COMM DECIMAL(7,2),
         DEPTNO SMALLINT,
         LAST_UPDT_DT_TM TIMESTAMP DEFAULT current_timestamp
    )
   ;
CREATE TABLE SCOTTWORK3
    (    EMPNO SMALLINT,
         ENAME VARCHAR(10),
         JOB VARCHAR(9),
         MGR SMALLINT,
         HIREDATE TIMESTAMP,
         SAL DECIMAL(7,2),
         COMM DECIMAL(7,2),
         DEPTNO SMALLINT,
         LAST_UPDT_DT_TM TIMESTAMP DEFAULT current_timestamp
    )
   ;
CREATE TABLE TCUSTMER
    (    CUST_CODE VARCHAR(4),
         NAME VARCHAR(30),
         CITY VARCHAR(20),
         STATE CHAR(2),
         LAST_UPDT_DT_TM TIMESTAMP DEFAULT current_timestamp
    )
   ;
CREATE TABLE TCUSTORD
    (    CUST_CODE VARCHAR(4),
         ORDER_DATE TIMESTAMP,
         PRODUCT_CODE VARCHAR(8),
         ORDER_ID BIGINT,
         PRODUCT_PRICE DECIMAL(8,2),
         PRODUCT_AMOUNT INT,
         TRANSACTION_ID BIGINT,
         LAST_UPDT_DT_TM TIMESTAMP DEFAULT current_timestamp
    )
   ;
CREATE TABLE TESTAB
    (    EMPNO SMALLINT,
         ENAME VARCHAR(10),
         JOB VARCHAR(9),
         MGR SMALLINT,
         HIREDATE TIMESTAMP,
         SAL DECIMAL(7,2),
         COMM DECIMAL(7,2),
         DEPTNO SMALLINT,
         LAST_UPDT_DT_TM TIMESTAMP DEFAULT current_timestamp
    )
   ;
 
CREATE UNIQUE INDEX PK_DEPT ON DEPT (DEPTNO)
   ;
CREATE UNIQUE INDEX PK_EMP ON EMP (EMPNO)
   ;
CREATE UNIQUE INDEX SYS_C0011589 ON TCUSTMER (CUST_CODE)
   ;
CREATE UNIQUE INDEX SYS_C0011590 ON TCUSTORD (CUST_CODE, ORDER_DATE, PRODUCT_CODE, ORDER_ID)
   ;
 
ALTER TABLE DEPT ADD CONSTRAINT PK_DEPT PRIMARY KEY (DEPTNO);
ALTER TABLE EMP ADD CONSTRAINT PK_EMP PRIMARY KEY (EMPNO);
ALTER TABLE TCUSTMER ADD PRIMARY KEY (CUST_CODE);
ALTER TABLE TCUSTORD ADD PRIMARY KEY (CUST_CODE, ORDER_DATE, PRODUCT_CODE, ORDER_ID);
ALTER TABLE EMP add FOREIGN KEY(DEPTNO) references dept(deptno);

 

Next, create the odbc data source. The odbc drivers are included in GoldenGate, but make sure that $GGH is in your path.

The file below, which is your odbc ini file:

vi odbc.ini

[ODBC Data Sources]
PostgreSQL on pgsql
[ODBC]
IANAAppCodePage=4
InstallDir=/u01/gghome
[pg96db]
Driver=/u01/gghome/lib/GGpsql25.so
Description=Postgres driver
Database=andy
HostName=pgsql
PortNumber=5432
LogonID=andy
Password=andy

Set the environment variable, ODBCINI, to point to this file:

export ODBCINI=/u01/gghome/odbc.ini

 

Next, create the defgen file on the oracle server. These are the same steps as used to create the defgen file for MySQL:

This allows GoldenGate to translate the Oracle definition to the PostgreSQL definition.

GGSCI@pgorcl>edit params defgen

defsfile ./dirdef/andy.def, purge
userid ggadmin, password ggadmin
table andy.*;

From the OS level, generate the defgen file.

$defgen paramfile ./dirprm/defgen.prm

The command will write the defgen file to the file specified, in this case $GGH/dirdef/andy.def

The file will have a section like this at the start:

Database type: ORACLE
Character set ID: UTF-8
National character set ID: UTF-16
Locale: neutral
Case sensitivity: 14 14 14 14 14 14 14 14 14 14 14 14 11 14 14 14
TimeZone: GMT
*

 

And sections like this for each table:

Definition for table ANDY.EMP
Record length: 144
Syskey: 0
Columns: 9
EMPNO            134    8      0 0 0 1 0     8     8     8 0 0 0 0 1   0 1   3   2       -1     0 0 0
ENAME            64     10     12 0 0 1 0    10    10    0 0 0 0 0 1   0 0   0   1       -1     0 0 0
JOB              64     9      28 0 0 1 0    9     9     0 0 0 0 0 1   0 0   0   1       -1     0 0 0
MGR              134    8      42 0 0 1 0    8     8     8 0 0 0 0 1   0 0   3   2       -1     0 0 0
HIREDATE         192    19     54 0 0 1 0    19    19    19 0 5 0 0 1  0 0   0   12      -1     0 0 0
SAL              134    9      76 2 0 1 0    8     8     8 0 0 0 0 1   0 0   3   2       -1     0 0 0
COMM             134    9      88 2 0 1 0    8     8     8 0 0 0 0 1   0 0   3   2       -1     0 0 0
DEPTNO           134    8      100 0 0 1 0   8     8     8 0 0 0 0 1   0 0   3   2       -1     0 0 0
LAST_UPDT_DT_TM  192    29     112 0 0 1 0   29    29    29 0 6 0 0 1  0 0   0   187     -1     0 0 0
End of definition

 

The file is used to translate the Oracle data to MySQL data. After generating the file, copy it to the corresponding directory on the PostgreSQL server.

eg scp andy.def pgsql:/u01/gghome/dirdef

Switch back to the Oracle server and create the initial load extract (this is very similar to the one we created for MySQL):

GGSCI@pgorcl > add extract pgext, sourceistable
GGSCI@pgorcl > edit params pgext
Extract pgext
userid ggadmin password ggadmin
rmthost pgsql, mgrport 7809 ← Parameters for the PostgreSQL server
rmttask replicat, group reppgext ← Points to the replicat on the PostgreSQL server
table andy.*;

 

Connect to ggsci on the PostgreSQL server and create the initial load replicat.

GGSCI@pgsql> create replicat reppgext, specialrun

GGSCO@pgsql> edit params reppgext
replicat reppgext ← Same name as defined in initial load extract on pgorcl
sourcedefs ./dirdef/andy.def
SETENV ( PGCLIENTENCODING = "UTF8" )
SETENV (ODBCINI="/u01/gghome/odbc.ini" ) ← odbc ini file
SETENV (NLS_LANG="AMERICAN_AMERICA.AL32UTF8")
targetdb pg96db, userid andy, password andy ← pg96db is the driver defined in odbc.ini
discardfile ./dirrpt/oracl.dsc, purge
map andy.* target public.*;  ← Public schema in database andy in PostgreSQL. We could create the andy schema if we wanted.

 

Return to the Oracle server and start the extract:

GGSCI@pgorcl> start extract pgext

 

And tail to the ggserr.log file. If you see ‘access denied’ in the error log, then go to the PostgreSQL server, and do this:

GGSCI@pgsql> stop mgr
Are you sure? Yes
(note: tail ggserr.log to make sure it stops, If necessary kill the process manually)

GGSCI@pgsql> edit params mgr
PORT 7809
ACCESSRULE, PROG *, IPADDR *, ALLOW ← add this line, access rules are a new feature of 12c

GGSCI@pgsl> start mgr

 

Return to the Oracle server and restart the extract:

GGSCI@pgorcl> start extract pgext

 

Return to the PostgreSQL server and verify that data is in the file:

psql> \c andy
psql> select count(1) from emp;
count
-------
12
(1 row)
andy=#

 

The initial load has succeeded.

Now configure change data capture. On the source DB, configure the extract:

GGSCI@pgorcl> edit params extorapg

extract extorapg
userid ggadmin, password ggadmin
rmthost pgsql, mgrport 7809
RMTTRAIL ./dirdat/ep
TABLE andy.*;

GGSCI@pgorcl> add extract extorapg, tranlog, begin now
GGSCI@pgorcl> add exttrail ./dirdat/ep, extract extorapg megabytes 10

 

Now log into the PostgreSQL server and configure the replicat:

GGSCI@pgsql>  edit params reporapg

replicat reporapg
sourcedefs ./dirdef/andy.def
SETENV ( PGCLIENTENCODING = "UTF8" )
SETENV (ODBCINI="/u01/gghome/odbc.ini" )
SETENV (NLS_LANG="AMERICAN_AMERICA.AL32UTF8")
targetdb pg96db, userid andy, password andy
discardfile ./dirrpt/oracl.dsc, purge
map andy.* target public.*;

GGSCI@pgsql>  add replicat reporapg, NODBCHECKPOINT, exttrail ./dirdat/ep

GGSCI@pgsql>  start replicat reporapg

Verify, check row count in PostgreSQL:
psql> select count(1) from emp;
count
-------
12
(1 row)

 

Insert a row in Oracle:

SQLPLUS> insert into emp values (7401, 'ANNE', 'SALESLDY', 7698, trunc(sysdate),95000,10000,20,systimestamp);

SQLPLUS> commit;

 

Select the count from PostgreSQL:

andy=# select count(1) from emp;
count
-------
13
(1 row)

andy=# select * from emp;
empno| ename  |   job    | mgr  |     hiredate        |   sal    |   comm   | deptno |     last_updt_dt_tm
-------+--------+-----------+------+---------------------+----------+----------+--------+----------------------------
7499 | ALLEN  | SALESMAN | 7698 | 1981-02-20 00:00:00 | 1600.00  |   300.00 |     30 | 2017-04-22 14:49:52.147335
7566 | JONES  | MANAGER  | 7839 | 1981-04-02 00:00:00 | 2975.00  |          |     20 | 2017-04-22 14:49:52.147335
7654 | MARTIN | SALESMAN | 7698 | 1981-09-28 00:00:00 | 1250.00  | 1400.00  |     30 | 2017-04-22 14:49:52.147335
7698 | BLAKE  | MANAGER  | 7839 | 1981-05-01 00:00:00 | 2850.00  |          |     30 | 2017-04-22 14:49:52.147335
7782 | CLARK  | MANAGER  | 7839 | 1981-06-09 00:00:00 | 2450.00  |          |     10 | 2017-04-22 14:49:52.147335
7788 | SCOTT  | ANALYST  | 7566 | 1987-04-19 00:00:00 | 3000.00  |          |     20 | 2017-04-22 14:49:52.147335
7839 | KING   | PRESIDENT|      | 1981-11-17 00:00:00 | 5000.00  |          |     10 | 2017-04-22 14:49:52.147335
7844 | TURNER | SALESMAN | 7698 | 1981-09-08 00:00:00 | 1500.00  |     0.00 |     30 | 2017-04-22 14:49:52.147335
7350 | DUMMY2 | SALES    | 7698 | 2017-04-19 00:00:00 | 35000.00 | 8000.00  |     30 | 2017-04-22 14:49:52.147335
7800 | SMARTY | SMARTIT  | 7839 | 2017-04-26 00:00:00 | 90000.00 | 10000.00 |     20 | 2017-04-26 19:53:16.731168
7400 | ANDY   | SALESSTUD| 7698 | 2017-04-23 00:00:00 | 45000.00 | 10000.00 |     20 | 2017-04-23 15:14:10.288964
7410 | ANDY   | TOPSTUD  | 7698 | 2017-04-29 22:44:58 | 80000.00 | 10000.00 |     20 | 2017-04-29 22:44:58.77862
7401 | ANNE   | SALESLDY | 7698 | 2017-05-01 00:00:00 | 95000.00 | 10000.00 |     20 | 2017-05-01 16:53:25.733787 ← Row we added

 

The replication process is working as expected.

We have configured one way replication to a PostgreSQL database.  In part four, we will add a third master database, Oracle version 11.2.0.1, where we cannot use integrated replicat and extract.

Please note: this blog contains code examples provided for your reference. All sample code is provided for illustrative purposes only. Use of information appearing in this blog is solely at your own risk. Please read our full disclaimer for details.

Table of Contents

Related Posts