【问题标题】:Python-Sqlalchemy Binary Column Type HEX() and UNHEX()Python-Sqlalchemy 二进制列类型 HEX() 和 UNHEX()
【发布时间】:2016-02-28 16:19:29
【问题描述】:

我正在尝试学习 Sqlalchemy 并使用 ORM。我的专栏之一将文件哈希存储为二进制。在 SQL 中,选择只是

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

如何使用我的 ORM 实现这样的选择(最好也使用插入示例)?我已经开始阅读有关列覆盖的内容,但我很困惑/不确定这是否真的是我所追求的。

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

想法?

【问题讨论】:

    标签: mysql python-2.7 sqlalchemy


    【解决方案1】:

    仅适用于选择和插入

    好吧,选择你可以使用:

    >>> 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 sqlalchemy 使用任何 process_bind_paramprocess_result_valuebind_expressioncolumn_expression,请参阅此 example

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

    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(代码基于此link):

    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)
    

    【讨论】:

    • 您使用 varchar 而不是 binary 作为 guid 字段。
    • @Ricardo,好吧,这只是一个例子,只是为了演示如何在python数据类型和db数据类型之间进行转换。
    • func.HEX 和 func.UNHEX 是否已弃用?遵循您的代码不起作用,无论输入如何,都会返回相同的对象,并且该值似乎隐藏在对象中。另外,我找不到关于这两个函数的任何文档,这是怎么回事??
    • @MatthewTrevor。我现在明白了。 (at)Clocker,根据documentation:“任何名称都可以给func。如果SQLAlchemy不知道函数名称,它将按原样呈现。”
    【解决方案2】:

    由于以下错误,我无法让 @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
    

    【讨论】:

      【解决方案3】:

      我真的很喜欢iuridiniz approach 更好的方法:使用 sql 函数转换数据的自定义列,但是在 MySQL 5.7 中使用 BINARY 和 VARBINARY 存储十六进制字符串时我遇到了一些麻烦.我尝试了不同的方法,但 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()
      

      这个想法是用 python 函数替换需要 DBMS 干预的 HEXUNHEX,它们执行相同的操作,对十六进制字符串进行编码和解码,就像 HEX 和 UNHEX 一样。如果您直接连接到数据库,您可以使用 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',这是完全不同的东西。

      【讨论】:

        猜你喜欢
        • 2016-03-06
        • 1970-01-01
        • 2015-11-19
        • 1970-01-01
        • 2021-08-04
        • 2012-06-02
        • 1970-01-01
        • 2016-11-02
        相关资源
        最近更新 更多