Как получить необработанный скомпилированный SQL-запрос из выражения SQLAlchemy?
у меня есть объект запроса SQLAlchemy и я хочу получить текст скомпилированного оператора SQL со всеми его параметрами (например, no %s или другие переменные, ожидающие привязки компилятором операторов или mysqldb dialect engine и т. д.).
вызов str() на запрос показывает что-то вроде этого:
SELECT id WHERE date_added <= %s AND date_added >= %s ORDER BY count DESC
Я пробовал искать в запросе._params но это пустой словарь. Я написал свой собственный компилятор с помощью этот пример sqlalchemy.ext.compiler.compiles оформителя но даже заявление там все равно есть %s где я хочу данные.
Я не могу понять, когда мои параметры смешиваются для создания запроса; при изучении объекта запроса они всегда являются пустым словарем (хотя запрос выполняется нормально, и двигатель распечатывает его при включении Эхо-входа).
я начинаю получать сообщение о том, что SQLAlchemy не хочет, чтобы я знал базовый запрос, поскольку он нарушает общий характер интерфейса API выражения все различные DB-API. Я не возражаю, если запрос будет выполнен до того, как я узнал, что это было; я просто хочу знать!
10 ответов:
этой блог предоставляет обновленный ответ.
цитируя сообщение в блоге, это предлагается и работает для меня.
>>> 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)или просто любой вид сессии.запрашивать.)(
спасибо Николя Каду за ответ! Я надеюсь, что это поможет другим, кто ищет здесь.
The документация использует
literal_bindsдля печати запросаqв том числе параметры:print(q.statement.compile(compile_kwargs={"literal_binds": True}))вышеописанный подход имеет предостережения о том, что он поддерживается только для основных типов, таких как ints и strings, и, кроме того, если bindParam() без предварительно заданного значения используется напрямую, он также не сможет его stringify.
Это должно работать с 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)
для mysqldb backend я изменил удивительный ответ альбертова (спасибо большое!) немного. Я уверен, что они могут быть объединены, чтобы проверить, если комп.позиционный был верен, но это немного выходит за рамки этого вопроса.
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 никогда не смешивает данные с вашим запросом. Запрос и данные передаются отдельно в базовый драйвер базы данных-интерполяция данных происходит в вашей базе данных.
Sqlalchemy передает запрос, как вы видели в
str(myquery)в базу данных, и значения будут идти в отдельном кортеже.вы можете использовать какой-то подход, когда вы интерполируете данные с помощью запроса самостоятельно (как предложил Альбертов ниже), но это не одно и то же что sqlalchemy выполняется.
для PostgreSQL backend с помощью 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://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"}}'}
вы можете использовать события от ConnectionEvents семья:
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) ...здесь вы можете получить доступ к вашей сообщении
следующее решение использует язык выражений 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())мы можем создать оператор запроса с помощью выберите
сначала позвольте мне предисловие, сказав, что я предполагаю, что вы делаете это в основном для целей отладки-я бы не рекомендовал пытаться изменить оператор за пределами SQLAlchemy fluent API.
к сожалению, не существует простого способа показать скомпилированный оператор с включенными параметрами запроса. SQLAlchemy на самом деле не помещает параметры в Оператор-они передается в компонент database engine в виде словаря. Это позволяет библиотека для конкретной базы данных обрабатывает экранирование специальных символов и т. п., Чтобы избежать инъекции SQL.
но вы можете сделать это в двухэтапном процессе достаточно легко. Чтобы получить инструкцию, вы можете сделать так, как вы уже показали, и просто распечатать запрос:
>>> print(query) SELECT field_1, field_2 FROM table WHERE id=%s;вы можете получить один шаг ближе с запросом.оператор, чтобы увидеть имена параметров. (Примечание
:id_1ниже vs%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.
Я думаю .заявление, возможно, сделает трюк: 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
Comments