tap-diffgen
tap-diffgen is a generic Singer tap for the "diffgen" pattern: an application-level audit-log table that journals every INSERT/UPDATE/DELETE made to other tables in the same database (columns: id, tablename, table_id, op, ts_created). Instead of reading a target table directly, the tap replays that log since the last bookmark and, per event:
i/u(insert/update): repulls the affected row fresh from the target table and emits it withis_deleted: false.d(delete): emits a tombstone -- just the primary key,is_deleted: true, every other business columnnull.
Every emitted record also carries _sdc_diffgen_ts_created (the diffgen event's own timestamp), regardless of op. This is what a plain timestamp-filtered incremental extraction can never represent on its own: a row that no longer exists doesn't show up in a WHERE updated_at > ... query, so deletes silently vanish downstream. Built with the Meltano Singer SDK, supporting PostgreSQL and MySQL.
Installation
pip install plinxore-tap-diffgen
Configuration
Connection settings are global to the tap -- one tap instance targets one database, shared by every configured table (a single connector is built once per run, not one per table).
| Setting | Required | Description |
|---|---|---|
sql_dialect |
yes | postgres or mysql. |
host |
yes | Database host. |
port |
no | Database port. Defaults to the dialect's standard port (5432 for postgres, 3306 for mysql) if omitted. |
user |
yes | Database user. |
password |
yes | Database password. |
database |
yes | Database name. |
schema |
no | Schema to look up the diffgen and target tables under. Postgres only (defaults to public if omitted); ignored for MySQL, where database already plays this role. |
tables |
yes | One entry per target table to replicate via diffgen -- see below. |
tables
Each entry is an object:
| Field | Required | Description |
|---|---|---|
table_name |
yes | The target table to replicate -- also the diffgen tablename filter value. |
pk_column |
yes | The target table's primary key column. |
mode |
no (default full) |
full repulls inserts/updates fresh and tombstones deletes. tombstones_only emits deletes only -- for a table that already has a separate, conventional incremental tap and just needs the DELETE gap filled. |
{
"sql_dialect": "postgres",
"host": "db.example.com",
"port": 5432,
"user": "diffgen_reader",
"password": "...",
"database": "app",
"tables": [
{ "table_name": "invoices", "pk_column": "id", "mode": "full" },
{ "table_name": "orders", "pk_column": "id", "mode": "tombstones_only" }
]
}
The stream's JSON Schema is derived by introspecting the target table's own columns (information_schema.columns), plus is_deleted (boolean) and _sdc_diffgen_ts_created (date-time) -- never anything from the diffgen table itself (op, tablename, ...).
Configuration via environment variables
Copy .env.example to .env and fill in the real values (never committed). Meltano convention: TAP_DIFFGEN_<SETTING_NAME> in uppercase, e.g. TAP_DIFFGEN_PASSWORD.
The full list of settings is available via:
tap-diffgen --about
Usage
Direct CLI (without Meltano)
tap-diffgen --config config.json --discover > catalog.json
tap-diffgen --config config.json --catalog catalog.json --state state.json
Via Meltano (recommended)
# Install the Meltano CLI (if not already done)
pipx install meltano
# Install the plugins declared in meltano.yml
meltano install
# Check the config
meltano config tap-diffgen list
meltano config tap-diffgen test
# Run the pipeline
meltano run tap-diffgen target-jsonl
Meltano automatically manages state (bookmarks) between runs via its own system database -- no need to manually handle a state.json file.
Development
Tests
Unit tests (fake connector, no database) run standalone:
uv run pytest tests/test_client.py tests/test_tap.py tests/test_connectors_base.py
Connector and end-to-end tests need real Postgres/MySQL instances -- they skip cleanly (not a failure) if none is reachable:
docker compose -f tests/fixtures/docker-compose.yml up --wait
uv run pytest
docker compose -f tests/fixtures/docker-compose.yml down -v
Notable implementation details
- Bookmark: the replication cursor is diffgen's own
id, which never appears on an emitted record (records mirror the target table's columns, not diffgen's) -- so the SDK's usual per-record auto-advance is neutralized (DiffgenStream._increment_stream_stateis a no-op) and the bookmark is instead advanced manually, once per processed page, to that page's max diffgenid. Batches are always committed in ascendingidorder, so an interrupted run's last-committed bookmark is always a safe resume point. - Dedup: within one page, the same row can appear more than once in diffgen (e.g. two updates, or an insert followed by a delete) -- only the latest event per
table_iddrives a repull/tombstone. - Decimal precision:
NUMERIC/DECIMALcolumns are mapped to a JSON SchemamultipleOfderived from the column's scale, the same convention target-s3 uses for its Parquetdecimal128mapping -- a value introspected here and later written to Parquet round-trips exact, not through afloat64approximation. - MySQL
BOOLEAN: stored asTINYINT(1), indistinguishable from a genuine small integer bydata_typealone -- schema introspection checkscolumn_typetoo, to avoid misclassifying every boolean column as an integer. - Read-only connections use autocommit: found the hard way, via a real end-to-end test against a real, long-lived Postgres instance -- without it, every
SELECTleft an open transaction that could block DDL on the source database indefinitely.
See the code in tap_diffgen/client.py (stream logic) and tap_diffgen/connectors/ (Postgres/MySQL implementations) for details.
SDK Dev Guide
See the Meltano Singer SDK dev guide for more information.
Metadata
Release files for plinxore-tap-diffgen 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| plinxore_tap_diffgen-0.1.0.tar.gz | 38.2 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| plinxore_tap_diffgen-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 65.5 kB
Release files / plinxore_tap_diffgen-0.1.0.tar.gz
| Download URL | plinxore_tap_diffgen-0.1.0.tar.gz |
|---|---|
| Size | 38.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b9c8b0f901f150150ec27672d80b3db38097e15cc42a6d15f6fa51b6061ef766
|
|
BLAKE2b-256 checksum How to use checksums |
0f65df6f6fba019f352f9565c8716f065d32bcfa3e55b56dd1af444d91fe2c69
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Release files / plinxore_tap_diffgen-0.1.0-py3-none-any.whl
| Download URL | plinxore_tap_diffgen-0.1.0-py3-none-any.whl |
|---|---|
| Size | 27.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
6878b25f2a6c19d6176214be53b7c1b0eb48c6b965a74e506dd4552f02005819
|
|
BLAKE2b-256 checksum How to use checksums |
2752c65e73ef4aec5ad14b45da77fb9c748fe7a5bd2fb9429ccd2052d64efc8c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Security for plinxore-tap-diffgen 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.