はじめに
PostgreSQL17リリース記念連載の1日目です。
Healthcare Innovation Group(HIG)所属の山本です。
先月の2024年9月、PostgreSQL 17がリリースされました。今回のリリースではバーフォーマンスの改善や、JSON周りなど大奥のアップデートが追加されていますが、その中でもデータ型文字列からデータ型の型修飾子(typemod)を取得する関数to_regtypemod
が新規に追加されました。
本記事では、to_regtypemod
の使用方法や従来の手法との比較を通じて、活用方法やその有用性、背景となる型修飾子自体について記載します。
0. サンプルデータ
本記事を下記にあたっての検証では、具体例などをイメージしやすくするために、以下のようにサンプルデータを作成しています。
-- データベースの作成 |
-- 作成したデータベースに接続 |
-- カスタム型の定義 |
作成後としては、以下のようになります。配列型、自作型などがあることがポイントです。
healthcare_data=# select * from patient_records ; |
1.はじめに
この章では、サンプルデータで具体例を示しつつ、to_regtypemod
それ自体や背景情報を説明します。
1.1 to_regtypemodとは?
https://pgpedia.info/t/to_regtypemod.html
(※執筆時点ではPostgreSQLの公式ドキュメントにないので、pgpediaを引用させていただいています)
to_regtypemod
は、PostgreSQL 17で新たに追加されたシステム関数で、文字列で指定されたデータ型から型修飾子(typemod)を取得することができます。
例えば、VARCHAR(32)やNUMERIC(5, 2)のようなデータ型には、文字列の長さ(32の部分)や数値の精度(5の部分)・スケール(2の部分)が指定されています。to_regtypemodは、これらの修飾子を内部表現の形式で返す関数です。
1.2 基本的な使用方法
to_regtypemod(text) → integer |
戻り値は以下の挙動となります。
- データ型に型修飾子がある場合、その内部表現が返される
- 修飾子がない場合は-1返される
- 存在しないデータ型が指定されたが、文法的に有効な場合はNULLを返す
- 文法エラーがある場合はERRORを発生させる
1.3 サンプルデータによるto_regtypemodの検証
イメージしやすくするため、それぞれ実際に確認してみます。特に、「内部表現が返される」ということが引っかかりポイントな気がします。
VARCHARの例
healthcare_data=# SELECT to_regtypemod('varchar(50)'); |
このように内部表現の値で返されます。
この値は、to_regtype
関数とformat_type
関数と併用することで、見てわかる直感的な表現となります。
healthcare_data=# WITH datatype AS ( |
NUMERIC型でのスケール指定
numeric型で、scale(小数点の右側の小数部分の桁数)が指定されている場合には以下のようになります。
healthcare_data=# SELECT to_regtypemod('numeric(5,2)'); |
healthcare_data=# WITH datatype AS ( |
配列型の場合
配列の場合にも、以下のように取得できています。
healthcare_data=# WITH datatype AS ( |
修飾子が存在しない場合の例
次に、値が不正やない場合の挙動を確認しましょう。
修飾子が存在しない場合、以下のように-1
が返されていますね。
healthcare_data=# SELECT to_regtypemod('numeric'); |
カスタム型の場合
サンプルで定義した、カスタムの型を指定するとどうなるでしょうか?
-- 患者の住所情報を管理するためのカスタム型を定義 |
healthcare_data=# SELECT to_regtypemod('address_type'); |
-1
で返されてしまいました。カスタム型の定義の際に、修飾子を指定していないからですね。
PostgreSQL 17時点の型宣言では、カスタム型の中でも複合型(上記のように、複数フィールドを指定するもの)に型修飾子を付与する構文は存在しないので、同様のことをしたい場合にはトリガーを作成することになるのではないでしょうか?
存在しない型の場合
存在しない型だが文法的に有効な場合には、以下のようにNULLが戻ります。
healthcare_data=# SELECT to_regtypemod('tekitounakata(200)'); |
文法エラーでは、以下のようにエラーが発生します。
healthcare_data=# SELECT to_regtypemod('!'); |
2. 型修飾子(typemod)とは?
ここまでで、to_regtypemod
を用いて、文字列で指定されたデータ型から型修飾子(typemod)を取得する簡単な事例を紹介してきました。
そもそも、型修飾子(typemod)とはなんなのか、もう少し詳細に見ていきます。
2.1 型修飾子の概要
型修飾子(typemod)は、PostgreSQLのデータ型に追加の制約を付与するための仕組みです。
データ型そのものだけでは、例えばNUMERICを指定しても最大長までは許容されてしまうので、実際の用途に合わせたデータのサイズや精度、その他の具体的な制約を表現することができません。型修飾子は、これらの制約を定義するために用いられます。
実際にNUMERIC型を例に挙動を確認していきます。
https://www.postgresql.jp/document/16/html/datatype-numeric.html
NUMERIC型では、型修飾子としてprecisionとscaleを指定可能で、省略することも可能です。
-- サンプルテーブルの作成 |
ageには型修飾子を指定していないので、以下のように大きな数字を入れることができます。46億年なので、だいたい地球の誕生と同じくらいの年齢ですね。
healthcare_data=# INSERT INTO sample_patient_info (age, heart_rate, blood_pressure) |
heart_rateに、桁数以上の数字を指定してみると、以下のようにエラーとなります。
healthcare_data=# INSERT INTO sample_patient_info (age, heart_rate, blood_pressure) |
一方で、位取りの桁数以上を指定した場合には、エラーとならずに数字が丸められます。
healthcare_data=# INSERT INTO sample_patient_info (age, heart_rate, blood_pressure) |
小数点の精度が問われないようなシステムであれば問題ないですが、例えば検査値など勝手に丸められると大事故になりかねません。
一般的なフロントエンド、バックエンド、DBのようなアプリケーション構成であれば画面部分でチェックすることも重要ですが、DBのマイグレーションや移行などでもこれらの観点は重要です。
この一端として、typemodについても意識する必要があると思います。
2.2 型修飾子の内部表現
せっかくのリリース連載なので、より内部的なデータや実装も眺めていきます。
前項までで登場している列ごとに定義した型修飾子の情報は、
PostgreSQLのpg_attribute
テーブルのatttypmod
カラム(int4, 32bit)などで管理されます。
このフィールドには、カラムごとの型修飾子の内部表現が整数として保存されています。このatttypmodを使って、カラムがどのような制約を持つかを確認できます。
サンプルデータのテーブルに確認すると、以下のようになっています。
healthcare_data=# SELECT attname, atttypid, atttypmod FROM pg_attribute WHERE attrelid = 'patient_records'::regclass; |
以下のようにしてto_regtypemodで取得した型修飾子と、同一の内部表現値ですね。
healthcare_data=# SELECT to_regtypemod('numeric(5,2)'); |
ご覧の通り、typemodの表現には内部表現が用いられているので、よほどの職人でなければ目で見てどの型修飾子が付与されているのかの判定は難しいです。
この内部表現は、以下のようにformat_type関数を適応することで、値を内部表現から戻すことができます。
healthcare_data=# SELECT format_type( |
せっかくなので、format_type関数の内部実装までおって確認をしましょう。
format_typeについては、postgresリポジトリにおける以下のファイルで実装されています。
https://github.com/postgres/postgres/blob/master/src/backend/utils/adt/format_type.c#L390
/* |
上記は関連部分の抜粋ですが、
typmodout
で指定されている関数を呼び出して、typmod
からの文字列へ変換をしているようです。
このtypmodout
については、以下のようにpg_attribute
テーブルとpg_type
テーブルを結合することで確認することができます。
今回は例として、atttypmod=327686のものを確認します。
healthcare_data=# WITH attribute_info AS ( |
typmodout
で指定されている関数のうち、確認対象としてはnumerictypmodout
が呼び出される関数であることを確認できました。これは、以下のnumeric.c
で実装されています。
https://github.com/postgres/postgres/blob/master/src/backend/utils/adt/numeric.c#L1368
以下が関連箇所の抜粋です。
Datum |
/* |
ようやく復元の処理にたどり着けましたね。VARHDRSZについては、データ全体のバイト数を表現しており、4バイトとなります。
まとめると、NUMERIC型と型修飾子で表現された327686
とい
う値をbit表現に変換してヘッダーを引いた場合は、以下のようなイメージで復元をされます。
型修飾子と文字列の相互変換というものは、それぞれ対象としたい型ごとのビット表現を把握して実装する必要があるので、自作するのであればかなり手間になりそうということは伝わったのではないでしょうか?
to_regtypemod
関数の追加より前では、実際にテーブルに追加してpg_attribute
テーブルとpg_type
から良い感じで取得すれば、文字列(‘numeric(5,2)’のようなもの)からの型修飾子の取得自体は可能です。
また、精度・位取り自体を取得したい場合にも正規表現などを工夫すれば不可能ではないです。
ですが、これらの作業はやりたいことのわりには手間がかかり、かつ不具合のリスクやアップデートへの対応力は高いとは言えないと思います。
これを文字列 -> typemod
の形式に直接変換できるという意味で、to_regtypemod
関数には大きな価値があると感じます。
3. 具体の活用事例を考える
to_regtypemod
自体は文字列からtypemodを取得する関数であるため、外部システムやテストとのやりとりが最も活用される事例ではないかと思います。
実際にこの機能の追加の契機となっている、以下のメーリングリストでは、postgresの単体テストツールであるpgTAPでデータ型のアサーションが困難であったとのことで、機能のパッチを提案しています。
https://www.postgresql.org/message-id/DF2324CA-2673-4ABE-B382-26B5770B6AA3@justatheory.com
データベースのテスト実装などでもそうですし、あるいは他システムとのマイグレーションの事前検証(MySQL -> PostgreSQLとか)、スキーマ間の移行などにも役立つかもしれません。
4. まとめ
PostgreSQL 17で新規に追加されたto_regtypemod
関数では、文字列(‘numeric(5,2)’のようなもの)から型修飾子(typemod)を直接取得することができます。
従来の方法では、実際にデータベースに登録してpg_attribute
テーブルとpg_type
テーブルなどを組み合わせて取得するであったり、正規表現を工夫するなどの方法も考えられますが、いささか煩雑です。
医療や金融システムなど、型修飾子レベルで正確なデータ型管理が求められる分野で to_regtypemod
を活用することで、データの整合性を高いレベルで維持しつつ、開発・運用の効率化を図ることができるのではないでしょうか?