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!
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.
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.
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)
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)
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.
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"}}'}
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
.
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 )
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
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