【问题标题】:How to quote a string value explicitly (Python DB API/Psycopg2)如何显式引用字符串值(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 保留在一行中

标签: python sql django psycopg2


【解决方案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 中完全不是您想要的。
        猜你喜欢
        • 2010-11-16
        • 1970-01-01
        • 2010-11-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-01-08
        • 1970-01-01
        相关资源
        最近更新 更多