-
Notifications
You must be signed in to change notification settings - Fork 76
Schema-dump all user-created functions, not just 'public' #140
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,16 +10,19 @@ 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 | ||
LEFT JOIN pg_depend pd | ||
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<Fx::Function>] | ||
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) | ||
Comment on lines
+47
to
+48
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the nudge @teoljungberg! I'm also a In resolving some discrepancies within that gem, I ended up relying on the way that a core Rails contribution enabled schema-handling for enums: rails/rails#45740 I think it would make a ton of sense for both
Points 2 and 3 there are really crucial. This allows a |
||
) | ||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I considered moving both of these "schema-aware" mappers to |
||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The reason this method is necessary is because PG itself is not schema-aware when returning the function definition, and will, for example, include the fully-qualified name ( This is probably the correct behavior on PG's part, but it means that for |
||
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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some strategies go as far as ignoring everything in
pg_*
. I'm considering making that change still.(Unfortunately I have not found any other way to isolate user-created functions, and there are many examples that take a similar approach to what I have done here.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@smudge I think you can use this (I stupidly branched and created a PR before I saw this one)