viewy 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4edfe32f3664cfbed4af1113853229bdf49a4fe6
4
- data.tar.gz: 059ab8cabc83a216c16d3a4448df2446da678b92
3
+ metadata.gz: c1796596ee3676d14308739bce83b2578bf45c96
4
+ data.tar.gz: 4f2a4a1c577ca5e08dd0f79aacdbdcb34c09c221
5
5
  SHA512:
6
- metadata.gz: 3f69525541b993729623b09a2fa9b8173f905ab6877add2b46094ed5e6d780a083c5bc0477c41039c5e11b57a4fe697bbe6f226d0d6ad0cd08ec141487a59223
7
- data.tar.gz: 08a5cf414e622398d2e6049e139971e885455acf0ca0db1980e6da555cc317b63d1a17b1ae2319a28b72763d9c80477d2ef0a4d449aa3c48dd3f07577657dcdb
6
+ metadata.gz: 6c98415f23bbbf74f7235b97fcf906a0d2c9e9196f4e410a4be84052879ef651e287eab3743589159460c9f6591b06527bb9b4e99be444f5d5dca4230dd6d90b
7
+ data.tar.gz: 32b3c33a89c6e462d65a9b131f9bb8bccab58b82f21e55bdf118b08ea82defce73b4e6eaced63855bbc498eea4f1a3450363d4d00f793b44571a7ff83ef8c726
data/Rakefile CHANGED
@@ -18,6 +18,15 @@ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
18
18
  load 'rails/tasks/engine.rake'
19
19
 
20
20
 
21
+ load 'rails/tasks/statistics.rake'
22
+
23
+ require 'rspec/core'
24
+ require 'rspec/core/rake_task'
25
+
26
+ desc 'Run all specs in spec directory (excluding plugin specs)'
27
+ RSpec::Core::RakeTask.new(:spec)
28
+
29
+ task :default => :spec
21
30
 
22
31
  Bundler::GemHelper.install_tasks
23
32
 
@@ -0,0 +1,75 @@
1
+ class AddViewReplaceFunction < ActiveRecord::Migration
2
+ def up
3
+ function_sql = <<-SQL
4
+ CREATE OR REPLACE FUNCTION replace_view(view_name TEXT, new_sql TEXT) RETURNS VOID AS $$
5
+ DECLARE
6
+ ordered_oids INTEGER[] := ARRAY(
7
+ SELECT oid FROM
8
+ (
9
+ WITH RECURSIVE dependency_graph(oid, depth, path, cycle) AS (
10
+ SELECT oid, 1, ARRAY[oid], FALSE
11
+ FROM pg_class
12
+ WHERE relname = view_name
13
+ UNION
14
+ SELECT
15
+ rewrites.ev_class,
16
+ dg.depth + 1,
17
+ dg.path || rewrites.ev_class,
18
+ rewrites.ev_class = ANY(dg.path)
19
+ FROM dependency_graph dg
20
+ JOIN pg_depend dependents ON dependents.refobjid = dg.oid
21
+ JOIN pg_rewrite rewrites ON rewrites.oid = dependents.objid
22
+ WHERE NOT dg.cycle
23
+ )
24
+ SELECT dependency_graph.OID, MIN(depth) AS min_depth FROM dependency_graph GROUP BY dependency_graph.OID ORDER BY min_depth
25
+ ) ordered_dependents
26
+ );
27
+ create_statements TEXT[] := '{}';
28
+ current_statement TEXT;
29
+ view_drop_statement TEXT;
30
+ object_id INT;
31
+ BEGIN
32
+ FOREACH object_id IN ARRAY ordered_oids LOOP
33
+ SELECT
34
+ (CASE
35
+ WHEN (SELECT TRUE FROM pg_views WHERE viewname = relname) THEN
36
+ 'CREATE OR REPLACE VIEW ' || pg_class.relname || ' AS ' || pg_get_viewdef(oid)
37
+ WHEN (SELECT TRUE FROM pg_matviews WHERE matviewname = relname) THEN
38
+ 'CREATE MATERIALIZED VIEW ' || pg_class.relname || ' AS ' || pg_get_viewdef(oid)
39
+ END)
40
+ INTO current_statement
41
+ FROM pg_class
42
+ WHERE oid = object_id
43
+ AND pg_class.relname != view_name;
44
+ IF current_statement IS NOT NULL THEN
45
+ create_statements = create_statements || current_statement;
46
+ END IF;
47
+ END LOOP;
48
+ SELECT
49
+ (
50
+ CASE
51
+ WHEN (SELECT TRUE FROM pg_views WHERE viewname = view_name) THEN
52
+ 'DROP VIEW ' || view_name || ' CASCADE;'
53
+ WHEN (SELECT TRUE FROM pg_matviews WHERE matviewname = view_name) THEN
54
+ 'DROP MATERIALIZED VIEW ' || view_name || ' CASCADE;'
55
+ END
56
+ )
57
+ INTO view_drop_statement;
58
+ EXECUTE view_drop_statement;
59
+ EXECUTE new_sql;
60
+ FOREACH current_statement IN ARRAY create_statements LOOP
61
+ EXECUTE current_statement;
62
+ END LOOP;
63
+ END;
64
+ $$ LANGUAGE plpgsql;
65
+ SQL
66
+ execute function_sql
67
+ end
68
+
69
+ def down
70
+ function_drop_sql = <<-SQL
71
+ DROP FUNCTION replace_view(view_name TEXT, new_sql TEXT);
72
+ SQL
73
+ execute function_drop_sql
74
+ end
75
+ end
@@ -0,0 +1,80 @@
1
+ class AddViewDependencies < ActiveRecord::Migration
2
+ def up
3
+ view_dependency_function_sql = <<-SQL
4
+ CREATE OR REPLACE FUNCTION view_dependencies(materialized_view NAME)
5
+ RETURNS NAME[]
6
+ AS $$
7
+ WITH RECURSIVE dependency_graph(oid, depth, path, cycle) AS (
8
+ SELECT oid, 1, ARRAY[oid], FALSE
9
+ FROM pg_class
10
+ WHERE relname = materialized_view
11
+ UNION
12
+ SELECT
13
+ dependents.refobjid,
14
+ dg.depth + 1,
15
+ dg.path || dependents.refobjid,
16
+ dependents.refobjid = ANY(dg.path)
17
+ FROM dependency_graph dg
18
+ JOIN pg_rewrite rewrites ON rewrites.ev_class = dg.oid
19
+ JOIN pg_depend dependents ON dependents.objid = rewrites.oid
20
+ WHERE NOT dg.cycle
21
+ ), dependencies AS(
22
+ SELECT
23
+ (SELECT relname FROM pg_class WHERE pg_class.OID = dependency_graph.oid) AS view_name,
24
+ dependency_graph.OID,
25
+ MIN(depth) AS min_depth
26
+ FROM dependency_graph
27
+ GROUP BY dependency_graph.OID ORDER BY min_depth
28
+ )
29
+ SELECT ARRAY(SELECT dependencies.view_name FROM
30
+ dependencies
31
+ JOIN pg_matviews ON pg_matviews.matviewname = dependencies.view_name
32
+ WHERE dependencies.view_name != materialized_view)
33
+ ;
34
+ $$ LANGUAGE SQL;
35
+ SQL
36
+ execute view_dependency_function_sql
37
+
38
+ dependency_sql = <<-SQL
39
+ CREATE MATERIALIZED VIEW materialized_view_dependencies AS
40
+ WITH normal_view_dependencies AS (
41
+ SELECT viewname AS view_name, view_dependencies(viewname) FROM pg_views
42
+ )
43
+ SELECT matviewname AS view_name, view_dependencies(matviewname), TRUE AS materialized_view FROM pg_matviews WHERE matviewname != 'materialized_view_dependencies'
44
+ UNION
45
+ SELECT normal_view_dependencies.*, FALSE AS materialized_view FROM normal_view_dependencies WHERE array_length(normal_view_dependencies.view_dependencies, 1) > 0;
46
+ ;
47
+ SQL
48
+ execute dependency_sql
49
+
50
+ trigger_sql = <<-SQL
51
+ CREATE OR REPLACE FUNCTION refresh_materialized_view_dependencies() RETURNS EVENT_TRIGGER AS $$
52
+ DECLARE
53
+ view_exists BOOLEAN := (SELECT TRUE FROM pg_class WHERE pg_class.relname = 'materialized_view_dependencies' AND pg_class.relkind = 'm');
54
+ BEGIN
55
+ RAISE NOTICE 'refreshing view dependency hierarchy';
56
+ IF view_exists THEN
57
+ REFRESH MATERIALIZED VIEW materialized_view_dependencies;
58
+ END IF;
59
+ END
60
+ $$ LANGUAGE plpgsql;
61
+
62
+ CREATE EVENT TRIGGER view_dependencies_update
63
+ ON DDL_COMMAND_END
64
+ WHEN TAG IN ('DROP VIEW', 'DROP MATERIALIZED VIEW', 'CREATE VIEW', 'CREATE MATERIALIZED VIEW', 'ALTER VIEW', 'ALTER MATERIALIZED VIEW')
65
+ EXECUTE PROCEDURE refresh_materialized_view_dependencies();
66
+ SQL
67
+
68
+ execute trigger_sql
69
+ end
70
+
71
+ def down
72
+ down_sql = <<-SQL
73
+ DROP MATERIALIZED VIEW materialized_view_dependencies;
74
+ DROP FUNCTION view_dependencies(materialized_view NAME);
75
+ DROP EVENT TRIGGER view_dependencies_update;
76
+ DROP FUNCTION refresh_materialized_view_dependencies();
77
+ SQL
78
+ execute down_sql
79
+ end
80
+ end
@@ -0,0 +1,32 @@
1
+ module Viewy
2
+ # Provides a wrapper for materialized views that allows them and their dependencies to be refreshed easily
3
+ module ActsAsMaterializedView
4
+ extend ActiveSupport::Concern
5
+
6
+ class_methods do
7
+ # Refreshes this view and all materialized views it depends on.
8
+ # NOTE: the look-up for dependencies can take a second to run.
9
+ #
10
+ # @raise [ActiveRecord::RecordNotFoundError] raised when the view #refresh! is called on does not exist
11
+ # @raise [ActiveRecord::StatementInvalidError] raised if a dependent view is somehow not refreshed correctly
12
+ #
13
+ # @return [PG::Result] the result of the refresh statement on the materialized view
14
+ def refresh!
15
+ view_dep = Viewy::Models::MaterializedViewDependency.find(table_name)
16
+ view_dep.view_dependencies.each do |view_dependency|
17
+ connection.execute("REFRESH MATERIALIZED VIEW #{view_dependency}")
18
+ end
19
+ refresh_without_dependencies!
20
+ end
21
+
22
+ # Refreshes this view without refreshing any dependencies
23
+ #
24
+ # @raise [ActiveRecord::StatementInvalidError] raised if a dependent view is somehow not refreshed correctly
25
+ #
26
+ # @return [PG::Result] the result of the refresh statement on the materialized view
27
+ def refresh_without_dependencies!
28
+ connection.execute("REFRESH MATERIALIZED VIEW #{table_name}")
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,22 @@
1
+ module Viewy
2
+ # Provides a wrapper for SQL views that allows them and their materialized dependencies to be refreshed easily
3
+ module ActsAsView
4
+ extend ActiveSupport::Concern
5
+
6
+ class_methods do
7
+ # Refreshes this view and all materialized views it depends on.
8
+ # NOTE: the look-up for dependencies can take a second to run.
9
+ #
10
+ # @raise [ActiveRecord::RecordNotFoundError] raised when the view #refresh! is called on does not exist
11
+ # @raise [ActiveRecord::StatementInvalidError] raised if a dependent view is somehow not refreshed correctly
12
+ #
13
+ # @return [nil]
14
+ def refresh!
15
+ view_dep = Viewy::Models::MaterializedViewDependency.find(table_name)
16
+ view_dep.view_dependencies.each do |view_dependency|
17
+ connection.execute("REFRESH MATERIALIZED VIEW #{view_dependency}")
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,50 @@
1
+ require 'tsort'
2
+
3
+ module Viewy
4
+ class DependencyManager
5
+ include TSort
6
+
7
+ def initialize
8
+ connection.execute(refresh_sql('materialized_view_dependencies'))
9
+ end
10
+
11
+ def connection
12
+ ActiveRecord::Base.connection
13
+ end
14
+
15
+ def refresh_all_materialized_views
16
+ ordered_views.each do |view_name|
17
+ connection.execute(refresh_sql(view_name))
18
+ end
19
+ end
20
+
21
+ private def views
22
+ @views ||= generate_view_hash
23
+ end
24
+
25
+ private def generate_view_hash
26
+ views = Viewy::Models::MaterializedViewDependency.all.select(&:materialized_view).map do |dep|
27
+ [dep.view_name, dep.view_dependencies]
28
+ end
29
+ Hash[views]
30
+ end
31
+
32
+ private def tsort_each_node
33
+ views.each_key do |view|
34
+ yield view
35
+ end
36
+ end
37
+
38
+ private def tsort_each_child(view, &block)
39
+ views[view].each(&block)
40
+ end
41
+
42
+ private def refresh_sql(name)
43
+ <<-SQL.strip
44
+ REFRESH MATERIALIZED VIEW #{name}
45
+ SQL
46
+ end
47
+
48
+ alias_method :ordered_views, :tsort
49
+ end
50
+ end
data/lib/viewy/engine.rb CHANGED
@@ -1,5 +1,13 @@
1
1
  module Viewy
2
2
  class Engine < ::Rails::Engine
3
3
  isolate_namespace Viewy
4
+
5
+ initializer :append_migrations do |app|
6
+ unless app.root.to_s.match root.to_s
7
+ config.paths["db/migrate"].expanded.each do |path|
8
+ app.config.paths["db/migrate"] << path
9
+ end
10
+ end
11
+ end
4
12
  end
5
13
  end
@@ -0,0 +1,7 @@
1
+ require_relative 'materialized_views/dependency_list'
2
+
3
+ module Viewy
4
+ module MaterializedViews
5
+
6
+ end
7
+ end
@@ -0,0 +1,11 @@
1
+ module Viewy
2
+ module Models
3
+ # Provides a means of accessing information about view dependencies from within a Rails app.
4
+ # The foreign key of the dependency information table is the name of the view that a user needs
5
+ # dependency information about.
6
+ class MaterializedViewDependency < ActiveRecord::Base
7
+ self.table_name = 'materialized_view_dependencies'
8
+ self.primary_key = 'view_name'
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ require_relative 'models/materialized_view_dependency'
2
+
3
+ module Viewy
4
+ # The models namespace for viewy
5
+ module Models
6
+ end
7
+ end
data/lib/viewy/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Viewy
2
- VERSION = "0.0.1"
2
+ VERSION = '0.0.2'
3
3
  end
data/lib/viewy.rb CHANGED
@@ -1,4 +1,8 @@
1
- require "viewy/engine"
1
+ require 'viewy/engine'
2
+ require 'viewy/models'
3
+ require 'viewy/acts_as_view'
4
+ require 'viewy/acts_as_materialized_view'
5
+ require 'viewy/dependency_manager'
2
6
 
3
7
  module Viewy
4
8
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: viewy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Emerson Huitt
@@ -74,15 +74,22 @@ executables: []
74
74
  extensions: []
75
75
  extra_rdoc_files: []
76
76
  files:
77
- - LICENSE
78
77
  - README.rdoc
79
78
  - Rakefile
80
79
  - config/routes.rb
80
+ - db/migrate/20150929144540_add_view_replace_function.rb
81
+ - db/migrate/20150929205301_add_view_dependencies.rb
81
82
  - lib/tasks/viewy_tasks.rake
82
83
  - lib/viewy.rb
84
+ - lib/viewy/acts_as_materialized_view.rb
85
+ - lib/viewy/acts_as_view.rb
86
+ - lib/viewy/dependency_manager.rb
83
87
  - lib/viewy/engine.rb
88
+ - lib/viewy/materialized_views.rb
89
+ - lib/viewy/models.rb
90
+ - lib/viewy/models/materialized_view_dependency.rb
84
91
  - lib/viewy/version.rb
85
- homepage: https://github.com/SciMed/viewy
92
+ homepage: http://www.scimedsolutions.com
86
93
  licenses:
87
94
  - MIT
88
95
  metadata: {}
data/LICENSE DELETED
@@ -1,20 +0,0 @@
1
- Copyright 2015 YOURNAME
2
-
3
- Permission is hereby granted, free of charge, to any person obtaining
4
- a copy of this software and associated documentation files (the
5
- "Software"), to deal in the Software without restriction, including
6
- without limitation the rights to use, copy, modify, merge, publish,
7
- distribute, sublicense, and/or sell copies of the Software, and to
8
- permit persons to whom the Software is furnished to do so, subject to
9
- the following conditions:
10
-
11
- The above copyright notice and this permission notice shall be
12
- included in all copies or substantial portions of the Software.
13
-
14
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.