LoginSignup
15
5

More than 5 years have passed since last update.

PostgreSQLの文字コード変換で Hello World!!

Posted at

これは、PostgreSQL Advent Calendar 2018の15日目の記事です。

はじめに

どんなSQLをPostgreSQLに投げても Hello World!! と結果を返してほしい場面が時にはあると思います。今回は、PostgreSQLの文字コード変換の仕組みを使って、どんなSQLを投げてもSELECT 'Hello World!!'を代わりに実行させてみたいと思います。

そもそもPostgreSQLの文字コード変換とは

PostgreSQLは、クライアントエンコーディングとデータベースエンコーディングが異なるとき、クライアントとデータベースの間でやりとりするSQL文やSQL実行結果について自動的に文字コード変換を行います。例えば、クライアントエンコーディングがSJIS、データベースエンコーディングがUTF-8の場合、SQL文中のSJIS「ア(0x8341)」はPostgreSQLによってUTF-8「ア(U+30A2)」に変換されます。

-- クライアントエンコーディングをSJISに設定変更して、SJIS「ア(0x8341)」をtestテーブルに格納する
=# CREATE TABLE test (chr TEXT);
=# SET client_encoding TO sjis;
=# \copy test from program 'echo "8341" | xxd -p -r'

-- クライアントエンコーディングをUTF-8に戻して、testテーブルに格納された文字がUTF-8「ア(U+30A2)」であることを確認する
=# SET client_encoding TO utf8;
=# SELECT chr, to_hex(ascii(chr)) FROM test;
 chr | to_hex 
-----+--------
   | 30a2
(1 row)

この文字コード変換のロジックはPostgreSQLがデフォルトで提供しますが、CREATE CONVERSIONを使って独自に定義することも可能です。今回は、この文字コード変換の独自定義によりSQLを丸ごとSELECT 'Hello World!!'に書き換えます。

独自文字コード変換の定義

まずはSQLを丸ごとSELECT 'Hello World!!'に書き換えるC言語関数を作成します。

#include "postgres.h"
#include "funcapi.h"

PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(hello_world);

Datum
hello_world(PG_FUNCTION_ARGS)
{
    unsigned char *dest = (unsigned char *) PG_GETARG_CSTRING(3);

    /* 文字コード変換後の文字列として Hello World のSQLを設定 */
    strcpy((char *)dest, "SELECT 'Hello World!!'");
    PG_RETURN_VOID();
}

C言語関数をコンパイルします。以下では、PostgreSQLは/pgsqlディレクトリにインストールしている前提です。

gcc -fpic -c hello_world.c -I/pgsql/include/server/

# Linuxの場合
gcc -shared -o /pgsql/lib/hello_world.so hello_world.o

# Macの場合
gcc -bundle -bundle_loader /pgsql/bin/postgres -o /pgsql/lib/hello_world.so hello_world.o

CREATE FUNCTIONでC言語関数をPostgreSQLに登録します。文字コード変換に使える関数の形式(引数や戻り値の型)は決まっているため、下記のとおり指定する必要があります。

CREATE FUNCTION hello_world(integer, integer, cstring, internal, integer) RETURNS void AS 'hello_world' LANGUAGE c STRICT VOLATILE;

登録した関数を使う独自の文字コード変換をCREATE CONVERSIONで定義します。今回は、SJISからUTF-8への文字コード変換として定義することにします。

CREATE CONVERSION hello_world FOR 'sjis' TO 'utf8' FROM hello_world;

また、この独自の文字コード変換を利用できるように、pg_conversionカタログのcondefaultカラムを直接更新します。更新後は、現在のセッションからログアウトします。次回のログインから独自文字コード変換をSJISからUTF-8へのデフォルトの文字コード変換として利用できます。

BEGIN;
UPDATE pg_conversion SET condefault = 'f' WHERE conname = 'sjis_to_utf8';
UPDATE pg_conversion SET condefault = 't' WHERE conname = 'hello_world';
COMMIT;

-- 現在のセッションをログアウト
\q

これで準備は整いました。

文字コード変換によるHello World!!

では、文字コード変換によって Hello World!! できるか試してみます。

まずはクライアントエンコーディングがSJISでないときは、投げられたSQLがそのまま実行されることを確認します。

=# SELECT name FROM pg_settings ORDER BY name DESC LIMIT 1;                                                                                                                                                 name        
--------------------
 zero_damaged_pages
(1 row)

=# VACUUM ANALYZE;
VACUUM

次に、クライアントエンコーディングをSJISに設定変更すると、独自の文字コード変換が動き、Hello World!! が結果として返ってくることを確認します。

=# SET client_encoding TO sjis;
SET
=# SELECT name FROM pg_settings ORDER BY name DESC LIMIT 1;
   ?column?    
---------------
 Hello World!!
(1 row)

=# VACUUM ANALYZE;
   ?column?    
---------------
 Hello World!!
(1 row)

というわけで無事に文字コード変換で Hello World!! できていることが確認できました!!

参考

CREATE CONVERSIONは、本来は、PostgreSQLがデフォルトでは提供していない文字コード変換を実現するためのものです。まじめに独自の文字コード変換を定義する必要がある場合は、pg_fallback_utf8_to_euc_jpを参考にしてもらえればと思います。

15
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
5