search
LoginSignup
0

More than 1 year has passed since last update.

posted at

updated at

続・PostgreSQL に Santa Clause(サンタ句)を実装する - good_behavior パラメータの追加 -

本記事は「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 にしました。

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としました。

src/backend/utils/misc/guc.c
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 句に指定された値の合計を最大とする乱数値を計算します。テーブルの各行は、その数だけ増やされて出力されることになります。

src/backend/parser/gram.y
/*
 * 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 が負の場合には、この関数は何も行を返さずに終了することになります。

src/backend/executor/nodeSeqscan.c
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_behaviorpg_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の内部状態を出力したり振る舞いをちょっと変えてみたいといった奇特な方がいましたら、ちょろっと自分でパラメータを追加して遊んでみるのはいかがでしょうか?


  1. 本当はクロスポストで前の記事を SRA Advent Calender の24日目記事として流用しようと思ったのですが、Qiita さんに「指定された記事は既に他のカレンダーに紐付けられています。」と怒られたので、急遽別のネタを拵えました。 

  2. まぁ、結局の所、自己申告なわけですが。 

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
What you can do with signing up
0