如何显式引用字符串值(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)的主要内容,如果未能解决你的问题,请参考以下文章

如何在Python中显式释放内存?

如何在 Mongo-DB 中删除多级引用模式

关于引用参数或显式返回值的 PHP 方法的最佳实践

有没有办法在函数调用中通过引用传递和通过值显式传递?

Android JNI 学习:JNI 接口整理 — References Api

游标在 Python 的 DB-API 中是如何工作的?