SQLAlchemy 2.0–2.1 dialect for the CUBRID database — Python ORM, schema reflection, Alembic migrations, and type mapping for SQLAlchemy and CUBRID-specific types.
🇰🇷 한국어 · 🇺🇸 English · 🇨🇳 中文 · 🇮🇳 हिन्दी · 🇩🇪 Deutsch · 🇷🇺 Русский
Status: Production/Stable —
sqlalchemy-cubridis a maintained SQLAlchemy dialect for CUBRID supporting SQLAlchemy 2.0–2.1 and CUBRID 10.2–11.4. Note: Versions prior to 1.0.0 used inconsistent numbering (0.x, 2.x). The canonical version history starts at 1.0.0.
CUBRID is a high-performance open-source relational database, widely adopted in Korean public-sector and enterprise applications. Until now, there was no actively maintained SQLAlchemy dialect that supports the modern 2.0–2.1 API.
sqlalchemy-cubrid bridges that gap:
- Full SQLAlchemy 2.0–2.1 dialect with statement caching and PEP 561 typing
- Extensive offline test suite — no database required to run it; CI enforces a minimum 95% line-coverage gate (
--cov-fail-under=95in theoffline-testsjob, CI badge above) - Concurrency stress tests —
QueuePoolsync threaded + asyncio.gather workloads validated against live CUBRID - SQLAlchemy 2.1-ready compat shim — private API access wrapped in
_compat.py; dependency pin now>=2.0,<2.3covering SA 2.0 and 2.1 - Tested against 4 CUBRID versions (10.2, 11.0, 11.2, 11.4) across Python 3.10 -- 3.14
- CUBRID-specific DML constructs:
ON DUPLICATE KEY UPDATE,MERGE,REPLACE INTO - Alembic migration support out of the box
- Three driver options — pure Python (
cubrid+pycubrid://, recommended), async pure Python (cubrid+aiopycubrid://), or the legacy C-extension (cubrid:///cubrid+cubriddb://)
- Status: Production/Stable
- Supported matrix: SQLAlchemy
>=2.0,<2.3, CUBRID10.2,11.0,11.2,11.4, Python3.10–3.14 - Integration CI runs a reduced matrix on every PR — Python 3.14 × CUBRID 11.4 (newest) and Python 3.10 × CUBRID 10.2 (oldest); the full Python 3.10–3.14 × CUBRID 10.2–11.4 matrix runs nightly, on demand, and as the release gate (integration-full.yml). Intermediate Python versions (3.11–3.13) are supported and validated via the offline test suite on every PR
- SQLAlchemy 2.1 pre-releases are exercised by a non-gating
--precanary CI job - See Known Limitations for behavior boundaries and unsupported features
flowchart TD
app["Application"] --> sa["SQLAlchemy Core/ORM"]
sa --> dialect["CubridDialect"]
dialect --> pycubrid["pycubrid driver"]
dialect --> cext["CUBRIDdb driver"]
dialect --> aio["pycubrid.aio async driver"]
pycubrid --> server["CUBRID Server"]
cext --> server
aio --> server
flowchart TD
expr["SQL Expression"] --> compiler["CubridSQLCompiler"] --> sql["SQL String"]
- Python 3.10+
- SQLAlchemy 2.0 – 2.1
- pycubrid (pure Python, recommended) or the legacy CUBRIDdb C extension built from cubrid-python v11.3.0.51 or later
pip install sqlalchemy-cubridWith the pure Python driver (sync and async):
pip install "sqlalchemy-cubrid[pycubrid]"The [pycubrid] extra supports both cubrid+pycubrid:// and
cubrid+aiopycubrid://. It includes SQLAlchemy's asyncio extra (greenlet);
greenlet may need build tools if a compatible wheel is unavailable.
With Alembic support:
pip install "sqlalchemy-cubrid[alembic]"With the legacy CUBRIDdb C-extension driver (the bare cubrid:// URL), build
CUBRIDdb from cubrid-python v11.3.0.51 or
later; see Driver Compatibility.
The
[cubrid]and[cubriddb]extras are deprecated. They install theCUBRID-Pythonpackage from PyPI, whose newest release is 9.3.x (2015). That release is untested with this dialect: it returnsBIGINTasstrand fails parts of the integration suite. The dialect warns (SAWarning) at the first connection when it finds a CUBRIDdb older than 11.3. For new projects, use the recommended pure-Python[pycubrid]driver (thecubrid+pycubrid://URL). To select the legacy C-extension driver explicitly, use thecubrid+cubriddb://URL.
from sqlalchemy import create_engine, text
engine = create_engine("cubrid+pycubrid://dba:password@localhost:33000/demodb")
with engine.connect() as conn:
result = conn.execute(text("SELECT 1"))
print(result.scalar())from sqlalchemy import create_engine, String
from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(100))
email: Mapped[str] = mapped_column(String(200), unique=True)
engine = create_engine("cubrid+pycubrid://dba:password@localhost:33000/demodb")
Base.metadata.create_all(engine)
with Session(engine) as session:
user = User(name="Alice", email="alice@example.com")
session.add(user)
session.commit()from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy import text
engine = create_async_engine("cubrid+aiopycubrid://dba:password@localhost:33000/demodb")
async with AsyncSession(engine) as session:
result = await session.execute(text("SELECT 1"))
print(result.scalar())CUBRID has no RETURNING clause. For ORM inserts, call await session.flush()
inside the transaction block to populate the auto-increment PK on the object before commit:
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
from sqlalchemy import String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
name: Mapped[str] = mapped_column(String(100))
engine = create_async_engine("cubrid+aiopycubrid://dba:password@localhost:33000/demodb")
async with AsyncSession(engine) as session:
async with session.begin():
user = User(name="Alice")
session.add(user)
await session.flush() # populates user.id without committing
print(f"Inserted id={user.id}")For raw Core inserts where you need the auto-increment PK, run SELECT LAST_INSERT_ID()
after the statement (see Known Limitations).
- Type mapping for SQLAlchemy standard and CUBRID-specific types — numeric, string, date/time, bit, LOB, collection, and JSON types
- SQL compilation -- SELECT, JOIN, CAST, LIMIT/OFFSET, subqueries, CTEs, window functions
- DML extensions --
ON DUPLICATE KEY UPDATE,MERGE,REPLACE INTO,FOR UPDATE,TRUNCATE - DDL support --
COMMENT,IF NOT EXISTS/IF EXISTS,AUTO_INCREMENT - Schema reflection -- tables, views, columns, PKs, FKs, indexes, unique constraints, comments
- Alembic migrations via
CubridImpl(registered automatically when the dialect loads) - Three CUBRID MVCC isolation levels —
READ COMMITTED(default),REPEATABLE READ,SERIALIZABLE - Async support —
create_async_engine("cubrid+aiopycubrid://...")via pycubrid.aio
- No
RETURNING—INSERT/UPDATE/DELETE ... RETURNINGnot supported; for ORM useawait session.flush()to populateidon the object (see Async Quick Start), or for Core usecursor.lastrowid/SELECT LAST_INSERT_ID()after the statement - No sequences — CUBRID uses
AUTO_INCREMENTonly - Single effective schema — CUBRID exposes one schema per connection (the current user's schema);
get_schema_names()reports that one schema and every reflection method honoursschema=consistently (the default schema is reflected; any other schema yields no tables/views). Owner-qualified cross-schema reflection is not supported. - Uncommitted DDL holds schema locks — CUBRID DDL is transactional (
ROLLBACKundoes it; only client autocommit, which the dialect turns off, commits it early), so by default a whole Alembic upgrade is one transaction (transactional_ddl = True) and keeps the tables it touches locked until it commits; usetransaction_per_migration=Truefor long or large-table migrations - SQLAlchemy 2.0–2.1 only — pinned to
<2.3; SA 2.1 pre-releases are forward-tested via shims and a--precanary CI job (details) - Async requires pycubrid >= 1.8.0,<2.0 — the
cubrid+aiopycubrid://driver needs the async-capable pycubrid package line currently supported by this project - CARDINALITY() broken —
func.cardinality()raisesCompileErrorwith workaround guidance; the CUBRID server has a known bug - Reserved words auto-quoted — Column names matching CUBRID reserved words (
day,count,value, etc.) are automatically double-quoted in DDL; see reserved word list - Timezone type reflection — CUBRID's
TIMESTAMPTZ,TIMESTAMPLTZ,DATETIMETZandDATETIMELTZreflect as distinct SQLAlchemy types (sqlalchemy_cubrid.TIMESTAMPTZ/TIMESTAMPLTZ/DATETIMETZ/DATETIMELTZ, each withtimezone=True) rather than collapsing into plainTIMESTAMP/DATETIME(#181, #442); see Type Mapping
| Guide | Description |
|---|---|
| Connection | Connection strings, URL format, driver setup, pool tuning |
| Type Mapping | Full type mapping, CUBRID-specific types, collection types |
| DML Extensions | ON DUPLICATE KEY UPDATE, MERGE, REPLACE INTO, query trace |
| Isolation Levels | The three CUBRID MVCC isolation levels, configuration |
| Alembic Migrations | Setup, configuration, limitations, batch workarounds |
| Feature Support | Comparison with MySQL, PostgreSQL, SQLite |
| ORM Cookbook | Practical ORM examples, relationships, queries |
| Development | Dev setup, testing, Docker, coverage, CI/CD |
| Driver Compatibility | CUBRID-Python driver versions and known issues |
| Troubleshooting | Common issues, error solutions, debugging techniques |
| Async Connection | Async engine setup with cubrid+aiopycubrid:// |
| Component | Supported versions |
|---|---|
| Python | 3.10, 3.11, 3.12, 3.13, 3.14 |
| CUBRID | 10.2, 11.0, 11.2, 11.4 |
| SQLAlchemy | 2.0–2.1 |
| Alembic | >=1.7.2 |
| pycubrid (sync) | >=1.8.0,<2.0 |
| pycubrid (async) | >=1.8.0,<2.0 |
from sqlalchemy import create_engine
engine = create_engine("cubrid+pycubrid://dba:password@localhost:33000/demodb")The recommended way is the pure-Python pycubrid driver: create_engine("cubrid+pycubrid://dba:password@localhost:33000/demodb"). The driver requires no CUBRID native libraries. The [pycubrid] extra also installs greenlet for SQLAlchemy's async bridge; it may require build tools when no compatible wheel is available. The bare cubrid:// URL uses the legacy CUBRIDdb C-extension driver, which must be built from cubrid-python v11.3.0.51 or later (the PyPI CUBRID-Python 9.3.x release that the deprecated [cubriddb] extra installs is untested); to select it explicitly and unambiguously use cubrid+cubriddb://.
Yes. sqlalchemy-cubrid is built for SQLAlchemy 2.0–2.1 and supports the 2.0-style API including Session.execute(), typed Mapped[] columns, and statement caching.
Yes. Install with pip install "sqlalchemy-cubrid[alembic]". The CUBRID migration implementation registers itself when the dialect loads, so the default env.py works unchanged with synchronous URLs; for cubrid+aiopycubrid://, use Alembic's async template (alembic init -t async). CUBRID DDL is transactional, so by default a failed alembic upgrade is rolled back whole, version bump included; set transaction_per_migration=True to commit after each revision for long or large-table migrations.
Python 3.10, 3.11, 3.12, 3.13, and 3.14.
No. CUBRID does not support INSERT ... RETURNING or UPDATE ... RETURNING. Use cursor.lastrowid or SELECT LAST_INSERT_ID() instead.
from sqlalchemy_cubrid import insert
stmt = insert(users).values(name="Alice").on_duplicate_key_update(name="Alice Updated")cubrid:// uses the C-extension driver (CUBRIDdb) which requires compilation. cubrid+pycubrid:// uses the pure Python driver, which requires no CUBRID native libraries. The [pycubrid] extra includes greenlet, whose installation may require build tools when no compatible wheel is available. cubrid+aiopycubrid:// uses the async variant of the pure Python driver for use with create_async_engine and AsyncSession.
Recommendation: for new projects prefer
cubrid+pycubrid://(pure Python driver, no CUBRID native libraries). The[pycubrid]extra'sgreenletdependency may need build tools. Usecubrid+cubriddb://with CUBRIDdb built from cubrid-python v11.3.0.51 or later when you specifically need the legacy C-extension driver.
Yes. Use create_async_engine("cubrid+aiopycubrid://...") with the pycubrid async driver. Requires pycubrid>=1.8.0,<2.0. Both pycubrid dialects use native Connection.ping(False) / AsyncConnection.ping(False) for pool_pre_ping, and all Core and ORM features work with AsyncSession.
- pycubrid — Pure Python DB-API 2.0 driver for CUBRID
- cubrid-cookbook-python — Production-ready Python examples for CUBRID
See ROADMAP.md for this project's direction and next milestones.
For the ecosystem-wide view, see the CUBRID Labs Ecosystem Roadmap.
See CONTRIBUTING.md for guidelines and docs/DEVELOPMENT.md for development setup.
New to CUBRID? Pick the repository that matches what you want to work on:
- Documentation and runnable examples: cubrid-cookbook-python
- Pure-Python driver fixes: pycubrid
- SQLAlchemy dialect fixes: sqlalchemy-cubrid
Most first issues can be developed and tested with the offline checks in CONTRIBUTING.md — no Docker or CUBRID server needed. Live CUBRID verification can be completed by CI and maintainers.
Browse open good first issue tasks.
Report vulnerabilities via email -- see SECURITY.md. Do not open public issues for security concerns.
This project is part of CUBRID Lab, an independent open-source initiative for CUBRID developer tooling, and is not affiliated with, sponsored by, or endorsed by CUBRID Corporation or the official CUBRID project.
MIT -- see LICENSE.
