ORATCL

Tcl Extension oratcl 9.1 (ODPI-C 5.6.4) April 2026

NAME

oratcl — Tcl 9 interface to Oracle via ODPI-C 5.6.4

SYNOPSIS

oralogon  connect-string ?-pool {min max incr}?
          ?-homogeneous 0|1?
          ?-getmode wait|nowait|forceget|timedwait?
          ?-waittimeout ms? ?-timeout s? ?-maxlifetime s?
          ?-pinginterval s? ?-pingtimeout ms?
          ?-stmtcachesize n?
          ?-failovercallback proc?
oralogoff logon-handle

oraopen   logon-handle           # open a statement handle
orastmt   logon-handle           # alias of oraopen
oraclose  statement-handle

oraparse  statement-handle ?-novalidate? sql-text
orasql    statement-handle sql-text ?-parseonly? ?-commit?
oraplexec statement-handle {pl/sql block} ?-commit?
oraexec   statement-handle ?-commit?

orabind      statement-handle :name value ? :name value ... ?
orabindexec  statement-handle ?-commit? ?-arraydml? :name list ...

orafetch statement-handle
         ?-datavariable varName?
         ?-dataarray arrayName?
         ?-indexbyname|-indexbynumber?
         ?-command script?
         ?-max N?
         ?-resultvariable varName?
         ?-returnrows?
         ?-asdict?

oracols statement-handle
oradesc logon-handle object-name

oraconfig handle ?name ?value??

oramsg  handle rc|error|rows|peo|ocicode|sqltype|fn|action|sqlstate|recoverable|warning|offset|all|allx

oralob  size|read|write|trim|close lob-handle ?args...?

oraautocommit logon-handle 0|1
oracommit    logon-handle
orarollback  logon-handle
orabreak     logon-handle         # cancel active call

oraexecasync  statement-handle ?-commit?
orawaitasync  statement-handle ?-timeout milliseconds?

DESCRIPTION

oratcl 9.1 implements the classic Oratcl API on top of ODPI-C (no OCI). It targets Tcl 9: command signatures use Tcl_Size and are thread/multi-interp safe. One ODPI context is created per process; per-interp state (handles, registries) is created on each load.

Async execution uses a persistent thread pool with reference-counted entries and a shared work queue. All operations on a physical Oracle connection are serialized through a shared per-connection gate, ensuring safe concurrent use across interpreter threads and async workers.

All commands are registered both at global scope (backward compatibility) and under the ::oratcl namespace. Use namespace import ::oratcl::* to access the namespaced forms.

COMMANDS

Connection

oralogon connect-string ?options?

Connect using either username/password@connect_identifier, or external auth (leave user/pw empty). Quoted passwords with backslash escapes are supported: user/"pass\"word"@db. Options:

-pool {min max incr} — Create a session pool (or acquire from an existing one) and check out the first connection. Multiple oralogon -pool calls with identical parameters share one underlying session pool process-wide; only the first call creates the pool.

-homogeneous 0|1 — Homogeneous pool (default 1).

-getmode wait|nowait|forceget|timedwait — Pool get mode. Use -waittimeout together with timedwait.

-waittimeout ms — Maximum wait in milliseconds for an available session when -getmode timedwait is active.

-timeout s — Idle session eviction timeout in seconds.

-maxlifetime s — Maximum session lifetime in seconds.

-pinginterval s — Liveness ping interval in seconds (0 to disable).

-pingtimeout ms — Ping timeout in milliseconds.

-stmtcachesize n — Statement cache size for sessions created from this pool.

-failovercallback proc — Register a Tcl proc invoked on recoverable errors (driver-side failover). Can also be set later via oraconfig.

Returns a logon-handle (e.g. "oraL1").

oralogoff logon-handle

Close the connection. Cancels pending async operations, releases associated LOBs and statements in phased order.

oraautocommit logon-handle 0|1

Set autocommit. DML/PLSQL commit on success when enabled or when -commit is supplied to exec commands. The change is propagated to the shared adoption record.

orainfo logon-handle

Return a dict with the current connection info. Currently returns autocommit only.

Statements & execution

oraopen logon-handle (alias: orastmt)

Open a statement handle.

oraparse stmt ?-novalidate? sql

Prepare a SQL statement. By default a server round-trip validates the SQL at parse time, reporting syntax errors immediately. With -novalidate the prepare is client-side only and errors surface at execute time; use this on hot paths where the SQL is known-good and the round-trip cost is measurable.

orasql stmt sql ?-parseonly? ?-commit?

Parse and, unless -parseonly, execute once. Clears the per-statement stored-bind cache if text changes.

oraexec stmt ?-commit?

Execute the already-parsed statement (single execution). Supports driver-side failover with configurable retry/backoff.

oraplexec stmt {pl/sql} ?-commit?

Prepare and execute a PL/SQL block.

orabind stmt :name value ...

Bind scalars by name. LOB type is inferred by name suffix (_blob, _clob) and/or value representation.

orabindexec stmt ?-commit? ?-arraydml? :name list ...

Array DML with name→list pairs. String values are pinned for the duration of the executeMany call; the lists must not be modified while the call is in progress.

Fetching

orafetch stmt ?options?

Fetches up to -max N rows and returns 0 while data remains, or 1403 at end-of-data. Use -returnrows or -resultvariable to obtain a list of rows; -asdict returns dicts keyed by column names.

Fetched data is deep-copied into local snapshots so that -command callbacks can safely issue other database operations (including closing the statement) without deadlock. Column names are uppercased with Unicode-aware conversion.

Metadata

oracols stmt

Return column names of the current query. Names are uppercased with Unicode-aware conversion.

oradesc logon-handle object-name

Return {name type} pairs for the columns of the given table or view.

LOBs

oralob subcmd lob-handle ?args?

oralob read lob-handle offset amount — Read as a bytearray.

oralob write lob-handle offset data — Write data to the LOB.

oralob trim lob-handle newsize — Truncate the LOB.

oralob size lob-handle — Return the current size.

oralob close lob-handle — Release the LOB handle.

When inlineLobs is enabled at the connection, orafetch returns raw data instead of LOB handles. LOB handles reference the shared connection gate for serialized I/O.

Async

oraexecasync stmt ?-commit?

Submit the statement for execution on a persistent worker thread pool. Failover policy fields are snapshotted at enqueue time to avoid data races. The worker holds addRef() on the underlying dpi handles and serializes through the shared connection gate.

orawaitasync stmt ?-timeout ms?

Wait for completion or timeout. Returns 0 on success, -3123 on timeout, or the Oracle error code on execution failure. On completion, temp LOBs created by orabind are released. On timeout, the async entry is marked as orphaned; the worker self-cleans on completion.

Transaction Control

oracommit logon-handle

Commit the current transaction.

orarollback logon-handle

Roll back the current transaction.

orabreak logon-handle

Cancel the currently executing call on this connection via the shared connection gate.

Diagnostics

oramsg handle field

Return per-handle diagnostic information. Fields: rc, error, rows, peo (parse error offset), ocicode, sqltype, fn, action, sqlstate, recoverable, warning, offset, all, allx.

CONFIGURATION

Use oraconfig on a logon-handle or a statement-handle. With no name/value, returns all keys; with a name only, returns that value; with name/value pairs, sets them.

oraconfig handle ?name ?value??

Connection-level keys:

stmtcachesize, fetcharraysize, prefetchrows, calltimeout (ms), inlineLobs (0/1), and failover policy: foMaxAttempts (capped at 1000), foBackoffMs, foBackoffFactor, foErrorClasses (network and/or connlost), foDebounceMs, failovercallback.

Statement-level keys:

fetchrows, prefetchrows (overrides connection default when set; 0 reverts to connection default).

Configuration changes on connections are synced to the shared adoption record so all wrappers on the same physical session remain consistent.

EXAMPLES

Basic select:

set L [oralogon "scott/tiger@//dbhost/pdb"]
set S [oraopen $L]
oraparse $S {select empno, ename from emp where deptno = :d}
orabind  $S :d 10
oraexec  $S
set rows {}
while 1 {
    set rc [orafetch $S -asdict -datavariable row]
    if {$rc == 1403} break
    lappend rows [dict get $row EMPNO] [dict get $row ENAME]
}
oraclose $S
oralogoff $L

Array DML:

set S [oraopen $L]
oraparse $S {insert into t(x,y) values(:x,:y)}
orabindexec $S -commit -arraydml :x {1 2 3} :y {A B C}

Async:

oraparse $S {begin dbms_lock.sleep(2); end;}
oraexecasync $S
# do other work...
orawaitasync $S -timeout 3000

Pooled connection with failover:

set L [oralogon "scott/tiger@//dbhost/pdb" -pool {2 10 2} \
       -failovercallback myFailoverHandler]
oraconfig $L foMaxAttempts 3 foBackoffMs 500 \
             foBackoffFactor 2.0 foErrorClasses {network connlost}

Namespace import:

namespace import ::oratcl::*
set L [oralogon "scott/tiger@//dbhost/pdb"]

ENVIRONMENT

The test suite uses ORATCL_CONNECT for the connect string.

THREADS & MULTIPLE INTERPRETERS

One global ODPI context is created lazily and destroyed on process exit (two-phase teardown). Each interpreter has isolated handle registries.

Connections can be adopted across interpreters: the adopter gets its own wrapper backed by the same physical dpiConn*, serialized through a shared per-connection operation gate. Behavioral configuration (autocommit, fetch sizes, failover policy) is synced to a shared record so adopters inherit consistent settings.

Async execution uses a persistent thread pool. Workers hold addRef'd dpi handles and operate only through the shared gate. Teardown uses interp-scoped cancellation with finite timeouts and an orphan mechanism for workers that do not complete in time.

oratcl_SafeInit rejects loading into safe interpreters with a descriptive error.

↑ Top