web-dev-qa-db-ja.com

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;'

洞察力に乾杯。

44
Caligari

テーブル名をパラメータとして渡すことはできませんが、他のすべては渡すことができます。したがって、テーブル名はアプリ内でハードコーディングする必要があります(入力として使用したり、プログラムの外部にある名前を使用したりしないでください)。あなたが持っているコードはこれのために働くべきです。

外部のテーブル名を使用する正当な理由があるわずかな機会に、ユーザーがテーブル名を直接入力できないようにしてください。おそらく、インデックスを渡してテーブルを選択したり、テーブル名を他の方法で検索したりできます。ただし、これを行うことに注意するのは正しいことです。周囲にテーブル名が比較的少ないため、これは機能します。テーブル名を検証する方法を見つければ、問題ないはずです。

このようなことを行って、テーブル名が存在するかどうかを確認することが可能です。これはパラメータ化されたバージョンです。 SQLコードを実行する前に、必ずこれを実行し、出力を確認してください。これのアイデアの一部は、 this answer から来ています。

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

公式文書によると:

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のドキュメントによると:

警告:しない、never[〜#〜] never [〜#〜]use Python string concatenation(+)または文字列パラメーターの補間(%)変数をSQLクエリ文字列に渡します。銃口でもない。

58

この答え ごとに次のようにできます:

import psycopg2
from psycopg2.extensions import AsIs

#Create your connection and cursor...

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

これは私が過去に使用した回避策です

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

それが役立つことを願っています:)

3

変数テーブル(...)名を持つ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ごとに%(key)sを返し、値の処理のためにそれらを保持します。この関数は、引用文字が存在しないかどうかをチェックできます。これは、すべての引用が処理されるためです...

2
Tobias

テーブル名をパラメーターとして渡したい場合は、このラッパーを使用できます。

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

1
malthe

テーブル名にモジュール形式を使用してから、実行に通常のパラメーター化を使用できます。

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

これがエンドユーザーに公開されている場合は、特に記述しない限り、SQLインジェクションから保護されないことに注意してください。

0
Derek Bartron