Skip to main content

From Nothing to 2.8 Million SAP Rows in Ten Minutes

· 9 min read
Joachim Rosskopf
Co-Founder & CEO

Every integration tool has two speeds: the one in the benchmark, and the one you actually experience on the first day. For erpl-rev those two numbers were embarrassingly far apart. It replicates 10 million BSEG-shaped rows in about a minute, and 2.5× faster than that since August. Getting to the point where you could run that benchmark took an afternoon.

Not because anything was hard. Because it was scattered. A type-T destination here. A function group there. Eight Z_DUCKDB_* function modules. Fourteen ABAP objects. A reginfo line in a file only Basis can touch. Four documents, each correct, none of them the whole path.

This post is the whole path, and it is now short enough to time with a stopwatch.

The short version

uvx erpl-rev doctor    # what is missing, and the fix for each
uvx erpl-rev setup # deploy the ABAP, then prove a round trip
uvx erpl-rev # run the server

Everything below is what those three commands actually do, and what happened when I ran them against a stock SAP A4H system with nothing deployed on it.

Step 1 — get the binary (1 second)

There is nothing to install:

$ uvx erpl-rev --help

Commands:
serve (default) Run the RFC server.
doctor Diagnose the SAP-side setup. Never writes.
setup Diagnose, show what will change, do it, verify it.

uvx fetches the wheel and runs it. Wheels are published for Linux x86-64, macOS arm64 and Windows x64, and each one carries DuckDB and nothing else — since the August release the RFC protocol is erpl-proto, our pure-Rust implementation, so there is no SAP NW RFC SDK to download and no ICU libraries to put on a path. If you would rather have it permanently, pip install erpl-rev.

Step 2 — ask what is missing (1 second)

Before changing anything, find out what state the system is in. doctor never writes to SAP:

$ export SAP_USER=DEVELOPER SAP_PASSWORD=...
$ uvx erpl-rev doctor

erpl-rev doctor

[ ok ] erpl-adt available
erpl-adt 2026.08.31
[ ok ] ADT reachable, credentials accepted
localhost:50000 client 001 as DEVELOPER
[ FAIL ] erpl-rev ABAP objects deployed
ZCL_ERPL_REV_UTIL not found
→ Run `erpl-rev setup` to deploy them.
[ ? ] destination ERPL_REV answers (round trip)
skipped: probe class not deployed yet
[ ok ] gateway reachable from this machine
localhost:3300
[ ? ] gateway registration allow-list
cannot be read remotely; proven only by an actual registration

Not ready. Run `erpl-rev setup` to fix what can be fixed
automatically, or `erpl-rev setup --print-runbook` to do it by hand.

Three things in that output are deliberate.

Every failure names its own fix. flutter doctor got this right years ago and it is still rare: a diagnosis that tells you the state but not the remedy has moved the problem, not solved it.

The round trip is skipped, not failed. The probe class is not deployed yet, so there is nothing to conclude. A tool that reported "round trip: FAILED" here would send you looking at gateways and firewalls for a problem that does not exist. Unknown and broken are different answers and deserve different symbols.

The allow-list check admits it cannot know. reginfo is a file on the SAP host; no client can read it. Rather than guess, doctor says so — the only honest proof is an actual registration, which comes later.

Step 3 — set up the SAP side (3 seconds)

$ uvx erpl-rev setup

setup runs the same diagnosis, then shows you exactly what it intends to change before touching anything:

Planned changes

· deploy 14 ABAP objects into $TMP
· create function group ZERPL_REV
· run ZCL_ERPL_REV_SETUP (destination ERPL_REV, gateway sapgw00, registration mode)
· run ZCL_ERPL_REV_MKFM (the eight Z_DUCKDB_* function modules)

Left for a human (setup cannot do these from a client)

· $TMP is a local, non-transportable package and some systems reap it.
Fine to evaluate with; use --package ZERPL_CORE for anything lasting.
· Add the reginfo line from erpl-rev-basis-handout.md and reload it in SMGW.
· Create the RFC communication user and role from the handout, if this
system requires a dedicated one.

Apply these changes to localhost? [y/N]

This is terraform plan applied to an ERP system, and for the same reason: writing to somebody's SAP system is not a small act. --dry-run stops right here. Nothing proceeds without either a terminal confirmation or an explicit --yes, and --non-interactive deliberately does not count as consent — it means "do not prompt me", not "yes, change my ERP".

Say yes, and it works through the list:

  ok   ZDELTA_WM
ok ZIF_ERPL_REV_PROGRESS
ok ZCL_ERPL_REV_TYPEMAP
ok ZCL_ERPL_REV_UTIL
...
ok function group ZERPL_REV
ok ZCL_ERPL_REV_SETUP (destination)
ok ZCL_ERPL_REV_MKFM (function modules)

Wrote erpl-rev-basis-handout.md — the steps that need Basis rights.

Deployed, but the round trip did NOT complete yet.

Read that last line again, because it is the part I care most about.

"Deployed" is not "working"

Fourteen objects were created. The function group exists. Both classruns succeeded. A less careful tool would print a green checkmark and hand you back your prompt — and then you would discover the truth twenty minutes later, in SAP, with an empty SYSTEM_FAILURE and no idea which of six things went wrong.

The only claim worth making is one you can prove. So setup ends by asking ABAP to call back out through the registered destination and return a value. Until that happens it reports exactly what it knows: the objects are there, the round trip has not been demonstrated, and here is why that is probably fine right now (the server is not running yet).

It is also honest about the two things a client genuinely cannot do. The gateway reginfo allow-list is an OS file; the gw/acl_mode profile parameters need an instance restart. setup writes erpl-rev-basis-handout.md, filled in for the system it just diagnosed — the exact line, with your host in it, nothing left to compose:

P TP=ERPL_REV HOST=ingest01.corp ACCESS=localhost CANCEL=localhost
D TP=*

Hand that to whoever holds Basis rights. It also tells them what setup already did, so they can see the whole picture rather than a decontextualised request.

Step 4 — start the server and prove it (4 seconds)

$ uvx erpl-rev &
$ uvx erpl-rev doctor

[ ok ] erpl-rev ABAP objects deployed
[ ok ] destination ERPL_REV answers (round trip)
STFC_CONNECTION returned PONG
[ ok ] gateway reachable from this machine

Ready.

That is the whole SAP-side setup: nine seconds of commands, and the last one is a proof rather than an assertion. Re-run setup now and it says nothing — the SAP side is already set up. and changes nothing.

Step 5 — the actual point: move some data

Now ABAP can call out into DuckDB. Here is a real table on a stock A4H system — REPOSRC, which holds the source code of every ABAP program in the system:

zcl_erpl_rev_util=>replicate(
iv_tab = 'REPOSRC'
iv_target = 'reposrc' ).
REPOSRC rows=2870228 replicated in 118.313846 s

2,870,228 rows in under two minutes, on a laptop-class Docker SAP, through a single RFC destination. No extractor, no staging area, no CSV on a fileshare.

The DuckDB side is a normal DuckDB file:

$ duckdb sap.duckdb
D SELECT count(*) AS rows, count(DISTINCT progname) AS programs FROM reposrc;
┌─────────┬──────────┐
rows │ programs │
├─────────┼──────────┤
28702282870221
└─────────┴──────────┘

D SELECT CNAM AS author, count(*) AS objects
FROM reposrc GROUP BY 1 ORDER BY 2 DESC LIMIT 5;
┌─────────────┬─────────┐
│ author │ objects │
├─────────────┼─────────┤
│ SAP │ 2812379
│ DDIC │ 56050
│ DEVELOPER │ 1628
│ BWDEVELOPER │ 81
│ SAP*51
└─────────────┴─────────┘

Both queries returned in under a second.

Note what DESCRIBE reposrc gives back: PROGNAME and R3STATE are not null and carry the table's real primary key, CDAT/UDAT are DATE, UTIME is TIME, DATALG is INTEGER. The DDIC types were mapped, not flattened into strings — which is the difference between a replica you can query and a dump you have to clean.

And the export everyone actually wants:

COPY (SELECT PROGNAME, R3STATE, SUBC, CNAM, CDAT, UNAM, UDAT, DATALG FROM reposrc)
TO 'reposrc.parquet' (FORMAT PARQUET);

25 MB of Parquet, written in under a second, readable by every tool your data platform already has.

The stopwatch

StepTime
uvx erpl-rev --help1 s
uvx erpl-rev doctor1 s
uvx erpl-rev setup3 s
server start + doctor again4 s
replicate 2.87 M rows of REPOSRC118 s
query it, export Parquet< 2 s
totaljust over two minutes

Ten minutes is the honest budget once you include reading the Basis handout and someone adding the reginfo line. Two minutes is what the machine spends, and almost all of it is moving 2.87 million rows.

What "verified" means here

It would be easy to write that paragraph without having done it. So, precisely: the run above started from an SAP system with all fourteen erpl-rev objects deleted. doctor reported them missing and exited non-zero. setup deployed them, created the destination and the function modules, and proved the round trip. Afterwards the full 13-stage end-to-end suite — data-identity checks comparing every cell against the SAP source, delta replication, trigger-CDC, partitioned parallel loads, CDS views — passed unchanged.

setup alone is enough to bring a wiped system back to that state.

Try it

uvx erpl-rev doctor

It writes nothing. Worst case you learn what your system is missing, in about a second. erpl-rev is on GitHub, and the manual path — for systems where an automated client has no business writing anything — is still documented in full in docs/INSTALL.md.

If it does not work on your system, that is the interesting case and I would like to hear about it. erpl-rev doctor --json produces exactly the output to attach.