SQLAlchemy : 실제 쿼리 인쇄
바인드 매개 변수가 아닌 값을 포함하여 내 응용 프로그램에 유효한 SQL을 실제로 인쇄하고 싶지만 SQLAlchemy 에서이 작업을 수행하는 방법은 확실하지 않습니다 (설계에 따르면 상당히 확실합니다).
누구 든지이 문제를 일반적인 방식으로 해결 했습니까?
대부분의 경우 SQLAlchemy 문 또는 쿼리의 "문자열"은 다음과 같이 간단합니다.
print str(statement)
이것은 ORM Query
뿐만 아니라 select()
다른 진술 에도 적용됩니다 .
참고 : 다음 자세한 답변은 sqlalchemy 설명서 에서 유지 관리됩니다 .
명령문을 특정 방언 또는 엔진에 컴파일 된 것으로 가져 오려면 명령문 자체가 아직 바인딩되지 않은 경우이를 compile () 에 전달할 수 있습니다 .
print statement.compile(someengine)
또는 엔진이없는 경우 :
from sqlalchemy.dialects import postgresql
print statement.compile(dialect=postgresql.dialect())
ORM Query
객체가 주어지면 compile()
메소드 를 얻으려면 먼저 .statement 접근 자만 액세스하면 됩니다 .
statement = query.statement
print statement.compile(someengine)
바운드 매개 변수가 최종 문자열에 "인라인"되어야한다는 원래 규정과 관련하여 여기서 해결해야 할 과제는 SQLAlchemy가 일반적으로이 작업을 수행하지 않는 것입니다. 아마도 현대 웹 응용 프로그램에서 가장 널리 이용되는 보안 허점 일 것입니다. SQLAlchemy는 DDL을 방출하는 것과 같은 특정 상황에서 이러한 엄격화를 수행하는 능력이 제한되어 있습니다. 이 기능에 액세스하기 위해 'literal_binds'플래그를 사용할 수 있습니다 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})
위의 접근법에는 int 및 string과 같은 기본 유형에만 지원된다는 경고가 있으며 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와 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
디버깅 할 때만 원하는 것이 의미가 있다고 생각하면 SQLAlchemy를 사용 echo=True
하여 모든 SQL 쿼리를 기록 할 수 있습니다. 예를 들면 다음과 같습니다.
engine = create_engine(
"mysql://scott:tiger@hostname/dbname",
encoding="latin1",
echo=True,
)
단일 요청에 대해서만 수정할 수도 있습니다.
echo=False
–이면True
엔진은 모든 명령문과repr()
매개 변수 목록을 엔진 로거에 기본값으로 기록합니다sys.stdout
. 의echo
속성은Engine
언제든지 로깅을 켜거나 끄도록 수정할 수 있습니다. string으로 설정하면"debug"
결과 행도 표준 출력으로 인쇄됩니다. 이 플래그는 궁극적으로 Python 로거를 제어합니다. 로깅을 직접 구성하는 방법에 대한 정보는 로깅 구성을 참조하십시오 .소스 : SQLAlchemy 엔진 구성
플라스크와 함께 사용하면 간단하게 설정할 수 있습니다
app.config["SQLALCHEMY_ECHO"] = True
같은 행동을 취합니다.
이 목적으로 컴파일 방법을 사용할 수 있습니다 . 로부터 문서 :
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'
문서에서 경고 :
웹 양식이나 다른 사용자 입력 응용 프로그램과 같은 신뢰할 수없는 입력에서 수신 한 문자열 내용에는이 기술을 사용하지 마십시오. Python 값을 직접 SQL 문자열 값으로 강제 변환하는 SQLAlchemy의 기능은 신뢰할 수없는 입력에 대해 안전하지 않으며 전달되는 데이터 유형의 유효성을 검증하지 않습니다. 관계형 데이터베이스에 대해 비 DDL SQL 문을 프로그래밍 방식으로 호출 할 때는 항상 바인딩 된 매개 변수를 사용하십시오.
그래서 @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
을 겪어 SQL을 다시 들여 쓰는 데 사용 했습니다. 로 설치할 수 있습니다 pip install sqlparse
.
This code is based on brilliant existing answer from @bukzor. I just added custom render for datetime.datetime
type into Oracle's TO_DATE()
.
Feel free to update code to suit your database:
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)
I would like to point out that the solutions given above do not "just work" with non-trivial queries. One issue I came across were more complicated types, such as pgsql ARRAYs causing issues. I did find a solution that for me, did just work even with pgsql ARRAYs:
borrowed from: https://gist.github.com/gsakkis/4572159
The linked code seems to be based on an older version of SQLAlchemy. You'll get an error saying that the attribute _mapper_zero_or_none doesn't exist. Here's an updated version that will work with a newer version, you simply replace _mapper_zero_or_none with bind. Additionally, this has support for pgsql arrays:
# 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)
Tested to two levels of nested arrays.
참고URL : https://stackoverflow.com/questions/5631078/sqlalchemy-print-the-actual-query
'Programing' 카테고리의 다른 글
FromBody와 FromUri를 지정해야하는 이유는 무엇입니까? (0) | 2020.06.25 |
---|---|
IEnumerable (0) | 2020.06.25 |
같은 클래스에서 두 개의 메소드를 동기화하면 동시에 실행할 수 있습니까? (0) | 2020.06.25 |
django-rest-framework의 관리자 스타일 탐색 가능한 인터페이스를 비활성화하는 방법은 무엇입니까? (0) | 2020.06.25 |
Emacs에서 패키지 업데이트 (0) | 2020.06.25 |