<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Saad Akram</title>
    <description>The latest articles on DEV Community by Saad Akram (@saad_akram_44bc637cbd3690).</description>
    <link>https://dev.to/saad_akram_44bc637cbd3690</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4093818%2F11c604a8-4bff-4547-8bc4-15d083cee6c5.jpg</url>
      <title>DEV Community: Saad Akram</title>
      <link>https://dev.to/saad_akram_44bc637cbd3690</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/saad_akram_44bc637cbd3690"/>
    <language>en</language>
    <item>
      <title>Zero Errors Isn't a Benchmark Score. It's a Design Law.</title>
      <dc:creator>Saad Akram</dc:creator>
      <pubDate>Mon, 31 Aug 2026 14:25:06 +0000</pubDate>
      <link>https://dev.to/saad_akram_44bc637cbd3690/zero-errors-isnt-a-benchmark-score-its-a-design-law-d57</link>
      <guid>https://dev.to/saad_akram_44bc637cbd3690/zero-errors-isnt-a-benchmark-score-its-a-design-law-d57</guid>
      <description>&lt;p&gt;Anyone can get lucky once. Here's the actual mechanism that keeps pgrecon's output at zero apply errors release after release — including a bug it found in itself along the way.&lt;/p&gt;

&lt;p&gt;A benchmark result is a snapshot. Run it again next week, on a different schema, and a tool that scored well can score badly — unless zero errors isn't the result of a benchmark at all, but a rule the software refuses to break.&lt;/p&gt;

&lt;p&gt;That's the distinction that matters here. pgrecon's rule is simple to state and expensive to keep: never emit a SQL statement PostgreSQL itself would reject. Not "try to be correct." Not "warn if unsure." Refuse, and name the reason, if the tool can't prove the output is right.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The mechanism, not the marketing&lt;/strong&gt;&lt;br&gt;
Here's what actually enforces that rule: every conversion pgrecon writes gets applied to a real, running PostgreSQL 16 database before it ships — not as a one-off benchmark stunt, but as a standing step in how the tool gets built. If a statement doesn't apply cleanly, it doesn't ship. There's no clever validator catching problems after the fact. The release process itself is the validator.&lt;/p&gt;

&lt;p&gt;That process finds real bugs, and it's more convincing to show one than to describe the discipline in the abstract.&lt;/p&gt;

&lt;p&gt;While converting triggers from a test schema, one of pgrecon's own outputs failed to even create. PostgreSQL refuses, at CREATE TRIGGER time, a WHEN clause that reads the new row on a DELETE trigger — because there is no new row on a delete. Oracle allows exactly this, and quietly evaluates the condition as false, row by row. The fix took under an hour: move the condition out of the WHEN clause and into the trigger body as a guard, so PostgreSQL accepts the CREATE and the trigger still fires on precisely the rows Oracle would have fired on. After the fix, it applied at zero errors and fired correctly under live inserts, updates, and deletes — verified by running them, not assumed from reading the code.&lt;/p&gt;

&lt;p&gt;That's one bug from one afternoon. The same benchmark sweep that produced [the nine-schema comparison in our last post] surfaced several more in the same sitting — an Oracle-specific timestamp function passing through unmapped, an internal index Oracle generates on its own getting emitted as if it were user DDL, arithmetic that silently drifted from Oracle's exact decimal math to floating point. Every one got caught the same way: applied to real PostgreSQL, failed or misbehaved, fixed, reapplied, confirmed. None of them shipped.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What zero errors doesn't claim&lt;/strong&gt;&lt;br&gt;
It's worth being exact here, so here's the line as written for exactly this purpose, unchanged: "Across nine benchmark schemas, pgrecon emitted zero statements PostgreSQL rejected — and everything it cannot prove is a named line in a residue report. That does not mean everything converts: about half of all objects convert mechanically, above 90 percent on business-shaped schemas, and the gap is dominated by Oracle packages and object types, which no tool converts mechanically."&lt;/p&gt;

&lt;p&gt;Zero errors and honest residue are two separate promises, and pgrecon only gets to make the first one by taking the second one seriously. A tool that guesses when it's unsure can post a lower error count simply by refusing less — right up until someone runs the output and finds out which guesses were wrong. The discipline here is refusing more, not less, and proving the difference on a live database every time, not just when a benchmark happens to be watching.&lt;/p&gt;

&lt;p&gt;pgrecon is Apache-2.0: &lt;a href="https://github.com/Muzzammil242/pgrecon" rel="noopener noreferrer"&gt;https://github.com/Muzzammil242/pgrecon&lt;/a&gt; has the source, the benchmark, and the install command. When the free tier hits its limit and you need a real number for what a migration will cost, the fixed-price assessment is the next step — book one at DevCrafter AI(&lt;a href="https://devcrafterai.com/contact/" rel="noopener noreferrer"&gt;https://devcrafterai.com/contact/&lt;/a&gt;).&lt;/p&gt;

</description>
      <category>oracle</category>
      <category>postgres</category>
      <category>database</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Oracle to PostgreSQL Migration Tools: A Live Database Benchmark</title>
      <dc:creator>Saad Akram</dc:creator>
      <pubDate>Tue, 25 Aug 2026 09:30:06 +0000</pubDate>
      <link>https://dev.to/saad_akram_44bc637cbd3690/oracle-to-postgresql-migration-tools-a-live-database-benchmark-1k0p</link>
      <guid>https://dev.to/saad_akram_44bc637cbd3690/oracle-to-postgresql-migration-tools-a-live-database-benchmark-1k0p</guid>
      <description>&lt;p&gt;&lt;strong&gt;We Ran Five Oracle-to-PostgreSQL Converters Against the Same Live Database. Only One Had Zero Errors.&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;Nine schemas, five tools, one PostgreSQL 16 database, and a definition of “error” nobody gets to argue with: a statement PostgreSQL itself rejects.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvoev79pz8qq98nqrqvsb.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvoev79pz8qq98nqrqvsb.jpeg" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every Oracle-to-PostgreSQL converter claims to convert your schema. Almost none of them tell you what happens when you actually run the output against a real database. So we did it for them.&lt;/p&gt;

&lt;p&gt;Over the last two weeks we took nine Oracle schemas — Oracle’s own HR, OE, and CO sample schemas, four well-known open-source PL/SQL projects (utPLSQL, PLJSON, Logger, and Alexandria), and two lab schemas built to be nasty on purpose — and ran every one through five Oracle-to-PostgreSQL converters, including pgrecon, the tool my cofounder Muzzammil has spent the year building. Then we took every tool’s output and applied it, statement by statement, to a live PostgreSQL 16 database.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr9u0ny1os4ce0x6v5qhd.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fr9u0ny1os4ce0x6v5qhd.jpeg" alt=" " width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;An error, in this benchmark, has exactly one definition: a statement PostgreSQL itself rejects. Not a missing feature. Not a stylistic complaint. A CREATE, an ALTER, or an INSERT that the target database refuses to run. That’s a low bar and a hard one — it’s the same bar a real migration hits in production, whether a tool warned you about it or not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The numbers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs7pfuc44a6kvsx29n1ec.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs7pfuc44a6kvsx29n1ec.png" alt=" " width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two of those deserve a sentence each. Ora2Pg’s output file carries a directive that stops the whole load at the first error — understandable behavior for a tool that expects you to fix things as you go, but it meant we had to remove that directive and pre-create an extension its output silently assumes, just to see the real count underneath. EDB Migration Toolkit’s number is generous to it: for a schema with 49 packages, it emitted exactly one statement — CREATE SCHEMA — and printed a success message. Zero attempts reads a lot like zero errors if nobody checks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What “zero” doesn’t mean&lt;/strong&gt;&lt;br&gt;
It doesn’t mean pgrecon converts everything. Across the nine schemas, about half of all objects converted mechanically — above 90 percent on schemas shaped like a normal business database, well below that on the two schemas that exist to showcase Oracle object types and PL/SQL packages, which is exactly the part nothing converts mechanically yet. The gap is real, and it’s named: every object we didn’t convert shows up as one line in a residue report, with a reason, instead of silently vanishing or shipping broken.&lt;/p&gt;

&lt;p&gt;That’s the part of this benchmark that actually matters. A tool that emits nothing invalid but also converts nothing would score zero errors too — the number only means something next to how much got attempted. Which is why we’re publishing the schemas, the tool versions, and the method, not just the table.(&lt;a href="https://muzzammil242.github.io/pgrecon/benchmark.html" rel="noopener noreferrer"&gt;https://muzzammil242.github.io/pgrecon/benchmark.html&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;If a number in there looks wrong to you, that’s a fair reaction — reproduce it. The schemas are public, the tool versions are named, and pgrecon’s own conversion output gets applied to a live PostgreSQL database before every release, not just for this post.&lt;/p&gt;

&lt;p&gt;pgrecon is Apache-2.0 and free to run: &lt;a href="https://github.com/Muzzammil242/pgrecon" rel="noopener noreferrer"&gt;https://github.com/Muzzammil242/pgrecon&lt;/a&gt; has the install command, the full write-up, and the source. If Oracle costs are on your organization’s roadmap this year, a fixed-price assessment is the cheapest way to find out what a real migration would cost — book one at DevCrafter AI.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftkw4qzcfj65b8f0jgbyb.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftkw4qzcfj65b8f0jgbyb.jpeg" alt=" " width="800" height="256"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>oracle</category>
      <category>postgressql</category>
      <category>postgres</category>
      <category>database</category>
    </item>
  </channel>
</rss>
