本記事は「SRA Advent Calendar 2020」の24日目です。
はじめに
この記事は「PostgreSQL に Santa Clause(サンタ句)を実装する」の続編です1。
前の記事は「SELECT
文の構文を拡張して、PostgreSQL に新しい機能 SANTA
句を追加してみる」という内容で、PostgreSQLの内部の処理の流れに沿って具体的な実装の流れを説明しました。
この記事では、前の記事で追加したSANTA
句の振る舞いに影響を与える good_behavior
という設定パラメータを追加してみます。
前提
前の記事の内容を踏まえていますので、先にそちらに目を通していただかないと意味がわからないと思います。
おさらい
一応、前の記事で作成した「SANTA
句(SANTA clause)」とはどんな機能だったのかをおさらいしておきます。
これは、今日がクリスマス・イブということで、それにあやかって作成した機能です。SELECT
文の中でSANTA
句を使うと、テーブルから取得された各行の数を増やすことができます。実際に何倍になるかはSANTA
句に指定した数字を上限にランダムに決まります。
例えば以下のようなテーブルがある場合:
santa=# SELECT * FROM box ;
present
-----------
chocolate
book
game
(3 rows)
SELECT
文の最後にSANTA 5
を付けると、サンタさんからのプレゼントで最大で行数が5倍に増えます。
santa=# SELECT * FROM box SANTA 5;
INFO: Merry Christmas! Santa will give you 2 times rows as a present!!
present
-----------
chocolate
chocolate
book
book
game
game
(6 rows)
santa=# SELECT * FROM box SANTA 5;
INFO: Merry Christmas! Santa will give you 5 times rows as a present!!
present
-----------
chocolate
chocolate
chocolate
chocolate
chocolate
book
book
book
book
book
game
game
game
game
game
(15 rows)
ただし、SANTA
句を指定しても行数が増えない場合もあります。今年は悪い子だったからサンタさんが来なかったのかな?
santa=# SELECT * FROM box SANTA 5;
INFO: Oh, were you a bad boy this year? Santa will not bring you any present on Christmas...
present
-----------
chocolate
book
game
(3 rows)
どんな設定を追加するか
さて、すでに実装済のSANTA
句には1つ問題があります。もらえるプレゼントの数(増やされる行数)が完全にランダムなのです。サンタさんも多忙ですからプレゼントの配分にムラがあるのは致し方がないとはいえ、やはり今年良い子だった人にはその分豪華なプレゼントを贈りたくなるものです。
サンタさんからプレゼントがもらえなかった理由も、あなたが悪い子だったからではなく単に「運の問題」です。それではあんまりなので、今年積んだ善行(good behavior)でバイアスをかけて、良い子は少しでもプレゼントをもらえる確率を増やせるようにしましょう。
ついでに、今年悪い子だった人からはすべての行を取り上げることにしようと思います。
ということで、今回追加する設定項目 good_behavior
には「今年どれだけよい振る舞いをしたか」を整数値で指定できるようにします2。この値が大きい場合には、SANTA
句によって貰えるプレゼントの数(増やされる行数)が多くなるようにします。また、この値がマイナスの場合(かつ SANTA
句がある場合)には、テーブルから一切の行を出力できないようにします。
パラメータの追加
PostgreSQLには様々な設定パラメータがありますが、内部ではこれらは GUC(Grand Unified Configuration)と呼ばれています。
実質的には GUC はグローバル変数です。なので、新しいグローバル変数 good_behavior
を追加しましょう。テーブルからの行の出力に影響をあたえるパラメータなので、追加するファイルはテーブルアクセスメソッド(Table Access Method)のコードが書かれている src/backend/access/table/tableam.c にしました。
/* GUC variables */
char *default_table_access_method = DEFAULT_TABLE_ACCESS_METHOD;
bool synchronize_seqscans = true;
+int good_behavior = 0;
GUCの詳細は src/backend/utils/misc/guc.c の中で定義されています。good_behavior
は整数値のパラメータなので以下のエントリを ConfigureNamesInt
配列にエントリを追加しましょう。
詳細の説明は省きますが、今回は設定ファイル postgresql.conf の中で記述する他に、SET
コマンドでも値を変更できるように PGC_USERSET
コンテキストを使用します。また、初期値は0、最小値は-1、最大値は10としました。
static struct config_int ConfigureNamesInt[] =
{
(中略)
{
+ {"good_behavior", PGC_USERSET, DEVELOPER_OPTIONS,
+ gettext_noop("How good your behavior was this year"),
+ NULL
+ },
+ &good_behavior,
+ 0, -1, 10,
+ NULL, NULL, NULL
+ },
+
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL, NULL
}
};
構文解析処理の修正
次に構文解析時の処理を修正します。構文解析については前の記事で説明しています。
good_behavior
が負値の場合は、INFO
メッセージを出力して、SANTA
句の解析結果をダミー値の 1 としています。このダミー値は「SANTA
句が使用されていること」をエクゼキュータノードに伝える役割があります。
good_behavior
が0以上の場合は、これと SANTA
句に指定された値の合計を最大とする乱数値を計算します。テーブルの各行は、その数だけ増やされて出力されることになります。
/*
* Santa Definitions
*/
santa_clause:
SANTA Iconst
{
+ if (good_behavior < 0)
+ {
+ ereport(INFO, errmsg("You were a bad boy, so you will lose all your rows. What a pity!"));
+ $$ = 1; /* dummy */
+ }
- if ($2 > 1)
+ else if ($2 > 1)
{
- int santa = random() % $2 + 1;
+ int santa = random() % ($2 + good_behavior) + 1;
if (santa > 1)
ereport(INFO, errmsg("Merry Christmas! Santa will give you %d times rows as a present!!", santa));
else
ereport(INFO, errmsg("Oh, were you a bad boy this year? Santa will not bring you any present on Christmas..."));
$$ = santa;
}
else
$$ = 0;
}
| /*EMPTY*/ { $$ = 0; }
;
エクゼキュータの修正
最後にエクゼキュータのコードを修正します。前回の記事と同様、ここではシーケンシャルスキャン(SeqScan)の場合のみを見ていきます。
SeqScan で「次の行を取得」する処理を行う SeqNext
関数の先頭に以下のコードを追加します。NULL
を返すと「次の行は存在しない = 全ての行を出力済」という意味になるので、SANTA
句が使われていて、かつ good_behavior
が負の場合には、この関数は何も行を返さずに終了することになります。
static TupleTableSlot *
SeqNext(SeqScanState *node)
{
TableScanDesc scandesc;
EState *estate;
ScanDirection direction;
TupleTableSlot *slot;
+ /* you were a bad boy! */
+ if (node->ss.ss_santa > 0 && good_behavior < 0)
+ return NULL;
他のスキャン(IndexScan、IndexOnlyScan、BitmapHeapScan、SampleScan、TiScan)のエクゼキュータのコードについても同様の修正を行います。
テーブル行を増やすコードは前回のまま手を加える必要はないので、これで good_behavior
パラメータの追加は完了です。
前回の実装からの差分全体は以下の通りとなりました。
前回からの差分(156行)
diff --git a/src/backend/access/table/tableam.c b/src/backend/access/table/tableam.c
index 6438c45716..fbd2ba481a 100644
--- a/src/backend/access/table/tableam.c
+++ b/src/backend/access/table/tableam.c
@@ -47,6 +47,7 @@
/* GUC variables */
char *default_table_access_method = DEFAULT_TABLE_ACCESS_METHOD;
bool synchronize_seqscans = true;
+int good_behavior = 0;
/* ----------------------------------------------------------------------------
diff --git a/src/backend/executor/nodeBitmapHeapscan.c b/src/backend/executor/nodeBitmapHeapscan.c
index 6c023177e7..192be5eb7c 100644
--- a/src/backend/executor/nodeBitmapHeapscan.c
+++ b/src/backend/executor/nodeBitmapHeapscan.c
@@ -81,6 +81,10 @@ BitmapHeapNext(BitmapHeapScanState *node)
ParallelBitmapHeapState *pstate = node->pstate;
dsa_area *dsa = node->ss.ps.state->es_query_dsa;
+ /* you were a bad boy! */
+ if (node->ss.ss_santa > 0 && good_behavior < 0)
+ return NULL;
+
/*
* extract necessary information from index scan node
*/
diff --git a/src/backend/executor/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c
index b4a4f5392a..feb8d56d45 100644
--- a/src/backend/executor/nodeIndexonlyscan.c
+++ b/src/backend/executor/nodeIndexonlyscan.c
@@ -66,6 +66,10 @@ IndexOnlyNext(IndexOnlyScanState *node)
TupleTableSlot *slot;
ItemPointer tid;
+ /* you were a bad boy! */
+ if (node->ss.ss_santa > 0 && good_behavior < 0)
+ return NULL;
+
/*
* extract necessary information from index scan node
*/
diff --git a/src/backend/executor/nodeIndexscan.c b/src/backend/executor/nodeIndexscan.c
index 6dd12248ab..1cd55122b3 100644
--- a/src/backend/executor/nodeIndexscan.c
+++ b/src/backend/executor/nodeIndexscan.c
@@ -86,6 +86,10 @@ IndexNext(IndexScanState *node)
IndexScanDesc scandesc;
TupleTableSlot *slot;
+ /* you were a bad boy! */
+ if (node->ss.ss_santa > 0 && good_behavior < 0)
+ return NULL;
+
/*
* extract necessary information from index scan node
*/
diff --git a/src/backend/executor/nodeSamplescan.c b/src/backend/executor/nodeSamplescan.c
index 83c073f07e..b8b4942473 100644
--- a/src/backend/executor/nodeSamplescan.c
+++ b/src/backend/executor/nodeSamplescan.c
@@ -339,6 +339,10 @@ tablesample_getnext(SampleScanState *scanstate)
TableScanDesc scan = scanstate->ss.ss_currentScanDesc;
TupleTableSlot *slot = scanstate->ss.ss_ScanTupleSlot;
+ /* you were a bad boy! */
+ if (scanstate->ss.ss_santa > 0 && good_behavior < 0)
+ return NULL;
+
/* If SANTA clause exists, repeate each tuple "santa" times. */
if (scanstate->donetuples > 0 && scanstate->ss.ss_santa > 1)
{
diff --git a/src/backend/executor/nodeSeqscan.c b/src/backend/executor/nodeSeqscan.c
index 69803b1982..904eb9efd6 100644
--- a/src/backend/executor/nodeSeqscan.c
+++ b/src/backend/executor/nodeSeqscan.c
@@ -54,6 +54,10 @@ SeqNext(SeqScanState *node)
ScanDirection direction;
TupleTableSlot *slot;
+ /* you were a bad boy! */
+ if (node->ss.ss_santa > 0 && good_behavior < 0)
+ return NULL;
+
/*
* get information from the estate and scan state
*/
diff --git a/src/backend/executor/nodeTidscan.c b/src/backend/executor/nodeTidscan.c
index 66f3fdc35b..1fef6de2bc 100644
--- a/src/backend/executor/nodeTidscan.c
+++ b/src/backend/executor/nodeTidscan.c
@@ -319,6 +319,10 @@ TidNext(TidScanState *node)
int numTids;
bool bBackward;
+ /* you were a bad boy! */
+ if (node->ss.ss_santa > 0 && good_behavior < 0)
+ return NULL;
+
/*
* extract necessary information from tid scan node
*/
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 9303dc607f..5a69a66e2d 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -14141,9 +14141,14 @@ window_specification: '(' opt_existing_window_name opt_partition_clause
santa_clause:
SANTA Iconst
{
- if ($2 > 1)
+ if (good_behavior < 0)
{
- int santa = random() % $2 + 1;
+ ereport(INFO, errmsg("You were a bad boy, so you will lose all your rows. What a pity!"));
+ $$ = 1; /* dummy */
+ }
+ else if ($2 > 1)
+ {
+ int santa = random() % ($2 + good_behavior) + 1;
if (santa > 1)
ereport(INFO, errmsg("Merry Christmas! Santa will give you %d times rows as a present!!", santa));
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index dabcbb0736..27fd7e5632 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -3389,6 +3389,16 @@ static struct config_int ConfigureNamesInt[] =
check_huge_page_size, NULL, NULL
},
+ {
+ {"good_behavior", PGC_USERSET, DEVELOPER_OPTIONS,
+ gettext_noop("How good your behavior was this year"),
+ NULL
+ },
+ &good_behavior,
+ 0, -1, 10,
+ NULL, NULL, NULL
+ },
+
/* End-of-list marker */
{
{NULL, 0, 0, NULL, NULL}, NULL, 0, 0, 0, NULL, NULL, NULL
diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h
index 387eb34a61..8a616e151b 100644
--- a/src/include/access/tableam.h
+++ b/src/include/access/tableam.h
@@ -30,6 +30,7 @@
/* GUCs */
extern char *default_table_access_method;
extern bool synchronize_seqscans;
+extern int good_behavior;
struct BulkInsertStateData;
動作結果
追加した good_behavior
は pg_settings
システムビューで確認することができます。
santa=# SELECT * FROM pg_settings WHERE name = 'good_behavior';
-[ RECORD 1 ]---+-------------------------------------
name | good_behavior
setting | 0
unit |
category | Developer Options
short_desc | How good your behavior was this year
extra_desc |
context | user
vartype | integer
source | default
min_val | -1
max_val | 10
enumvals |
boot_val | 0
reset_val | 0
sourcefile |
sourceline |
pending_restart | f
この値を1以上の値に設定しておくと、SANTA
句で指定した以上の数の行を貰えることがあります。普段の行いが良いと、良いことがありますね!
santa=# SET good_behavior TO 3;
SET
santa=# SELECT * FROM box SANTA 5;
INFO: Merry Christmas! Santa will give you 7 times rows as a present!!
present
-----------
chocolate
chocolate
chocolate
chocolate
chocolate
chocolate
chocolate
book
book
book
book
book
book
book
game
game
game
game
game
game
game
(21 rows)
ただし、普段の行いが悪いとテーブルから何も得られません。悲しい。。。
santa=# SET good_behavior TO -1;
SET
santa=# SELECT * FROM box SANTA 5;
INFO: You were a bad boy, so you will lose all your rows. What a pity!
present
---------
(0 rows)
こういう時はサンタに頼らず、素直に自分で行を取得すれば大丈夫です。来年は良い子でがんばりましょう。
santa=# SELECT * FROM box;
present
-----------
chocolate
book
game
(3 rows)
おわりに
PostgreSQL の SELECT
文を拡張して新しい句を追加した前回に続いて、今回は新しい設定パラメータを追加してみました。
パラメータの追加自体は思ったほど難しくないと思われたのではないでしょうか。PostgreSQLの内部状態を出力したり振る舞いをちょっと変えてみたいといった奇特な方がいましたら、ちょろっと自分でパラメータを追加して遊んでみるのはいかがでしょうか?