[python] SQLAlchemy 식에서 컴파일 된 원시 SQL 쿼리를 가져 오는 방법은 무엇입니까?

SQLAlchemy 쿼리 개체가 있고 모든 매개 변수가 바인딩 된 컴파일 된 SQL 문의 텍스트를 얻고 싶습니다 (예 : %s 문 컴파일러 또는 MySQLdb 언어 엔진에 의해 바인딩되기를 기다리는 변수 없거나 다른 변수 등).

str()쿼리를 호출 하면 다음과 같은 결과가 나타납니다.

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

query._params에서 찾아 봤지만 빈 dict입니다. sqlalchemy.ext.compiler.compiles데코레이터 예제를 사용하여 내 컴파일러를 작성 했지만 거기에있는 문에도 여전히%s 데이터 곳에 .

쿼리를 생성하기 위해 내 매개 변수가 언제 섞여 있는지 알 수 없습니다. 쿼리 개체를 검사 할 때 항상 빈 사전입니다 (쿼리가 제대로 실행되고 에코 로깅을 켜면 엔진이 출력하지만).

SQLAlchemy가 기본 쿼리를 알기를 원하지 않는다는 메시지를 받기 시작했습니다. 식 API 인터페이스의 일반적인 특성이 모든 다른 DB-API를 깨뜨리기 때문입니다. 쿼리가 무엇인지 알아 내기 전에 실행 되더라도 상관 없습니다. 알고 싶어요!



답변

블로그는 업데이트 된 답변을 제공합니다.

블로그 게시물에서 인용하면 이것이 제안되고 저에게 효과적이었습니다.

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

여기서 q는 다음과 같이 정의됩니다.

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

또는 모든 종류의 session.query ().

답변을 주신 Nicolas Cadou에게 감사드립니다! 이곳을 찾는 다른 사람들에게 도움이되기를 바랍니다.


답변

문서는 사용 literal_binds하는 쿼리 인쇄 q매개 변수를 포함하여 :

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

위의 접근 방식은 int 및 문자열과 같은 기본 유형에 대해서만 지원되며 사전 설정된 값이없는 bindparam ()을 직접 사용하는 경우에도이를 문자열화할 수 없다는 경고가 있습니다.

문서는 또한 다음 경고를 발행합니다.

웹 양식 또는 기타 사용자 입력 응용 프로그램에서와 같이 신뢰할 수없는 입력에서받은 문자열 콘텐츠에는이 기술을 사용하지 마십시오. Python 값을 직접 SQL 문자열 값으로 강제 변환하는 SQLAlchemy의 기능은 신뢰할 수없는 입력에 대해 안전하지 않으며 전달되는 데이터 유형의 유효성을 검사하지 않습니다. 관계형 데이터베이스에 대해 비 DDL SQL 문을 프로그래밍 방식으로 호출 할 때는 항상 바인딩 된 매개 변수를 사용하십시오.


답변

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


답변

MySQLdb 백엔드의 경우 albertov의 멋진 답변 (정말 감사합니다!)을 약간 수정했습니다. 나는 확실히 한 경우에는 확인을 병합 할 수있어 comp.positional이었다 True하지만 약간이 질문의 범위를 벗어입니다.

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)


답변

문제는 sqlalchemy가 데이터를 쿼리와 혼합하지 않는다는 것입니다. 쿼리와 데이터는 기본 데이터베이스 드라이버에 별도로 전달됩니다. 데이터 보간은 데이터베이스에서 발생합니다.

Sqlalchemy는 str(myquery)데이터베이스 에서 본 것처럼 쿼리를 전달하고 값은 별도의 튜플에 들어갑니다.

쿼리로 데이터를 보간하는 몇 가지 방법을 사용할 수 있습니다 (아래에 albertov가 제안한대로).하지만 sqlalchemy가 실행하는 것과는 다릅니다.


답변

먼저 주로 디버깅 목적으로이 작업을 수행한다고 가정합니다. SQLAlchemy 유창한 API 외부에서 문을 수정하는 것은 권장하지 않습니다.

불행히도 쿼리 매개 변수가 포함 된 컴파일 된 문을 표시하는 간단한 방법이없는 것 같습니다. SQLAlchemy는 실제로 매개 변수를 명령문에 넣지 않고 데이터베이스 엔진에 사전으로 전달됩니다 . 이를 통해 데이터베이스 별 라이브러리는 SQL 주입을 피하기 위해 특수 문자 이스케이프와 같은 작업을 처리 할 수 ​​있습니다.

그러나 합리적으로 쉽게 2 단계 프로세스로이를 수행 할 수 있습니다. 명령문을 얻으려면 이미 표시된대로 수행하고 쿼리를 인쇄하면됩니다.

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

query.statement를 사용하면 한 단계 더 가까워져 매개 변수 이름을 볼 수 있습니다. :id_1아래와 %s위의 참고 사항 -이 매우 간단한 예제에서는 실제로 문제가되지 않지만 더 복잡한 문장에서는 핵심이 될 수 있습니다.

>>> print(query.statement)
>>> print(query.statement.compile()) # seems to be equivalent, you can also
                                     # pass in a dialect if you want
SELECT field_1, field_2 FROM table WHERE id=:id_1;

그런 다음 params컴파일 된 문의 속성을 가져 와서 매개 변수의 실제 값을 가져올 수 있습니다 .

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

이것은 적어도 MySQL 백엔드에서 작동했습니다. 를 사용할 필요없이 PostgreSQL에도 충분히 일반적 일 것으로 예상합니다 psycopg2.


답변

psycopg2를 사용하는 postgresql 백엔드의 do_execute경우 이벤트를 수신 한 다음 커서, 명령문 및 강제 매개 변수를 함께 사용 Cursor.mogrify()하여 매개 변수를 인라인 할 수 있습니다. True를 반환하여 쿼리가 실제로 실행되지 않도록 할 수 있습니다.

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

샘플 사용법 :

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