viewy 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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.