schema_plus_views 0.3.1 → 1.0.0
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 +5 -5
- data/.github/workflows/prs.yml +194 -0
- data/.gitignore +1 -0
- data/.simplecov +20 -0
- data/Gemfile +4 -1
- data/README.md +58 -31
- data/Rakefile +2 -0
- data/gemfiles/Gemfile.base +1 -1
- data/gemfiles/activerecord-5.2/Gemfile.base +4 -0
- data/gemfiles/activerecord-5.2/Gemfile.mysql2 +10 -0
- data/gemfiles/activerecord-5.2/Gemfile.postgresql +10 -0
- data/gemfiles/{activerecord-4.2 → activerecord-5.2}/Gemfile.sqlite3 +3 -3
- data/gemfiles/activerecord-6.0/Gemfile.base +4 -0
- data/gemfiles/activerecord-6.0/Gemfile.mysql2 +10 -0
- data/gemfiles/activerecord-6.0/Gemfile.postgresql +10 -0
- data/gemfiles/activerecord-6.0/Gemfile.sqlite3 +10 -0
- data/lib/schema_plus/views/active_record/connection_adapters/abstract_adapter.rb +21 -5
- data/lib/schema_plus/views/active_record/connection_adapters/mysql2_adapter.rb +7 -11
- data/lib/schema_plus/views/active_record/connection_adapters/postgresql_adapter.rb +76 -17
- data/lib/schema_plus/views/active_record/connection_adapters/sqlite3_adapter.rb +7 -9
- data/lib/schema_plus/views/active_record/migration/command_recorder.rb +5 -1
- data/lib/schema_plus/views/middleware.rb +35 -34
- data/lib/schema_plus/views/schema_dump.rb +28 -0
- data/lib/schema_plus/views/version.rb +3 -1
- data/lib/schema_plus/views.rb +3 -0
- data/lib/schema_plus_views.rb +2 -0
- data/schema_dev.yml +7 -2
- data/schema_plus_views.gemspec +10 -9
- data/spec/dumper_spec.rb +60 -10
- data/spec/introspection_spec.rb +54 -6
- data/spec/middleware_spec.rb +3 -18
- data/spec/migration_spec.rb +108 -58
- data/spec/named_schemas_spec.rb +10 -42
- data/spec/sanity_spec.rb +2 -0
- data/spec/spec_helper.rb +16 -3
- metadata +39 -56
- data/.travis.yml +0 -18
- data/gemfiles/activerecord-4.2/Gemfile.base +0 -3
- data/gemfiles/activerecord-4.2/Gemfile.mysql2 +0 -10
- data/gemfiles/activerecord-4.2/Gemfile.postgresql +0 -10
@@ -1,32 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SchemaPlus::Views
|
2
4
|
module ActiveRecord
|
3
5
|
module ConnectionAdapters
|
4
6
|
module PostgresqlAdapter
|
7
|
+
POSTGIS_VIEWS = %W[
|
8
|
+
geography_columns
|
9
|
+
geometry_columns
|
10
|
+
raster_columns
|
11
|
+
raster_overviews
|
12
|
+
].freeze
|
13
|
+
|
14
|
+
# Create a view given the SQL definition. Specify :force => true
|
15
|
+
# to first drop the view if it already exists.
|
16
|
+
def create_view(view_name, definition, options={})
|
17
|
+
SchemaMonkey::Middleware::Migration::CreateView.start(connection: self, view_name: view_name, definition: definition, options: options) do |env|
|
18
|
+
definition = env.definition
|
19
|
+
view_name = env.view_name
|
20
|
+
options = env.options
|
21
|
+
definition = definition.to_sql if definition.respond_to? :to_sql
|
22
|
+
|
23
|
+
if options[:materialized] && options[:allow_replace]
|
24
|
+
raise ArgumentError, 'allow_replace is not supported for materialized views'
|
25
|
+
end
|
26
|
+
|
27
|
+
if options[:force]
|
28
|
+
drop_view(view_name, {if_exists: true}.merge(options.slice(:materialized)))
|
29
|
+
end
|
30
|
+
|
31
|
+
command = if options[:materialized]
|
32
|
+
"CREATE MATERIALIZED"
|
33
|
+
elsif options[:allow_replace]
|
34
|
+
"CREATE OR REPLACE"
|
35
|
+
else
|
36
|
+
"CREATE"
|
37
|
+
end
|
38
|
+
|
39
|
+
execute "#{command} VIEW #{quote_table_name(view_name)} AS #{definition}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Drop the named view. Specify :if_exists => true
|
44
|
+
# to fail silently if the view doesn't exist.
|
45
|
+
def drop_view(view_name, options = {})
|
46
|
+
SchemaMonkey::Middleware::Migration::DropView.start(connection: self, view_name: view_name, options: options) do |env|
|
47
|
+
view_name = env.view_name
|
48
|
+
options = env.options
|
49
|
+
materialized = options[:materialized] ? 'MATERIALIZED' : ''
|
50
|
+
sql = "DROP #{materialized} VIEW"
|
51
|
+
sql += " IF EXISTS" if options[:if_exists]
|
52
|
+
sql += " #{quote_table_name(view_name)}"
|
53
|
+
execute sql
|
54
|
+
end
|
55
|
+
end
|
5
56
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
SQL
|
14
|
-
sql += " AND schemaname != 'postgis'" if adapter_name == 'PostGIS'
|
15
|
-
env.views += env.connection.query(sql, env.query_name).map { |row| row[0] }
|
16
|
-
}.views
|
57
|
+
# Refresh a materialized view.
|
58
|
+
def refresh_view(view_name, options = {})
|
59
|
+
SchemaMonkey::Middleware::Migration::RefreshView.start(connection: self, view_name: view_name, options: options) do |env|
|
60
|
+
view_name = env.view_name
|
61
|
+
sql = "REFRESH MATERIALIZED VIEW #{quote_table_name(view_name)}"
|
62
|
+
execute sql
|
63
|
+
end
|
17
64
|
end
|
18
65
|
|
19
|
-
def
|
20
|
-
|
66
|
+
def views #:nodoc:
|
67
|
+
# Filter out any view that begins with "pg_"
|
68
|
+
super.reject do |c|
|
69
|
+
c.start_with?("pg_") || POSTGIS_VIEWS.include?(c)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def view_full_definition(view_name, name = nil) #:nodoc:
|
74
|
+
data = SchemaMonkey::Middleware::Schema::ViewDefinition.start(connection: self, view_name: view_name, query_name: name, view_type: :view) { |env|
|
21
75
|
result = env.connection.query(<<-SQL, name)
|
22
|
-
SELECT pg_get_viewdef(oid)
|
76
|
+
SELECT pg_get_viewdef(oid), relkind
|
23
77
|
FROM pg_class
|
24
|
-
WHERE relkind
|
78
|
+
WHERE relkind in ('v', 'm')
|
25
79
|
AND relname = '#{env.view_name}'
|
26
80
|
SQL
|
27
81
|
row = result.first
|
28
|
-
|
29
|
-
|
82
|
+
unless row.nil?
|
83
|
+
env.definition = row.first.chomp(';').strip
|
84
|
+
env.view_type = :materialized if row.second == 'm'
|
85
|
+
end
|
86
|
+
}
|
87
|
+
|
88
|
+
[data.definition, data.view_type]
|
30
89
|
end
|
31
90
|
|
32
91
|
end
|
@@ -1,20 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SchemaPlus::Views
|
2
4
|
module ActiveRecord
|
3
5
|
module ConnectionAdapters
|
4
6
|
module Sqlite3Adapter
|
5
7
|
|
6
|
-
def
|
7
|
-
SchemaMonkey::Middleware::Schema::
|
8
|
-
env.views += env.connection.execute("SELECT name FROM sqlite_master WHERE type='view'", env.query_name).collect{|row| row["name"]}
|
9
|
-
}.views
|
10
|
-
end
|
11
|
-
|
12
|
-
def view_definition(view_name, name = nil)
|
13
|
-
SchemaMonkey::Middleware::Schema::ViewDefinition.start(connection: self, view_name: view_name, query_name: name) { |env|
|
8
|
+
def view_full_definition(view_name, name = nil)
|
9
|
+
data = SchemaMonkey::Middleware::Schema::ViewDefinition.start(connection: self, view_name: view_name, query_name: name, view_type: :view) { |env|
|
14
10
|
sql = env.connection.execute("SELECT sql FROM sqlite_master WHERE type='view' AND name=#{quote(env.view_name)}", env.query_name).collect{|row| row["sql"]}.first
|
15
11
|
sql.sub!(/^CREATE VIEW \S* AS\s+/im, '') unless sql.nil?
|
16
12
|
env.definition = sql
|
17
|
-
}
|
13
|
+
}
|
14
|
+
|
15
|
+
[data.definition, data.view_type]
|
18
16
|
end
|
19
17
|
|
20
18
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SchemaPlus::Views
|
2
4
|
module ActiveRecord
|
3
5
|
module Migration
|
@@ -11,7 +13,9 @@ module SchemaPlus::Views
|
|
11
13
|
end
|
12
14
|
|
13
15
|
def invert_create_view(args)
|
14
|
-
|
16
|
+
options = {}
|
17
|
+
options[:materialized] = args[2][:materialized] if args[2].has_key?(:materialized)
|
18
|
+
[ :drop_view, [args.first, options] ]
|
15
19
|
end
|
16
20
|
|
17
21
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module SchemaPlus::Views
|
2
4
|
module Middleware
|
3
5
|
|
@@ -9,44 +11,43 @@ module SchemaPlus::Views
|
|
9
11
|
re_view_referent = %r{(?:(?i)FROM|JOIN) \S*\b(\S+)\b}
|
10
12
|
env.connection.views.each do |view_name|
|
11
13
|
next if env.dumper.ignored?(view_name)
|
12
|
-
|
13
|
-
env.dump.tables[view.name] = view
|
14
|
-
env.dump.depends(view.name, view.definition.scan(re_view_referent).flatten)
|
15
|
-
end
|
16
|
-
end
|
14
|
+
definition, view_type = env.connection.view_full_definition(view_name)
|
17
15
|
|
18
|
-
|
19
|
-
class View < KeyStruct[:name, :definition]
|
20
|
-
def assemble(stream)
|
21
|
-
heredelim = "END_VIEW_#{name.upcase}"
|
22
|
-
stream.puts <<-ENDVIEW
|
23
|
-
create_view "#{name}", <<-'#{heredelim}', :force => true
|
24
|
-
#{definition}
|
25
|
-
#{heredelim}
|
16
|
+
indexes = []
|
26
17
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
18
|
+
if view_type == :materialized
|
19
|
+
env.connection.indexes(view_name).each do |index|
|
20
|
+
indexes << SchemaPlus::Core::SchemaDump::Table::Index.new(
|
21
|
+
name: index.name, columns: index.columns, options: view_index_options(index, env.connection)
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
32
25
|
|
33
|
-
|
34
|
-
|
26
|
+
view = ::SchemaPlus::Views::SchemaDump::View.new(
|
27
|
+
name: view_name,
|
28
|
+
definition: definition,
|
29
|
+
view_type: view_type,
|
30
|
+
indexes: indexes
|
31
|
+
)
|
35
32
|
|
36
|
-
|
37
|
-
|
38
|
-
Tables.filter_out_views(env)
|
33
|
+
env.dump.tables[view.name] = view
|
34
|
+
env.dump.depends(view.name, view.definition.scan(re_view_referent).flatten)
|
39
35
|
end
|
40
36
|
end
|
41
37
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
38
|
+
# Take from ActiveRecord::SchemaDumper#index_parts
|
39
|
+
def view_index_options(index, connection)
|
40
|
+
options = {}
|
41
|
+
options[:unique] = true if index.unique
|
42
|
+
options[:length] = index.lengths if index.lengths.present?
|
43
|
+
options[:order] = index.orders if index.orders.present?
|
44
|
+
options[:opclass] = index.opclasses if index.opclasses.present?
|
45
|
+
options[:where] = index.where if index.where
|
46
|
+
options[:using] = index.using if !connection.default_index_type?(index)
|
47
|
+
options[:type] = index.type if index.type
|
48
|
+
options[:comment] = index.comment if index.comment
|
47
49
|
|
48
|
-
|
49
|
-
env.tables -= env.connection.views(env.query_name)
|
50
|
+
options
|
50
51
|
end
|
51
52
|
end
|
52
53
|
end
|
@@ -56,11 +57,8 @@ module SchemaPlus::Views
|
|
56
57
|
# for tables
|
57
58
|
|
58
59
|
module Schema
|
59
|
-
module Views
|
60
|
-
ENV = [:connection, :query_name, :views]
|
61
|
-
end
|
62
60
|
module ViewDefinition
|
63
|
-
ENV = [:connection, :view_name, :query_name, :definition]
|
61
|
+
ENV = [:connection, :view_name, :query_name, :definition, :view_type]
|
64
62
|
end
|
65
63
|
end
|
66
64
|
|
@@ -71,6 +69,9 @@ module SchemaPlus::Views
|
|
71
69
|
module DropView
|
72
70
|
ENV = [:connection, :view_name, :options]
|
73
71
|
end
|
72
|
+
module RefreshView
|
73
|
+
ENV = [:connection, :view_name, :options]
|
74
|
+
end
|
74
75
|
end
|
75
76
|
end
|
76
77
|
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SchemaPlus
|
4
|
+
module Views
|
5
|
+
class SchemaDump
|
6
|
+
# quacks like a SchemaMonkey Dump::Table
|
7
|
+
class View < Struct.new(:name, :definition, :view_type, :indexes, keyword_init: true)
|
8
|
+
def assemble(stream)
|
9
|
+
extra_options = ", materialized: true" if view_type == :materialized
|
10
|
+
heredelim = "END_VIEW_#{name.upcase}"
|
11
|
+
stream.puts <<~ENDVIEW
|
12
|
+
create_view "#{name}", <<-'#{heredelim}', :force => true#{extra_options}
|
13
|
+
#{definition}
|
14
|
+
#{heredelim}
|
15
|
+
ENDVIEW
|
16
|
+
stream.puts
|
17
|
+
|
18
|
+
indexes.each do |index|
|
19
|
+
stream.write " add_index \"#{name}\", "
|
20
|
+
index.assemble(stream)
|
21
|
+
stream.puts ""
|
22
|
+
end
|
23
|
+
stream.puts ""
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/schema_plus/views.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'schema_plus/core'
|
2
4
|
|
3
5
|
module SchemaPlus
|
@@ -9,6 +11,7 @@ require_relative 'views/version'
|
|
9
11
|
require_relative 'views/active_record/connection_adapters/abstract_adapter'
|
10
12
|
require_relative 'views/active_record/migration/command_recorder'
|
11
13
|
require_relative 'views/middleware'
|
14
|
+
require_relative 'views/schema_dump'
|
12
15
|
|
13
16
|
module SchemaPlus::Views
|
14
17
|
module ActiveRecord
|
data/lib/schema_plus_views.rb
CHANGED
data/schema_dev.yml
CHANGED
data/schema_plus_views.gemspec
CHANGED
@@ -1,4 +1,5 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
2
3
|
lib = File.expand_path('../lib', __FILE__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require 'schema_plus/views/version'
|
@@ -17,13 +18,13 @@ Gem::Specification.new do |gem|
|
|
17
18
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
19
|
gem.require_paths = ["lib"]
|
19
20
|
|
20
|
-
gem.
|
21
|
-
|
21
|
+
gem.required_ruby_version = '>= 2.5'
|
22
|
+
|
23
|
+
gem.add_dependency "activerecord", '>= 5.2', '< 6.1'
|
24
|
+
gem.add_dependency "schema_plus_core", '~> 3.0'
|
22
25
|
|
23
|
-
gem.add_development_dependency "bundler"
|
24
|
-
gem.add_development_dependency "rake",
|
25
|
-
gem.add_development_dependency "rspec",
|
26
|
-
gem.add_development_dependency "schema_dev",
|
27
|
-
gem.add_development_dependency "simplecov"
|
28
|
-
gem.add_development_dependency "simplecov-gem-profile"
|
26
|
+
gem.add_development_dependency "bundler"
|
27
|
+
gem.add_development_dependency "rake", '~> 13.0'
|
28
|
+
gem.add_development_dependency "rspec", '~> 3.0'
|
29
|
+
gem.add_development_dependency "schema_dev", '~> 4.1'
|
29
30
|
end
|
data/spec/dumper_spec.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
class Item < ActiveRecord::Base
|
@@ -5,8 +7,6 @@ end
|
|
5
7
|
|
6
8
|
describe "Dumper" do
|
7
9
|
|
8
|
-
let(:schema) { ActiveRecord::Schema }
|
9
|
-
|
10
10
|
let(:migration) { ActiveRecord::Migration }
|
11
11
|
|
12
12
|
let(:connection) { ActiveRecord::Base.connection }
|
@@ -39,27 +39,77 @@ describe "Dumper" do
|
|
39
39
|
# when in the (say) development database, but then uses it to
|
40
40
|
# initialize the test database when testing. this meant that the
|
41
41
|
# test database had views into the development database.
|
42
|
-
db = connection.respond_to?(:current_database)? connection.current_database : SchemaDev::Rspec.db_configuration[:database]
|
42
|
+
db = connection.respond_to?(:current_database) ? connection.current_database : SchemaDev::Rspec.db_configuration[:database]
|
43
43
|
expect(dump).not_to match(%r{#{connection.quote_table_name(db)}[.]})
|
44
44
|
end
|
45
45
|
|
46
|
+
context 'materialized views', postgresql: :only do
|
47
|
+
before do
|
48
|
+
apply_migration do
|
49
|
+
create_view :materialized, Item.select('b, s').where(:a => 1), materialized: true
|
50
|
+
|
51
|
+
add_index :items, :a, where: 'a = 1'
|
52
|
+
|
53
|
+
add_index :materialized, :s
|
54
|
+
add_index :materialized, :b, unique: true, name: 'index_materialized_unique'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
it "include the view definitions" do
|
59
|
+
expect(dump).to match(view_re("materialized", /SELECT .*b.*,.*s.* FROM .*items.* WHERE \(?.*a.* = 1\)?/mi, ', materialized: true'))
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'includes the index definitions' do
|
63
|
+
expect(dump).to match(/index_materialized_on_s/)
|
64
|
+
expect(dump).to match(/index_materialized_unique.+unique/)
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'when indexes are function results', postgresql: :only do
|
68
|
+
before do
|
69
|
+
apply_migration do
|
70
|
+
add_index :materialized, 'length(s)', name: 'index_materialized_function'
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'includes the index definition' do
|
75
|
+
expect(dump).to match(/length\(.+name.+index_materialized_function/)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'when index has a where clause', postgresql: :only do
|
80
|
+
before do
|
81
|
+
apply_migration do
|
82
|
+
add_index :materialized, :s, where: 'b = 1', name: 'index_materialized_conditional'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'includes the index definition' do
|
87
|
+
expect(dump).to match(/index_materialized_conditional.+where/)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
46
92
|
protected
|
47
93
|
|
48
|
-
def view_re(name, re)
|
94
|
+
def view_re(name, re, extra_config = '')
|
49
95
|
heredelim = "END_VIEW_#{name.upcase}"
|
50
|
-
%r{create_view "#{name}", <<-'#{heredelim}', :force => true\n\s*#{re}\s*\n *#{heredelim}$}mi
|
96
|
+
%r{create_view "#{name}", <<-'#{heredelim}', :force => true#{extra_config}\n\s*#{re}\s*\n *#{heredelim}$}mi
|
51
97
|
end
|
52
98
|
|
53
99
|
def define_schema_and_data
|
54
|
-
connection.views.each do |view|
|
55
|
-
|
100
|
+
connection.views.each do |view|
|
101
|
+
connection.drop_view view
|
102
|
+
end
|
103
|
+
connection.tables.each do |table|
|
104
|
+
connection.drop_table table, cascade: true
|
105
|
+
end
|
56
106
|
|
57
|
-
|
107
|
+
apply_migration do
|
58
108
|
|
59
109
|
create_table :items, :force => true do |t|
|
60
110
|
t.integer :a
|
61
111
|
t.integer :b
|
62
|
-
t.string
|
112
|
+
t.string :s
|
63
113
|
end
|
64
114
|
|
65
115
|
create_view :a_ones, Item.select('b, s').where(:a => 1)
|
@@ -71,7 +121,7 @@ describe "Dumper" do
|
|
71
121
|
connection.execute "insert into items (a, b, s) values (2, 2, 'two_two')"
|
72
122
|
end
|
73
123
|
|
74
|
-
def dump(opts={})
|
124
|
+
def dump(opts = {})
|
75
125
|
StringIO.open { |stream|
|
76
126
|
ActiveRecord::SchemaDumper.ignore_tables = Array.wrap(opts[:ignore_tables])
|
77
127
|
ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, stream)
|
data/spec/introspection_spec.rb
CHANGED
@@ -1,12 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
class Item < ActiveRecord::Base
|
4
6
|
end
|
5
7
|
|
6
8
|
describe "Introspection" do
|
7
|
-
|
8
|
-
let(:schema) { ActiveRecord::Schema }
|
9
|
-
|
10
9
|
let(:migration) { ActiveRecord::Migration }
|
11
10
|
|
12
11
|
let(:connection) { ActiveRecord::Base.connection }
|
@@ -17,8 +16,6 @@ describe "Introspection" do
|
|
17
16
|
|
18
17
|
it "should list all views" do
|
19
18
|
expect(connection.views.sort).to eq(%W[a_ones ab_ones])
|
20
|
-
expect(connection.view_definition('a_ones')).to match(%r{^SELECT .*b.*,.*s.* FROM .*items.* WHERE .*a.* = 1}mi)
|
21
|
-
expect(connection.view_definition('ab_ones')).to match(%r{^SELECT .*s.* FROM .*a_ones.* WHERE .*b.* = 1}mi)
|
22
19
|
end
|
23
20
|
|
24
21
|
it "should ignore views named pg_*", postgresql: :only do
|
@@ -30,6 +27,29 @@ describe "Introspection" do
|
|
30
27
|
end
|
31
28
|
end
|
32
29
|
|
30
|
+
context "when using PostGIS", postgresql: :only do
|
31
|
+
before do
|
32
|
+
apply_migration do
|
33
|
+
SchemaPlus::Views::ActiveRecord::ConnectionAdapters::PostgresqlAdapter::POSTGIS_VIEWS.each do |view|
|
34
|
+
create_view view, 'select 1'
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
after do
|
40
|
+
apply_migration do
|
41
|
+
SchemaPlus::Views::ActiveRecord::ConnectionAdapters::PostgresqlAdapter::POSTGIS_VIEWS.each do |view|
|
42
|
+
drop_view view
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should hide views in postgis views" do
|
48
|
+
expect(connection.views.sort).to eq(%W[a_ones ab_ones])
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
|
33
53
|
it "should not be listed as a table" do
|
34
54
|
expect(connection.tables).not_to include('a_ones')
|
35
55
|
expect(connection.tables).not_to include('ab_ones')
|
@@ -40,6 +60,11 @@ describe "Introspection" do
|
|
40
60
|
expect(connection.view_definition('ab_ones')).to match(%r{^SELECT .*s.* FROM .*a_ones.* WHERE .*b.* = 1}mi)
|
41
61
|
end
|
42
62
|
|
63
|
+
it 'returns them as view types' do
|
64
|
+
expect(connection.view_type('a_ones')).to eq(:view)
|
65
|
+
expect(connection.view_type('ab_ones')).to eq(:view)
|
66
|
+
end
|
67
|
+
|
43
68
|
context "in mysql", :mysql => :only do
|
44
69
|
|
45
70
|
around(:each) do |example|
|
@@ -67,13 +92,36 @@ describe "Introspection" do
|
|
67
92
|
end
|
68
93
|
end
|
69
94
|
|
95
|
+
context 'in postgresql', postgresql: :only do
|
96
|
+
context 'for materialized views' do
|
97
|
+
around(:each) do |example|
|
98
|
+
begin
|
99
|
+
migration.drop_view :materialized, materialized: true, if_exists: true
|
100
|
+
example.run
|
101
|
+
ensure
|
102
|
+
migration.drop_view :materialized, materialized: true, if_exists: true
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'returns the definition' do
|
107
|
+
migration.create_view :materialized, 'SELECT * FROM items WHERE (a=2)', materialized: true
|
108
|
+
expect(connection.view_definition('materialized')).to match(%r{FROM items})
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'returns the type as materialized' do
|
112
|
+
migration.create_view :materialized, 'SELECT * FROM items WHERE (a=2)', materialized: true
|
113
|
+
expect(connection.view_type('materialized')).to eq(:materialized)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
70
118
|
protected
|
71
119
|
|
72
120
|
def define_schema_and_data
|
73
121
|
connection.views.each do |view| connection.drop_view view end
|
74
122
|
connection.tables.each do |table| connection.drop_table table, cascade: true end
|
75
123
|
|
76
|
-
|
124
|
+
apply_migration do
|
77
125
|
|
78
126
|
create_table :items, :force => true do |t|
|
79
127
|
t.integer :a
|
data/spec/middleware_spec.rb
CHANGED
@@ -1,15 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'spec_helper'
|
2
4
|
|
3
5
|
module TestMiddleware
|
4
6
|
module Middleware
|
5
7
|
|
6
8
|
module Schema
|
7
|
-
module Views
|
8
|
-
SPY = []
|
9
|
-
def after(env)
|
10
|
-
SPY << env.to_hash.except(:connection)
|
11
|
-
end
|
12
|
-
end
|
13
9
|
module ViewDefinition
|
14
10
|
SPY = []
|
15
11
|
def after(env)
|
@@ -40,12 +36,11 @@ SchemaMonkey.register TestMiddleware
|
|
40
36
|
|
41
37
|
context SchemaPlus::Views::Middleware do
|
42
38
|
|
43
|
-
let(:schema) { ActiveRecord::Schema }
|
44
39
|
let(:migration) { ActiveRecord::Migration }
|
45
40
|
let(:connection) { ActiveRecord::Base.connection }
|
46
41
|
|
47
42
|
before(:each) do
|
48
|
-
|
43
|
+
apply_migration do
|
49
44
|
create_table :items, force: true do |t|
|
50
45
|
t.integer :a
|
51
46
|
end
|
@@ -53,16 +48,6 @@ context SchemaPlus::Views::Middleware do
|
|
53
48
|
end
|
54
49
|
end
|
55
50
|
|
56
|
-
context TestMiddleware::Middleware::Schema::Views do
|
57
|
-
it "calls middleware" do
|
58
|
-
expect(spy_on {connection.views 'qn'}).to eq({
|
59
|
-
#connection: connection,
|
60
|
-
views: ['a_view'],
|
61
|
-
query_name: 'qn'
|
62
|
-
})
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
51
|
context TestMiddleware::Middleware::Schema::ViewDefinition do
|
67
52
|
it "calls middleware" do
|
68
53
|
spied = spy_on {connection.view_definition('a_view', 'qn')}
|