Skip to content

Support Matrix

Compatibility and feature support for pycubrid releases.

Reference: This page describes the main branch, which can be ahead of the latest published release. pycubrid.__version__ in pycubrid/__init__.py on main is the version label of the source, not proof that this version is published: main keeps the last released number until a release pull request changes it, and an open release pull request is only a candidate. A version is published when its tag appears on the release list and its files are on PyPI; the support statement for that version is this page at its tag (docs/SUPPORT_MATRIX.md). Release notes are in CHANGELOG.md.


Version Compatibility

Python

Python Version Status
3.11 ✅ Supported
3.12 ✅ Supported
3.13 ✅ Supported
3.14 ✅ Supported
3.15 ✅ Supported (standard GIL builds)
3.10 ❌ Not supported from 1.10.0 (last supported in 1.9.x)
< 3.10 ❌ Not supported

Python 3.10 support retirement: Python 3.10 reached upstream end of life on 2026-10-01 (PEP 619). The 1.8.x and 1.9.x lines support Python 3.10; 1.9.0 carried the advance notice. From 1.10.0 the package requires Python 3.11 or newer (requires-python = ">=3.11"), so pip on Python 3.10 keeps installing 1.9.x. Only the latest release line receives fixes, so 1.9.x gets no further releases. Upgrade your interpreter, recreate your virtual environment and validate your application before upgrading.

Python 3.15: Supported on the final CPython 3.15.0 release with the standard GIL; free-threaded builds are not supported. The full compatibility matrix runs Python 3.15 against every supported CUBRID version on manual dispatch and every release, and 3.15 is the newest offline endpoint. The manual python-canary.yml lane still checks fresh wheel/sdist installs on 3.15. Environment notes:

  • Decimal-subclass parameters need the C _decimal module. A Python built without libmpdec lacks it, and such a parameter raises ProgrammingError; plain Decimal values are unaffected. The actions/setup-python 3.15.0 build that CI uses, the python:3.15-rc Docker image and a source build without libmpdec-dev were all observed without it, so CI runs the 3.15 cells without the C module and the tests that need it are skipped there.
  • Python 3.15 maps its ksc5601 codec alias and the other KS C 5601 aliases to CP949. pycubrid keeps charset="ksc5601" as EUC-KR; the other aliases follow Python and are rejected (see Connection).
  • Docker Hub had no final python:3.15 image when 3.15 was promoted; CI uses actions/setup-python.

CUBRID Server

CUBRID Version Status Notes
11.4 ✅ Supported Latest stable
11.2 ✅ Supported
11.0 ✅ Supported
10.2 ✅ Supported Minimum tested version
< 10.2 ❌ Not supported Current driver targets CAS protocol v8

CI Matrix

Validation Routine execution Full compatibility
Offline PR smoke on Ubuntu/Python 3.12; high-risk PR full regressions without coverage; main/changed-weekly full suite, 95% coverage Local full tests remain available
Live integration High-risk PR Python 3.14/CUBRID 11.4; main/changed-weekly also Python 3.11/CUBRID 10.2 Python 3.11–3.15 × CUBRID 10.2/11.0/11.2/11.4 on manual dispatch and every release

See CI execution policy. That policy selects what each run tests; it does not define the supported versions listed above, and representative PR checks are not evidence for every supported combination.

Server Behavior Differences Between CUBRID Versions

The version differential (tests/test_version_differential.py) compares what pycubrid returns for the same statements on every supported server: error class, errno and sqlstate, rowcount, lastrowid, description, and each value's Python type and value. pycubrid decodes every version the same way. The differences below come from the server (the same results appear in csql), and they are the only ones the suite allows. Each entry, with its upstream link, is in tests/helpers/version_matrix.py.

Behavior 10.2 11.0 11.2 11.4
String/bit value longer than its CHAR(n)/VARCHAR(n)/BIT VARYING(n) column Silently truncated ProgrammingError -494 ProgrammingError -494 ProgrammingError -494
REGEXP_LIKE / REGEXP_* functions Undefined (-494) Available Available Available
Bare value as a condition (IF(1, ...), WHERE 1) Accepted Accepted ProgrammingError -493 ProgrammingError -493
Type of 'a' \|\| 'b' / CONCAT CHAR VARCHAR VARCHAR VARCHAR
CAST('a' AS VARCHAR) = 'a ' True False False False
TRUNCATE of an FK-referenced parent IntegrityError -924 IntegrityError -924 IntegrityError -1284 IntegrityError -1284
Type of COUNT(*) INTEGER INTEGER BIGINT BIGINT
Integer mixed with NUMERIC ((5) * (0.100)) NUMERIC(14,3) NUMERIC(14,3) NUMERIC(19,3); 17+ digit BIGINT overflows (-427) NUMERIC(14,3)
REGEXP operator on text with 3-byte UTF-8 characters Matches Never matches Never matches Never matches
REGEXP_* on text with 3-byte UTF-8 characters Undefined NULL 0 0

Feature Support

PEP 249 (DB-API 2.0)

Feature Status Notes
apilevel ✅ "2.0"
threadsafety ✅ 1 Threads may share the module, not connections
paramstyle ✅ "qmark" ? parameter markers
connect() ✅ Module-level constructor
Connection ✅ Full lifecycle: commit, rollback, close, autocommit
Cursor ✅ execute, executemany, fetch*, callproc, description, rowcount
Cursor.nextset() ✅ Since 1.2.0 (#79) — raises NotSupportedError; CUBRID has no multiple result sets
Exception hierarchy ✅ All 10 PEP 249 exception classes
errno / sqlstate on DatabaseError ✅ Since 1.2.0 (#71) — 19 SQLSTATE mappings
Type objects ✅ STRING, BINARY, NUMBER, DATETIME, ROWID
Type constructors ✅ Date, Time, Timestamp, *FromTicks, Binary
Context managers ✅ Both Connection and Cursor

Connection Features

Feature Status Since Notes
connect_timeout ✅ 1.0.0 TCP connect timeout (seconds), each attempt including a redirect reconnect (#855)
read_timeout (sync) ✅ 1.2.0 (#81) Per socket send/receive call; a reply arriving in timely pieces is not cut off (#855)
read_timeout (async) ✅ 1.2.0 (#82) asyncio.wait_for() deadline for each whole request round trip; neither driver's timeout is an SQL wall-clock deadline or cancels the statement on the server (#855)
fetch_size ✅ 1.2.0 (#81) Configurable result batch size (default 100)
autocommit property ✅ 1.0.0 Get/set on Connection
Connection.ping() ✅ 1.2.0 (#70) Native CHECK_CAS health check, no SQL needed
get_server_version() ✅ 1.0.0 Returns version string (e.g. "11.2.0.0378")
get_last_insert_id() ✅ 1.0.0 After AUTO_INCREMENT INSERT
Schema getter ✅ 1.0.0 Raw GetSchemaPacket; original positional arguments retained
Owned schema rows ✅ 1.8.0 (#456) Sync/async fetch_schema_info() / close_schema_info(), keyword-only arg2=None; #457 live matrix covers CLASS/VCLASS/ATTRIBUTE/CONSTRAINT/PRIMARY_KEY/IMPORTED_KEYS/EXPORTED_KEYS on 10.2/11.4, not native parity or other schema codes
Dual-stack address fallback (sync) ✅ 1.0.0 getaddrinfo IPv4/IPv6 iteration
Dual-stack address fallback (async) ✅ 1.2.0 (#83) Async equivalent
Explicit connection recovery ✅ 1.2.0 (#70); 1.8.0 (#471, #485) ping(reconnect=True) can reconnect after disconnect, negative CHECK_CAS (broken CAS-to-DB link), or a check transport/protocol error; CAS_INFO[0]=0 is OUT_TRAN and keeps the session. Before the next request an OUT_TRAN session is verified with CHECK_CAS; only a failed probe (CAS restart, broker reset, CHANGE CLIENT) reconnects once, without SQL replay (#485). Auto escape mode re-probes per new physical session; explicit mode stays pinned; failed probe returns False. Async prebound parameterized SQL from an old generation is rejected before send. No dynamic SET or heterogeneous-failover guarantee.
Unknown-option reporting ✅ 1.8.0 (#377) An unrecognised connection keyword is ignored but emits UnknownConnectionOptionWarning (with a spelling suggestion); escalate with warnings.simplefilter("error", ...)

TLS / SSL

Feature Status Since Notes
Sync TLS — ssl=True (verified context) ✅ 1.3.0 (#85) Default secure context; enforces TLS 1.2 minimum (#145)
Sync TLS — ssl=ssl.SSLContext(...) ✅ 1.3.0 (#85) Custom context (caller controls minimum TLS version)
Sync TLS — ssl=False / None ✅ 1.3.0 Plaintext (default)
Async TLS ✅ 1.4.0 STARTTLS-style upgrade: plaintext CUBRS handshake then loop.start_tls() (ssl_handshake_timeout bounded) before OPEN_DATABASE (#136, #154). Default context enforces TLS 1.2 minimum (#145).

Async (pycubrid.aio)

Feature Status Since Notes
pycubrid.aio.connect() ✅ 1.1.0 Similar async surface; AsyncConnection.ping() added in 1.3.2 (native CHECK_CAS FC=32). create_lob() remains sync-only.
AsyncCursor execute / fetch / executemany / callproc ✅ 1.1.0 Sync-like cursor APIs with await; connection autocommit changes use set_autocommit() rather than a property setter
AsyncConnection.commit() / rollback() / close() ✅ 1.1.0
Async context managers ✅ 1.1.0 async with for both connection and cursor
Async read_timeout ✅ 1.2.0 (#82)
Async dual-stack fallback ✅ 1.2.0 (#83)
Async parameter binding parity ✅ 1.2.0 (#76, #77) Shares _escape_string with sync
Async TLS ✅ 1.4.0 STARTTLS-style upgrade: plaintext CUBRS handshake then loop.start_tls() (ssl_handshake_timeout bounded) before OPEN_DATABASE (#136, #154). Default context enforces TLS 1.2 minimum (#145).

Driver-Level Diagnostics

Feature Status Since Notes
Optional timing hooks (enable_timing=True) ✅ 1.0.0 (#54) Off by default; zero overhead when disabled — see PERFORMANCE.md
PYCUBRID_ENABLE_TIMING env var ✅ 1.0.0 Truthy: 1, true, yes (case-insensitive)
Connection.timing_stats ✅ 1.0.0 Returns TimingStats or None
TimingStats (connect / execute / fetch / close) ✅ 1.0.0 Nanosecond precision, thread-safe
DEBUG logging (pycubrid.connection, pycubrid.cursor, pycubrid.lob, pycubrid.aio.*) ✅ 1.x Driver emits opt-in debug logs for connection, cursor, LOB, and async operations

Data Types

CUBRID Type Python Type Status Notes
INTEGER, BIGINT, SMALLINT, SHORT int ✅
FLOAT, DOUBLE, MONETARY float ✅
NUMERIC, DECIMAL decimal.Decimal ✅
CHAR, VARCHAR, NCHAR, NVARCHAR, STRING str ✅
DATE datetime.date ✅
TIME datetime.time ✅
DATETIME, TIMESTAMP datetime.datetime ✅ Naive (no tzinfo)
DATETIMETZ, TIMESTAMPTZ datetime.datetime (tz-aware) ✅ Since 1.2.0 (#78) — IANA timezone keys
BIT, VARBIT bytes ✅
BLOB dict ✅ LOB handle dict with lob_type, lob_length, file_locator, packed_lob_handle[^lob]
CLOB dict ✅ LOB handle dict with lob_type, lob_length, file_locator, packed_lob_handle[^lob]
JSON Any (via deserializer) ✅ Since 1.2.0 (#72) — opt-in json_deserializer= on connect(); CAS protocol v8
SET frozenset ✅ Since 1.2.0 (#73) — opt-in decode_collections=True on connect()
MULTISET list ✅ Since 1.2.0 (#73) — opt-in decode_collections=True
SEQUENCE list ✅ Since 1.2.0 (#73) — opt-in decode_collections=True
Collections (default, decode_collections=False) bytes ⚠️ Raw CAS wire format for backward compatibility
OBJECT (OID) str ⚠️ Decoded as OID:@page|slot|volume; no high-level OID API
NULL None ✅

Statement / Cursor

Feature Status Since Notes
cursor.execute(sql, params) ✅ 1.0.0 Driver-side literal binding; the rendered SQL is sent with PREPARE_AND_EXECUTE (no server-side typed binding) — see PARAMETER_BINDING.md
cursor.executemany(sql, seq) ✅ 1.0.0 Batches non-SELECT DML via BatchExecutePacket; only SELECT falls back to the per-row loop
cursor.executemany_batch(sql_list, auto_commit=None) ✅ 1.0.0 Single round-trip BatchExecutePacket
cursor.callproc(name, params) ✅ 1.0.0 Stored procedure invocation
cursor.fetchone() / fetchmany() / fetchall() ✅ 1.0.0
Iterator protocol (for row in cursor) ✅ 1.0.0
cursor.description ✅ 1.0.0 PEP 249 7-tuple
cursor.rowcount ✅ 1.0.0
cursor.lastrowid ✅ 1.0.0

LOB

Feature Status Since Notes
Connection.create_lob(BLOB) ✅ 1.0.0
Connection.create_lob(CLOB) ✅ 1.0.0
LOB read/write ✅ 1.0.0
Insert bytes/str directly into BLOB/CLOB columns ✅ 1.0.0 Recommended over Lob parameter binding (which is not supported)

Performance Optimisations

Feature Status Since Notes
socket.recv_into zero-copy receives ✅ 1.0.0
TCP_NODELAY enabled ✅ 1.0.0
Cursor class cache ✅ 1.0.0
Pre-compiled struct packers ✅ 1.0.0
Type-dispatch table for fetch ✅ 1.0.0
Slice-based fetch ✅ 1.0.0
Batch executemany (BatchExecutePacket) ✅ 1.0.0

Operational

Concern Status Notes
Pure Python (no C extensions) ✅ pip install pycubrid only
PEP 561 typed package ✅ py.typed shipped
Connection pooling ❌ Not built-in. Use SQLAlchemy QueuePool or external pool — see Connection Guide
External profiling library dependency ❌ Only time.perf_counter_ns from stdlib

Test Coverage

Metric Value
Offline regressions make test; current cases in the test tree
Representative integration High-risk PR: Python 3.14/CUBRID 11.4; main/changed-weekly: also Python 3.11/CUBRID 10.2
Full integration (release workflow_call + manual dispatch) 20 (Python 5 versions × CUBRID 4 versions)
Stress tests Threaded (16 workers × 25 inserts, 32 readers) and asyncio.gather (16 workers, 32 readers)
Reconnect / network edge cases Reset, timeout, broken pipe and partial-read regressions in the current test tree
Full-run coverage Enforced 95% floor; measured results, not a routine PR-smoke claim

[^lob]: Fetching a LOB column returns a handle dictionary, not the content itself. Use pycubrid.lob.Lob with packed_lob_handle to read bytes, or insert str/bytes directly when writing CLOB/BLOB values.

See also: Connection Guide · Type System · API Reference · Performance Guide · Changelog