Skip to content

Support Matrix

Compatibility and feature support for sqlalchemy-cubrid releases.


Version Compatibility

SQLAlchemy

SQLAlchemy Version Status Notes
2.0.x ✅ Supported Minimum required version
2.1.x ✅ Supported CI-tested on SQLAlchemy 2.1.1
≥ 2.2 ❌ Not supported Excluded by the <2.2 cap until 2.2 ships and passes the full matrix; code uses private SA internals (see below)
< 2.0 ❌ Not supported SA 1.x API removed

Why <2.2? The dialect accesses private SQLAlchemy APIs that may change without notice:

Private API Location Usage
select._limit_clause compiler.py:104 LIMIT clause compilation
select._offset_clause compiler.py:105 OFFSET clause compilation
select._for_update_arg compiler.py:93 FOR UPDATE clause

Literal detection and typed-bind recreation now go through local _compat.py helpers, so the direct private API surface is down to these three attributes.

Python

Python Version Status
3.11 ✅ Supported
3.12 ✅ Supported
3.13 ✅ Supported
3.14 ✅ Supported
3.15 ✅ Supported (standard GIL builds; see the notes below)
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. The scope of the claim:

  • pycubrid (cubrid+pycubrid://, cubrid+aiopycubrid://): pycubrid declares Python 3.15 from the release after 1.10.0 (cubrid-lab/pycubrid#831). The [pycubrid] floor stays pycubrid>=1.8.0,<2.0; pycubrid 1.10.0 passes the sync and async integration suites on 3.15 but does not declare it. On 3.15, pycubrid 1.10.0 and earlier reject ?charset=ksc5601, because Python 3.15 maps that codec alias to CP949; use ?charset=euckr, or the pycubrid release that declares 3.15, which maps ksc5601 to EUC-KR itself.
  • CUBRIDdb (cubrid://): the official cubrid-python v11.3.0.51 source builds for 3.15 with the CI recipe on ubuntu-latest, and the full matrix runs its lanes on 3.15. With GCC 14 the stock build fails on Python 3.14 and 3.15 alike (the two versions compared; 3.11–3.13 were not tried): GCC 14 makes -Wincompatible-pointer-types and -Wint-conversion errors. This project does not add warning-downgrade flags to CI; see Driver Compatibility.
  • Not run on 3.15: the SQLAlchemy compliance lanes stay on Python 3.14/CUBRID 11.4 and Python 3.11/CUBRID 10.2.
  • Garbage-collected connections: with SQLAlchemy 2.1, a pooled connection lost inside a reference cycle (such as one with its transaction still open) is neither terminated nor returned to the pool on 3.15. This is a SQLAlchemy 2.1/CPython 3.15 interaction that affects every dialect, and the integration test for it is an expected failure there; see Connection Guide.
  • Environment: Decimal-subclass parameters through pycubrid need the C _decimal module, which a Python built without libmpdec lacks; pycubrid then raises ProgrammingError for them, and 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 observed without it, so the Decimal-subclass round trip is skipped in CI's 3.15 cells. 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

Drivers

Driver Install URL Scheme Status
CUBRIDdb (CCI) Build from cubrid-python v11.3.0.51+ (how); the [cubrid] / [cubriddb] extras are deprecated and install the untested PyPI 9.3.x cubrid:// / cubrid+cubriddb:// ✅ Supported C-extension driver (v11.3.0.51+ only)
pycubrid (Pure Python) pip install "sqlalchemy-cubrid[pycubrid]" cubrid+pycubrid:// ✅ Supported
pycubrid async pip install "sqlalchemy-cubrid[pycubrid]" cubrid+aiopycubrid:// ✅ Supported

Feature Support

SQLAlchemy Core

Feature Status Notes
create_engine() ✅ cubrid://, cubrid+cubrid://, cubrid+cubriddb://, and cubrid+pycubrid:// schemes
Async engine ✅ create_async_engine("cubrid+aiopycubrid://...")
SQL compilation ✅ SELECT, INSERT, UPDATE, DELETE, JOIN, subqueries
DDL compilation ✅ CREATE TABLE, ALTER, DROP, AUTO_INCREMENT, COMMENT
Type system ✅ All shipped CUBRID types compile; reflection coverage is listed separately below
Schema reflection ✅ Tables, columns, PKs, FKs, indexes, unique constraints, comments
Transaction management ✅ commit, rollback, savepoint (no RELEASE SAVEPOINT)
Connection pooling ✅ SA pool with pool_pre_ping, disconnect detection
Statement caching ✅ supports_statement_cache = True
executemany (text(), UPDATE, DELETE) ✅ None binds as NULL and rowcount is the total across parameter sets on every driver (supports_sane_multi_rowcount = True). On cubrid://, the dialect runs each parameter set as its own execute() to work around a CUBRIDdb bug. This costs one extra statement prepare per row: about 2.9× slower than the driver's own executemany in a 1000-row UPDATE. pycubrid keeps its prepare-once executemany. A multi-row Core insert() that uses insertmanyvalues is unaffected. An INSERT into a table with a column whose type defines bind_expression() falls back to executemany (#421) and uses the per-row guard on cubrid://. See Driver Compatibility, Known Issue 7

SQLAlchemy ORM

Feature Status Notes
Declarative models ✅
Relationships ✅
Session / Unit of Work ✅
Batched UPDATE / DELETE row checks ✅ Batched flushes check the matched-row count on every driver. On cubrid:// this relies on the per-row executemany guard, see Driver Compatibility, Known Issue 7
Query API ✅
Hybrid properties ✅

Alembic

Feature Status Notes
Auto-registration ✅ Registered when the dialect loads (no env.py import)
Schema migrations ✅ CREATE, ALTER, DROP
Autogenerate ✅ Including collection types (SET, MULTISET, SEQUENCE); CUBRID-specific member types render as cubrid_types.MONETARY(), cubrid_types.BIT(...) and so on
Transactional DDL ✅ DDL rolls back with the transaction; the whole upgrade is atomic by default (transaction_per_migration=True for per-revision commits)

DML Extensions

Feature Status Notes
ON DUPLICATE KEY UPDATE ✅ Via sqlalchemy_cubrid.insert()
MERGE statement ✅ Via sqlalchemy_cubrid.merge()
REPLACE INTO ✅ Via sqlalchemy_cubrid.replace()
GROUP_CONCAT ✅
TRUNCATE TABLE ✅ Committed with the enclosing transaction
FOR UPDATE ✅ Including OF clause
Recursive CTE ✅ WITH RECURSIVE (verified on CUBRID 10.2, 11.0, 11.2 and 11.4)
Window functions ✅ ROW_NUMBER, RANK, LAG, LEAD, etc.
Index hints ✅ Via SA with_hint() / suffix_with()

Known Limitations

Feature Status Notes
JSON type ✅ Since v1.2.0, requires CUBRID ≥ 10.2
Native Enum ✅ Native ENUM('a', 'b', ...) — verified on 10.2–11.4 (#343)
Interval type ❌ Not supported by CUBRID
RETURNING clause ❌ INSERT/UPDATE/DELETE ... RETURNING not supported
BOOLEAN ⚠️ Mapped to SMALLINT (0/1) — no native boolean. col.is_(True) / is_not(False) etc. are emulated with null-safe <=> (col <=> 1, (col <=> 1) = 0) because CUBRID's IS accepts only NULL/TRUE/FALSE (#465); IS [NOT] NULL, col == True and not_(col) compile natively. The dialect groups a boolean expression used as a SELECT-list item, a CAST or function argument, or a window ORDER BY/PARTITION BY, so select(and_(...)), select(exists()...) and over(order_by=exists()) work (#966, #968, #970); a boolean expression is still rejected as a GROUP BY item. See Boolean SELECT-list items for the supported positions and that limitation, and Boolean predicates for IS/IS NOT
Sequences ❌ CUBRID uses AUTO_INCREMENT only
CHECK constraint reflection ❌ get_check_constraints() returns empty list
Multi-schema ❌ CUBRID has single-schema model
RELEASE SAVEPOINT ❌ No-op (CUBRID doesn't support it)
Lateral joins ❌ CUBRID lacks LATERAL subquery support
Full-text search ❌ No MATCH … AGAINST syntax
Async DBAPI ✅ Via pycubrid.aio async driver (cubrid+aiopycubrid://), requires pycubrid >= 1.8.0,<2.0

Type Mapping

Declared/compiled types and reflected types are not identical. REAL, MONETARY, and OBJECT compile correctly and can be declared in models, but they are not present in dialect.ischema_names, so reflection will not auto-map them back. The reflection column describes schema metadata mapping; the Python type column describes the intended value shape. Neither column promises that every driver binds and returns that value unchanged. See Type Mapping and Driver Compatibility for BLOB/CLOB locators, empty BIT values, CUBRIDdb DECIMAL truncation and collection binding differences.

CUBRID Type SQLAlchemy Type Python Type Reflection
INTEGER sa.Integer int ✅
BIGINT sa.BigInteger int ✅
SMALLINT sa.SmallInteger int ✅
FLOAT sa.Float float ✅
REAL REAL float ❌ Declared/compiled only
DOUBLE sa.Float float ✅
NUMERIC / DECIMAL sa.Numeric decimal.Decimal ✅
MONETARY MONETARY float ❌ Declared/compiled only
CHAR sa.CHAR str ✅
VARCHAR sa.String str ✅
NCHAR NCHAR str ✅
NVARCHAR NVARCHAR str ✅
STRING STRING str ✅
DATE sa.Date datetime.date ✅
TIME sa.Time datetime.time ✅
DATETIME sa.DateTime datetime.datetime ✅
TIMESTAMP sa.TIMESTAMP datetime.datetime ✅
TIMESTAMPTZ TIMESTAMPTZ (timezone=True) datetime.datetime ✅ Distinct reflected type, not collapsed into TIMESTAMP
TIMESTAMPLTZ TIMESTAMPLTZ (timezone=True) datetime.datetime ✅ Distinct reflected type, not collapsed into TIMESTAMP
DATETIMETZ DATETIMETZ (timezone=True) datetime.datetime ✅ Distinct reflected type, not collapsed into DATETIME
DATETIMELTZ DATETIMELTZ (timezone=True) datetime.datetime ✅ Distinct reflected type, not collapsed into DATETIME
BIT(n) / BIT VARYING(n) BIT(n) / BIT(n, varying=True) bytes ✅ Length and VARYING preserved
BIT(n*8) sa.BINARY(n) (BINARY() → BIT(8)) bytes ✅ Reflects as BIT(n*8)
BIT VARYING(n*8) sa.VARBINARY(n) (VARBINARY() → BIT VARYING) bytes ✅ Reflects as BIT VARYING(n*8)
CHAR(32) sa.Uuid / sa.UUID uuid.UUID; str with as_uuid=False ✅ Reflects as CHAR(32)
BLOB sa.LargeBinary bytes (documented); non-NULL reads currently return a driver LOB locator, see Driver Compatibility, Known Issue 6 ✅
CLOB CLOB str (documented); non-NULL reads currently return a driver LOB locator, see Driver Compatibility, Known Issue 6 ✅
SET SET Collection ✅
MULTISET MULTISET Collection ✅
SEQUENCE SEQUENCE Collection ✅
OBJECT OBJECT OID reference ❌ Declared/compiled only

CUBRID has no BINARY, VARBINARY or UUID type. sa.BINARY(n) / sa.VARBINARY(n) compile to bit strings sized in bits (BIT(n*8) / BIT VARYING(n*8)); BINARY pads shorter values with \x00. An empty b"" is not preserved: it reads back as None (or as zero bytes from BINARY(n) on pycubrid). sa.Uuid and sa.UUID are stored as 32-character hex in CHAR(32). Reflected BIT columns keep their length, so Alembic autogenerate reports no false type change for these columns. See Standard SQL Types.


CI Matrix

Validation Routine execution Full compatibility
Offline Full suite with 95% coverage: Ubuntu/Python 3.12 for PRs; Python 3.11 and 3.15 for main, changed-weekly and dispatched runs 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.

SQLAlchemy compliance lanes

The official SQLAlchemy compliance suite blocks merges for both drivers. Each lane has its own reviewed known-failure baseline in test/known_failures.txt; see Development Guide.

Lane Driver SQLAlchemy CUBRID (PR CI) Known failures (11.4 / 10.2)
cubrid@sa2.0 CUBRIDdb (cubrid-python v11.3.0.51) 2.0.53 11.4 68 / 61
pycubrid@sa2.0 pycubrid 1.8.0 (declared floor) 2.0.53 10.2 54 (gated on 10.2 only)
pycubrid@sa2.1 pycubrid 1.8.0 (declared floor) 2.1.1 11.4 58 / 51

pycubrid is the recommended driver. Its compliance lanes pin the declared floor of pycubrid>=1.8.0,<2.0 on purpose, so the oldest release users can install is tested. The integration lanes install the latest pycubrid release instead, and behavior that changed after 1.8.0 has its own regression tests, which are strict xfails on 1.8.0 (#479).

Most known failures are shared by both drivers: CUBRID backend rules (identifier case folding, [ ] identifier delimiters, no window frame clause, per-row foreign-key checks, single-precision FLOAT) and open dialect reflection/DDL bugs listed in test/known_failures.txt. Only the CUBRIDdb lane carries its NUMERIC truncation and integer-division entries.

Test Coverage

Offline and integration test counts grow with every PR; see the offline-tests / integration-tests CI job output for current numbers instead of a pinned snapshot here.

Metric Value
Coverage threshold 95% (CI-enforced, --cov-fail-under=95 in the offline-tests job)

See also: Connection Guide · Type System · Feature Support · Driver Compatibility · Changelog