195 votes

SQLAlchemy: affiche la requête réelle

J'aimerais vraiment pouvoir imprimer du code SQL valide pour mon application, y compris des valeurs, plutôt que des paramètres de liaison, mais il n'est pas évident de savoir comment procéder dans SQLAlchemy (à dessein, j'en suis à peu près sûr).

Quelqu'un at-il résolu ce problème de manière générale?

194voto

zzzeek Points 22617

Note: cette réponse est maintenu sur le sqlalchemy de la documentation.

tout d'abord, pour ceux qui éprouvent cette question, remarque que le "stringification" de SQLAlchemy instruction ou de la Requête dans la grande majorité des cas, est aussi simple que:

print str(statement)

cela s'applique à la fois à un ORM Query ainsi que tout select() ou toute autre déclaration. En outre, pour obtenir la déclaration de la compilation d'un dialecte spécifique ou le moteur, si la déclaration n'est pas déjà lié à un, vous pouvez passer ce à compiler():

print statement.compile(someengine)

ou sans moteur:

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

Lorsque l'ORM Query objet, afin d'obtenir à l' compile() méthode, nous avons seulement besoin d'avoir accès à l' .déclaration de l'accesseur en premier:

statement = query.statement
print statement.compile(someengine)

en ce qui concerne la stipulation originale que les paramètres liés à des "inline" dans la chaîne finale, le défi ici est que SQLAlchemy, normalement, n'est pas chargé à cet effet, comme cela est géré de manière appropriée par l'Python DBAPI, pour ne pas mentionner de contourner les paramètres liés est probablement le plus largement exploité les failles de sécurité dans les applications web modernes. SQLAlchemy a une capacité limitée pour ce faire stringification dans certaines circonstances comme celle d'émettre DDL. Pour accéder à cette fonctionnalité, on peut utiliser le 'literal_binds' drapeau, passé de compile_kwargs:

from sqlalchemy.sql import table, column, select

t = table('t', column('x'))

s = select([t]).where(t.c.x == 5)

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

l'approche ci-dessus est la mise en garde qu'il est uniquement pris en charge pour la base de les types, tels que des entiers et des chaînes, et en outre, si un bindparam sans un pré-réglage de la valeur est utilisée directement, il ne sera pas en mesure de stringify que ce soit.

À l'appui de inline interprétation littérale pour les types non pris en charge, de mettre en œuvre un TypeDecorator de la cible, du type qui comprend un TypeDecorator.process_literal_param méthode:

from sqlalchemy import TypeDecorator, Integer


class MyFancyType(TypeDecorator):
    impl = Integer

    def process_literal_param(self, value, dialect):
        return "my_fancy_formatting(%s)" % value

from sqlalchemy import Table, Column, MetaData

tab = Table('mytable', MetaData(), Column('x', MyFancyType()))

print(
    tab.select().where(tab.c.x > 5).compile(
        compile_kwargs={"literal_binds": True})
)

de la production comme:

SELECT mytable.x
FROM mytable
WHERE mytable.x > my_fancy_formatting(5)

70voto

bukzor Points 11085

Combinant les recettes existantes, mais en utilisant l'héritage plutôt que le patcher de singe:

 def literalquery(statement, dialect=None):
    """Generate an SQL expression string with bound parameters rendered inline
    for the given SQLAlchemy statement.

    WARNING: This method of escaping is insecure, incomplete, and for debugging
    purposes only. Executing SQL statements with inline-rendered user values is
    extremely insecure.
    """
    import sqlalchemy.orm
    if isinstance(statement, sqlalchemy.orm.Query):
        if dialect is None:
            dialect = statement.session.get_bind(
                statement._mapper_zero_or_none()
            ).dialect
        statement = statement.statement
    if dialect is None:
        dialect = getattr(statement.bind, 'dialect', None)
    if dialect is None:
        from sqlalchemy.dialects import mysql
        dialect = mysql.dialect()

    Compiler = type(statement._compiler(dialect))

    class LiteralCompiler(Compiler):
        visit_bindparam = Compiler.render_literal_bindparam

        def render_literal_value(self, value, type_):
            if isinstance(value, (Decimal, long)):
                return str(value)
            elif isinstance(value, datetime):
                return repr(str(value))
            else:  # fallback
                value = super(LiteralCompiler, self).render_literal_value(
                    value, type_,
                )
                if isinstance(value, unicode):
                    return value.encode('UTF-8')
                else:
                    return value

    return LiteralCompiler(dialect, statement)
 

Démo:

 # coding: UTF-8
from sqlalchemy.dialects import mysql
from datetime import datetime
from decimal import Decimal

from literalquery import literalquery


def test():
    from sqlalchemy.sql import table, column, select

    mytable = table('mytable', column('mycol'))
    values = (
        5,
        u'snowman: ☃',
        b'UTF-8 snowman: \xe2\x98\x83',
        datetime.now(),
        Decimal('3.14159'),
        long(1234),
    )

    statement = select([mytable]).where(mytable.c.mycol.in_(values)).limit(1)
    print literalquery(statement)


if __name__ == '__main__':
    test()
 

Donne cette sortie:

 SELECT mytable.mycol
FROM mytable
WHERE mytable.mycol IN (5, 'snowman: ☃', 'UTF-8 snowman: ☃',
    '2014-08-01 11:19:54.514764', 3.14159, 1234)
 LIMIT 1
 

12voto

vvladymyrov Points 1765

Ce code est basé sur la réponse existante brillante de @bukzor. Je viens d'ajouter un rendu personnalisé pour datetime.datetime dans Oracle To_Date .

N'hésitez pas à mettre à jour le code correspondant à votre base de données:

 import decimal
import datetime

def printquery(statement, bind=None):
    """
    print a query, with values filled in
    for debugging purposes *only*
    for security, you should always separate queries from their values
    please also note that this function is quite slow
    """
    import sqlalchemy.orm
    if isinstance(statement, sqlalchemy.orm.Query):
        if bind is None:
            bind = statement.session.get_bind(
                    statement._mapper_zero_or_none()
            )
        statement = statement.statement
    elif bind is None:
        bind = statement.bind 

    dialect = bind.dialect
    compiler = statement._compiler(dialect)
    class LiteralCompiler(compiler.__class__):
        def visit_bindparam(
                self, bindparam, within_columns_clause=False, 
                literal_binds=False, **kwargs
        ):
            return super(LiteralCompiler, self).render_literal_bindparam(
                    bindparam, within_columns_clause=within_columns_clause,
                    literal_binds=literal_binds, **kwargs
            )
        def render_literal_value(self, value, type_):
            """Render the value of a bind parameter as a quoted literal.

            This is used for statement sections that do not accept bind paramters
            on the target driver/database.

            This should be implemented by subclasses using the quoting services
            of the DBAPI.

            """
            if isinstance(value, basestring):
                value = value.replace("'", "''")
                return "'%s'" % value
            elif value is None:
                return "NULL"
            elif isinstance(value, (float, int, long)):
                return repr(value)
            elif isinstance(value, decimal.Decimal):
                return str(value)
            elif isinstance(value, datetime.datetime):
                return "TO_DATE('%s','YYYY-MM-DD HH24:MI:SS')" % value.strftime("%Y-%m-%d %H:%M:%S")

            else:
                raise NotImplementedError(
                            "Don't know how to literal-quote value %r" % value)            

    compiler = LiteralCompiler(dialect, statement)
    print compiler.process(statement)
 

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X