web-dev-qa-db-fra.com

Comment obtenir une requête SQL brute et compilée à partir d'une expression SQLAlchemy?

J'ai un objet de requête SQLAlchemy et je veux obtenir le texte de l'instruction SQL compilée, avec tous ses paramètres liés (par exemple, pas de %s Ou d'autres variables en attente d'être liées par le compilateur d'instructions ou le moteur de dialecte MySQLdb, etc.) .

L'appel de str() sur la requête révèle quelque chose comme ceci:

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

J'ai essayé de chercher dans query._params mais c'est un dict vide. J'ai écrit mon propre compilateur en utilisant cet exemple du décorateur sqlalchemy.ext.compiler.compiles mais même l'instruction y a toujours %s Où je veux des données.

Je n'arrive pas à comprendre quand mes paramètres sont mélangés pour créer la requête; lors de l'examen de l'objet de requête, il s'agit toujours d'un dictionnaire vide (bien que la requête s'exécute correctement et que le moteur l'imprime lorsque vous activez la journalisation d'écho).

Je commence à recevoir le message que SQLAlchemy ne veut pas que je connaisse la requête sous-jacente, car elle rompt la nature générale de l'interface de l'API d'expression toutes les différentes DB-API. Cela ne me dérange pas si la requête est exécutée avant de découvrir ce que c'était; Je veux juste savoir!

81
cce

This blog fournit une réponse mise à jour.

Citant le blog, cela est suggéré et a fonctionné pour moi.

>>> from sqlalchemy.dialects import postgresql
>>> print str(q.statement.compile(dialect=postgresql.dialect()))

Où q est défini comme:

>>> q = DBSession.query(model.Name).distinct(model.Name.value) \
             .order_by(model.Name.value)

Ou tout type de session.query ().

Merci à Nicolas Cadou pour la réponse! J'espère que cela aide ceux qui viennent chercher ici.

79
AndyBarr

documentation utilise literal_binds pour imprimer une requête q y compris les paramètres:

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

l'approche ci-dessus présente les avertissements qu'elle n'est prise en charge que pour les types de base, tels que les entiers et les chaînes, et en outre si un bindparam () sans valeur prédéfinie est utilisé directement, il ne pourra pas non plus le strifier.

65
Matt

Cela devrait fonctionner avec 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)
24
albertov

Pour le backend MySQLdb, j'ai un peu modifié la réponse impressionnante d'Albertov (merci beaucoup!). Je suis sûr qu'ils pourraient être fusionnés pour vérifier si comp.positional était True, mais cela dépasse légèrement la portée de cette question.

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)
18
cce

Le fait est que sqlalchemy ne mélange jamais les données avec votre requête. La requête et les données sont transmises séparément à votre pilote de base de données sous-jacent - l'interpolation des données se produit dans votre base de données.

Sqlalchemy transmet la requête comme vous l'avez vu dans str(myquery) à la base de données, et les valeurs iront dans un Tuple séparé.

Vous pouvez utiliser une approche où vous interpolez les données avec la requête vous-même (comme le suggère albertov ci-dessous), mais ce n'est pas la même chose que sqlalchemy exécute.

13
nosklo

Pour le backend postgresql utilisant psycopg2, vous pouvez écouter l'événement do_execute, Puis utiliser le curseur, l'instruction et taper les paramètres forcés avec Cursor.mogrify() pour aligner les paramètres. Vous pouvez retourner True pour empêcher l'exécution réelle de la requête.

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

Exemple d'utilisation:

>>> 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"}}'}
8
rectalogic

Tout d'abord, permettez-moi de commencer en disant que je suppose que vous faites cela principalement à des fins de débogage - je ne recommanderais pas d'essayer de modifier l'instruction en dehors de l'API couramment SQLAlchemy.

Malheureusement, il ne semble pas y avoir un moyen simple d'afficher l'instruction compilée avec les paramètres de requête inclus. SQLAlchemy ne place pas réellement les paramètres dans l'instruction - ils sont passés dans le moteur de base de données sous forme de dictionnaire . Cela permet à la bibliothèque spécifique à la base de données de gérer des choses comme l'échappement de caractères spéciaux pour éviter l'injection SQL.

Mais vous pouvez le faire dans un processus en deux étapes assez facilement. Pour obtenir la déclaration, vous pouvez faire comme vous l'avez déjà montré et imprimer simplement la requête:

>>> print(query)
SELECT field_1, field_2 FROM table WHERE id=%s;

Vous pouvez vous rapprocher avec query.statement pour voir les noms des paramètres. (Remarque :id_1 ci-dessous vs %s ci-dessus - pas vraiment un problème dans cet exemple très simple, mais pourrait être la clé d'une déclaration plus compliquée.)

>>> 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;

Ensuite, vous pouvez obtenir les valeurs réelles des paramètres en obtenant la propriété params de l'instruction compilée:

>>> print(query.statement.compile().params)
{u'id_1': 1} 

Cela a fonctionné pour un backend MySQL au moins; Je m'attendrais à ce qu'il soit également assez général pour PostgreSQL sans avoir besoin d'utiliser psycopg2.

6
Hannele

La solution suivante utilise le langage d'expression SQLAlchemy et fonctionne avec SQLAlchemy 1.1. Cette solution ne mélange pas les paramètres avec la requête (comme demandé par l'auteur d'origine), mais fournit un moyen d'utiliser des modèles SQLAlchemy pour générer des chaînes de requête SQL et des dictionnaires de paramètres pour différents dialectes SQL. L'exemple est basé sur le tutoriel http://docs.sqlalchemy.org/en/rel_1_0/core/tutorial.html

Compte tenu de la classe,

from sqlalchemy import Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class foo(Base):
    __table= 'foo'
    id = Column(Integer(), primary_key=True)
    name = Column(String(80), unique=True)
    value = Column(Integer())

nous pouvons produire une instruction de requête en utilisant la fonction select .

from sqlalchemy.sql import select    
statement = select([foo.name, foo.value]).where(foo.value > 0)

Ensuite, nous pouvons compiler l'instruction dans un objet de requête.

query = statement.compile()

Par défaut, l'instruction est compilée à l'aide d'une implémentation de base "nommée" compatible avec les bases de données SQL telles que SQLite et Oracle. Si vous devez spécifier un dialecte tel que PostgreSQL, vous pouvez le faire

from sqlalchemy.dialects import postgresql
query = statement.compile(dialect=postgresql.dialect())

Ou si vous souhaitez spécifier explicitement le dialecte comme SQLite, vous pouvez changer le paramstyle de 'qmark' en 'named'.

from sqlalchemy.dialects import sqlite
query = statement.compile(dialect=sqlite.dialect(paramstyle="named"))

De l'objet de requête, nous pouvons extraire la chaîne de requête et les paramètres de requête

query_str = str(query)
query_params = query.params

et enfin exécuter la requête.

conn.execute( query_str, query_params )
3
eric

Vous pouvez utiliser des événements de ConnectionEvents family: after_cursor_execute ou before_cursor_execute .

Dans sqlalchemy sageRecipes by @zzzeek vous pouvez trouver cet exemple:

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

Ici, vous pouvez accéder à votre déclaration

2
Alex Bender

Je pense que .statement ferait probablement l'affaire: 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
0
user2757902