Python-Sqlalchemy二进制列类型HEX()和UNHEX()

时间:2015-11-25 18:27:30

标签: mysql python-2.7 sqlalchemy

我正在尝试学习Sqlalchemy并使用ORM。我的一个列将文件哈希存储为二进制。在SQL中,select只是

SELECT type, column FROM table WHERE hash = UNHEX('somehash')

如何使用我的ORM实现这样的选择(理想情况下也使用插入示例)?我已经开始阅读有关列覆盖的内容,但我很困惑/不确定那是我真正想要的。

例如 res = session.query.filter(Model.hash == __something__? )

思想?

3 个答案:

答案 0 :(得分:8)

仅适用于选择和插入

嗯,对于select你可以使用:

>>> from sqlalchemy import func
>>> session = (...)
>>> (...)
>>> engine = create_engine('sqlite:///:memory:', echo=True)
>>> q = session.query(Model.id).filter(Model.some == func.HEX('asd'))
>>> print q.statement.compile(bind=engine)
SELECT model.id
FROM model
WHERE model.some = HEX(?)

对于插入:

>>> from sqlalchemy import func
>>> session = (...)
>>> (...)
>>> engine = create_engine('sqlite:///:memory:', echo=True)
>>> m = new Model(hash=func.HEX('asd'))
>>> session.add(m)
>>> session.commit()
INSERT INTO model (hash) VALUES (HEX(%s))

更好的方法:使用sql函数

转换数据的自定义列

但是,我认为最适合您的是使用custom column on sqlalchemyprocess_bind_paramprocess_result_valuebind_expressioncolumn_expression examplelink

检查下面的代码,它会创建一个我认为符合您需求的自定义列:

from sqlalchemy.types import VARCHAR
from sqlalchemy import func

class HashColumn(VARCHAR):

    def bind_expression(self, bindvalue):
        # convert the bind's type from String to HEX encoded 
        return func.HEX(bindvalue)

    def column_expression(self, col):
        # convert select value from HEX encoded to String
        return func.UNHEX(col)

您可以为表格建模,如:

from sqlalchemy import Column, types
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Model(Base):
    __tablename__ = "model"
    id = Column(types.Integer, primary_key=True)
    col = Column(HashColumn(20))

    def __repr__(self):
        return "Model(col=%r)" % self.col

一些用法:

>>> (...)
>>> session = create_session(...)
>>> (...)
>>> model = Model(col='Iuri Diniz')
>>> session.add(model)
>>> session.commit()

这会发出此查询:

INSERT INTO model (col) VALUES (HEX(?)); -- ('Iuri Diniz',)

更多用法:

>>> session.query(Model).first()
Model(col='Iuri Diniz')

这会发出此查询:

SELECT 
    model.id AS model_id, UNHEX(model.col) AS model_col 
FROM model 
LIMIT ? ; -- (1,)

再多一点:

>>> session.query(Model).filter(Model.col == "Iuri Diniz").first()
Model(col='Iuri Diniz')

这会发出此查询:

SELECT 
    model.id AS model_id, UNHEX(model.col) AS model_col 
FROM model 
WHERE model.col = HEX(?) 
LIMIT ? ; -- ('Iuri Diniz', 1)

Extra:使用python类型

转换数据的自定义列

也许您想使用一些漂亮的自定义类型,并希望在python和数据库之间进行转换。

在下面的示例中,我在python和数据库之间转换UUID(代码基于此standardized vocabulary):

import uuid
from sqlalchemy.types import TypeDecorator, VARCHAR

class UUID4(TypeDecorator):
    """Portable UUID implementation

    >>> str(UUID4())
    'VARCHAR(36)'
    """

    impl = VARCHAR(36)

    def process_bind_param(self, value, dialect):
        if value is None:
            return value
        else:
            if not isinstance(value, uuid.UUID):
                return str(uuid.UUID(value))
            else:
                # hexstring
                return str(value)

    def process_result_value(self, value, dialect):
        if value is None:
            return value
        else:
            return uuid.UUID(value)

答案 1 :(得分:1)

由于以下错误,我无法让@ iuridiniz的自定义列解决方案正常工作:

sqlalchemy.exc.StatementError: (builtins.TypeError) encoding without a string argument

表达式如下:

m = Model(col='FFFF')
session.add(m)
session.commit()

我通过覆盖处理参数的process_bind_param来解决它 在将其传递给bind_expression以插入查询语言之前。

from sqlalchemy.types import VARCHAR
from sqlalchemy import func

class HashColumn(VARCHAR):

    def process_bind_param(self, value, dialect):
        # encode value as a binary
        if value:
            return bytes(value, 'utf-8')

    def bind_expression(self, bindvalue):
        # convert the bind's type from String to HEX encoded
        return func.HEX(bindvalue)

    def column_expression(self, col):
        # convert select value from HEX encoded to String
        return func.UNHEX(col)

然后定义表是相同的:

from sqlalchemy import Column, types
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Model(Base):
    __tablename__ = "model"
    id = Column(types.Integer, primary_key=True)
    col = Column(HashColumn(20))

    def __repr__(self):
        return "Model(col=%r)" % self.col

答案 2 :(得分:1)

我真的很喜欢iuridiniz approach 一个更好的方法:使用sql函数转换数据的自定义列,但是在使用BINARY和VARBINARY将十六进制字符串存储在其中时,我遇到了一些麻烦MySQL 5.7。我尝试了不同的方法,但是SQLAlchemy一直抱怨编码,和/或在无法使用它们的情况下使用func.HEXfunc.UNHEX。使用python3和SQLAlchemy 1.2.8,我设法使其能够扩展基类并替换其处理器,因此sqlalchemy不需要数据库中的函数来绑定数据并计算结果,而是在python中完成,如下所示:

import codecs
from sqlalchemy.types import VARBINARY

class VarBinaryHex(VARBINARY):
    """Extend VARBINARY to handle hex strings."""

    impl = VARBINARY

    def bind_processor(self, dialect):
        """Return a processor that decodes hex values."""
        def process(value):
            return codecs.decode(value, 'hex')
        return process

    def result_processor(self, dialect, coltype):
        """Return a processor that encodes hex values."""
        def process(value):
            return codecs.encode(value, 'hex')
        return process

    def adapt(self, impltype):
        """Produce an adapted form of this type, given an impl class."""
        return VarBinaryHex()

这个想法是将需要DBMS干预的HEXUNHEX替换为与HEX和UNHEX一样执行相同功能的python函数,对十六进制字符串进行编码和解码。如果直接连接到数据库,则可以使用HEX和UNHEX,但是从SQLAlchemy中可以使用codecs.encondecodecs.decode函数。

我敢打赌,如果有人感兴趣,编写适当的处理器,甚至可以从python的角度将十六进制值作为整数进行管理,从而允许存储大于BIGINT的整数。

一些注意事项:

    如果已知十六进制字符串的长度,可以使用
  • BINARY代替VARBINARY
  • 根据您要执行的操作,可能需要取消/将要使用这种类型的列的类的构造函数上的字符串大写/首字母大写,以便此时使用一致的大写字母对象初始化。即'aa' != 'AA'0xaa == 0xAA
  • 如前所述,您可以考虑将db二进制十六进制值转换为prython整数的处理器。
  • 使用VARBINARY时要小心,因为'aa' != '00aa'
  • 如果您使用BINARY,可以说您的列为col = Column(BinaryHex(length=4)),请考虑到您提供的任何少于length个字节的值都将以零完成。我的意思是,如果你这样做 obj.col = 'aabb'并提交,以后再从数据酶中检索到时,将得到obj.col == 'aabb0000',这是完全不同的。