Я дуже хотів би мати можливість роздрукувати дійсні SQL для мого додатка, включаючи значення, а не прив’язувати параметри, але це не очевидно, як це зробити в SQLAlchemy (за дизайном я досить впевнений).
Хтось вирішив цю проблему загальним способом?
Я дуже хотів би мати можливість роздрукувати дійсні SQL для мого додатка, включаючи значення, а не прив’язувати параметри, але це не очевидно, як це зробити в SQLAlchemy (за дизайном я досить впевнений).
Хтось вирішив цю проблему загальним способом?
Відповіді:
У переважній більшості випадків "строфікація" оператора або запиту SQLAlchemy така ж проста, як:
print str(statement)
Це стосується як ORM, Queryтак і будь-якого select()чи іншого твердження.
Примітка . Наступна детальна відповідь зберігається в документації sqlalchemy .
Щоб отримати оператор як скомпільований на певному діалекті чи двигуні, якщо висловлювання вже не пов'язане з одним, ви можете передати це у компілювати () :
print statement.compile(someengine)
або без двигуна:
from sqlalchemy.dialects import postgresql
print statement.compile(dialect=postgresql.dialect())
Коли нам дають Queryоб'єкт ORM , для того, щоб отримати compile()метод, нам потрібен лише спочатку доступ до доступу .statement :
statement = query.statement
print statement.compile(someengine)
що стосується початкового положення про те, що зв'язані параметри мають бути "вкладені" в остаточний рядок, то проблема тут полягає в тому, що SQLAlchemy зазвичай не ставить перед собою завдання, оскільки це обробляється належним чином Python DBAPI, не кажучи вже про обхід пов'язаних параметрів. мабуть, найбільш широко використовувані дірки в безпеці в сучасних веб-додатках. 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})
вищевказаний підхід містить застереження, що він підтримується лише для базових типів, таких як ints та рядки, і, крім того, якщо значення 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)
query.prettyprint(). Це дуже полегшує біль на налагодження з великими запитами.
@compilesтощо) для будь-якої кількості сторонніх пакетів для впровадження симпатичних систем друку.
Це працює в 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
Зважаючи на те, що те, що ви хочете, має сенс лише при налагодженні, ви можете запустити SQLAlchemy з echo=True, щоб увімкнути всі запити SQL. Наприклад:
engine = create_engine(
"mysql://scott:tiger@hostname/dbname",
encoding="latin1",
echo=True,
)
Це також можна змінити лише для одного запиту:
echo=False- якщоTrue, Engine буде реєструвати всі оператори, а такожrepr()їхні списки параметрів до реєстратора двигунів, для якого за замовчуваннямsys.stdout.echoАтрибутEngineможе бути змінений в будь-який час , щоб включити ведення журналу і вимикається. Якщо встановлено рядок"debug", рядки результатів також будуть надруковані на стандартний вихід. Цей прапор врешті-решт керує реєстратором Python; Див. розділ Налаштування журналу для отримання інформації про те, як налаштувати журнал безпосередньо.Джерело: Конфігурація двигуна SQLAlchemy
Якщо використовується з колбою, ви можете просто встановити
app.config["SQLALCHEMY_ECHO"] = True
щоб отримати таку саму поведінку.
flask-sqlalchemyце має бути прийнятою відповіддю.
Для цього ми можемо використовувати метод компіляції . З документів :
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'
Попередження від документів:
Ніколи не використовуйте цю техніку з рядковим вмістом, отриманим з ненадійного введення, наприклад, з веб-форм або інших програм для введення користувачем. Можливості SQLAlchemy для примусового значення Python у прямі значення рядка SQL не захищені від ненадійного введення та не підтверджують тип переданих даних. Завжди використовуйте прив'язані параметри, коли програмно викликати оператори SQL, що не є DDL, проти реляційної бази даних.
Отже, спираючись на коментарі @ zzzeek щодо коду @ bukzor, я придумав це, щоб легко отримати запит на "досить друкуваний":
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.
datatime.now()одного, коли використовується python 3 + sqlalchemy 1.0. Вам доведеться дотримуватися порад @ zzzeek щодо створення користувальницького TypeDecorator для того, щоб він також працював.
Цей код заснований на блискучій відповіді від @bukzor. Я щойно додав користувальницьку візуалізацію для datetime.datetimeтипу в Oracle 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)
return "%s" % valueзамість return repr(value)float, int, довгий розділ, тому що Python виводив longs, 22Lа не просто22
"STR_TO_DATE('%s','%%Y-%%m-%%d %%H:%%M:%%S')" % value.strftime("%Y-%m-%d %H:%M:%S")у mysql
Я хотів би зазначити, що рішення, наведені вище, не «працюють» лише з нетривіальними запитами. Одне питання, на яке я натрапив, були складнішими типами, наприклад, PGSQL ARRAY, що викликає проблеми. Я знайшов рішення, яке для мене просто працювало навіть з pgsql ARRAY:
запозичено у: 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)
Тестується на двох рівнях вкладених масивів.
from file import render_query; print(render_query(query))
sqlalchemy.engineжурнал SQLAlchemy . Він реєструє запити та параметри прив'язки, вам потрібно буде лише замінити заповнювачі заповнення на значення зі зручно побудованої рядки запитів SQL.