diff --git a/CHANGES.md b/CHANGES.md index c6c88028..8998f4da 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,4 +1,5 @@ # Changelog +- Types: Added support for BLOB type, per base64 encoding ## 2026/05/28 0.42.0 - Added support for SQL Alchemy 2.1 diff --git a/src/sqlalchemy_cratedb/compiler.py b/src/sqlalchemy_cratedb/compiler.py index 851e8ebb..d257b718 100644 --- a/src/sqlalchemy_cratedb/compiler.py +++ b/src/sqlalchemy_cratedb/compiler.py @@ -257,6 +257,9 @@ def visit_TIMESTAMP(self, type_, **kw): """ return "TIMESTAMP %s" % ((type_.timezone and "WITH" or "WITHOUT") + " TIME ZONE",) + def visit_BLOB(self, type_, **kw): + return "STRING" + class CrateCompiler(compiler.SQLCompiler): def visit_getitem_binary(self, binary, operator, **kw): diff --git a/src/sqlalchemy_cratedb/dialect.py b/src/sqlalchemy_cratedb/dialect.py index d78306c0..818a7125 100644 --- a/src/sqlalchemy_cratedb/dialect.py +++ b/src/sqlalchemy_cratedb/dialect.py @@ -36,6 +36,7 @@ ) from .sa_version import SA_1_4, SA_2_0, SA_VERSION from .type import FloatVector, ObjectArray, ObjectType +from .type.binary import LargeBinary from .util import SSLMode TYPES_MAP = { @@ -161,6 +162,7 @@ def process(value): sqltypes.Date: Date, sqltypes.DateTime: DateTime, sqltypes.TIMESTAMP: DateTime, + sqltypes.LargeBinary: LargeBinary, } if SA_VERSION >= SA_2_0: diff --git a/src/sqlalchemy_cratedb/type/__init__.py b/src/sqlalchemy_cratedb/type/__init__.py index b524bb39..6d92e0e2 100644 --- a/src/sqlalchemy_cratedb/type/__init__.py +++ b/src/sqlalchemy_cratedb/type/__init__.py @@ -1,4 +1,5 @@ from .array import ObjectArray +from .binary import LargeBinary from .geo import Geopoint, Geoshape from .object import ObjectType from .vector import FloatVector, knn_match @@ -6,6 +7,7 @@ __all__ = [ Geopoint, Geoshape, + LargeBinary, ObjectArray, ObjectType, FloatVector, diff --git a/src/sqlalchemy_cratedb/type/binary.py b/src/sqlalchemy_cratedb/type/binary.py new file mode 100644 index 00000000..04b04073 --- /dev/null +++ b/src/sqlalchemy_cratedb/type/binary.py @@ -0,0 +1,44 @@ +import base64 + +import sqlalchemy as sa + + +class LargeBinary(sa.String): + """A type for large binary byte data. + + The :class:`.LargeBinary` type corresponds to a large and/or unlengthed + binary type for the target platform, such as BLOB on MySQL and BYTEA for + PostgreSQL. It also handles the necessary conversions for the DBAPI. + + """ + + __visit_name__ = "large_binary" + + def bind_processor(self, dialect): + if dialect.dbapi is None: + return None + + # TODO: DBAPIBinary = dialect.dbapi.Binary + + def process(value): + if value is not None: + # TODO: return DBAPIBinary(value) + return base64.b64encode(value).decode() + else: + return None + + return process + + # Python 3 has native bytes() type + # both sqlite3 and pg8000 seem to return it, + # psycopg2 as of 2.5 returns 'memoryview' + def result_processor(self, dialect, coltype): + if dialect.returns_native_bytes: + return None + + def process(value): + if value is not None: + return base64.b64decode(value) + return value + + return process