Hard sources — not covered by free tools
Also parsed — certify what free tools miss
Runtimes
After migration
Parser-driven modernization of packages, procedures, functions, triggers, and views — including cursor loops, CONNECT BY hierarchies, and BULK COLLECT processing. Full lineage, automated conversion, validated parity.
PL/SQL package parsed into interactive lineage graph
One source, every target. Deterministic parsers read the estate and emit native code for the platform you pick — not PL/SQL wrapped in a compatibility layer.
Oracle PL/SQL estate → MigryX parser → native platforms
Deterministic parseAI where it helpsThe parser is deterministic: the same input produces the same output on every run. Every output is validated against the original, row by row, before go-live.
Cursor loops with per-row DML are normal in PL/SQL and pathological on a columnar warehouse. Lifting them unchanged produces a migration that runs slower than the system it replaced, which is how cloud programmes lose their business case.
Capacity is sized for peak batch windows and paid for continuously, per core, with options scoped separately. Warehouses that separate storage from compute let the batch window scale up and then stop costing money.
Decades of logic sit in package bodies, triggers and views. There is no design document, so the code is the specification, and reading it well enough to reimplement it is the actual project.
Oracle's hierarchical query syntax, with LEVEL, SYS_CONNECT_BY_PATH and ORDER SIBLINGS BY, has no counterpart in modern SQL dialects. It has to be re-expressed as recursion, and that rewrite is mechanical but easy to get subtly wrong.
-- Organisational rollup with CONNECT BY
SELECT employee_id,
manager_id,
LEVEL AS depth,
SYS_CONNECT_BY_PATH(employee_name, '/') AS org_path,
CONNECT_BY_ROOT employee_name AS top_manager,
CONNECT_BY_ISLEAF AS is_leaf
FROM employees
START WITH manager_id IS NULL
CONNECT BY PRIOR employee_id = manager_id
ORDER SIBLINGS BY employee_name;
-- CONNECT BY → recursive CTE
WITH RECURSIVE org AS (
-- START WITH
SELECT employee_id, manager_id, employee_name,
1 AS depth,
'/' || employee_name AS org_path,
employee_name AS top_manager
FROM employees
WHERE manager_id IS NULL
UNION ALL
-- CONNECT BY PRIOR employee_id = manager_id
SELECT e.employee_id, e.manager_id, e.employee_name,
o.depth + 1,
o.org_path || '/' || e.employee_name,
o.top_manager
FROM employees e
JOIN org o ON e.manager_id = o.employee_id
)
SELECT o.*,
-- CONNECT_BY_ISLEAF
CASE WHEN NOT EXISTS (
SELECT 1 FROM employees c
WHERE c.manager_id = o.employee_id)
THEN 1 ELSE 0 END AS is_leaf
FROM org o
ORDER BY org_path;
START WITH becomes the anchor member and CONNECT BY PRIOR becomes the recursive join. LEVEL becomes an incremented depth, the path is accumulated explicitly, and leaf detection becomes a NOT EXISTS test.
Every PL/SQL construct in your packages maps to a defined target equivalent, recorded in the lineage report.
| Oracle PL/SQL Construct | Target Equivalent | Notes |
|---|---|---|
| Package specification and body | Module of related routines | Public and private routines separated |
| Stored procedure | Stored procedure or scripted task | Parameter modes IN, OUT and IN OUT |
| Function | Scalar or table function | Deterministic functions inlined where valid |
| Cursor FOR loop | Set-based statement | Per-row DML rewritten as one operation |
| BULK COLLECT / FORALL | Set-based insert or MERGE | Batching becomes a single statement |
| CONNECT BY hierarchy | RECURSIVE CTE | LEVEL, ROOT, ISLEAF and PATH rewritten |
| Analytic functions | Window functions | Partitioning and framing preserved |
| MERGE statement | MERGE | Matched and not-matched clauses carried over |
| Global temporary table | Temporary or transient table | Session scope mapped to target semantics |
| Collections and records | Structured types or DataFrames | Nested tables and VARRAYs flattened |
| Exception blocks | Explicit error handling | Named exceptions and SQLCODE preserved |
| Trigger | Pipeline step or stream task | Row-level triggers made explicit in the load |
| Sequence | Sequence or identity column | Current value and increment carried over |
| DBMS_OUTPUT / UTL_FILE | Logging and file writes | Server file access flagged for review |
| Dynamic SQL (EXECUTE IMMEDIATE) | Parameterized SQL | Constructed statements reported for review |
MigryX Data Matching compares Oracle output against the new warehouse output, row by row and column by column, with configurable tolerance rules and mismatch drill-down.
See how Data Matching works →Source files are enough. DDL and package source extracted from the data dictionary or from version control is the normal input, so parsing does not require production database access. A connection is only useful later, during parity validation.
They are rewritten as set-based statements wherever the loop body permits it, because carrying the row-at-a-time pattern to a columnar warehouse is the most common reason migrated batch runs are slower than the Oracle original.
Yes, in one of two forms. Logic that is really set manipulation becomes SQL, and logic that is genuinely procedural becomes a stored procedure in the target's own scripting language or a Python task, depending on which target you are moving to.
EXECUTE IMMEDIATE and DBMS_SQL statements are extracted and reported rather than converted blind, because the statement text is assembled at runtime and a parser cannot prove what it will contain.