長い文字列をカラムに入れて保存しようとするとどうなるか

環境: Rails 6.0、PostgreSQL 12.2、MySQL 5.7

## ActiveRecord::ValueTooLong

テーブルにレコードを保存するとき、255文字のように長さに制限があるカラムに300文字の文字列を指定して保存しようとするとどうなるでしょう。

こういうテーブルがあって（MySQL）、

```text
+------------+--------------+------+-----+---------+----------------+
| Field      | Type         | Null | Key | Default | Extra          |
+------------+--------------+------+-----+---------+----------------+
| id         | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| name       | varchar(255) | YES  |     | NULL    |                |
| created_at | datetime(6)  | NO   |     | NULL    |                |
| updated_at | datetime(6)  | NO   |     | NULL    |                |
+------------+--------------+------+-----+---------+----------------+
```

バリデーションを記述せずに300文字保存しようとすると、

```Ruby
User.create!(name: "a" * 300)
```

例外 `ActiveRecord::ValueTooLong` が発生します。

```text
ActiveRecord::ValueTooLong (Mysql2::Error: Data too long for column 'name' at row 1)
```

PostgreSQLの場合は、マイグレーションの`t.string`がデフォルトで制限なしの `character varying` になるので、サイズ制限を付けて試します。

```ruby
  def change
    create_table :users do |t|
      t.string :name, limit: 255
      t.timestamps
    end
  end
```

300文字保存しようとすると、やはり例外 `ActiveRecord::ValueTooLong` が発生します。

```
ActiveRecord::ValueTooLong (PG::StringDataRightTruncation: ERROR:  value too long for type character varying(255))
```

## MySQLの厳密なSQLモード

これを試してみたのは、「長い文字列は自動的に切り落とされるんじゃなかったけ？」という記憶がなんとなくあったからです。MySQLのマニュアルを見てみると……

[MySQL :: MySQL 5.6 リファレンスマニュアル :: 11.4.1 CHAR および VARCHAR 型](https://dev.mysql.com/doc/refman/5.6/ja/char.html)
[MySQL :: MySQL 5.7 Reference Manual :: 11.3.2 The CHAR and VARCHAR Types](https://dev.mysql.com/doc/refman/5.7/en/char.html) （5.7のマニュアルの日本語版はない）

> 厳密な SQL モードが有効でない場合に、CHAR または VARCHAR カラムにその最大長を超える値を割り当てると、その値はカラムの最大長に合わせて切り捨てられ、警告メッセージが表示されます。スペース以外の文字の切り捨てに関しては、厳密な SQL モードを使用することで、警告ではなくエラーを発生させて、その値の挿入を抑制できます。

とあります。昔のMySQLには「厳密なSQLモード」（strict SQL mode）がなかったせいで切り落とされていたのかな。厳密なSQLモードのオンオフを切り替えるには……

- [MySQL :: MySQL 5.6 リファレンスマニュアル :: 5.1.7 サーバー SQL モード](https://dev.mysql.com/doc/refman/5.6/ja/sql-mode.html)
- [MySQL :: MySQL 5.7 Reference Manual :: 5.1.10 Server SQL Modes](https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html)（5.7のマニュアルの日本語版はない）

>  厳密な SQL モードは、STRICT_ALL_TABLES または STRICT_TRANS_TABLES のいずれかが有効な場合に有効になりますが、これらのモードの影響はいくらか異なります。

MySQL 5.7のデフォルトSQLモードからSTRICT_TRANS_TABLESを外したものをmy.cnfに設定して試します。

```text
sql-mode="ONLY_FULL_GROUP_BY,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION"
```

やっぱり`ActiveRecord::ValueTooLong` が出ます。が、Railsのログで次の出力に気付きました。Railsは自動的に厳密なSQLモードを設定しているらしい。

```text
SET NAMES utf8mb4,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
```

Railsでこれをオフにするには、database.ymlで `strict: false` を設定します。（Rails Guides には見当たらない。参照: https://stackoverflow.com/questions/21420122/how-to-turn-off-mysql-strict-mode-in-rails/21894108 ）

```text
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: xxxxxxxx
  socket: /tmp/mysql.sock
  strict: false
```

Railsによって厳密モードが外され、長い文字列が切り落とされるようになりました。my.cnf関係なかったですね。

```
irb(main):001:0> User.create!(name: "a" * 300)
   (11.2ms)  SET NAMES utf8mb4,  @@SESSION.sql_mode = CONCAT(REPLACE(REPLACE(REPLACE(@@sql_mode, 'STRICT_TRANS_TABLES', ''), 'STRICT_ALL_TABLES', ''), 'TRADITIONAL', ''), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
   (0.2ms)  BEGIN
  User Create (0.3ms)  INSERT INTO `users` (`name`, `created_at`, `updated_at`) VALUES ('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', '2020-04-11 09:01:39.414519', '2020-04-11 09:01:39.414519')
   (11.6ms)  COMMIT
=> #<User id: 1, name: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...", created_at: "2020-04-11 09:01:39", updated_at: "2020-04-11 09:01:39">
```

## 末尾の空白は切り落とされる

PostgreSQLのマニュアルを見ていたところ、次の記述に気付きました。

[PostgreSQL 11.5文書 第8章 データ型 8.3. 文字型](https://www.postgresql.jp/document/11/html/datatype-character.html)

> ただし、上限を超えた部分にある文字がすべて空白の場合はエラーにはならず、文字列の最大長にまで切り詰められます。 （この一風変わった例外は標準SQLで要求されています。） 

そんな仕様があるとは。試してみたところ、MySQL（厳密モード）でもPostgreSQLでも空白が切り落とされました。

```text
irb(main):002:0> User.create!(name: "a" * 150 + " " * 150)
   (0.4ms)  BEGIN
  User Create (0.4ms)  INSERT INTO `users` (`name`, `created_at`, `updated_at`) VALUES ('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa                                                                                                                                                      ', '2020-04-11 11:00:30.125770', '2020-04-11 11:00:30.125770')
   (5.8ms)  COMMIT
=> #<User id: 1, name: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...", created_at: "2020-04-11 11:00:30", updated_at: "2020-04-11 11:00:30">
irb(main):003:0> User.last.name.size
  User Load (0.5ms)  SELECT `users`.* FROM `users` ORDER BY `users`.`id` DESC LIMIT 1
=> 255
```

以上です。