【发布时间】:2017-09-09 15:14:06
【问题描述】:
短版
在 SQLAlchemy 的 ORM 列声明中,如何在一种方言上使用 server_default=sa.FetchedValue(),在另一种方言上使用 default=somePythonFunction,以便我的真实 DBMS 可以使用触发器填充事物,并且我的测试代码可以针对 sqlite 编写?
背景
我正在使用 SQLAlchemy 的声明式 ORM 来处理 Postgres 数据库,但尝试针对 sqlite:///:memory: 编写单元测试,并遇到了在其主键上计算了默认值的列的问题。举个简单的例子:
CREATE TABLE test_table(
id VARCHAR PRIMARY KEY NOT NULL
DEFAULT (lower(hex(randomblob(16))))
)
SQLite 本身对这个表定义很满意 (sqlfiddle) 但 SQLAlchemy 似乎无法计算出新创建行的 ID。
class TestTable(Base):
__tablename__ = 'test_table'
id = sa.Column(
sa.VARCHAR,
primary_key=True,
server_default=sa.FetchedValue())
像这样的定义在 postgres 中工作得很好,但是当我调用 Session.commit 时,在 sqlite(如您所见 on Ideone)和 FlushError 中死亡:
sqlalchemy.orm.exc.FlushError:实例<TestTable at 0x7fc0e0254a10>有一个NULL身份密钥。如果这是一个自动生成的值,请检查数据库表是否允许生成新的主键值,以及映射的 Column 对象是否配置为期望这些生成的值。还要确保此flush()不会在不适当的时间发生,例如在load()事件中。
FetchedValuewarns us that this can happen on dialects that don't support the RETURNING clause on INSERT 的文档:
对于使用触发器生成主键的特殊情况 值,并且正在使用的数据库不支持 RETURNING 子句, 可能有必要放弃触发器的使用,而是 将 SQL 表达式或函数应用为“预执行”表达式:
t = Table('test', meta, Column('abc', MyType, default=func.generate_new_value(), primary_key=True) )
func.generate_new_value 是not defined anywhere else in SQLAlchemy,所以他们似乎打算在 Python 中生成默认值,或者编写一个单独的函数来执行 SQL 查询以在 DBMS 中生成默认值。我可以这样做,但问题是,我只想为 SQLite 这样做,因为 FetchedValue 在 postgres 上完全符合我的要求。
死胡同
子类化
Column可能不起作用。我在源代码中找不到任何东西告诉专栏正在使用什么方言,default和server_default字段的行为是defined outside the class编写一个在真实 DBMS 上手动调用触发器的 Python 函数会创建一个竞争条件。通过更改 isolation level 来避免竞争条件会导致死锁。
我目前的解决方法
不好,因为它破坏了连接到真实 postgres 的集成测试。
import sys
import sqlalchemy as sa
def trigger_column(*a, **kw):
python_default = kw.pop('python_default')
if 'unittest' in sys.modules:
return sa.Column(*a, default=python_default, **kw)
else
return sa.Column(*a, server_default=sa.FetchedValue(), **kw)
【问题讨论】:
-
处理类似的问题,我想对 mysql 和 sqlite 使用不同的排序规则
标签: python sqlite sqlalchemy