Skip to main content

Cross-database materialisations for Postgres

Project description

pg-xmat

Cross-database materialisations for PostgreSQL.

pg-xmat lets you define jobs for transforming and streaming data between different database servers.

It is a good choice when more than one of the following are requirements:

  • one-off/batch jobs
  • moving data between database instances
  • use-cases not suitable for FDWs
  • programmatic use

Installation

pip install pg-xmat

Quick Start

  1. Define jobs (pg_xmat_jobs.py):
import os

SOURCE_DB_URL = os.getenv("SOURCE_DB_URL")
TARGET_DB_URL = os.getenv("TARGET_DB_URL")

JOBS = {
    "export:users": {
        "source": {"database": SOURCE_DB_URL, "schema": "public"},
        "target": {"database": TARGET_DB_URL, "schema": "staging"},
        "tables": {
            "users": {"where": {"active": True}},
            "profiles": {
                "where": {"user_id": [1, 2, 3, 4]}}
                "select": { "inserted_at": "current_date" },
            }
        }
    }
}
  1. Run the job:
pg-xmat "export:users"

# or run all jobs matching glob expression
pg-xmat "export:*"

How it works

  1. Schema Preparation: Drops and recreates target schema, then replicates table structures from source
  2. Query Building: Constructs filtered SELECT queries based on your where and select configurations
  3. Streaming Transfer: Uses PostgreSQL's COPY command to stream data directly between databases
  4. Constraint Replication: Copies indexes, foreign keys, and constraints using pg_dump --section=post-data

The process is designed to be fast and maintain data integrity by leveraging PostgreSQL's native bulk operations rather than row-by-row processing.

CLI Usage

pg-xmat [job_pattern] [options]

Arguments:
  job_pattern           Glob pattern to match job names (e.g., "export:*", "mat:users")

Options:
  -c, --config FILE     Path to configuration file (default: pg_xmat_jobs.py)
  -v, --verbose         Enable verbose logging
  -h, --help            Show help message

Job API

Schema

JOBS = {
    "job_name": {
        "source": {
            "database": "postgresql://...",  # Source database URL
            "schema": "schema_name"          # Source schema name
        },
        "target": {
            "database": "postgresql://...",  # Target database URL
            "schema": "schema_name"          # Target schema name
        },
        "tables": {
            "table_name": {
                "where": {...},     # Optional: Filter conditions
                "select": {...}     # Optional: Column transformations
            }
        }
    }
}

Where Filters

Filter data during transfer using various condition types:

"where": {
    # Exact match
    "status": "active",

    # IN clause (list/tuple)
    "user_id": [1, 2, 3, 4],
    "category": ("A", "B", "C"),

    # Range queries (dict)
    "created_at": {"gte": "2023-01-01", "lte": "2023-12-31"},
    "price": {"gte": 100},
    "score": {"lte": 90},

    # Boolean values
    "is_active": True,
    "is_deleted": False,
}

Select Transformations

Transform columns during transfer using SQL expressions:

"select": {
    "created_at": "DATE({column_name})",                   # Extract date part
    "full_name": "CONCAT(first_name, ' ', last_name)",     # Concatenate columns
    "shifted_date": "{column_name} + INTERVAL '30 days'",  # Date arithmetic
    "normalized_email": "LOWER(TRIM({column_name}))"       # Text normalization
}

The {column_name} placeholder is automatically replaced with the properly quoted column name.

Wildcard Tables

Process all tables in a schema:

"tables": {
    "*": {
        "where": {"tenant_id": 123}  # Applied to all tables that have this column
    }
}

Python API

Basic Usage

from pg_xmat import run_job, run_jobs

# Run a single job
job_config = {
    "source": {"database": "postgresql://...", "schema": "public"},
    "target": {"database": "postgresql://...", "schema": "staging"},
    "tables": {"users": {"where": {"active": True}}}
}
run_job(job_config, verbose=True)

# Run multiple jobs with pattern matching
jobs_config = {"export:users": job_config, "export:orders": {...}}
run_jobs("export:*", jobs_config, verbose=True)

Examples

Data Migration

MIGRATION_JOB = {
    "source": {"database": PROD_DB_URL, "schema": "public"},
    "target": {"database": STAGING_DB_URL, "schema": "public"},
    "tables": {
        "users": {"where": {"created_at": {"gte": "2023-01-01"}}},
        "orders": {"where": {"status": ["completed", "shipped"]}},
        "products": {"where": {"active": True}}
    }
}

Data Anonymization

ANONYMIZE_JOB = {
    "source": {"database": PROD_DB_URL, "schema": "public"},
    "target": {"database": TEST_DB_URL, "schema": "public"},
    "tables": {
        "users": {
            "select": {
                # Replace real emails with user1@example.com, user2@example.com, etc.
                "email": "'user' || id || '@example.com'",
                # Generate fake phone numbers like +15550000001, +15550000002, etc.
                "phone": "'+1555' || LPAD(id::text, 7, '0')",
                # Replace real names with "Test User 1", "Test User 2", etc.
                "name": "'Test User ' || id"
            }
        }
    }
}

Time-shifted Data

SHIFT_JOB = {
    "source": {"database": PROD_DB_URL, "schema": "events"},
    "target": {"database": TEST_DB_URL, "schema": "events"},
    "tables": {
        "*": {
            "select": {
                "created_at": "{column_name} + (CURRENT_DATE - DATE '2023-06-01')",
                "updated_at": "{column_name} + (CURRENT_DATE - DATE '2023-06-01')"
            }
        }
    }
}

Multi-tenant Data Extraction

TENANT_EXPORT = {
    "source": {"database": MAIN_DB_URL, "schema": "public"},
    "target": {"database": TENANT_DB_URL, "schema": "tenant_123"},
    "tables": {
        "users": {"where": {"tenant_id": 123}},
        "orders": {"where": {"tenant_id": 123, "status": ["active", "pending"]}},
        "analytics": {"where": {"tenant_id": 123, "date": {"gte": "2023-01-01"}}}
    }
}

Requirements

  • Python 3.7+
  • PostgreSQL client tools (psql, pg_dump)
  • Network access between source and target databases

Security

  • Database passwords are automatically redacted in log output
  • SQL identifiers are properly quoted to prevent injection
  • Environment variables recommended for sensitive connection strings

License

MIT License

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pg_xmat-0.1.2.tar.gz (9.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pg_xmat-0.1.2-py3-none-any.whl (10.1 kB view details)

Uploaded Python 3

File details

Details for the file pg_xmat-0.1.2.tar.gz.

File metadata

  • Download URL: pg_xmat-0.1.2.tar.gz
  • Upload date:
  • Size: 9.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.2

File hashes

Hashes for pg_xmat-0.1.2.tar.gz
Algorithm Hash digest
SHA256 1ef89b98ad2c84a9f91f7b33fe2812dc4c427adc7f8def3512085489d47f1e01
MD5 27eabb1424056723963dc9441b3afb60
BLAKE2b-256 8663f6d9cf710fd7159498cfd147a4d6339579c0b1b13e9f91b391ad898d8d7a

See more details on using hashes here.

File details

Details for the file pg_xmat-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: pg_xmat-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 10.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.12.2

File hashes

Hashes for pg_xmat-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 fc1181bf97046f1435f920df323464335bc3c79ddc89c9a8d095da8e0317980d
MD5 68495dca985d54da28310560530687c4
BLAKE2b-256 fafa4adb9d56525b30b018e613a6acf9f08da594cae66b5743630aa806eb425c

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page