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()
方法,同时传递 Engine
或 Dialect
表示目标数据库的对象。如下图所示,如果我们有一个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)
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)
许多 DBAPI 实现利用 pyformat
或 format
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
这个 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()
假定左侧的复合表达式总是可以无害地加括号。也许这种改变可以在某个时刻进行,但是目前保持括号规则在内部更一致似乎是更安全的方法。
flambé! the dragon and The Alchemist image designs created and generously donated by Rotem Yaari.
Created using Sphinx 4.2.0.