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) |
| 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