Formation MIGORPG
Dalibo SCOP
25.09
5 septembre 2025
| Formation | Formation MIGORPG |
| Titre | Migrer d’Oracle à PostgreSQL |
| Révision | 25.09 |
| ISBN | N/A |
| https://dali.bo/migorpg_pdf | |
| EPUB | https://dali.bo/migorpg_epub |
| HTML | https://dali.bo/migorpg_html |
| Slides | https://dali.bo/migorpg_slides |
Cette formation est sous licence CC-BY-NC-SA. Vous êtes libre de la redistribuer et/ou modifier aux conditions suivantes :
PostgreSQL® Postgres® et le logo Slonik sont des marques déposées par PostgreSQL Community Association of Canada.
Ce document ne couvre que les versions supportées de PostgreSQL au moment de sa rédaction, soit les versions 13 à 17.
Ce module est organisé en quatre parties :
La première migration est importante :
Le projet doit être choisi avec soin :
Souvent moins important :
Avant de pouvoir porter l’application et le PL :
number(18,0), number(4,0) …int : -2147483648 à
+2147483647 (4 octets, number(9,0))bigint : -9223372036854775808 à
9223372036854775807 (8 octets,
number(18,0))numeric(x,0), d’autres en
int/bigint
Veut-on :
Lors de la migration, certaines fonctionnalités d’Oracle auront peu ou pas d’équivalent :
Suivant les réponses aux questions précédentes, vous choisirez :
COPY)blob et clob -> bytea et
textnumber -> int, bigint,
real, double, decimalÉtapes :
ORACLE_HOME et
PATH comprenant sqlplus)libaio (Redhat) ou libaio1 (Debian)Time::HiResPerl DBI > v1.614 et DBD::OracleEn option :
DBD::Pg pour l’import direct dans PostgreSQLCompress::Zlib : compression des fichiers en
sortieDBD::MySQL pour migrer les bases MySQLDBD::ODBC pour migrer les bases Microsoft
SQL ServerPoints essentiels :
N’hésitez pas, c’est le moment !
Ce module est organisé en trois parties :
Étapes de la configuration :
Structure
#)0 pour
désactivé et 1 pour activéIMPORT fichier.confORACLE_HOME /path/.../DEBUG [0|1]LOGFILE /path/.../migration.logORACLE_DSN
dbi:Oracle:host=serveur;sid=INSTANCEdbi:Oracle:TNSNAMEdbi:Oracle://serveur:1521/serviceORACLE_USER (system par défaut)ORACLE_PWD (manager par défaut)SCHEMA
NOM_SCHEMA versus SYSUSERSUSER_GRANTS [0|1]
Cette étape d’exploration permet d’obtenir des informations sur la base Oracle :
Certaines informations sont disponibles immédiatement, sans plus de configuration :
SHOW_VERSION affiche la version de l’instance
Oracle.SHOW_SCHEMA liste les schémas définis sous Oracle.SHOW_TABLE affiche la liste des tables de la base
Oracle.SHOW_COLUMN affiche la liste des colonnes par table
d’une base Oracle.Recherche de l’encodage utilisé par l’instance Oracle :
SHOW_ENCODING : affiche les valeurs utilisées par
Ora2Pg pour
NLS_LANGCLIENT_ENCODINGNLS_LANG
AMERICAN_AMERICA.AL32UTF8French_France.WE8ISO8895P1…NLS_NCHAR
AL32UTF8…CLIENT_ENCODING
utf8, latin1, latin9BINMODE
utf8, rawLe but du fichier de configuration générique est multiple :
Utilisation de fichiers de sortie dédiés
FILE_PER_CONSTRAINT 1FILE_PER_INDEX 1FILE_PER_FKEYS 1FILE_PER_TABLE 1FILE_PER_FUNCTION 1DISABLE_TRIGGERS 1TRUNCATE_TABLE 1DISABLE_SEQUENCE 1COMPILE_SCHEMA [0|1]PLSQL_PGSQL 0Utilisation d’un schéma sous PostgreSQL ?
EXPORT_SCHEMA [0|1]PG_SCHEMA nom_du_shemaCREATE_SCHEMA 0PG_VERSION 9.6PG_VERSION 11BITMAP_AS_GIN : export des index bitmap en
btree_ginSTANDARD_CONFORMING_STRINGS échappement dans les
chaines de caractèresLa base contient des champs de type SDO_GEOMETRY.
AUTODETECT_SPATIAL_TYPE [0|1]DEFAULT_SRID 4326CONVERT_SRID [0|1|N]POSTGIS_SCHEMA schema_nameGEOMETRY_EXTRACT_TYPE [INTERNAL|WKT|WKB]L’export des champs CLOB et BLOB sont contrôlés par :
LONGREADLEN 1047552LONGTRUNCOK 0NO_LOB_LOCATOR 0BLOB_LIMIT 500Étapes :
ora2pg.conf générique
ora2pg
-t type d’export-b répertoire de stockage des fichiers-o nom du fichier de sortie-p conversion automatique du codeUSE_TABLESPACEREPLACE_TABLES ORIG_TABLE1:DEST_TABLE1REPLACE_COLS TABLE1(ORIG_COL1:DEST_COL1, [..])INDEXES_SUFFIX _idxINDEXES_RENAMINGDATA_TYPE NUMBER(*\,0):bigintMODIFY_TYPE TABLE1:COL1:integer TABLE1:COL2:timestamptzREPLACE_AS_BOOLEAN TABLE1:COL1 [..]ora2pg -p -t TRIGGER -o triggers.sql -b schema/triggers -c config/ora2pg.conf
ora2pg -p -t FUNCTION -o functions.sql -b schema/functions -c config/ora2pg.conf
ora2pg -p -t PROCEDURE -o procedures.sql -bschema/procedures -c config/ora2pg.conf
ora2pg -p -t PACKAGE -o packages.sql -b schema/packages -c config/ora2pg.confora2pg -t TYPE -o types.sql -b sources/types -c config/ora2pg.conf
ora2pg -t VIEW -o views.sql -b sources/views -c config/ora2pg.conf
ora2pg -t MVIEW -o mviews.sql -b sources/mviews -c config/ora2pg.conf
ora2pg -t TRIGGER -o triggers.sql -b sources/triggers -c config/ora2pg.conf
ora2pg -t FUNCTION -o functions.sql -b sources/functions -c config/ora2pg.conf
ora2pg -t PROCEDURE -o procedures.sql -b sources/procedures -c config/ora2pg.conf
ora2pg -t PACKAGE -o packages.sql -b sources/packages -c config/ora2pg.confPostgreSQL ne possède pas d’objet de type SYNONYM
search_pathPostgreSQL ne possède pas d’objets de type DIRECTORY ni
de tables EXTERNAL
file_fdw
COPYPostgreSQL ne possède pas d’objets de type
DATABASE LINK
oracle_fdw
DIRECTORY et BFILE
BFILE en donnée bytea
par défautBFILE est redéfini en TEXT,
stockage du chemin du fichier externeexternal_file
EFILE correspondant au type BFILE :
(directory_name, filename)BFILE est redéfini en EFILE
(directive DATA_TYPE)Oracle Index Texte
CONTAINSCATSEARCHEXPORT_SCHEMA est activé
Création des objets du schéma :
psql -U myuser -f schema/tables/tables.sql mydb >> create_mydb.log 2>&1
psql -U myuser -f schema/partitions/partitions.sql mydb >> create_mydb.log 2>&1
psql -U myuser -f schema/views/views.sql mydb >> create_mydb.log 2>&1
psql -U myuser -f schema/tablespaces/tablespaces.sql mydb >> create_mydb.log 2>&1Chargement différé de certains objets :
Bilan de l’export/import du schéma :
CHECK et conditions
des indexCHECK (WYEAR between 0 and 9);CHECK (wyear::integer between 0 and 9);Étapes :
bytea
BLOB et CLOB en
bytea très lentLOBSRID, système spatial de référence
CONVERT_SRID converti la valeur Oracle dans la norme
EPSGDEFAULT_SRID force la valeur du SRID par défautGEOMETRY_EXTRACT_TYPE [WKT|WKB|INTERNAL]Restauration des contraintes, triggers, séquences et index
psql -U myuser -f schema/tables/CONSTRAINTS_tables.sql mygdb >> create_mydb.log 2>&1
psql -U myuser -f schema/tables/INDEXES_tables.sql mygdb >> create_mydb.log 2>&1
psql -U myuser -f schema/sequences/sequences.sql mygdb >> create_mydb.log 2>&1
psql -U myuser -f schema/triggers/triggers.sql mygdb >> create_mydb.log 2>&1COPYCLIENT_ENCODINGDEFAULT_NUMERIC ou
ALTER TABLECLOB, BLOB et XML :
LONGREADLENJOBS NcoresORACLE_COPIES NcoresDEFINED_PKEY EMPLOYEE:IDPARALLEL_TABLES NcoresJOBS x ORACLE_COPIES | PARALLEL_TABLES =
Total Nombre cœursALLOW TABLE1 TABLE2 [..] TABLENEXCLUDE TABLE1 TABLE2 [..] TABLENWHERE TABLE[condition valide] GLOBAL_CONDITIONWHERE TABLE_TEST[ID1='001']WHERE DATE_CREATION > '2001-01-01’REPLACE_QUERY TABLENAME[SQL_QUERY]VACUUM FREEZE préventifN’hésitez pas, c’est le moment !
Ce module est organisé en quatre parties :
Oracle et PostgreSQL sont assez proches :
PostgreSQL et Oracle :
USER.OBJECT
fillfactor" " autour des identifiantsCREATE TYPE) nécessitent une
réécritureDBLINK) n’existent pas sauf sous
forme d’extensions (dblink ou fdw)smallint, integer,
bigintnumeric du standard SQL est appelé
number sous Oraclevarchar2 dans PostgreSQL
varcharvarchar (n) : taille en nombre de caractèrestext équivalent à varchar sans
taille (1 Go maximum)'' équivaut à NULL
'' et NULL sont
distinctsboolean
boolean sous PostgreSQL alors que
vous aurez migré un intlarge objects et fonctions lo_*byteadate : jourtime : heure seuletimestamp : date + heure (alias
timestamptz)YYYY-MM-DD HH24:MI:SS.mmmmmmm+TZ (conforme SQL)interval sous PostgreSQLSYSDATE
localtimestampadd_months,
etc.NLS_DATE_FORMAT (TO_CHAR et
TO_DATE)
DateStylePostgreSQL fournit de nombreux types de données spécialisés :
PERIODip4r…DECODE et
NVLNULLROWNUMROWNUM n’existe pas dans PostgreSQL
row_number() OVER ()ORDER BYROWNUMFROM tab1, tab2 WHERE tab1.col = tab2.colFROM tab1 JOIN tab2 ON (tab1.col = tab2.col)(+) d’Oracle historiqueLEFT JOINRIGHT JOINFULL OUTER JOINFROM t1, t2;FROM t1 CROSS JOIN t2UNION / UNION ALLINTERSECTEXCEPT
MINUSCONNECT BY OracleWITH RECURSIVE PostgreSQLniveau 1/niveau 2/niveau 3niveau initial AS pathprior.path || niveau courantNOCYCLEcycleelement = ANY (tableau) AS cycleWHERE cycle = falseWITH RECURSIVE obligatoire dans PostgreSQLBEGINBEGIN caché)BEGIN TRANSACTION ISOLATION LEVEL xxxx
READ COMMITTEDREPEATABLE READSERIALIZABLESAVEPOINTRELEASE SAVEPOINTROLLBACK TO SAVEPOINTSELECT FOR SHARE/UPDATE
LOCK TABLEN’hésitez pas, c’est le moment !
Oracle et PostgreSQL n’ont pas le même langage PL :
Ce module est organisé en cinq parties :
to_char(1 param), add_month(),
decode()…DBMS_ALERT, DBMS_PIPE,
DBMS_OUTPUT, DBMS_RANDOM et
UTL_FILEnom_sequence.nextval =>
nextval('nom_sequence')dblink ou pg_backgroundRETURN => RETURNSEXECUTE IMMEDIATE => EXECUTESELECT sans INTO =>
PERFORMREVERSE LOOP => inversion des bornesCONNECT BY n’existe pas, utiliser
WITH RECURSIVEREF CURSOR doit être remplacé par
REFCURSORnom_curseur%ROWTYPE doit être remplacé par
RECORDBULK COLLECT => Array:NEW et :OLD => NEW et
OLDUPDATING, INSERTING, DELETING
=> TG_OP (UPDATE, INSERT, DELETE)RETURN NEW impératif dans les triggers
BEFORE, retour implicite sous OracleRECORD,
correspondant à un enregistrement
TABLE FUNCTIONnom_paquet.nom_fonctionPACKAGES ou paquets de procédures stockéesLes noms diffèrent :
NVL() => coalesce()SYSDATE => LOCALTIMESTAMP
CURRENT_TIMESTAMP sans le fuseau
horaireNLSSORT(colname, 'nls_sort=GERMAN') =>
colname COLLATE "de_DE"Les paramètres changent :
to_number(num)
to_number(num, '99...99D99...99')to_date( string1, format_mask, nls_language)
to_date(text, text)replace(a, b)
replace(a, b, ' ')nom.nextval => nextval('nom')nom.currval => currval('nom'):new. en NEW. et
:old. en OLD. dans les triggersINSERTING|DELETING|UPDATING en
TG_OP='INSERT|DELETE|UPDATE' dans les fonctions de
trigger: devant les noms de variable
OracleDBMS_OUTPUT.(put_line|put|new_line)(...) en
RAISE NOTICE '...'FOR … IN … REVERSE min .. maxRAISE EXCEPTIONavec concaténation
|| par le format à la sprintf utilisé par
PostgreSQLROWNUM dans la clause where par des
clauses LIMIT et/ou OFFSETHAVING … GROUP BY (variante acceptée
par Oracle mais pas PostgreSQL) en GROUP BY … HAVINGMINUS par
EXCEPTFROM DUALDEFAULT NULL qui est la valeur par défaut
sous PostgreSQL lorsqu’aucune valeur par défaut n’est préciséeEND fct_name; est réécrit en END;CASE entre le
WHEN et le THEN, non supporté par
PostgreSQLIS NULL et
IS NOT NULL par des instructions à base de
coalesce (pour Oracle, une chaîne vide est équivalente à
NULL)CURSOR moncurseur;
pour les rendre compatibles avec PostgreSQL :
moncurseur CURSOR;IN de la déclaration des
curseurs.EXIT WHEN ...%NOTFOUND par
IF NOT FOUND THEN EXIT; END IF;STRICT aux SELECT … INTO
lorsqu’il y a EXCEPTION … NO_DATA_FOUND ou
TOO_MANY_ROWSREGEX_LIKE( string, pattern ) en
syntaxe avec l’opérateur PostgreSQL de recherche regex
string ~ pattern.SYS_CONTECT('USERENV', ...) en équivalent PostgreSQL.SDO_GEOM.* en
appels aux fonctions PostGis équivalentes.SDO_* en
opérateurs correspondants PostGis.Remplacement de :
STORAGE_ERROR par OUT_OF_MEMORYZERO_DIVIDE par DIVISION_BY_ZEROINVALID_CURSOR par
INVALID_CURSOR_STATESQLCODE par le presque équivalent SQLSTATE
sous PostgreSQLraise_application_error en
RAISE EXCEPTIONRemplacement de :
SYS_REFCURSOR par REFCURSORSQL%NOTFOUND par NOT FOUNDSYS_EXTRACT_UTC par
AT TIME ZONE 'UTC’dup_val_on_index en unique_violationÉtapes :
Non supportées nativement par PostgreSQL, Ora2Pg utilise une fonction de substitution :
_atx_atx au
travers d’un dblinkpg_background à
partir de PostgreSQL 9.5 et Ora2Pg 17.5Absence de certaines fonctions ou paquets de fonctions dans l’export
COMPILE_SCHEMAEXPORT_INVALIDCertains commentaires des paquets de fonctions ne sont pas importés
Valider le portage du code :
Deux actions permettent de tester à minima :
TEST : compare le nombre d’objets et de lignes des deux
bases.
ora2pg -c config/ora2pg.conf -t TESTTEST_VIEW : compare le nombre de lignes retounées par
les vues.
ora2pg -c config/ora2pg.conf -t TEST_VIEWPG_DSN doit être positionné.N’hésitez pas, c’est le moment !