Skip to content

Add a constraint to ensure application_settings.rate_limits is a hash

Abdul Wadood requested to merge 420321-add-rate-limits-hash-constraint into master

What does this MR do and why?

Add a constraint to ensure application_settings.rate_limits is a hash

In f89cc164, we introduced a JSONB column rate_limits to store the application rate limits. We want to ensure that we always store a hash in this column otherwise, the rails application will break. This can happen if a bad update query is executed like in the following case if we redefine the application_settings class:

class MyMigration

  class TmpSetting < ApplicationRecord
    self.table_name = :application_settings
  end

  def up
    TmpSetting.update(rate_limits: [1])
  end
end

MR acceptance checklist

Please evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

Migration output

up
bin/rails db:migrate
main: == [advisory_lock_connection] object_id: 183100, pg_backend_pid: 45389
main: == 20240115115029 AddRateLimitsHashConstraintToApplicationSettings: migrating =
main: -- transaction_open?(nil)
main:    -> 0.0000s
main: -- transaction_open?(nil)
main:    -> 0.0000s
main: -- execute("ALTER TABLE application_settings\nADD CONSTRAINT check_application_settings_rate_limits_is_hash\nCHECK ( (jsonb_typeof(rate_limits) = 'object') )\nNOT VALID;\n")
main:    -> 0.0036s
main: -- execute("SET statement_timeout TO 0")
main:    -> 0.0002s
main: -- execute("ALTER TABLE application_settings VALIDATE CONSTRAINT check_application_settings_rate_limits_is_hash;")
main:    -> 0.0006s
main: -- execute("RESET statement_timeout")
main:    -> 0.0002s
main: == 20240115115029 AddRateLimitsHashConstraintToApplicationSettings: migrated (0.0264s)

main: == [advisory_lock_connection] object_id: 183100, pg_backend_pid: 45389
ci: == [advisory_lock_connection] object_id: 183340, pg_backend_pid: 45391
ci: == 20240115115029 AddRateLimitsHashConstraintToApplicationSettings: migrating =
ci: -- transaction_open?(nil)
ci:    -> 0.0000s
ci: -- transaction_open?(nil)
ci:    -> 0.0000s
ci: -- execute("ALTER TABLE application_settings\nADD CONSTRAINT check_application_settings_rate_limits_is_hash\nCHECK ( (jsonb_typeof(rate_limits) = 'object') )\nNOT VALID;\n")
ci:    -> 0.0036s
ci: -- execute("SET statement_timeout TO 0")
ci:    -> 0.0002s
ci: -- execute("ALTER TABLE application_settings VALIDATE CONSTRAINT check_application_settings_rate_limits_is_hash;")
ci:    -> 0.0011s
ci: -- execute("RESET statement_timeout")
ci:    -> 0.0002s
ci: == 20240115115029 AddRateLimitsHashConstraintToApplicationSettings: migrated (0.0213s)

ci: == [advisory_lock_connection] object_id: 183340, pg_backend_pid: 45391
down
VERSION=20240115115029 bin/rails db:rollback:main && bin/rails db:rollback:ci
main: == [advisory_lock_connection] object_id: 182700, pg_backend_pid: 52189
main: == 20240115115029 AddRateLimitsHashConstraintToApplicationSettings: reverting =
main: -- transaction_open?(nil)
main:    -> 0.0000s
main: -- transaction_open?(nil)
main:    -> 0.0000s
main: -- execute("            ALTER TABLE application_settings\n            DROP CONSTRAINT IF EXISTS check_application_settings_rate_limits_is_hash\n")
main:    -> 0.0017s
main: == 20240115115029 AddRateLimitsHashConstraintToApplicationSettings: reverted (0.0140s)

main: == [advisory_lock_connection] object_id: 182700, pg_backend_pid: 52189
ci: == [advisory_lock_connection] object_id: 182640, pg_backend_pid: 52597
ci: == 20240115115029 AddRateLimitsHashConstraintToApplicationSettings: reverting =
ci: -- transaction_open?(nil)
ci:    -> 0.0000s
ci: -- transaction_open?(nil)
ci:    -> 0.0000s
ci: -- execute("            ALTER TABLE application_settings\n            DROP CONSTRAINT IF EXISTS check_application_settings_rate_limits_is_hash\n")
ci:    -> 0.0015s
ci: == 20240115115029 AddRateLimitsHashConstraintToApplicationSettings: reverted (0.0237s)

ci: == [advisory_lock_connection] object_id: 182640, pg_backend_pid: 52597

How to set up and validate locally

Update the rate_limits column using the below queries on the master branch and all of them will succeed. On this branch, only {} will be allowed.

update application_settings set rate_limits = '"a"';
update application_settings set rate_limits = '1';
update application_settings set rate_limits = '[]';
update application_settings set rate_limits = '{}';

select jsonb_typeof(rate_limits) from application_settings;

Related to #420321 (closed)

Edited by Abdul Wadood

Merge request reports