LoginSignup
4
1

More than 3 years have passed since last update.

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

Posted at

環境: Rails 6.0、PostgreSQL 12.2、MySQL 5.7

ActiveRecord::ValueTooLong

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

こういうテーブルがあって(MySQL)、

+------------+--------------+------+-----+---------+----------------+
| 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文字保存しようとすると、

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

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

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

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

  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 型
MySQL :: MySQL 5.7 Reference Manual :: 11.3.2 The CHAR and VARCHAR Types (5.7のマニュアルの日本語版はない)

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

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

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

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

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モードを設定しているらしい。

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

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. 文字型

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

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

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

以上です。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1