🍆

SupabaseのINSERTやUPDATE時に含まれる絵文字の扱いを考える【文字数カウントと闘う】

2023/11/08に公開

Supabase + Next.js で開発するのであれば、フロントエンドから直接Supabaseに INSERT や UPDATE を行いたいところです。

文字数制限が無ければ何も考える必要はありません。しかし、制限を設ける場合はCHECK制約を設定する必要があります。SQLで char_length() を使うのですが、絵文字や「絵文字」や「サロゲートペア」が悩みの種となってしまいます。

例を出しながら見ていきましょう。

JavaScriptでの絵文字やサロゲートペアの振る舞い

const strings = "😀👨‍👩‍👧‍👦👨𨨞𨨞𨨞👩👧𧾷👦😀𨸶𨸶😀";
console.log(strings.length);

この結果は 37 となります。

PostgreSQLでの絵文字やサロゲートペアの振る舞い

select char_length('😀👨‍👩‍👧‍👦👨𨨞𨨞𨨞👩👧𧾷👦😀𨸶𨸶😀');

この結果は 20 となります。

JavaScriptとPostgreSQLでの乖離があるので扱いにくい

言語 結果
JavaScript 37
PostgreSQL 20

残念ながら上記の結果となってしまいました。
これではユーザーが投稿したい文字数と大きな乖離があるのでUX的にも問題があります。(GUI上では文字数問題ないのに送信時にエラーが出るなど。ユーザーの投稿意欲に悪影響を及ぼすので重大な問題。)

一応、JavaScriptでそれなりに正確に絵文字をカウントする方法があります。

https://zenn.dev/masa5714/articles/0e8663e9f98082

しかし、この方法を使うと、JavaScriptでのカウントは 14 となり、これまたPostgreSQLとの乖離が発生します。であれば、Edge Functions でバリデーションを任せればいいかと思うところですが、PostgreSQLのカラムのCHECK制約で絵文字が含まれることを想定した大きな値を設定することになるため、DBとしては好ましくありません。

この問題を解決するには妥協が必要(フロントエンドだけで INSERTやUPDATEをする場合に発生する妥協)

では、この問題をどうやって解決するかと言うと、「JavaScriptでのカウントの精度をやや落とし、PostgreSQLのカウントに合わせる。一致すればUX的には問題ないだろう。」という妥協に落ち着きました。

どうやって JavaScriptでのカウントを PostgreSQLのカウントに合わせるのか?

先ほどと同じ文字列でカウントしてみます。

const strings = "😀👨‍👩‍👧‍👦👨𨨞𨨞𨨞👩👧𧾷👦😀𨸶𨸶😀";
console.log(Array.from(strings).length);

を実行してみます。

なんと 20 という値が出力されました。
この処理の方法であれば JavaScript と PostgreSQLでのカウントが一致するので、満たしたい要件に到達しました!

ちなみに、残念ながら 👨‍👩‍👧‍👦 このような合成絵文字は 7 文字扱いになります。こういう不完全な部分が妥協点です。

はみ出るやばい文字も確認しておく

☜( ด็็็็็้้้้้็็็็้้้้้็็็็็้้้้้็็็็็้้้้้็็็็ ਊ ส้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้้ )☞ こういうやばいやつ。

▼ JavaScript

▼ PostgreSQL

これも一致しているので大きな問題なし。

改行不可の要件を満たすには?

改行不可や特定の文字列が含まれる場合は、CHECK制約で条件を作ると良いと思います。改行不可ぐらいの仕様であれば勝手にこっち側で削除してあげてもいいかもしれません。(基本的にフロント側で処理しておくが、Postman等で不正なPOSTをしてきた時用の対処として。)

トリガーで before のタイミングで値を加工して、加工された値を本来のINSERT文やUPDATE文として使ってもらえばいいんじゃないですか、ってChatGPTさんが言ってました。(間違ってもupdateトリガーでupdate処理を入れないように注意しましょう。無限ループになります。)

まとめ

  • 絵文字やサロゲートペアは見た通りの文字数としてカウントが難しい。
  • JavaScriptとPostgreSQLでの文字数カウントに乖離がある。
  • Edge Functionsを使えばバリデーションで比較的に正確なカウントが可能だが、CHECK制約の扱いが難しくなってしまう。
  • 妥協として、JavaScriptでのカウントをPostgreSQLと同じカウントになるようにすることにした。
  • PostgreSQLでCHECK制約を作るときは char_length() で設定しておけばフロントエンドとのカウント差が基本的に無いので、UIとバックエンドとの乖離がなくなり、UXが著しく低下する可能性を回避できる。
  • フロントエンドからSupabaseに直接INSERTやUPDATEができるので実装を単純化できる。

こんなところでしょうか。
再掲しますが、絵文字はフロントエンドでも割りと正確にカウントする方法はあります。Supabase + Next.jsという構成においては正確性を捨てて実装の単純化をゴールとしました。

https://zenn.dev/masa5714/articles/0e8663e9f98082

※CHECK制約とのギャップを埋められるなら正確にカウントしたいんですけどね...。

みなさんのご意見を募集!

この方法の問題性や、他の良い解決方法があればコメント欄で教えてください!
どんな些細な情報でも構いません。ヒントとなるキーワードがとにかく欲しいです!

Discussion