psycopg2 との違い#

Psycopg 3 は、他の多くのデータベースアダプターと共通の DBAPI 構造を使用しており、できるだけ psycopg2 に近い動作になるように努めています。しかし、注意するべき異なる点がいくつかあります。

Tip

ほとんどの場合、ここで提案しているワークアラウンドは Psycopg 2 と 3 の両方で動作します。これは Psycopg 2 と 3 の両方で動作する必要があるプログラムをポーティング・作成している場合に役に立つかもしれません。

サーバーサイド バインディング#

Psycopg 3 は、クエリとパラメータをクライアントサイドでマージするのではなく、サーバーに別々に送信します。サーバーサイド バインディングは 通常の SELECT とデータ操作のステートメント (INSERTUPDATEDELETE) に対して動作しますが、他の多くのステートメントでは動作しません。たとえば、次のように SETNOTIFY では動作しません。

>>> conn.execute("SET TimeZone TO %s", ["UTC"])
Traceback (most recent call last):
...
psycopg.errors.SyntaxError: syntax error at or near "$1"
LINE 1: SET TimeZone TO $1
                        ^

>>> conn.execute("NOTIFY %s, %s", ["chan", 42])
Traceback (most recent call last):
...
psycopg.errors.SyntaxError: syntax error at or near "$1"
LINE 1: NOTIFY $1, $2
               ^

そして、次のようなデータ定義のステートメントでも動作しません。

>>> conn.execute("CREATE TABLE foo (id int DEFAULT %s)", [42])
Traceback (most recent call last):
...
psycopg.errors.UndefinedParameter: there is no parameter $1
LINE 1: CREATE TABLE foo (id int DEFAULT $1)
                                         ^

ときには、PostgreSQL が代替手段を提供してくれることもあります。たとえば、SET ステートメントの代わりに set_config() 関数が、NOTIFY の代わりに pg_notify() 関数がそれぞれ利用できます。

>>> conn.execute("SELECT set_config('TimeZone', %s, false)", ["UTC"])

>>> conn.execute("SELECT pg_notify(%s, %s)", ["chan", "42"])

これが不可能な場合は、クエリとパラメータをクライアントサイドでマージしなければなりません。次のように psycopg.sql オブジェクトを使うとマージが行なえます。

>>> from psycopg import sql

>>> cur.execute(sql.SQL("CREATE TABLE foo (id int DEFAULT {})").format(42))

あるいは、次のように、ClientCursor などの クライアントサイドのバインディング カーソル を作成します。

>>> cur = ClientCursor(conn)
>>> cur.execute("CREATE TABLE foo (id int DEFAULT %s)", [42])

ClientCursor が頻繁に必要になる場合、Connection.cursor_factory を設定すると、Connection.cursor() がデフォルトで作成するように設定できます。このようにすると、Psycopg 3 は大部分で Psycopg 2 と同じように動作するようになります。

サーバーサイドとクライアントサイドのいずれでも、パラメータとして指定できるのは特定の (つまり、シングルクォート内に入る文字列) だけであることに注意してください。ステートメントの異なるパーツ (テーブル名など) をパラメータ化する必要がある場合には、次のように psycopg.sql モジュールを使う必要があります。

>>> from psycopg import sql

# これはユーザーとパスワードを正しいクォートを使ってクォートします
# 例: ALTER USER "foo" SET PASSWORD 'bar'
>>> conn.execute(
...     sql.SQL("ALTER USER {} SET PASSWORD {}")
...     .format(sql.Identifier(username), password))

同じクエリ内の複数のステートメント#

サーバーサイド バインディング の利用結果として、パラメータが使用されている場合、同じ execute() 呼び出しで、カンマで区切られた複数のステートメントを実行することは不可能になります。

>>> conn.execute(
...     "INSERT INTO foo VALUES (%s); INSERT INTO foo VALUES (%s)",
...     (10, 20))
Traceback (most recent call last):
...
psycopg.errors.SyntaxError: cannot insert multiple commands into a prepared statement

問題を回避する明らかな方法の1つは、複数の execute() 呼び出しを行うことです。

パラメータが使用されていない場合には、そのような制限はありません。結果として、次のように psycopg.sql オブジェクトを使用することで、複数のクエリをクライアントサイドで構築して、同じ execute() 呼び出し内で実行できます。

>>> from psycopg import sql
>>> conn.execute(
...     sql.SQL("INSERT INTO foo VALUES ({}); INSERT INTO foo values ({})"
...     .format(10, 20))

または、次のように クライアントサイド バインディング カーソル を使用します。

>>> cur = psycopg.ClientCursor(conn)
>>> cur.execute(
...     "INSERT INTO foo VALUES (%s); INSERT INTO foo VALUES (%s)",
...     (10, 20))

警告

ステートメントをトランザクションの外部で実行するする必要がある場合 (CREATE DATABASE など)、次のように、たとえコネクションが autocommit モードだとしても、他のステートメントとともにバッチで実行することはできません。

>>> conn.autocommit = True
>>> conn.execute("CREATE DATABASE foo; SELECT 1")
Traceback (most recent call last):
...
psycopg.errors.ActiveSqlTransaction: CREATE DATABASE cannot run inside a transaction block

この問題が起きるのは、PostgreSQL 自体が複数のステートメントをトランザクション内にラッピングするためです。psql では異なる動作を経験することに注意してください (psql はクエリをセミコロンで分割して、これらを別々にサーバーに送信します)。

これは Psycopg 3 の新たな変更というわけではありません。psycopg2 にも同じ制限がありました。

複数のステートメントから返される複数の結果#

結果を返す2つ以上のステートメントが psycopg2 で実行された場合、次のように最後のステートメントの結果だけが返されます。

>>> cur_pg2.execute("SELECT 1; SELECT 2")
>>> cur_pg2.fetchone()
(2,)

代わりに psycopg 3 では、すべての結果が利用できます。クエリを実行した後、最初の結果はカーソル上ですぐに利用可能になり、通常の fetch*() メソッドを使用して取得できます。後続の結果にアクセスするためには、次のように Cursor.nextset() メソッドが使用できます。

>>> cur_pg3.execute("SELECT 1; SELECT 2")
>>> cur_pg3.fetchone()
(1,)
>>> cur_pg3.nextset()
True
>>> cur_pg3.fetchone()
(2,)
>>> cur_pg3.nextset()
None  # no more results

ただし、パラメータをクエリに渡している場合、サーバーサイド バインディングを使って 2つ以上のステートメントを同じクエリ内で実行する ことはできないことに注意してください。

異なるキャスト ルール#

稀な状況では、特に可変引数関数の辺りでは、次のように、PostgreSQL が与えられたデータ型に対する関数の候補を見つけるのに失敗する可能性があります

>>> conn.execute("SELECT json_build_array(%s, %s)", ["foo", "bar"])
Traceback (most recent call last):
...
psycopg.errors.IndeterminateDatatype: could not determine data type of parameter $1

これは、次のようにキャストを介して引数の型を明示的に指定することで回避できます。

>>> conn.execute("SELECT json_build_array(%s::text, %s::text)", ["foo", "bar"])

IN %s はタプルとともに使えない#

IN は単一のパラメータとしてタプルとともに使うことはできません。これは psygopg2 では可能でした。

>>> conn.execute("SELECT * FROM foo WHERE id IN %s", [(10,20,30)])
Traceback (most recent call last):
...
psycopg.errors.SyntaxError: syntax error at or near "$1"
LINE 1: SELECT * FROM foo WHERE id IN $1
                                      ^

できることは、= ANY() construct を使用して、次のように候補値をタプルではなくリストとして渡すことです。これは PostgreSQL の配列に適応されます。

>>> conn.execute("SELECT * FROM foo WHERE id = ANY(%s)", [[10,20,30]])

ANY()psycopg2 でも使用でき、空の値のリストも引数として受け取れる利点があることに注意してください。IN 空のリストは演算子ではサポートされていません。

IS %s は使用できない#

次のように、IS %sIS NOT %s は使用できません。

>>> conn.execute("SELECT * FROM foo WHERE field IS %s", [None])
Traceback (most recent call last):
...
psycopg.errors.SyntaxError: syntax error at or near "$1"
LINE 1: SELECT * FROM foo WHERE field IS $1
                                     ^

これはおそらく、IS が PostgreSQL では二項演算子ではないという事実に起因します。むしろ、IS NULLIS NOT NULL は単項演算子であり、IS を右側にある他の何かとともに使用することはできません。psql でテストしてみると、次のようになります。

=# SELECT 10 IS 10;
ERROR:  syntax error at or near "10"
LINE 1: select 10 is 10;
                     ^

代わりにできることは、IS DISTINCT FROM 演算子 を使用することです。この演算子は、次のようにプレースホルダーを喜んで受け入れます。

>>> conn.execute("SELECT * FROM foo WHERE field IS NOT DISTINCT FROM %s", [None])

同様に、IS DISTINCT FROM %s はパラメータ バージョンの IS NOT %s として使用できます。

カーソル サブクラス#

psycopg2 では、少数のカーソルのサブクラスが、タプル以外の形式でデータを返すことができました。psycopg 3 では、同じことが次のように 行ファクトリ を設定することで可能になります。

他の利用可能な行ファクトリは、psycopg.rows モジュールにあります。DictCursor (その結果が列の位置と列の名前の両方でインデックス可能なもの) のように動作するオブジェクトはありません。

from psycopg.rows import dict_row, namedtuple_row

# デフォルトでは、すべてのカーソルがディクショナリを返す。
conn = psycopg.connect(DSN, row_factory=dict_row)

# 行ファクトリには単一のカーソルも設定できる。
cur = conn.cursor(row_factory=namedtuple_row)

異なる適応システム#

適応システムは完全に書き換えられました。これは、サーバーサイドのパラメータの適応に対応するためですが、性能、柔軟性、カスタマイズを簡単にすることも考慮しています。

ビルトインのデータを使用したデフォルトの動作は、あなたが期待するとおり のはずです。データの適応方法をカスタマイズしていた場合、または、独自の拡張型を管理している場合、新しい適応システム に目を通す必要があります。

参考

copy はファイルベースではなくなった#

psycopg2 は PostgreSQL の COPY とやり取りするために いくつかの copy メソッド を公開していました。それらのファイルベースのインターフェイスでは、動的に生成されたデータをデータベースに読み込むのが簡単ではありません。

現在は、1つの copy() メソッドだけがあります。このメソッドは、自由形式の COPY コマンドを受け取り、ブロック単位またはレコード単位のデータの読み込み/書き込みのためのオブジェクトを返すという点で psycopg2copy_expert() に似ています。異なる使用パターンにより、COPY を非同期の対話でも使用することもできます。

参考

詳細については Using COPY TO and COPY FROM も参照してください。

with connection#

psycopg2 では、with connection という構文を使うと、トランザクションだけがクローズされ、コネクションはクローズされませんでした。この動作は、他のさまざまなリソースをラッピングするファイルなどの Python クラスに慣れている人たちを驚かせました。

psycopg 3 では、with connection を使用すると with ブロックの最後でコネクションをクローズするようになり、コネクションのリソースの処理をより慣れ親しんだものにしました。

トランザクションをブロックとして管理するためには、Connection.transaction() メソッドが使えます。このメソッドを使うと、たとえばネストされたトランザクションの使用など、細かい制御が可能になります。

参考

詳細については、Transaction contexts を参照してください。

callproc() はなくなった#

cursor.callproc() は実装されていません。このメソッドは単純なセマンティクスしか持たず、PostgreSQL の位置引数、プロシージャ、set-returning 関数などを考慮に入れません。代わりに、通常の execute()SELECT function_name(...) または CALL procedure_name(...) を使ってください。

client_encoding はなくなった#

psycopg は、データをUnicode 文字列にデコードするために、自動的にデータベース クライアントのエンコーディングを使います。エンコーディングを読み取る必要がある場合は、ConnectionInfo.encoding を使用してください。コネクション時のエンコーディングを選択するには client_encoding コネクション パラメータが使用でき、コネクションのエンコーディングは SET client_encoding ステートメントを実行すれば変換することはできます。でも、なぜそのようなことをするのでしょうか?

トランザクションの性質の属性は autocommit セッションに影響を及ぼさない#

read_only などの トランザクションの性質の属性 は自動的な autocommit セッションに影響を及ぼさなくなりました。これらの属性は非 autocommit なセッションにより開始された暗黙のトランザクションと、transaction() ブロックにより作成されたトランザクション (autocommit および非 autocommit コネクションの両方) にのみ影響します。

autocommit なトランザクションを read-only モードにしたい場合、default_transaction_read_only GUC を使用してください。たとえば、ステートメント SET default_transaction_read_only TO true を実行します。

デフォルトの infinity な日付の処理はない#

PostgreSQL は、Python よりずっと広い範囲の日付とタイムスタンプを表現できます。Python の日付は 1 年から 9999 年に制限されますが (datetime.date.minmax などの定数で表現される)、PostgreSQL の日付は BC の日付から 10,000 年を超えた日付まで拡張します。さらに、PostgreSQL では両方向でシンボルの日付 "infinity" を表現することもできます。

psycopg2 では、デフォルトで infinity の日付とタイムスタンプがマッピングされるのは 'date.max' および同様の定数です。これには、全単射ではないマッピングを作成してしまうという問題があります (2 つの Postgres の日付、infinity と 9999-12-31が、両方とも同じ Python の日付にマッピングされてしまいます)。有効な PostgreSQL の日付 (Python の date.max より大きいが、おそらく無限大より小さい) が依然としてオーバーフローしてしまうという歪みもあります。

psycopg 3 では、9999 年より大きいすべての日付は、infinity を含めてオーバーフローします。このマッピングをカスタマイズしたい (たとえば、10,000 年を超えるすべての日付を date.max に平坦化する) 場合には、適切なローダーをサブクラス化して適応できます。方法を学ぶには この例 を参照してください。

psycopg 3 の新機能#