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 +4 -4
- data/Rakefile +9 -0
- data/db/migrate/20150929144540_add_view_replace_function.rb +75 -0
- data/db/migrate/20150929205301_add_view_dependencies.rb +80 -0
- data/lib/viewy/acts_as_materialized_view.rb +32 -0
- data/lib/viewy/acts_as_view.rb +22 -0
- data/lib/viewy/dependency_manager.rb +50 -0
- data/lib/viewy/engine.rb +8 -0
- data/lib/viewy/materialized_views.rb +7 -0
- data/lib/viewy/models/materialized_view_dependency.rb +11 -0
- data/lib/viewy/models.rb +7 -0
- data/lib/viewy/version.rb +1 -1
- data/lib/viewy.rb +5 -1
- metadata +10 -3
- data/LICENSE +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c1796596ee3676d14308739bce83b2578bf45c96
|
4
|
+
data.tar.gz: 4f2a4a1c577ca5e08dd0f79aacdbdcb34c09c221
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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,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
|
data/lib/viewy/models.rb
ADDED
data/lib/viewy/version.rb
CHANGED
data/lib/viewy.rb
CHANGED
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.
|
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:
|
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.
|