Type Mapping¶
This document covers the full type mapping between SQLAlchemy types, CUBRID SQL types, and the CUBRID-specific type extensions provided by this dialect.
Table of Contents¶
- Standard SQL Types
- CUBRID-Specific Types
- Numeric Types
- String Types
- Bit String Types
- LOB Types
- Collection Types
- Type Reflection (ischema_names)
- Boolean Handling
- Text and STRING
- Usage Examples
Standard SQL Types¶
The dialect maps standard SQLAlchemy types to CUBRID SQL types:
| SQLAlchemy Type | CUBRID SQL Type | Notes |
|---|---|---|
Integer |
INTEGER |
32-bit signed integer |
SmallInteger |
SMALLINT |
16-bit signed integer |
BigInteger |
BIGINT |
64-bit signed integer |
Float |
FLOAT |
7-digit precision (single precision) |
Double / REAL |
DOUBLE |
15-digit precision (double precision) |
Numeric(p, s) |
NUMERIC(p, s) |
Exact numeric, up to 38 digits |
String(n) |
VARCHAR(n) |
Variable-length character data |
Text |
STRING |
Alias for VARCHAR(1,073,741,823) |
Unicode(n) |
VARCHAR(n) |
Database charset (no NCHAR needed) |
UnicodeText |
STRING |
Same as Text; CUBRID has no TEXT type |
LargeBinary |
BLOB |
Binary Large Object |
BINARY(n) |
BIT(n*8) |
Fixed-length bytes; CUBRID has no BINARY |
VARBINARY(n) |
BIT VARYING(n*8) |
Variable-length bytes; no VARBINARY |
Uuid / UUID |
CHAR(32) |
No native UUID; stored as 32-char hex |
Boolean |
SMALLINT |
⚠️ No native boolean — mapped to 0/1 |
Date |
DATE |
Calendar date |
Time |
TIME |
Time of day |
DateTime |
DATETIME |
Date and time combined |
TIMESTAMP |
TIMESTAMP |
Timestamp with auto-update behavior |
VARCHAR default length: When
String()is used without a length, the dialect defaults toVARCHAR(4096). An explicit non-positive length (String(0),VARCHAR(0),NVARCHAR(0)) is not a valid CUBRID length and raisesCompileErrorinstead of being widened to the default.BINARY / VARBINARY: CUBRID has no
BINARYorVARBINARYtype, so the dialect stores them as bit strings, whose length is in bits:BINARY(n)compiles toBIT(n*8)(BINARY()toBIT(8)),VARBINARY(n)toBIT VARYING(n*8)andVARBINARY()toBIT VARYING(up to 1,073,741,823 bits). Values bind and return asbyteson both drivers.BINARYis fixed-length, so a shorter value comes back padded with\x00bytes. An emptyb""is not preserved: depending on the driver it reads back asNone(or as zero bytes forBINARYon pycubrid). A non-positive length (BINARY(0),VARBINARY(0)) raisesCompileError. These columns reflect asBIT(n*8)/BIT VARYING(n*8), so Alembic autogenerate reports no type change for them.UUID: CUBRID has no
UUIDtype.sa.Uuidandsa.UUIDboth compile toCHAR(32)and use SQLAlchemy's non-native UUID handling: values are stored as 32-character hex strings, and read back asuuid.UUID(default) or as a hyphenatedstrwithas_uuid=False. The column reflects asCHAR(32).Character set and collation: the dialect does not render a column-level
CHARSETorCOLLATEclause for string and text types. Acollation=argument (for exampleString(50, collation="utf8_bin"),Text(collation=...)orUnicodeText(collation=...)) is dropped, and the column uses the database charset and collation.Unicode/UnicodeTextdo not select a different charset either: they compile toVARCHAR(n)/STRINGlikeString/Text, so non-ASCII text (for example Korean, Japanese, Chinese or emoji) round-trips only when the database was created with a UTF-8 charset (for examplecubrid createdb testdb en_US.utf8).
CUBRID-Specific Types¶
Import CUBRID-specific types from the dialect package:
from sqlalchemy_cubrid import (
# Numeric
SMALLINT, BIGINT, NUMERIC, DECIMAL, FLOAT, REAL,
DOUBLE, DOUBLE_PRECISION,
# String
CHAR, VARCHAR, NCHAR, NVARCHAR, STRING,
# Binary
BIT,
# LOB
BLOB, CLOB,
# Collections
SET, MULTISET, SEQUENCE,
# JSON
JSON,
)
1.x migration notice (#892): The dialect-specific
SMALLINTandBIGINTdisplay_widthargument andCHAR,VARCHAR,NCHAR,NVARCHAR,STRINGvaluesargument are accepted but ignored, and still emit aDeprecationWarning. The earlier warning promised removal in a future MAJOR; this version revises that notice: they may be removed in a later reviewed 1.x MINOR, after this revised warning has been published. Remove the unused keyword argument now (e.g.SMALLINT(display_width=11)→SMALLINT();VARCHAR(30, values=["x"])→VARCHAR(30)). PositionalSMALLINT(11)andBIGINT(20)also represent ignored widths and should becomeSMALLINT()andBIGINT(). No DDL or data semantics change when they are omitted. Applications allowingsqlalchemy-cubrid<2.0may otherwise receive a breaking MINOR; test upgrades or use an explicit dependency cap until migrated. These calls remain accepted in this release.
Numeric Types¶
| Type | CUBRID SQL | Description |
|---|---|---|
SMALLINT |
SMALLINT |
16-bit signed integer (-32,768 to 32,767) |
BIGINT |
BIGINT |
64-bit signed integer |
NUMERIC(p, s) |
NUMERIC(p, s) |
Exact numeric with precision (1–38) and scale |
DECIMAL(p, s) |
DECIMAL(p, s) |
Synonym for NUMERIC |
FLOAT(p) |
FLOAT(p) |
Approximate numeric, default precision 7 |
REAL |
REAL |
Synonym for single-precision float |
DOUBLE |
DOUBLE |
Double-precision floating point |
DOUBLE_PRECISION |
DOUBLE PRECISION |
Synonym for DOUBLE |
from sqlalchemy import Column, MetaData, Table
from sqlalchemy_cubrid import NUMERIC, DOUBLE, BIGINT
metadata = MetaData()
products = Table(
"products", metadata,
Column("id", BIGINT, primary_key=True),
Column("price", NUMERIC(10, 2)),
Column("weight", DOUBLE),
)
String Types¶
| Type | CUBRID SQL | Description |
|---|---|---|
CHAR(n) |
CHAR(n) |
Fixed-length character data |
VARCHAR(n) |
VARCHAR(n) |
Variable-length character data |
NCHAR(n) |
NCHAR(n) |
Fixed-length national character data |
NVARCHAR(n) |
NCHAR VARYING(n) |
Variable-length national character data |
STRING |
STRING |
VARCHAR(1,073,741,823) — max length |
from sqlalchemy_cubrid import CHAR, VARCHAR, NVARCHAR, STRING
metadata = MetaData()
users = Table(
"users", metadata,
Column("code", CHAR(10)),
Column("name", VARCHAR(255)),
Column("name_ko", NVARCHAR(255)),
Column("bio", STRING),
)
NCHAR / NVARCHAR: CUBRID has first-class national character types for multi-language support. The dialect renders
NVARCHAR(n)asNCHAR VARYING(n)to match CUBRID's preferred DDL syntax.
Bit String Types¶
| Type | CUBRID SQL | Description |
|---|---|---|
BIT(n) |
BIT(n) |
Fixed-length bit string |
BIT(n, varying=True) |
BIT VARYING(n) |
Variable-length bit string |
from sqlalchemy_cubrid import BIT
metadata = MetaData()
flags = Table(
"flags", metadata,
Column("fixed_bits", BIT(8)), # BIT(8)
Column("var_bits", BIT(256, varying=True)), # BIT VARYING(256)
)
LOB Types¶
| Type | CUBRID SQL | Description |
|---|---|---|
BLOB |
BLOB |
Binary Large Object |
CLOB |
CLOB |
Character Large Object |
from sqlalchemy_cubrid import BLOB, CLOB
metadata = MetaData()
documents = Table(
"documents", metadata,
Column("content", CLOB),
Column("attachment", BLOB),
)
Collection Types¶
CUBRID provides three collection types that have no direct equivalent in standard SQL:
| Type | CUBRID SQL | Description |
|---|---|---|
SET(type) |
SET(type) |
Unordered collection of unique elements |
MULTISET(type) |
MULTISET(type) |
Unordered collection, duplicates allowed |
SEQUENCE(type) |
SEQUENCE(type) |
Ordered collection, duplicates allowed |
from sqlalchemy_cubrid import SET, MULTISET, SEQUENCE, VARCHAR
metadata = MetaData()
tagged_items = Table(
"tagged_items", metadata,
Column("tags", SET("VARCHAR")),
Column("scores", MULTISET("INTEGER")),
Column("history", SEQUENCE("DOUBLE")),
)
DDL output:
Note: Collection types are CUBRID-specific. Standard SQL uses
ARRAY[](PostgreSQL) or has no collection support. If portability is a concern, useSET/MULTISET/SEQUENCEonly when targeting CUBRID.
LISTis a synonym forSEQUENCE. CUBRID acceptsLIST(type)in DDL but normalizes it toSEQUENCEat parse time — aLIST(INTEGER)column is stored and reflected asSEQUENCE OF INTEGER(verified on CUBRID 11.2). The dialect therefore exposes only the canonicalSEQUENCEtype; declareSEQUENCE(...)in your models and reflection will round-trip cleanly. There is intentionally no separateLISTtype, since a type compiling toLIST(...)would produce spurious Alembic autogenerate diffs against the reflectedSEQUENCE(...).
Collection values¶
Bind a SET or MULTISET column's value as a Python list, tuple, set
or frozenset. A SEQUENCE is ordered, so bind it as a list or tuple: on
the pycubrid drivers a set or frozenset for a SEQUENCE column raises
TypeError ("SEQUENCE is ordered; pass a list or tuple", wrapped in
SQLAlchemy's StatementError) whatever the pycubrid version, and the dialect
never sorts it for you. What happens next depends on the driver:
cubrid+pycubrid://, cubrid+aiopycubrid:// with pycubrid >= 1.9.0 |
pycubrid 1.8.x (supported floor) | cubrid:// (CUBRIDdb) |
|
|---|---|---|---|
Binding a list/tuple (and a set/frozenset for SET/MULTISET) |
Wrapped in pycubrid.types.Set, Multiset or Sequence to match the column type and sent as a SET{...}, MULTISET{...} or SEQUENCE{...} literal |
ProgrammingError (pycubrid rejects collection parameters) |
Bound by CUBRIDdb itself, always as a SET: a MULTISET loses duplicates and a SEQUENCE loses its order (Driver Compatibility, Known Issue 11) |
Reading SET |
frozenset with ?decode_collections=true, raw bytes without it |
Same | set of str |
Reading MULTISET / SEQUENCE |
list with ?decode_collections=true, raw bytes without it |
Same | list of str |
A member type may be given as a class or an instance: SET(Integer) and SET(Integer()) render the same SET(INTEGER) DDL (#870).
from sqlalchemy import Column, Integer, MetaData, String, Table, create_engine, select
from sqlalchemy_cubrid import MULTISET, SEQUENCE, SET
engine = create_engine("cubrid+pycubrid://dba@localhost:33000/demodb?decode_collections=true")
items = Table(
"items", MetaData(),
Column("id", Integer, primary_key=True),
Column("tags", SET(String(20))),
Column("scores", MULTISET(Integer())),
Column("history", SEQUENCE(Integer())),
)
with engine.begin() as conn:
conn.execute(items.insert(), {"id": 1, "tags": {"a", "b"}, "scores": [2, 2, 1], "history": [3, 1, 3]})
row = conn.execute(select(items)).one()
# row.tags == frozenset({"a", "b"}); sorted(row.scores) == [1, 2, 2]; row.history == [3, 1, 3]
conn.execute(select(items.c.id).where(items.c.history == [3, 1, 3])) # SEQUENCE{3, 1, 3}
On pycubrid:
- Typed parameters (
pycubrid.types.Set,MultisetandSequence) shipped in pycubrid 1.9.0 (cubrid-lab/pycubrid#567). The dialect detects them; with the still-supported pycubrid 1.8.x it passes values to the driver unchanged, so a collection parameter fails as before. - Each element must be a value pycubrid can bind on its own:
None,bool,int,float,Decimal,str,bytes,bytearray,date,timeordatetime. Nested collections are rejected. - A value that is already a
pycubrid.types.Set,MultisetorSequence,Noneand any other value (for example astr) are passed to the driver unchanged. - The server keeps the collection semantics, and the dialect does not change
them: a
SETdrops duplicates, aMULTISETkeeps duplicates but not their order (comparesorted(...)), and aSEQUENCEkeeps both. An empty collection reads back asfrozenset()or[], aNULLcolumn asNone, and aNULLelement asNoneinside the collection. - The dialect does not convert values read back: they are what pycubrid
decodes. Without
decode_collections=truepycubrid returns the raw collection bytes. - A
set/frozensetbound to aSEQUENCEcolumn raisesTypeError(see above), with every pycubrid version. - Collection values cannot be rendered inline:
literal_bindsandliteral_executeraiseCompileErrorfor a non-NULLcollection value (aNULLrenders asNULL). Bind collections as parameters. - In the ORM, assign a new collection to change a column
(
obj.history = [*obj.history, 4]). SQLAlchemy does not track in-place changes to alistorsetattribute.
The round trips were tested live on CUBRID 10.2 and 11.4 with pycubrid main
before the 1.9.0 release (Core and ORM, sync and async;
test/test_collection_roundtrip.py).
JSON Type¶
CUBRID 10.2+ supports native JSON (RFC 7159). The dialect provides full JSON type support:
| Type | CUBRID SQL | Description |
|---|---|---|
JSON |
JSON |
Native JSON storage (RFC 7159 compliant) |
JSONIndexType |
— | Single-key path formatting ($."key") |
JSONPathType |
— | Multi-level path formatting |
from sqlalchemy_cubrid import JSON
from sqlalchemy import Column, Integer, Table, MetaData, func, select
metadata = MetaData()
events = Table(
"events", metadata,
Column("id", Integer, primary_key=True),
Column("payload", JSON),
)
Path expressions use JSON_EXTRACT under the hood. Each key is quoted as a JSON
string (#866): " and \ are backslash-escaped and non-ASCII characters become
\uXXXX escapes, so col['a"b'] reads $."a\"b" and col["한글"] reads
$."\ud55c\uae00" whatever the connection charset:
# Single key access: col["key"] → JSON_EXTRACT(col, '$."key"')
stmt = select(events.c.payload["type"])
# Nested path: col[("a", "b")] → JSON_EXTRACT(col, '$."a"."b"')
stmt = select(events.c.payload[("data", "name")])
# Typed access with null handling
stmt = select(events).where(
events.c.payload["status"].as_string() == "active"
)
# Direct function usage
stmt = select(func.JSON_EXTRACT(events.c.payload, "$.type"))
Path components are int array positions (col[0] reads $[0], negative values
render as given) or str keys. bool is rejected even though it is an int subclass
(#914): col[True] or col[("items", False)] would render $[True], which CUBRID 10.2
and 11.4 reject as an invalid JSON path (-494). Execution raises
sqlalchemy.exc.StatementError wrapping TypeError, before any SQL is sent, and
literal_binds rendering raises sqlalchemy.exc.CompileError. Use 0/1 for an array
position, or "True" for a key.
Note: Generic
sa.JSONis automatically adapted to CUBRID'sJSONtype viacolspecs. JSON columns are reflected correctly from existing tables.
Type Reflection (ischema_names only)¶
When reflecting existing tables, the dialect maps only the CUBRID type names present in dialect.ischema_names back to SQLAlchemy types:
| CUBRID Type Name | SQLAlchemy Type |
|---|---|
SHORT |
SMALLINT |
SMALLINT |
SMALLINT |
INTEGER |
INTEGER |
BIGINT |
BIGINT |
NUMERIC |
NUMERIC |
DECIMAL |
DECIMAL |
FLOAT |
FLOAT |
DOUBLE |
DOUBLE |
DOUBLE PRECISION |
DOUBLE_PRECISION |
DATE |
DATE |
TIME |
TIME |
TIMESTAMP |
TIMESTAMP |
TIMESTAMPTZ |
TIMESTAMPTZ (timezone=True) |
TIMESTAMPLTZ |
TIMESTAMPLTZ (timezone=True) |
DATETIME |
DATETIME |
DATETIMETZ |
DATETIMETZ (timezone=True) |
DATETIMELTZ |
DATETIMELTZ (timezone=True) |
BIT(n) |
BIT(n) |
BIT VARYING(n) |
BIT(n, varying=True) |
CHAR |
CHAR |
VARCHAR |
VARCHAR |
NCHAR |
NCHAR |
CHAR VARYING |
VARCHAR |
NCHAR VARYING |
NVARCHAR |
STRING |
STRING |
ENUM('a', ...) |
ENUM('a', ...) (DBA; otherwise NullType + warning) |
BLOB |
BLOB |
CLOB |
CLOB |
SET |
SET |
MULTISET |
MULTISET |
SEQUENCE |
SEQUENCE |
The following dialect types are declared/compiled but not auto-reflected because they are not present in dialect.ischema_names: REAL, MONETARY, and OBJECT.
TZ/LTZ reflection (#181, #442). TIMESTAMPTZ, TIMESTAMPLTZ, DATETIMETZ and DATETIMELTZ reflect as their own dedicated dialect classes rather than collapsing into plain TIMESTAMP/DATETIME, and each sets timezone=True, so a round-tripped datetime keeps its timezone awareness and metadata.reflect() + create_all() reproduces the original column type. The dialect does not otherwise distinguish explicit-timezone (TZ) from local-timezone (LTZ) value semantics in Python — both are represented as an aware datetime — only the reflected SQLAlchemy type class differs.
ENUM and collection columns (#631). SHOW COLUMNS prints native ENUM values without escaping: one legal value containing ', ' can be byte-identical to two separate values. For a user authorized to read _db_domain (normally DBA), the dialect reads the exact, ordered labels from the domain catalog rather than splitting that ambiguous text. A non-DBA user's specific -494 denial is reported as a warning and NullType; unrelated catalog failures propagate. Such users must declare the ENUM type in their model until an authorized public metadata source is available.
Non-DBA ENUM reflection: tested limitation (#641). None of the sources tested gives a non-DBA user the ENUM labels unambiguously. Checked on CUBRID 10.2.18, 11.0.16, 11.2.9 and 11.4.6 as a user with no grants, on its own tables ENUM('a'', ''b', 'other') (two labels) and ENUM('a', 'b', 'other') (three labels):
| Candidate source | Result on all four versions |
|---|---|
SHOW COLUMNS, SHOW FULL COLUMNS, SHOW CREATE TABLE |
Both tables print ENUM('a', 'b', 'other') |
db_attribute |
data_type is ENUM, no labels; no other public catalog view carries a domain's labels |
_db_domain |
-494 SELECT is not authorized on _db_domain |
Driver metadata (pycubrid cursor.description, CCI attribute schema information) |
Type code only, no labels |
SQL expressions on the column's type (UNION ALL, COALESCE, IFNULL, CASE, NVL2 with integers; comparison with a label or a non-label string) |
Integers stay integers, strings stay strings, and a string that is not a label is accepted, so neither a label's position nor its membership can be read |
Such a column therefore keeps reflecting as NullType with a warning. CUBRIDdb and JDBC metadata calls were not tested. This is what the listed versions do, not a statement about later servers.
SHOW COLUMNS prints collections as SET OF NUMERIC,VARCHAR (also MULTISET OF ... and SEQUENCE OF ...; LIST is printed as SEQUENCE) without member modifiers and sometimes in a different order. The public db_attr_setdomain_elm view supplies each member's precision, scale and object-domain class. The dialect uses those rows only when they account for every reported member family; missing or inconsistent rows warn and yield NullType instead of silently changing the DDL. An OBJECT member whose class was dropped (listed without its class) and a member type the dialect cannot express warn and yield NullType; the rest of the table still reflects. MONETARY members reflect as MONETARY. A column name that SHOW COLUMNS lists twice (a CLASS ATTRIBUTE of the same name) warns and yields NullType for ENUM and collection columns, and the catalog lookups read instance attributes only. On CUBRID 11.2+ an OBJECT member whose class belongs to another owner than the table reflects as owner.class. An ENUM member (SET(ENUM('x', 'y')), listed without its values) warns and yields NullType. Members keep the order the view lists them in, which is the declaration order on the supported servers. A member's collation (VARCHAR(10) COLLATE utf8_bin) is not in the view and is not reflected. An untyped collection takes its kind from db_attribute and reflects as SET() / MULTISET() / SEQUENCE(). With authorized ENUM metadata and complete collection-domain rows, metadata.reflect() followed by create_all() reproduced the tested DDL on CUBRID 10.2, 11.2 and 11.4; 11.0 is covered by the full integration matrix (manual dispatch and the release gate). This is not a claim of full ENUM reflection for non-DBA users.
On CUBRID 11.2+, an OBJECT member also warns and yields NullType when either its domain owner or the reflected table owner is unavailable; rendering an unqualified class could resolve to a different owner's class.
Boolean Handling¶
CUBRID does not have a native BOOLEAN data type. The dialect maps Boolean to SMALLINT:
from sqlalchemy import Boolean, Column
class User(Base):
__tablename__ = "users"
is_active = Column(Boolean) # → SMALLINT in DDL
Trueis stored as1Falseis stored as0- The
supports_native_boolean = Falseflag tells SQLAlchemy to handle the conversion automatically
Boolean predicates¶
CUBRID's IS accepts only [NOT] NULL and [NOT] TRUE/FALSE, and since CUBRID 11.2 IS TRUE/IS FALSE needs a logical operand (b IS TRUE fails for a SMALLINT column). SQLAlchemy renders col.is_(True) for a non-native Boolean as col IS 1, which CUBRID rejects on every version, so the dialect renders IS/IS NOT against a value with the null-safe equal <=>, like IS [NOT] DISTINCT FROM:
| SQLAlchemy | SQL | Value for 1 / 0 / NULL |
|---|---|---|
col.is_(True) |
col <=> 1 |
true / false / false |
col.is_(False) |
col <=> 0 |
false / true / false |
col.is_not(True) |
(col <=> 1) = 0 |
false / true / true |
col.is_not(False) |
(col <=> 0) = 0 |
true / false / true |
col.is_(None) / col == None |
col IS NULL |
false / false / true |
col == True / col |
col = 1 |
true / false / NULL |
col == False / not_(col) |
col = 0 |
false / true / NULL |
true() / false() |
1 = 1 / 0 = 1 (1 / 0 in a SELECT list) |
constant |
IS TRUE/IS FALSE never yield NULL, and IS NOT TRUE/IS NOT FALSE are their exact complements, so SQLAlchemy's three-valued semantics are kept in WHERE clauses and in SELECT lists alike; the same applies to any expression, e.g. (col == 5).is_(True) renders (col = 5) <=> 1. 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_(a, b)), select(or_(a, b)), a projected NOT (...), select(cast(and_(...), Integer)) and over(order_by=exists()) work (#966, #968, #970); see Boolean SELECT-list items for the supported positions. CUBRID rejects a boolean expression as a GROUP BY item even inside parentheses, so label it in the SELECT list and group by the label.
Text and STRING¶
SQLAlchemy's Text type maps to CUBRID's STRING type:
from sqlalchemy import Text, Column
class Article(Base):
__tablename__ = "articles"
body = Column(Text) # → STRING in DDL
CUBRID's STRING is an alias for VARCHAR(1,073,741,823) — the maximum possible VARCHAR length. This is functionally equivalent to TEXT in other databases.
Usage Examples¶
Defining a Table with Mixed Types¶
from sqlalchemy import Column, MetaData, Table, Integer, String, DateTime, Text
from sqlalchemy_cubrid import NUMERIC, CLOB, SET
metadata = MetaData()
orders = Table(
"orders", metadata,
Column("id", Integer, primary_key=True, autoincrement=True),
Column("customer_name", String(100), nullable=False),
Column("total", NUMERIC(12, 2)),
Column("notes", Text),
Column("attachments", CLOB),
Column("tags", SET("VARCHAR")),
Column("created_at", DateTime),
)
Reflecting an Existing Table¶
from sqlalchemy import MetaData, create_engine
engine = create_engine("cubrid://dba@localhost:33000/testdb")
metadata = MetaData()
metadata.reflect(bind=engine)
# Access reflected table
users = metadata.tables["users"]
for col in users.columns:
print(f"{col.name}: {col.type}")
Using colspecs for Type Coercion¶
The dialect registers type coercions (colspecs) that automatically map generic SQLAlchemy types:
| Generic Type | CUBRID Type |
|---|---|
sqltypes.Numeric |
NUMERIC |
sqltypes.Float |
FLOAT |
sqltypes.Time |
TIME |
This means Column(Numeric(10, 2)) automatically uses the CUBRID NUMERIC implementation without explicit imports.
Machine-Readable Type Mapping Matrix¶
The table below is designed for copy/paste into tooling pipelines and architecture docs.
| CUBRID Type | SQLAlchemy Type | Python Type | Notes |
|---|---|---|---|
SHORT |
sqlalchemy_cubrid.SMALLINT |
int |
Reflected alias for SMALLINT. |
SMALLINT |
sqlalchemy_cubrid.SMALLINT |
int |
16-bit signed integer. |
INTEGER |
sqlalchemy.Integer |
int |
Standard 32-bit integer. |
BIGINT |
sqlalchemy_cubrid.BIGINT |
int |
64-bit integer values. |
NUMERIC(p,s) |
sqlalchemy_cubrid.NUMERIC |
decimal.Decimal |
Exact numeric; precision 1-38. |
DECIMAL(p,s) |
sqlalchemy_cubrid.DECIMAL |
decimal.Decimal |
Synonym of NUMERIC. |
FLOAT(p) |
sqlalchemy_cubrid.FLOAT |
float |
Approximate numeric, default precision 7. |
REAL |
sqlalchemy_cubrid.REAL |
float |
Approximate numeric single precision semantics. Declared/compiled only; not auto-reflected. |
DOUBLE |
sqlalchemy_cubrid.DOUBLE |
float |
Approximate numeric double precision. |
DOUBLE PRECISION |
sqlalchemy_cubrid.DOUBLE_PRECISION |
float |
Reflected as dedicated dialect type. |
MONETARY |
sqlalchemy_cubrid.MONETARY |
float |
Currency-aware server type; represented as numeric value in Python. Declared/compiled only; not auto-reflected. |
DATE |
sqlalchemy.Date |
datetime.date |
Calendar date only. |
TIME |
sqlalchemy.Time / sqlalchemy_cubrid.TIME |
datetime.time |
Time of day only. |
DATETIME |
sqlalchemy.DateTime / sqlalchemy_cubrid.DATETIME |
datetime.datetime |
Date + time in one value. |
TIMESTAMP |
sqlalchemy.TIMESTAMP / sqlalchemy_cubrid.TIMESTAMP |
datetime.datetime |
CUBRID timestamp semantics may auto-update depending on schema defaults. |
TIMESTAMPTZ |
sqlalchemy_cubrid.types.TIMESTAMPTZ |
datetime.datetime (aware) |
Explicit-timezone timestamp; timezone=True. Reflects as its own type, not TIMESTAMP (#181). |
TIMESTAMPLTZ |
sqlalchemy_cubrid.types.TIMESTAMPLTZ |
datetime.datetime (aware) |
Local-timezone timestamp; timezone=True. Reflects as its own type, not TIMESTAMP (#181). |
DATETIMETZ |
sqlalchemy_cubrid.types.DATETIMETZ |
datetime.datetime (aware) |
Explicit-timezone datetime; timezone=True. Reflects as its own type, not DATETIME (#442). |
DATETIMELTZ |
sqlalchemy_cubrid.types.DATETIMELTZ |
datetime.datetime (aware) |
Local-timezone datetime; timezone=True. Reflects as its own type, not DATETIME (#442). |
BIT(n) |
sqlalchemy_cubrid.BIT(length=n, varying=False) / sqlalchemy.BINARY(n/8) |
bytes |
Fixed-length bit string; sa.BINARY(n) compiles to BIT(n*8). |
BIT VARYING(n) |
sqlalchemy_cubrid.BIT(length=n, varying=True) / sqlalchemy.VARBINARY(n/8) |
bytes |
Variable-length bit string; sa.VARBINARY(n) compiles to BIT VARYING(n*8). |
CHAR(n) |
sqlalchemy_cubrid.CHAR |
str |
Fixed-length character data. |
VARCHAR(n) |
sqlalchemy_cubrid.VARCHAR / sqlalchemy.String |
str |
Variable-length string. |
NCHAR(n) |
sqlalchemy_cubrid.NCHAR |
str |
National character set type. |
CHAR VARYING(n) |
sqlalchemy_cubrid.VARCHAR |
str |
Synonym of VARCHAR(n); reflected to VARCHAR. |
CHAR(32) (UUID) |
sqlalchemy.Uuid / sqlalchemy.UUID |
uuid.UUID / str |
No native UUID; 32-char hex. Reflects as CHAR(32). |
STRING |
sqlalchemy_cubrid.STRING / sqlalchemy.Text |
str |
Equivalent to very large VARCHAR. |
CLOB |
sqlalchemy_cubrid.CLOB |
str (documented) |
Character LOB. Current drivers return a LOB locator on read; see the warning below. |
BLOB |
sqlalchemy_cubrid.BLOB / sqlalchemy.LargeBinary |
bytes (documented) |
Binary LOB. Current drivers return a LOB locator on read; see the warning below. |
SET(...) |
sqlalchemy_cubrid.SET |
Driver-dependent; frozenset on pycubrid with decode_collections=true |
CUBRID-specific collection; unique unordered members. See Collection values. |
MULTISET(...) |
sqlalchemy_cubrid.MULTISET |
Driver-dependent; list on pycubrid with decode_collections=true |
CUBRID-specific collection; duplicates allowed. See Collection values. |
SEQUENCE(...) |
sqlalchemy_cubrid.SEQUENCE |
Driver-dependent; list on pycubrid with decode_collections=true |
CUBRID-specific collection; ordered with duplicates. See Collection values. |
OBJECT |
sqlalchemy_cubrid.OBJECT |
Driver-dependent object reference | OID reference type; database-specific. Declared/compiled only; not auto-reflected. |
BOOLEAN (emulated) |
sqlalchemy.Boolean -> SMALLINT |
bool |
Stored as 1 / 0; supports_native_boolean=False. |
Type Resolution Flow¶
flowchart LR
cubrid[CUBRID SQL type name] --> ischema[Dialect ischema_names lookup]
ischema --> satype[SQLAlchemy TypeEngine instance]
satype --> bind[DBAPI bind/result processors]
bind --> pyval[Python runtime value]
Precision and rounding
Use NUMERIC/DECIMAL for financial values. FLOAT/DOUBLE are approximate and can introduce rounding error in aggregates.
Character set and national string columns
NCHAR/NVARCHAR use national character semantics. Keep application encoding and database collation aligned to avoid unexpected comparisons/sorting.
Reading BLOB/CLOB returns a driver LOB locator
Verified live on CUBRID 10.2 and 11.4, Core and ORM, sync and async, with pycubrid 1.8.0 (the supported floor), pycubrid main, and CUBRIDdb 11.3 (#485, test/test_lob_value_contract.py): binding bytes/str stores the full value and NULL round-trips as None, but selecting a non-NULL BLOB/CLOB column returns the driver's LOB locator (a dict handle on sync pycubrid, the file_locator string on cubrid+aiopycubrid://, a 'file:...' string on CUBRIDdb) instead of bytes/str. For LargeBinary/BLOB SQLAlchemy's result processor then raises TypeError. Binding LargeBinary/BLOB values (including None) through cubrid+aiopycubrid:// works since #500.
To read content, convert on the server (CLOB_TO_CHAR(col), BLOB_TO_BIT(col)), or store large text in Text (CUBRID STRING), which round-trips as str. pycubrid's LOB handles (cubrid-lab/pycubrid#441/#442) are explicit calls on pycubrid.compat.native, which the dialect does not use; ordinary fetch on pycubrid main still returns the locator, so the strict xfail applies there too and turns into a failing XPASS if that changes.
LOB and collection payload shape can differ by driver
CUBRIDdb and pycubrid can expose BLOB/CLOB and collection values differently.
Validate payload type (str, bytes, mapping-like metadata) in integration tests for your chosen driver.
See also: Feature Support · DML Extensions · Connection Setup