PostgreSQLへの新規構文の追加


2005.01.17 に行った、PostgreSQL しくみ分科会 第8回勉強会のメモです。

psql などから

template1=# shikumi; 

と入力すると 

shikumi executed 

と表示されるプログラムを作成しました。

■ はじめに

PostgreSQLでのSQL処理の大まかな流れは次のようになっています。

  1. スキャン&パーズ、
  2. アナライズ(意味解析)
  3. リライト
  4. オプティマイズ
  5. 実行

ただし、これが完全に実行されるのはDML系のSQLだけで、ほとんどのDDLでは1,2,5 のところが実行されます。VACUUMのようなユーティリティ系のコマンドでは、1,5 という流れになり、今回は1,5 で十分なので、1,5 のところに手を加えていきます。

◇ バージョン

しくみ分科会では、PostgreSQL 8.0.0 RC5 を使用している人が多かったようですが、井久保は RC3 までしかPCに入れていなかったので、RC3 を使ってやりました。たぶん、ほとんど差はないと思います。


■ STEP 1 「パーザに構文を追加して、elogで文字列を表示させる。

やること

  1. スキャナ(字句解析器)に、shikumi という文字列をキーワードとして登録し、トークンとして認識させる。
  2. パーザ(構文解析器)に、shikumi の文法を定義する。
    ※ とりあえず、STEP 1 では、パーザから直接文字列を出力する。

◇ スキャナへのキーワード追加

parser/keywords.c にキーワードの追加を行います。

次のように、アルファベット順にソートされて書いてあるので、shikumi のキーワードを s から始まるところに追加を行います。キーワードを適当なところに追加した人がいたようですが、そうすると、キーワードを認識してくれなかったようです。どうやら、キーワードの検索に2分探索が使われているようですので、正しい場所に追加しましょう。

/*
 * List of (keyword-name, keyword-token-value) pairs.
 *
 * !!WARNING!!: This list must be sorted, because binary
 *       search is used to locate entries.
 */
static const ScanKeyword ScanKeywords[] = {
    /* name, value */
    {"abort", ABORT_P},
    {"absolute", ABSOLUTE_P},
    {"access", ACCESS},
          :
          :

次のように追加しました。

    {"share", SHARE},
    {"shikumi", SHIKUMI},
    {"show", SHOW},

左側の "shikumi" がパターンマッチするときの文字列。右の SHIKUMI はトークンとしてパーザに渡すための定数です。
したがって、次に定数 SHIKUMI を登録します。

ソースコード中で、パーザのトークン用の定数が定義されている場所を探すと、include/parser/parse.h に登録してありそうです。
include/parser/parse.hは、次のようになっています。

enum yytokentype {
     ABORT_P = 258,
     ABSOLUTE_P = 259,
     ACCESS = 260,
     ACTION = 261,
     ADD = 262,
     AFTER = 263,
     AGGREGATE = 264,

しかし、このファイルは、parser/gram.y から自動生成されるため、直接編集しても次回コンパイル時に消えてしまいます。
ということで、次にparser/gram.y を編集します。

次のように %token <keyword> の定義してあるところを探します。

/* ordinary key words in alphabetical order */
%token <keyword> ABORT_P ABSOLUTE_P ACCESS ACTION ADD AFTER
    AGGREGATE ALL ALSO ALTER ANALYSE ANALYZE AND ANY ARRAY AS ASC

ここに、先ほどのSHIKUMI という定数を追加します。アルファベット順に追加しろと書いてあるので、次のように追加しました。

    SAVEPOINT SCHEMA SCROLL SECOND_P SECURITY SELECT SEQUENCE
    SERIALIZABLE SESSION SESSION_USER SET SETOF SHARE SHIKUMI
    SHOW SIMILAR SIMPLE SMALLINT SOME STABLE START STATEMENT
    STATISTICS STDIN STDOUT STORAGE STRICT_P SUBSTRING SYSID

これで、キーワードは追加がでしました。しかし、スキャナは shikumi をトークンとして拾っているはずですが、キーワードの登録の有無にかかわらず、次のような構文エラーになってしまいます。

template1=# shikumi;
ERROR:  syntax error at or near "shikumi" at character 1
LINE 1: shikumi;

これは、キーワード登録されていない場合、IDENT というトークン、つまり、名前などの名称になっている文字列として、スキャナがパーザに渡していました。どちらにしても、構文として SHIKUMI または IDENT から始まるのはおかしいということで、同じ構文エラーになってしまいます。
ということで、構文の追加に進みましょう。 

◇ パーザへの構文追加

構文の追加は、parser/gram.y を編集していきます。

まず、先頭が %% の行を探しましょう。ここから後ろが構文の定義です。そして、再び %% の行で構文定義が終わります。

%left       JOIN UNIONJOIN CROSS LEFT FULL RIGHT INNER_P NATURAL
%%

/*
 *  Handle comment-only lines, and ;; SELECT * FROM pg_class ;;;
 *  psql already handles such cases, but other interfaces don't.
 *  bjm 1999/10/05
 */
stmtblock:  stmtmulti                               { parsetree = $1; }
        ;

/* the thrashing around here is to discard "empty" statements... */
stmtmulti:  stmtmulti ';' stmt
                { if ($3 != NULL)
                    $$ = lappend($1, $3);
                  else
                    $$ = $1;
                }
            | stmt
                    { if ($1 != NULL)
                        $$ = list_make1($1);
                      else
                        $$ = NIL;
                    }
        ;

stmt :
            AlterDatabaseSetStmt
            | AlterDomainStmt
            | AlterGroupStmt
            | AlterOwnerStmt
            | AlterSeqStmt
            | AlterTableStmt

とりあえず、構文定義の先頭の部分です。%% の前までの %left は構文解析のときに右辺があることを知らせるための定義です。

  1. stmtblock から始まっています。つまり、全体は stmtblock という大きな定義になります。
  2. stmtblock は、stmtmulti という定義で再定義されることを示してあります。そして、 { } の中が stmtblock としての処理内容です。ここでは、{ parsetree = $1; } と書いてあるので、stmtmulti での実行結果をparsetree という変数に代入しなさいいう意味です。
  3. 次に、stmtmulti の定義ですが、「stmtmulti ';' stmt」または 「stmt」という定義になっています。前者では、stmtmulti が再帰的に使われています。
    ここは、ようするに、「stmt」 が1つか、または、「stmt;stmt;stmt; … ; stmt」 とstmt が並んでいる状態を作っています。
    { } の中を読むと、まず、stmtが1つだった場合、stmtの解析結果が、NULL の場合は戻り値 $$ は NIL を返して( $$ = NIL; )、stmt が何かの正しい構文だった場合、リストを生成して呼び出し元に返すように書いてあります( $$ = list_make1($1); )。
    次に、「stmtmulti ';' stmt」 の場合は、stmtmulti と stmt を再帰的にリストに追加していくようになっています。

ここまでは、複数のSQLステートメントを並べえて書くための定義だということがわかりました。要するに新しい構文1つは、stmt の定義に追加すればいいということです。

stmtの定義を見てみると、SQL で見慣れた ALTER XXX あたりの定義らしきものから始まっています。

ここに、ShikumiStmt という構文(ステートメント)を追加することにします。これで、SQL分の1つとしての shikumi 文の分岐ができるようになります。

            | SelectStmt
            | ShikumiStmt
            | TransactionStmt

ShikumiStmt というノードの型を追加する必要があるので、gram.yの上のほうに戻って、%type <node> の定義の末尾に追加しておきます。

%type <node>    stmt schema_stmt
        AlterDatabaseSetStmt AlterDomainStmt AlterGroupStmt AlterOwnerStmt
        AlterSeqStmt AlterTableStmt AlterUserStmt AlterUserSetStmt
                       :
                       :
        DeallocateStmt PrepareStmt ExecuteStmt ShikumiStmt

続いて、ShikumiStmt の定義を記述します。

初版としては、次のように書いてみました。書く場所は、最初の %% だけ行以降で、次の %% だけの行までの間でしたらどこでもOKです。
どこでもいいと言われて困るようでしたら、stmt: の定義の直後くらいに書いておけばいいでしょう。

ShikumiStmt:
            SHIKUMI
                {
                    elog(WARNING,"shikumi executed");
                }
        ;

stmtのところで、stmt のステートメントの1つとして、Shikumi で定義される文がありますよという定義を行いました。これは、それに対するShikumi の定義になります。
内容は、ShikumiStmt は、SHIKUMI というトークン1つからなり、それを受け取ったら { } に囲まれている elog() を実行します。つまり、スキャナからSQL文(stmt)の先頭に SHIKUMI というトークンを取り出した場合、ここの構文に一致したと判断されます。

ここまで書き換えたら、コンパイルして実行することができます。

このまま実行すると、目的の「shikumi executed」は表示されましたが、他のワーニングとエラーが出ているはずです。

frisk(9)% pg_ctl start
postmaster starting
frisk(10)% psql template1
Welcome to psql 8.0.0rc3, the PostgreSQL interactive terminal.

Type:  \copyright for distribution terms
       \h for help with SQL commands
       \? for help with psql commands
       \g or terminate with semicolon to execute query
       \q to quit

template1=# shikumi;
WARNING:  shikumi executed
WARNING:  unrecognized node type: 1802070131
ERROR:  unrecognized node type: 1802070131
template1=#

これは、パーザは、shikumi という文を構文解析した結果、目的の構文の解析に成功し、そこで WARNING:  shikumi executed を実行しました。
その後、エグゼキュータに ShikumiStmt で解析したパーズ木が渡されて、知らないノードが来たというエラーで終わっています。

◇ ちょっとだけ yacc 

yacc (というか bison?)の構文定義の書式は次のようになっています。

定義:  サブ定義1 サブ定義2 サブ定義3 ....   { 実行する内容 } ;
サブ定義1:  サブのサブ定義1 ... { 実行する内容 } ;
                  :

このように再帰的に、構文を定義します。そして、最後はキーワードなどで登録してある単位まで分解されます。
また、サブ定義1の実行結果が $1、サブ定義2の実行結果が $2 というように$N に実行結果が返ってきます。

では、サブ定義の { 実行する内容 } では、どのようにして返す値を決めるのでしょうか?他のところを見るとわかりますよね。 $$ = で値を代入すればいいわけです。

次のように定義を OR 条件で並べて書くこともできます。

定義:  定義1 { 定義1の場合に実行する内容 } | 定義2 { 定義2の場合に実行する内容 } 
                 | .... | 定義N { 定義Nで実行する内容 } ;

 

これを考慮して、stmtblock からの構文定義を見てみます。今回は、ShikumiStmt に関係するところだけ残して書いてあります。

stmtblock:  stmtmulti                               { parsetree = $1; }
        ;
stmtmulti:  stmt  { if ($1 != NULL)
                        $$ = list_make1($1);
                      else
                        $$ = NIL;
                    }
        ;

stmt : ShikumiStmt
        ;

stmtblock を見ると、stmtmulti の実行結果を parsetree という変数に代入しています。
stmtmulti を見ると、stmt の結果によって、stmtの結果をリストに加えるようとしていると思われる list_make($1) を実行するか、NIL (つまり空)を返すということをしています。
stmtをみると、本当は OR 条件でいろいろなSQLステートメントの定義が書いてありましたが、今回マッチするのは ShikumiStmt だけです。つまり、関数呼び出しのようにShikumiStmt を呼び出すだけです。

◇ エラーを取り除く

さて、改めて先ほど書いた ShikumiStmt を見てみましょう。上のほう (stmtmultiの定義) で、実行結果を期待しているにも関わらず、$$ で戻り値を返してませんね。これでおかしなポインタが返され、意味不明のリストのノードが生成されていたようです。
そこで、次のように書き換えましょう。

ShikumiStmt:
            SHIKUMI
                {
                    elog(WARNING,"shikumi executed");
                    $$ = NULL;
                }
        ;

これをコンパイルして実行すると、今度は、目的の文字列を表示してエラーにならないことが確認できると思います。

template1=# shikumi;
WARNING:  shikumi executed
template1=#

ここまでで、パーザへ処理の追加が終了です。
しかし、お気づきだと思いますが、パーザで文字列を表示してしまっています。そして、パーズ木にはNULLを返すことでSQLの処理を終了してしまっているので、通常のSQLのエグゼキュータや、DDLなどの実行系へ処理が渡っていません。

次は、通常の処理フェーズで処理されるように改造していきましょう。

■ STEP2 「エグゼキュータ側から文字列を表示させる」

エグゼキュータで処理をさせるには、パーザからパーズ木のノードを返す必要があります。
その後、パーズ木の処理を行う部分で、elog を実行するようにします。

やること

  1. 新規に Shikumi 構文用のノード構造体 ShikumiStmt を追加する
  2. エグゼキュータ側に、ShikumiStmt の実行をできるようにする
    今回は、特に何もしない構文なので、DDL系の構文と同じように処理されるようにする。つまりDML系のクエリ処理ではない流れで処理する。

◇ ノードの追加

まず、STEP1で追加した gram.y のまわりのコードを見てみます。すると、

例えば、stmtの次に定義されていた CreateUserStmt を見てみましょう。

CreateUserStmt:
            CREATE USER UserId opt_with OptUserList
                {
                    CreateUserStmt *n = makeNode(CreateUserStmt);
                    n->user = $3;
                    n->options = $5;
                    $$ = (Node *)n;
                }
        ;

CreateUserStmt型のポインタ n に対して、 makeNode で CreateUserStmt という型のノードを作っていているらしいということが見て取れます。その後、新しく作成したノード n の user に、$3 つまり UserId の定義の実行結果を代入し、n の options に$5 つまり OptUserList の定義の実行結果を代入しています。こうして作成したノード n を Node 型にキャストして$$に代入します。つまり CreateUserStmtの実行結果として上の定義に返しています。

この中で makeNode が何をしているか気になってきますので、見ておきましょう。makeNodeは、include/node/nodes.h で定義されています。

#define makeNode(_type_)        ((_type_ *) newNode(sizeof(_type_),T_##_type_))

makeNode はマクロになっていました。newNode に対して、第1引数として makeNodeに渡された型のサイズを渡します。第2引数は、makeNodeに渡された型の名前に T_ というプレフィックスをつけて渡します。
今回、ShikumiStmt という型を作ったとします。 makeNode(ShikumiStmt) で呼び出すと、newNode(sizeof(ShikumiStmt, T_ShikumiStmt) と展開されることになります。

次に newNode がすぐ上に定義されているので、それも見ておきましょう。

#define newNode(size, tag) \
( \
    AssertMacro((size) >= sizeof(Node)),        /* need the tag, at least */ \
    newNodeMacroHolder = (Node *) palloc0fast(size), \
    newNodeMacroHolder->type = (tag), \
    newNodeMacroHolder \
)

newNodeでは、渡された size のチェックをして、そのサイズを palloc0fast でメモリアロケートしています。次に、Node 型でキャストされている状態で、新規に割り当てたメモリの typeの項目に tag を設定しています。最後に、今割り当てたメモリのポインタが返るようにしてあります。

つまり、makeNodeでは、引数で与えた型のメモリを割り当ててノードを作成し、type に T_<型名> を設定することが確認できました。

ということは、ShikumiStmt というノード型の定義と T_ShikumiStmt を定義すればいいことになります。

◇ ノード構造体とノードタイプの追加

まず、先に T_ShikumiStmt を追加しておきましょう。ノードタイプ T_<型名> は、include/node/nodes.h のNodeTag というenumで定義されています。

PostgreSQLでは、このnode 以下のコードで、SQLの処理に必要な木構造で共有しています。 include/node/nodes.h で、NodeTagは次のようになっています。

typedef enum NodeTag
{
    T_Invalid = 0,

    /*
     * TAGS FOR EXECUTOR NODES (execnodes.h)
     */
    T_IndexInfo = 10,
    T_ExprContext,
    T_ProjectionInfo,
    T_JunkFilter,
    T_ResultRelInfo,
    T_EState,
    T_TupleTableSlot,

    /*
     * TAGS FOR PLAN NODES (plannodes.h)
     */
    T_Plan = 100,
    T_Result,

       :
       :

エグゼキュータで使用するノードタイプが10から始まり、クエリプラン用のノードタイプは 100 から始まるというように分かれています。今回はパーズ木用のノードですから、700番台か800番台の「TAGS FOR PARSE TREE NODES (parsenodes.h)」 のところに追加します。とりあえず、私は700番台の最後に追加しました。

       :
       :
    T_DropTableSpaceStmt,
    T_AlterOwnerStmt,
    T_ShikumiStmt,

    T_A_Expr = 800,
    T_ColumnRef,
       :
       :

◇ 新構文用のノード構造体の定義

続いて、パーズ木のノード構造体を追加します。makeNodeを見てわかるように、自動的に型名が埋め込まれるので、ノード構造体の追加は必須になります。

新しいノード構造体は、include/nodes/parsenodes.h に定義します。ここでもすべての木構造用の定義が入っていますが、パーザ用は一番下のようですので、末尾(#endif /* PARSENODES_H */の直前)に追加します。

/* ----------------------
 *      SHIKUMI Statement
 * ----------------------
 */
typedef struct ShikumiStmt
{
    NodeTag     type;
    char        *name;
} ShikumiStmt;

今回は、typeだけあれば十分なのですが、試しに name もつけてみました(使いませんでしたが...)。

◇ ノード構造体の操作関数の作成

新しいノード構造体を追加したら、その構造体を操作するための関数を登録しておく必要があります。
(※ 今回の範囲では、実は登録しなくても動く。主にアナライザやオプティマイザで使われるのだと思います。)

ファイル 外部インタフェース 内容
nodes/copyfuncs.c copyObject() ノードのコピーを行う関数群
nodes/equalfuncs.c equal() ノードが等しいかチェックするための関数群
nodes/outfuncs.c nodeToString() デバッグ用にノードの内容を表示する関数群

それぞれ、ファイルの作りはほぼ同じです。それぞれのノード構造体名NNNに対して、それぞれ _copyNNN(), _equalNNN(), _outNNN() という内部関数を定義しておきます。そして外部インタフェースの引数に Node を渡して、その場で Node の型を調べ、型に合わせた _copyNNN() を呼び出します。例えば、T_Plan をコピーする場合は _copyPlan() を呼び出し、T_Result をコピーする場合は _copyResult() を呼び出すといった具合です。

したがって、ノード構造体を処理する内部関数と分岐点にコードを追加すればいいことになります。

nodes/copyfuncs.c では、 _copyShikumiStmt() を作成します。コードの追加場所は、次のようなブロックの最後がいいでしょう。

/* ****************************************************************
 *                  parsenodes.h copy functions
 * ****************************************************************
 */

このブロックの最後に、次の定義を追加します。

static ShikumiStmt *
_copyShikumiStmt(ShikumiStmt *from)
{
    ShikumiStmt *newnode = makeNode(ShikumiStmt);

    COPY_STRING_FIELD(name);

    return newnode;
}

そして、関数 copyObject() の分岐の追加を行います。追加場所としては、T_DeallocateStmt と T_A_Expr の間が適切でしょう。理由は、include/nodes/nodes.hで、T_A_Exprの直前に定義したということです。ただし、どこであっても、自分で使うだけであればそれほど問題はないでしょう。

case T_ShikumiStmt:
            retval = _copyShikumiStmt(from);
            break;

 

nodes/equalfuncs.c では、_equalShikumiStmt() を作成します。場所としては、_equalDeallocateStmt() と_equalAExpr() の間が適当でしょう。

static bool
_equalShikumiStmt( ShikumiStmt *a, ShikumiStmt *b)
{
    COMPARE_STRING_FIELD(name);

    return true;
}

copyfuncs.c と同じく、分岐を行っている equal() の中のT_DeallocateStmt と T_A_Expr の間に次の定義を追加します。

        case T_ShikumiStmt:
            retval = _equalShikumiStmt(a, b);
            break;

 

nodes/outfuncs.c  では、_outShikumiStmt() を追加します。
※ デバッグのときに使わないようであれば、特に登録する必要はありません。今回のしくみ分科会の勉強会でも作成しませんでした。

追加場所は、_outAExpr() の直前か、_outFuncs() の直前あたりにします。

static void
_outShikumiStmt(StringInfo str, A_Expr *node)
{
    WRITE_NODE_TYPE("SHIKUMI");
    WRITE_STRING_FIELD(name);
}

nodes/outfuncs.cだけは、他の2つのファイルと少しだけ違い、外部インタフェースの関数内に条件分岐がありません。外部インタフェースで _outNode() を呼び出し、その中で条件分岐を行っています。ただ、それだけなので、_outNode() の条件分岐に T_ShikumiStmt の条件も付け加えます。加える場所は、T_A_Expr:の前か、default の直前でいいと思います。

            case T_ShikumiStmt:
                _outShikumiStmt(str, obj);
                break;

以上で、ノード構造体を処理する関数の追加が完了です。

※ 実は、nodes/outfuncs.c での変更をパラメータだけで見ることは、できません。パーズ木を表示するように、次のように手を加えておく必要があります。tcop/postgres.cのpg_analyze_and_rewrite() の最初のほうに次の行を入れておきます。

pg_analyze_and_rewrite(Node *parsetree, Oid *paramTypes, int numParams)
{
    List       *querytree_list;

    elog_node_display(DEBUG1, "raw parse tree", parsetree, Debug_pretty_print);

◇ 構文処理の変更

それでは、parser/gram.y を変更して、パーズ木にノードを返すように変更します。

ShikumiStmt:
            SHIKUMI
                {
                    ShikumiStmt *n = makeNode(ShikumiStmt);
                    n->name = NULL;
                    $$ = (Node *)n;
                }
        ;

一応、name は初期化するようにしておきました。

◇ エグゼキュータの処理の流れの確認

次は、実行系の作成です。今回作成した T_ShikumiStmt のノードが来た場合の処理を書きます。

DDL系のSQLやユーティリティ系のSQLは、ProcessUtility() で処理が分岐しています。
そこで、まず、ProcessUtility()が呼ばれるまでの流れを確認します。

  1. psql から postmaster に接続され、postgres プロセスが fork されて、リクエスト待ちになる
  2. psql で、shikumi; と入力するとpostgres プロセスにリクエストが送られる
  3. PostgresMain() の中の ReadCommand() でリクエストを読み取る
  4. psqlの通常の処理は、exec_simple_query() が呼ばれる
  5. exec_simple_query()の中では、スキャナ&パーザが実行され、パーズ木が生成される
  6. foreach(parsetree_item, parsetree_list) { } のループ内に入る
  7. 準備の1つとして、CreateCommandTag(parsetree)で、コマンドの表示用のタグ作成を行う [要コード追加]
  8. querytree_list = pg_analyze_and_rewrite(parsetree, NULL, 0);
          default:
    
                /*
                 * other statements don't require any transformation-- just
                 * return the original parsetree, yea!
                 */
                result = makeNode(Query);
                result->commandType = CMD_UTILITY;
                result->utilityStmt = (Node *) parseTree;
                break;
  9. plantree_list = pg_plan_queries(querytree_list, NULL, true); でクエリプランの生成するフェーズでも、ユーティティ系コマンドであることがわかるので、プランをNULL に設定するだけで終わる
  10. PortalDefineQuery()、PortalStart() で、処理実行の準備を行う
  11. PortalRun() に入る [処理の本体]
  12. switch (portal->strategy) の分岐の中で、PORTAL_UTIL_SELECT が選択されるようにして、PortalRunUtility()に入る
  13. その中で、ProcessUtility() に処理が呼び出される
  14. ProcessUtility() で、ユーティリティ系コマンドの処理の分岐が行われる [要コード追加]

ここで、ProcessUtility() まで到達する。

◇ エグゼキュータの変更

変更の必要な CreateCommandTag () と ProcessUtility() は、両方とも tcop/utility.c にある。

まず、コマンド表示用のタグを作成する CreateCommandTag() に追加を行う。次のように、tag を"SHIKUMI"としておく。追加する場所は、どこでも構わないので、default: のすぐ上に追加した。

        case T_ShikumiStmt:
            tag = "SHIKUMI";
            break;

 

次に、tcop/utility.c のProcessUtility() の条件分岐に、T_ShikumiStmt の処理を書く。

        case T_ShikumiStmt:
            elog(WARNING,"shikumi executed");
            break;

本来であれば、実行する部分は command ディレクトリ以下にファイルを作成した上で、関数の本体を作成するべきである。しかし、今回はelogを呼ぶだけであったことと、時間がなかったので、ここにelogを書いて終了とした。

◇ 実行例

ここまでできたら、コンパイルして実行してみよう。以下のように、WARNINGで文字列が出力された後、コマンドのタグ SHIKUMI が表示される

template1=# shikumi;
WARNING:  shikumi executed
SHIKUMI
template1=#

一応、nodes/outfuncs.cで作ったデバッグ文も見てみましょう。

template1=# set log_min_messages = debug1;
WARNING:  could not dump unrecognized node type: 739
SET
template1=# shikumi;
WARNING:  shikumi executed
SHIKUMI
template1=#

もともと、パーズ木は出力する対象になっていないので、一部、WARNINGが出ていますが気にしないことにします。ログのほうに、次の出力が確認できるはずです。

DEBUG:  raw parse tree:
DETAIL:  {SHIKUMI :name Y}

WARNING:  shikumi executed

■ まとめ

最後に、どこに何を加えるかまとめておきます。

parser/keywords.c キーワードとトークンの追加 {"shikumi", SHIKUMI},
parser/gram.y トークンの定義の追加 %token <keyword> に SHIKUMI を追加
構文の分岐点追加 stmt: に ShikumiStmt を追加
構文の追加 ShikumiStmt: の定義を追加
構文用のノードを定義 %type <node> に ShikumiStmt を追加
include/node/nodes.h ノードタグの追加 typedef enum NodeTag に T_ShikumiStmt, を追加
include/nodes/parsenodes.h 新しい構文用のノード構造体を定義する typedef struct ShikumiStmt を追加
nodes/copyfuncs.c 新しいノード構造体をコピーする関数と分岐点を追加する _copyShikumiStmt() を作成し、分岐する場所を追加
nodes/equalfuncs.c 新しいノード構造体を比較する関数と分岐点を追加する _equalShikumiStmt() を作成し、分岐する場所を追加
nodes/outfuncs.c 新しいノード構造体を表示する関数と分岐点を追加する _outShikumiStmt() を作成し、分岐する場所を追加
tcop/utility.c タグの生成処理を追加する CreateCommandTag() に分岐を追加して、tag = "SHIKUMI"; を追加
処理の分岐点を追加し、実際の処理を書いた。 ProcessUtility() に分岐を追加して、elog() の処理を追加した(※ 本来はここから実行する関数の本体を呼び出す)
(commands/shikumi.c) shikumiの処理を書く 今回は作成していない。本来なら ProcessTility() から呼び出される関数を記述

以上、ユーティリティ系コマンド作成の手順のまとめです。


2007.05.29 更新

2005.1.20 井久保