AI

Accuracy vs Speed: How We Balance AI Automation with Human Review

Rakesh Mamidala·Founder & Lead Engineer··7 min read

The Only Number That Matters Is the One You Can Check

Every migration tool advertises automation. Very few tell you what happens to the part it can’t automate. That second number is the one that decides your timeline, because unconverted code doesn’t disappear — it lands on a person, usually late, usually during testing, usually as a surprise.

Our position is simple: a high conversion rate is only useful if the remainder is itemised. So we hold ourselves to a measured gate rather than a slogan.

The 95.5% Gate

We benchmark against a synthetic 4,000-object Oracle dump — tables, indexes, materialized views, partitions, packages and triggers in roughly the proportions a real ERP schema carries. The current run converts 95.5% of objects without human intervention. That benchmark is a script in the repo, not a slide; it runs on every change, and a regression fails the build.

The interesting half is the 4.5%. It breaks down like this:

50x  BITMAP indexes            -> B-tree fallback (semantics differ)
30x  ON COMMIT refresh MVs     -> no PostgreSQL equivalent
20x  FAST refresh MVs          -> depends on Oracle MV logs
20x  INTERVAL partitioning     -> no native equivalent
10x  REFERENCE partitioning    -> rebuild via parent key
10x  REVERSE-key indexes       -> no equivalent
10x  Index-organized tables    -> cluster + B-tree
10x  Autonomous transactions   -> dblink or redesign
20x  misc PL/SQL

None of those are bugs. They are constructs where PostgreSQL genuinely differs from Oracle, and where a mechanical translation would produce something that compiles but behaves differently. That is the worst possible outcome, so we don’t do it.

The Rule: Never Fail Silently

The engineering rule underneath the gate is that unconvertible input must produce a warning, never quiet output. A tool that emits plausible-looking SQL for a construct it didn’t understand has spent your review budget on the wrong file.

A concrete example from our own tree. Oracle allows a routine with no argument list at all:

-- Oracle
CREATE OR REPLACE PROCEDURE bulk_load IS
  TYPE id_array IS TABLE OF NUMBER;
  v_ids id_array;
BEGIN
  ...
END;

PostgreSQL requires () even when there are no parameters, requires a RETURNS clause, and has no collection types at all. A transpiler that quietly hands back something close-but-invalid has actively hurt you. What you should get instead is a converted routine plus an explicit note that the collection type needs a human:

CREATE OR REPLACE FUNCTION bulk_load()
  RETURNS void
  LANGUAGE plpgsql
AS $$
DECLARE
  ...
END;
$$ ;
-- WARNING: TYPE id_array IS TABLE OF NUMBER - PG has no collection types;
--          declare the variable as NUMBER[] and index it with [i] not (i)

Where the Human Belongs

  • Semantic divergence. Oracle treats ’’ as NULL; PostgreSQL does not. No amount of automation decides for you which column meant which — that is a business question.
  • Anything destructive. Dropping, truncating, or overwriting a target requires explicit confirmation. The assistant proposes; it does not execute a destructive action on its own.
  • Performance shape. A converted query is correct long before it is fast. Plans get reviewed after the data lands, with real statistics.
  • The flagged 4.5%. Each item arrives as a discrete, located task rather than a vague “review the output”.

Why This Is Faster, Not Slower

Itemising the remainder sounds like the cautious, slow choice. In practice it is the fast one. A migration’s schedule rarely blows up because conversion took an extra week — it blows up because a defect surfaced in UAT, and the team spent three weeks bisecting which of 4,000 objects was wrong. Every construct the tool flags up front is a defect that never reaches that stage.

See the gap report on your own schema

DBMigrateAIPro assesses free: object inventory, conversion rate, and an itemised list of what needs a human — before you commit to anything.

Related articles