😎

RustでPostgreSQLで自分で定義した型の利用

2024/03/01に公開

目的

前回PostgreSQLが持っている型とRustの型をマッピングしてみました。

今回は自分て定義した型でPostgreSQLとRustのstructやenumにマッピングしたいと思います。
リファレンスには定義の仕方が詳細に書いてありますが、使い方は書いてないのでこの記事が参考になると思います。

リポジトリはこちら。

コード

PostgreSQL

ここではOSの種類を表すenumとしてtype_enum_osを、整数を意味するドメインとしてdomain_integerを定義します。

それらを使ってストアードプロシージャーで値をやり取りする型としてtype_pg_get_select_compositeを定義します。

ストアードプロシージャーでは受け取った値を2回返しています。

pg_get_select_composite.sql
DROP TYPE IF EXISTS type_enum_os CASCADE;
CREATE TYPE type_enum_os AS ENUM (
  'linux',
  'mac_os',
  'windows'
);

DROP DOMAIN IF EXISTS domain_integer CASCADE;
CREATE DOMAIN domain_integer AS BIGINT CHECK(VALUE >= 0);

DROP TYPE IF EXISTS type_pg_get_select_composite CASCADE;
CREATE TYPE type_pg_get_select_composite AS (
  os type_enum_os,
  cpu_count domain_integer,
  memory_size BIGINT
);

CREATE OR REPLACE FUNCTION pg_get_select_composite(
  p_value type_pg_get_select_composite DEFAULT NULL
) RETURNS SETOF type_pg_get_select_composite AS $FUNCTION$
DECLARE
BEGIN
  RETURN NEXT p_value;
  RETURN NEXT p_value;
END;
$FUNCTION$ LANGUAGE plpgsql;

Rust

設定した値が2回になって返ってくるのを確認するRustのコードは以下のようななります。
Rust側ではmemory_sizeをNewTypeとして定義しています。
enumとdomainと型はリファレンスにある通りに書いています。

一旦全部のコードを紹介した後で利用の仕方について説明します。

composite.rs
use postgres_types::{FromSql, ToSql};

use crate::pool::PgClient;

const SQL: &str = r#"
SELECT
    ROW(t1.*)::type_pg_get_select_composite
FROM
    pg_get_select_composite(
        p_value := $1
    ) AS t1 
"#;

#[derive(Debug, ToSql, FromSql, PartialEq)]
#[postgres(name = "type_enum_os", rename_all = "snake_case")]
enum TypeEnumOs {
    Linux,
    MacOs,
    Windows
}

#[derive(Debug, ToSql, FromSql, PartialEq)]
#[postgres(name = "domain_integer", rename_all = "snake_case")]
struct DomainInteger(i64);

#[derive(Debug, ToSql, FromSql, PartialEq)]
#[postgres(transparent)]
struct MemorySizeUnit(i64);

#[derive(Debug, ToSql, FromSql, PartialEq)]
#[postgres(name = "type_pg_get_select_composite", rename_all = "snake_case")]
struct TypePgGetSelectComposite {
    os: TypeEnumOs,
    cpu_count: DomainInteger,
    memory_size: MemorySizeUnit,
}

pub(crate) async fn execute(pg_client: &PgClient) -> anyhow::Result<()> {
    let data = TypePgGetSelectComposite {
        os: TypeEnumOs::Linux,
        cpu_count: DomainInteger(2),
        memory_size: MemorySizeUnit(1024),
    };

    let rows: Vec<TypePgGetSelectComposite> = pg_client.query(SQL, &[&data]).await?
        .iter()
        .map(|row| row.get(0))
        .collect();

    assert_eq!(rows[0], data);
    assert_eq!(rows[1], data);
    println!("{:?}", rows[0]);

    Ok(())
}

SQLの呼び出し方に特徴があります。ROW()を使って行を1つのカラムに変換しています。これはRustのRow型から直接定義したStructに変換できないからです。変換できるのはカラムだけです。

const SQL: &str = r#"
SELECT
    ROW(t1.*)::type_pg_get_select_composite
FROM
    pg_get_select_composite(
        p_value := $1
    ) AS t1 
"#;

Rowから目的のstructに書き換える部分は以下になります。上記で説明した通り最初のカラムを目的のstructに変換しています。

let rows: Vec<TypePgGetSelectComposite> = pg_client.query(SQL, &[&data]).await?
        .iter()
        .map(|row| row.get(0))
        .collect();

まとめ

PostgreSQLで自分で定義した型とRustのstructやenumと相互に変換できるようになりました。
ストアードプロシージャーを作ると自分で戻る型を作ることが多いので、今回の内容が役に立つと思います。

Discussion