Skip to content

Repository files navigation

pg_stage_rs

RustLicense: MITRust

RU - documentation

Streaming anonymizer for PostgreSQL dumps. Supports both plain text (-Fp) and custom binary (-Fc) formats.

Reads a pg_dump output from stdin, applies data mutations defined via COMMENT ON COLUMN/TABLE statements, and writes the anonymized dump to stdout.

Features

  • Streaming architecture -- processes data line-by-line without loading the entire dump into memory
  • Plain (-Fp) and Custom (-Fc) format support with auto-detection
  • 25+ mutation types: names, emails, phones, addresses, UUIDs, numerics, dates, IPs, masks
  • Referential integrity via relation tracking across tables
  • Conditions -- apply mutations only when column values match specified criteria
  • Unique value generation with configurable retry limits
  • Deterministic obfuscation for phone numbers (HMAC-SHA256)
  • Locale support: English and Russian (names, patronymics, addresses)
  • Table deletion by name or regex pattern

Installation

Install rust and cargo, then run:

curl https://sh.rustup.rs -sSf | sh

Then install pg_stage_rs via cargo:

cargo install --git https://github.com/ak4code/pg_stage_rs

Usage

# Plain format (auto-detected)
pg_dump -Fp mydb | pg_stage_rs > anonymized.sql
# Custom format (auto-detected)
pg_dump -Fc mydb | pg_stage_rs > anonymized.dump
# Explicit format, Russian locale
pg_dump -Fp mydb | pg_stage_rs --locale ru --format plain > anonymized.sql
# Delete specific tables by regex
pg_dump -Fp mydb | pg_stage_rs --delete-table-pattern "^audit_.*"> anonymized.sql
# Verbose mode (show dump metadata)
pg_dump -Fc mydb | pg_stage_rs --verbose > anonymized.dump
# Output to stderr:# [INFO] pg_dump format version: 1.16.0# [INFO] Compression: Zlib# [INFO] Database: "mydb"# [INFO] TOC entries: 1234

CLI Options

OptionDefaultDescription
-l, --localeenLocale for generated data (en, ru)
-d, --delimiter\tColumn delimiter character
-f, --formatautoForce format: plain/p, custom/c
-v, --verboseoffShow dump info: format version, compression, TOC count, parse warnings
--delete-table-pattern--Regex pattern for tables to remove (repeatable)
--rules-file--Path to JSON file with regex-based pattern rules (see "Pattern Rules File")
--zstd-level1Zstd compression level for output dump (1-22)
--zstd-threads0Zstd compression threads (0 = auto-detect CPU count)
--strictoffFail-fast prefix (error: instead of warning:) for invalid anon: JSON in COMMENTs

Defining Mutations

Mutations are configured as JSON embedded in PostgreSQL column/table comments. Add them to your schema before dumping:

Column-level mutations

COMMENT ON COLUMN public.users.email IS 'anon: [ { "mutation_name": "email", "mutation_kwargs": {"unique": true}, "conditions": [], "relations": [] }]';

Conditional mutations

Apply different mutations based on column values:

COMMENT ON COLUMN public.users.email IS 'anon: [ { "mutation_name": "email", "mutation_kwargs": {"unique": true}, "conditions": [ {"column_name": "role", "operation": "equal", "value": "user"} ], "relations": [] }, { "mutation_name": "fixed_value", "mutation_kwargs": {"value": "admin@company.com"}, "conditions": [ {"column_name": "role", "operation": "equal", "value": "admin"} ], "relations": [] }]';

Relation tracking (FK consistency)

Ensure the same FK value always maps to the same obfuscated value:

COMMENT ON COLUMN public.orders.customer_email IS 'anon: [ { "mutation_name": "email", "mutation_kwargs": {"unique": true}, "conditions": [], "relations": [ { "table_name": "users", "column_name": "email", "from_column_name": "user_id", "to_column_name": "id" } ] }]';

Table-level deletion

COMMENT ON TABLE public.audit_log IS 'anon: {"mutation_name": "delete"}';

Pattern Rules File (--rules-file)

Alternative to COMMENT ON COLUMN/TABLE: a JSON file with regex-based rules. Useful when you can't (or don't want to) modify the source schema, or when the same rules should apply to multiple databases.

Rules file format:

{
"table_patterns": [
{ "table": "<regex on schema.table>", "mutation": { "mutation_name": "delete" } }
],
"column_patterns": [
{
"table": "<regex on schema.table>",
"column": "<regex on column name>",
"mutations": [ /* same MutationSpec array as in COMMENT */ ]
}
]
}
  • table_patterns — table-level rules. Currently only delete is meaningful (equivalent to --delete-table-pattern, just expressed in JSON).
  • column_patterns — same MutationSpec shape as in COMMENT ON COLUMN, attached to columns whose schema.table and column name both match the given regexes. Rules from the file add to any rules already declared via COMMENT — they do not override them.

The full name compared is always schema.table (with schema prefix). Anchor your regexes (^...$) — bare users will also match users_archive.

Errors in the rules file (invalid JSON, bad regex, unknown mutation name) abort the run regardless of --strict/--verbose.

Example:

{
"table_patterns": [
{ "table": "^public\\.(audit_log|temp_.*)$",
"mutation": { "mutation_name": "delete" } }
],
"column_patterns": [
{
"table": "^public\\.users$",
"column": "^email$",
"mutations": [{
"mutation_name": "email",
"mutation_kwargs": {"unique": true},
"conditions": [], "relations": []
}]
},
{
"table": "^public\\..*$",
"column": "^(phone|mobile|.*_phone)$",
"mutations": [{
"mutation_name": "phone_number",
"mutation_kwargs": {"mask": "+7XXXXXXXXXX"},
"conditions": [], "relations": []
}]
}
]
}
pg_dump -Fc mydb | pg_stage_rs --rules-file rules.json > out.dump

Available Mutations

Names

MutationParametersDescription
first_nameuniqueRandom first name
last_nameuniqueRandom last name
full_nameuniqueFull name (RU: last + first + patronymic)
middle_nameuniquePatronymic (Russian locale only)

Contact

MutationParametersDescription
emailuniqueGenerated email address
phone_numbermask, uniquePhone by mask (X/# = digit)
addressuniqueFull postal address
deterministic_phone_numberobfuscated_numbers_countHMAC-based phone obfuscation

Numeric

MutationParametersDescription
numeric_smallintstart, end, uniquei16 range
numeric_integerstart, end, uniquei32 range
numeric_bigintstart, end, uniquei64 range
numeric_smallserialstart, end, unique1..i16
numeric_serialstart, end, unique1..i32
numeric_bigserialstart, end, unique1..i64
numeric_decimalstart, end, precision, uniqueFloat with precision
numeric_realstart, end, uniqueFloat, 6 decimal places
numeric_double_precisionstart, end, uniqueFloat, 15 decimal places

DateTime

MutationParametersDescription
datestart, end, date_format, uniqueRandom date in year range

Network

MutationParametersDescription
urimax_length, uniqueRandom HTTPS URI
ipv4uniqueRandom IPv4 address
ipv6uniqueRandom IPv6 address

Identity

MutationParametersDescription
uuid4--Random UUID v4
uuid5_by_source_valuenamespace, source_columnDeterministic UUID v5

Simple

MutationParametersDescription
null--PostgreSQL NULL (\N)
empty_string--Empty string
fixed_valuevalueStatic value
random_choicechoicesRandom pick from array

Mask

MutationParametersDescription
string_by_maskmask, char, digit, uniqueTemplate: @=letter, #=digit

JSON

MutationParametersDescription
json_updatemap of key → nested mutation specPartially updates a JSON object column. Each value is {"mutation_name": ..., "mutation_kwargs": ...}. mutation_name: "delete" clears the value (sets it to "") — the key stays. Missing keys are skipped — the mutation is not applied and the key is not added. Nested mutation output is inserted as a JSON string (or null when it returns \N).

Example:

COMMENT ON COLUMN public.users.meta IS 'anon: [{ "mutation_name": "json_update", "mutation_kwargs": { "name": {"mutation_name": "first_name"}, "secret": {"mutation_name": "delete"} }}]';

Condition Operations

OperationDescription
equalExact string match
not_equalString inequality
by_patternRegex match

Environment Variables

VariableUsed byDescription
SECRET_KEYdeterministic_phone_numberHMAC key for deterministic obfuscation
SECRET_KEY_NONCEdeterministic_phone_numberNonce appended to input before hashing

Supported PostgreSQL Versions

Custom format (-Fc) support covers pg_dump format versions 1.12.0 -- 1.16.0.

Running Tests

cargo test

License

MIT

About

Streaming anonymizer for PostgreSQL dumps. Supports both plain text (`-Fp`) and custom binary (`-Fc`) formats.

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages