schema_plus_views 0.3.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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')}
|