Передача имени таблицы в качестве параметра в psycopg2

у меня есть следующий код, используя 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;'

Ура для любых идей.

8 ответов


имя таблицы не может быть передан в качестве параметра, но все остальное можно. Таким образом, имя таблицы должно быть жестко закодировано в вашем приложении (не принимайте входы или не используйте что-либо вне программы в качестве имени). Код, который у вас есть, должен работать для этого.

при небольшом шансе, что у вас есть законная причина взять имя внешней таблицы, убедитесь, что вы не позволяете пользователю напрямую вводить его. Возможно, индекс может быть передан для выбора таблицы, или имя таблицы может быть просмотрено каким-то другим способом. Однако вы правы,опасаясь этого. Это работает, потому что вокруг относительно мало имен таблиц. Найдите способ проверить имя таблицы, и все будет в порядке.

можно было бы сделать что-то вроде этого, чтобы увидеть, существует ли имя таблицы. Это параметризованная версия. Просто убедитесь, что вы делаете это и проверяете вывод перед запуском кода SQL. Часть идеи этого этот ответ.

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

согласно официальной документации:

Если вам нужно динамически генерировать SQL-запрос (например динамический выбор имени таблицы) вы можете воспользоваться услугами предоставлено psycopg2.модуль sql.

на sql модуль является новым в psycopg2 версии 2.7. Он имеет следующий синтаксис:

from psycopg2 import sql

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

больше на: http://initd.org/psycopg/docs/sql.html#module-psycopg2.sql

[обновление 2017-03-24: AsIs не следует использовать для представления имен таблиц или полей, новый sql вместо этого следует использовать модуль:https://stackoverflow.com/a/42980069/5285608 ]

также, согласно документации psycopg2:

предупреждение никогда, никогда, никогда использовать конкатенацию строк Python (+) или интерполяция параметров строки (%) для передачи переменных в строку запроса SQL. Даже под дулом пистолета.


Per ответ вы можете сделать это так:

import psycopg2
from psycopg2.extensions import AsIs

#Create your connection and cursor...

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

Я создал небольшую утилиту для предварительной обработки операторов SQL с переменной table (...) имена:

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 для каждого неизвестно!--2-->, сохранив их для обработки. Функция может проверить отсутствие каких-либо символов цитаты, так как все цитаты теперь должны быть позаботились ...


Если вы хотите передать имя таблицы в качестве параметра, вы можете использовать эту оболочку:

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), ))


это обходной путь, который я использовал в прошлом

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

надеюсь, что это поможет :)


вы можете просто использовать формат модуля для имени таблицы, а затем использовать регулярную параматериализацию для выполнения:

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

имейте в виду, если это доступно конечному пользователю, вы не будете защищены от SQL-инъекции, если вы не пишете для него.


удивлен, что никто не упомянул об этом:

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

format помещает строку без кавычек.