Skip to content
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

Add topological sorting for dumped views via TSort #398

Closed
wants to merge 12 commits into from
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ jobs:
strategy:
fail-fast: false
matrix:
ruby: ["2.7", "3.0", "3.1", "3.2"]
rails: ["6.1", "7.0"]
ruby: ["3.1", "3.2", "3.3"]
rails: ["6.1", "7.0", "7.1", "main"]
continue-on-error: [false]
exclude:
- ruby: "3.2"
Expand Down
3 changes: 3 additions & 0 deletions .mailmap
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Caleb Hearth <[email protected]> <[email protected]>
Derek Prior <[email protected]> <[email protected]>
Devon Estes <[email protected]> <[email protected]>
9 changes: 4 additions & 5 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,13 @@ source "https://rubygems.org"
# Specify your gem's dependencies in scenic.gemspec
gemspec

rails_version = ENV.fetch("RAILS_VERSION", "6.1")
rails_version = ENV.fetch("RAILS_VERSION", "7.1")

rails_constraint = if rails_version == "master"
rails_constraint = if rails_version == "main"
{github: "rails/rails"}
else
"~> #{rails_version}.0"
end

gem "rails", rails_constraint
gem "sprockets", "< 4.0.0"
gem "pg", "~> 1.1"
gem "activerecord", rails_constraint
gem "railties", rails_constraint
2 changes: 2 additions & 0 deletions lib/scenic/adapters/postgres/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ def postgresql_version
end
end

def with_connection(&) = connection_pool.with_connection(&)

private

def undecorated_connection
Expand Down
68 changes: 66 additions & 2 deletions lib/scenic/schema_dumper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,34 @@
module Scenic
# @api private
module SchemaDumper
# A hash to do topological sort
class TSortableHash < Hash
include TSort

alias_method :tsort_each_node, :each_key
def tsort_each_child(node, &)
fetch(node).each(&)
end
end

# Query for the dependencies between views
DEPENDENT_SQL = <<~SQL.freeze
SELECT distinct dependent_ns.nspname AS dependent_schema
, dependent_view.relname AS dependent_view
, source_ns.nspname AS source_schema
, source_table.relname AS source_table
FROM pg_depend
JOIN pg_rewrite ON pg_depend.objid = pg_rewrite.oid
JOIN pg_class as dependent_view ON pg_rewrite.ev_class = dependent_view.oid
JOIN pg_class as source_table ON pg_depend.refobjid = source_table.oid
JOIN pg_namespace dependent_ns ON dependent_ns.oid = dependent_view.relnamespace
JOIN pg_namespace source_ns ON source_ns.oid = source_table.relnamespace
WHERE dependent_ns.nspname = ANY (current_schemas(false)) AND source_ns.nspname = ANY (current_schemas(false))
AND source_table.relname != dependent_view.relname
AND source_table.relkind IN ('m', 'v') AND dependent_view.relkind IN ('m', 'v')
ORDER BY dependent_view.relname;
SQL

def tables(stream)
super
views(stream)
Expand All @@ -22,11 +50,47 @@ def views(stream)
private

def dumpable_views_in_database
@dumpable_views_in_database ||= Scenic.database.views.reject do |view|
ignored?(view.name)
@ordered_dumpable_views_in_database ||= begin
existing_views = Scenic.database.views.reject do |view|
ignored?(view.name)
end

tsorted_views(existing_views.map(&:name)).map do |view_name|
existing_views.find do |ev|
ev.name == view_name || ev.name == view_name.split(".").last
end
end.compact
end
end

# When dumping the views, their order must be topologically
# sorted to take into account dependencies
def tsorted_views(views_names)
views_hash = TSortableHash.new

::Scenic.database.execute(DEPENDENT_SQL).each do |relation|
source_v = [
relation["source_schema"],
relation["source_table"]
].compact.join(".")
dependent = [
relation["dependent_schema"],
relation["dependent_view"]
].compact.join(".")
views_hash[dependent] ||= []
views_hash[source_v] ||= []
views_hash[dependent] << source_v
views_names.delete(relation["source_table"])
views_names.delete(relation["dependent_view"])
end

# after dependencies, there might be some views left
# that don't have any dependencies
views_names.sort.each { |v| views_hash[v] ||= [] }

views_hash.tsort
end

unless ActiveRecord::SchemaDumper.private_instance_methods(false).include?(:ignored?)
# This method will be present in Rails 4.2.0 and can be removed then.
def ignored?(table_name)
Expand Down
6 changes: 3 additions & 3 deletions scenic.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ Gem::Specification.new do |spec|
spec.add_development_dependency "database_cleaner"
spec.add_development_dependency "rake"
spec.add_development_dependency "rspec", ">= 3.3"
spec.add_development_dependency "pg", "~> 0.19"
spec.add_development_dependency "pry"
spec.add_development_dependency "pg", "~> 1.1"
spec.add_development_dependency "ammeter", ">= 1.1.3"
spec.add_development_dependency "yard"
spec.add_development_dependency "redcarpet"
spec.add_development_dependency "standard"
spec.add_development_dependency "sprockets", "< 4.0.0"

spec.add_dependency "activerecord", ">= 4.0.0"
spec.add_dependency "railties", ">= 4.0.0"

spec.required_ruby_version = ">= 2.3.0"
spec.required_ruby_version = ">= 3.1"
end
Loading
Loading