【13日目】トランザクションサポートデータベース実装

# まえがき
先日に続き、updateとdeleteの実装を説明します。説明の都合上、deleteを先に説明します。

## delete
データの削除の実装を説明する。クライアントからは「このテーブル(TableName)のこのカラム(ColName)がこの値(Val)になっているデータを削除してちょうだい」というメッセージ{delete, TableName, ColName, Val}が飛んでくる想定である。

```Erlang:query_exec.erl
handle_call({exec_query, {delete, TableName, ColName, Val}}, _From, State)->
    %% トランザクションが許可されている場合のみ次に進める
    case ask_transaction(State) of
        transaction_not_found ->
            {reply, transaction_not_found, State};
        ok -> 
            QueryId = generate_query_id(),
            QueryIdList = get_query_id_list(State),
            %% オブジェクトIDを取得する
            OidList = select_object_id_list(State, TableName, ColName, Val, QueryIdList),
            lists:map(fun(Oid) -> local_delete_data(State, QueryId, TableName, Oid, select_data(State, TableName, Oid, QueryIdList)) end,
            OidList),
            {reply, ok, State#state{queryId = QueryIdList ++ [QueryId]}}
    end;
```

QueryIdは今まで通りこのクエリのIDである。
QueryIdListは今までローカル領域に積み上がってきたクエリのIDリストである。コミットする前のデータの削除も可能にするために、今まで積み上げてきたデータを持ってくるという意図である。
OidListは削除するデータのオブジェクトIDである。
取得してきたオブジェクトID一つずつ、local_delete_dataでdelタグのついたデータを積み上げていく。

```Erlang:local_delete_data
local_delete_data(State, QueryId, TableName, Oid, Val) ->
    LKvstore = get_local_kvstore(State),
    LColumnIndex = get_local_column_index(State),
    ColList = simple_db_server:get_column_list(TableName),
    if
        ColList =:= not_found -> not_found;
        true ->
            lists:map(fun({ColName, ColVal}) ->
                ets:insert(LColumnIndex, {QueryId, del, TableName, ColName, ColVal, Oid}) end,
                lists:zip(ColList, Val)),
            ets:insert(LKvstore, {QueryId, del, TableName, Oid, Val})
    end.
```
local_delete_dataはローカルカラムインデックスと、ローカルキーバリューストアにdelタグのついたデータを挿入するだけである。
例えば、フルーツテーブル（名前と値段の列を持つ）から名前が「みかん」となっている行を削除するとする。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/201287/ab5ada34-2c6a-efa5-a5a7-4d15c8dba37b.png)

ローカルカラムインデックスには下記のようなデータを登録する。(削除するデータのオブジェクトIDはAとする。)

* {0002, del, Fruit, name, みかん, A}
* {0002, del, Fruit, price, 100円, A}

ローカルキーバリューストアには下記のようなデータを登録する。

* {0002, del, Fruit, A, [みかん, 100円]}

トランザクションをコミットしたときに、ローカルキーバリューストアのdelタグのついたデータを見て、共有カラムインデックスから物理削除し、ローカルキーバリューストアのdelタグのついたデータを見て、共有キーバリューストアから物理削除する。

## update
データの更新の実装を説明する。クライアントからは「このテーブル(TableName)のこのカラム(ColName)がこの値(Val)になっているデータをこんな風に(SetQuery)に更新してちょうだい」というメッセージ{update, TableName, SetQuery, ColName, Val}が飛んでくる想定である。

```Erlang:query_exec.erl
handle_call({exec_query, {update, TableName, SetQuery, ColName, Val}}, _From, State)->
    %% トランザクションが許可されている場合のみ次に進める
    case ask_transaction(State) of
        transaction_not_found ->
            {reply, transaction_not_found, State};
        ok -> 
            QueryId = generate_query_id(),
            LKvstore = get_local_kvstore(State),
            LColumnIndex = get_local_column_index(State),
            QueryIdList = get_query_id_list(State),
            ColumnList = simple_db_server:get_column_list(TableName),
            SetQueryConverted = simple_db_server:convert_set_query(SetQuery, ColumnList),
            OidList = select_object_id_list(State, TableName, ColName, Val, QueryIdList),
            F = fun(Oid) ->
                %% OldVal -> [banana, 100]
                %% NewVal -> [apple, 100]
                OldVal = select_data(State, TableName, Oid, QueryIdList),
                OldValWithCol = lists:zip(simple_db_server:get_column_list(TableName),OldVal),
                NewVal = simple_db_server:build_new_val(OldVal, SetQueryConverted),
                %% 更新前値をローカルデータから削除
                ets:insert(LKvstore, {QueryId, del, TableName, Oid, OldVal}),
                %% 更新後値をローカルデータに挿入
                ets:insert(LKvstore, {QueryId, ins, TableName, Oid, NewVal}),
                %% 更新対象のカラムごとにカラムインデックスを更新する
                %% ex. {name, apple}
                FF = fun({ColumnN, NewColVal}) ->
                    {_Key, OldColVal} = lists:keyfind(ColumnN, 1, OldValWithCol),
                    ets:insert(LColumnIndex, {QueryId, del, TableName, ColumnN, OldColVal, Oid}),
                    ets:insert(LColumnIndex, {QueryId, ins, TableName, ColumnN, NewColVal, Oid})
                end,
                lists:map(FF, SetQuery)
            end,
            lists:map(F, OidList),
            {reply, ok, State#state{queryId = QueryIdList ++ [QueryId]}}
    end;
```
OldValは更新前の値、NewValは更新後の値を代入して、
OldValを削除し、NewValを挿入するというのが大雑把な実装方針です。
OldValは今まで通り、共有データを取ってきて、ローカルデータをマージする。
NewValは、OldValをSetQueryで更新して作成する。

# あとがき
4つのDMLの実装について説明しました。
次回は、tx_mngの挙動を説明する予定です。