Подтвердить что ты не робот

Как получить исходный, скомпилированный SQL-запрос из выражения SQLAlchemy?

У меня есть объект запроса SQLAlchemy и вы хотите получить текст скомпилированного оператора SQL со всеми его параметрами (например, no %s или другие переменные, ожидающие привязки компилятором оператора или диалогическим движком MySQLdb и т.д.)..

Вызов str() по запросу показывает что-то вроде этого:

SELECT id WHERE date_added <= %s AND date_added >= %s ORDER BY count DESC

Я пробовал искать в query._params, но это пустой dict. Я написал свой собственный компилятор, используя этот пример декоратора sqlalchemy.ext.compiler.compiles, но даже в заявлении все еще есть %s, где мне нужны данные.

Я не могу понять, когда мои параметры смешиваются для создания запроса; при исследовании объекта запроса они всегда являются пустым словарем (хотя запрос выполняется отлично, и двигатель выводит его при включении эхо-журнала).

Я начинаю получать сообщение о том, что SQLAlchemy не хочет, чтобы я знал базовый запрос, поскольку он нарушает общий характер интерфейса API выражения всех разных DB-API. Я не возражаю, если запрос выполняется, прежде чем я узнаю, что это такое; Я просто хочу знать!

4b9b3361

Ответ 1

Этот блог содержит обновленный ответ.

Цитата из сообщения в блоге, это предлагается и работает для меня.

>>> 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().

Спасибо Николасу Каду за ответ! Я надеюсь, что это поможет другим, которые приходят искать здесь.

Ответ 2

документация использует literal_binds для печати запроса q, включая параметры:

print(q.statement.compile(compile_kwargs={"literal_binds": True}))

у вышеупомянутого подхода есть предостережения, что он поддерживается только для базовых типов, таких как целые и строковые, и, кроме того, если bindparam() без предварительно заданного значения используется напрямую, он также не сможет его преобразовать в строку.

Документация также выдает это предупреждение:

Никогда не используйте эту технику с содержимым строки, полученным от ненадежного ввод, например, из веб-форм или других приложений ввода пользователя. Средства SQLAlchemys для преобразования значений Python в прямую строку SQL значения не защищены от ненадежного ввода и не проверяют тип передаваемых данных. Всегда используйте связанные параметры, когда программно вызывать не-DDL операторы SQL против реляционного база данных.

Ответ 3

Это должно работать с 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)

Ответ 4

Для бэкэнда MySQLdb я немного изменил отличный ответ для альбертов (большое спасибо!). Я уверен, что их можно объединить, чтобы проверить, был ли 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)

Ответ 5

Дело в том, что sqlalchemy никогда не смешивает данные с вашим запросом. Запрос и данные передаются по отдельности вашему базовому драйверу базы данных - интерполяция данных происходит в вашей базе данных.

Sqlalchemy передает запрос, как вы видели в str(myquery) в базу данных, и значения str(myquery) в отдельный кортеж.

Вы могли бы использовать некоторый подход, где вы интерполируете данные с запросом самостоятельно (как предложили альбертовы ниже), но это не то же самое, что выполняет sqlalchemy.

Ответ 6

Для бэкэнд postgresql, использующего psycopg2, вы можете прослушивать событие 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://[email protected]/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"}}'}

Ответ 7

Сначала позвольте мне предисловие, сказав, что я предполагаю, что вы делаете это в основном для целей отладки - я бы не рекомендовал пытаться изменять оператор за пределами свободно используемого API-интерфейса SQLAlchemy.

К сожалению, нет простого способа показать скомпилированный оператор с включенными параметрами запроса. 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()) # reasonably 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.

Ответ 8

В следующем решении используется язык выражения 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()

По умолчанию оператор компилируется с использованием базовой реализации "named", которая совместима с базами данных SQL, такими как SQLite и Oracle. Если вам нужно указать диалект, такой как 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 )

Ответ 9

Вы можете использовать события из ConnectionEvents family: after_cursor_execute или before_cursor_execute.

В sqlalchemy UsageRecipes от пользователя @zzzeek вы можете найти этот пример:

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)
...

Здесь вы можете получить доступ к своему заявлению

Ответ 10

Я думаю, что это может сделать трюк: 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