ArpePGSQL — Data types
How PostgreSQL types map to Apache Arrow on the fetch (read) path, and how Arrow types map back to PostgreSQL on the ingest/bind (write) path.
Both directions use the COPY-binary fast path — read via COPY TO / the binary
result format, write via COPY FROM STDIN (binary) — so there is no text round-trip
for the common types. The headline case is NUMERIC, decoded straight from
PostgreSQL's base-10000 binary layout into a decimal128/decimal256 with no
string detour.
SQL type name in the field metadata
Since ArpePGSQL v0.4.3.
Every field the driver returns (query results, ExecuteSchema and
GetTableSchema) carries the column's SQL type in the field metadata key
ARPEPGSQL:type, spelled the way PostgreSQL's format_type spells it (what
psql \d shows): for example integer, character varying(1000),
numeric(10,2), timestamp(3) with time zone or integer[]. Enum and
extension types (such as PostGIS) are named by the server; a domain reports its
base type. On geospatial columns the key sits alongside the geoarrow.wkb
extension metadata.
Read path: PostgreSQL → Arrow
Numeric
| PostgreSQL type | Arrow type | Notes |
|---|---|---|
bool | bool | |
int2 | int16 | |
int4 | int32 | |
int8 | int64 | |
float4 | float32 | |
float8 | float64 | |
oid | int32 | |
numeric(p,s) | decimal128(p,s) (p ≤ 38) · decimal256 (39–76) · utf8 (> 76 / unconstrained, or numeric_format=string) | native base-10000 binary decode, no text round-trip; NaN handling: see NUMERIC special values |
An unconstrained numeric (no declared precision) has no fixed Arrow decimal
width, so it renders as utf8 (canonical PostgreSQL text).
NUMERIC special values
Since ArpePGSQL v0.4.3.
PostgreSQL NUMERIC can hold NaN, and (unconstrained only, PostgreSQL 14+)
Infinity / -Infinity. Arrow decimals cannot represent them, so by default a
NaN read into a decimal128/decimal256 column fails the read with an
error naming the column, rather than silently changing the value:
column "price": NUMERIC NaN/Infinity cannot be represented as decimal128(15,2);
set adbc.arpepgsql.numeric_format=string to read NUMERIC as text (lossless), or
adbc.arpepgsql.numeric_special=null to read NaN as NULL
Two options control this:
| Option | Effect |
|---|---|
adbc.arpepgsql.numeric_format=string | Every NUMERIC column is returned as utf8 in PostgreSQL's text form ("12345.67", "NaN", "Infinity"). Lossless. |
adbc.arpepgsql.numeric_special=null | Keep typed decimals and read NaN as NULL (indistinguishable from a real SQL NULL). |
Unconstrained numeric is always utf8, so its special values come back as text
and never trigger the error.
Character & binary
| PostgreSQL type | Arrow type | Notes |
|---|---|---|
text / varchar / bpchar (char(n)) / name | utf8 | |
uuid | utf8 | 36-char canonical form; lowercase by default, casing via uuid_casing |
json / jsonb | utf8 | canonical PostgreSQL text |
inet / cidr / macaddr | utf8 | canonical PostgreSQL text |
bit / varbit | utf8 | canonical PostgreSQL text |
xml | utf8 | text export |
bytea | binary | raw bytes |
| unknown / exotic OIDs (arrays, ranges, enums, …) | binary | raw wire bytes |
Temporal
| PostgreSQL type | Arrow type | Notes |
|---|---|---|
date | date32 | days since epoch |
time(p) | time32[s|ms] or time64[us] | unit follows the column precision |
timetz | utf8 | text export (Arrow has no time-with-offset type) |
timestamp(p) | timestamp[s|ms|us] | unit follows the column precision |
timestamptz(p) | timestamp[s|ms|us, tz=UTC] | stored UTC instant |
interval | month_day_nano | Arrow's month/day/nanosecond interval |
PostgreSQL's microsecond storage resolution means true Arrow-nanosecond precision is not reachable.
Geospatial (PostGIS)
| PostgreSQL type | Arrow type | Notes |
|---|---|---|
geometry / geography | binary + geoarrow.wkb extension (default) | EWKB passes straight through; see Geospatial types |
Geospatial types
Since ArpePGSQL v0.2.4.
PostGIS geometry / geography columns are read over the COPY-binary path as
EWKB — which is already a valid GeoArrow
geoarrow.wkb encoding, so the bytes pass straight through as Arrow binary
with no server-side ST_AsBinary round-trip. The
adbc.arpepgsql.geospatial database
option controls the Arrow field metadata:
| Value | Output | Notes |
|---|---|---|
geoarrow.wkb (default) | Arrow binary + geoarrow.wkb extension | EWKB plus ARROW:extension:name=geoarrow.wkb and an ARROW:extension:metadata JSON object; read directly by GeoPandas / GeoParquet / DuckDB |
wkb | Arrow binary | EWKB bytes, no extension metadata |
binary | Arrow binary | opaque binary, no extension metadata |
How it works. The geometry/geography type OIDs are resolved per session from
pg_type; when PostGIS is not installed this is a clean no-op. In geoarrow.wkb
mode the extension metadata carries edges: spherical for geography (planar
geometry omits it) and crs: EPSG:<srid> taken from the column's PostGIS
typmod. CRS is emitted only for a typmod-constrained column with an SRID other
than 0/4326 (mirroring the GeoParquet convention); a plain geometry column with
mixed/unconstrained SRID omits it. The SRID embedded in each EWKB value is
preserved regardless of the metadata.
Write path: Arrow → PostgreSQL (ingest & bind)
On adbc_ingest (COPY FROM STDIN, binary) and prepared-statement bind (extended
protocol), Arrow arrays are routed to PostgreSQL types.
Bulk ingest introspects the target table's column OIDs
(pg_catalog.pg_attribute) before writing, so Arrow columns land in the right
receive format even when the source Arrow type is a generic utf8:
- Integers, floats,
bool,decimal,date,time,timestamp(with and without timezone), andbinarymap directly. - Arrow
utf8lands correctly injson,jsonb,inet,cidr,macaddr,bit/varbit, anduuidcolumns (the encoder transforms the text into each type's binary receive format). - Arrow
month_day_nanolands inintervalcolumns.
Both the array-bind and stream-bind ingest paths introspect the target (stream
bind consumes its input one batch at a time). The temp-table same-session fast path is the one exception: it keeps
Arrow-only type inference (no OID transforms), so a utf8 bound into a
jsonb/inet/… column through it is rejected by the server.
Arrow inputs with no PostgreSQL equivalent, and Arrow nanosecond temporal
precision (PostgreSQL tops out at microseconds), are rejected with NOT_IMPLEMENTED.
See also
- Connection → Type rendering — the
uuid_casing,numeric_*andgeospatialoptions - Compatibility — validated PostgreSQL versions
- Examples — reading and ingesting typed data