※当サイトの記事には、広告・プロモーションが含まれます。

PythonからPostgreSQLのPL/pgSQLの関数を呼び出してみる

www.publickey1.jp

今回、C言語が変換元として指定されたのは、C言語で書かれたシステムが大量に存在するからだとDARPは今回の発表の中で次のように説明しています。

米国防総省DARPA、C言語のコードからRustへの自動変換実現を目指す「TRACTOR」プログラム開始 - Publickey

⇧ う~む、Pythonの内部で利用されてるっぽいCPythonとかを刷新して欲しい気がしますな...

他にも、コアな部分でC言語に依存しているプログラミング言語とかありそうな気がするので、影響範囲が広過ぎる気はしますな...

PostgreSQLPL/pgSQLとは?

PostgreSQLの公式のドキュメントによると、

www.postgresql.jp

43.1. 概要

PL/pgSQLは、PostgreSQLデータベースシステム用の読み込み可能な手続き言語です。

https://www.postgresql.jp/docs/16/plpgsql-overview.html

PL/pgSQLの設計目的は、次のような読み込み可能な手続き言語でした。

  • 関数、プロシージャとトリガを作成するために使用できること

  • SQL言語に制御構造を追加すること

  • 複雑な演算が可能であること

  • 全てのユーザ定義型、関数、プロシージャ、演算子を継承すること

  • サーバによって信頼できるものと定義できること

  • 使いやすいこと

https://www.postgresql.jp/docs/16/plpgsql-overview.html

PL/pgSQLで作成した関数は、組み込み関数が使えるところであれば、どこでも使用できます。

https://www.postgresql.jp/docs/16/plpgsql-overview.html

PostgreSQL 9.0以降ではPL/pgSQLはデフォルトでインストールされます。 しかしこれはまだロード可能なモジュールですので、特にセキュリティに厳しい管理者は削除することもできます。

https://www.postgresql.jp/docs/16/plpgsql-overview.html

⇧ う~む、分からん...

公式のドキュメントではないですが、

lets.postgresql.jp

一方バックエンドの方は、中心となるのがデータベース処理を実行するデータベースエンジンです。 データベースエンジンにはユーザが作成したアプリケーションを実行する機能があります。 この機能を使って、ユーザは柔軟に PostgreSQL の機能を拡張できます。 良く利用されるのは、ほかのデータベースで言うところの「ストアドプロシジャ」で、PostgreSQL ではユーザ定義関数と呼びます。言語別に以下のようなものが利用できます。

PostgreSQL の構造とソースツリー | Let's POSTGRES

⇧ 上記サイト様によりますと、他の「RDBMS(Relational DataBase Management Systems)」だと「ストアドプロシージャ」と呼ばれる機能が、PostgreSQLでは「ユーザー定義関数」と呼ばれ、「PL/pgSQL」はPostgreSQLの「ユーザー定義関数」を実現するPostgreSQLの機能の1つということらしい。

と思ったら、

dev.appswingby.com

2018年 10月にリリースされたPostgreSQL11からストアド・プロシージャが実装されています。

PL/pgSQL プロシージャとファンクションについて – APPSWINGBY Developers Blog

⇧ 上記サイト様によりますと、「PostgreSQL 11」からPostgreSQLにも「ストアド・プロシージャ」が導入されたらしいという情報もあり、情報が錯綜しておりますと...

カオス過ぎるんだが...

ストアド・ファンクションの主な特徴は、下記の通りです。

PL/pgSQL プロシージャとファンクションについて – APPSWINGBY Developers Blog

⇧ とりあえず、

  • PL/pgSQL
    • function ←関数
    • procedure ←プロシージャ

のどちらかを作る感じになるということなんでしょうかね?

話が脱線しましたが、「PL/pgSQL」の「関数」ないしは「プロシージャ」は、PostgreSQLのインストールされているマシンで作成でき、保管もされると思われますと。

一応、PostgreSQLの公式のドキュメントによると、

www.postgresql.jp

System catalog【システムカタログ】

インスタンスのすべてのSQLオブジェクトの構造を記述するテーブルの集まり。 システムカタログはpg_catalogスキーマに存在します。 これらのテーブルは内部表現のデータを格納しているので、典型的にはユーザが調べる目的には適しません。 pg_catalogスキーマにもユーザによりわかりやすい多くのビューが提供されており、一部の情報にはより便利なアクセスを提供しています。 一方SQL標準によって管理されているものと同じあるいはさらに追加の情報を提供するinformation_schemaスキーマ第37章参照)に追加のテーブルとビューがあります。

詳細については5.9を参照してください。

https://www.postgresql.jp/document/16/html/glossary.html#GLOSSARY-SYSTEM-CATALOG

PostgreSQLで用意されている「システムカタログ」というテーブル群があり、

www.postgresql.jp

53.39. pg_proc

pg_procカタログは関数、プロシージャ、集約関数あるいはWINDOW関数(これらをまとめてルーチンとも言います)に関する情報を格納します。 CREATE FUNCTIONCREATE PROCEDURE38.3を参照してください。

https://www.postgresql.jp/document/16/html/catalog-pg-proc.html

PostgreSQLの「PL/pgSQL」で作成した「関数」の情報も格納されるとあるので。

PythonPostgreSQLPL/pgSQLを利用するには、PostgreSQLのドライバーがインストールされていれば良さ気

で、Pythonのアプリケーションの話にはなりますが、「PL/pgSQL」の「関数」を実行するには、

pynative.com

Python側で、PostgreSQLのドライバーがインストールされていれば良さそうでありますと。

とりあえず、

wiki.postgresql.org

Python向けのPostgreSQLのドライバーは、5つ用意されておりますと。

自分の環境では、

ts0818.hatenablog.com

⇧ 前回の記事で、「psycopg3」をインストールしていたので、Python側から「PL/pgSQL」の「関数」を呼び出すための要件は整っていそう。

PostgreSQL側でPL/pgSQLの関数を作成する

Python側から呼び出す「PL/pgSQL」の「関数」を、PostgreSQL側で作成します。

qiita.com

qiita.com

stackoverflow.com

qiita.com

⇧ 上記サイト様を参考に作成してみます。

気を付けたいのが、テーブル名の部分は、

  • 「ダブルクォーテーション」で囲む
  • スキーマ名.テーブル名」の形にする

のいずれかにしていないとエラーが出てしまうことがあるということでしょうか、と言うかエラーになって実行できなかったんで...

「user」テーブルという名前が、PostgreSQLのオブジェクトとバッティングしたりしてるってことなのかな?

ネット上の情報だと、テーブル名だけの記述のサンプルが多いからして、正解が分からない...

コーディングのルールとかハッキリさせて欲しいんですけどね...

PostgreSQLにログインし、以下を実行。

CREATE OR REPLACE FUNCTION select_all_user_func() 
  RETURNS SETOF "user"
AS 
$$
BEGIN
-- userテーブルから全レコードを取得し、結果を返す
  RETURN QUERY 
    SELECT * FROM "user";
END;
$$
LANGUAGE plpgsql;

作成できているか、「pg_proc」テーブルを確認してみます。

⇧「pg_proc」テーブルに追加されているので「PL/pgSQL」の「関数」が作成できていそうです。

PythonからPostgreSQLPL/pgSQLの関数を呼び出してみる

では、「VS CodeVisual Studio Code)」で、「PL/pgSQL」の「関数」を呼び出すPythonソースコードを追加していきます。

その前に

PostgreSQLのデータベースとしてtestdbを作成しており、userテーブルを作成していて、1レコード登録してある状態です。

今回、追加したソースコード以外は、

ts0818.hatenablog.com

⇧ 上記の記事の時のものを利用しています。

ソースコードは、以下のようになりました。

今回は、「PL/pgSQL」の「関数」が呼び出せるかどうかのチェックをしたいだけだったので、手っ取り早く試すために、データベース接続情報とかハードコーディングしてしまっていますが、外部ファイルに定義するようにして、外部ファイルから読み込むようにしましょう。

■C:\Users\Toshinobu\Desktop\soft_work\python_work\fastapi\app\src\main\py\call\procedural_language\select_all_user_func.py

import asyncio
import sys
from pprint import pprint
from typing import List
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import sessionmaker

from app.src.main.py.entity.user_entity import UserEntity

# Windows環境でのイベントループポリシーの設定
if sys.platform == "win32":
    asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
# from sqlalchemy.ext.compiler import compiles

################################################################
# PostgreSQLのデータベース接続情報
################################################################
CONNECTION_DB_URL = "postgresql+psycopg://test_user:password@172.24.91.141:5432/testdb"
engine = create_async_engine(CONNECTION_DB_URL, echo=True)
async_session = sessionmaker(engine, expire_on_commit=False, class_=AsyncSession)

################################################################
# PL/pgSQLの関数を呼び出す処理の関数
################################################################
# async def call_select_all_user_func() -> UserEntity:
#     async with async_session() as session:
#         result = await session.execute(select(func.select_all_user_func()))
#         print("■■■PL/PgSQLの関数の呼び出し結果■■■")
#         pprint(result)
#         users = result.fetchall()
#         pprint(users)
#         if not users:
#             print("No users found.")
#         return users


async def call_select_all_user_func() -> List[UserEntity]:
    async with async_session() as session:
        result = await session.execute(select(func.select_all_user_func()))
        print("■■■PL/PgSQLの関数の呼び出し結果■■■")
        pprint(result)
        users = result.scalars().all()
        print("■■■users■■■")
        print(type(users))
        pprint(users)

        if not users:
            print("No users found.")
        return users


################################################################
# メイン処理
################################################################
################################################################
#   関数を呼び出す
################################################################
# users = asyncio.run(call_select_all_user_func())
# if users:
#     for user in users:
#         print(user)
# else:
#     print("The user table is empty.")

users = asyncio.run(call_select_all_user_func())
if users:
    for user in users:
        print(user)
else:
    print("The user table is empty.")
    

で、保存。実行すると、

⇧「PL/pgSQL」の「関数」を呼び出すことができていそうです。

とりあえず、

  • psycopg3
  • SQLAlchemy

のどちらの公式ドキュメントにも、「PL/pgSQL」の「関数」をPythonから呼び出す実装の方法についての言及が見当たらなかったんよね...

stackoverflowの情報とMicrosoft Copilotの情報を元にコーディングしてるから、正しい実装になっているのか判断が付かない...

公式のドキュメントで記載されていないのなら、記載して欲しいと思う今日この頃...

不毛な時間に疲弊するのみですな...

毎度モヤモヤ感が半端ない…

今回はこのへんで。