【Prisma,TypeScript】Prismaを用いたビットフラグによるデータ格納方法

# 導入・背景
たまたま仕事でビットフラグを用いたデータ登録を行う機会がありました。
Rubyでは、[active_flag](https://github.com/kenn/active_flag)なるビットフラグを用いたデータ登録をサポートしてくれるgemがあり、それによって簡単にデータ登録ができるようになります。
**「JSでも同じようなライブラリがあるかなー」と探してみたのですが、あまり無いっぽいので**ロジックだけ作ってみました。（有名なライブラリがあるときはコメントしてくれると嬉しいです。）
もしよかったら、見てみてください！

# そもそも、ビットフラグってなに？
> ビット単位のフラグは変数の集合で、通常は単純な数値であり、メソッドやその他のコード構造の特定の使用法や機能を有効にしたり無効にしたりするのに使われる。ビットレベルで動作するため、迅速かつ効率的にこれを行うことができる。同じグループ内の関連フラグには、一般に、1 つの値（16 進数など）の異なるビット位置を表す相補的な値が与えられ、複数のフラグ設定を 1 つの値で表すことができます。
https://developer.mozilla.org/en-US/docs/Glossary/Bitwise_flags

よくわかんないですが、簡単にいうと

**整数の各ビット（0または1）を使って、複数の状態やフラグを一つの数値で表現する仕組み**

です。

例えば、「好きなサメは？」という選択肢（複数回答可）があったとします。



選択肢としては
- シュモクザメ
- ジンベイザメ
- ウバザメ
- ホホジロザメ
- レモンザメ
- ツマグロ
- ダルマザメ
- ウバザメ
- シロワニ(一番推し)

があるとします。


この時、回答内容を保存する方法としては以下が考えられると思います。
1. 中間テーブルを設けて選択された数だけレコードを作成する
1. JSON型として保存する
1. 1つのカラムにカンマ区切りで登録する
1. ...etc

しかし、
- 選択肢の回答だけでテーブルを作成するのもやり過ぎな感じがしますし、
- JSON型で登録する場合もSQLをゴリゴリに書かないといけないし、
- カンマ区切りで登録するのはアンチパターンだし...

ということで、ビットフラグの出番です！

---

次のようにビット（2進数）毎に選択肢を定義した場合を考えてみます。
| ビット値    | 10進数 | サメ名       |
|-------------|--------|--------------|
| 00000001    | 1      | シュモクザメ |
| 00000010    | 2      | ジンベイザメ |
| 00000100    | 4      | ウバザメ     |
| 00001000    | 8      | ホホジロザメ |
| 00010000    | 16     | レモンザメ   |
| 00100000    | 32     | ツマグロ     |
| 01000000    | 64     | ダルマザメ   |
| 10000000    | 128    | シロワニ     |

計算方法としては、**選択されたサメの各10進数の和を求めることで、１つのレコードに複数の選択情報を格納することができます。**

例えば、以下のようなイメージです。
```
// 「シュモクザメ」「ホホジロザメ」が選択された場合
1:シュモクザメ + 8:ホホジロザメ = 9

// 「ジンベイザメ」「ツマグロ」「シロワニ」が選択された場合
2:ジンベイザメ + 8:ツマグロ + 128:シロワニ = 138


→ 選択肢に対応する10進数の和がDBに登録される！
```

大体イメージが掴めてきたでしょうか？

選択肢を１つの数値に圧縮して管理することで、
「**複数の選択肢を効率的に管理し、ストレージやパフォーマンスの観点で優れている**」らしいです。

しかし、登録データが10進数の値だけなので、
「**パッと見でどんな値が登録されているか分かりづらい**」といったデメリットもあるようです。

Enumとかもありますし、適切に使い分けしたほうがいいってことですかね。。。？
（このあたりの選定基準はあまり理解できていないので、コメントしてくれると嬉しいです！）

Rubyにはなってしまいますが、なぜビットフラグを選定するのかについてわかりやすい記事があったため、ぜひ参考にしてみてください！
↓

https://tech.actindi.net/2019/05/13/091143

# ビットフラグを使ったデータ登録

いよいよ、Prismaでビットフラグを用いたデータ登録の方法を解説したいと思います。
（※Prismaを使用する理由としては、個人開発で使っているからという安直な理由です。。）

## 前提：Prismaスキーマの定義

Prismaでは、`Int`型を使ってビットフラグを保存します。
以下は、`flags`フィールドを持つ`User`モデルの例です。

```prisma
model User {
  id    Int    @id @default(autoincrement())
  name  String
  flags Int    @default(0)
}
```

この`flags`フィールドをビットフラグとして使用します。


## TypeScriptでのビットフラグ管理

TypeScriptを用いて、ビットフラグのデコードとエンコードを行うユーティリティ関数を作成します。

### ①フラグマップ及び型の作成

まず、2進数を10進数に変換した値をキーに持つサメの名前が記述されたオブジェクトを作成します。
```typescript
type FlagMap = Record<number, string>

const FLAGS: FlagMap = {
  1: 'シュモクザメ',
  2: 'ジンベイザメ',
  4: 'ウバザメ',
  8: 'ホホジロザメ',
  16: 'レモンザメ',
  32: 'ツマグロ',
  64: 'ダルマザメ',
  128: 'シロワニ',
}
```

---

### ②ビットフラグのデコード
次に、数値のビットフラグから対応するフラグ名を取得する関数を定義します。

```typescript
const decodeFlags = (value: number): string[] =>
  Object.entries(FLAGS)
    .filter(([key]) => (value & Number(key)) === Number(key))
    .map(([, label]) => label)

// 使用例
console.log(decodeFlags(3)) // ['シュモクザメ', 'ジンベイザメ']
console.log(decodeFlags(5)) // ['シュモクザメ', 'ウバザメ']
console.log(decodeFlags(128)) // ['シロワニ']
```
#### 解説
`Object.entries`でキーとバリューを値に持つ配列を生成します。
```typescript
console.log(Object.entries(FLAGS))
// [['1', 'シュモクザメ'], ['2', 'ジンベイザメ'], ['4', 'ウバザメ'], ...]
```

次にビット演算を用いて、`filter`で`value`に設定されているビットを抽出しています。
`value & Number(key)` は、ビット演算のAND（&）を用いて計算されており、
`key`と`value`の該当するビットが両方「1」の場合のみ、対応するビットが「1」になります。
その結果が`Number(key)`と等しい場合、そのビットが`value`に含まれていることを意味します。

以下のようなイメージですね！
↓
value = 3（2進数で 11）、key = 1（2進数で 01）
→ 3 & 1 → 01（1）となり、条件が**真**になります。

value = 3（2進数で 11）、key = 4（2進数で 100）
→ 3 & 4 → 000（0）となり、条件が**偽**になります。

この記事がとてもわかり易かったので、ぜひ参考にしてください！
↓

https://qiita.com/Ingward/items/43acda931c8a62c70d2f

最後に`map`によって、対応するフラグ名だけを配列として返されます。

これにより、**ビットフラグを簡単に人間が読める形式に変換できます。**

---

### ③ビットフラグのエンコード

次に、フラグ名の配列からビットフラグ値を生成する関数を定義します。

```typescript
const encodeFlags = (labels: string[]): number =>
  Object.entries(FLAGS)
    .filter(([, label]) => labels.includes(label))
    .reduce((acc, [key]) => acc | Number(key), 0)

// 使用例
console.log(encodeFlags(['シュモクザメ', 'ジンベイザメ'])) // 3
console.log(encodeFlags(['シュモクザメ', 'ウバザメ'])) // 5
console.log(encodeFlags(['シロワニ'])) // 128
```

#### 解説
デコード時と同様に`Object.entries`でキーとバリューを値に持つ配列を生成します。
```typescript
console.log(Object.entries(FLAGS))
// [['1', 'シュモクザメ'], ['2', 'ジンベイザメ'], ['4', 'ウバザメ'], ...]
```

次に`filter`によって、引数に含まれるフラグ名を抽出します。
```typescript
const encodeFlags = (labels: string[]): number =>
  Object.entries(FLAGS)
    .filter(([, label]) => labels.includes(label))
console.log(encodeFlags(['シュモクザメ', 'piyo'])) // [[1, "シュモクザメ"]]
console.log(encodeFlags(['hoge', 'ウバザメ'])) // [[4, "ウバザメ"]]
console.log(encodeFlags(['レモンザメ', 'シロワニ'])) // [[16, "レモンザメ"], [128, "シロワニ"]] 
```

最後に`reduce`によって、ビット値（キー）をOR演算（`|`）で合成して求めます。

これにより、**複数のフラグ（選択肢）を簡単に数値に変換できます。**





## Prismaとビットフラグの連携

### ①登録時にビットフラグをエンコード

Prismaでデータを登録する際に、フラグ名の配列をエンコードして保存します。

```typescript
import { PrismaClient } from '@prisma/client'

const prisma = new PrismaClient()

async function createUser(name: string, flags: string[]) {
  const flagsValue = encodeFlags(flags) // ← ここでエンコード（ビットフラグに変換）している！
  await prisma.user.create({
    data: { name, flags: flagsValue },
  })
}

createUser('鮫島鱶太郎', ['シュモクザメ', 'ダルマザメ'])
// 登録値としては以下のようなイメージ
// id: 1, name: '鮫島鱶太郎', flags: 65
```

### ②取得時にビットフラグをデコード

データを取得する際に、ビットフラグをデコードします。

```typescript
async function getUser(id: number) {
  const user = await prisma.user.findUnique({ where: { id } })

  return {
    ...user,
    flags: decodeFlags(user.flags), // ← ここでデコード（人間に読める値に変換）している！
  }
}

const exmapleUser = await getUser(1)
console.log(exmapleUser)
// id: 1, name: '鮫島鱶太郎', flags: ['シュモクザメ', 'ダルマザメ']
```



## ちょっと脱線：フラグマップを生成する関数
「①フラグマップ及び型の作成」にて、
```typescript
type FlagMap = Record<number, string>

const FLAGS: FlagMap = {
  1: 'シュモクザメ',
  2: 'ジンベイザメ',
  4: 'ウバザメ',
  8: 'ホホジロザメ',
  16: 'レモンザメ',
  32: 'ツマグロ',
  64: 'ダルマザメ',
  128: 'シロワニ',
}
```
のような感じでフラグマップを作成していたと思います。
フラグの種類がたくさん存在する場合（例えば「クジラ」「イルカ」など）、毎回同じようなフラグマップを作成するのは面倒なので、文字列が格納されている配列からフラグマップを生成する共通関数を作ってみました。

```typescript
const generateFlags = (keys: string[]): FlagMap => {
  return keys.reduce((acc, key, index) => {
    const bitValue = 1 << index // 2^index
    acc[bitValue] = key
    return acc
  }, {} as FlagMap)
}

const sharks = ['ウバザメ', 'ホホジロザメ', 'シロワニ']
const sharkFlags = generateFlags(sharks)
console.log(sharkFlags)
// {1: 'ウバザメ', 2: 'ホホジロザメ', 4: 'シロワニ'}
```

#### 解説
`reduce`を用いて、初期値`{}`から`FlagMap型`の計算結果を蓄積させるようにしています。


`bitValue`では2進数のビット値を生成しています。
**左ビットシフト**っていうらしいです。
```
index = 0 の場合: 1 << 0 = 1（2進数で 001）
index = 1 の場合: 1 << 1 = 2（2進数で 010）
index = 2 の場合: 1 << 2 = 4（2進数で 100）
```
あとは取得したビット値をキー名、引数の文字列`key`をバリューの値に設定することでフラグマップが生成されます。

```
acc[bitValue] = key

// 生成イメージ
// {}[1] = 'ウバザメ'
// {1: 'ウバザメ'}[2] = 'ホホジロザメ'
// {1: 'ウバザメ', 2: 'ホホジロザメ'}[4] = 'シロワニ'
// → {1: 'ウバザメ', 2: 'ホホジロザメ', 4: 'シロワニ'}
```



## まとめ
今回はPrismaを用いて、エンコード・デコードによってビットフラグを簡単に扱える仕組みを構築してみました。

ビットフラグは、効率的にフラグ（冒頭では選択肢）を管理できる手法です。

私自身、普段Webアプリケーションの開発をしていますが、あまり目にする機会はなかったです。。。
実務での採用はケースバイケースだと思いますが、ビットフラグに関する知見を深めることが出来て良かったです。
（あまり使われていないのかな？そのあたりの知見がなく、わかる方がいらっしゃったらコメントしてくれると嬉しいです！）


また、シンプルなフラグ管理が必要な場合は`enum`も有効な選択肢なのかなと思います。
`enum`の設定方法については、別の記事で解説できればと思っています。

最後まで読んでいただき、本当にありがとうございました！

## 最後に
他にもいろいろな記事を投稿しているので、もしよかったら見てみてください！

https://qiita.com/rtkjm22/items/cd45058a2631cd7086fd

https://qiita.com/rtkjm22/items/84d253631f518f8e6fff

ではでは！！