SQLAlchemy 0.9.X对None条件处理的差异

SQLAlchemy 最新的版本已经是0.9.1了,不过有人报告说Uliweb在使用这个版本时有问题,get()不到数据。后来查了一下,发现生成的查询条件多了一个NULL,比如:

Blog.get(1)
SELECT todo.title, todo.post_date, todo.finished, todo.id FROM todo 
WHERE todo.id = 1 AND NULL LIMIT 1 OFFSET 0;

在条件中多了一个NULL,很奇怪,換成0.8.X就好了。然后我仔细查了一下,在Uliweb中,get()会调用filter,而filter本身可以传入多个条件,因此需要将条件以与的方式合并,因此代码是:

cond = None
for c in condition:
    if c is not None:
        cond = c & cond

因此,最后条件会和None相与。

然后就发现是 and_(&) 的处理和以前不同的。以前的代码是在sqlalchemy.sql.expression中的ClauseList的类中,以0.8.3版本为例是在3325行左右,代码为:

if self.group_contents:
    self.clauses = [
        _literal_as_text(clause).self_group(against=self.operator)
        for clause in clauses if clause is not None]
else:
    self.clauses = [
        _literal_as_text(clause)
        for clause in clauses if clause is not None]

可以看到,在for中已经装饰None的条件过滤了,所以对于条件是None的会自动过滤掉。

但是到了0.9.1中,代码发生了很大的变化,这块代码都移到了elements.py中了,而且真正的and_的处理从ClauseList中移到了BooleanClauseList中,并且代码处理也有所不同:

convert_clauses = []

clauses = util.coerce_generator_arg(clauses)
for clause in clauses:
    clause = _literal_as_text(clause)

    if isinstance(clause, continue_on):
        continue
    elif isinstance(clause, skip_on):
        return clause.self_group(against=operators._asbool)

    convert_clauses.append(clause)

没有了对None的过滤。所以造成了None在0.9.1中与0.8.X中不兼容的情况。不知道这算不算一个Bug。不过我已经把uliweb中的cond = None 改为了 cond = '' 。这样结果在0.8.X中还是在0.9.X中都是对的。

所以如果你是在0.9.X中使用None条件,建议改为 true()。true 可以从 sqlalchemy.sql 中导出。

update

如果你希望0.9.x和0.8.x对None的处理一样,我找到一个打补丁的方法:

from sqlalchemy.sql.compiler import SQLCompiler
def visit_null(self, expr, **kw):
    return ''
setattr(SQLCompiler, 'visit_null', visit_null)

这样 NULL 就不会出现。你也可以考虑在这个地方加一个抛异常,强制用户修改,这也是一个办法。

你可能感兴趣的:(SQLAlchemy 0.9.X对None条件处理的差异)