Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

adbc-driver-oracle

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

An independent, open-source project. Not affiliated with, endorsed by, or sponsored by Oracle Corporation. "Oracle" and "Oracle Database" are trademarks of Oracle Corporation, used here only to describe compatibility.

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: Release candidate (1.0.0-rc1). 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 generic ADBC driver manager 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.)

Batch sizing. Each Arrow record batch becomes one Flight SQL DoPut message on the GizmoSQL side, and the GizmoSQL driver's gRPC client caps messages at 16 MiB by default. The Oracle driver's batches are Flight-safe out of the box: a batch is capped at 65,536 rows (batch_size) and 8 MiB (batch_bytes), so even very wide tables stream through without tripping the cap. Both knobs are tunable (URI parameter or adbc.oracle.* in db_kwargs):

src = oracle.connect(uri=oracle_uri + "?batch_bytes=4194304")   # smaller batches
# or, equivalently:
src = oracle.connect(uri=oracle_uri, db_kwargs={"adbc.oracle.batch_bytes": str(4 * 1024 * 1024)})

batch_bytes=0 removes the byte cap (batches then bound only by batch_size rows — a pre-1.0 default that could exceed 16 MiB for rows wider than ~250 bytes and fail ingest with ResourceExhausted: trying to send message larger than max). To move more than 8 MiB per message, also raise the GizmoSQL client's gRPC cap with adbc.flight.sql.client_option.with_max_msg_size — see the GizmoSQL driver README.

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;

-- credentials once, in a DuckDB secret
CREATE SECRET ora_secret (
    TYPE adbc,
    SCOPE 'oracle://db.example.com:1521/PROD',
    driver 'oracle',                      -- by name after `python -m adbc_driver_oracle install-manifest`,
                                          -- or a path: '/path/to/libadbc_driver_oracle.so'
    uri 'oracle://db.example.com:1521/PROD',
    username 'app',
    password 's3cret'
);

-- pull: Oracle as an attached catalog (projection + filter pushdown)
ATTACH 'oracle://db.example.com:1521/PROD' AS ora (TYPE adbc);
SELECT * FROM ora.SALES.ORDERS WHERE AMT > 100;
SELECT o.ORDER_ID, c.name FROM ora.SALES.ORDERS o JOIN customers c ON c.id = o.CUST_ID;

-- push (the simple way): write straight into Oracle through the attached
-- catalog with plain SQL — USE the Oracle schema, then CREATE TABLE ... AS
USE ora.SALES;
CREATE TABLE ORDERS_COPY AS SELECT * FROM memory.local_orders;   -- CTAS into Oracle
USE memory;

-- push (the function API): the same, for arbitrary SQL, via the secret
SET VARIABLE ora = adbc_connect({'secret': 'ora_secret'});
SELECT * FROM adbc_scan(getvariable('ora')::BIGINT, 'SELECT * FROM sales.orders WHERE ROWNUM <= 10');
SELECT * FROM adbc_insert(getvariable('ora')::BIGINT, 'ORDERS_COPY2', (SELECT * FROM local_orders), mode := 'create');

Both write paths work: USE <attached schema>; CREATE TABLE ... AS ... (and INSERT INTO ...) through the attached catalog, or the adbc_insert() function for arbitrary relations. python/tests/test_adbc_scanner.py covers reads and both write paths.

Credentials can live in a self-contained DuckDB secret (above) or in an ADBC connection profile — adbc_scanner resolves profile://… URIs too, so profiles are not specific to any one extension (the connection-profiles section below shows the profile setup).

DuckDB via connection profiles (the adbc community extension)

DuckDB's adbc community extension (INSTALL adbc FROM community; see the GizmoSQL guide) resolves databases through ADBC connection profiles — no credentials in your SQL — and additionally supports writes (INSERT, COPY, CREATE TABLE AS) into the attached database through ADBC bulk ingest:

python -m adbc_driver_oracle install-manifest      # registers driver "oracle"
cat > ~/.config/adbc/profiles/prod.toml <<EOF        # macOS: ~/Library/Application Support/ADBC/Profiles/
profile_version = 1
driver = "oracle"

[Options]
uri = "oracle://db.example.com:1521/PROD"
username = "app"
password = "s3cret"
EOF
INSTALL adbc FROM community;
LOAD adbc;

SELECT * FROM read_adbc('profile://prod', 'SELECT * FROM sales.orders WHERE ROWNUM <= 10');

ATTACH 'profile://prod' AS ora (TYPE adbc);
USE ora.SALES;
SELECT COUNT(*) FROM ORDERS;
CREATE TABLE ORDERS_2024 AS SELECT * FROM memory.staged_orders;   -- bulk ingest into Oracle

python/tests/test_duckdb_adbc_client.py covers read_adbc, ATTACH, and CTAS/INSERT. (The adbc_scanner extension above is the one to use when you want predicate/projection pushdown on attached tables.)

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.
adbc.oracle.ingest.struct_type JSON JSON (21c+), CLOB, VARCHAR2 or BLOB for list / struct / map columns (stored as JSON text).
adbc.oracle.ingest.tablespace (default) TABLESPACE clause for tables created by ingest.

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

PL/SQL: OUT / IN OUT binds and implicit result sets

A PL/SQL block's OUT / IN OUT binds come back as a one-row result set (the Arrow C Data Interface can't mutate bound input); each field carries ORACLE:parameter_type = OUT / IN OUT metadata. Bind a typed placeholder to choose the return type — an untyped None returns text:

cur.execute(
    "BEGIN :doubled := :n * 2; :greeting := 'hello ' || :greeting; END;",
    pa.record_batch([pa.array([None], pa.int64()), pa.array([21], pa.int64()), pa.array(["world"])],
                    names=["doubled", "n", "greeting"]),
)
cur.fetch_arrow_table().to_pylist()   # [{'DOUBLED': 42, 'GREETING': 'hello world'}]

Cursors returned with DBMS_SQL.RETURN_RESULT stream back exactly like a query (the first cursor; further ones are closed):

cur.execute("""DECLARE c SYS_REFCURSOR; BEGIN
                 OPEN c FOR SELECT * FROM emp; DBMS_SQL.RETURN_RESULT(c); END;""")
table = cur.fetch_arrow_table()

Object types, collections, XMLType and SDO_GEOMETRY

User-defined object types, VARRAYs and nested tables come back as JSON text — attributes keyed by name, collections as arrays, LOB attributes inlined, dates as ISO-8601 — so any Arrow consumer can read them without Oracle-specific type knowledge. XMLType columns come back as XML text and MDSYS.SDO_GEOMETRY as GeoArrow WKB (points, lines, polygons incl. rectangles, multi-geometries and collections, 2D/3D/4D; circular arcs are not supported). With adbc.oracle.use_extension_types=true the fields are annotated with Arrow extension types (arrow.json, arrow.opaque for XMLType, geoarrow.wkb) — pyarrow materialises them automatically:

with oracle.connect(uri=uri, db_kwargs={"adbc.oracle.use_extension_types": "true"}) as conn, conn.cursor() as cur:
    cur.execute("SELECT name, address, boundary FROM sites")   # address is an object type, boundary SDO_GEOMETRY
    table = cur.fetch_arrow_table()
    table.schema.field("BOUNDARY").type   # binary with geoarrow.wkb metadata -> shapely.from_wkb(...)

Nested Arrow columns (list, struct, map) are ingested as JSON text into JSON columns (21c+; adbc.oracle.ingest.struct_type selects CLOB, VARCHAR2 or BLOB instead).

Native Network Encryption (NNE)

The driver speaks Oracle's Advanced Networking Option, so it works with servers that require Native Network Encryption and/or data integrity checksumming — no Oracle Client and no TLS certificate wrangling. After the connect handshake it runs the encryption / checksum negotiation (Diffie-Hellman key exchange) and then AES-encrypts and checksums every packet.

The adbc.oracle.nne level follows Oracle's sqlnet semantics:

  • accepted (default) — encrypt only when the server asks for it: nothing changes against a server that doesn't, and a server that requires NNE is transparently negotiated (AES-256 / SHA-512 in the common case).
  • requested — the client initiates the negotiation; the session is encrypted with any server that accepts (Oracle's server-side default), and falls back to cleartext only if the server rejects NNE.
  • required — like requested, but fails closed: if encryption cannot be negotiated the connection is refused with ORA-12660 rather than ever sending data in cleartext. (A TLS tcps channel satisfies the requirement.)
  • rejected — never negotiate.
# Just works against a server with SQLNET.ENCRYPTION_SERVER=REQUIRED:
with oracle.connect(uri="oracle://user:pw@exadata-scan:1521/PROD") as conn, conn.cursor() as cur:
    cur.execute("SELECT * FROM sales.orders")   # every packet AES-encrypted + checksummed

# Refuse to connect unless the channel is encrypted:
oracle.connect(uri=uri, db_kwargs={"adbc.oracle.nne": "required"})

Whether a session is actually protected is verifiable from the application via two read-only connection options — and, server-side, V$SESSION_CONNECT_INFO.NETWORK_SERVICE_BANNER lists the encryption / crypto-checksumming service adapters when NNE is active:

with oracle.connect(uri=uri, db_kwargs={"adbc.oracle.nne": "required"}) as conn:
    assert conn.adbc_connection.get_option(key="adbc.oracle.nne_active") == "true"
    print(conn.adbc_connection.get_option(key="adbc.oracle.nne_algorithms"))  # e.g. AES256,SHA256

Supported: AES-128/192/256 encryption and MD5 / SHA-1 / SHA-256 / SHA-384 / SHA-512 checksums (the driver offers only AES ciphers; legacy RC4/DES are never used). Kerberos / RADIUS network authentication is not supported — use password, TLS or token auth. Over a tcps (TLS) connection NNE stays off by default since the channel is already encrypted.

Cancellation

cursor.adbc_cancel() (or a cancelled/expired context.Context in Go) interrupts the running server call; the statement fails with ORA-01013 and the connection stays usable. Cancellation uses TCP out-of-band data on Linux/macOS (in-band markers on Windows and over TLS), the same mechanism as python-oracledb.

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.interval_mode monthdaynano INTERVAL → Arrow policy: monthdaynano, duration (DAY TO SECOND), string (ISO 8601).
adbc.oracle.date_mode timestamp DATEtimestamp[s] (keeps the time of day) or date32.
adbc.oracle.batch_bytes 8388608 Approximate upper bound on bytes per Arrow record batch (8 MiB default keeps batches under Flight SQL's 16 MiB gRPC cap; 0 = only batch_size applies).
adbc.oracle.disable_oob false Disable out-of-band (TCP urgent) breaks used for cancellation; falls back to in-band markers.
adbc.oracle.nne accepted Native Network Encryption / data integrity: accepted, requested, required (fails closed if not negotiated), rejected.
adbc.oracle.nne_checksum (=nne) Data-integrity level, if different from nne.
adbc.oracle.nne_encryption_algorithms (all AES) Comma-separated encryption preference, e.g. AES256,AES192.
adbc.oracle.nne_checksum_algorithms (all) Comma-separated checksum preference, e.g. SHA512,SHA256.
adbc.oracle.use_extension_types false Annotate JSON / object (arrow.json), XMLType (arrow.opaque) and SDO_GEOMETRY (geoarrow.wkb) columns with Arrow extension-type metadata.
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.batch_bytes, adbc.oracle.prefetch_rows, adbc.oracle.number_mode, adbc.oracle.interval_mode, adbc.oracle.date_mode and the end-to-end tracing attributes adbc.oracle.module / .action / .client_info / .client_identifier can be changed per connection. The read-only connection options adbc.oracle.nne_active / adbc.oracle.nne_algorithms report whether the session is protected by Native Network Encryption and with what.

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] (date32 with date_mode=date32)
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 month_day_nano_interval; duration[unit by precision] with interval_mode=duration; ISO-8601 utf8 (P1DT2H3M4.5S) with interval_mode=string
INTERVAL YEAR TO MONTH month_day_nano_interval; ISO-8601 utf8 (P2Y3M) with interval_mode=string
ROWID / UROWID utf8
JSON (21c+) utf8 — native OSON decoded to JSON text client-side
BOOLEAN (23ai) bool
Object types, VARRAY, nested table utf8 JSON text (arrow.json extension with use_extension_types)
XMLTYPE utf8 XML text (arrow.opaque extension)
MDSYS.SDO_GEOMETRY binary WKB (geoarrow.wkb extension)

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)
float16 / float32 / float64 BINARY_FLOAT / 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)
list, struct, map VARCHAR2 / LONG (JSON text) JSON (21c+) — see ingest.struct_type

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

Limitations

  • Kerberos / RADIUS network authentication is not supported (use password, TLS, wallet or token auth). Native Network Encryption is supported (see above).
  • REF CURSOR bind/column values, BFILE, LOB-typed OUT binds, VECTOR, Advanced Queuing, objects stored in LOBs (degenerate images) and SDO_GEOMETRY circular arcs / compound elements are not supported yet (select VECTOR_SERIALIZE(...), SDO_UTIL.TO_WKBGEOMETRY(...) etc. to read those as text/binary). Object types are read-only: they cannot be bound as parameters or written by ingest.
  • Strings inside object images are decoded as UTF-8 (AL32UTF8 databases; national-character attributes as UTF-16).
  • 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.
  • Cancellation is only honoured by the server at SQL execution checkpoints; a call blocked in e.g. DBMS_SESSION.SLEEP finishes before ORA-01013 is raised (python-oracledb behaves the same).

Repo layout

adbc-driver-oracle/
├── go.mod, go.sum
├── internal/
│   ├── tns/         — TNS packets (CONNECT/ACCEPT/REDIRECT/DATA/MARKER), TLS, SDU, Native Network Encryption (ANO)
│   ├── 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

Provenance & licensing

This is an independent, from-scratch reimplementation of a client for Oracle Database's TNS/TTC network protocol, written in Go. It contains no Oracle Corporation source code and links against no Oracle Corporation libraries — that's the whole point (no Instant Client). Network protocols and the interfaces needed for interoperability are not, in themselves, proprietary to any vendor.

The wire behavior was implemented by reference to publicly available, openly licensed source — no confidential specification, non-public documentation, or binary disassembly was used:

  • oracle/python-oracledb — Oracle's own open-source driver, whose "thin mode" speaks the same protocol, published by Oracle under a dual UPL 1.0 / Apache-2.0 license. It is the authoritative public reference for the protocol.
  • sijms/go-ora — a community pure-Go Oracle client (MIT); the Native Network Encryption negotiation was reimplemented by reference to it.
  • Apache Arrow ADBC and Apache Arrow Go (Apache-2.0) — the ADBC framework and Arrow libraries.

See NOTICE for full attribution.

Trademarks

Oracle and Oracle Database are registered trademarks of Oracle Corporation. adbc-driver-oracle is not affiliated with, endorsed by, or sponsored by Oracle Corporation. References to "Oracle Database" identify the software this driver interoperates with and are nominative fair use.

License

MIT — Copyright (c) 2026 GizmoData LLC. See NOTICE for third-party attributions.

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-1.0.0rc1-py3-none-win_amd64.whl (8.2 MB view details)

Uploaded Python 3Windows x86-64

adbc_driver_oracle-1.0.0rc1-py3-none-macosx_12_0_universal2.whl (4.0 MB view details)

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

File details

Details for the file adbc_driver_oracle-1.0.0rc1-py3-none-win_amd64.whl.

File metadata

File hashes

Hashes for adbc_driver_oracle-1.0.0rc1-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 6c73a64c505811e0043e9aee8b6382f9be2214522c5297d8666fa0f37bd102a9
MD5 31c9cea974258a5b4a8f19a4ced1c347
BLAKE2b-256 7eb16fa9c3df00fbdd7a6107f773911c61cb06534308c174e2b1d32a216c8ab1

See more details on using hashes here.

Provenance

The following attestation bundles were made for adbc_driver_oracle-1.0.0rc1-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-1.0.0rc1-py3-none-manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for adbc_driver_oracle-1.0.0rc1-py3-none-manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 a3477f01806a837ebb98d42ed77d047207a19d22fc55c0764f98e300e54507d8
MD5 2c1ef4fadcf0c1c6a5813fde1bc9f97e
BLAKE2b-256 4c088cf3d3d3bf75fe8bcacd73955d4615871b31ceae043fdbaf3b64ffb70032

See more details on using hashes here.

Provenance

The following attestation bundles were made for adbc_driver_oracle-1.0.0rc1-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-1.0.0rc1-py3-none-manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for adbc_driver_oracle-1.0.0rc1-py3-none-manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 a84655536699bdc53a5222bab1b8615841a7faa2a5a32367ebdf45f1168c5eea
MD5 cec183fd5428ce08e94ebf00933630a3
BLAKE2b-256 6fc18a13026bce4ddf683e4ce438d39a17dcf653412322e7c7fe164073271a08

See more details on using hashes here.

Provenance

The following attestation bundles were made for adbc_driver_oracle-1.0.0rc1-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-1.0.0rc1-py3-none-macosx_12_0_universal2.whl.

File metadata

File hashes

Hashes for adbc_driver_oracle-1.0.0rc1-py3-none-macosx_12_0_universal2.whl
Algorithm Hash digest
SHA256 aac5d4c98e3421a8ea4f7af770baa976ee13f92d8a2eb407557642bdd4e0bc33
MD5 953edb663a356aad13f6015ae933512c
BLAKE2b-256 1acc37a691af747451bb71889c116f7ea8f59161a9a23caf2104d0f7c14a7be0

See more details on using hashes here.

Provenance

The following attestation bundles were made for adbc_driver_oracle-1.0.0rc1-py3-none-macosx_12_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

This release

1.0.0rc1 This release

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

0.1.0

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