SQL表达式¶

如何将SQL表达式呈现为字符串(可能是内联绑定参数)?

SQLAlchemy核心语句对象或表达式片段以及ORM的“字符串化”。 Query 对象,在大多数简单的情况下,使用 str() 内置函数,如下所示 print 函数(注意python print 函数也调用 str() 如果不明确使用,则自动)::

>>> from sqlalchemy import table, column, select
>>> t = table('my_table', column('x'))
>>> statement = select([t])
>>> print(str(statement))
SELECT my_table.x
FROM my_table

这个 str() 可以在ORM上调用builtin或等效函数 Query 对象以及任何语句,如 select()insert() 以及任何表达式片段,例如:

>>> from sqlalchemy import column
>>> print(column('x') == 'some value')
x = :x_1

特定数据库的字符串化

当我们正在串接的语句或片段包含具有数据库特定字符串格式的元素时,或者当它包含仅在某类数据库中可用的元素时,就会出现复杂的情况。在这些情况下,我们可能会得到一个字符串化语句,该语句对于我们要定位的数据库语法不正确,或者操作可能会引发 UnsupportedCompilationError 例外。在这些情况下,有必要使用 ClauseElement.compile() 方法,同时传递 EngineDialect 表示目标数据库的对象。如下图所示,如果我们有一个MySQL数据库引擎,我们可以用MySQL方言将语句串接起来:

from sqlalchemy import create_engine

engine = create_engine("mysql+pymysql://scott:tiger@localhost/test")
print(statement.compile(engine))

更直接,不需要建立 Engine 对象我们可以实例化 Dialect 对象,如下所示,我们使用PostgreSQL方言:

from sqlalchemy.dialects import postgresql
print(statement.compile(dialect=postgresql.dialect()))

提供ORM时 Query 对象,以便 ClauseElement.compile() 方法我们只需要访问 statement 先访问:

statement = query.statement
print(statement.compile(someengine))

内联呈现绑定参数

警告

从未 对从不受信任的输入(如Web窗体或其他用户输入应用程序)接收的字符串内容使用此技术。sqlAlchemy将python值强制为直接sql字符串值的功能有 对不受信任的输入不安全,也不验证正在传递的数据类型 . 对关系数据库以编程方式调用非DDL SQL语句时,始终使用绑定参数。

上述表单将在传递给python时呈现SQL语句。 DBAPI ,其中包括绑定参数不会以内联方式呈现。sqlAlchemy通常不将绑定参数串接,因为这是由python dbapi适当处理的,更不用说绕过绑定参数可能是现代Web应用程序中最广泛利用的安全漏洞。在某些情况下(如发出DDL的情况下),SQLAlchemy执行此字符串化的能力有限。为了访问此功能,可以使用 literal_binds 标志,传递给 compile_kwargs ::

from sqlalchemy.sql import table, column, select

t = table('t', column('x'))

s = select([t]).where(t.c.x == 5)

print(s.compile(compile_kwargs={"literal_binds": True}))  # **do not use** with untrusted input!!!

上面的方法有一个警告,即它只支持基本类型,如int和string,而且如果 bindparam() 如果不直接使用预设值,它也无法将其串接起来。

要支持不支持的类型的内联文本呈现,请实现 TypeDecorator 对于包含 TypeDecorator.process_literal_param() 方法:

from sqlalchemy import TypeDecorator, Integer


class MyFancyType(TypeDecorator):
    impl = Integer

    def process_literal_param(self, value, dialect):
        return "my_fancy_formatting(%s)" % value

from sqlalchemy import Table, Column, MetaData

tab = Table('mytable', MetaData(), Column('x', MyFancyType()))

print(
    tab.select().where(tab.c.x > 5).compile(
        compile_kwargs={"literal_binds": True})
)

生产产量如下:

SELECT mytable.x
FROM mytable
WHERE mytable.x > my_fancy_formatting(5)

我正在使用op()生成一个自定义运算符,但括号中的内容不正确。

这个 Operators.op() 方法允许创建一个自定义数据库运算符,否则sqlAlchemy不知道:

>>> print(column('q').op('->')(column('p')))
q -> p

但是,当在复合表达式的右侧使用它时,它不会像我们预期的那样生成括号::

>>> print((column('q1') + column('q2')).op('->')(column('p')))
q1 + q2 -> p

在上面的什么地方,我们可能想要 (q1 + q2) -> p .

这种情况的解决方案是使用 Operators.op.precedence 参数设置为高位,其中100是最大值,任何sqlAlchemy运算符使用的最大值当前为15::

>>> print((column('q1') + column('q2')).op('->', precedence=100)(column('p')))
(q1 + q2) -> p

我们还可以使用 ColumnElement.self_group() 方法:

>>> print((column('q1') + column('q2')).self_group().op('->')(column('p')))
(q1 + q2) -> p

为什么括号规则是这样的?

许多数据库都会在括号过多或括号位于不希望出现的异常位置时出错,因此SQLAlchemy不会基于分组生成括号,它使用运算符优先级,并且如果已知运算符是关联的,则括号生成的最少。否则,表达式如下:

column('a') & column('b') & column('c') & column('d')

将产生:

(((a AND b) AND c) AND d)

这很好,但可能会惹恼人们(并被报告为一个bug)。在其他情况下,它会导致更容易混淆数据库或至少具有可读性的事情,例如:

column('q', ARRAY(Integer, dimensions=2))[5][6]

将产生:

((q[5])[6])

也有一些边缘案例,我们可以得到 "(x) = 7" 数据库也不喜欢这样。所以括号并不是简单的括号,它使用操作符优先级和关联性来确定分组。

为了 Operators.op() ,优先级值默认为零。

如果我们违约了 Operators.op.precedence 到100,例如最高?然后这个表达式产生更多的圆括号,但如果不是这样就可以了,也就是说,这两个圆括号等价:

>>> print (column('q') - column('y')).op('+', precedence=100)(column('z'))
(q - y) + z
>>> print (column('q') - column('y')).op('+')(column('z'))
q - y + z

但这两个不是:

>>> print column('q') - column('y').op('+', precedence=100)(column('z'))
q - y + z
>>> print column('q') - column('y').op('+')(column('z'))
q - (y + z)

目前,还不清楚,只要我们基于运算符优先级和关联性进行括号化,是否真的有一种方法可以自动为没有给定优先级的泛型运算符进行括号化,这在所有情况下都适用,因为有时您希望自定义运算符的优先级低于其他运算符有时你希望它更高。

如果上面的“binary”表达式强制使用 self_group() 方法时 op() 假定左侧的复合表达式总是可以无害地加括号。也许这种改变可以在某个时刻进行,但是目前保持括号规则在内部更一致似乎是更安全的方法。