source

SQL Chemy: 실제 쿼리 인쇄

itover 2022. 12. 21. 22:32
반응형

SQL Chemy: 실제 쿼리 인쇄

파라미터를 바인드하는 것이 아니라 값을 포함한 어플리케이션에 유효한 SQL을 출력하고 싶지만 SQL Chemy에서 이를 실행하는 방법은 명확하지 않습니다(설계상 확실합니다).

이 문제를 일반적인 방법으로 해결한 사람이 있습니까?

대부분의 경우 SQL Chemy 스테이트먼트 또는 쿼리의 "문자열화"는 다음과 같이 간단합니다.

print(str(statement))

은, ORM 의 양쪽 됩니다.Query 것과 select()★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★

주의: 다음 답변은 sqlalchemy 문서에 기재되어 있습니다.

특정 방언 또는 엔진에 컴파일된 스테이트먼트를 가져오려면 스테이트먼트 자체가 아직1개에 바인드 되어 있지 않은 경우, 이것을 compile()에 전달합니다.

print(statement.compile(someengine))

또는 엔진이 없는 경우:

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

ORM이 되어 있는 Query 「」를 , 「」를 참조해 주세요.compile()method 먼저 .statement 접근자만 접근하면 됩니다.

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

바인딩된 파라미터가 마지막 문자열에 "삽입"되어야 한다는 원래의 조건에 관해, 여기서의 과제는 SQLChemy가 일반적으로 이 작업을 수행하지 않는다는 것입니다. Python DBAPI 。운운파파sssssssssssssssssssssssssssssssssssssssssssssss는 DDL을 내보내는 에서 이 할 수 있는 기능이 이 Chemy DDL에 할 수 .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}))

및 string 이 있습니다.또, 「Int」스트링의 는, 「Int」스트링이 서포트되고 있습니다.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)

한 것은 디버깅 를 SQL Chemy로 할 수 .echo=True리 sql SQL 。예를 들어 다음과 같습니다.

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

이것은, 1개의 요구에 대해서만 변경할 수도 있습니다.

echo=False – – ★★True은 「 」, 「 」, 「 」repr()리스트는 로는 「」로 설정되어 있습니다.기본값은sys.stdout . 。echo「」의 어트리뷰트Engine는 언제든지 수정하여 로깅을 켜거나 끌 수 있습니다.로 되어 있는 "debug"결과 행도 표준 출력으로 출력됩니다.이 플래그는 궁극적으로 Python 로거를 제어합니다. 로깅을 직접 구성하는 방법에 대한 자세한 내용은 로깅 구성을 참조하십시오.

출처: SQL Chemy 엔진 구성

플라스크와 함께 사용하면 간단히 설정할 수 있습니다.

app.config["SQLALCHEMY_ECHO"] = True

같은 행동을 취하기 위해서요

이것은 python 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()

다음 출력을 제공합니다(python 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

이를 위해 컴파일 방식을 사용할 수 있습니다.문서에서:

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 String 값으로 Python 값을 강제하는 SQL Chemy의 기능은 신뢰할 수 없는 입력에 대해 안전하지 않으며 전달되는 데이터 유형을 검증하지 않습니다.관계형 데이터베이스에 대해 비 DDL SQL 문을 프로그래밍 방식으로 호출할 때는 항상 바인딩된 매개 변수를 사용하십시오.

@bukzzeek의 코드에 대한 @zzeek의 코멘트에 근거해, 간단하게 「인쇄할 수 있는」쿼리를 취득하기 위해서, 다음과 같이 생각해 냈습니다.

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)

는 를 읽는 것이 ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★sqlparseSQL sql sql sql sql sql sql sql sql sql 。 it음 it it it it it it it it it it it it it it it it it 。pip install sqlparse.

이 코드는 @bukzor의 기존 답변을 기반으로 합니다.커스텀 렌더를 추가했습니다.datetime.datetimeTO_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)

위에 제시된 솔루션은 단순한 쿼리에서만 작동하는 것이 아니라는 점을 지적하고 싶습니다.발생한 문제 중 하나는 pgsql 어레이로 인해 문제가 발생하는 등 더 복잡한 유형입니다.pgsql 어레이에서도 동작하는 솔루션을 찾았습니다.

차용처: https://gist.github.com/gsakkis/4572159

링크된 코드는 이전 버전의 SQL Chemy에 기반하고 있는 것 같습니다._mapper_zero_or_none 속성이 존재하지 않는다는 오류가 나타납니다.새로운 버전으로 동작하는 업데이트 버전은 다음과 같습니다._mapper_zero_or_none을 bind로 대체하기만 하면 됩니다.또한 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)

2레벨의 네스트 어레이로 테스트.

ORM의 Query와 pygments를 사용한 단순한 색상 예시입니다.

import sqlparse
from pygments import highlight
from pygments.formatters.terminal import TerminalFormatter
from pygments.lexers import SqlLexer
from sqlalchemy import create_engine
from sqlalchemy.orm import Query

engine = create_engine("sqlite+pysqlite:///db.sqlite", echo=True, future=True)

def format_sql(query: Query):
    compiled = query.statement.compile(
         engine, compile_kwargs={"literal_binds": True})
    parsed = sqlparse.format(str(compiled), reindent=True, keyword_case='upper')
    print(highlight(parsed, SqlLexer(), TerminalFormatter()))

또는 sqlparse가 없는 버전(sqlparse가 없는 경우 출력에 새로운 행이 적음)

def format_sql(query: Query):
    compiled = query.statement.compile(
        engine, compile_kwargs={"literal_binds": True})
    print(highlight(str(compiled), SqlLexer(), TerminalFormatter()))

Python 로깅을 사용하여 SQL 쿼리를 로깅하려면echo=True플래그:

import logging

logging.basicConfig()
logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)

를 참조해 주세요.

언급URL : https://stackoverflow.com/questions/5631078/sqlalchemy-print-the-actual-query

반응형