SQLAlchemy: वास्तविक क्वेरी प्रिंट करें


165

मैं वास्तव में अपने अनुप्रयोग के लिए मान्य SQL प्रिंट करने में सक्षम होना चाहता हूं, जिसमें मानों को शामिल किया गया है, बल्कि बाइंड मापदंडों के अनुसार, लेकिन यह स्पष्ट नहीं है कि SQLAlchemy में यह कैसे करना है (डिजाइन से, मुझे काफी यकीन है)।

क्या किसी ने इस समस्या को सामान्य तरीके से हल किया है?


1
मैंने नहीं किया है, लेकिन आप शायद SQLAlchemy के sqlalchemy.engineलॉग में टैप करके एक कम नाजुक समाधान का निर्माण कर सकते हैं । यह क्वेरीज़ और बाइंड पैरामीटर्स को लॉग करता है, आपको केवल बाइंड प्लेसहोल्डर्स को मूल्यों के साथ आसानी से निर्मित SQL क्वेरी स्ट्रिंग पर बदलना होगा।
साइमन

@ साइमन: लकड़हारे का उपयोग करने में दो समस्याएं हैं: 1) यह केवल प्रिंट करता है जब एक स्टेटमेंट निष्पादित हो रहा है 2) मुझे अभी भी एक स्ट्रिंग को बदलना होगा, उस मामले को छोड़कर, मुझे बाइंड-टेम्पलेट स्ट्रिंग नहीं पता होगा , और मुझे किसी तरह इसे क्वेरी पाठ से बाहर करना होगा, जिससे समाधान अधिक नाजुक हो जाएगा।
bukzor

नया URL @ zzzeek के FAQ के लिए docs.sqlalchemy.org/en/latest/faq/… प्रतीत होता है ।
जिम डेलांट

जवाबों:


168

अधिकांश मामलों में, SQLAlchemy स्टेटमेंट या क्वेरी का "स्ट्रिंगिफिकेशन" जितना सरल है:

print str(statement)

यह एक ORM के Queryसाथ-साथ किसी भी select()या अन्य कथन पर लागू होता है ।

नोट : निम्नलिखित विस्तृत जवाब sqlalchemy प्रलेखन पर बनाए रखा जा रहा है ।

किसी विशिष्ट बोली या इंजन को संकलित करने के लिए कथन प्राप्त करने के लिए, यदि कथन स्वयं पहले से ही बाध्य नहीं है, तो आप इसे संकलन करने के लिए पास कर सकते हैं () :

print statement.compile(someengine)

या बिना इंजन के:

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

जब ORM Queryऑब्जेक्ट दिया जाता है , तो उस compile()विधि को प्राप्त करने के लिए हमें केवल .statement accessor की आवश्यकता होती है :

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

मूल शर्त के संबंध में कि बाउंड पैरामीटर को अंतिम स्ट्रिंग में "इनलाइन" किया जाना है, यहां चुनौती यह है कि SQLAlchemy को आमतौर पर इसके साथ काम नहीं किया जाता है, क्योंकि यह पायथन डीबीआरआई द्वारा उचित रूप से नियंत्रित किया जाता है, बाध्य मापदंडों का उल्लेख नहीं करना है। आधुनिक वेब अनुप्रयोगों में संभवतः सबसे व्यापक रूप से शोषित सुरक्षा छेद हैं। SQLAlchemy में डीडीएल को उत्सर्जित करने जैसी कुछ परिस्थितियों में इस स्ट्रिंग को करने की सीमित क्षमता है। इस कार्यक्षमता का उपयोग करने के लिए, कोई व्यक्ति 'शाब्दिक_बंड्स' फ़्लैग का उपयोग कर सकता है 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})

उपर्युक्त दृष्टिकोण के बारे में कहा जाता है कि यह केवल बुनियादी प्रकारों के लिए समर्थित है, जैसे कि इन्ट्स और स्ट्रिंग्स, और इसके अलावा यदि bindparam बिना पूर्व-निर्धारित मूल्य का सीधे उपयोग किया जाता है, तो वह इसे स्ट्रिंग करने में सक्षम नहीं होगा।

समर्थित नहीं प्रकारों के लिए इनलाइन शाब्दिक प्रतिपादन का समर्थन करने के TypeDecoratorलिए, लक्ष्य प्रकार के लिए लागू करें जिसमें एक TypeDecorator.process_literal_paramविधि शामिल है :

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

उत्पादन का उत्पादन की तरह:

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

2
यह स्ट्रिंग के आसपास उद्धरण नहीं डालता है, और कुछ बाध्य पैरामेट्स को हल नहीं करता है।
बूकॉर

1
उत्तर की दूसरी छमाही को नवीनतम जानकारी के साथ अद्यतन किया गया है।
zzzeek

2
@zzzeek क्यों डिफ़ॉल्ट रूप से sqlalchemy में सुंदर-प्रिंटिंग क्वेरी शामिल नहीं है? की तरह query.prettyprint()। यह बड़े प्रश्नों के साथ डिबगिंग दर्द को कम करता है।
jmagnusson

2
@jmagnusson क्योंकि सुंदरता देखने वाले की नजर में है :) @compilesसुंदर-मुद्रण प्रणाली को लागू करने के लिए किसी भी तीसरे पक्ष के पैकेजों के लिए पर्याप्त हुक (जैसे कि कर्सर_एक्सट्यूट इवेंट, पायथन लॉगिंग फिल्टर , आदि) हैं।
zzzeek

1
@buzkor re: सीमा जो 1.0 bitbucket.org/zzzeek/sqlalchemy/issue/3034/… में तय की गई है
zzzeek

66

यह अजगर 2 और 3 में काम करता है और पहले की तुलना में थोड़ा साफ है, लेकिन इसके लिए SA> = 1.0 की आवश्यकता होती है।

from sqlalchemy.engine.default import DefaultDialect
from sqlalchemy.sql.sqltypes import String, DateTime, NullType

# python2/3 compatible.
PY3 = str is not bytes
text = str if PY3 else unicode
int_type = int if PY3 else (int, long)
str_type = str if PY3 else (str, unicode)


class StringLiteral(String):
    """Teach SA how to literalize various things."""
    def literal_processor(self, dialect):
        super_processor = super(StringLiteral, self).literal_processor(dialect)

        def process(value):
            if isinstance(value, int_type):
                return text(value)
            if not isinstance(value, str_type):
                value = text(value)
            result = super_processor(value)
            if isinstance(result, bytes):
                result = result.decode(dialect.encoding)
            return result
        return process


class LiteralDialect(DefaultDialect):
    colspecs = {
        # prevent various encoding explosions
        String: StringLiteral,
        # teach SA about how to literalize a datetime
        DateTime: StringLiteral,
        # don't format py2 long integers to NULL
        NullType: StringLiteral,
    }


def literalquery(statement):
    """NOTE: This is entirely insecure. DO NOT execute the resulting strings."""
    import sqlalchemy.orm
    if isinstance(statement, sqlalchemy.orm.Query):
        statement = statement.statement
    return statement.compile(
        dialect=LiteralDialect(),
        compile_kwargs={'literal_binds': True},
    ).string

डेमो:

# coding: UTF-8
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'),
        10 ** 20,  # a long integer
    )

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


if __name__ == '__main__':
    test()

इस आउटपुट देता है: (अजगर 2.7 और 3.4 में परीक्षण किया गया)

SELECT mytable.mycol
FROM mytable
WHERE mytable.mycol IN (5, 'snowman: ☃', 'UTF-8 snowman: ☃',
      '2015-06-24 18:09:29.042517', 3.14159, 100000000000000000000)
 LIMIT 1

2
यह बहुत बढ़िया है ... इसे कुछ डिबग लिबास में जोड़ना होगा ताकि हम इसे आसानी से एक्सेस कर सकें। इस पर फुटवर्क करने के लिए धन्यवाद। मुझे आश्चर्य है कि इसे इतना जटिल होना पड़ा।
कोरी ओ

5
मुझे पूरा यकीन है कि यह जानबूझकर कठिन है, क्योंकि newbies को char.execute () स्ट्रिंग पसंद है। आमतौर पर वयस्कों की सहमति के सिद्धांत का उपयोग हालांकि अजगर में किया जाता है।
bukzor

बहुत उपयोगी। धन्यवाद!
क्लीम

वाकई बहूत बढिया। मैंने स्वतंत्रता प्राप्त की और इसे stackoverflow.com/a/42066590/2127439 में शामिल किया , जिसमें SQLAlchemy v0.7.9 - v1.1.15 शामिल है, जिसमें INSERT और UPDATE स्टेटमेंट (PY2 (PY3) शामिल हैं।
वोल्फमैनक्स

बहुत अच्छा। लेकिन यह नीचे के रूप में परिवर्तित हो रहा है। 1) क्वेरी (तालिका) .filter (Table.Column1.is_ (गलत) जहां से कॉलम 1 IS 0. 2) क्वेरी (तालिका) .filter (Table.Column1.is_ (सच) WHERE Column1 IS 1. 3) क्वेरी के लिए। तालिका) .filter (Table.Column1 == func.any ([1,2,3])) WHERE Column1 = किसी भी ('[1,2,3]') से ऊपर के वाक्य विन्यास में गलत हैं।
शेखर सी

51

यह देखते हुए कि आप क्या चाहते हैं केवल डिबगिंग करते समय, आप SQLAlchemy को echo=Trueसभी SQL प्रश्नों को लॉग इन करने के लिए शुरू कर सकते हैं । उदाहरण के लिए:

engine = create_engine(
    "mysql://scott:tiger@hostname/dbname",
    encoding="latin1",
    echo=True,
)

यह भी केवल एक अनुरोध के लिए संशोधित किया जा सकता है:

echo=False- यदि True, इंजन repr()इंजन लॉगर के लिए सभी स्टेटमेंट्स के साथ-साथ उनके पैरामीटर सूचियों को भी लॉग करेगा , जो कि चूक करता है sys.stdoutechoकी विशेषता Engineकिसी भी समय चालू और बंद लॉगिंग चालू करने के लिए संशोधित कर सकते हैं। यदि स्ट्रिंग पर सेट किया जाता है "debug", तो परिणाम पंक्तियों को मानक आउटपुट पर भी मुद्रित किया जाएगा। यह ध्वज अंततः पायथन लॉगर को नियंत्रित करता है; लॉगिंग कॉन्फ़िगर करना देखेंलॉगिंग को सीधे कॉन्फ़िगर करने के तरीके के बारे में जानकारी के लिए ।

स्रोत: SQLAlchemy इंजन कॉन्फ़िगरेशन

यदि फ्लास्क के साथ उपयोग किया जाता है, तो आप बस सेट कर सकते हैं

app.config["SQLALCHEMY_ECHO"] = True

समान व्यवहार पाने के लिए।


6
यह उत्तर उच्चतर होने के योग्य है .. और इसके लिए उपयोगकर्ताओं flask-sqlalchemyको स्वीकृत उत्तर होना चाहिए।
jso

25

हम इस उद्देश्य के लिए संकलन विधि का उपयोग कर सकते हैं । से डॉक्स :

from sqlalchemy.sql import text
from sqlalchemy.dialects import postgresql

stmt = text("SELECT * FROM users WHERE users.name BETWEEN :x AND :y")
stmt = stmt.bindparams(x="m", y="z")

print(stmt.compile(dialect=postgresql.dialect(),compile_kwargs={"literal_binds": True}))

परिणाम:

SELECT * FROM users WHERE users.name BETWEEN 'm' AND 'z'

डॉक्स से चेतावनी:

अविश्वसनीय तकनीक से प्राप्त स्ट्रिंग सामग्री के साथ कभी भी इस तकनीक का उपयोग न करें, जैसे कि वेब फ़ॉर्म या अन्य उपयोगकर्ता-इनपुट अनुप्रयोगों से। सीधे SQL स्ट्रिंग मानों में पायथन मानों को एकरूप करने के लिए SQLAlchemy की सुविधाएं अविश्वसनीय इनपुट के खिलाफ सुरक्षित नहीं हैं और पारित किए जा रहे डेटा के प्रकार को मान्य नहीं करती हैं। प्रोग्राम से संबंधित गैर-डीडीएल एसक्यूएल स्टेटमेंट को एक रिलेशनल डेटाबेस के विरुद्ध प्रयोग करते समय हमेशा बाउंड पैरामीटर का उपयोग करें।


13

तो @ bukzor के कोड पर @ zzzeek की टिप्पणियों का निर्माण मैं आसानी से एक "सुंदर प्रिंट" प्राप्त करने के लिए इस के साथ आया था:

def prettyprintable(statement, dialect=None, reindent=True):
    """Generate an SQL expression string with bound parameters rendered inline
    for the given SQLAlchemy statement. The function can also receive a
    `sqlalchemy.orm.Query` object instead of statement.
    can 

    WARNING: Should only be used for debugging. Inlining parameters is not
             safe when handling user created data.
    """
    import sqlparse
    import sqlalchemy.orm
    if isinstance(statement, sqlalchemy.orm.Query):
        if dialect is None:
            dialect = statement.session.get_bind().dialect
        statement = statement.statement
    compiled = statement.compile(dialect=dialect,
                                 compile_kwargs={'literal_binds': True})
    return sqlparse.format(str(compiled), reindent=reindent)

मेरे पास व्यक्तिगत रूप से एक कठिन समय पढ़ने वाला कोड है जो इंडेंट नहीं है इसलिए मैंने sqlparseएसक्यूएल को रीइंडेंट करने के लिए उपयोग किया है। इसके साथ स्थापित किया जा सकता है pip install sqlparse


@ ब्यूज़ोर सभी मूल्यों को छोड़कर काम करता datatime.now()है जब अजगर 3 + sqlalchemy 1.0 का उपयोग करता है। आपको उस कार्य के लिए एक कस्टम टाइप दाता बनाने पर @ zzzeek की सलाह का पालन करना होगा।
jmagnusson

वह थोड़ा बहुत विशिष्ट है। अजगर और sqlalchemy के किसी भी संयोजन में डेटाटाइम काम नहीं करता है। इसके अलावा, py27 में, गैर-एसिसी यूनिकोड एक विस्फोट का कारण बनता है।
bukzor

जहाँ तक मैं देख सकता था, TypeDecorator मार्ग के लिए मुझे अपनी तालिका परिभाषाएँ बदलने की आवश्यकता होती है, जो कि आपके प्रश्नों को देखने के लिए एक उचित आवश्यकता नहीं है। मैंने अपने उत्तर को आपके और zzzeek के करीब होने के लिए संपादित किया, लेकिन मैंने एक कस्टम बोली का मार्ग लिया, जो तालिका परिभाषाओं के लिए ठीक से orthogonal है।
bukzor

11

यह कोड @bukzor के शानदार मौजूदा जवाब पर आधारित है । मैंने अभी datetime.datetimeओरेकल में टाइप के लिए कस्टम रेंडर जोड़ा हैTO_DATE()

अपने डेटाबेस के अनुरूप कोड अपडेट करने के लिए स्वतंत्र महसूस करें:

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)

22
मैं यह नहीं देखता कि एसए लोक का मानना ​​है कि इस तरह के एक सरल ऑपरेशन के लिए यह इतना कठिन है
बूचरोर

धन्यवाद! रेंडर_लिटरल_वेल्यू ने मेरे लिए अच्छा काम किया। मेरा एकमात्र बदलाव था: फ्लोट के return "%s" % valueबजाय return repr(value), इंट, लॉन्ग सेक्शन क्योंकि पायथन 22Lसिर्फ के बजाय 22
लॉन्ग आउटपुट

यह नुस्खा (साथ ही साथ मूल) यूनिकोडडबलएयर को बढ़ाता है यदि कोई बिंदपाराम स्ट्रिंग मान एससीआई में प्रतिनिधित्व करने योग्य नहीं है। मैंने एक जिस्ट पोस्ट किया जो इसे ठीक करता है।
gsakkis

1
"STR_TO_DATE('%s','%%Y-%%m-%%d %%H:%%M:%%S')" % value.strftime("%Y-%m-%d %H:%M:%S")mysql में
Zitrax

1
@bukzor - मुझे याद नहीं आ रहा है कि अगर ऊपर "उचित" है तो आप वास्तव में यह नहीं कह सकते हैं कि मैं "विश्वास" करता हूं - एफडब्ल्यूआईडब्ल्यू, यह नहीं है! :) कृपया मेरा उत्तर देखें।
zzzeek

8

मैं यह बताना चाहूंगा कि ऊपर दिए गए समाधान गैर-तुच्छ प्रश्नों के साथ "सिर्फ काम" नहीं करते हैं। एक मुद्दा जो मुझे आया था, वह और अधिक जटिल प्रकार थे, जैसे कि pgsql ARRAYs के कारण समस्याएँ। मुझे एक समाधान मिला जो मेरे लिए, सिर्फ pgsql ARRAYs के साथ भी काम किया:

से उधार लिया गया: https://gist.github.com/gsakkis/4572159

लिंक किए गए कोड SQLAlchemy के पुराने संस्करण पर आधारित प्रतीत होते हैं। आपको यह कहते हुए एक त्रुटि मिलेगी कि विशेषता _mapper_zero_or_none मौजूद नहीं है। यहां एक अद्यतन संस्करण है जो एक नए संस्करण के साथ काम करेगा, आप बस _mapper_zero_or_none को बाइंड के साथ बदलें। इसके अतिरिक्त, इसमें pgsql सरणियों का समर्थन है:

# adapted from:
# https://gist.github.com/gsakkis/4572159
from datetime import date, timedelta
from datetime import datetime

from sqlalchemy.orm import Query


try:
    basestring
except NameError:
    basestring = str


def render_query(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.
    Based on http://stackoverflow.com/questions/5631078/sqlalchemy-print-the-actual-query
    """
    if isinstance(statement, Query):
        if dialect is None:
            dialect = statement.session.bind.dialect
        statement = statement.statement
    elif dialect is None:
        dialect = statement.bind.dialect

    class LiteralCompiler(dialect.statement_compiler):

        def visit_bindparam(self, bindparam, within_columns_clause=False,
                            literal_binds=False, **kwargs):
            return self.render_literal_value(bindparam.value, bindparam.type)

        def render_array_value(self, val, item_type):
            if isinstance(val, list):
                return "{%s}" % ",".join([self.render_array_value(x, item_type) for x in val])
            return self.render_literal_value(val, item_type)

        def render_literal_value(self, value, type_):
            if isinstance(value, long):
                return str(value)
            elif isinstance(value, (basestring, date, datetime, timedelta)):
                return "'%s'" % str(value).replace("'", "''")
            elif isinstance(value, list):
                return "'{%s}'" % (",".join([self.render_array_value(x, type_.item_type) for x in value]))
            return super(LiteralCompiler, self).render_literal_value(value, type_)

    return LiteralCompiler(dialect, statement).process(statement)

नेस्टेड सरणियों के दो स्तरों का परीक्षण किया गया।


कृपया इसका उपयोग करने का एक उदाहरण दिखाएं धन्यवाद
slashdottir

from file import render_query; print(render_query(query))
अल्फांसो पेरेज़

इस पूरे पृष्ठ का एकमात्र उदाहरण यह है कि मेरे लिए काम किया! धन्यवाद !
फौजेरेजो
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.