新/重写SQLAlchemy运算符编译器输出

wst*_*wst 5 python sqlalchemy

in_对于非常大的列表,用于编译表达式的默认SQLAlchemy行为是病理性的,我想为运算符创建一个自定义,更快的编译器。解决方案是新运算符(例如in_list_)还是覆盖的默认编译器,对于应用程序都没有关系in_。但是,我还没有找到有关如何执行此操作的任何文档。

编译扩展子类准则不包含任何有关运算符的内容,这表明这不是一个开始的地方。重新定义和创建新运算符文档着重于更改或创建新运算符的行为,但是运算符的行为不是问题,而仅仅是编译器。

这是我要完成的工作的非常不可行的示例:

from sqlalchemy.types import TypeEngine

class in_list_(TypeEngine.Comparator):
  pass

@compiles(in_list_)
def in_list_impl(element, compiler, **kwargs):
  return "IN ('Now', 'I', 'can', 'inline', 'the', 'list')"

Run Code Online (Sandbox Code Playgroud)

然后在表达式中:

select([mytable.c.x, mytable.c.y]).where(mytable.c.x.in_list_(long_list))
Run Code Online (Sandbox Code Playgroud)

Ilj*_*ilä 5

使用IN非常大名单确实是病理性的,你可能会得到更好的服务使用临时表IN反对子查询或连接。但问题是“如何覆盖特定运算符的编译器输出”。在二元运算符如的情况下,INNOT IN你需要重写的是SQLAlchemy的程序如何处理编译BinaryExpressionS:

from sqlalchemy.ext.compiler import compiles
from sqlalchemy.sql.elements import BinaryExpression
from sqlalchemy.sql.operators import in_op, notin_op

def visit_in_op_binary(compiler, binary, operator, **kw):
    return "%s IN %s" % (
        compiler.process(binary.left, **kw),
        compiler.process(binary.right, **{**kw, "literal_binds": True}))

def visit_notin_op_binary(compiler, binary, operator, **kw):
    return "%s NOT IN %s" % (
        compiler.process(binary.left, **kw),
        compiler.process(binary.right, **{**kw, "literal_binds": True}))

@compiles(BinaryExpression)
def compile_binary(binary, compiler, override_operator=None, **kw):
    operator = override_operator or binary.operator

    if operator is in_op:
        return visit_in_op_binary(
            compiler, binary, operator, override_operator=override_operator,
            **kw)

    if operator is notin_op:
        return visit_notin_op_binary(
            compiler, binary, operator, override_operator=override_operator,
            **kw)

    return compiler.visit_binary(binary, override_operator=override_operator, **kw)
Run Code Online (Sandbox Code Playgroud)

请注意,对于非常大的列表,简单地生成包含绑定参数的分组和子句列表的二进制表达式会花费惊人的大量时间,更不用说即使使用文字绑定也要编译所有内容,因此您可能不会观察到显着的性能提升。另一方面,许多实现对您可以在语句中使用的占位符/参数的数量有限制,因此内联绑定允许此类查询完全运行。

另一方面,如果您的列表确实符合您的实现设置的限制(Postgresql 似乎仅受可用 RAM 的限制),则您可能不需要使用足够新的 SQLAlchemy 的任何编译器变通方法;使用扩展绑定参数代替

In [15]: %%time
    ...: session.query(Foo).\
    ...:     filter(Foo.data.in_(range(250000))).\
    ...:     all()
    ...: 
CPU times: user 5.09 s, sys: 91.9 ms, total: 5.18 s
Wall time: 5.18 s
Out[15]: []

In [16]: %%time
    ...: session.query(Foo).\
    ...:     filter(Foo.data.in_(bindparam('xs', range(250000), expanding=True))).\
    ...:     all()
    ...: 
CPU times: user 310 ms, sys: 8.05 ms, total: 318 ms
Wall time: 317 ms
Out[16]: []
Run Code Online (Sandbox Code Playgroud)

正如评论中提到的,在 1.4 版中,扩展bindparam将支持开箱即用的文字执行:

In [4]: session.query(Foo).\
   ...:     filter(Foo.data.in_(
   ...:         bindparam('xs', range(10), expanding=True, literal_execute=True))).\
   ...:     all()
2019-09-07 20:35:04,560 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
2019-09-07 20:35:04,561 INFO sqlalchemy.engine.base.Engine SELECT foo.id AS foo_id, foo.data AS foo_data 
FROM foo 
WHERE foo.data IN (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)
2019-09-07 20:35:04,561 INFO sqlalchemy.engine.base.Engine ()
Out[4]: []
Run Code Online (Sandbox Code Playgroud)