First published on the Tech Style blog.
Most Oracle to PostgreSQL projects are priced before anyone has read the code. Someone counts tables and guesses at the PL/SQL, and the expensive surprises turn up late: package-level state, autonomous transactions, LONG columns, interval partitioning, database links. pgrecon is our free, open-source tool for finding those things first.
It is on GitHub under Apache 2.0 and on PyPI as pgrecon. It needs Python 3.11 or later.
It never connects to your database
That matters, because it decides whether a bank or a hospital will let you run the tool at all. pgrecon writes a plain SQL*Plus script, SELECT only, against the dictionary views. The DBA reads it, runs it with a read-only account and hands back a folder of files. Only files cross the boundary.
pgrecon script --source-version 19
sqlplus readonly_user@service @pgrecon_extract.sql SCHEMA_NAME
pgrecon load dump_dir --db inventory.db
pgrecon report --db inventory.db
The extracting account needs SELECT_CATALOG_ROLE or the equivalent grants. Oracle 11.2 and later use the standard script, which is tested nightly against Oracle XE 21c and Oracle Free 23ai. Older servers, 9.2 to 11.1, get a legacy script that rebuilds DDL from the catalog instead of calling DBMS_METADATA, because on those systems DBMS_METADATA is slow when it works at all.
Findings you can check
The report comes from 80 rules, each with a stable id and its own fixture tests. PL/SQL is parsed with a real grammar, so a finding names the object and the line where the problem sits:
high R-PKG-01 PKG_LEDGER 2 package-level declaration(s), first at line 2
high R-TRG-02 TRG_EMP_AUDIT PRAGMA AUTONOMOUS_TRANSACTION (first at line 5)
high R-TYPE-01 LEGACY_NOTES.BODY LONG
Same dump in, same findings out. An engineer can open the code and see the problem rather than take the tool's word for it. DDL that fails to parse is reported as a finding too, never skipped.
You can try it without an Oracle database. The repository ships a real dump taken from an Oracle XE 21c instance loaded with a deliberately awkward schema, and it produces 58 findings.
An estimate you can argue with
pgrecon estimate turns the findings into person-days, as a sum of named parts: baseline, schema conversion, finding remediation, PL/SQL porting by volume and data movement, with testing on top. On the bundled sample it comes to a range of 106 to 180 person-days, 131 expected.
It is a range on purpose. A single number for a migration is a guess dressed up as a fact. Every run prints its assumptions, and the default rates are conservative. Treat it as a scoping tool, not a quote.
Conversion, where it can be proved
Since version 0.2 the same inventory drives a converter. It writes PostgreSQL DDL for what it can prove: tables (NUMBER stays exact, never a float), keys, checks, indexes, native partitions, views with (+) joins rewritten as ANSI joins, sequences restarted where they were, and standalone functions and procedures whose every construct has an equivalent in PL/pgSQL.
Everything else goes into a residue file, one line per object, naming the construct and the line. Packages, CONNECT BY, autonomous transactions, BULK COLLECT and REF CURSOR interfaces are refused by name rather than guessed at.
Two rules hold. Nothing invalid ships: CI applies the sample conversion to live PostgreSQL 16, 17 and 18 on every commit. Nothing is lost silently: what the converter cannot carry faithfully becomes a residue line, not quietly wrong output.
How big it goes
A synthetic estate of 5,000 tables and 100,000 lines of PL/SQL, one package body alone 16,000 lines long, loads and parses in under 40 seconds on a laptop. The generator is in the repository, so you can repeat the measurement.
Where it stands
pgrecon is alpha. The extraction scripts and the inventory are stable, and the rule catalogue is still growing. If it refuses something in your schema, open an issue with the residue line. Residue never contains data.
pgrecon is a Tech Style product: tech-style.co/product-pgrecon. The code is at github.com/Muzzammil242/pgrecon.
Top comments (0)