[英]How do I get a raw, compiled SQL query from a SQLAlchemy expression?
我有一个 SQLAlchemy 查询 object 并希望获取已编译的 SQL 语句的文本,其所有参数都已绑定(例如,没有%s
或其他等待语句编译器或 MySQLdb 方言引擎绑定的变量等)。
在查询上调用str()
会显示如下内容:
SELECT id WHERE date_added <= %s AND date_added >= %s ORDER BY count DESC
我试过查看 query._params 但它是一个空字典。 我使用sqlalchemy.ext.compiler.compiles
装饰器的这个例子编写了我自己的编译器,但即使那里的语句仍然有%s
我想要数据的地方。
我不太清楚何时将我的参数混入以创建查询; 在检查查询 object 时,它们始终是一个空字典(尽管查询执行得很好,并且当您打开 echo 日志记录时引擎将其打印出来)。
我开始收到 SQLAlchemy 不想让我知道底层查询的消息,因为它打破了所有不同 DB-API 的表达式 API 接口的一般性质。 我不介意在我发现它是什么之前是否执行了查询; 我只是想知道!
该博客提供了更新的答案。
引用博客文章,这是建议并为我工作。
>>> from sqlalchemy.dialects import postgresql
>>> print str(q.statement.compile(dialect=postgresql.dialect()))
其中 q 定义为:
>>> q = DBSession.query(model.Name).distinct(model.Name.value) \
.order_by(model.Name.value)
或者只是任何一种session.query()
。
感谢 Nicolas Cadou 的回答! 我希望它可以帮助到这里搜索的其他人。
该文档使用literal_binds
打印包含参数的查询q
:
print(q.statement.compile(compile_kwargs={"literal_binds": True}))
上面的方法有一个警告,它只支持基本类型,例如整数和字符串,而且如果直接使用没有预设值的 bindparam() ,它也无法将其字符串化。
该文档还发出此警告:
切勿将此技术用于从不受信任的输入(例如来自 Web 表单或其他用户输入应用程序)接收的字符串内容。 SQLAlchemy 将 Python 值强制转换为直接 SQL 字符串值的工具对于不受信任的输入是不安全的,并且不会验证传递的数据类型。 在对关系数据库以编程方式调用非 DDL SQL 语句时,始终使用绑定参数。
这应该适用于 Sqlalchemy >= 0.6
from sqlalchemy.sql import compiler
from psycopg2.extensions import adapt as sqlescape
# or use the appropiate escape function from your db driver
def compile_query(query):
dialect = query.session.bind.dialect
statement = query.statement
comp = compiler.SQLCompiler(dialect, statement)
comp.compile()
enc = dialect.encoding
params = {}
for k,v in comp.params.iteritems():
if isinstance(v, unicode):
v = v.encode(enc)
params[k] = sqlescape(v)
return (comp.string.encode(enc) % params).decode(enc)
问题是,sqlalchemy 永远不会将数据与您的查询混合在一起。 查询和数据分别传递给您的底层数据库驱动程序 - 数据的插值发生在您的数据库中。
Sqlalchemy 将您在str(myquery)
看到的查询传递给数据库,并且这些值将放在一个单独的元组中。
您可以使用某种方法,自己用查询插入数据(如下面的 albertov 建议),但这与 sqlalchemy 正在执行的不同。
对于 MySQLdb 后端,我稍微修改了 albertov 的精彩答案(非常感谢!)。 我确信它们可以合并以检查comp.positional
为True
但这稍微超出了这个问题的范围。
def compile_query(query):
from sqlalchemy.sql import compiler
from MySQLdb.converters import conversions, escape
dialect = query.session.bind.dialect
statement = query.statement
comp = compiler.SQLCompiler(dialect, statement)
comp.compile()
enc = dialect.encoding
params = []
for k in comp.positiontup:
v = comp.params[k]
if isinstance(v, unicode):
v = v.encode(enc)
params.append( escape(v, conversions) )
return (comp.string.encode(enc) % tuple(params)).decode(enc)
首先让我先说我假设您这样做主要是为了调试目的——我不建议尝试修改 SQLAlchemy fluent API 之外的语句。
不幸的是,似乎没有一种简单的方法来显示包含查询参数的编译语句。 SQLAlchemy 实际上并没有将参数放入语句中——它们作为字典传递到数据库引擎中。 这允许特定于数据库的库处理诸如转义特殊字符以避免 SQL 注入之类的事情。
但是,您可以相当轻松地分两步完成此操作。 要获取语句,您可以按照已经显示的方式进行操作,只需打印查询:
>>> print(query)
SELECT field_1, field_2 FROM table WHERE id=%s;
您可以使用 query.statement 更近一步,以查看参数名称。 注意:id_1
下面与%s
以上 - 在这个非常简单的例子中并不是真正的问题,但可能是更复杂语句中的关键。
>>> print(query.statement)
>>> print(query.statement.compile()) # seems to be equivalent, you can also
# pass in a dialect if you want
SELECT field_1, field_2 FROM table WHERE id=:id_1;
然后,您可以通过获取编译语句的params
属性来获取参数的实际值:
>>> print(query.statement.compile().params)
{u'id_1': 1}
这至少适用于 MySQL 后端; 我希望它对于 PostgreSQL 也足够通用,而无需使用psycopg2
。
对于使用 psycopg2 的 postgresql 后端,您可以监听do_execute
事件,然后使用游标、语句和类型强制参数以及Cursor.mogrify()
来内联参数。 您可以返回 True 以防止实际执行查询。
import sqlalchemy
class QueryDebugger(object):
def __init__(self, engine, query):
with engine.connect() as connection:
try:
sqlalchemy.event.listen(engine, "do_execute", self.receive_do_execute)
connection.execute(query)
finally:
sqlalchemy.event.remove(engine, "do_execute", self.receive_do_execute)
def receive_do_execute(self, cursor, statement, parameters, context):
self.statement = statement
self.parameters = parameters
self.query = cursor.mogrify(statement, parameters)
# Don't actually execute
return True
示例用法:
>>> engine = sqlalchemy.create_engine("postgresql://postgres@localhost/test")
>>> metadata = sqlalchemy.MetaData()
>>> users = sqlalchemy.Table('users', metadata, sqlalchemy.Column("_id", sqlalchemy.String, primary_key=True), sqlalchemy.Column("document", sqlalchemy.dialects.postgresql.JSONB))
>>> s = sqlalchemy.select([users.c.document.label("foobar")]).where(users.c.document.contains({"profile": {"iid": "something"}}))
>>> q = QueryDebugger(engine, s)
>>> q.query
'SELECT users.document AS foobar \nFROM users \nWHERE users.document @> \'{"profile": {"iid": "something"}}\''
>>> q.statement
'SELECT users.document AS foobar \nFROM users \nWHERE users.document @> %(document_1)s'
>>> q.parameters
{'document_1': '{"profile": {"iid": "something"}}'}
以下解决方案使用 SQLAlchemy 表达式语言并与 SQLAlchemy 1.1 配合使用。 该解决方案没有将参数与查询混合(按照原作者的要求),而是提供了一种使用 SQLAlchemy 模型为不同的 SQL 方言生成 SQL 查询字符串和参数字典的方法。 该示例基于教程http://docs.sqlalchemy.org/en/rel_1_0/core/tutorial.html
鉴于班级,
from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class foo(Base):
__tablename__ = 'foo'
id = Column(Integer(), primary_key=True)
name = Column(String(80), unique=True)
value = Column(Integer())
我们可以使用select函数生成查询语句。
from sqlalchemy.sql import select
statement = select([foo.name, foo.value]).where(foo.value > 0)
接下来,我们可以将语句编译成查询对象。
query = statement.compile()
默认情况下,该语句是使用与 SQLite 和 Oracle 等 SQL 数据库兼容的基本“命名”实现编译的。 如果需要指定像PostgreSQL这样的方言,可以这样做
from sqlalchemy.dialects import postgresql
query = statement.compile(dialect=postgresql.dialect())
或者,如果您想明确指定方言为 SQLite,您可以将 paramstyle 从 'qmark' 更改为 'named'。
from sqlalchemy.dialects import sqlite
query = statement.compile(dialect=sqlite.dialect(paramstyle="named"))
从查询对象中,我们可以提取查询字符串和查询参数
query_str = str(query)
query_params = query.params
最后执行查询。
conn.execute( query_str, query_params )
您可以使用ConnectionEvents系列中的事件: after_cursor_execute
或before_cursor_execute
。
在@zzzeek的 sqlalchemy UsageRecipes 中,您可以找到以下示例:
Profiling
...
@event.listens_for(Engine, "before_cursor_execute")
def before_cursor_execute(conn, cursor, statement,
parameters, context, executemany):
conn.info.setdefault('query_start_time', []).append(time.time())
logger.debug("Start Query: %s" % statement % parameters)
...
在这里您可以访问您的声明
更新:提出了另一种情况,这里以前的解决方案没有正确生成正确的 SQL 语句。 在深入了解 SQLAlchemy 之后,很明显您不仅需要针对特定方言进行编译,还需要获取已编译的查询并将其初始化为正确的 DBAPI 连接上下文。 否则,不会执行类型绑定处理器之类的事情,并且不会正确转换 JSON.NULL 之类的值。
请注意,这使得该解决方案非常适合 Flask + Flask-SQLAlchemy + psycopg2 + PostgreSQL。 您可能需要通过更改方言和引用连接的方式将此解决方案转换为您的环境。 但是,我非常有信心这会为所有数据类型生成准确的 SQL。
下面的结果是一种简单的方法,可以通过查询查询本身偶尔但可靠地获取将发送到我的 PostgreSQL 后端的准确的、已编译的 SQL:
import sqlalchemy.dialects.postgresql.psycopg2
from flask import current_app
def query_to_string(query):
dialect = sqlalchemy.dialects.postgresql.psycopg2.dialect()
compiled_query = query.statement.compile(dialect=dialect)
sqlalchemy_connection = current_app.db.session.connection()
context = dialect.execution_ctx_cls._init_compiled(
dialect,
sqlalchemy_connection,
sqlalchemy_connection.connection,
compiled_query,
None
)
mogrified_query = sqlalchemy_connection.connection.cursor().mogrify(
context.statement,
context.parameters[0]
)
return mogrified_query.decode()
query = [ .... some ORM query .... ]
print(f"compiled SQL = {query_to_string(query)}")
我认为 .statement 可能会起作用: http ://docs.sqlalchemy.org/en/latest/orm/query.html?highlight=query
>>> local_session.query(sqlalchemy_declarative.SomeTable.text).statement
<sqlalchemy.sql.annotation.AnnotatedSelect at 0x6c75a20; AnnotatedSelectobject>
>>> x=local_session.query(sqlalchemy_declarative.SomeTable.text).statement
>>> print(x)
SELECT sometable.text
FROM sometable
我创建了这个小函数,当我想打印完整的查询时,我导入了这个小函数,考虑到我正处于测试中,而方言已经绑定:
import re
def print_query(query):
regex = re.compile(":(?P<name>\w+)")
params = query.statement.compile().params
sql = regex.sub("'{\g<name>}'", str(query.statement)).format(**params)
print(f"\nPrinting SQLAlchemy query:\n\n")
print(sql)
return sql
如果 SQLAlchemy 你使用的是 PyMySQL,你可以做一个技巧。
我很着急,浪费了很多时间,所以我改变了驱动程序来打印当前语句的参数。
SQLAlchemy 有意不支持文字值的完全字符串化。
但是 PyMySQL 具有执行此操作的“mogrify”方法,但是,SQLALchemy 在使用 ORM 插入/更新(当它控制游标时)如 db.add 或 commit/flush(用于更新)时没有调用它的 HOOK。
所以,只是 go 驱动程序正在使用的地方(知道在哪里使用):pip 显示 pycharm
在文件夹中,找到并编辑文件 cursors.py。
在方法中:
def execute(self, query, args=None):
线下:
query = self.mogrify(query, args)
只需添加:
print(query)
将像魅力一样工作,调试,解决问题并删除打印。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.