schema_plus_views 0.3.1 → 0.4.0

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
- SHA1:
3
- metadata.gz: af2a18ccfac25ea4e3e6f15ee3955625ea1392c5
4
- data.tar.gz: 9093bc5d4b94ca74b452a64a4be17ecbca2803ef
2
+ SHA256:
3
+ metadata.gz: 4ff5c6a7f05fbebbb07b6b84ec71761cc533640f3affe94d3e3153645a5599cc
4
+ data.tar.gz: 8f79673795c32b02c6ca0ff80495e115cdd3c9caf24e73c8fcf7c4ade95b4923
5
5
  SHA512:
6
- metadata.gz: 06efc28124b8d57232e72076b9f134a3f3699f8cccc4e3bf480e261026e32af33a3a3e4808ae7aa869cae3d45900ba8a56fc8de1dabfeadc72fb58c12b188e7d
7
- data.tar.gz: b3456b0c828d5fb09b7ae30b9a2e5af9ddec51a4175475fbc2220ddc0378c412c459fdccf66c8bb5efb365c3da7222629db16dc7644082817341a12e1d0eaf9b
6
+ metadata.gz: 928a52a3e5e42e5e745754a35ad03244dba6fadb7d8681dc48b20643aaaa34675ce0e9befd4d58a6a3139bdaafab132cc5815d4f7be021f78a77eddbf8486ab8
7
+ data.tar.gz: 674f0f9bcb0d7cbda52e9d32a2b21d67e3a095f9c2970a34b55db9146d8bee78379fe4e83c37f951b8aacf8c838d11159a35af12169f27bedf97b1ef6ea4adb2
data/.travis.yml CHANGED
@@ -3,16 +3,87 @@
3
3
  # Please do not edit this file; any changes will be overwritten next time
4
4
  # schema_dev gets run.
5
5
  ---
6
- sudo: false
7
6
  rvm:
8
- - 2.1.5
7
+ - 2.5.9
8
+ - 2.7.3
9
9
  gemfile:
10
- - gemfiles/activerecord-4.2/Gemfile.mysql2
11
- - gemfiles/activerecord-4.2/Gemfile.postgresql
12
- - gemfiles/activerecord-4.2/Gemfile.sqlite3
13
- env: POSTGRESQL_DB_USER=postgres MYSQL_DB_USER=travis
14
- addons:
15
- postgresql: '9.4'
10
+ - gemfiles/activerecord-5.2/Gemfile.sqlite3
16
11
  before_script: bundle exec rake create_databases
17
12
  after_script: bundle exec rake drop_databases
18
13
  script: bundle exec rake travis
14
+ jobs:
15
+ include:
16
+ - gemfile: gemfiles/activerecord-5.2/Gemfile.mysql2
17
+ rvm: 2.5.9
18
+ services:
19
+ - mysql
20
+ env: MYSQL_DB_USER=travis
21
+ - gemfile: gemfiles/activerecord-5.2/Gemfile.postgresql
22
+ rvm: 2.5.9
23
+ addons:
24
+ postgresql: '10'
25
+ apt:
26
+ packages:
27
+ - postgresql-10
28
+ - postgresql-client-10
29
+ env: POSTGRESQL_DB_USER=postgres
30
+ - gemfile: gemfiles/activerecord-5.2/Gemfile.postgresql
31
+ rvm: 2.5.9
32
+ addons:
33
+ postgresql: '11'
34
+ apt:
35
+ packages:
36
+ - postgresql-11
37
+ - postgresql-client-11
38
+ env: POSTGRESQL_DB_USER=travis PGPORT=5433
39
+ - gemfile: gemfiles/activerecord-5.2/Gemfile.postgresql
40
+ rvm: 2.5.9
41
+ addons:
42
+ postgresql: '12'
43
+ apt:
44
+ packages:
45
+ - postgresql-12
46
+ - postgresql-client-12
47
+ env: POSTGRESQL_DB_USER=travis PGPORT=5433
48
+ - gemfile: gemfiles/activerecord-5.2/Gemfile.postgresql
49
+ rvm: 2.5.9
50
+ addons:
51
+ postgresql: '9.6'
52
+ env: POSTGRESQL_DB_USER=postgres
53
+ - gemfile: gemfiles/activerecord-5.2/Gemfile.mysql2
54
+ rvm: 2.7.3
55
+ services:
56
+ - mysql
57
+ env: MYSQL_DB_USER=travis
58
+ - gemfile: gemfiles/activerecord-5.2/Gemfile.postgresql
59
+ rvm: 2.7.3
60
+ addons:
61
+ postgresql: '11'
62
+ apt:
63
+ packages:
64
+ - postgresql-11
65
+ - postgresql-client-11
66
+ env: POSTGRESQL_DB_USER=travis PGPORT=5433
67
+ - gemfile: gemfiles/activerecord-5.2/Gemfile.postgresql
68
+ rvm: 2.7.3
69
+ addons:
70
+ postgresql: '12'
71
+ apt:
72
+ packages:
73
+ - postgresql-12
74
+ - postgresql-client-12
75
+ env: POSTGRESQL_DB_USER=travis PGPORT=5433
76
+ - gemfile: gemfiles/activerecord-5.2/Gemfile.postgresql
77
+ rvm: 2.7.3
78
+ addons:
79
+ postgresql: '10'
80
+ apt:
81
+ packages:
82
+ - postgresql-10
83
+ - postgresql-client-10
84
+ env: POSTGRESQL_DB_USER=postgres
85
+ - gemfile: gemfiles/activerecord-5.2/Gemfile.postgresql
86
+ rvm: 2.7.3
87
+ addons:
88
+ postgresql: '9.6'
89
+ env: POSTGRESQL_DB_USER=postgres
data/README.md CHANGED
@@ -28,7 +28,8 @@ SchemaPlus::Views is tested on:
28
28
 
29
29
  <!-- SCHEMA_DEV: MATRIX - begin -->
30
30
  <!-- These lines are auto-generated by schema_dev based on schema_dev.yml -->
31
- * ruby **2.1.5** with activerecord **4.2**, using **mysql2**, **sqlite3** or **postgresql**
31
+ * ruby **2.5.9** with activerecord **5.2**, using **mysql2**, **sqlite3** or **postgresql**
32
+ * ruby **2.7.3** with activerecord **5.2**, using **mysql2**, **sqlite3** or **postgresql**
32
33
 
33
34
  <!-- SCHEMA_DEV: MATRIX - end -->
34
35
 
@@ -56,8 +57,30 @@ Additional options can be provided:
56
57
 
57
58
  * `:allow_replace => true` will use the command "CREATE OR REPLACE" when creating the view, for seamlessly redefining the view even if other views depend on it. It's only supported by MySQL and PostgreSQL, and each has some limitations on when a view can be replaced; see their docs for details.
58
59
 
60
+ * `:materialized => true` will create a materialized view instead of a standard view. This view caches its contents on disk and must be refreshed to update its contents. It is only supported on PostgreSQL. Further, allow_replace is not supported on materialized views.
61
+
59
62
  SchemaPlus::Views also arranges to include the `create_view` statements (with literal SQL) in the schema dump.
60
63
 
64
+ #### Materialized views
65
+
66
+ Materialized views persist their data when created and must be manually refreshed to see new data.
67
+ Further materialized views can have indexes defined on them.
68
+
69
+ ```ruby
70
+ create_view :posts_commented_by_staff, <<~SQL, materialized: true
71
+ SELECT * FROM posts LEFT OUTER JOIN comments ON comments.post_id = posts.id WHERE comments.id IS NULL
72
+ SQL
73
+
74
+ add_index :posts_commented_by_staff, :category
75
+ add_index :posts_commented_by_staff, :token, unique: true
76
+ ```
77
+
78
+ To refresh a materialized view run the refresh_view connection command.
79
+
80
+ ```ruby
81
+ ActiveRecord::Base.connection.refresh_view('posts_commented_by_staff')
82
+ ```
83
+
61
84
  ### Dropping views
62
85
 
63
86
  In a migration:
@@ -65,6 +88,10 @@ In a migration:
65
88
  ```ruby
66
89
  drop_view :posts_commented_by_staff
67
90
  drop_view :uncommented_posts, :if_exists => true
91
+
92
+ # materialized views
93
+ drop_view :posts_commented_by_staff, materialized: true
94
+ drop_view :uncommented_posts, :if_exists => true, materialized: true
68
95
  ```
69
96
 
70
97
  ### Using views
@@ -86,14 +113,16 @@ You can look up the defined views analogously to looking up tables:
86
113
 
87
114
  ```ruby
88
115
  connection.tables # => array of table names [method provided by ActiveRecord]
89
- connection.views # => array of view names [method provided by SchemaPlus::Views]
116
+ connection.views # => array of view names [method overridden by SchemaPlus::Views for postgres]
90
117
  ```
91
118
 
92
119
  Notes:
93
120
 
94
- 1. For Mysql and SQLite3, ActiveRecord's `connection.tables` method would return views as well as tables; SchemaPlus::Views normalizes them to return only tables.
95
-
96
- 2. For PostgreSQL, `connection.views` suppresses views prefixed with `pg_` as those are presumed to be internal.
121
+ 1. For PostgreSQL, `connection.views` suppresses views prefixed with `pg_` as those are presumed to be internal. Also it suppresses the "postgis" specifically named tables
122
+ - geography_columns
123
+ - geometry_columns
124
+ - raster_columns
125
+ - raster_overviews
97
126
 
98
127
  ### Querying view definitions
99
128
 
@@ -105,26 +134,19 @@ connection.view_definition(view_name) # => returns SQL string
105
134
 
106
135
  This returns just the body of the definition, i.e. the part after the `CREATE VIEW 'name' AS` command.
107
136
 
108
- ## Customization API: Middleware Stacks
109
-
110
- All the methods defined by SchemaPlus::Views provide middleware stacks, in case you need to do any custom filtering, rewriting, triggering, or whatever. For info on how to use middleware stacks, see the READMEs of [schema_monkey](https://github.com/SchemaPlus/schema_monkey) and [schema_plus_core](https://github.com/SchemaPlus/schema_plus_core).
111
-
137
+ You can also lookup the type of view (regular or materialized) using
112
138
 
113
- ### `Schema::Views` stack
114
-
115
- Wraps the `connection.views` method. Env contains:
139
+ ```ruby
140
+ connection.view_type(view_name) # => returns a Symbol, either :view or :materialized
141
+ ```
116
142
 
117
- Env Field | Description | Initialized
118
- --- | --- | ---
119
- `:views` | The result of the lookup | `[]`
120
- `:connection` | The current ActiveRecord connection | *context*
121
- `:query_name` | Optional label for ActiveRecord logging | *arg*
143
+ ## Customization API: Middleware Stacks
122
144
 
123
- The base implementation appends its results to `env.views`
145
+ All the methods defined by SchemaPlus::Views provide middleware stacks, in case you need to do any custom filtering, rewriting, triggering, or whatever. For info on how to use middleware stacks, see the READMEs of [schema_monkey](https://github.com/SchemaPlus/schema_monkey) and [schema_plus_core](https://github.com/SchemaPlus/schema_plus_core).
124
146
 
125
147
  ### `Schema::ViewDefinition` stack
126
148
 
127
- Wraps the `connection.view_definition` method. Env contains:
149
+ Wraps the `connection.view_full_definition` method. Env contains:
128
150
 
129
151
  Env Field | Description | Initialized
130
152
  --- | --- | ---
@@ -132,6 +154,7 @@ Env Field | Description | Initialized
132
154
  `:view_name` | The view to look up | *arg*
133
155
  `:query_name` | Optional label for ActiveRecord logging | *arg*
134
156
  `:definition` | The view definition SQL | `nil`
157
+ `:view_type` | The view type symbol. | :view`
135
158
 
136
159
  The base implementation looks up the definition of the view named
137
160
  `env.view_name` and assigns the result to `env.definition`
@@ -166,6 +189,7 @@ options in `env.options`
166
189
 
167
190
  ## History
168
191
 
192
+ * 0.4.0 - Added support for Rails 5.2 and materialized views in PostgreSQL
169
193
  * 0.3.1 - Upgrade schema_plus_core and schema_dev dependencies
170
194
  * 0.3.0
171
195
  - Added middleware stacks
@@ -1,4 +1,4 @@
1
1
  source 'https://rubygems.org'
2
- gemspec :path => File.expand_path('..', __FILE__)
2
+ gemspec path: File.expand_path('..', __FILE__)
3
3
 
4
4
  File.exist?(gemfile_local = File.expand_path('../Gemfile.local', __FILE__)) and eval File.read(gemfile_local), binding, gemfile_local
@@ -0,0 +1,4 @@
1
+ base_gemfile = File.expand_path('../../Gemfile.base', __FILE__)
2
+ eval File.read(base_gemfile)
3
+
4
+ gem "activerecord", ">= 5.2.0.beta0", "< 5.3"
@@ -0,0 +1,10 @@
1
+ base_gemfile = File.expand_path('../Gemfile.base', __FILE__)
2
+ eval File.read(base_gemfile), binding, base_gemfile
3
+
4
+ platform :ruby do
5
+ gem "mysql2"
6
+ end
7
+
8
+ platform :jruby do
9
+ gem 'activerecord-jdbcmysql-adapter'
10
+ end
@@ -0,0 +1,10 @@
1
+ base_gemfile = File.expand_path('../Gemfile.base', __FILE__)
2
+ eval File.read(base_gemfile), binding, base_gemfile
3
+
4
+ platform :ruby do
5
+ gem "pg"
6
+ end
7
+
8
+ platform :jruby do
9
+ gem 'activerecord-jdbcpostgresql-adapter'
10
+ end
@@ -1,5 +1,5 @@
1
- require "pathname"
2
- eval(Pathname.new(__FILE__).dirname.join("Gemfile.base").read, binding)
1
+ base_gemfile = File.expand_path('../Gemfile.base', __FILE__)
2
+ eval File.read(base_gemfile), binding, base_gemfile
3
3
 
4
4
  platform :ruby do
5
5
  gem "sqlite3"
@@ -7,4 +7,4 @@ end
7
7
 
8
8
  platform :jruby do
9
9
  gem 'activerecord-jdbcsqlite3-adapter', '>=1.3.0.beta2'
10
- end
10
+ end
@@ -6,6 +6,7 @@ module SchemaPlus::Views
6
6
  # to first drop the view if it already exists.
7
7
  def create_view(view_name, definition, options={})
8
8
  SchemaMonkey::Middleware::Migration::CreateView.start(connection: self, view_name: view_name, definition: definition, options: options) do |env|
9
+ raise ArgumentError, 'Materialized views are not implemented or supported on this database' if options[:materialized]
9
10
  definition = env.definition
10
11
  view_name = env.view_name
11
12
  options = env.options
@@ -28,6 +29,7 @@ module SchemaPlus::Views
28
29
  # to fail silently if the view doesn't exist.
29
30
  def drop_view(view_name, options = {})
30
31
  SchemaMonkey::Middleware::Migration::DropView.start(connection: self, view_name: view_name, options: options) do |env|
32
+ raise ArgumentError, 'Materialized views are not implemented or supported on this database' if options[:materialized]
31
33
  view_name = env.view_name
32
34
  options = env.options
33
35
  sql = "DROP VIEW"
@@ -37,19 +39,31 @@ module SchemaPlus::Views
37
39
  end
38
40
  end
39
41
 
42
+ # Returns the SQL definition of a given view. This is
43
+ # the literal SQL would come after 'CREATVE VIEW viewname AS ' in
44
+ # the SQL statement to create a view.
45
+ def view_definition(view_name, name = nil)
46
+ view_full_definition(view_name, name).first
47
+ end
48
+
49
+ # Returns the view type of a given view. This is either :view or :materialized
50
+ def view_type(view_name, name = nil)
51
+ view_full_definition(view_name, name).second
52
+ end
53
+
40
54
  #####################################################################
41
55
  #
42
56
  # The functions below here are abstract; each subclass should
43
57
  # define them all. Defining them here only for reference.
44
58
  #
45
59
 
46
- # (abstract) Returns the names of all views, as an array of strings
47
- def views(name = nil) raise "Internal Error: Connection adapter didn't override abstract function"; [] end
60
+ # (abstract) Refreshes the given materialized view.
61
+ def refresh_view(view_name, name = nil) raise "Internal Error: Connection adapter didn't override abstract function"; end
48
62
 
49
- # (abstract) Returns the SQL definition of a given view. This is
63
+ # (abstract) Returns the SQL definition and type of a given view. This is
50
64
  # the literal SQL would come after 'CREATVE VIEW viewname AS ' in
51
- # the SQL statement to create a view.
52
- def view_definition(view_name, name = nil) raise "Internal Error: Connection adapter didn't override abstract function"; end
65
+ # the SQL statement to create a view. The type is either :view, or :materialized
66
+ def view_full_definition(view_name, name = nil) raise "Internal Error: Connection adapter didn't override abstract function"; end
53
67
  end
54
68
  end
55
69
  end
@@ -3,16 +3,8 @@ module SchemaPlus::Views
3
3
  module ConnectionAdapters
4
4
  module Mysql2Adapter
5
5
 
6
- def views(name = nil)
7
- SchemaMonkey::Middleware::Schema::Views.start(connection: self, query_name: name, views: []) { |env|
8
- select_all("SELECT table_name FROM information_schema.views WHERE table_schema = SCHEMA()", env.query_name).each do |row|
9
- env.views << row["table_name"]
10
- end
11
- }.views
12
- end
13
-
14
- def view_definition(view_name, name = nil)
15
- SchemaMonkey::Middleware::Schema::ViewDefinition.start(connection: self, view_name: view_name, query_name: name) { |env|
6
+ def view_full_definition(view_name, name = nil)
7
+ data = SchemaMonkey::Middleware::Schema::ViewDefinition.start(connection: self, view_name: view_name, query_name: name, view_type: :view) { |env|
16
8
  results = select_all("SELECT view_definition, check_option FROM information_schema.views WHERE table_schema = SCHEMA() AND table_name = #{quote(view_name)}", name)
17
9
  if results.any?
18
10
  row = results.first
@@ -24,7 +16,9 @@ module SchemaPlus::Views
24
16
  end
25
17
  env.definition = sql
26
18
  end
27
- }.definition
19
+ }
20
+
21
+ [data.definition, data.view_type]
28
22
  end
29
23
 
30
24
  end
@@ -2,31 +2,88 @@ module SchemaPlus::Views
2
2
  module ActiveRecord
3
3
  module ConnectionAdapters
4
4
  module PostgresqlAdapter
5
+ POSTGIS_VIEWS = %W[
6
+ geography_columns
7
+ geometry_columns
8
+ raster_columns
9
+ raster_overviews
10
+ ].freeze
5
11
 
6
- def views(name = nil) #:nodoc:
7
- SchemaMonkey::Middleware::Schema::Views.start(connection: self, query_name: name, views: []) { |env|
8
- sql = <<-SQL
9
- SELECT viewname
10
- FROM pg_views
11
- WHERE schemaname = ANY (current_schemas(false))
12
- AND viewname NOT LIKE 'pg\_%'
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
12
+ # Create a view given the SQL definition. Specify :force => true
13
+ # to first drop the view if it already exists.
14
+ def create_view(view_name, definition, options={})
15
+ SchemaMonkey::Middleware::Migration::CreateView.start(connection: self, view_name: view_name, definition: definition, options: options) do |env|
16
+ definition = env.definition
17
+ view_name = env.view_name
18
+ options = env.options
19
+ definition = definition.to_sql if definition.respond_to? :to_sql
20
+
21
+ if options[:materialized] && options[:allow_replace]
22
+ raise ArgumentError, 'allow_replace is not supported for materialized views'
23
+ end
24
+
25
+ if options[:force]
26
+ drop_view(view_name, {if_exists: true}.merge(options.slice(:materialized)))
27
+ end
28
+
29
+ command = if options[:materialized]
30
+ "CREATE MATERIALIZED"
31
+ elsif options[:allow_replace]
32
+ "CREATE OR REPLACE"
33
+ else
34
+ "CREATE"
35
+ end
36
+
37
+ execute "#{command} VIEW #{quote_table_name(view_name)} AS #{definition}"
38
+ end
39
+ end
40
+
41
+ # Drop the named view. Specify :if_exists => true
42
+ # to fail silently if the view doesn't exist.
43
+ def drop_view(view_name, options = {})
44
+ SchemaMonkey::Middleware::Migration::DropView.start(connection: self, view_name: view_name, options: options) do |env|
45
+ view_name = env.view_name
46
+ options = env.options
47
+ materialized = options[:materialized] ? 'MATERIALIZED' : ''
48
+ sql = "DROP #{materialized} VIEW"
49
+ sql += " IF EXISTS" if options[:if_exists]
50
+ sql += " #{quote_table_name(view_name)}"
51
+ execute sql
52
+ end
17
53
  end
18
54
 
19
- def view_definition(view_name, name = nil) #:nodoc:
20
- SchemaMonkey::Middleware::Schema::ViewDefinition.start(connection: self, view_name: view_name, query_name: name) { |env|
55
+ # Refresh a materialized view.
56
+ def refresh_view(view_name, options = {})
57
+ SchemaMonkey::Middleware::Migration::RefreshView.start(connection: self, view_name: view_name, options: options) do |env|
58
+ view_name = env.view_name
59
+ sql = "REFRESH MATERIALIZED VIEW #{quote_table_name(view_name)}"
60
+ execute sql
61
+ end
62
+ end
63
+
64
+ def views #:nodoc:
65
+ # Filter out any view that begins with "pg_"
66
+ super.reject do |c|
67
+ c.start_with?("pg_") || POSTGIS_VIEWS.include?(c)
68
+ end
69
+ end
70
+
71
+ def view_full_definition(view_name, name = nil) #:nodoc:
72
+ data = SchemaMonkey::Middleware::Schema::ViewDefinition.start(connection: self, view_name: view_name, query_name: name, view_type: :view) { |env|
21
73
  result = env.connection.query(<<-SQL, name)
22
- SELECT pg_get_viewdef(oid)
74
+ SELECT pg_get_viewdef(oid), relkind
23
75
  FROM pg_class
24
- WHERE relkind = 'v'
76
+ WHERE relkind in ('v', 'm')
25
77
  AND relname = '#{env.view_name}'
26
78
  SQL
27
79
  row = result.first
28
- env.definition = row.first.chomp(';').strip unless row.nil?
29
- }.definition
80
+ unless row.nil?
81
+ env.definition = row.first.chomp(';').strip
82
+ env.view_type = :materialized if row.second == 'm'
83
+ end
84
+ }
85
+
86
+ [data.definition, data.view_type]
30
87
  end
31
88
 
32
89
  end
@@ -3,18 +3,14 @@ module SchemaPlus::Views
3
3
  module ConnectionAdapters
4
4
  module Sqlite3Adapter
5
5
 
6
- def views(name = nil)
7
- SchemaMonkey::Middleware::Schema::Views.start(connection: self, query_name: name, views: []) { |env|
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|
6
+ def view_full_definition(view_name, name = nil)
7
+ data = SchemaMonkey::Middleware::Schema::ViewDefinition.start(connection: self, view_name: view_name, query_name: name, view_type: :view) { |env|
14
8
  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
9
  sql.sub!(/^CREATE VIEW \S* AS\s+/im, '') unless sql.nil?
16
10
  env.definition = sql
17
- }.definition
11
+ }
12
+
13
+ [data.definition, data.view_type]
18
14
  end
19
15
 
20
16
  end
@@ -11,7 +11,9 @@ module SchemaPlus::Views
11
11
  end
12
12
 
13
13
  def invert_create_view(args)
14
- [ :drop_view, [args.first] ]
14
+ options = {}
15
+ options[:materialized] = args[2][:materialized] if args[2].has_key?(:materialized)
16
+ [ :drop_view, [args.first, options] ]
15
17
  end
16
18
 
17
19
  end
@@ -9,45 +9,63 @@ module SchemaPlus::Views
9
9
  re_view_referent = %r{(?:(?i)FROM|JOIN) \S*\b(\S+)\b}
10
10
  env.connection.views.each do |view_name|
11
11
  next if env.dumper.ignored?(view_name)
12
- view = View.new(name: view_name, definition: env.connection.view_definition(view_name))
12
+ definition, view_type = env.connection.view_full_definition(view_name)
13
+
14
+ indexes = []
15
+
16
+ if view_type == :materialized
17
+ env.connection.indexes(view_name).each do |index|
18
+ indexes << SchemaPlus::Core::SchemaDump::Table::Index.new(
19
+ name: index.name, columns: index.columns, options: view_index_options(index, env.connection)
20
+ )
21
+ end
22
+ end
23
+
24
+ view = View.new(
25
+ name: view_name,
26
+ definition: definition,
27
+ view_type: view_type,
28
+ indexes: indexes
29
+ )
30
+
13
31
  env.dump.tables[view.name] = view
14
32
  env.dump.depends(view.name, view.definition.scan(re_view_referent).flatten)
15
33
  end
16
34
  end
17
35
 
18
- # quacks like a SchemaMonkey Dump::Table
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}
36
+ # Take from ActiveRecord::SchemaDumper#index_parts
37
+ def view_index_options(index, connection)
38
+ options = {}
39
+ options[:unique] = true if index.unique
40
+ options[:length] = index.lengths if index.lengths.present?
41
+ options[:order] = index.orders if index.orders.present?
42
+ options[:opclass] = index.opclasses if index.opclasses.present?
43
+ options[:where] = index.where if index.where
44
+ options[:using] = index.using if !connection.default_index_type?(index)
45
+ options[:type] = index.type if index.type
46
+ options[:comment] = index.comment if index.comment
26
47
 
27
- ENDVIEW
28
- end
48
+ options
29
49
  end
30
- end
31
- end
32
50
 
33
- module Schema
34
- module Tables
35
-
36
- module Mysql
37
- def after(env)
38
- Tables.filter_out_views(env)
39
- end
40
- end
51
+ # quacks like a SchemaMonkey Dump::Table
52
+ class View < KeyStruct[:name, :definition, :view_type, :indexes]
53
+ def assemble(stream)
54
+ extra_options = ", materialized: true" if view_type == :materialized
55
+ heredelim = "END_VIEW_#{name.upcase}"
56
+ stream.puts <<~ENDVIEW
57
+ create_view "#{name}", <<-'#{heredelim}', :force => true#{extra_options}
58
+ #{definition}
59
+ #{heredelim}
60
+ ENDVIEW
41
61
 
42
- module Sqlite3
43
- def after(env)
44
- Tables.filter_out_views(env)
62
+ indexes.each do |index|
63
+ stream.write "add_index \"#{name}\", "
64
+ index.assemble(stream)
65
+ stream.puts ""
66
+ end
45
67
  end
46
68
  end
47
-
48
- def self.filter_out_views(env)
49
- env.tables -= env.connection.views(env.query_name)
50
- end
51
69
  end
52
70
  end
53
71
 
@@ -56,11 +74,8 @@ module SchemaPlus::Views
56
74
  # for tables
57
75
 
58
76
  module Schema
59
- module Views
60
- ENV = [:connection, :query_name, :views]
61
- end
62
77
  module ViewDefinition
63
- ENV = [:connection, :view_name, :query_name, :definition]
78
+ ENV = [:connection, :view_name, :query_name, :definition, :view_type]
64
79
  end
65
80
  end
66
81
 
@@ -71,6 +86,9 @@ module SchemaPlus::Views
71
86
  module DropView
72
87
  ENV = [:connection, :view_name, :options]
73
88
  end
89
+ module RefreshView
90
+ ENV = [:connection, :view_name, :options]
91
+ end
74
92
  end
75
93
  end
76
94
 
@@ -1,5 +1,5 @@
1
1
  module SchemaPlus
2
2
  module Views
3
- VERSION = "0.3.1"
3
+ VERSION = "0.4.0"
4
4
  end
5
5
  end
data/schema_dev.yml CHANGED
@@ -1,8 +1,11 @@
1
1
  ruby:
2
- - 2.1.5
2
+ - 2.5.9
3
+ - 2.7.3
3
4
  activerecord:
4
- - 4.2
5
+ - 5.2
5
6
  db:
6
7
  - mysql2
7
8
  - sqlite3
8
9
  - postgresql
10
+ dbversions:
11
+ postgresql: ['9.6', '10', '11', '12']