Release: 1.4.25 | Release Date: September 22, 2021

SQLAlchemy 1.4 Documentation

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() 方法我们只需要访问 Query.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)

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

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

此功能主要用于日志记录或调试目的,其中查询的原始sql字符串可能会很有用。注意 dialect 参数也应传递给 ClauseElement.compile() 方法呈现将发送到数据库的查询。

要支持不支持的类型的内联文本呈现,请实现 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()))

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

生产产量如下:

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

将“POSTCOMPILE”参数呈现为绑定参数

SQLAlChemy在名为的绑定参数上包含一个变量 BindParameter.expanding ,它是在编译SQL构造时以中间状态呈现的“后期求值”参数,然后在语句执行时传递实际已知值时对其进行进一步处理。扩展参数用于 ColumnOperators.in_() 表达式,以便可以独立于传递给特定调用的实际值列表安全地缓存SQL字符串 ColumnOperators.in_() ::

>>> from sqlalchemy import column
>>> expr = column('x').in_([1, 2, 3])
>>> print(expr)
x IN ([POSTCOMPILE_x_1])

若要使用实数绑定参数符号呈现IN子句,请使用 render_postcompile=True 标记为 ClauseElement.compile() ::

>>> print(expr.compile(compile_kwargs={"render_postcompile": True}))
x IN (:x_1_1, :x_1_2, :x_1_3)

如上一节所述, literal_binds 标志在此通过自动设置 render_postcompile 至True::

>>> print(expr.compile(compile_kwargs={"literal_binds": True}))
x IN (1, 2, 3)

为什么在串化SQL语句时百分号要加倍?

许多 DBAPI 实现利用 pyformatformat paramstyle ,它们的语法中必然包含百分号。大多数执行此操作的DBAPI都希望将出于其他原因使用的百分号以所用语句的字符串形式加倍(即转义),例如::

SELECT a, b FROM some_table WHERE a = %s AND c = %s AND num %% modulus = 0

当SQLAlChemy将SQL语句传递到基础DBAPI时,替换绑定参数的方式与Python字符串插值运算符的工作方式相同 % ,在许多情况下,DBAPI实际上直接使用此运算符。如上所述,绑定参数的替换将如下所示::

SELECT a, b FROM some_table WHERE a = 5 AND c = 10 AND num % modulus = 0

PostgreSQL(缺省DBAPI为ological copg2)和MySQL(缺省DBAPI为mysqlclient)等数据库的默认编译器将有此百分号转义行为::

>>> from sqlalchemy import table, column
>>> from sqlalchemy.dialects import postgresql
>>> t = table("my_table", column("value % one"), column("value % two"))
>>> print(t.select().compile(dialect=postgresql.dialect()))
SELECT my_table."value %% one", my_table."value %% two"
FROM my_table

当使用这样的方言时,如果需要不包含绑定参数符号的非dbapi语句,移除百分号的一种快捷方法是使用Python语句在空参数集中简单地替身。 % 操作员直接::

>>> strstmt = str(t.select().compile(dialect=postgresql.dialect()))
>>> print(strstmt % ())
SELECT my_table."value % one", my_table."value % two"
FROM my_table

另一种方法是在正在使用的方言上设置不同的参数样式;全部 Dialect 实现接受参数 paramstyle 这将使该方言的编译器使用给定的参数样式。下面是非常常见的 named 参数样式设置在用于编译的方言中,因此百分号在编译后的SQL形式中不再重要,并且不再转义::

>>> print(t.select().compile(dialect=postgresql.dialect(paramstyle="named")))
SELECT my_table."value % one", my_table."value % two"
FROM my_table

我正在使用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() 假定左侧的复合表达式总是可以无害地加括号。也许这种改变可以在某个时刻进行,但是目前保持括号规则在内部更一致似乎是更安全的方法。

Previous: 元数据/架构 Next: ORM配置