SQLが嫌いすぎてデータベースを自作した話

# 追記
いくらSQLが嫌いでも、データベースを自作すると大変なことになりかねませんので、やめておきましょう（ぼくもう知らないもん）

https://qiita.com/nattyan_tv/items/4cdae737a308b7c62644

# 目的
1. 何のdbを選ぶのかは自分で選べるということです
2. ちなみに自作したゆーとりますが、完成度はそんなに高くないです
3. ちなみに少し脚色してまs((

# 関係知識
[にらBOT](https://github.com/team-i2021/nira_bot)という、Python製DiscordBOTを作っているのですが、もともと、データは`pickle`を使ってファイルに保存していたのですが、これがちょっとめんどくさいなーと思い、データベースに保存するように変えました。
最初は、Googleスプレッドシートに保存していましたが、セットアップが面倒なのと、データの読み書き速度が遅い事に難がありました。
てなわけで、セルフホスティングなデータベースを使用する事にしたのです。
さて、「セルフホスティングなデータベースと言えばなんでしょう？」と聞かれたら大半の方が「SQL！」と元気よく答えると思います。
ですがSQLには難点があります。それは...
__構文を覚えるのが面倒！__（毛嫌い）
てなわけで、自作のもうちょっと楽なデータベースを作ろうと思ったわけです。

# 実験
## Plan

まず、データベース...というより「データを保存する形式」として有名なものがありますよね。
そう、`JSON`ですよね？？？~~（だいぶ強引）~~
そして、データベースはサーバーがあって、そこと通信してデータをやり取りしますよね。なので、サーバーサイドとクライアントサイドで通信する機構が必要です。
そう！`HTTP`ですよね！！！！
こうして、`HTTP通信を使ってJSON形式でやりとりする`と言うところまで決まりました。


さて、まずはサーバーサイドソフトの構造から決めましょう。
`HTTP`と`JSON`をメインとするのにちょうどいい言語があります。
`JSON`という名前から察する方も沢山いるでしょう。
ご名答です。`JavaScript`...__ではなく`Python`です！！！__
(ほら...`JSON`の最後の2文字と`Python`の最後の2文字が同じでしょ...？)

<details><summary>なぜJavaScriptを使わなかったのか</summary>


今の時代、サーバーサイドとして`JavaScript`を使っていると、`TypeScriptはどうした？^^`と煽られてしまうのと、サーバーサイドの`JavaScript`について教養が浅かったからです。
</details>

てなわけで、`Python`で`HTTP`サーバーを建てるということで、前々から気になっていた`Sanic`に触れる事にしました。

## Do

そして出来上がったサーバーサイドソフトウェアがこちらでs((

https://github.com/nattyan-tv/HTTP_db

`Sanic`のレスポンス処理で、`JSON`を返していくというような感じの、至って単純なものです。
なんせ`Simple and easy database system using HTTP`ですので...。

さて、ここからはクライアントサイドの構造です。
クライアントサイドは、普通に`HTTP POST`をすればいいのですが、にらBOTで扱う時にラッパーがあれば楽だなと言う事で`Python`ラッパーを作っていきます。

そして、人生で初めてちゃんとクラスを扱う事になりました。
オブジェクト指向ですねぇ〜

```py:client.py
class Client:
    def __init__(self, url: str = "http://localhost:8080"):
        self.url = url

    async def info(self) -> dict:
        ...
```

みたいな感じです。（適当）

なぜアドレスとポートに分けずURLとしたのかと言うと、私がリバースプロキシを使ってデータベースのURLを`https://example.com/example_database`みたいにしたせいで、これをそのまま入れると`example.com/example_database:443`となってしまって、良い方法も見つからなかったのでこうしました。

## Check

ある程度期待の動作をしてくれました。
凄く嬉しい事です。
さて、「このままじゃポート(URL)が分かると、誰からでもデータをいじられてしまう」と言うのが議題に上がりました。
そして、にらBOTのエラーハンドルを適当にしているので、もしデータベースのエラーが起きてた場合に、アドレスが普通に表示されてしまうような気がするので、もう情報ダダ漏れ待ったなしです。
DiscordDeveloperの規定上、ユーザーの個人情報を保護するのは絶対なので、これではいけません。



## Action

さて、パスワード保護をつける事にしましょう。
私の中で、パスワード保護は2種類の方法に分けています。

種類|説明
---|---
暗号化式|サーバー/クライアント側で暗号化して送信して、クライアント/サーバー側で復号する
認証式|正しいパスワードでのみデータの受信/送信が出来る

今回は、ちょうど良い暗号化モジュールがなかったので、認証式にしました。

```diff_python:client.py
class Client:
-   def __init__(self, url: str = "http://localhost:8080"):
-       self.url = url
+   def __init__(self, url: str = "http://localhost:8080", password: str = ""):
+       self.url = url
+       self.password = password

    async def info(self) -> dict:
        ...
```

こんな風にしてパスワード認証をつけました。
やはり認証は必要ですね。

# 結果の検討

## Check(2度目)

ワイ「おっしゃこれで完成やろ！」
ワイ「BOTからデータいじれるか試してみよ！」
にら「出来たで」
ワイ「おっしゃ！これで完璧や！じゃあ実際にこれからはここにデータを入れよう！」
ワイ「`{サーバーID: {チャンネルID: 設定項目 } }`みたいなの送ってみるか。いやーJSONは辞書形式にも対応してくれてて嬉しいわ〜」
にら「送ったで」
ワイ「おっしゃ！あとはこれを読み込んでちゃんと設定項目が確認できれば...」
にら「データベースから読み込んだで。ちなみに、__データの中にこのサーバーIDは見つからんかったで__」
ワイ「( ˙꒳​˙  )？？？？？」

これは`JSON`の辞書形式において、`int`形式のキーに対応してない事が原因で、データを送る際に勝手に変換されてしまう事が原因でした。

```py:送りたかったデータ
{
    1234567890: {
        9876543210: true,
        1528371918: false,
    }
}
```
```json:実際に送られたデータ
{
    "1234567890": {
        "9876543210": true,
        "1528371918": false,
    }
}
```
```py:実際に受け取ったデータ
{
    "1234567890": {
        "9876543210": true,
        "1528371918": false,
    }
}
```

てなわけで、辞書を`int`で参照したけど`str`しかないのでそりゃあデータがないと言われるわけです。
解決策をいろいろ模索した結果、最終的にBOT側では「辞書形式で送らない」事にしました。

## Action(2度目)

「いや待ちなされ奥さん、辞書で送らないってどう言うことよ」って思われた方も多いと思います。
勿論、後で`eval()`で戻せるように辞書をそのまんま`str`に変えるってのは、_Nonsense_ ですよね。
てなわけで、`list`で送る事にしました。
辞書形式とは、本当(？)は連想配列形式っていうことなので、楽に配列にする事ができます。
実際に試してみると分かります。

```py:repl
>>> value = {12345: 'hello', 'hola': 67890}
>>> list_value = list(value.items())
>>> print(list_value)
[(12345, 'hello'), ('hola', 67890)]
```

ね？簡単でしょ？~~（某ペンギン風）~~

てなわけで、これを渡すようにしました。
そしてデータを読み込むときは、配列形式を辞書形式にするようにしました。

```py:実際のコード
def listToDict(source) -> dict:
    temp = {}
    source = dict(source)
    for i in list(source.keys()):
        if i not in temp:
            temp[i] = {}
        temp[i].update(dict(source[i]))
    return temp
```

こんなのを`util`にいろいろ突っ込んだりして、なんとかデータベースとちゃんとデータをやり取り出来るようになりました。

# 結果の整理

てなわけで、実際にデータベースを自作する事ができました。
詳しくは、各レポジトリを見てみてほしいです。

- HTTP_db（自作したデータベース）

https://github.com/nattyan-tv/HTTP_db

- にらBOT（自作しているDiscord BOT）

https://github.com/team-i2021/nira_bot


# 感想
今回、このデータベース自作を通して、データベースを作るのは大変だと思いました。（小並感）
SQLの方が楽そうだと思いました。（本末転倒）
でもここまで来たら引き下がれませんでした。（謎のプライド）
多分そのうち限界が来たらSQL使います。（諦め）
色々学べる事があって楽しかったです。（ピーク・エンドの法則）
皆さんはSQL使いましょう（何がしたかったんだか）


# よければ...
「いいね」とか「Star」よろしくね⭐︎