ฉันต้องการพิมพ์ 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 accessor ก่อน:
statement = query.statement
print statement.compile(someengine)
ด้วยความเคารพต่อข้อตกลงดั้งเดิมที่พารามิเตอร์ที่ผูกไว้จะต้อง "inlined" ในสตริงสุดท้ายความท้าทายที่นี่คือ SQLAlchemy โดยปกติจะไม่ได้มอบหมายให้กับสิ่งนี้เนื่องจากการจัดการอย่างเหมาะสมโดย Python DBAPI ไม่ต้องพูดถึงการข้ามพารามิเตอร์ที่ถูกผูกไว้ อาจเป็นช่องโหว่ด้านความปลอดภัยที่ใช้กันอย่างแพร่หลายมากที่สุดในเว็บแอปพลิเคชันที่ทันสมัย SQLAlchemy มีความสามารถที่ จำกัด ในการทำ stringification นี้ในบางสถานการณ์เช่นการเปล่ง 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
เครื่องยนต์จะเข้าสู่ระบบงบทั้งหมดเช่นเดียวกับรายการพารามิเตอร์ของพวกเขาไปตัดไม้เครื่องมือที่เริ่มต้นที่repr()
แอตทริบิวต์สามารถปรับเปลี่ยนได้ตลอดเวลาเพื่อเลี้ยวเข้าสู่ระบบในและนอก หากตั้งค่าเป็นสตริงแถวผลลัพธ์จะถูกพิมพ์ไปยังเอาต์พุตมาตรฐานเช่นกัน การตั้งค่าสถานะนี้ในที่สุดควบคุมตัวบันทึก Python ดูที่การกำหนดค่าการบันทึกสำหรับข้อมูลเกี่ยวกับวิธีกำหนดค่าการบันทึกโดยตรงsys.stdout
echo
Engine
"debug"
ที่มา: การกำหนดค่าเครื่องมือ SQLAlchemy
หากใช้กับ Flask คุณสามารถตั้งค่าได้ง่ายๆ
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 โดยตรงจะไม่ปลอดภัยต่ออินพุตที่ไม่น่าเชื่อถือและไม่ตรวจสอบประเภทของข้อมูลที่จะส่งผ่าน ใช้พารามิเตอร์ที่ถูกผูกไว้เสมอเมื่อเรียกใช้คำสั่งที่ไม่ใช่ DDL SQL โดยทางโปรแกรมกับฐานข้อมูลเชิงสัมพันธ์
ดังนั้นการสร้างความคิดเห็นของ @ 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
ประเภทลงใน OracleTO_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)
เป็นส่วนโฟลต, int, long เพราะ 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 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)
ผ่านการทดสอบถึงสองระดับของอาร์เรย์ที่ซ้อนกัน
from file import render_query; print(render_query(query))
sqlalchemy.engine
บันทึกของ SQLAlchemy มันบันทึกแบบสอบถามและพารามิเตอร์ผูกคุณจะต้องแทนที่ตัวยึดตำแหน่งผูกกับค่าในสตริงแบบสอบถาม SQL สร้างขึ้นอย่างง่ายดาย