Skip to content

Commit

Permalink
Add topological sorting for dumped views using TSort
Browse files Browse the repository at this point in the history
Co-authored-by: Mykhaylo Sorochan <mykhaylo.sorochan@toptal.com>
Co-authored-by: Edward Loveall <edward@edwardloveall.com>
  • Loading branch information
3 people committed May 2, 2024
1 parent 1590e80 commit 4ea9399
Showing 1 changed file with 66 additions and 2 deletions.
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

0 comments on commit 4ea9399

Please sign in to comment.