A Singer target that loads data into PostgreSQL.
See SPEC.md for the full behavioral specification this implementation follows.
uv sync
uv run target-postgres --config config.json < input.jsonlShow plugin metadata (name, version, and supported capabilities) without reading stdin:
uv run target-postgres --aboutIn addition to RECORD messages, this target accepts Singer BATCH messages whose
encoding.format is "arrow" -- e.g. from tap-mysql or mapper-fivetran. No
configuration is required: any BATCH message pointing at one or more Arrow
IPC file format
files is loaded automatically, using
adbc-driver-postgresql to bulk-ingest the Arrow
data directly into Postgres (no per-row Python materialization). pyarrow and
adbc-driver-postgresql are regular dependencies of this package - unlike some other
ADBC drivers (e.g. MySQL's), adbc-driver-postgresql is a normal pip-installable
wheel, so there's no separate native driver install step.
Manifest files are consumed once: each listed file is read, loaded, and then deleted
from disk (only after the full batch has been successfully loaded, so a failure
partway through leaves the source files intact for a retry). Table DDL/typing still
comes entirely from the stream's SCHEMA message, exactly as for RECORD messages --
Arrow data is matched into columns by name, with no separate Arrow-to-Postgres type
inference. Keyed streams (key_properties set) MERGE-upsert the same way RECORD
batches do; keyless streams append.
A few things worth knowing:
- Flattening/inflection restriction:
data_flattening_max_level > 0andunderscore_camel_case_fieldsare not supported for BATCH-sourced streams whose schema has nested object properties (or whose property names would be renamed by inflection) -- both rename columns away from the raw property names the Arrow file itself uses, and BATCH loading matches columns by name. Loading such a stream via BATCH raises a clear error; setdata_flattening_max_levelto0(the default) andunderscore_camel_case_fieldstofalsefor that stream, or keep it onRECORDmode. _sdc_*metadata columns (add_metadata_columns/hard_delete) are handled per-column for BATCH-sourced records:_sdc_batched_atis populated (a wall-clock constant applied to every row in the batch, refreshed on each subsequent BATCH update of the same rows);_sdc_extracted_atis never populated, since BATCH has no per-recordtime_extractedequivalent to source a value from;_sdc_deleted_atis only populated if a tap's own Arrow schema happens to include that column (nothing target-side prevents it, but the tap-mysql/mapper-fivetran convention this was built against doesn't emit it). Whatever isn't populated is left untouched by BATCH-sourced updates rather than overwritten withNULL, so values from earlierRECORDsyncs survive.- String value representation differs from
RECORDmode:RECORD-sourced string values are stored JSON-quoted (a quirk inherited from the original pipelinewise-target-postgres's CSV/COPYloading path -- seeSPEC.md§15). BATCH/Arrow-sourced string values are stored as plain, unquoted text. This is new functionality with no prior behavior to preserve, and native values are the more correct choice going forward -- just be aware a column populated by bothRECORDandBATCHmessages over time will have this inconsistency.
uv run pytest tests/unit # no DB required
uv run pytest tests/integration # requires Docker (testcontainers)