これはPostgreSQL Advent calendar 2023シリーズ2の8日目の記事です。

bannerプログラムは、UnixやUnixライクなシステムで提供されており、文字列をASCIIアートに変換してターミナル上に大きく出力プログラムとして有名です。これは普通のbannerプログラム。

$ banner hello

#     #  #######  #        #        #######
#     #  #        #        #        #     #
#     #  #        #        #        #     #
#######  #####    #        #        #     #
#     #  #        #        #        #     #
#     #  #        #        #        #     #
#     #  #######  #######  #######  #######

こちらの記事では、PostgreSQLでSQL関数を使ってバナーを表示する方法が紹介されています。

今回は一風変わったbannerプログラム、”block_banner“を書いてみました。

SQLを出力するbannerプログラム

block_bannerはC言語で書かれたプログラムで、標準出力にSQLを出力します。まずはビルドして実行してみます。-tにはテーブル名、-mには表示したい文字列を指定します。

$ gcc -o block_banner block_banner.c
$ ./block_banner -i -t test_table -m hello
drop table if exists test_table;
create table test_table ( c1 int4, c2 int4, c3 int4, c4 int4, c5 int4, c6 int4, c7 int4, c8 text, c9 text, c10 text, c11 text, c12 text, c13 text, c14 text,
c15 text, c16 text, c17 text, c18 text, c19 text, c20 text, c21 text, c22 text, c23 text, c24 text, c25 text, c26 text, c27 text, c28 text, c29 text, c30 text, c31 text, c32 text, c33 text, c34 text, c35 text, c36 text, c37 text, c38 text, c39 text, c40 text, c41 text, c42 text, c43 text, c44 text, c45 text, c46
text, c47 text, c48 text, c49 text, c50 text, c51 text, c52 text, c53 text, c54 text, c55 text, c56 text, c57 text, c58 text, c59 text, c60 text, c61 text, c62 text, c63 text, c64 text);
insert into test_table (c1, c2, c3, c4, c5, c6, c7, c8) values (0, 0, 0, 0, 0, 0, 0, repeat(chr(17), 32) ||repeat(chr(17), 256)|| repeat(chr(17), 32));
insert into test_table (c1, c2, c3, c4, c5, c6, c7, c8) values (0, 0, 0, 0, 0, 0, 0, repeat(chr(17), 32) ||repeat(chr(17), 256)|| repeat(chr(17), 32));
insert into test_table (c1, c2, c3, c4, c5, c6, c7, c8) values (0, 0, 0, 0, 0, 0, 0, repeat(chr(17), 32) ||repeat(chr(17), 256)|| repeat(chr(17), 32));
insert into test_table (c1, c2, c3, c4, c5, c6, c7, c8) values (0, 0, 0, 0, 0, 0, 0, repeat(chr(17), 32) ||repeat(chr(17), 256)|| repeat(chr(17), 32));
insert into test_table (c1, c2, c3, c4, c5, c6, c7, c8) values (0, 0, 0, 0, 0, 0, 0, repeat(chr(17), 32) ||repeat(chr(17), 256)|| repeat(chr(17), 32));
insert into test_table (c1, c2, c3, c4, c5, c6, c7, c8) values (0, 0, 0, 0, 0, 0, 0, repeat(chr(17), 32) ||repeat(chr(17), 256)|| repeat(chr(17), 32));
insert into test_table (c1, c2, c3, c4, c5, c6, c7, c8) values (0, 0, 0, 0, 0, 0, 0, repeat(chr(17), 32) ||repeat(chr(17), 256)|| repeat(chr(17), 32));
insert into test_table (c1, c2, c3, c4, c5, c6, c7, c8) values (0, 0, 0, 0, 0, 0, 0, repeat(chr(17), 32) ||repeat(chr(17), 256)|| repeat(chr(17), 32));
insert into test_table (c1, c2, c3, c4, c5, c6, c7, c8) values (0, 0, 0, 0, 0, 0, 0, repeat(chr(17), 32) ||repeat(chr(17), 256)|| repeat(chr(17), 32));
insert into test_table (c1, c2, c3, c4, c5, c6, c7, c8) values (0, 0, 0, 0, 0, 0, 0, repeat(chr(17), 32) ||repeat(chr(17), 256)|| repeat(chr(17), 32));
insert into test_table (c1, c2, c3, c4, c5, c6, c7, c8) values (0, 0, 0, 0, 0, 0, 0, repeat(chr(17), 32) ||repeat(chr(17), 256)|| repeat(chr(17), 32));
insert into test_table (c1, c2, c3, c4, c5, c6, c7, c8) values (0, 0, 0, 0, 0, 0, 0, repeat(chr(17), 32) ||repeat(chr(17), 256)|| repeat(chr(17), 32));
insert into test_table (c1, c2, c3, c4, c5, c6, c7, c8) values (0, 0, 0, 0, 0, 0, 0, repeat(chr(17), 32) ||repeat(chr(17), 256)|| repeat(chr(17), 32));
insert into test_table (c1, c2, c3, c4, c5, c6, c7, c8) values (0, 0, 0, 0, 0, 0, 0, repeat(chr(17), 32) ||repeat(chr(17), 256)|| repeat(chr(17), 32));
insert into test_table (c1, c2, c3, c4, c5, c6, c7, c8) values (0, 0, 0, 0, 0, 0, 0, repeat(chr(17), 32) ||repeat(chr(17), 256)|| repeat(chr(17), 32));
insert into test_table (c1, c2, c3, c4, c5, c6, c7, c8) values (0, 0, 0, 0, 0, 0, 0, repeat(chr(17), 32) ||repeat(chr(17), 256)|| repeat(chr(17), 32));
insert into test_table (c1, c2, c3, c4, c5, c6, c7, c8) values (0, 0, 0, 0, 0, 0, 0, repeat(chr(17), 32) ||  repeat(chr(17), 6)||  repeat(chr(35), 20)||  repeat(chr(17), 6)||  repeat(chr(17), 4)||  repeat(chr(35), 2)||  repeat(chr(17), 20)||  repeat(chr(35), 2)||  repeat(chr(17), 4)||  repeat(chr(17), 4)||  repeat(chr(35), 2)||  repeat(chr(17), 20)||  repeat(chr(35), 2)||  repeat(chr(17), 4)||  repeat(chr(17), 4)||  repeat(chr(35), 2)||  repeat(chr(17), 20)||  repeat(chr(35), 2)||  repeat(chr(17), 4)||  repeat(chr(17), 4)||  repeat(chr(35), 2)||  repeat(chr(17), 20)||  repeat(chr(35), 2)||  repeat(chr(17), 4)||  repeat(chr(17), 4)||  repeat(chr(35), 2)||  repeat(chr(17), 20)||  repeat(chr(35), 2)||  repeat(chr(17), 4)||  repeat(chr(17), 4)||  repeat(chr(35), 2)||  repeat(chr(17), 20)||  repeat(chr(35), 2)||  repeat(chr(17), 4)||  repeat(chr(17), 6)||  repeat(chr(35), 20)||  repeat(chr(17), 6)|| repeat(chr(17), 32));
insert into test_table (c1, c2, c3, c4, c5, c6, c7, c8) values (0, 0, 0, 0, 0, 0, 0, repeat(chr(17), 32) ||  repeat(chr(17), 4)||  repeat(chr(35), 2)||  repeat(chr(17), 26)||  repeat(chr(17), 4)||  repeat(chr(35), 2)||  repeat(chr(17), 26)||  repeat(chr(17), 4)||  repeat(chr(35), 2)||  repeat(chr(17), 26)||  repeat(chr(17), 4)||  repeat(chr(35), 2)||  repeat(chr(17), 26)||  repeat(chr(17), 4)||  repeat(chr(35), 2)||  repeat(chr(17), 26)||  repeat(chr(17), 4)||  repeat(chr(35), 2)||  repeat(chr(17), 26)||  repeat(chr(17), 4)||  repeat(chr(35), 2)||  repeat(chr(17), 26)||  repeat(chr(17), 4)||  repeat(chr(35), 24)||  repeat(chr(17), 4)|| repeat(chr(17), 32));
:
checkpoint;

なんだか大量のSQLが出力されました。このSQLをPostgreSQLで実行してみましょう。psqlにリダイレクトします。

$ ./block_banner -i -t test_table -m hello | psql
DROP TABLE
CREATE TABLE
INSERT
:

テーブルが作られデータが入りました。データが入ったテーブルを見てみます。

$ psql
=# select * from test_table limit 1;
 c1 | c2 | c3 | c4 | c5 | c6 | c7 |                                                     c8                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | c9 | c10 | c11 | c12 | c13 | c14 | c15 | c16 | c17 | c18 | c19 | c20 | c21 | c22 | c23 | c24 | c25 | c26 | c27 | c28 | c29 | c30 | c31 | c32 | c33 | c34 | c35 | c36 | c37 | c38 | c39 | c40 | c41 | c42 | c43 | c44 | c45 | c46 | c47 | c48 | c49 | c50 | c51 | c52 | c53 | c54 | c55 | c56 | c57 | c58 | c59 | c60 | c61 | c62 | c63 | c64
----+----+----+----+----+----+----+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----
  0 |  0 |  0 |  0 |  0 |  0 |  0 | \x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11\x11 |    |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |     |
(1 row)

テーブルにはデータが入っているようですが、なにかはわかりません。では”バナー”はどこに出力されているのでしょうか?

バナーはどこ?

テーブルの”見方”をちょっと変えるとわかります。以下のコマンドで、当該テーブルのファイルを見てみると・・・

$ xxd -u -c 32   $(psql -d postgres -Atq -X -c "select current_setting('data_directory') || '/' || pg_relation_filepath('test_table')")'
00000000: 0000 0000 D875 7B01 C001 0000 6C00 8000 0020 0420 0000 0000 809E 0003 009D 0003  .....u{.....l.... . ............
00000020: 809B 0003 009A 0003 8098 0003 0097 0003 8095 0003 0094 0003 8092 0003 0091 0003  ................................
00000040: 808F 0003 008E 0003 808C 0003 008B 0003 8089 0003 0088 0003 8086 0003 0085 0003  ................................
00000060: 8083 0003 0082 0003 8080 0003 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000  ................................
00000080: F502 0000 0000 0000 0000 0000 0000 0000 1500 4000 0308 20FF 0000 0000 0000 0000  ..................@... .........
000000a0: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1005 0000  ................................
000000c0: 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ................................
000000e0: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 2323 1111 1111  ....##....................##....
00000100: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 2323 1111 1111  ....##....................##....
00000120: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 2323 1111 1111  ....##....................##....
00000140: 1111 1111 2323 2323 2323 2323 2323 2323 2323 2323 2323 2323 2323 2323 1111 1111  ....########################....
00000160: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 2323 1111 1111  ....##....................##....
00000180: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 2323 1111 1111  ....##....................##....
000001a0: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 2323 1111 1111  ....##....................##....
000001c0: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 2323 1111 1111  ....##....................##....
000001e0: 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ................................
00000200: F402 0000 0000 0000 0000 0000 0000 0000 1400 4000 0308 20FF 0000 0000 0000 0000  ..................@... .........
00000220: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1005 0000  ................................
00000240: 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ................................
00000260: 1111 1111 2323 2323 2323 2323 2323 2323 2323 2323 2323 2323 2323 2323 1111 1111  ....########################....
00000280: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ....##..........................
000002a0: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ....##..........................
000002c0: 1111 1111 2323 2323 2323 2323 2323 2323 2323 2323 2323 2323 2323 2323 1111 1111  ....########################....
000002e0: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ....##..........................
00000300: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ....##..........................
00000320: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ....##..........................
00000340: 1111 1111 2323 2323 2323 2323 2323 2323 2323 2323 2323 2323 2323 2323 1111 1111  ....########################....
00000360: 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ................................
00000380: F302 0000 0000 0000 0000 0000 0000 0000 1300 4000 0308 20FF 0000 0000 0000 0000  ..................@... .........
000003a0: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1005 0000  ................................
000003c0: 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ................................
000003e0: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ....##..........................
00000400: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ....##..........................
00000420: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ....##..........................
00000440: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ....##..........................
00000460: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ....##..........................
00000480: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ....##..........................
000004a0: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ....##..........................
000004c0: 1111 1111 2323 2323 2323 2323 2323 2323 2323 2323 2323 2323 2323 2323 1111 1111  ....########################....
000004e0: 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ................................
00000500: F202 0000 0000 0000 0000 0000 0000 0000 1200 4000 0308 20FF 0000 0000 0000 0000  ..................@... .........
00000520: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1005 0000  ................................
00000540: 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ................................
00000560: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ....##..........................
00000580: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ....##..........................
000005a0: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ....##..........................
000005c0: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ....##..........................
000005e0: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ....##..........................
00000600: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ....##..........................
00000620: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ....##..........................
00000640: 1111 1111 2323 2323 2323 2323 2323 2323 2323 2323 2323 2323 2323 2323 1111 1111  ....########################....
00000660: 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ................................
00000680: F102 0000 0000 0000 0000 0000 0000 0000 1100 4000 0308 20FF 0000 0000 0000 0000  ..................@... .........
000006a0: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1005 0000  ................................
000006c0: 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ................................
000006e0: 1111 1111 1111 2323 2323 2323 2323 2323 2323 2323 2323 2323 2323 1111 1111 1111  ......####################......
00000700: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 2323 1111 1111  ....##....................##....
00000720: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 2323 1111 1111  ....##....................##....
00000740: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 2323 1111 1111  ....##....................##....
00000760: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 2323 1111 1111  ....##....................##....
00000780: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 2323 1111 1111  ....##....................##....
000007a0: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 2323 1111 1111  ....##....................##....
000007c0: 1111 1111 1111 2323 2323 2323 2323 2323 2323 2323 2323 2323 2323 1111 1111 1111  ......####################......
000007e0: 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ................................
00000800: F002 0000 0000 0000 0000 0000 0000 0000 1000 4000 0308 20FF 0000 0000 0000 0000  ..................@... .........
00000820: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1005 0000  ................................
:

バナーが表示されました!実行したxxdでは、PostgreSQLのテーブルのファイル(バイナリファイル)を直接見ることで指定したバナーが見れます。block_bannerで出力されたSQLは、指定されたバナーをバイナリファイル上に出現させるようなSQLを発行していたのでした。

xxdの出力をゆっくり見たい人はawk '{print $0; system("sleep 0.05")}'にリダイレクトしてみてください。

コードはGithubに公開しています。

block_bannerでは何をしているか?

仕組みが気になる方のためにblock_bannerで何をしているかを少し紹介します。

といってもやっていることは単純です。PostgreSQLは1つテーブルデータを1つ以上のファイルに格納しているので、そのファイルをxxd -c 32(つまり1行に32 bytes分のデータが出力される)で見たときに文字ができるようINSERTでデータを入れているだけです。

PostgreSQLはテーブルを8kBのブロック(またはページ)で区切っています。各ページの先頭にはページヘッダが格納されており、タプルデータはページの末尾から格納されていきます(公式ドキュメントの参照)。

そして、各タプルには23 bytesのタプルヘッダがあり、その後にnull bitmap(各列がNULLであるかどうかを1 bitで表す)が続き、そして実際のデータが続きます。データ部分をきっちり左端から始めるたいのですが、タプルヘッダだけではxxd -c 32の1行分の出力埋めることはできませんそのため、残りはnull bitmapで埋めています。テーブルに64列持たせNULLデータをもたせることで8 byteのnull bitmapが完成です。

ASCIIアートを書きたいデータをtext型の列に格納するのですが、データの先頭にその長さを表す4 bytesのヘッダを考慮する必要があります(ASCIIアートのデータを左端から始めるために)。なので、先頭7列はint4型で、0を入れます。そうすることで、0が28 bytes分続き、そしてtextのヘッダが4 byte続くので、1行がピッタリ埋まります。

00000080: F502 0000 0000 0000 0000 0000 0000 0000 1500 4000 0308 20FF 0000 0000 0000 0000  ..................@... ......... // tuple header + null bitmap
000000a0: 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 1005 0000  ................................ // 7 int4 values + text data header (4 bytes)
000000c0: 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ................................ // text data...
000000e0: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 2323 1111 1111  ....##....................##....
00000100: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 2323 1111 1111  ....##....................##....
00000120: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 2323 1111 1111  ....##....................##....
00000140: 1111 1111 2323 2323 2323 2323 2323 2323 2323 2323 2323 2323 2323 2323 1111 1111  ....########################....
00000160: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 2323 1111 1111  ....##....................##....
00000180: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 2323 1111 1111  ....##....................##....
000001a0: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 2323 1111 1111  ....##....................##....
000001c0: 1111 1111 2323 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 2323 1111 1111  ....##....................##....
000001e0: 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111  ................................

ASCIアート用のデータは、下のようなデータを元にいい感じにSQLをtext型でデータを入れていきます。

    {
        "........#.......",
        ".......#.#......",
        "......#...#.....",
        ".....#.....#....",
        "....#########...",
        "...#.........#..",
        "..#...........#.",
        ".#.............#"
    },

あとは、タプルは下から埋まっていくこと、そして、1ブロックに入る文字数を考慮しながらINSERT文を生成します。

おわりに

ということで、年末に全く役に立たないプログラムを書いてみました。

PostgreSQL開発しているときに、テーブルのバイナリファイルを見る機会が多く、それが退屈だったので「たまたま開いたテーブルのファイルにメッセージが載っていたら面白いな」と思ったのがきっかけです。実際に作ってみると意外と面白く、タプルサイズの計算方法など勉強になることも多くありました。ぜひblock_bannerを使ってPostgreSQLのテーブルファイルに好きなメッセージを描いてみてください!