doctolib / safe-pg-migrations Goto Github PK
View Code? Open in Web Editor NEWMake your PostgreSQL migrations safe
License: MIT License
Make your PostgreSQL migrations safe
License: MIT License
safe-pg-migrations
doesn't support to add a default value on table with a UUID primary key.
If you do, you will get the following error:
PG::UndefinedFunction: ERROR: operator does not exist: uuid > integer
LINE 1: SELECT id FROM "mytable" WHERE id > 0 ORDER BY id LIMIT 10...
This issue is related to this line.
That safe-pg-migrations
supports both integer and uuid for primary key id.
Hi, thanks for creating an amazing gem.
I ran into an issue on the recent 1.4.0 update, getting an error undefined method 'table' for "index_name":String
when running a create table migration with reference
or belongs_to
I tested It on 1.3.0 and it works fine
My env:
ruby 2.7.4
rails 5.2.5
Sample migration
class CreateComments < ActiveRecord::Migration[5.2]
def change
create_table :comments do |t|
t.references :user, foreign_key: true
t.string :text, null: false
t.timestamps
end
end
end
Logs:
Caused by:
NoMethodError: undefined method `table' for "index_comments_on_user_id":String
Did you mean? tableize
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/safe-pg-migrations-1.4.0/lib/safe-pg-migrations/plugins/idempotent_statements.rb:10:in `add_index'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/safe-pg-migrations-1.4.0/lib/safe-pg-migrations/plugins/statement_insurer.rb:76:in `block in add_index'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/safe-pg-migrations-1.4.0/lib/safe-pg-migrations/plugins/statement_insurer.rb:121:in `block (2 levels) in without_timeout'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/safe-pg-migrations-1.4.0/lib/safe-pg-migrations/plugins/statement_insurer.rb:117:in `block in without_lock_timeout'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/safe-pg-migrations-1.4.0/lib/safe-pg-migrations/plugins/statement_insurer.rb:100:in `with_setting'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/safe-pg-migrations-1.4.0/lib/safe-pg-migrations/plugins/statement_insurer.rb:117:in `without_lock_timeout'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/safe-pg-migrations-1.4.0/lib/safe-pg-migrations/plugins/statement_insurer.rb:121:in `block in without_timeout'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/safe-pg-migrations-1.4.0/lib/safe-pg-migrations/plugins/statement_insurer.rb:113:in `block in without_statement_timeout'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/safe-pg-migrations-1.4.0/lib/safe-pg-migrations/plugins/statement_insurer.rb:100:in `with_setting'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/safe-pg-migrations-1.4.0/lib/safe-pg-migrations/plugins/statement_insurer.rb:113:in `without_statement_timeout'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/safe-pg-migrations-1.4.0/lib/safe-pg-migrations/plugins/statement_insurer.rb:121:in `without_timeout'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/safe-pg-migrations-1.4.0/lib/safe-pg-migrations/plugins/statement_insurer.rb:76:in `add_index'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/safe-pg-migrations-1.4.0/lib/safe-pg-migrations/plugins/useless_statements_logger.rb:15:in `add_index'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activerecord-5.2.5/lib/active_record/connection_adapters/abstract/schema_statements.rb:315:in `block in create_table'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activerecord-5.2.5/lib/active_record/connection_adapters/abstract/schema_statements.rb:314:in `each'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activerecord-5.2.5/lib/active_record/connection_adapters/abstract/schema_statements.rb:314:in `create_table'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/safe-pg-migrations-1.4.0/lib/safe-pg-migrations/plugins/blocking_activity_logger.rb:22:in `block (3 levels) in <module:BlockingActivityLogger>'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/safe-pg-migrations-1.4.0/lib/safe-pg-migrations/plugins/blocking_activity_logger.rb:65:in `log_blocking_queries'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/safe-pg-migrations-1.4.0/lib/safe-pg-migrations/plugins/blocking_activity_logger.rb:22:in `block (2 levels) in <module:BlockingActivityLogger>'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/safe-pg-migrations-1.4.0/lib/safe-pg-migrations/plugins/idempotent_statements.rb:58:in `create_table'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/safe-pg-migrations-1.4.0/lib/safe-pg-migrations/plugins/statement_insurer.rb:57:in `block in create_table'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/safe-pg-migrations-1.4.0/lib/safe-pg-migrations/plugins/statement_insurer.rb:100:in `with_setting'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/safe-pg-migrations-1.4.0/lib/safe-pg-migrations/plugins/statement_insurer.rb:56:in `create_table'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activerecord-5.2.5/lib/active_record/migration.rb:871:in `block in method_missing'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activerecord-5.2.5/lib/active_record/migration.rb:840:in `block in say_with_time'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activerecord-5.2.5/lib/active_record/migration.rb:840:in `say_with_time'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activerecord-5.2.5/lib/active_record/migration.rb:860:in `method_missing'
/Users/hassan/moodpath-rails/db/migrate/20220227212107_create_comments.rb:3:in `change'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activerecord-5.2.5/lib/active_record/migration.rb:814:in `exec_migration'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/safe-pg-migrations-1.4.0/lib/safe-pg-migrations/base.rb:81:in `block in exec_migration'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/safe-pg-migrations-1.4.0/lib/safe-pg-migrations/base.rb:34:in `block in setup_and_teardown'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/safe-pg-migrations-1.4.0/lib/safe-pg-migrations/plugins/statement_insurer.rb:100:in `with_setting'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/safe-pg-migrations-1.4.0/lib/safe-pg-migrations/base.rb:34:in `setup_and_teardown'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/safe-pg-migrations-1.4.0/lib/safe-pg-migrations/base.rb:80:in `exec_migration'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activerecord-5.2.5/lib/active_record/migration.rb:798:in `block (2 levels) in migrate'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activerecord-5.2.5/lib/active_record/migration.rb:797:in `block in migrate'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activerecord-5.2.5/lib/active_record/connection_adapters/abstract/connection_pool.rb:416:in `with_connection'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activerecord-5.2.5/lib/active_record/migration.rb:796:in `migrate'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activerecord-5.2.5/lib/active_record/migration.rb:977:in `migrate'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activerecord-5.2.5/lib/active_record/migration.rb:1292:in `block in execute_migration_in_transaction'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activerecord-5.2.5/lib/active_record/migration.rb:1345:in `ddl_transaction'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activerecord-5.2.5/lib/active_record/migration.rb:1291:in `execute_migration_in_transaction'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activerecord-5.2.5/lib/active_record/migration.rb:1263:in `block in migrate_without_lock'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activerecord-5.2.5/lib/active_record/migration.rb:1262:in `each'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activerecord-5.2.5/lib/active_record/migration.rb:1262:in `migrate_without_lock'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activerecord-5.2.5/lib/active_record/migration.rb:1210:in `block in migrate'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activerecord-5.2.5/lib/active_record/migration.rb:1363:in `with_advisory_lock'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activerecord-5.2.5/lib/active_record/migration.rb:1210:in `migrate'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activerecord-5.2.5/lib/active_record/migration.rb:1036:in `up'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activerecord-5.2.5/lib/active_record/migration.rb:1011:in `migrate'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activerecord-5.2.5/lib/active_record/tasks/database_tasks.rb:172:in `migrate'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activerecord-5.2.5/lib/active_record/railties/databases.rake:60:in `block (2 levels) in <top (required)>'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activesupport-5.2.5/lib/active_support/dependencies.rb:285:in `load'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activesupport-5.2.5/lib/active_support/dependencies.rb:285:in `block in load'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activesupport-5.2.5/lib/active_support/dependencies.rb:257:in `load_dependency'
/Users/hassan/.rvm/gems/ruby-2.7.4@moodpath-rails/gems/activesupport-5.2.5/lib/active_support/dependencies.rb:285:in `load'
Hi!
Some migrations unfortunately cannot be run safely and are then impossible to run.
When using a partitioned table on PostgreSQL, this migration, where slots
is the partitioned table, will fail because of 2 Postgres errors:
add_reference :slots, :variation, index: true, foreign_key: true
ActiveRecord::StatementInvalid: PG::WrongObjectType: ERROR: cannot add NOT VALID foreign key on partitioned table "slots" referencing relation "variations"
: since validate: false
is forcedPG::FeatureNotSupported: ERROR: cannot create index on partitioned table "stock_slots" concurrently
: since algorithm: :concurrently
is forcedIs there a way to explicitly disable the safeness in a block? If not, what about a block:
unsafe_migration do
add_reference :slots, :variation, index: true, foreign_key: true, validate: true
end
Telling: I know it is not safe, and then it can be caught before deployment to take appropriate measures.
Hi ๐
https://github.com/doctolib/safe-pg-migrations/blob/master/Gemfile.lock - the Gemfile is preventing to upgrade to Rails 7.1, as both activerecord
and activesupport
are currently specified as < 7.1
. Would be great to update this with Rails's new version.
Hello ๐
First of all thank you for sharing this very handy gem which works out of the box and solves such a great pain ๐
Problem
I ran into an issue using the latest 1.3.0
version of the gem on a ruby 2.7.4
rails 5.2.6
project.
Adding a foreign key to a table with add_reference
triggered an ArgumentError: unknown keyword: :column
when running rails db:migrate
. I reproduced the error on a vanilla rails new
project with the same versions to let you reproduce the ๐ easily ๐
Here is the backtrace ๐
== 20211015145629 AddRestaurantReferenceToReviews: migrating ==================
-- add_reference(:reviews, :restaurant, {:foreign_key=>true})
-> add_index("reviews", ["restaurant_id"], {:algorithm=>:concurrently})
rails aborted!
StandardError: An error has occurred, all later migrations canceled:
unknown keyword: :column
/Users/edwardschults/code/Eschults/safe-pg-add-reference/db/migrate/20211015145629_add_restaurant_reference_to_reviews.rb:3:in `change'
/Users/edwardschults/code/Eschults/safe-pg-add-reference/bin/rails:9:in `<top (required)>'
/Users/edwardschults/code/Eschults/safe-pg-add-reference/bin/spring:15:in `<top (required)>'
./bin/rails:3:in `load'
./bin/rails:3:in `<main>'
Caused by:
ArgumentError: unknown keyword: :column
/Users/edwardschults/code/Eschults/safe-pg-add-reference/db/migrate/20211015145629_add_restaurant_reference_to_reviews.rb:3:in `change'
/Users/edwardschults/code/Eschults/safe-pg-add-reference/bin/rails:9:in `<top (required)>'
/Users/edwardschults/code/Eschults/safe-pg-add-reference/bin/spring:15:in `<top (required)>'
./bin/rails:3:in `load'
./bin/rails:3:in `<main>'
Tasks: TOP => db:migrate
(See full trace by running task with --trace)
After debugging and step
-ping in the source code, I found out that the error was coming from this line, which changed between 1.2.3
and 1.3.0
with the addition of support for Ruby 3.0.
Note that when this error occurs, the creation of the column actually goes through, but not the creation of the schema_migrations
record, making any new rails db:migrate
run into the error again and preventing next migrations to run.
Solution
Downgrading to 1.2.3
solves the error on ruby 2.7 ๐
Cheers!
I've posted the description here.
Is it a flawed idea?
Would you be interested in a PR with such helpers?
Would you be open to adding an option to disable IdempotentStatements
?
Idempotency is great, but the current code doesn't go quite far enough currently to ensure actual idempotence imo. E.g. it only verifies that a column name exists, not that it matches the column being requested. I know I've made mistakes along those lines in the past, and I'd rather get an error message instead of having it be ignored.
Are you using the idempotency approach in general to lessen the effect of disabling transactional migrations? Are there other aspects I'm not thinking about?
We chose to go with this gem over strong-migrations because of the nicer logging. However if does not play well with a Gem called data-migrate. In the sense that the logging is really verbose, where updating thousands of records will create tens of thousands of migration logs.
So is there a way to disable the logging for some migrations but not others perhaps a class method or option ?
Those two don't play too well together when used via PgBouncer in transactional pooling mode.
Transactional pooling mode is the default for server-side (Postgres-side) PgBouncer installation on Heroku.
SET statement_timeout
/CREATE INDEX CONCURRENTLY
and reset of statement_timeout
could run in three different server connections. Because of disable_ddl_transaction!
. There are two problems here:
statement_timeout
has no effect on CREATE INDEX CONCURRENTLY
's timeout if they are executed in different server connectionsstatement_timeout
It makes sense to mention that somewhere in the documentation.
I can handle sending a PR if you confirm and approve the problem.
I had a migration which was:
add_column :video_chats, :source, :string, default: 'patient', null: false
And it added the column, added the default but failed when adding the null constraint.
Maybe it should add a CONSTRAINT CHECK
instead?
When using with Rails 6.1 I receive a lot of times error:
StandardError: An error has occurred, all later migrations canceled:
wrong number of arguments (given 4, expected 3)
Migration:
add_column :actions, :approval_level_id, :bigint, index: true
Is this gem compatible with latest Rails release?
Such a migration
class AddAwesomeNotesToPatient
def change
add_column :awesome_notes, :string, array: true, default: [], null: false
end
end
is using two alter table statements whereas it should only use one.
The ideal statement should be
ALTER TABLE "patients" ADD "awesome_notes" character varying[] DEFAULT '{}' NOT NULL
When using safe-pg-migrations together with https://github.com/ankane/strong_migrations, the latter warns about add_foreign_key
being a dangerous operation.
safe-pg-migrations makes it a safe operation by doing it in two steps (adding as not valid / validate), adding a foreign key could be marked as safe (as done with add_index
) by safe-pg-migrations
That is practically achievable by providing a SafeMigration
concern that users could include in their migrations.
It will:
This approach would still allow having migrations that have DDL transaction turned on.
currently debugging locally a (probably gem versioning) issue where this value
has a string timestamp like:
[["select pg_sleep(300), id from table limit 1;", "2021-09-27 14:58:18.677782+00"]]
and then the time formatter blows up with:
can't convert String into an exact number
--
0: /home/me/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/activesupport-5.2.6/lib/active_support/core_ext/time/calculations.rb:275:in `-'
1: /home/me/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/activesupport-5.2.6/lib/active_support/core_ext/time/calculations.rb:275:in `minus_with_duration'
2: /home/me/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/activesupport-5.2.6/lib/active_support/core_ext/time/calculations.rb:286:in `minus_with_coercion'
3: /home/me/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/safe-pg-migrations-1.2.3/lib/safe-pg-migrations/plugins/blocking_activity_logger.rb:115:in `format_start_time'
4: /home/me/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/safe-pg-migrations-1.2.3/lib/safe-pg-migrations/plugins/blocking_activity_logger.rb:100:in `block in output_blocking_queries'
5: /home/me/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/safe-pg-migrations-1.2.3/lib/safe-pg-migrations/plugins/blocking_activity_logger.rb:100:in `each'
6: /home/me/.rbenv/versions/2.7.3/lib/ruby/gems/2.7.0/gems/safe-pg-migrations-1.2.3/lib/safe-pg-migrations/plugins/blocking_activity_logger.rb:100:in `output_blocking_queries'
[...]
Steps to reproduce:
safety_assured
and within the block call a remove_column with 4 arguments.safety_assured do
remove_column :users, :first_name, :boolean, null: false, default: false
end
the 4th arguments is a hash containing null: false and default: false
error:
rake aborted!
StandardError: An error has occurred, all later migrations canceled:
wrong number of arguments (given 4, expected 2..3)
I've been looking at recent commits that were supplied, but wasn't able to find in which one this issue was introduced.
This gem is a fantastic idea! Thank you for the work to make it available to the community.
I'm attempting to use it in a project, and ran into an issue straight away.
$ bundle info safe-pg-migrations
* safe-pg-migrations (1.2.3)
Summary: Make your PG migrations safe.
Homepage: https://github.com/doctolib/safe-pg-migrations
Path: /Users/ylansegal/.asdf/installs/ruby/3.0.1/lib/ruby/gems/3.0.0/gems/safe-pg-migrations-1.2.3
Here is my test migration:
class AddTestIndex < ActiveRecord::Migration[6.1]
def change
add_index :users, :created_at
end
end
I can run db:migrate
and see the expected safe operations for concurrently:
$ rails db:migrate
== 20210617192601 AddTestIndex: migrating =====================================
-- add_index(:users, :created_at)
-> add_index("users", :created_at, {:algorithm=>:concurrently})
-> 0.0087s
== 20210617192601 AddTestIndex: migrated (0.0118s) ============================
However, I encounter an error when I try to rollback:
$ rails db:rollback
== 20210617192601 AddTestIndex: reverting =====================================
-- remove_index(:users, :created_at)
-> remove_index("users", {:column=>:created_at, :algorithm=>:concurrently})
rails aborted!
StandardError: An error has occurred, all later migrations canceled:
No indexes found on users with the options provided.
Caused by:
ArgumentError: No indexes found on users with the options provided.
Thank you!
Rename column is missing from the safe rails methods, is this intended?
safe-pg-migrations/lib/safe-pg-migrations/base.rb
Lines 84 to 92 in 267c2b7
I ask because the alter table statement to rename a column will need an access exclusive lock, no?
rails also drops and recreates the index, using add_index
which is a safe method.
Based on all of this, but I could be missing something, I would expect this gem to protect rename_column
.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.