The Rails schema dumper can't capture CREATE FUNCTION statements, so every db:schema:dump silently drops the execute <<~SQL block that defines f_unaccent, breaking schema loading downstream (the functional GIN indexes reference the missing function). Mirror the existing schema-load enhance hook with a post-dump task that re-injects the block between the last enable_extension and the first create_table. Idempotent: skips when the block is already present. Also bundle the merge-upstream skill that documents the recurring merge patterns for this fork.
83 lines
3.4 KiB
Ruby
83 lines
3.4 KiB
Ruby
# Creates the `f_unaccent` wrapper function used by the internal chat search
|
|
# functional GIN trigram indexes.
|
|
#
|
|
# `schema.rb` can only capture `enable_extension`/`create_table`/indexes, not
|
|
# `CREATE FUNCTION`, so without this hook `db:schema:load` would fail trying to
|
|
# create indexes that reference the non-existent `f_unaccent` function.
|
|
#
|
|
# These tasks are wired from the Rakefile (after `Rails.application.load_tasks`):
|
|
# - `ensure_search_functions` runs BEFORE `db:schema:load` to create the function
|
|
# in the target database.
|
|
# - `inject_schema_functions` runs AFTER `db:schema:dump` to re-insert the
|
|
# `execute <<~SQL CREATE OR REPLACE FUNCTION ...` block the dumper drops.
|
|
|
|
SCHEMA_FUNCTION_MARKER = 'CREATE OR REPLACE FUNCTION f_unaccent'.freeze
|
|
|
|
SCHEMA_FUNCTION_BLOCK = <<~RUBY.gsub(/^(?=.)/, ' ').freeze
|
|
# Custom SQL functions (required before index creation)
|
|
execute <<~SQL
|
|
CREATE OR REPLACE FUNCTION f_unaccent(text)
|
|
RETURNS text LANGUAGE sql IMMUTABLE PARALLEL SAFE STRICT
|
|
AS $func$ SELECT public.unaccent('public.unaccent', $1) $func$
|
|
SQL
|
|
RUBY
|
|
|
|
# rubocop:disable Metrics/BlockLength
|
|
namespace :db do
|
|
namespace :internal_chat do
|
|
desc 'Ensure the f_unaccent wrapper function required by internal chat search indexes exists'
|
|
task ensure_search_functions: :load_config do
|
|
# `db:schema:load` in development iterates over BOTH the development AND
|
|
# test databases (see `ActiveRecord::Tasks::DatabaseTasks.each_current_environment`),
|
|
# so we need to install the f_unaccent function on every relevant config,
|
|
# not just the currently-connected one.
|
|
original_db_config = ActiveRecord::Base.connection_db_config
|
|
environments = [Rails.env]
|
|
environments << 'test' if Rails.env.development? && !ENV['SKIP_TEST_DATABASE'] && !ENV['DATABASE_URL']
|
|
|
|
environments.each do |env|
|
|
ActiveRecord::Base.configurations.configs_for(env_name: env).each do |db_config|
|
|
ActiveRecord::Base.establish_connection(db_config)
|
|
conn = ActiveRecord::Base.connection
|
|
conn.execute('CREATE EXTENSION IF NOT EXISTS unaccent')
|
|
conn.execute(<<~SQL.squish)
|
|
CREATE OR REPLACE FUNCTION public.f_unaccent(text)
|
|
RETURNS text
|
|
LANGUAGE sql
|
|
IMMUTABLE
|
|
PARALLEL SAFE
|
|
STRICT
|
|
AS $func$ SELECT public.unaccent('public.unaccent', $1) $func$
|
|
SQL
|
|
end
|
|
end
|
|
ensure
|
|
ActiveRecord::Base.establish_connection(original_db_config) if original_db_config
|
|
end
|
|
|
|
desc 'Inject the f_unaccent function block into db/schema.rb (run after db:schema:dump)'
|
|
task inject_schema_functions: :environment do
|
|
schema_path = Rails.root.join('db/schema.rb')
|
|
next unless File.exist?(schema_path)
|
|
|
|
content = File.read(schema_path)
|
|
next if content.include?(SCHEMA_FUNCTION_MARKER)
|
|
|
|
new_content = content.sub(
|
|
/(^ enable_extension "[^"]+"\n)(\n create_table)/,
|
|
"\\1\n#{SCHEMA_FUNCTION_BLOCK}\\2"
|
|
)
|
|
|
|
if new_content == content
|
|
warn '[inject_schema_functions] Could not find insertion point in db/schema.rb ' \
|
|
'(last enable_extension + create_table); f_unaccent block NOT injected. Add it manually.'
|
|
next
|
|
end
|
|
|
|
File.write(schema_path, new_content)
|
|
puts '-- Injected f_unaccent function block into db/schema.rb'
|
|
end
|
|
end
|
|
end
|
|
# rubocop:enable Metrics/BlockLength
|