ERROR: extension "btree_gist" must be installed in schema "heroku_ext" - ruby-on-rails

Heroku made a change to the way postgressql extensions gets installed
This is screwing up new rails review apps in heroku with the following error.
ERROR: extension "btree_gist" must be installed in schema "heroku_ext"
This is screwing up things as I need to drop existing extensions and re-enable with heroku_ext schema. I use bin/rails db:structure:load which is before running a migration.
Also the structure.sql is going to diverge as heroku add the schema in review app and we need to run the creation manually in local dev machine.
Does anybody came across this issue?

I have developed a monkey-patch solution that is, well, definitely a hack, but better than pre-dating migrations, or deleting lines from schema.rb.
Put this in config/initializers. I called mine _enable_extension_hack.rb to make sure it gets loaded early.
module EnableExtensionHerokuMonkeypatch
# Earl was here
def self.apply_patch!
adapter_const = begin
Kernel.const_get('ActiveRecord::ConnectionAdapters::PostgreSQLAdapter')
rescue NameError => ne
puts "#{self.name} -- #{ne}"
end
if adapter_const
patched_method = adapter_const.instance_method(:enable_extension)
# Only patch this method if it's method signature matches what we're expecting
if 1 == patched_method&.arity
adapter_const.prepend InstanceMethods
end
end
end
module InstanceMethods
def enable_extension(name)
name_override = name
if schema_exists?('heroku_ext')
puts "enable_extension -- Adding SCHEMA heroku_ext"
name_override = "#{name}\" SCHEMA heroku_ext -- Ignore trailing double quote"
end
super name_override
end
end
end
EnableExtensionHerokuMonkeypatch.apply_patch!
What does it do? It monkey-patches the enable_extension call that's causing the problem in db/schema.rb. Specifically, if it finds that there is a schema called "heroku_ext", it will decorate the parameter given to enable_extension so that the SQL that gets executed specifies the "heroku_ext" schema. Like this:
CREATE EXTENSION IF NOT EXISTS "pg_stat_statements" SCHEMA heroku_ext -- Ignore trailing double quote"
Without this hack, the generated SQL looks like this:
CREATE EXTENSION IF NOT EXISTS "pg_stat_statements"

That's cleaner way of patching schema.rb
# config/initializers/patch_enable_extension.rb
require 'active_record/connection_adapters/postgresql_adapter'
# NOTE: patch for https://devcenter.heroku.com/changelog-items/2446
module EnableExtensionHerokuPatch
def enable_extension(name, **)
return super unless schema_exists?("heroku_ext")
exec_query("CREATE EXTENSION IF NOT EXISTS \"#{name}\" SCHEMA heroku_ext").tap { reload_type_map }
end
end
module ActiveRecord
module ConnectionAdapters
class PostgreSQLAdapter
prepend EnableExtensionHerokuPatch
end
end
end

EDIT: Here’s the official changelog as a reference https://devcenter.heroku.com/changelog-items/2446
You'll need to do the unthinkable because of the recent heroku changes and modify past migration files for your review apps to work with the new Heroku system for extensions.
Add a predated, or at the top of the first migration file connection.execute 'CREATE SCHEMA IF NOT EXISTS heroku_ext'
Potentially also add to the database.yml a schema_search_path that includes heroku_ext (or set it to public,heroku_ext if you hadn't customized it)
Grep all enable_extension('extension_name')
Replace them all by a connection.execute('CREATE EXTENSION IF NOT EXISTS "extension_name" WITH SCHEMA "heroku_ext")
Pray that is enough to fix the problem
After making those changes we still had to contact heroku support because of, in order:
a pgaudit stack is not empty error
The fix here was to run a maintenance twice (because the postgres add-on which was scheduled for maintenance pre-dated the schema/extension changes change)
a ERROR: function pg_stat_statements_reset(oid, oid, bigint) does not exist error
The fix here was a manual intervention from heroku on the databases. It was caused by heroku trying to run a pg_stat_statements_reset each time a schema is created.

This hack seems to be working for me. The following script runs in the postdeploy step in app.json.
#!/bin/bash -xue
# Create extensions in the schema where Heroku requires them to be created
# The plpgsql extension has already been created before this script is run
heroku pg:psql -a $HEROKU_APP_NAME -c 'create extension if not exists citext schema heroku_ext'
heroku pg:psql -a $HEROKU_APP_NAME -c 'create extension if not exists pg_stat_statements schema heroku_ext'
# Remove enable_extension statements from schema.rb before loading it, since
# even 'create extension if not exists' fails when the schema is not heroku_ext
mv db/schema.rb{,.orig}
grep -v enable_extension db/schema.rb.orig > db/schema.rb
rails db:schema:load

As of Tuesday 16th August 2022, we've found that a patch no longer seems to be needed and enable_extension appears to be working seamlessly as normal without the need to explicitly specify a schema.
When I mentioned this in our ongoing thread with Heroku Support, they said that some of their fixes around the heroku_ext change may have been released at the time of resolving https://status.heroku.com/incidents/2450, but that there are still further fixes in development and yet to be released - so my guess is that this particular issue is one on those that has been fixed!

The answer supplied by dzirtusss got me most of the way there, the last thing I had to do to get this working for my Rails application was to edit the search paths in config/database.yml to include heroku_ext. This is what I appended to my default configuration.
# config/database.yml
default:
schema_search_path: public,heroku_ext
After adding the initialization script and the database.yml edit, I was able to successfully run a heroku pg:copy from one application to another with no errors and no unexpected behavior.

Heroku has introduced an --extensions flag recently, which helps as a workaround. See this FAQ.
For example, if you're using the btree_gist extension, you can use heroku run pg:reset --extensions 'btree_gist' --app YOUR_APP_NAME before running your migrations, they should then work without any changes in your codebase.

Use the Heroku datastore durability tool to create a backup on the
source database or heroku pg:backups:capture -a <SOURCE_APP>.
Determine which pg extensions the database uses (can check from psql
with \dx)
create a comma-separated string of the extensions (e.g.:fuzzystrmatch,pg_stat_statements,pg_trgm,pgcrypto,plpgsql,unaccent,uuid-ossp')
Make sure your Heroku CLI is updated to at least version 7.63.0 (use
heroku update to update)
Run this:
heroku pg:backups:restore $(heroku pg:backups public-url -a <SOURCE_APP>) DATABASE_URL --extensions '<EXTENSIONS>' -a <TARGET_APP>
Reset dynos on the TARGET_APP

Related

Rails 6, Schema file not matching migration files after switching Git branch

Rails 6, development with sqlite3.
My schema.rb has a file that shouldn't be there: no migration files (On current git branch) says it should be generated. By it's name I can see it's from an earlier branch that I abandoned, and wen't back to try a different approach to building my rails app.
To double check: I get a name error when I try to access the table in the Rails Console, so it's only there in the schema file, but not in the Database itself.
Can I force rails to run and or confirm that current schema is matching the migration files and if not matching, would run the migration?
Edit/Update:
I need to clarify that I have 6 migration files, that I went over to make sure none of them were from the earlier abandoned branch.
(admins is the table at issue)
ActiveRecord::Base.connection.tables
in the rails commandline generates:
["schema_migrations", "ar_internal_metadata", "events", "admins", "details"]
When I do Event I get the columns name and type. But when I do Admin I get
Traceback (most recent call last):
1: from (irb):3
NameError (uninitialized constant Admin)
So the issue is: How do I correctly reset the database to
The schema.rb file is auto-generated from the current state of the database, so just run rails db:migrate to re-generate schema.rb file
running rails db:migrate:reset removed seem to have fixed the issue:
My schema file does not have the 'admins' in it, and it does not show up when running ActiveRecord::Base.connection.tables in rails commandline.
I believe this was caused by Git, as it has the DB in the files: /db/*.sqlite3 and that 'admins' was created on a branch I abandoned earlier and never merged. So it was kept in the database, but the migration files were removed when I went back and created a new branch earlier on the timeline.

How to create database from schema.rb without initializing Rails?

I am trying to create all my tables from schema.rb
I used the command: "rake db:schema:load"
However, this fails because in one of my initializers, it is referencing a model/table that obviously doesn't exist (since the database is empty)
I could comment out these lines, and then run schema:load again, but is there an alternative?
Probably the fastest way is to just move the offending initializer to a temporary directory that is outside of the app, and then run your schema load. But if that doesn't work, or isn't an option for some reason, you could always work around that by creating a bare bones rails app to do the schema load:
Create a new rails app: rails new (app)-fixer
Copy your gemfile (unless there are specific exceptions) to the fixer app.
Copy your database.yml config to the fixer app.
Copy your schema.rb file to the fixer app.
Do all appropriate "bundle install" commands as needed for your app.
Then run "rake db:drop db:create db:schema:load"
That will build up a new database from scratch, based on your current schema.
You can add a check for the table existance in your initializer.
if TheModel.table_exists?
// do something with the model
end

ERROR: must be owner of language plpgsql

I'm using PostgreSQL v9.0.1 with Rails (and it's deps) # v2.3.8, owing to the use of the fulltext capability of postgres, I have a table which is defined as:
CREATE TABLE affiliate_products (
id integer NOT NULL,
name character varying(255),
model character varying(255),
description text,
price numeric(9,2),
created_at timestamp without time zone,
updated_at timestamp without time zone,
textsearch_vector tsvector,
);
Note the last line, this ensures that active record isn't able to process it with the standard schema dumper, so I have to set config.active_record.schema_format = :sql in ./config/environment.rb; and use rake db:test:clone_structure instead of rake db:test:clone.
None of this is too remarkable, only inconvenient - however rake db:test:clone_structure fails with the error:
ERROR: must be owner of language plpgsql
Because of line #16 in my resulting ./db/development_schema.sql:
CREATE OR REPLACE PROCEDURAL LANGUAGE plpgsql;
Under PostgreSQL v9.0+ the language plpsql is installed by the superuser, to the initial template, which is then available to the newly created schema.
I cannot run tests on this project without resolving this, and even editing ./db/development_schema.sql manually is futile as it is regenerated every time I run rake db:test:clone_structure (and ignored by rake db:test:clone).
I hope someone can shed some light on this?
Note: I have used both the pg 0.9.0 adapter gem, and the postgres gem at version 0.7.9.2008.01.28 - both display identical behaviour.
My teammates run PostgreSQL v8.4 where the language installation is a manual step.
I had the same problem. I fixed my template with the commands below
psql template1
template1=# alter role my_user_name with superuser;
read more at http://gilesbowkett.blogspot.com/2011/07/error-must-be-owner-of-language-plpgsql.html
For new readers, I read this older post after having run into this error in one of my own projects. I strongly feel that giving the app's PostgreSQL a superuser role is a terrible idea and changing the template is not ideal either. Since the referenced PSQL commands that are added by db:structure:dump are not needed by the Rails app's database, I have written a custom rake task that comments out the problematic lines in structure.sql. I have shared that code publicly on Github as a Gist at https://gist.github.com/rietta/7898366.
The solution was as follows:
On my installation, there are standard templates template0 and template1 - at least as I understand it postgres will look for the highest numbered templateN when creating a new database, unless the template is specified.
In this instance, as template0 included plpgsql, so did template1… the idea being that you will customise template1 to suite your site specific default needs, and in the case that you blow everything up, you would restore template1 from template0.
As my site specific requirement was to install plpgsql as part of the automated build of my web application (a step we had to keep to maintain 8.4 compatibility) - the solution was easy: remove plpgsql from template1 and the warning/error went away.
In the case that the site-specific defaults would change, and we should need to go back to the default behaviour, we would simply remove template1 and recreate it (which would use template0)
I encountered this error while attempting to do RAILS_ENV=development bundle exec rake db:reset. I was able to accomplish the same thing (for my purposes) by doing RAILS_ENV=development bundle exec rake db:drop db:create db:migrate instead.
I just filter the plpgsql extension statements from the structure.sql file post-dump:
# lib/tasks/db.rake
namespace :db do
desc "Fix 'ERROR: must be owner of extension plpgsql' complaints from Postgresql"
task :fix_psql_dump do |task|
filename = ENV['DB_STRUCTURE'] || File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, "structure.sql")
sql = File.read(filename)
sql.sub!(/(CREATE EXTENSION IF NOT EXISTS plpgsql)/, '-- \1')
sql.sub!(/(COMMENT ON EXTENSION plpgsql)/, '-- \1')
File.open(filename, 'w') do |f|
f.write(sql)
end
task.reenable
end
end
Rake::Task["db:structure:dump"].enhance do
Rake::Task["db:fix_psql_dump"].invoke
end

What is the preferred way to manage schema.rb in git?

I don't want to add schema.rb to .gitignore, because I want to be able to load a new database schema from that file. However, keeping it checked in is causing all sorts of spurious conflicts that are easily resolved by a fresh db:migrate:reset.
Basically I want a way to:
Keep schema.rb in the repository for deploy-time database setup
Keep schema.rb in '.gitignore' for general development
There would be one or two people responsible for updating schema.rb and knowing that it was correct.
Is there a way I can have my cake and eat it, too?
I'm afraid the magic solution you're looking for does not exist. This file is normally managed in version control, then for any conflicts on the version line just choose the later of the two dates. As long as you're also running all of the associated migrations nothing should get out of sync this way. If two developers have caused modifications to a similar area of schema.rb and you get conflicts in addition to the version then you are faced with a normal merge conflict resolution, but in my opinion these are normally easy to understand and resolve. I hope this helps some!
One other thing you can do is use:
git update-index --assume-unchanged /path/schema.rb
This will keep the file in the repository but won't track changes. you can switch the tracking anytime by using:
git update-index --no-assume-unchanged /path/schema.rb
What has worked really well for me is to delete and .gitignore schema.rb and then have it regenerated for each developer when they rake db:migrate.
You can still achieve what you wanted without migrating from 0 and risking broken migrations from years ago by simply doing a "roll-up" of the migrations periodically. You can do this by:
Run all outstanding migrations with rake db:migrate
Taking the contents of your schema.rb in the ActiveRecord::Schema.define block
Paste it into your initial_schema migration inside def up (overwriting what's already there)
Delete all other migrations
Now your initial_schema migration is your starting point for new systems and you don't have to worry about conflicts in schema.rb that may not be resolved correctly. It's not magical, but it works.
Would it be sufficient to do a rake db:dump in a pre-commit git hook?
The following won't necessarily fix (1) or (2), but it might take care of the merging issue, and then maybe (1) and (2) go away.
Instead of using .gitignore, use separate branches: Develop which omits schema.rb and Test and Deploy which include schema.rb. Only make code changes in the Develop branches and never merge from Test into Develop. Keep schema.rb in a separate branch:
Developer A
Develop --------
Local Schema \ Your Repo
Test ---------> Dev A
---------> Dev B
Developer B / Master
Develop -------- Schema
Local Schema Test
Test Deploy
In Git, branches are pointers to collections of file contents, so they can include or exclude particular files as well as track file versions. This makes them flexible tools for building your particular workflow.
You could define a merge strategy.
I've found this solution, but dont remember the source
[merge "railsschema"]
name = newer Rails schema version
driver = "ruby -e '\n\
system %(git), %(merge-file), %(--marker-size=%L), %(%A), %(%O), %(%B)\n\
b = File.read(%(%A))\n\
b.sub!(/^<+ .*\\nActiveRecord::Schema\\.define.:version => (\\d+). do\\n=+\\nActiveRecord::Schema\\.define.:version => (\\d+). do\\n>+ .*/) do\n\
%(ActiveRecord::Schema.define(:version => #{[$1, $2].max}) do)\n\
end\n\
File.open(%(%A), %(w)) {|f| f.write(b)}\n\
exit 1 if b.include?(%(<)*%L)'"
put this "somewhere" and
git-config --global core.attributesfile "somewhere"
I built a gem to solve this problem.
It sorts columns, index names and foreign keys, removes excess whitespace and runs Rubocop for some formatting to unify the output of your schema.rb file.
https://github.com/jakeonrails/fix-db-schema-conflicts
After you add it to your Gemfile you just run rake db:migrate or rake db:schema:dump like normal.
Commit schema.rb file.
Run git pull (or continue with what you're doing)
Every time you migrate the database, the schema.rb file updates and appears in git status. When working on something and occasionally doing git pull, this can be annoying because you have to commit schema.rb file before pulling to resolve conflict. This means that every time you migrate the database, you need to commit schema.rb file.
schema.rb should be tracked Git, of course.
I've just released this gem that can solve an issue with "conflicts" between branches for good.
The idea of that gem is simple. It keeps all migrated migrations inside tmp folder so that Git ignores them. It's just only your local story. These files are needed to roll back the "unknown" migrations being in another branch. Now, whenever you have an inconsistent DB schema due to running migrations in some other branch just run rails db:migrate inside the current branch and it will fix the issue automatically. The gem does all this magic automatically for you.

Run a single migration file

Is there an easy way to run a single migration? I don't want to migrate to a certain version I just want to run a specific one.
Assuming fairly recent version of Rails you can always run:
rake db:migrate:up VERSION=20090408054532
Where version is the timestamp in the filename of the migration.
Edit: At some point over the last 8 years (I'm not sure what version) Rails added checks that prevent this from running if it has already been run. This is indicated by an entry in the schema_migrations table. To re-run it, simply execute rake db:migrate:redo VERSION=20090408054532 instead.
You can just run the code directly out of the ruby file:
rails console
>> require "db/migrate/20090408054532_add_foos.rb"
>> AddFoos.new.up
Note: Very old versions of rails may require AddFoos.up rather than AddFoos.new.up.
An alternative way (without IRB) which relies on the fact that require returns an array of class names:
script/runner 'require("db/migrate/20090408054532_add_foos.rb").first.constantize.up'
Note that if you do this, it won't update the schema_migrations table, but it seems like that's what you want anyway.
Additionally, if it can't find the file you may need to use require("./db/..." or try require_relative depending on your working directory
If you want to run a specific migration, do
$ rake db:migrate:up VERSION=20080906120000
If you want to run migrations multiple times, do
# use the STEP parameter if you need to go more than one version back
$ rake db:migrate:redo STEP=3
If you want to run a single migration multiple times, do
# this is super useful
$ rake db:migrate:redo VERSION=20080906120000
(you can find the version number in the filename of your migration)
Edit: You can also simply rename your migration file, Eg:
20151013131830_my_migration.rb -> 20151013131831_my_migration.rb
Then migrate normally, this will treat the migration as a new one (usefull if you want to migrate on a remote environment (such as staging) on which you have less control.
Edit 2: You can also just nuke the migration entry in the database. Eg:
rails_c> q = "delete from schema_migrations where version = '20151013131830'"
rails_c> ActiveRecord::Base.connection.execute(q)
rake db:migrate will then rerun the up method of the nuked migrations.
If you've implemented a change method like this:
class AddPartNumberToProducts < ActiveRecord::Migration
def change
add_column :products, :part_number, :string
end
end
You can create an instance of the migration and run migrate(:up) or migrate(:down) on an instance, like this:
$ rails console
>> require "db/migrate/20090408054532_add_part_number_to_products.rb"
>> AddPartNumberToProducts.new.migrate(:down)
This are the steps to run again this migration file "20150927161307_create_users.rb"
Run the console mode. (rails c)
Copy and past the class which is in that file to the console.
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :name
t.string :email
t.timestamps null: false end
end
end
end
Create an instance of the class CreateUsers: c1 = CreateUsers.new
Execute the method change of that instance: c1.change
As of rails 5 you can also use rails instead of rake
Rails 3 - 4
# < rails-5.0
rake db:migrate:up VERSION=20160920130051
Rails 5
# >= rails-5.0
rake db:migrate:up VERSION=20160920130051
# or
rails db:migrate:up VERSION=20160920130051
If you're having trouble with paths you can use
require Rails.root + 'db/migrate/20090408054532_add_foos.rb'
If you want to run it from console, this is what you are looking for:
$ rails console
irb(main)> require "#{Rails.root.to_s}/db/migrate/XXXXX_my_migration.rb"
irb(main)> AddFoo.migrate(:up)
I tried the other answers, but requiring without Rails.root didnt work for me.
Also, .migrate(:up) part forces the migration to rerun regardless if it has already run or not. This is useful for when you already ran a migration, have kinda undone it by messing around with the db and want a quick solution to have it up again.
Method 1 :
rake db:migrate:up VERSION=20080906120000
Method 2:
In Rails Console
1. Copy paste the migration class in console (say add_name_to_user.rb)
2. Then in console, type the following
Sharding.run_on_all_shards{AddNameToUser.up}
It is done!!
Please notice that instead of script/runner, you may have to use rails runner on new rails environments.
Looks like at least in the latest Rails release (5.2 at the time of writing) there is one more way of filtering the migrations being ran. One can pass a filter in a SCOPE environment variable which would be then used to select migration files.
Assuming you have two migration files 1_add_foos.rb and 2_add_foos.run_this_one.rb running
SCOPE=run_this_one rails db:migrate:up
will select and run only 2_add_foos.run_this_one.rb. Keep in mind that all migration files matching the scope will be ran.

Resources