iachat/lib/tasks/internal_chat_search.rake
gabrieljablonski 35ea658548 chore(schema): auto-reinject f_unaccent block after schema dump
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.
2026-04-17 18:33:13 -03:00

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