CloudSpannerでENUMを使う方法

# 背景
CloudSpannerではENUMを使うことができるので使い方を調べてみました〜

# ENUM作り方・使い方
CloudSpannerは、基本的にgRPCで構成されているので対応する型を作って登録することができます。
そして、その型をCloudSpanner上でENUMとして使うことができます。下記が手順です。

1. protoファイル定義
    1. protocコマンド実行
    1. pbファイル生成
1. DDLファイル作成
1. gcloudコマンド実行
    1. 引数
        1. DDLファイル or DDL
        1. pbファイル
1. SQL実行時にENUMと紐づける

## protoファイル定義
```proto:product.proto
syntax = "proto3";

package product;

enum ProductType {
  HOGE = 0;
  HUGU = 1;
}
```

### protocコマンド実行
```bash
protoc --descriptor_set_out=product.pb product.proto
```

#### pbファイル爆誕

このファイルを使ってCloudSpannerに適用させていく。

```protobuf:product.pb
K
product.proto	product*%
ProductType	
HOGE	
HUGUbproto3
```

## DDLファイル作成

ENUMを使いたくば `CREATE PTORO BUNDLE` をCloudSpannerに適用しないといけないらしい。ソースは下記。

https://cloud.google.com/spanner/docs/reference/standard-sql/data-types#enum_type 

https://cloud.google.com/spanner/docs/reference/standard-sql/protocol-buffers#create_a_protocol_buffer 

そのためDDLファイルを作成した。

```sql:product.ddl
CREATE PROTO BUNDLE (
  product.ProductType
);
```

## gcloudコマンド実行

「ddlの適用」と「DDLが登録されているかを確認」をしている。

```bash
gcloud spanner databases ddl update hoge-db --instance=hoge-local --ddl-file=/app/cloudspanner/sql/product.ddl --proto-descriptors-file=/app/cloudspanner/product.pb
gcloud spanner databases ddl describe hoge-db --instance=hoge-local
```

### 実行結果

```bash
Schema updating...
.....done.
CREATE PROTO BUNDLE (
  product.ProductType,
);

CREATE TABLE SchemaMigrations (
  Version INT64 NOT NULL,
  Dirty BOOL NOT NULL,
) PRIMARY KEY(Version);
```

## SQL実行時にENUMとひもづける

cloudspannerを使っているのでwrenchを使いこなそうと思っている。
DB操作クライアント(dbeaver)をつかったDDLの実行は問題なくできたが、ENUMを使っているテーブルはマイグレーション管理ができなくなってしまうという課題感がでてきた。

### wrenchでマイグレーションできない

wrenchからマイグレーションファイルを生成して下記のように記述した。

```sql:wrenchのマイグレーション実行結果
CREATE TABLE products (
    id INT64 NOT NULL,
    product_type product.ProductType NOT NULL -- product.ProductTypeでエラー
) PRIMARY KEY(id);

-- ↓ 実行時エラー
-- 登録したenumが使えない...
-- Error command: up, version: 1.10.1
--   failed to parse DDL/DML statements: 000001.sql:3: got "product", want scalar type, 000001.sql:1.0: unknown DML statement
```

#### failed to parse DDL/DML statements

`wrench/pkg/spanner/migration.go` の `ddlToStatements` でこけていることがわかった。

https://github.com/cloudspannerecosystem/wrench/blob/91633744f840223ff511808381c61f1079450e8e/pkg/spanner/migration.go#L120

##### ddlToStatements

内部的にspansqlというものを使っているらしい。

https://github.com/googleapis/google-cloud-go/blob/48eb29554b4bfc602ab0786a2e17a633f2a0a72e/spanner/spansql/parser.go#L3041 

##### 処理の流れ

- ParseDDL
- parseStatements
- parseDDLStmt
- parseCreateTable
- parseColumnDef
- parseType
- parseBaseOrParameterizedType
    - ここでscalarにenumがないことが判明
        - https://github.com/googleapis/google-cloud-go/blob/48eb29554b4bfc602ab0786a2e17a633f2a0a72e/spanner/spansql/parser.go#L3049 
    - 返ってきたエラーは下記の処理から発生
        - https://github.com/googleapis/google-cloud-go/blob/48eb29554b4bfc602ab0786a2e17a633f2a0a72e/spanner/spansql/parser.go#L3070 

## 余談
ちなみにSpanSQLにENUM自体は次のリリースで対応されるらしい。

### ENUMに対応したPR
https://github.com/googleapis/google-cloud-go/pull/10945

### リリースタグ
https://github.com/googleapis/google-cloud-go/pull/11088

# ENUM使わないなら、どう制約かける？
- アプリケーション内でチェックをする
- SQL文にCHECK制約を使う

```sql
CREATE TABLE products (
    id STRING(36) NOT NULL,
    product_type STRING(10) NOT NULL,
    CHECK (product_type IN ('HOGE', 'HUGU'))
) PRIMARY KEY(id);
```