如何显式引用字符串值(Python DB API/Psycopg2)
Posted
技术标签:
【中文标题】如何显式引用字符串值(Python DB API/Psycopg2)【英文标题】:How to quote a string value explicitly (Python DB API/Psycopg2) 【发布时间】:2010-09-23 12:32:07 【问题描述】:出于某些原因,我想对字符串值进行显式引用(成为构造 SQL 查询的一部分),而不是等待 cursor.execute
方法对其第二个参数的内容执行隐式引用。
“隐式引用”是指:
value = "Unsafe string"
query = "SELECT * FROM some_table WHERE some_char_field = %s;"
cursor.execute( query, (value,) ) # value will be correctly quoted
我更喜欢这样的:
value = "Unsafe string"
query = "SELECT * FROM some_table WHERE some_char_field = %s;" % \
READY_TO_USE_QUOTING_FUNCTION(value)
cursor.execute( query ) # value will be correctly quoted, too
Python DB API 规范是否期望如此低级别的READY_TO_USE_QUOTING_FUNCTION
(我在PEP 249 文档中找不到这样的功能)。如果没有,也许 Psycopg2 提供了这样的功能?如果没有,也许 Django 提供了这样的功能?我不想自己写这样的函数......
【问题讨论】:
我建议你看看 SQLAlchemy 的 SQL builder API,即使你对 ORM 组件不感兴趣;这将让您在绑定值的同时仍保持灵活性。 类似db_cur.execute('''UPDATE test_table SET field_1="%s" WHERE field_2="%s"''' % (data, condition))
的东西注意%s
周围的三个单引号和双引号
'''' 允许避免将整个 sql 保留在一行中
【参考方案1】:
好的,所以我很好奇,就去看了psycopg2的来源。原来我不必比示例文件夹走得更远:)
是的,这是特定于 psycopg2 的。基本上,如果你只想引用一个字符串,你会这样做:
from psycopg2.extensions import adapt
print adapt("Hello World'; DROP DATABASE World;")
但您可能想要做的是编写和注册自己的适配器;
在 psycopg2 的示例文件夹中,您可以找到文件 'myfirstrecipe.py',其中有一个如何以特殊方式强制转换和引用特定类型的示例。
如果你有你想做的事情的对象,你可以只创建一个符合“IPsycopgSQLQuote”协议的适配器(请参阅 myfirstrecipe.py-example 的 pydocs...实际上这是我能找到的唯一参考到那个名字)引用你的对象,然后像这样注册它:
from psycopg2.extensions import register_adapter
register_adapter(mytype, myadapter)
另外,其他例子也很有趣;尤其是'dialtone.py' 和 'simple.py'。
【讨论】:
看来您的解决方案正是我所寻求的。如果我使用默认适配器来引用字符串值(将其用作字符串常量),它会阻止我进行 SQL 注入吗?看起来是这样,但也许我错过了什么......【参考方案2】:我猜你正在寻找mogrify 函数。
例子:
>>> cur.mogrify("INSERT INTO test (num, data) VALUES (%s, %s)", (42, 'bar'))
"INSERT INTO test (num, data) VALUES (42, E'bar')"
【讨论】:
那是我当时真正想要的:)【参考方案3】:您应该尽量避免自己引用。正如人们所指出的那样,它不仅是特定于数据库的,而且引用中的缺陷也是 SQL 注入错误的根源。
如果您不想单独传递查询和值,请传递参数列表:
def make_my_query():
# ...
return sql, (value1, value2)
def do_it():
query = make_my_query()
cursor.execute(*query)
(我可能有 cursor.execute 的语法错误)这里的重点是,仅仅因为 cursor.execute 需要许多参数,这并不意味着您必须单独处理它们。您可以将它们作为一个列表来处理。
【讨论】:
您也可以将它们作为 dict 传递:cursor.execute("SOME SQL %(aparam)s, %(another)s, %(aparam)s", adict) 不过,这并不适用于所有情况;在进行多行插入和类似的构造时,这是不可能的。 无论您需要进行何种数据库调用结构,您都可以构建一个抽象来保存数据,直到需要使用它为止。想要抽象地构建查询并不是将自己的引用组合在一起的借口。 当然正确,但是在某些时候,无论您将数据隐藏在多少抽象层下,您都需要引用您的数据,并且 execute/executemany 提供的机制不能支持所有值的使用SQL。 好的,所以可以这样做。 sql = 'insert into x values'+','.join(('(%s, %s)',) * len(values)) 等等,但我觉得这不是一个很好的解决方案。 【参考方案4】:这将取决于数据库(iirc,mysql 允许 \
作为转义字符,而像 oracle 之类的东西希望引号加倍:'my '' quoted string'
)。
如果我错了,有人纠正我,但双引号方法是标准方法。
看看其他数据库抽象库做了什么(sqlalchemy、cx_Oracle、sqlite 等)可能值得一看。
我不得不问 - 为什么要内联值而不是绑定它们?
【讨论】:
我有很多相当重要的查询,其中包含一些重复模式(例如,多个 WHERE 条件集)。我已经定义了一些封装必要数据和 SQL 生成代码的类。从它们单独返回 SQL 代码和参数序列会很不舒服。 FWIW 在处理复杂查询生成时,我通常会传回参数化查询字符串和单独的参数列表。没那么糟糕。【参考方案5】:这将取决于数据库。例如,对于 MySQLdb,connection
类有一个 literal
方法,该方法会将值转换为正确的转义表示以传递给 MySQL(这就是 cursor.execute
使用的)。
我想 Postgres 也有类似的东西,但我认为作为 DB API 2.0 规范的一部分,我不认为有一个函数可以转义值。
【讨论】:
【参考方案6】:我认为你没有给出任何充分的理由来避免这样做是正确的方式。请按设计使用 APi,不要过分努力使您的代码对下一个人来说可读性降低,并且变得更脆弱。
【讨论】:
"...WHERE some_field IN (%s)" % ", ".join(...嗯,什么?...) 然后我会做类似 "... WHERE some_field IN (%s)" % ",".join('%s') 并生成参数化查询。之后我仍然可以应用参数值。【参考方案7】:根据psycopg extension docs,您的代码 sn-p 会变成这样
from psycopg2.extensions import adapt
value = "Unsafe string"
query = "SELECT * FROM some_table WHERE some_char_field = %s;" % \
adapt(value).getquoted()
cursor.execute( query ) # value will be correctly quoted, too
getquoted
函数将 value
作为带引号和转义的字符串返回,因此您也可以使用:"SELECT * FROM some_table WHERE some_char_field = " + adapt(value).getquoted()
。
【讨论】:
【参考方案8】:PyPika 是构建 SQL 语句的另一个不错的选择。使用示例(基于项目主页上的示例):
>>> from pypika import Order, Query
>>> Query.from_('customers').select('id', 'fname', 'lname', 'phone').orderby('id', order=Order.desc)
SELECT "id","fname","lname","phone" FROM "customers" ORDER BY "id" DESC
【讨论】:
【参考方案9】:如果您使用 django,您可能希望使用自动适应当前配置的 DBMS 的引用功能:
from django.db import backend
my_quoted_variable = backend.DatabaseOperations().quote_name(myvar)
【讨论】:
quote_name
用双引号将结果字符串括起来。 PostgreSQL(8.2.5)不允许它作为字符串常量分隔符(看起来它们被用于所谓的带引号的标识符)。使用 django 提供的函数对我来说是最好的,不幸的是我在 db ops 中看不到正确的函数。
看来我完全错了,quote_name 用于转义表和列名!【参考方案10】:
import re
def db_quote(s):
return "\"" + re.escape(s) + "\""
可以完成至少适用于 MySQL 的简单引用工作。我们真正需要的是 cursor.format() 函数,它可以像 cursor.execute() 一样工作,除了它会返回结果查询而不是执行它。有时您不希望查询完全执行 - 例如,您可能希望先记录它,或者在继续之前将其打印出来进行调试。
【讨论】:
只要您严格限制实际可以传递给它的内容,就可以很好地与 mysql 一起使用。这里的解决方案非常危险。 对于标准 SQL'...'
文字,这是完全错误的。对于 postgresql 的非标准 E'...'
文字样式,它接近于 C 字符串,这是 closer 但仍然很危险。序列化/转义时,总是使用为目标语法设计的工具!例如,re.escape
使用 [.]
转义 .
是合法的,这在正则表达式中有效,但在 SQL 中完全不是您想要的。以上是关于如何显式引用字符串值(Python DB API/Psycopg2)的主要内容,如果未能解决你的问题,请参考以下文章