特定于Dialect的SQLAlchemy声明性列默认值

时间:2017-09-09 15:14:06

标签: python sqlite sqlalchemy

短版

在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中运行得很好,但是当我调用on Ideone时,使用FlushError在sqlite中死掉(因为你可以看到Session.commit}:

  

sqlalchemy.orm.exc.FlushError:实例<TestTable at 0x7fc0e0254a10>具有NULL身份密钥。如果这是自动生成的值,请检查数据库表是否允许生成新的主键值,以及映射的Column对象是否配置为期望这些生成的值。还要确保此flush()未在不适当的时间发生,例如在load()事件中。

FetchedValue warns 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_valuenot defined anywhere else in SQLAlchemy,因此他们似乎打算在Python中生成默认值,或者编写一个单独的函数来执行SQL查询以在DBMS中生成默认值。我可以这样做,但问题是,我只想为SQLite 做这件事,因为FetchedValue完全符合我想要的postgres。

死胡同

  • 子类化Column 可能无法正常工作。我在源中找不到任何内容,告诉Column使用什么方言,defaultserver_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)

1 个答案:

答案 0 :(得分:1)

不是直接回答你的问题,但希望对某人有帮助

我的问题是想要根据方言改变整理,这是我的解决方案:

from sqlalchemy import Unicode
from sqlalchemy.ext.compiler import compiles

@compiles(Unicode, 'sqlite')    
def compile_unicode(element, compiler, **kw):
    element.collation = None
    return compiler.visit_unicode(element, **kw)

这仅更改了sqlite的所有Unicode列的排序规则。

以下是一些文档:http://docs.sqlalchemy.org/en/latest/core/custom_types.html#overriding-type-compilation