Summary
CrateDialect._resolve_type() falls back to sqlalchemy.types.UserDefinedType for any CrateDB type not present in TYPES_MAP. UserDefinedType is abstract, it has no get_col_spec(), so reflection appears to succeed but any subsequent DDL compilation fails with a misleading error:
AttributeError: 'UserDefinedType' object has no attribute 'get_col_spec'
Several CrateDB types are missing from TYPES_MAP. This is the same defect class as #226 (timestamps).The fallback itself was left in place, so the bug simply moved to the next unmapped type.
Reproduction
import sqlalchemy as sa
engine = sa.create_engine("crate://localhost:4200/")
meta = sa.MetaData(schema="sys")
table = sa.Table("summits", meta, autoload_with=engine) # succeeds
print(sa.schema.CreateTable(table).compile(engine)) # AttributeError
sys.summits.coordinates is a geo_point. Reflection silently produces UserDefinedType for that column; the failure only surfaces at compile time, far from the cause.
Root cause
src/sqlalchemy_cratedb/dialect.py:
def _resolve_type(self, type_):
return TYPES_MAP.get(type_, sqltypes.UserDefinedType)
Two distinct problems:
- The fallback is not a usable type.
sqltypes.NullType is SQLAlchemy's own sentinel for "type unknown to the dialect". It supports reflect-and-SELECT (the common case), and raises a clear CompileError naming the column if someone tries to emit DDL from it. UserDefinedType supports neither and produces an AttributeError with no indication of which column or type caused it.
- The failure is silent at reflection time. Nothing is logged when a type cannot be resolved, so the information needed to diagnose it (the unmapped
data_type string) is discarded at exactly the moment it is available.
Missing mappings
geo_point and geo_shape are the notable case, because working implementations already exist in src/sqlalchemy_cratedb/type/geo.py (Geopoint, Geoshape, both with correct get_col_spec()) and are exported from the package — they are simply never registered in TYPES_MAP. Registering them is a two-line change.
| CrateDB type |
In TYPES_MAP |
Dialect implementation exists |
geo_point |
no |
yes (Geopoint) |
geo_shape |
no |
yes (Geoshape) |
ip |
no |
no |
bit |
no |
no |
interval |
no |
no |
numeric |
no |
no |
date |
no |
no |
time / time with time zone |
no |
no |
character / char(n) |
no |
no |
uuid |
no |
no |
row |
no |
no |
regclass, regproc, regtype |
no |
no |
Arrays are enumerated rather than derived
Array types are registered one key at a time (integer_array, text_array, … and the oddly-spelled "double precision_array"), which is why object_array works but geo_point_array, ip_array, numeric_array and friends do not. Since the key format is consistently f"{inner}_array", _resolve_type() could handle the whole family by recursing on the inner type and wrapping in ARRAY, instead of the map growing a second entry for every type added.
Impact
Summary
CrateDialect._resolve_type()falls back tosqlalchemy.types.UserDefinedTypefor any CrateDB type not present inTYPES_MAP.UserDefinedTypeis abstract, it has noget_col_spec(), so reflection appears to succeed but any subsequent DDL compilation fails with a misleading error:Several CrateDB types are missing from
TYPES_MAP. This is the same defect class as #226 (timestamps).The fallback itself was left in place, so the bug simply moved to the next unmapped type.Reproduction
sys.summits.coordinatesis ageo_point. Reflection silently producesUserDefinedTypefor that column; the failure only surfaces at compile time, far from the cause.Root cause
src/sqlalchemy_cratedb/dialect.py:Two distinct problems:
sqltypes.NullTypeis SQLAlchemy's own sentinel for "type unknown to the dialect". It supports reflect-and-SELECT(the common case), and raises a clearCompileErrornaming the column if someone tries to emit DDL from it.UserDefinedTypesupports neither and produces anAttributeErrorwith no indication of which column or type caused it.data_typestring) is discarded at exactly the moment it is available.Missing mappings
geo_pointandgeo_shapeare the notable case, because working implementations already exist insrc/sqlalchemy_cratedb/type/geo.py(Geopoint,Geoshape, both with correctget_col_spec()) and are exported from the package — they are simply never registered inTYPES_MAP. Registering them is a two-line change.TYPES_MAPgeo_pointGeopoint)geo_shapeGeoshape)ipbitintervalnumericdatetime/time with time zonecharacter/char(n)uuidrowregclass,regproc,regtypeArrays are enumerated rather than derived
Array types are registered one key at a time (
integer_array,text_array, … and the oddly-spelled"double precision_array"), which is whyobject_arrayworks butgeo_point_array,ip_array,numeric_arrayand friends do not. Since the key format is consistentlyf"{inner}_array",_resolve_type()could handle the whole family by recursing on the inner type and wrapping inARRAY, instead of the map growing a second entry for every type added.Impact
cratedb-toolkithad to carry a hardcoded blocklist to keep ctk cfr sys-exportfrom aborting onsys.summits` (see Makectk cfr sys-exportthe one-stop raw diagnostics command for support cases cratedb-toolkit#870), which silently dropped that table from diagnostics bundles.