我正在尝试学习Sqlalchemy并使用ORM。我的一个列将文件哈希存储为二进制。在SQL中,select只是
SELECT type, column FROM table WHERE hash = UNHEX('somehash')
如何使用我的ORM实现这样的选择(理想情况下也使用插入示例)?我已经开始阅读有关列覆盖的内容,但我很困惑/不确定那是我真正想要的。
例如
res = session.query.filter(Model.hash == __something__? )
思想?
答案 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))
但是,我认为最适合您的是使用custom column on sqlalchemy,process_bind_param
,process_result_value
和bind_expression
的column_expression
example。link
检查下面的代码,它会创建一个我认为符合您需求的自定义列:
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)
也许您想使用一些漂亮的自定义类型,并希望在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.HEX
和func.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干预的HEX
和UNHEX
替换为与HEX和UNHEX一样执行相同功能的python函数,对十六进制字符串进行编码和解码。如果直接连接到数据库,则可以使用HEX和UNHEX,但是从SQLAlchemy中可以使用codecs.enconde
和codecs.decode
函数。
我敢打赌,如果有人感兴趣,编写适当的处理器,甚至可以从python的角度将十六进制值作为整数进行管理,从而允许存储大于BIGINT的整数。
一些注意事项:
BINARY
代替VARBINARY
。'aa' != 'AA'
但0xaa == 0xAA
。VARBINARY
时要小心,因为'aa' != '00aa'
BINARY
,可以说您的列为col = Column(BinaryHex(length=4))
,请考虑到您提供的任何少于length
个字节的值都将以零完成。我的意思是,如果你这样做
obj.col = 'aabb'
并提交,以后再从数据酶中检索到时,将得到obj.col == 'aabb0000'
,这是完全不同的。