Feature Support Comparison¶
A comprehensive comparison of sqlalchemy-cubrid capabilities against the mature SQLAlchemy dialects for MySQL, PostgreSQL, and SQLite.
Use this document to understand what the CUBRID dialect supports, what it doesn't, and how it compares to other database backends when choosing a dialect for your project.
Table of Contents¶
- Legend
- Summary
- DML — Data Manipulation Language
- DDL — Data Definition Language
- Query Features
- Type System
- Schema Reflection
- Transactions & Connections
- Dialect Engine Features
- CUBRID-Specific Features
- CUBRID-Specific DML Constructs
- Index Hints
- Known Limitations & Roadmap
Legend¶
| Symbol | Meaning |
|---|---|
| ✅ | Fully supported |
| ⚠️ | Partial or emulated support |
| ❌ | Not supported |
Summary¶
High-level overview by feature category.
| Category | CUBRID | MySQL | PostgreSQL | SQLite |
|---|---|---|---|---|
| DML | ✅ | ✅ | ✅ | ⚠️ |
| DDL | ✅ | ✅ | ✅ | ⚠️ |
| Query features | ✅ | ✅ | ✅ | ✅ |
| Type system | ⚠️ | ✅ | ✅ | ⚠️ |
| Schema reflection | ✅ | ✅ | ✅ | ⚠️ |
| Transactions | ✅ | ✅ | ✅ | ⚠️ |
| Engine features | ⚠️ | ✅ | ✅ | ⚠️ |
DML — Data Manipulation Language¶
| Feature | CUBRID | MySQL | PostgreSQL | SQLite |
|---|---|---|---|---|
| INSERT … RETURNING | ❌ | ❌ | ✅ | ✅ |
| UPDATE … RETURNING | ❌ | ❌ | ✅ | ✅ |
| DELETE … RETURNING | ❌ | ❌ | ✅ | ✅ |
| INSERT … DEFAULT VALUES | ✅ | ❌ | ✅ | ✅ |
| Empty INSERT | ✅ | ⚠️ | ✅ | ✅ |
| Multi-row INSERT | ✅ | ✅ | ✅ | ✅ |
| INSERT FROM SELECT | ✅ | ✅ | ✅ | ✅ |
| ON DUPLICATE KEY UPDATE | ✅ | ✅ | ❌ | ❌ |
| MERGE statement | ✅ | ❌ | ❌ | ❌ |
| REPLACE INTO | ✅ | ✅ | ❌ | ✅ |
| FOR UPDATE (row locking) | ✅ | ✅ | ✅ | ❌ |
| UPDATE with LIMIT | ✅ | ✅ | ❌ | ❌ |
| TRUNCATE TABLE | ✅ | ✅ | ✅ | ❌ |
| IS DISTINCT FROM | ❌ | ❌ | ✅ | ❌ |
| Postfetch LASTROWID | ❌ | ✅ | ❌ | ✅ |
Notes¶
- RETURNING: CUBRID has no
RETURNINGclause. Auto-generated keys cannot be fetched in the same round-trip as the INSERT;postfetch_lastrowidis also unavailable. - DEFAULT VALUES: CUBRID supports
INSERT INTO t DEFAULT VALUES. The dialect setssupports_default_values = True. - ON DUPLICATE KEY UPDATE: CUBRID supports
INSERT … ON DUPLICATE KEY UPDATEwithVALUES()references (identical to MySQL pre-8.0 syntax). Usesqlalchemy_cubrid.insert(table).on_duplicate_key_update(col=value). See CUBRID-Specific DML Constructs for usage examples. - MERGE: CUBRID supports the full SQL MERGE statement. Use
sqlalchemy_cubrid.dml.merge(target)with.using(),.on(),.when_matched_then_update(), and.when_not_matched_then_insert(). See CUBRID-Specific DML Constructs. - FOR UPDATE: CUBRID supports
SELECT … FOR UPDATE [OF col1, col2]. NOWAIT and SKIP LOCKED are not supported. - UPDATE with LIMIT: CUBRID and MySQL both support
UPDATE … LIMIT n. PostgreSQL and SQLite do not. - TRUNCATE: CUBRID supports
TRUNCATE TABLE. The dialect includesTRUNCATEin autocommit detection. - IS DISTINCT FROM: Not a CUBRID SQL operator. SQLAlchemy may emulate it with
CASEexpressions on dialects that lack native support.
DDL — Data Definition Language¶
| Feature | CUBRID | MySQL | PostgreSQL | SQLite |
|---|---|---|---|---|
| ALTER TABLE | ✅ | ✅ | ✅ | ⚠️ |
| Table comments | ✅ | ✅ | ✅ | ❌ |
| Column comments | ✅ | ✅ | ✅ | ❌ |
| CREATE … IF NOT EXISTS | ✅ | ✅ | ✅ | ✅ |
| DROP … IF EXISTS | ✅ | ✅ | ✅ | ✅ |
| Temporary tables | ❌ | ✅ | ✅ | ✅ |
| Multiple schemas | ❌ | ✅ | ✅ | ⚠️ |
Notes¶
- ALTER TABLE: CUBRID supports standard
ALTER TABLEfor adding/dropping columns and constraints. SQLite has limited ALTER support (add column only; no drop/rename column before 3.35). - Comments: CUBRID supports inline
COMMENTsyntax for both tables (e.g.,CREATE TABLE t (...) COMMENT = 'text') and columns (e.g.,col TYPE COMMENT 'text'). The dialect implementsSetTableComment,DropTableComment, andSetColumnCommentDDL constructs. Comment reflection is supported viaget_table_comment()and column comments inget_columns(). - IF NOT EXISTS / IF EXISTS: CUBRID supports
CREATE TABLE IF NOT EXISTSandDROP TABLE IF EXISTS. The base SA compiler handles these natively. - Temporary tables: CUBRID does not support
CREATE TEMPORARY TABLEor session-scoped tables. - Multiple schemas: CUBRID operates in a single-schema model. MySQL uses databases as schemas. SQLite can attach databases but does not have true schema support.
Query Features¶
| Feature | CUBRID | MySQL | PostgreSQL | SQLite |
|---|---|---|---|---|
| Common Table Expressions (WITH) | ✅ | ✅ | ✅ | ✅ |
| Recursive CTEs (WITH RECURSIVE) | ✅ | ✅ | ✅ | ✅ |
| CTEs on DML | ❌ | ❌ | ✅ | ❌ |
| Window functions | ✅ | ✅ | ✅ | ✅ |
| NULLS FIRST / NULLS LAST | ✅ | ❌ | ✅ | ✅ |
| GROUP_CONCAT | ✅ | ✅ | ❌ | ✅ |
| INTERSECT | ✅ | ✅ | ✅ | ✅ |
| EXCEPT | ✅ | ✅ | ✅ | ✅ |
| DISTINCT | ✅ | ✅ | ✅ | ✅ |
| LIMIT / OFFSET | ✅ | ✅ | ✅ | ✅ |
| Lateral joins | ❌ | ❌ | ✅ | ❌ |
| Full-text search (MATCH … AGAINST) | ❌ | ✅ | ✅ | ✅ |
| Query trace / EXPLAIN | ⚠️ | ✅ | ✅ | ✅ |
| ### Notes |
- CTEs: CUBRID 11.0+ supports
WITHclauses for read queries. Writable CTEs (WITH … INSERT/UPDATE/DELETE) are not supported. - Recursive CTEs: CUBRID 11.x+ supports
WITH RECURSIVEfor recursive queries. SQLAlchemy's base compiler generates correct syntax — no dialect-specific compilation needed. - Window functions: CUBRID supports
ROW_NUMBER(),RANK(),DENSE_RANK(), and other window functions withOVER(PARTITION BY … ORDER BY …). The SA base compiler handles these natively. - NULLS FIRST / NULLS LAST: CUBRID supports
ORDER BY col ASC NULLS FIRSTandORDER BY col DESC NULLS LAST. The SA base compiler handles these natively. - GROUP_CONCAT: CUBRID supports
GROUP_CONCAT([DISTINCT] expr [ORDER BY …] [SEPARATOR '…']). Usesa.func.group_concat(column). - LIMIT / OFFSET: CUBRID uses MySQL-style
LIMIT [offset,] countsyntax. When only an offset is given, the dialect emitsLIMIT offset, 1073741823(max int) as a workaround. - Lateral joins: CUBRID does not support
LATERALsubqueries. TheLATERALkeyword causes a syntax error. - Full-text search: CUBRID does not support
MATCH … AGAINSTsyntax or full-text indexes. - Query trace: CUBRID uses
SET TRACE ON/SHOW TRACEinstead of standardEXPLAIN. The dialect providestrace_query()as a utility function — see CUBRID-Specific DML Constructs.
Type System¶
Standard SQL Types¶
| Type | CUBRID | MySQL | PostgreSQL | SQLite |
|---|---|---|---|---|
| SMALLINT | ✅ | ✅ | ✅ | ✅ |
| INTEGER | ✅ | ✅ | ✅ | ✅ |
| BIGINT | ✅ | ✅ | ✅ | ✅ |
| NUMERIC / DECIMAL | ✅ | ✅ | ✅ | ✅ |
| FLOAT | ✅ | ✅ | ✅ | ✅ |
| DOUBLE / REAL | ✅ | ✅ | ✅ | ✅ |
| BOOLEAN | ⚠️ | ⚠️ | ✅ | ⚠️ |
| DATE | ✅ | ✅ | ✅ | ✅ |
| TIME | ✅ | ✅ | ✅ | ✅ |
| DATETIME | ✅ | ✅ | ✅ | ✅ |
| TIMESTAMP | ✅ | ✅ | ✅ | ✅ |
| CHAR | ✅ | ✅ | ✅ | ✅ |
| VARCHAR | ✅ | ✅ | ✅ | ✅ |
| NCHAR / NVARCHAR | ✅ | ⚠️ | ❌ | ❌ |
| TEXT | ✅ | ✅ | ✅ | ✅ |
| BLOB | ✅ | ✅ | ✅ | ✅ |
| CLOB | ✅ | ✅ | ✅ | ✅ |
| BIT / BIT VARYING | ✅ | ✅ | ✅ | ❌ |
Extended Types¶
| Type | CUBRID | MySQL | PostgreSQL | SQLite |
|---|---|---|---|---|
| ENUM | ❌ | ✅ | ✅ | ❌ |
| JSON | ❌ | ✅ | ✅ | ⚠️ |
| ARRAY | ❌ | ❌ | ✅ | ❌ |
| UUID | ❌ | ❌ | ✅ | ❌ |
| INTERVAL | ❌ | ❌ | ✅ | ❌ |
| HSTORE | ❌ | ❌ | ✅ | ❌ |
Notes¶
- BOOLEAN: CUBRID maps
BOOLEANtoSMALLINT. MySQL maps it toTINYINT(1). SQLite stores booleans as integers. Only PostgreSQL has a nativeBOOLEANtype. - NCHAR / NVARCHAR: CUBRID has first-class national character types. MySQL handles national characters via column charset. PostgreSQL and SQLite have no separate national character types.
- JSON: CUBRID does not have a JSON data type or JSON functions. MySQL (5.7+) and PostgreSQL have native JSON support. SQLite has JSON functions but no dedicated column type.
- ARRAY: CUBRID uses collection types (
SET,MULTISET,SEQUENCE) which serve a similar purpose but are not SQL-standard arrays. PostgreSQL has nativeARRAY[]support. - CLOB: CUBRID and MySQL have explicit
CLOBtypes. PostgreSQL usesTEXT(unlimited length). SQLite stores all text asTEXT.
Schema Reflection¶
| Feature | CUBRID | MySQL | PostgreSQL | SQLite |
|---|---|---|---|---|
| Table names | ✅ | ✅ | ✅ | ✅ |
| Column information | ✅ | ✅ | ✅ | ✅ |
| Primary keys | ✅ | ✅ | ✅ | ✅ |
| Foreign keys | ✅ | ✅ | ✅ | ✅ |
| Indexes | ✅ | ✅ | ✅ | ✅ |
| Unique constraints | ✅ | ✅ | ✅ | ✅ |
| Check constraints | ❌ | ✅ | ✅ | ❌ |
| Table comments | ✅ | ✅ | ✅ | ❌ |
| Column comments | ✅ | ✅ | ✅ | ❌ |
| View names | ✅ | ✅ | ✅ | ✅ |
| View definitions | ✅ | ✅ | ✅ | ✅ |
| Schema names | ❌ | ✅ | ✅ | ❌ |
| Sequences | ❌ | ❌ | ✅ | ❌ |
has_table |
✅ | ✅ | ✅ | ✅ |
has_index |
✅ | ❌ | ✅ | ✅ |
has_sequence |
❌ | ❌ | ✅ | ❌ |
Notes¶
- Check constraints: CUBRID parses check constraints but ignores them at runtime (officially documented behavior). The
get_check_constraints()method returns an empty list. - Table comments: Reflected via
get_table_comment()querying thedb_class.commentsystem catalog column. - Column comments: Reflected via
get_columns()querying the_db_attribute.commentsystem catalog column. Returned in the"comment"key of each column dict. - has_index: The CUBRID dialect implements
has_index()by queryingdb_index. The MySQL SA dialect does not provide a dedicatedhas_index()method. - Reflection source: CUBRID reflection queries the system catalog tables (
db_class,db_attribute,db_index,db_constraint,_db_index,_db_attribute) rather thanINFORMATION_SCHEMA.
Transactions & Connections¶
| Feature | CUBRID | MySQL | PostgreSQL | SQLite |
|---|---|---|---|---|
| Isolation level management | ✅ | ✅ | ✅ | ✅ |
| Savepoints | ✅ | ✅ | ✅ | ✅ |
| Two-phase commit | ❌ | ✅ | ✅ | ❌ |
| Server-side cursors | ❌ | ✅ | ✅ | ❌ |
| Autocommit detection | ✅ | ✅ | ✅ | ✅ |
| Connection-level encoding | ❌ | ✅ | ✅ | ❌ |
CUBRID Isolation Levels¶
CUBRID supports six isolation levels — more than the SQL standard's four:
| Level | Description |
|---|---|
SERIALIZABLE |
Full serialization |
REPEATABLE READ CLASS, REPEATABLE READ INSTANCES |
Repeatable read for both schema and data |
REPEATABLE READ CLASS, READ COMMITTED INSTANCES |
Repeatable read for schema, read committed for data |
REPEATABLE READ CLASS, READ UNCOMMITTED INSTANCES |
Repeatable read for schema, read uncommitted for data |
READ COMMITTED CLASS, READ COMMITTED INSTANCES |
Read committed for both |
READ COMMITTED CLASS, READ UNCOMMITTED INSTANCES |
Read committed for schema, read uncommitted for data |
Notes¶
- Two-phase commit: CUBRID does not support distributed transactions via
XA. - Server-side cursors: The CUBRID Python driver does not expose server-side cursor functionality.
- Autocommit detection: The CUBRID execution context uses a regex pattern matching
SET,ALTER,CREATE,DROP,GRANT,REVOKE, andTRUNCATEstatements to determine when to enable autocommit. - Savepoints: CUBRID supports
SAVEPOINTandROLLBACK TO SAVEPOINT.RELEASE SAVEPOINTis not supported — the dialect implementsdo_release_savepoint()as a no-op.
Dialect Engine Features¶
| Feature | CUBRID | MySQL | PostgreSQL | SQLite |
|---|---|---|---|---|
| Statement caching | ✅ | ✅ | ✅ | ✅ |
| Native enum | ❌ | ✅ | ✅ | ❌ |
| Native boolean | ❌ | ❌ | ✅ | ❌ |
| Native decimal | ✅ | ✅ | ✅ | ❌ |
| Sequences | ❌ | ❌ | ✅ | ❌ |
| ON UPDATE CASCADE | ✅ | ✅ | ✅ | ✅ |
| ON DELETE CASCADE | ✅ | ✅ | ✅ | ✅ |
| Self-referential FKs | ✅ | ✅ | ✅ | ✅ |
| Independent connections | ✅ | ✅ | ✅ | ✅ |
| Unicode DDL | ✅ | ✅ | ✅ | ✅ |
| Name normalization | ✅ | ❌ | ✅ | ❌ |
Notes¶
- Statement caching:
supports_statement_cache = True. The dialect is fully compatible with SQLAlchemy 2.0's compiled cache. - Name normalization: CUBRID folds unquoted identifiers to lowercase. The dialect normalizes identifiers to match Python-side expectations (
requires_name_normalize = True). - Max identifier length: CUBRID allows identifiers up to 254 characters — significantly longer than MySQL (64) or PostgreSQL (63).
CUBRID-Specific Features¶
These types and capabilities are unique to the CUBRID dialect and have no direct equivalent in MySQL, PostgreSQL, or SQLite.
| Feature | Description |
|---|---|
MONETARY type |
Fixed-point currency type with locale-aware formatting |
STRING type |
Alias for VARCHAR(1,073,741,823) — maximum-length variable string |
OBJECT type |
OID reference type pointing to another row by object identifier |
SET collection |
Unordered collection of unique elements |
MULTISET collection |
Unordered collection that allows duplicates |
SEQUENCE collection |
Ordered collection that allows duplicates |
| 6 isolation levels | Separate class-level and instance-level isolation granularity |
| 254-char identifiers | Longer than MySQL (64) and PostgreSQL (63) |
Collection Types¶
CUBRID's collection types (SET, MULTISET, SEQUENCE) are typed containers that hold elements of a specified data type. They are declared in DDL as:
These are fully supported by the dialect's type compiler and can be used in Column definitions.
CUBRID-Specific DML Constructs¶
The dialect provides custom SQLAlchemy constructs for CUBRID-specific DML features that go beyond the standard SA API.
ON DUPLICATE KEY UPDATE¶
CUBRID supports INSERT … ON DUPLICATE KEY UPDATE with VALUES() references (identical to MySQL pre-8.0 syntax).
from sqlalchemy_cubrid import insert
stmt = insert(users).values(id=1, name="alice", email="alice@example.com")
stmt = stmt.on_duplicate_key_update(name="updated_alice")
# INSERT INTO users (id, name, email) VALUES (1, 'alice', 'alice@example.com')
# ON DUPLICATE KEY UPDATE name = 'updated_alice'
# Reference the inserted value:
stmt = insert(users).values(id=1, name="alice", email="alice@example.com")
stmt = stmt.on_duplicate_key_update(name=stmt.inserted.name)
# ON DUPLICATE KEY UPDATE name = VALUES(name)
Accepted argument forms:
- Keyword arguments: stmt.on_duplicate_key_update(name="value")
- Dictionary: stmt.on_duplicate_key_update({"name": "value"})
- List of tuples (ordered): stmt.on_duplicate_key_update([("name", "value"), ("email", "value")])
MERGE Statement¶
CUBRID supports the SQL MERGE statement for conditional INSERT/UPDATE in a single operation.
from sqlalchemy_cubrid.dml import merge
stmt = (
merge(target_table)
.using(source_table)
.on(target_table.c.id == source_table.c.id)
.when_matched_then_update(
{"name": source_table.c.name, "email": source_table.c.email},
where=source_table.c.name.is_not(None), # optional WHERE
delete_where=target_table.c.active == False, # optional DELETE WHERE
)
.when_not_matched_then_insert(
{
"id": source_table.c.id,
"name": source_table.c.name,
"email": source_table.c.email,
},
where=source_table.c.name.is_not(None), # optional WHERE
)
)
Generated SQL:
MERGE INTO target_table
USING source_table
ON (target_table.id = source_table.id)
WHEN MATCHED THEN UPDATE SET name = source_table.name, email = source_table.email
WHERE source_table.name IS NOT NULL
DELETE WHERE target_table.active = 0
WHEN NOT MATCHED THEN INSERT (id, name, email)
VALUES (source_table.id, source_table.name, source_table.email)
WHERE source_table.name IS NOT NULL
Builder methods:
- merge(target) — factory function, sets target table
- .using(source) — source table or subquery
- .on(condition) — join condition
- .when_matched_then_update(values, where=None, delete_where=None) — UPDATE clause
- .when_matched_then_delete(where=None) — adds DELETE WHERE to an existing WHEN MATCHED clause
- .when_not_matched_then_insert(values, where=None) — INSERT clause
At least one of when_matched_then_update or when_not_matched_then_insert must be specified.
GROUP_CONCAT¶
CUBRID supports GROUP_CONCAT as an aggregate function:
import sqlalchemy as sa
stmt = sa.select(sa.func.group_concat(users.c.name))
# SELECT GROUP_CONCAT(users.name) FROM users
REPLACE INTO¶
CUBRID supports REPLACE INTO which inserts a new row, or deletes the conflicting row and inserts the new one if a duplicate key is found.
from sqlalchemy_cubrid import replace
stmt = replace(users).values(id=1, name="alice", email="alice@example.com")
# REPLACE INTO users (id, name, email) VALUES (1, 'alice', 'alice@example.com')
The replace() construct behaves like insert() but generates REPLACE INTO instead of INSERT INTO.
Query Trace¶
CUBRID uses SET TRACE ON / SHOW TRACE instead of standard EXPLAIN. The dialect provides a trace_query() utility:
from sqlalchemy_cubrid import trace_query
with engine.connect() as conn:
traces = trace_query(conn, text("SELECT * FROM users WHERE id = 1"))
for line in traces:
print(line)
trace_query() handles the full lifecycle: enables tracing, executes your statement, collects trace output, and disables tracing — all within a safe try/finally block.¶
Index Hints¶
CUBRID supports index hints in SELECT queries. These can be used via SQLAlchemy's built-in hint mechanisms — no custom dialect constructs are needed.
USING INDEX¶
# Using Select.with_hint()
stmt = (
sa.select(users)
.with_hint(users, "USING INDEX idx_users_name", dialect_name="cubrid")
)
# Using Select.suffix_with()
stmt = sa.select(users).suffix_with("USING INDEX idx_users_name")
USE INDEX / FORCE INDEX / IGNORE INDEX¶
stmt = (
sa.select(users)
.with_hint(users, "USE INDEX (idx_users_name)", dialect_name="cubrid")
)
stmt = (
sa.select(users)
.with_hint(users, "FORCE INDEX (idx_users_email)", dialect_name="cubrid")
)
stmt = (
sa.select(users)
.with_hint(users, "IGNORE INDEX (idx_users_old)", dialect_name="cubrid")
)
Note: When using
with_hint(dialect_name="cubrid"), the hint is only emitted when compiling against the CUBRID dialect. Other dialects will ignore it, making your code safely portable.
Known Limitations & Roadmap¶
Features not currently supported that may be added in future releases, depending on CUBRID database evolution and community contributions.
| Feature | Status | Reason |
|---|---|---|
| RETURNING clause | ❌ | CUBRID does not support INSERT/UPDATE/DELETE … RETURNING |
| Postfetch LASTROWID | ❌ | CUBRID Python driver limitation |
| JSON type | ❌ | CUBRID does not have a JSON data type |
| Temporary tables | ❌ | CUBRID does not support CREATE TEMPORARY TABLE |
| Multiple schemas | ❌ | CUBRID operates in a single-schema model |
| IS DISTINCT FROM | ❌ | Not a CUBRID SQL operator |
| Check constraint reflection | ❌ | CUBRID parses but ignores CHECK constraints |
| Sequences | ❌ | CUBRID uses AUTO_INCREMENT instead |
| Lateral joins | ❌ | LATERAL keyword causes syntax error in CUBRID |
| Full-text search | ❌ | No MATCH … AGAINST syntax or full-text indexes |
| Standard EXPLAIN | ❌ | CUBRID uses SET TRACE ON / SHOW TRACE instead (supported via trace_query()) |
| Alembic migrations | ✅ | Supported via CubridImpl entry-point (pip install sqlalchemy-cubrid[alembic]) |
Last updated: March 2026 · sqlalchemy-cubrid v0.7.1 · SQLAlchemy 2.0+