在 psycopg2 中将表名作为参数传递

问题描述 投票:0回答:10

我有以下代码,使用 pscyopg2:

sql = 'select %s from %s where utctime > %s and utctime < %s order by utctime asc;'
data = (dataItems, voyage, dateRangeLower, dateRangeUpper)
rows = cur.mogrify(sql, data)

这输出:

select 'waterTemp, airTemp, utctime' from 'ss2012_t02' where utctime > '2012-05-03T17:01:35+00:00'::timestamptz and utctime < '2012-05-01T17:01:35+00:00'::timestamptz order by utctime asc;

当我执行这个时,它倒下了——这是可以理解的,因为表名周围的引号是非法的。

有没有办法合法地将表名作为参数传递,或者我是否需要进行(明确警告不要)字符串连接,即:

voyage = 'ss2012_t02'
sql = 'select %s from ' + voyage + ' where utctime > %s and utctime < %s order by utctime asc;'

为任何见解干杯。

python sql postgresql sql-injection psycopg2
10个回答
112
投票

根据官方文档:

如果您需要动态生成 SQL 查询(例如 动态选择表名)你可以使用这些设施 由 psycopg2.sql 模块提供。

sql
模块是 psycopg2 2.7 版中的新模块。它具有以下语法:

from psycopg2 import sql

cur.execute(
    sql.SQL("insert into {table} values (%s, %s)")
        .format(table=sql.Identifier('my_table')),
    [10, 20])

更多信息:https://www.psycopg.org/docs/sql.html#module-usage

[2017 年 3 月 24 日更新:

AsIs
不应用于表示表或字段名称,应使用新的
sql
模块:https://stackoverflow.com/a/42980069/5285608]

此外,根据 psycopg2 文档:

警告:从不,neverNEVER使用Python字符串连接(

+
)或字符串参数插值(
%
)将变量传递给SQL查询字符串。甚至没有在枪口下。


32
投票

Per this answer你可以这样做:

import psycopg2
from psycopg2.extensions import AsIs

#Create your connection and cursor...

cursor.execute("SELECT * FROM %(table)s", {"table": AsIs("my_awesome_table")})

26
投票

表名不能作为参数传递,其他都可以。因此,表名应该在您的应用程序中进行硬编码(不要输入或使用程序之外的任何内容作为名称)。您拥有的代码应该适用于此。

在极少数情况下,您有正当理由使用外部表名,请确保您不允许用户直接输入它。也许可以传递一个索引来选择一个表,或者可以通过其他方式查找表名。但是,您对这样做保持警惕是正确的。这是可行的,因为周围的表名相对较少。找到一种方法来验证表名,你应该没问题。

可以做这样的事情,看看表名是否存在。这是一个参数化版本。只需确保在运行 SQL 代码之前执行此操作并验证输出即可。部分想法来自this answer.

SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' and table_name=%s LIMIT 1

4
投票

这是我过去用过的解决方法

query = "INSERT INTO %s (col_1, col_2) VALUES (%%s, %%s)" % table_name
cur.execute(query, (col_1_var, col_2_var))

希望有帮助:)


4
投票

这是对@Antoine Dusséaux 回答的一个小补充。如果要在 SQL 查询中传递两个(不带引号的)参数,可以按如下方式进行:-

query = sql.SQL("select {field} from {table} where {pkey} = %s").format(
    field=sql.Identifier('my_name'),
    table=sql.Identifier('some_table'),
    pkey=sql.Identifier('id'))

根据文档,

通常您应该将查询的模板表示为 SQL 带有 {} 样式占位符的实例并使用 format() 合并 可变部分放入其中,所有这些都必须是可组合的子类。 您仍然可以在查询和传递值中使用 %s 样式的占位符 to execute():这样的值占位符将不会被 format()

来源:https://www.psycopg.org/docs/sql.html#module-usage

此外,请在撰写查询时牢记this


2
投票

我创建了一个小工具,用于预处理带有变量表 (...) 名称的 SQL 语句:

from string import letters
NAMECHARS = frozenset(set(letters).union('.'))

def replace_names(sql, **kwargs):
    """
    Preprocess an SQL statement: securely replace table ... names
    before handing the result over to the database adapter,
    which will take care of the values.

    There will be no quoting of names, because this would make them
    case sensitive; instead it is ensured that no dangerous chars
    are contained.

    >>> replace_names('SELECT * FROM %(table)s WHERE val=%(val)s;',
    ...               table='fozzie')
    'SELECT * FROM fozzie WHERE val=%(val)s;'
    """
    for v in kwargs.values():
        check_name(v)
    dic = SmartDict(kwargs)
    return sql % dic

def check_name(tablename):
    """
    Check the given name for being syntactically valid,
    and usable without quoting
    """
    if not isinstance(tablename, basestring):
        raise TypeError('%r is not a string' % (tablename,))
    invalid = set(tablename).difference(NAMECHARS)
    if invalid:
        raise ValueError('Invalid chars: %s' % (tuple(invalid),))
    for s in tablename.split('.'):
        if not s:
            raise ValueError('Empty segment in %r' % tablename)

class SmartDict(dict):
    def __getitem__(self, key):
        try:
            return dict.__getitem__(self, key)
        except KeyError:
            check_name(key)
            return key.join(('%(', ')s'))

SmartDict 对象为每个未知的

%(key)s
返回
key
,保留它们用于值处理。该函数可以检查是否缺少任何引号字符,因为现在应该处理所有引号......


1
投票

如果你想将表名作为参数传递,你可以使用这个包装器:

class Literal(str):
    def __conform__(self, quote):
        return self

    @classmethod
    def mro(cls):
        return (object, )

    def getquoted(self):
        return str(self)

用法:

cursor.execute("CREATE TABLE %s ...", (Literal(name), ))


0
投票

您可以只使用表名的模块格式,然后使用常规参数化执行:

xlist = (column, table)
sql = 'select {0} from {1} where utctime > %s and utctime < %s order by utctime asc;'.format(xlist)

请记住,如果这暴露给最终用户,除非您为此编写,否则您将无法免受 SQL 注入的侵害。


0
投票

如果您需要传递合格的标识符,例如架构名称 + 表名称:

from psycopg2 import sql

cur.execute(
    sql.SQL(
        "INSERT INTO {table} VALUES (%s, %s)"
    ).format(
        table=sql.Identifier("my_schema", "my_table")
    ),
    [10, 20]
)

# INSERT INTO "my_schema"."my_table" VALUES (10, 20)

参见:https://www.psycopg.org/docs/sql.html#psycopg2.sql.Identifier


-6
投票

很惊讶没有人提到这样做:

sql = 'select {} from {} where utctime > {} and utctime < {} order by utctime asc;'.format(dataItems, voyage, dateRangeLower, dateRangeUpper)
rows = cur.mogrify(sql)

format 放入不带引号的字符串。

© www.soinside.com 2019 - 2024. All rights reserved.