First published on the Tech Style blog.

When a company moves off Oracle, most of the effort goes into converting the code. The part that hurts later is quieter. PostgreSQL is not Oracle, and in places it computes a different answer to the same expression. There is no error at build time and none in test. A pricing query returns a slightly wrong number, and a customer is the one who finds it.

Paritas is our answer to that. It is PostgreSQL 18 with an Oracle mode that compiles and runs real PL/SQL: packages, triggers, collections and exceptions. It is still in development and not for sale. This post is mostly about how we test it, because that is the part we think matters.

Same code, different answers

Here are three cases, each measured on live engines with Oracle Database 21c as the reference. All three convert cleanly. None raises an error you would see at build time.

  • SELECT 3.4e38f * 10f multiplies binary floats past the top of the type. Oracle returns inf. Stock PostgreSQL refuses the literal, and the nearest equivalent raises "value out of range".
  • SELECT 1 UNION SELECT 2 INTERSECT SELECT 2 has no parentheses. Oracle returns 2. PostgreSQL binds INTERSECT tighter and returns 1 and 2.
  • FOR i IN REVERSE 1 .. 3 LOOP runs 3, 2, 1 on Oracle. PostgreSQL's REVERSE takes the bounds the other way round, so the loop runs no iteration at all.

Paritas gives Oracle's answer in each case. A loop that walks a collection backwards and silently does nothing is exactly the kind of bug that gets through code review. The floating-point fix has since been merged into the upstream open-source project.

The harness came first

Anyone can fork a database. What we built first was the test harness. Oracle Database 21c, stock PostgreSQL and the unmodified open-source base run beside Paritas, and every answer is compared with Oracle's. A difference only counts once a live engine shows it. On 9 October two fixes written straight from Oracle's manual gave the wrong answer on a real Oracle 21c, which is why the manual alone is never the test.

It has three parts:

  • A differential corpus of 764 SQL and PL/SQL cases, each with the answer a real Oracle instance returned. Every verdict is kept from run to run, so a change that fixes one answer and breaks another shows up in the next run.
  • An application gauntlet of 591 units and 92,000 lines of real PL/SQL from nine schemas, among them the Alexandria, Logger, utPLSQL and PL/JSON libraries and Oracle's HR and OE samples, compiled unchanged. It ranks what breaks, so the most common blocker gets fixed first.
  • 170 stress cases on long values, and an AddressSanitizer build in CI.

Where 1.0 stands

The bar for 1.0 was set in advance as eight criteria, each a measurement. These are the figures from mid-October:

  • Real PL/SQL compiling unchanged: 137 of the 253 package-level units that Oracle itself compiles, or 54%. 1.0 needs 90%.
  • Answers matching Oracle: 599 of the 764 corpus cases, or 78%. The unmodified base gets 515. 1.0 needs 95%.
  • Calls into built-in packages that resolve: 1,436 of 2,994 call sites, or 48%. 1.0 needs 70%.
  • Errors behaving like Oracle (the codes handlers catch, SQLCODE, SQLERRM, RAISE_APPLICATION_ERROR): 31 of 38 cases. 1.0 needs all of them.
  • No crash in the stress cases and a clean sanitizer run. One known crash case is open, a PL/SQL SELECT ... INTO of a ROWID, which crashes the unmodified base too. A 24-hour soak with flat memory runs last, on the release candidate.

The other three are paperwork and a release: the list of limitations in a customer copy, runbooks each rehearsed once, and a 1.0.0-rc1 tag that builds from a clean clone. We give no date. It ships when the numbers hold.

Where compatibility ends

Our list of limitations runs to more than 500 entries, each saying what happens and usually what to do instead.

Some things are out of 1.0 by decision: object types with methods, the network and XML packages, DBMS_SCHEDULER and DBMS_JOB, database links and flashback queries. Autonomous transactions are refused when the code compiles, because a fake one would be a trap. Optimizer hints are read as comments.

Others are simply open today. Integer division truncates, so SELECT 10/4 gives 2 where Oracle gives 2.5. GREATEST and LEAST skip a NULL where Oracle returns NULL. LISTAGG and FORALL are not there yet.

We would rather you read that here than find it six months into a project.

It pairs with pgrecon

pgrecon, our free assessment tool, tells you what an Oracle estate contains and roughly what moving it will cost. The Paritas harness tells you which of that code behaves differently on the destination, expression by expression. They already share plumbing: the tables the gauntlet compiles against are extracted and converted by pgrecon.

If you are planning a move off Oracle and would like to see the harness run, ask us through the product page. It is more convincing to watch than to read about.

Paritas is a Tech Style product: tech-style.co/product-paritas. pgrecon is at tech-style.co/product-pgrecon.