Create a new default schema in Oracle, customize it and push the data directly from PostgreSQL without intermediate export files by using oracle_fdw (foreign data wrapper).
Current state of this solution
Works as expected, but is very slow because one of the biggest tables asset_feature has a CLOB column and the OCI library doesn't allow PostgreSQL to prefetch the rows. For example, a pull of a 40 Million rows table takes 3 hours. There are different workarounds that are described in our internal PostgreSQL mailing list.
Prerequisites
-
installed, running and empty Oracle database.
-
access as user oracle to the Oracle DB host, as postgres to the PostgresSQL host, as corpus to the censhare Server host.
-
port 1521 on Oracle host should be open for PostgreSQL host
Create the default Oracle schema
We use the standard create-user.sql and create-db.sql scripts from the same censhare Server release whose schema we have to migrate
# get create-db.sql from /opt/corpus/css/database/ on the censhare server
# we have to separate tables from everything else
perl -ne 'print if /^CREATE TABLE/../^\);/ and /^CREATE TABLE/../^ROWDEPENDENCIES;/ '
create-db.sql
> create-db-TABLES.sql
perl -ne 'print if not ( /^CREATE TABLE/../^\);/ and /^CREATE TABLE/../^ROWDEPENDENCIES;/ ) ' create-db.sql > create-db-REST.sql
-- create user and tables
sqlplus / as sysdba
drop user corpus cascade;
@create-user.sql
@create-db-TABLES.sql
-- create helper tables for sequences and custom columns
create table seqtmp(sequence_name varchar(100), last_value number(19), start_value number(19), increment_by number(19));
create table columnstmp (
table_name varchar2(100),
column_name varchar2(100),
data_type varchar2(100),
is_nullable varchar2(100),
character_maximum_length number(10),
numeric_precision number(10)
);
Stop censhare process and prepare the PostgreSQL database
We create a new temporary table to hold sequence values.
# stop censhare process at the censhare Server host.
# create a temporary table to transfer sequence values to Oracle
psql -d corpus -U corpus
create table public.seqtmp(sequence_name varchar(100), last_value bigint, start_value bigint, increment_by bigint);
DO $$
DECLARE
c record;
BEGIN
FOR c IN SELECT * FROM information_schema.sequences where sequence_schema='public'
and sequence_name not like '%rowid%'
LOOP
EXECUTE 'insert into public.seqtmp select '''||c.sequence_name||''', last_value , start_value, increment_by FROM '||c.sequence_name;
END LOOP;
END$$;
commit;
# Help function to help us with customized database schema
CREATE OR REPLACE FUNCTION get_columns(varchar, varchar) RETURNS varchar AS $$
select string_agg('"'||column_name||'"',',') FROM information_schema.columns
WHERE table_name = $1 and table_schema= $2 AND column_name NOT IN('rowid');
$$ LANGUAGE SQL;
Set up and test Oracle Instant Client
Follow the instructions in the corresponding section in Migration Oracle → PostgreSQL.
Set up oracle_fdw and connect PostgreSQL to Oracle
Follow the instructions in the corresponding section in Migration Oracle → PostgreSQL.
Check PostgreSQL for custom TABLES and create them in Oracle
We compare the custom PostgreSQL schema with default Oracle schema. You can exclude other tables in the list:
psql -d corpus -U corpus
SELECT table_name FROM information_schema.tables WHERE table_schema='public' and table_name
not in ('pg_stat_statements')
EXCEPT
SELECT table_name FROM information_schema.tables WHERE table_schema='corpus';
.. create them manually in Oracle. Then check again.
Collect custom COLUMNS in PostgreSQL
Execute the following commands:
psql -d corpus -U corpus
drop table public.columnstmp;
create table public.columnstmp as (
SELECT table_name, column_name FROM information_schema.columns WHERE table_schema='public'
and column_name <> 'rowid' and table_name not in ('pg_stat_statements')
EXCEPT
SELECT table_name, column_name FROM information_schema.columns WHERE table_schema='corpus'
order by 1,2
);
# Get also the data types for these columns
alter table public.columnstmp add data_type varchar;
alter table public.columnstmp add is_nullable varchar;
alter table public.columnstmp add character_maximum_length integer;
alter table public.columnstmp add numeric_precision integer;
UPDATE public.columnstmp SET
data_type = c.data_type,
is_nullable=c.is_nullable,
character_maximum_length=c.character_maximum_length,
numeric_precision=c.numeric_precision
FROM information_schema.columns c
WHERE c.table_name = columnstmp.table_name and c.column_name = columnstmp.column_name;
# transfer the custom columns to Oracle
insert into corpus.columnstmp select * from public.columnstmp;
Create custom COLUMNS in Oracle
Execute the following commands:
# create the missing columns in Oracle
sqlplus corpus/corpus
-- set serveroutput on size 30000;
BEGIN
FOR lst IN (select * from columnstmp order by 1)
LOOP
execute immediate 'alter table '|| lst.table_name ||' ADD ('||
lst.column_name||' '||
CASE lst.data_type
WHEN 'smallint' THEN 'NUMBER(6)'
WHEN 'numeric' THEN 'NUMBER('||COALESCE(lst.numeric_precision,38)||')'
WHEN 'character varying' THEN 'varchar2('||COALESCE(lst.character_maximum_length,4000)||')'
WHEN 'timestamp without time zone' THEN 'TIMESTAMP(6)'
END ||' '||
CASE lst.is_nullable
WHEN 'YES' THEN 'NULL'
WHEN 'NO' THEN 'NOT NULL'
END ||')';
END LOOP;
END;
/
# recreate and import the schema again
psql -d corpus -U corpus
drop schema corpus cascade;
CREATE SCHEMA corpus;
IMPORT FOREIGN SCHEMA "CORPUS" FROM SERVER oradb INTO CORPUS;
# check again for differences
SELECT table_name, column_name FROM information_schema.columns WHERE table_schema='public'
and column_name <> 'rowid' and table_name not in ('pg_stat_statements')
EXCEPT
SELECT table_name, column_name FROM information_schema.columns WHERE table_schema='corpus'
order by 1,2;
Compare the Oracle database schemas (optional)
Do this only if this database was running on Oracle before the migration to PostgreSQL and if you still have access to this database.
# get the BEFORE migration to Postgres state by executing this the OLD oracle DB
sqlplus corpus/corpus
set lines 200 pages 3000
col table_name format a30
col column_name format a30
col nullable format a1
col data_type format a15
spool BEFORE-db.txt
select table_name,column_name, nullable, data_type,data_length,data_precision from user_tab_cols order by 1,2;
spool off
# get the AFTER migration to Postgres state by executing this the NEW oracle DB
.. do the same, but change this line
spool AFTER-db.txt
# copy to some linux VM, make a fullscreen and compare
diff -y -W 230 AFTER-db.txt BEFORE-db.txt | less -S
Push the data from PostgreSQL to Oracle
Execute the following commands:
-- set the prefetch size
DO $$
DECLARE
c record;
BEGIN
FOR c IN SELECT table_name FROM information_schema.tables WHERE table_schema='public' and
table_name not in ('pg_stat_statements')
LOOP
EXECUTE 'alter foreign table corpus.' || c.table_name || ' OPTIONS (prefetch ''10240'');';
END LOOP;
END$$;
-- create a help table
create table public.columns_list as
select table_name, get_columns(table_name,'public') FROM information_schema.tables WHERE table_schema='public';
DO $$
DECLARE
c record;
v timestamp;
BEGIN
FOR c IN SELECT * FROM columns_list where table_name not in ('pg_stat_statements') order by table_name desc
LOOP
select timeofday() into v;
RAISE NOTICE '% BEGIN processing table %', v, c.table_name;
EXECUTE 'insert into corpus.'|| c.table_name ||' (' || c.get_columns || ') select '||
c.get_columns || ' FROM public.'|| c.table_name;
END LOOP;
END$$;
-- collect statistics on Oracle
exec dbms_stats.gather_schema_stats('CORPUS',no_invalidate=>false,cascade=>true,degree=>4);
--- create indexes, constraints etc.
sqlplus corpus/corpus
@create-db-REST.sql