diff --git a/lib/fx/adapters/postgres/functions.rb b/lib/fx/adapters/postgres/functions.rb index eca39301..230cfa3e 100644 --- a/lib/fx/adapters/postgres/functions.rb +++ b/lib/fx/adapters/postgres/functions.rb @@ -10,8 +10,10 @@ class Functions # dumpable into `db/schema.rb`. FUNCTIONS_WITH_DEFINITIONS_QUERY = <<-EOS.freeze SELECT - pp.proname AS name, - pg_get_functiondef(pp.oid) AS definition + pp.proname AS name, + pn.nspname AS schema, + pg_get_functiondef(pp.oid) AS definition, + current_schema() AS current_schema FROM pg_proc pp JOIN pg_namespace pn ON pn.oid = pp.pronamespace @@ -19,7 +21,8 @@ class Functions ON pd.objid = pp.oid AND pd.deptype = 'e' LEFT JOIN pg_aggregate pa ON pa.aggfnoid = pp.oid - WHERE pn.nspname = 'public' AND pd.objid IS NULL + WHERE pn.nspname NOT IN ('pg_catalog', 'information_schema') + AND pd.objid IS NULL AND pa.aggfnoid IS NULL ORDER BY pp.oid; EOS @@ -39,7 +42,12 @@ def initialize(connection) # # @return [Array] def all - functions_from_postgres.map { |function| to_fx_function(function) } + functions_from_postgres.map do |function| + to_fx_function( + "name" => schema_aware_name(function), + "definition" => schema_aware_definition(function) + ) + end end private @@ -53,6 +61,25 @@ def functions_from_postgres def to_fx_function(result) Fx::Function.new(result) end + + def schema_aware_name(function) + if function.fetch("schema") == function.fetch("current_schema") + function.fetch("name") + else + "#{function.fetch("schema")}.#{function.fetch("name")}" + end + end + + def schema_aware_definition(function) + if function.fetch("schema") == function.fetch("current_schema") + function.fetch("definition").sub( + /CREATE OR REPLACE FUNCTION #{function.fetch("schema")}\./, + "CREATE OR REPLACE FUNCTION " + ) + else + function.fetch("definition") + end + end end end end diff --git a/spec/fx/adapters/postgres/functions_spec.rb b/spec/fx/adapters/postgres/functions_spec.rb index 35ef9f0e..4dd42f81 100644 --- a/spec/fx/adapters/postgres/functions_spec.rb +++ b/spec/fx/adapters/postgres/functions_spec.rb @@ -1,33 +1,53 @@ require "spec_helper" RSpec.describe Fx::Adapters::Postgres::Functions, :db do + def create_function_sql(name) + <<-FX.strip_heredoc + CREATE OR REPLACE FUNCTION #{name}() + RETURNS text + LANGUAGE plpgsql + AS $function$ + BEGIN + RETURN 'test'; + END; + $function$ + FX + end + describe ".all" do - it "returns `Function` objects" do + it "returns `Function` objects in all schemas" do + connection = ActiveRecord::Base.connection + connection.execute "CREATE SCHEMA test_schema" + connection.execute create_function_sql("public.test") + connection.execute create_function_sql("test_schema.test") + functions = Fx::Adapters::Postgres::Functions.new(connection).all + + expect(functions.size).to eq 2 + expect(functions[0].name).to eq "test" + expect(functions[0].definition).to eq create_function_sql("test") + expect(functions[1].name).to eq "test_schema.test" + expect(functions[1].definition).to eq create_function_sql("test_schema.test") + end + end + + context "when 'public' is not the default schema" do + it "returns `Function` objects with schema-aware names and definitions" do connection = ActiveRecord::Base.connection - connection.execute <<-EOS.strip_heredoc - CREATE OR REPLACE FUNCTION test() - RETURNS text AS $$ - BEGIN - RETURN 'test'; - END; - $$ LANGUAGE plpgsql; - EOS + search_path_was = connection.execute("SHOW search_path")[0]["search_path"] + connection.execute "SET search_path TO test_schema" + connection.execute "CREATE SCHEMA test_schema" + connection.execute create_function_sql("public.test") + connection.execute create_function_sql("test_schema.test") functions = Fx::Adapters::Postgres::Functions.new(connection).all - first = functions.first - expect(functions.size).to eq 1 - expect(first.name).to eq "test" - expect(first.definition).to eq <<-EOS.strip_heredoc - CREATE OR REPLACE FUNCTION public.test() - RETURNS text - LANGUAGE plpgsql - AS $function$ - BEGIN - RETURN 'test'; - END; - $function$ - EOS + expect(functions.size).to eq 2 + expect(functions[0].name).to eq "public.test" + expect(functions[0].definition).to eq create_function_sql("public.test") + expect(functions[1].name).to eq "test" + expect(functions[1].definition).to eq create_function_sql("test") + ensure + connection.execute "SET search_path TO #{search_path_was}" end end end