When a converter says it handles everything, it's either wrong or hiding something. This is what you get when it tells you what it couldn't do.
Every real Oracle estate has objects that nothing converts mechanically. An object type with member methods, a trigger that runs an autonomous transaction, a view built on a feature PostgreSQL doesn't have yet. No tool is to blame for that; twenty years of Oracle development leaves those things behind. The useful question is whether your tool tells you about them before the migration starts, or whether the migration finds them for you.
pgrecon's answer is the residue report: one line for every object it declines to convert, with the object's name and the reason. Nothing disappears quietly. Here are four lines from a real run, wrapped to fit the page:
view CO.PRODUCT_REVIEWS: uses JSON_TABLE, which PostgreSQL adds in
version 17 with different clause syntax; rewrite the view by hand
trigger RECON_TEST.TRG_EMP_AUDIT: runs an autonomous transaction;
redesign with dblink or a queue (line 5)
trigger RECON_TEST.TRG_EMP_SAL_GUARD: compound triggers combine timing
points; restructure as separate triggers by hand
column RECON_TEST.LEGACY_REFS.SCAN_DOC: external file locators have
no counterpart
Each reason is specific enough to act on without opening the source and working the problem out yourself. That's what we aim for with every line: it should tell the reader what to do next, not only that something failed.
A refusal from this week
Every night, a fuzz test in our CI generates 40 random Oracle schemas, converts them, and applies the output to a live PostgreSQL 16 and 18. On September 24, one of those schemas had a view that returned the first ten rows sorted by an XMLTYPE column. pgrecon converted it, ROWNUM became LIMIT, and PostgreSQL rejected the statement, because PostgreSQL has no way to sort xml values. Oracle can't sort XMLType either, so no working Oracle view has this shape. A broken one can still sit in a real dump, though, and a converter has to handle what's actually there.
We fixed it the same day and released it as pgrecon 0.7.2. That view now lands in the residue, wrapped the same way:
view FUZZ.V_3: sorts by AMOUNT, a column that lands as xml;
PostgreSQL cannot sort or compare xml - rewrite by hand
Every refusal in pgrecon works like this. If the converter can't prove a statement will apply, the statement becomes a line someone reads, not an error someone finds at cutover.
What that adds up to
On the eight public schemas in our benchmark, pgrecon 0.7.3 turns 241 of the 594 objects it's answerable for into working DDL, 41 percent. The spread matters more than the average. HR and CO, which look like ordinary business schemas, convert above 95 percent. OE, which is full of object types, sits at 40 percent. The package libraries come last: utPLSQL at 25 percent, PLJSON at 4, and Alexandria, which is almost all packages, at zero. Packages are the one thing nothing converts mechanically today. The other 353 objects are named in the residue, and none are lost. The full numbers, and how they're measured, are on the benchmark page.
On a real estate, the residue list runs to hundreds of lines. Out of context that looks alarming. In context it's the most useful part of the report, because it's the actual scope of the work, visible before anyone commits a budget.
Why we don't hide residue
A tool that quietly emits its best guess for every hard case will always show a shorter "declined" list than pgrecon does. It will also show more broken statements once someone runs its output. We chose the other trade: say "I can't prove this one" every time, rather than ship something that only looks like it worked.
That's also what turns a free assessment into a priced plan. Each residue line is a known unit of work, an object and a reason, which is a far better starting point than "we'll find out during the migration." The paid assessment picks up where the free residue report stops. It triages each line and prices it, and for Oracle packages, which make up most of the residue in almost every hard estate, it converts the part it can prove and prices the rest.
pgrecon 0.7.3 is on PyPI (pip install pgrecon), and the rules for what it declines, and why, are documented at muzzammil242.github.io/pgrecon. If you want your own schema's residue list, triaged and priced for a board, that's what the fixed-price assessment produces: book one at DevCrafter AI.











