Skip to main content

adbc-driver-oracle

An Apache Arrow ADBC driver for Oracle Database — pure Go, no Oracle Client libraries required.

CI Go Reference Go Version Supported Python Versions PyPI version PyPI Downloads License

Speaks Oracle's native TNS/TTC wire protocol directly from Go and returns Apache Arrow RecordBatches straight from the result set — the way python-oracledb's "thin mode" does, but for the whole ADBC ecosystem. No Instant Client, no ORACLE_HOME, no LD_LIBRARY_PATH. Just pip install. Supports the standard ADBC bulk-ingest path (Statement.BindStream → array-bind INSERT) for fast Arrow → Oracle loads.

Distributed as:

  • a Go module — github.com/gizmodata/adbc-driver-oracle
  • a pip install adbc-driver-oracle wheel for Python (macOS / Linux / Windows × x64 / arm64)
  • a c-shared library (libadbc_driver_oracle.{so,dylib,dll}) attached to each GitHub Release for C / C++ / Rust / R / driver-manifest consumers

Status: Alpha. Tested against Oracle Database 23ai Free; the wire protocol targets Oracle 12.1 and later (the same range as python-oracledb thin mode). See Limitations for what is not covered yet.

Quickstart

1. Have an Oracle Database handy

For local development the gvenzl/oracle-free container is the fastest route (x86_64 and arm64):

docker run --name oracle-adbc-test -d -p 1521:1521 \
  -e ORACLE_PASSWORD=tiger -e APP_USER=scott -e APP_USER_PASSWORD=tiger \
  gvenzl/oracle-free:23-slim-faststart

That gives you oracle://scott:tiger@localhost:1521/FREEPDB1.

2. Install the driver

Python:

pip install adbc-driver-oracle

Go:

go get github.com/gizmodata/adbc-driver-oracle@latest

3. Connect and query

import adbc_driver_oracle.dbapi as oracle
import pyarrow

with oracle.connect(
    uri="oracle://scott:tiger@localhost:1521/FREEPDB1",
) as conn, conn.cursor() as cur:
    cur.execute("SELECT 42 AS answer, 'hello oracle' AS greeting FROM DUAL")
    table: pyarrow.Table = cur.fetch_arrow_table()
    print(table)

The result is a real pyarrow.Table — pass it straight to Polars, Pandas, DuckDB, ibis, or anything else that consumes Arrow:

import polars as pl
df = pl.from_arrow(table)

Prefer to keep credentials out of the URI? Pass them as options:

with oracle.connect(
    uri="oracle://localhost:1521/FREEPDB1",
    db_kwargs={"username": "scott", "password": "tiger"},
) as conn:
    ...

Alternative: drive adbc_driver_manager directly

If you prefer the adbc-quickstarts idiom — passing the driver to adbc_driver_manager.dbapi.connect rather than going through our wrapper — point at the bundled shared library via _driver_path():

from adbc_driver_manager import dbapi
import adbc_driver_oracle

with dbapi.connect(
    driver=adbc_driver_oracle._driver_path(),
    entrypoint="OracleDriverInit",
    db_kwargs={"uri": "oracle://scott:tiger@localhost:1521/FREEPDB1"},
) as conn, conn.cursor() as cur:
    cur.execute("SELECT 42 AS answer FROM DUAL")
    table = cur.fetch_arrow_table()

Streaming large result sets

Cursor.fetch_record_batch() returns a pyarrow.RecordBatchReader that pulls rows from the server one batch at a time. Memory stays bounded by adbc.oracle.batch_size even when the result is millions of rows:

with conn.cursor() as cur:
    cur.execute("SELECT * FROM sales.orders")  # arbitrary size
    reader = cur.fetch_record_batch()
    for batch in reader:
        process(batch)

Oracle → GizmoSQL (or any ADBC target), ADBC to ADBC

The reader above can be handed straight to another driver's bulk ingest, so a table moves from Oracle into GizmoSQL without ever being materialised on the client — no pandas, no ODBC, no Oracle Client:

import adbc_driver_oracle.dbapi as oracle
import adbc_driver_gizmosql.dbapi as gizmosql

with oracle.connect(uri=oracle_uri) as src, \
     gizmosql.connect(gizmosql_uri, username="token", password=token) as dst:
    with src.cursor() as s, dst.cursor() as d:
        s.execute("SELECT * FROM sales.orders")
        rows = d.adbc_ingest(table_name="orders", data=s.fetch_record_batch(), mode="replace")
        dst.commit()
        print(f"Loaded {rows:,} rows")

(python/tests/test_oracle_to_gizmosql.py runs exactly this against a live Oracle + GizmoSQL pair in CI; GizmoSQL is a test-only dependency — the driver itself has no GizmoSQL code.)

DuckDB / GizmoSQL ↔ Oracle via adbc_scanner

Because the driver is a plain ADBC c-shared library, DuckDB — and therefore GizmoSQL, which embeds DuckDB — can talk to Oracle live through the adbc_scanner community extension, in both directions:

INSTALL adbc_scanner FROM community;
LOAD adbc_scanner;

SET VARIABLE ora = adbc_connect({
    'driver': '/path/to/libadbc_driver_oracle.so',   -- adbc_driver_oracle._driver_path() in Python
    'entrypoint': 'OracleDriverInit',
    'uri': 'oracle://db.example.com:1521/PROD',
    'username': 'app', 'password': 's3cret'
});

-- pull: run SQL in Oracle, get an Arrow-backed DuckDB relation
SELECT * FROM adbc_scan(getvariable('ora')::BIGINT, 'SELECT * FROM sales.orders WHERE amt > 100');

-- push: create an Oracle table from a DuckDB relation
SELECT * FROM adbc_insert(getvariable('ora')::BIGINT, 'ORDERS_COPY', (SELECT * FROM local_orders), mode := 'create');

python/tests/test_adbc_scanner.py covers both directions.

Bulk ingest (Arrow → Oracle)

import pyarrow as pa
import adbc_driver_oracle.dbapi as oracle

table = pa.table({"id": [1, 2, 3], "name": ["alice", "bob", "carol"]})
with oracle.connect(
    uri="oracle://scott:tiger@localhost:1521/FREEPDB1",
    autocommit=True,  # ADBC connections are autocommit-OFF by default;
                      # opt in here so the ingest persists on close
) as conn, conn.cursor() as cur:
    # create_append: create CUSTOMERS from the Arrow schema if it
    # doesn't exist, then append via array-bind INSERT.
    cur.adbc_ingest(table_name="customers", data=table, mode="create_append")

Heads-up — autocommit is off by default. Per the Python DB-API, oracle.connect() opens connections inside a transaction. Without the autocommit=True above (or an explicit conn.commit()), the append is rolled back when the connection closes. (Oracle DDL — the CREATE TABLE in the create-family modes — always commits implicitly.)

mode accepts the four standard ADBC ingest modes:

mode behavior
create create the table (errors if it already exists), then append — this is the default when mode is omitted
append append to an existing table (no DDL; errors if missing)
replace drop the table if it exists, recreate it, then append
create_append create the table if it doesn't exist, then append

Table DDL for the create-family modes is generated from the Arrow schema (see Type mapping); simple column names are upper-cased like unquoted Oracle identifiers, anything else is quoted verbatim. Pass db_schema_name=... to target another schema. Rows are sent as array-bound INSERTs, 5000 per round trip. Statement options (cur.adbc_statement.set_options(...) / stmt.SetOption in Go):

Statement option Default Notes
adbc.oracle.ingest.batch_rows 5000 Rows per array-bind INSERT round trip.
adbc.oracle.ingest.varchar_length 4000 VARCHAR2(n) length for Arrow string columns in generated DDL.
adbc.oracle.ingest.string_type VARCHAR2 VARCHAR2, NVARCHAR2, CLOB or NCLOB for string columns.
adbc.oracle.ingest.raw_length 2000 RAW(n) length for Arrow binary columns.
adbc.oracle.ingest.binary_type RAW RAW or BLOB for binary columns.

Values larger than the server's maximum VARCHAR2/RAW size are bound as LONG / LONG RAW, so strings and blobs of any size load into CLOB / BLOB columns.

Transactions (autocommit off)

import adbc_driver_oracle.dbapi as oracle

with oracle.connect(
    uri="oracle://scott:tiger@localhost:1521/FREEPDB1",
    autocommit=False,
) as conn, conn.cursor() as cur:
    cur.execute("INSERT INTO orders VALUES (1, 'pending')")
    cur.execute("INSERT INTO order_items VALUES (1, 'widget', 2)")
    conn.commit()  # both inserts persist atomically

Parameter binding

Positional ? placeholders are rewritten to Oracle's :1, :2, … bind variables; native :name / :1 styles pass through untouched:

cur.execute("SELECT ename, sal FROM emp WHERE deptno = ? AND sal > ?", (10, 1500))

Connection URL

oracle://[user[:password]@]host[:port]/SERVICE_NAME[?option=value...]
oracle://[user[:password]@]host[:port]?sid=ORCL

Oracle Easy Connect strings (host:port/service) and full (DESCRIPTION=...) TNS connect descriptors are accepted in place of the oracle:// form.

Option Default Notes
adbc.uri Pass as the uri= kwarg to oracle.connect.
username / password (URI) Standard ADBC credential options; override the URI's user:password.
adbc.oracle.host (URI) Database host.
adbc.oracle.port 1521 Listener port.
adbc.oracle.service_name (URI) Service name (e.g. FREEPDB1).
adbc.oracle.sid (none) SID, as an alternative to a service name.
adbc.oracle.tls false true → TLS (tcps) transport.
adbc.oracle.tls.ca_cert (none) PEM CA bundle for verifying the server certificate.
adbc.oracle.tls.skip_verify false true → skip server certificate verification.
adbc.oracle.tls.server_name (host) Host name for certificate verification / SNI.
adbc.oracle.wallet_location (none) Directory containing ewallet.pem (Autonomous Database wallet); implies TLS. mTLS works with an unencrypted key.
adbc.oracle.token (none) OAuth / IAM bearer token instead of a password (TLS only).
adbc.oracle.mode (none) sysdba, sysoper, sysasm, sysbackup, sysdg, syskm, sysrac.
adbc.oracle.connect_timeout 30 Dial timeout, as seconds or a Go duration like 1.5s.
adbc.oracle.batch_size 65536 Maximum rows per Arrow record batch.
adbc.oracle.prefetch_rows (batch size, max 65536) Rows the server returns per fetch round trip.
adbc.oracle.number_mode auto NUMBER → Arrow policy: auto, decimal, double, string (see Type mapping).
adbc.oracle.session_time_zone +00:00 Session TIME_ZONE; TIMESTAMP WITH LOCAL TIME ZONE values are returned in it.
adbc.oracle.sdu (server) Requested session data unit (packet size) in bytes.
adbc.oracle.application_name (executable name) Program name reported to the server (V$SESSION.PROGRAM, CLIENT_PROGRAM_NAME).
adbc.oracle.current_schema (none) Sets the session's current schema after connecting.
adbc.oracle.trace false true → hex-dump TNS packets to stderr.

All of these are also accepted as ?key=value URI query parameters (without the adbc.oracle. prefix, e.g. ?tls=true&number_mode=decimal). After connecting, adbc.oracle.batch_size, adbc.oracle.prefetch_rows, adbc.oracle.number_mode and the end-to-end tracing attributes adbc.oracle.module / .action / .client_info / .client_identifier can be changed per connection.

The URI is its own kwarg; everything else goes through db_kwargs:

import adbc_driver_oracle.dbapi as oracle

oracle.connect(
    uri="oracle://db.example.com:2484/PROD",
    db_kwargs={
        "username": "app",
        "password": "s3cret",
        "adbc.oracle.tls": "true",
        "adbc.oracle.tls.ca_cert": "/etc/ssl/certs/corp-ca.pem",
    },
)

Connection profiles & driver manifests

ADBC connection profiles (adbc-driver-manager ≥ 1.11) let you keep a connection's driver + options in a reusable TOML file instead of code. Profiles resolve the driver by name, which requires a driver manifest on the search path. Install ours once per environment:

$ python -m adbc_driver_oracle install-manifest
Wrote ADBC driver manifest: .../etc/adbc/drivers/oracle.toml

(Inside a virtualenv/conda env this targets the environment's auto-searched etc/adbc/drivers/; otherwise the per-user ADBC config directory. --user, --venv, and --dir PATH override; the same is available programmatically as adbc_driver_oracle.install_manifest().)

With the manifest in place, the driver manager finds the driver by name — no import of adbc_driver_oracle needed:

from adbc_driver_manager import dbapi

# Resolve by URI scheme alone:
conn = dbapi.connect(uri="oracle://scott:tiger@localhost:1521/FREEPDB1")

And a profile bundles the whole connection. Drop this in ~/.config/adbc/profiles/oracle_prod.toml (Linux; ~/Library/Application Support/ADBC/Profiles/ on macOS, or any directory named in ADBC_PROFILE_PATH):

profile_version = 1
driver = "oracle"

[Options]
uri = "oracle://db.example.com:2484/PROD"
username = "app"
password = "{{ env_var(ORACLE_PASSWORD) }}"
"adbc.oracle.tls" = true

then connect from any ADBC driver-manager binding:

conn = dbapi.connect(profile="oracle_prod")

The {{ env_var(...) }} substitution keeps secrets out of the file; options set explicitly in code still override profile values.

Using from Go

import (
    "context"

    "github.com/apache/arrow-go/v18/arrow/memory"
    "github.com/gizmodata/adbc-driver-oracle/driver/oracle"
)

drv := oracle.NewDriver(memory.DefaultAllocator)
db, _ := drv.NewDatabase(map[string]string{
    "uri": "oracle://scott:tiger@localhost:1521/FREEPDB1",
})
conn, _ := db.Open(context.Background())
stmt, _ := conn.NewStatement()
_ = stmt.SetSqlQuery("SELECT ename, sal FROM emp")
reader, _, _ := stmt.ExecuteQuery(context.Background())
defer reader.Release()
for reader.Next() {
    rec := reader.Record()
    // ...
}

Type mapping

Reads (adbc.oracle.number_mode=auto, the default):

Oracle type Arrow type
NUMBER(p,0) with 1 ≤ p ≤ 18 int64
NUMBER(p,s) with 1 ≤ p ≤ 38 decimal128(p,s)
NUMBER (no precision), FLOAT, computed expressions (COUNT(*), 1/3, literals) float64
BINARY_FLOAT / BINARY_DOUBLE float32 / float64
CHAR, VARCHAR2, NCHAR, NVARCHAR2, LONG, CLOB, NCLOB utf8
RAW, LONG RAW, BLOB binary
DATE timestamp[s]
TIMESTAMP(n) timestamp[s / ms / us / ns] by fractional-second precision n
TIMESTAMP WITH TIME ZONE / WITH LOCAL TIME ZONE timestamp[…, tz=UTC] (the instant; the original offset is not kept)
INTERVAL DAY TO SECOND / YEAR TO MONTH month_day_nano_interval
ROWID / UROWID utf8
JSON (21c+) utf8 — native OSON decoded to JSON text client-side
BOOLEAN (23ai) bool

number_mode=decimal maps every NUMBER to decimal128 ((38,10) when the precision is unknown), double maps all of them to float64, and string returns the exact decimal text — useful when precision matters and a column's declared scale can't be trusted.

Writes (bind parameters and bulk-ingest DDL):

Arrow type Bind type Generated DDL
int8/16/32/64, uint* NUMBER NUMBER(3/5/10/19/20)
float32 / float64 BINARY_FLOAT / BINARY_DOUBLE same
decimal128/256(p,s) NUMBER NUMBER(p,s)
utf8, large_utf8, utf8_view VARCHAR2 (LONG above the server max) VARCHAR2(4000) (CLOB for large_utf8; see ingest options)
binary, fixed_size_binary, large_binary RAW (LONG RAW above the max) RAW(2000) / RAW(n) / BLOB
bool BOOLEAN on 23ai, else NUMBER 0/1 BOOLEAN / NUMBER(1)
date32 / date64 DATE DATE
timestamp[unit] (naive) TIMESTAMP TIMESTAMP(0/3/6/9)
timestamp[unit, tz] TIMESTAMP WITH TIME ZONE (as UTC) TIMESTAMP(n) WITH TIME ZONE
duration, month_day_nano_interval (no months) INTERVAL DAY TO SECOND INTERVAL DAY(9) TO SECOND(9)

Empty strings are bound as NULL, matching Oracle's own '' semantics.

Limitations

  • Native Network Encryption / checksumming (ANO) is not implemented; servers that require it (SQLNET.ENCRYPTION_SERVER=required) refuse the connection with a clear error. Use TLS (tcps) instead — the same constraint python-oracledb thin mode has.
  • Object types (CREATE TYPE), XMLTYPE, REF CURSOR / implicit result sets, BFILE, PL/SQL OUT/IN OUT binds, VECTOR and Advanced Queuing are not supported yet (select XMLSERIALIZE(...), TO_CLOB(...), VECTOR_SERIALIZE(...) etc. to read those as text).
  • Named time-zone regions in TIMESTAMP WITH TIME ZONE values are returned as UTC instants (offset-based zones are exact).
  • Kerberos / RADIUS / external OS authentication, DRCP pooling and Oracle wallet private keys with a password are not supported.
  • Statement cancellation / call timeouts (context deadlines mid-fetch) are not wired up yet.

Repo layout

adbc-driver-oracle/
├── go.mod, go.sum
├── internal/
│   ├── tns/         — TNS packet framing (CONNECT/ACCEPT/REDIRECT/DATA/MARKER), TLS, SDU negotiation
│   ├── ttc/         — TTC message layer: protocol/data-type negotiation, O5LOGON auth, cursors, fetch
│   └── oratype/     — NUMBER / DATE / TIMESTAMP / INTERVAL / ROWID / OSON (JSON) codecs
├── driver/oracle/   — pure-Go ADBC Driver/Database/Connection/Statement impl
├── pkg/oracle/      — cgo c-shared wrapper (produces libadbc_driver_oracle.{so,dylib,dll})
├── python/          — Python wheel sources (adbc_driver_oracle)
└── .github/         — CI: go test, python tests (Oracle Free + GizmoSQL services), wheel matrix, PyPI publish

Credits

License

MIT. Protocol reference credits are listed above.

Download files

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

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

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

adbc_driver_oracle-0.1.0-py3-none-win_amd64.whl (8.0 MB view details)

Uploaded Python 3Windows x86-64

adbc_driver_oracle-0.1.0-py3-none-macosx_26_0_universal2.whl (4.0 MB view details)

Uploaded Python 3macOS 26.0+ universal2 (ARM64, x86-64)

File details

Details for the file adbc_driver_oracle-0.1.0-py3-none-win_amd64.whl.

File metadata

File hashes

Hashes for adbc_driver_oracle-0.1.0-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 9d25d738a295f856cd01631632bd03aef5922f5383586c68682d61b7439819a0
MD5 839f630ca9b315824c3a76cb4ca2eaa9
BLAKE2b-256 5cc92dace157a36b68c67ad2ef7a9f99babbacf54740e7f53615f7527e64b04f

See more details on using hashes here.

Provenance

The following attestation bundles were made for adbc_driver_oracle-0.1.0-py3-none-win_amd64.whl:

Publisher: ci.yml on gizmodata/adbc-driver-oracle

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file adbc_driver_oracle-0.1.0-py3-none-manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for adbc_driver_oracle-0.1.0-py3-none-manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 6c310eaa93faf4949d78cd2718975c193f274295045cda37dec175b6d57aeca6
MD5 31ba4f548ae1cee942f93c77a97d86da
BLAKE2b-256 5cef85e20f54f234b933573026e51ca97b868ac5a24afe8799a742275ab32a7e

See more details on using hashes here.

Provenance

The following attestation bundles were made for adbc_driver_oracle-0.1.0-py3-none-manylinux2014_x86_64.whl:

Publisher: ci.yml on gizmodata/adbc-driver-oracle

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file adbc_driver_oracle-0.1.0-py3-none-manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for adbc_driver_oracle-0.1.0-py3-none-manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 4137a1f35379405d7a115b0c366fa0e6c862c5fc9aaaf7183fa025b561afce25
MD5 f8ce9ac3f53f8eb55755dd31606a5cea
BLAKE2b-256 0e3977267aa57cef9699b43af5d36c288b103d9e94d151fa3a5253f69eb21856

See more details on using hashes here.

Provenance

The following attestation bundles were made for adbc_driver_oracle-0.1.0-py3-none-manylinux2014_aarch64.whl:

Publisher: ci.yml on gizmodata/adbc-driver-oracle

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file adbc_driver_oracle-0.1.0-py3-none-macosx_26_0_universal2.whl.

File metadata

File hashes

Hashes for adbc_driver_oracle-0.1.0-py3-none-macosx_26_0_universal2.whl
Algorithm Hash digest
SHA256 f982a1a6b4003eb7d0248d4ac84d5b7f57a030b337a879a70d89cfa848af2648
MD5 e6babafaad3c50fe0ad4ef8b9e7f8ef8
BLAKE2b-256 ea3f147c960ac946a5d3aa08a6324d19186003b3178a1f3e751e49747f9e26cb

See more details on using hashes here.

Provenance

The following attestation bundles were made for adbc_driver_oracle-0.1.0-py3-none-macosx_26_0_universal2.whl:

Publisher: ci.yml on gizmodata/adbc-driver-oracle

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

1.0.0

4 files

0.4.2

4 files

0.4.1

4 files

0.4.0

4 files

0.3.0

4 files

0.2.0

4 files

0.1.2

4 files

0.1.1

4 files

This release

0.1.0 This release

4 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page