scenic 1.0.0 → 1.1.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 +4 -4
- data/.gitignore +1 -0
- data/.travis.yml +14 -4
- data/.yardopts +0 -1
- data/Appraisals +25 -0
- data/CONTRIBUTING.md +5 -4
- data/LICENSE.txt +1 -1
- data/NEWS.md +20 -0
- data/README.md +68 -28
- data/bin/appraisal +16 -0
- data/bin/setup +5 -0
- data/bin/yard +16 -0
- data/gemfiles/rails40.gemfile +8 -0
- data/gemfiles/rails41.gemfile +8 -0
- data/gemfiles/rails42.gemfile +8 -0
- data/gemfiles/rails50.gemfile +14 -0
- data/lib/generators/scenic/generators.rb +1 -0
- data/lib/generators/scenic/model/templates/model.erb +1 -1
- data/lib/scenic.rb +1 -0
- data/lib/scenic/adapters/postgres.rb +140 -33
- data/lib/scenic/adapters/postgres/connection.rb +57 -0
- data/lib/scenic/adapters/postgres/errors.rb +26 -0
- data/lib/scenic/adapters/postgres/index_reapplication.rb +71 -0
- data/lib/scenic/adapters/postgres/indexes.rb +53 -0
- data/lib/scenic/adapters/postgres/views.rb +51 -0
- data/lib/scenic/configuration.rb +2 -2
- data/lib/scenic/index.rb +36 -0
- data/lib/scenic/schema_dumper.rb +1 -1
- data/lib/scenic/statements.rb +7 -13
- data/lib/scenic/version.rb +1 -1
- data/lib/scenic/view.rb +0 -3
- data/scenic.gemspec +4 -1
- data/spec/dummy/config/application.rb +3 -0
- data/spec/scenic/adapters/postgres/connection_spec.rb +79 -0
- data/spec/scenic/adapters/postgres/views_spec.rb +37 -0
- data/spec/scenic/adapters/postgres_spec.rb +84 -26
- data/spec/scenic/statements_spec.rb +14 -15
- data/spec/smoke +16 -3
- data/spec/spec_helper.rb +4 -0
- metadata +64 -8
- data/spec/dummy/config/environments/development.rb +0 -6
- data/spec/dummy/config/environments/test.rb +0 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d42b1d2b3e1f8fd304e1f6ba852a0cc54b6727b6
|
4
|
+
data.tar.gz: 3a84ebfd4b1a34ad03a68f232961c6381107cd74
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e67ff566304f93fd0c70a6c4968d2551ea9a82c3111f9f8a95f4b1dbc4e43a3f7233d9b71fc0551b8ffd806f78c5967bbd43d7b0a3783a80cc1da68b99edd041
|
7
|
+
data.tar.gz: 8b19e9f8e3e93142a57c15ec0f191abeab1278395bb6a09fc9face3908e8039a58cc1704d32cbecb834f966f355a6b98256ed8d2ba0727e4209efe6c83cb52e9
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
addons:
|
2
|
-
postgresql: "9.
|
2
|
+
postgresql: "9.4"
|
3
3
|
before_install:
|
4
4
|
- "echo '--colour' > ~/.rspec"
|
5
5
|
- "echo 'gem: --no-document' > ~/.gemrc"
|
@@ -15,7 +15,17 @@ language:
|
|
15
15
|
notifications:
|
16
16
|
email:
|
17
17
|
- false
|
18
|
-
rvm:
|
19
|
-
- 2.1.7
|
20
|
-
- 2.2.3
|
21
18
|
sudo: false
|
19
|
+
rvm:
|
20
|
+
- 2.3.0
|
21
|
+
- 2.2.4
|
22
|
+
- 2.1.8
|
23
|
+
gemfile:
|
24
|
+
- gemfiles/rails40.gemfile
|
25
|
+
- gemfiles/rails41.gemfile
|
26
|
+
- gemfiles/rails42.gemfile
|
27
|
+
- gemfiles/rails50.gemfile
|
28
|
+
matrix:
|
29
|
+
exclude:
|
30
|
+
- rvm: 2.1.8
|
31
|
+
gemfile: gemfiles/rails50.gemfile
|
data/.yardopts
CHANGED
data/Appraisals
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
appraise "rails40" do
|
2
|
+
gem "activerecord", "~> 4.0.0"
|
3
|
+
gem "railties", "~> 4.0.0"
|
4
|
+
end
|
5
|
+
|
6
|
+
appraise "rails41" do
|
7
|
+
gem "activerecord", "~> 4.1.0"
|
8
|
+
gem "railties", "~> 4.1.0"
|
9
|
+
end
|
10
|
+
|
11
|
+
appraise "rails42" do
|
12
|
+
gem "activerecord", "~> 4.2.0"
|
13
|
+
gem "railties", "~> 4.2.0"
|
14
|
+
end
|
15
|
+
|
16
|
+
appraise "rails50" do
|
17
|
+
gem "activerecord", "~> 5.0.0.beta1"
|
18
|
+
gem "railties", "~> 5.0.0.beta1"
|
19
|
+
gem "rspec-rails", github: "rspec/rspec-rails"
|
20
|
+
gem "rspec-support", github: "rspec/rspec-support"
|
21
|
+
gem "rspec-core", github: "rspec/rspec-core"
|
22
|
+
gem "rspec-mocks", github: "rspec/rspec-mocks"
|
23
|
+
gem "rspec-expectations", github: "rspec/rspec-expectations"
|
24
|
+
gem "rspec", github: "rspec/rspec"
|
25
|
+
end
|
data/CONTRIBUTING.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# Contributing
|
2
2
|
|
3
|
-
We love
|
3
|
+
We love contributions from everyone. By participating in this project, you
|
4
4
|
agree to abide by the thoughtbot [code of conduct].
|
5
5
|
|
6
6
|
[code of conduct]: https://thoughtbot.com/open-source-code-of-conduct
|
@@ -8,12 +8,13 @@ agree to abide by the thoughtbot [code of conduct].
|
|
8
8
|
We expect everyone to follow the code of conduct anywhere in thoughtbot's
|
9
9
|
project codebases, issue trackers, chatrooms, and mailing lists.
|
10
10
|
|
11
|
-
##
|
11
|
+
## Contributing Code
|
12
12
|
|
13
|
-
1.
|
13
|
+
1. Fork the repository.
|
14
14
|
2. Run `bin/setup`, which will install dependencies and create the dummy
|
15
15
|
application database.
|
16
|
-
3. Run `rake` to verify that the tests pass
|
16
|
+
3. Run `bin/appraisal rake` to verify that the tests pass against all
|
17
|
+
supported versions of Rails.
|
17
18
|
4. Make your change with new passing tests, following the [style guide].
|
18
19
|
5. Write a [good commit message], push your fork, and submit a pull request.
|
19
20
|
|
data/LICENSE.txt
CHANGED
data/NEWS.md
CHANGED
@@ -5,6 +5,26 @@ changelog, see the [CHANGELOG] for each version via the version links.
|
|
5
5
|
|
6
6
|
[CHANGELOG]: https://github.com/thoughtbot/scenic/commits/master
|
7
7
|
|
8
|
+
## [1.1.0] - January 8, 2016
|
9
|
+
|
10
|
+
### Added
|
11
|
+
- Added support for updating materialized view definitions while maintaining
|
12
|
+
existing indexes that are still applicable after the update.
|
13
|
+
- Added support for refreshing materialized views concurrently (requires
|
14
|
+
Postgres 9.4 or newer).
|
15
|
+
|
16
|
+
### Fixed
|
17
|
+
- The schema dumper will now dump views and materialized views together in the
|
18
|
+
order they are returned by Postgres. This fixes issues when loading views that
|
19
|
+
depend on other views via `rake db:schema:load`.
|
20
|
+
- Scenic now works on [supported versions of Postgres] older than 9.3.0.
|
21
|
+
Attempts to use database features not supported by your specific version of
|
22
|
+
Postgres will raise descriptive errors.
|
23
|
+
- Fixed inability to dump materialized views in Rails 5.0.0.beta1.
|
24
|
+
|
25
|
+
[supported versions of Postgres]: http://www.postgresql.org/support/versioning/
|
26
|
+
[1.1.0]: https://github.com/thoughtbot/scenic/compare/v1.0.0...v1.1.0
|
27
|
+
|
8
28
|
## [1.0.0] - November 23, 2015
|
9
29
|
|
10
30
|
### Added
|
data/README.md
CHANGED
@@ -1,5 +1,11 @@
|
|
1
1
|
# Scenic
|
2
2
|
|
3
|
+

|
4
|
+
|
5
|
+
[](https://travis-ci.org/thoughtbot/scenic)
|
6
|
+
[](https://codeclimate.com/repos/53c9736269568066a3000c35/feed)
|
7
|
+
[](http://inch-ci.org/github/thoughtbot/scenic)
|
8
|
+
|
3
9
|
Scenic adds methods to `ActiveRecord::Migration` to create and manage database
|
4
10
|
views in Rails.
|
5
11
|
|
@@ -17,18 +23,18 @@ Scenic ships with support for PostgreSQL. The adapter is configurable (see
|
|
17
23
|
|
18
24
|
## Great, how do I create a view?
|
19
25
|
|
20
|
-
You've got this great idea for a view you'd like to call `
|
21
|
-
create the migration and the corresponding view definition file with the
|
26
|
+
You've got this great idea for a view you'd like to call `search_results`. You
|
27
|
+
can create the migration and the corresponding view definition file with the
|
22
28
|
following command:
|
23
29
|
|
24
30
|
```sh
|
25
|
-
$ rails generate scenic:view
|
26
|
-
create db/views/
|
27
|
-
create db/migrate/[TIMESTAMP]
|
31
|
+
$ rails generate scenic:view search_results
|
32
|
+
create db/views/search_results_v01.sql
|
33
|
+
create db/migrate/[TIMESTAMP]_create_search_results.rb
|
28
34
|
```
|
29
35
|
|
30
|
-
Edit the `db/views/
|
31
|
-
your view. In our example, this might look something like this:
|
36
|
+
Edit the `db/views/search_results_v01.sql` file with the SQL statement that
|
37
|
+
defines your view. In our example, this might look something like this:
|
32
38
|
|
33
39
|
```sql
|
34
40
|
SELECT
|
@@ -62,13 +68,13 @@ $ rake db:migrate
|
|
62
68
|
Here's where Scenic really shines. Run that same view generator once more:
|
63
69
|
|
64
70
|
```sh
|
65
|
-
$ rails generate scenic:view
|
66
|
-
create db/views/
|
67
|
-
create db/migrate/[TIMESTAMP]
|
71
|
+
$ rails generate scenic:view search_results
|
72
|
+
create db/views/search_results_v02.sql
|
73
|
+
create db/migrate/[TIMESTAMP]_update_search_results_to_version_2.rb
|
68
74
|
```
|
69
75
|
|
70
|
-
Scenic detected that we already had an existing `
|
71
|
-
created a copy of that definition as version 2, and created a migration to
|
76
|
+
Scenic detected that we already had an existing `search_results` view at version
|
77
|
+
1, created a copy of that definition as version 2, and created a migration to
|
72
78
|
update to the version 2 schema. All that's left for you to do is tweak the
|
73
79
|
schema in the new definition and run the `update_view` migration.
|
74
80
|
|
@@ -76,11 +82,13 @@ schema in the new definition and run the `update_view` migration.
|
|
76
82
|
|
77
83
|
You bet! Using view-backed models can help promote concepts hidden in your
|
78
84
|
relational data to first-class domain objects and can clean up complex
|
79
|
-
ActiveRecord or ARel queries. As far as ActiveRecord is concerned,
|
85
|
+
ActiveRecord or ARel queries. As far as ActiveRecord is concerned, a view is
|
80
86
|
no different than a table.
|
81
87
|
|
82
88
|
```ruby
|
83
|
-
class
|
89
|
+
class SearchResult < ActiveRecord::Base
|
90
|
+
belongs_to :searchable, polymorphic: true
|
91
|
+
|
84
92
|
private
|
85
93
|
|
86
94
|
# this isn't strictly necessary, but it will prevent
|
@@ -109,18 +117,6 @@ $ rails generate scenic:model recent_status
|
|
109
117
|
create db/migrate/20151112015036_create_recent_statuses.rb
|
110
118
|
```
|
111
119
|
|
112
|
-
### When I query that model with `find` I get an error. What gives?
|
113
|
-
|
114
|
-
Your view cannot have a primary key, but ActiveRecord's `find` method expects to
|
115
|
-
query based on one. You can use `find_by!` or you can explicitly set the primary
|
116
|
-
key column on your model like so:
|
117
|
-
|
118
|
-
```ruby
|
119
|
-
class People < ActiveRecord::Base
|
120
|
-
self.primary_key = :id
|
121
|
-
end
|
122
|
-
```
|
123
|
-
|
124
120
|
## What about materialized views?
|
125
121
|
|
126
122
|
Materialized views are essentially SQL queries whose results can be cached to a
|
@@ -134,20 +130,64 @@ refreshes:
|
|
134
130
|
|
135
131
|
```ruby
|
136
132
|
def self.refresh
|
137
|
-
Scenic.database.refresh_materialized_view(table_name)
|
133
|
+
Scenic.database.refresh_materialized_view(table_name, concurrently: false)
|
138
134
|
end
|
139
135
|
```
|
140
136
|
|
137
|
+
This will perform a non-concurrent refresh, locking the view for selects until
|
138
|
+
the refresh is complete. You can avoid locking the view by passing
|
139
|
+
`concurrently: true` but this requires both PostgreSQL 9.4 and your view to have
|
140
|
+
at least one unique index that covers all rows.
|
141
|
+
|
141
142
|
## I don't need this view anymore. Make it go away.
|
142
143
|
|
143
144
|
Scenic gives you `drop_view` too:
|
144
145
|
|
145
146
|
```ruby
|
146
147
|
def change
|
147
|
-
drop_view :
|
148
|
+
drop_view :search_results, revert_to_version: 2
|
148
149
|
end
|
149
150
|
```
|
150
151
|
|
152
|
+
## FAQs
|
153
|
+
|
154
|
+
**When I query a view-backed model with `find` I get an error. What gives?**
|
155
|
+
|
156
|
+
Your view cannot have a primary key, but ActiveRecord's `find` method expects to
|
157
|
+
query based on one. You can use `find_by!` or you can explicitly set the primary
|
158
|
+
key column on your model like so:
|
159
|
+
|
160
|
+
```ruby
|
161
|
+
class People < ActiveRecord::Base
|
162
|
+
self.primary_key = :id
|
163
|
+
end
|
164
|
+
```
|
165
|
+
|
166
|
+
**Why is my view missing columns from the underlying table?**
|
167
|
+
|
168
|
+
Did you create the view with `SELECT [table_name].*`? Most (possibly all)
|
169
|
+
relational databases freeze the view definition at the time of creation. New
|
170
|
+
columns will not be available in the view until the definition is updated once
|
171
|
+
again. This can be accomplished by "updating" the view to its current definition
|
172
|
+
to bake in the new meaning of `*`.
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
add_column :posts, :title, :string
|
176
|
+
update_view :posts_with_aggregate_data, version: 2, revert_to_version: 2
|
177
|
+
```
|
178
|
+
|
179
|
+
**When will you support MySQL?**
|
180
|
+
|
181
|
+
We have no plans to add first-party support for MySQL at this time because we
|
182
|
+
(the maintainers) do not currently have a use for it. It's our experience that
|
183
|
+
maintaining a library effectively requires regular use of its features. We're
|
184
|
+
not in a good position to support MySQL users.
|
185
|
+
|
186
|
+
Scenic *does* support configuring different database adapters and should be
|
187
|
+
extendable with adapter libraries. If you implement such an adapter, we're happy
|
188
|
+
to review and link to it. We're also happy to make changes that would better
|
189
|
+
accommodate adapter gems.
|
190
|
+
|
151
191
|
## About
|
152
192
|
|
153
193
|
Scenic is maintained by [Derek Prior] and [Caleb Thompson], funded by
|
data/bin/appraisal
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'appraisal' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('appraisal', 'appraisal')
|
data/bin/setup
CHANGED
data/bin/yard
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'yard' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('yard', 'yard')
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "activerecord", "~> 5.0.0.beta1"
|
6
|
+
gem "railties", "~> 5.0.0.beta1"
|
7
|
+
gem "rspec-rails", :github => "rspec/rspec-rails"
|
8
|
+
gem "rspec-support", :github => "rspec/rspec-support"
|
9
|
+
gem "rspec-core", :github => "rspec/rspec-core"
|
10
|
+
gem "rspec-mocks", :github => "rspec/rspec-mocks"
|
11
|
+
gem "rspec-expectations", :github => "rspec/rspec-expectations"
|
12
|
+
gem "rspec", :github => "rspec/rspec"
|
13
|
+
|
14
|
+
gemspec :path => "../"
|
data/lib/scenic.rb
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
require_relative "postgres/connection"
|
2
|
+
require_relative "postgres/errors"
|
3
|
+
require_relative "postgres/index_reapplication"
|
4
|
+
require_relative "postgres/indexes"
|
5
|
+
require_relative "postgres/views"
|
6
|
+
|
1
7
|
module Scenic
|
2
8
|
# Scenic database adapters.
|
3
9
|
#
|
@@ -7,14 +13,31 @@ module Scenic
|
|
7
13
|
module Adapters
|
8
14
|
# An adapter for managing Postgres views.
|
9
15
|
#
|
10
|
-
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
14
|
-
# For methods usable in migrations see {Statements}.
|
16
|
+
# These methods are used interally by Scenic and are not intended for direct
|
17
|
+
# use. Methods that alter database schema are intended to be called via
|
18
|
+
# {Statements}, while {#refresh_materialized_view} is called via
|
19
|
+
# {Scenic.database}.
|
15
20
|
#
|
16
|
-
#
|
21
|
+
# The methods are documented here for insight into specifics of how Scenic
|
22
|
+
# integrates with Postgres and the responsibilities of {Adapters}.
|
17
23
|
class Postgres
|
24
|
+
# Creates an instance of the Scenic Postgres adapter.
|
25
|
+
#
|
26
|
+
# This is the default adapter for Scenic. Configuring it via
|
27
|
+
# {Scenic.configure} is not required, but the example below shows how one
|
28
|
+
# would explicitly set it.
|
29
|
+
#
|
30
|
+
# @param connection The database connection the adapter should use. This
|
31
|
+
# defaults to `ActiveRecord::Base.connection`
|
32
|
+
#
|
33
|
+
# @example
|
34
|
+
# Scenic.configure do |config|
|
35
|
+
# config.adapter = Scenic::Adapters::Postgres.new
|
36
|
+
# end
|
37
|
+
def initialize(connection = ActiveRecord::Base.connection)
|
38
|
+
@connection = Connection.new(connection)
|
39
|
+
end
|
40
|
+
|
18
41
|
# Returns an array of views in the database.
|
19
42
|
#
|
20
43
|
# This collection of views is used by the [Scenic::SchemaDumper] to
|
@@ -22,73 +45,157 @@ module Scenic
|
|
22
45
|
#
|
23
46
|
# @return [Array<Scenic::View>]
|
24
47
|
def views
|
25
|
-
|
26
|
-
SELECT viewname, definition, FALSE AS materialized
|
27
|
-
FROM pg_views
|
28
|
-
WHERE schemaname = ANY (current_schemas(false))
|
29
|
-
AND viewname NOT IN (SELECT extname FROM pg_extension)
|
30
|
-
UNION
|
31
|
-
SELECT matviewname AS viewname, definition, TRUE AS materialized
|
32
|
-
FROM pg_matviews
|
33
|
-
WHERE schemaname = ANY (current_schemas(false))
|
34
|
-
ORDER BY viewname
|
35
|
-
SQL
|
48
|
+
Views.new(connection).all
|
36
49
|
end
|
37
50
|
|
38
51
|
# Creates a view in the database.
|
39
52
|
#
|
53
|
+
# This is typically called in a migration via {Statements#create_view}.
|
54
|
+
#
|
40
55
|
# @param name The name of the view to create
|
41
|
-
# @param sql_definition
|
56
|
+
# @param sql_definition The SQL schema for the view.
|
57
|
+
#
|
42
58
|
# @return [void]
|
43
59
|
def create_view(name, sql_definition)
|
44
|
-
execute "CREATE VIEW #{name} AS #{sql_definition};"
|
60
|
+
execute "CREATE VIEW #{quote_table_name(name)} AS #{sql_definition};"
|
61
|
+
end
|
62
|
+
|
63
|
+
# Updates a view in the database.
|
64
|
+
#
|
65
|
+
# This results in a {#drop_view} followed by a {#create_view}. The
|
66
|
+
# explicitness of that two step process is preferred to `CREATE OR
|
67
|
+
# REPLACE VIEW` because the former ensures that the view you are trying to
|
68
|
+
# update did, in fact, already exist. Additionally, `CREATE OR REPLACE
|
69
|
+
# VIEW` is allowed only to add new columns to the end of an existing
|
70
|
+
# view schema. Existing columns cannot be re-ordered, removed, or have
|
71
|
+
# their types changed. Drop and create overcomes this limitation as well.
|
72
|
+
#
|
73
|
+
# This is typically called in a migration via {Statements#update_view}.
|
74
|
+
#
|
75
|
+
# @param name The name of the view to update
|
76
|
+
# @param sql_definition The SQL schema for the updated view.
|
77
|
+
#
|
78
|
+
# @return [void]
|
79
|
+
def update_view(name, sql_definition)
|
80
|
+
drop_view(name)
|
81
|
+
create_view(name, sql_definition)
|
45
82
|
end
|
46
83
|
|
47
84
|
# Drops the named view from the database
|
48
85
|
#
|
86
|
+
# This is typically called in a migration via {Statements#drop_view}.
|
87
|
+
#
|
49
88
|
# @param name The name of the view to drop
|
89
|
+
#
|
50
90
|
# @return [void]
|
51
91
|
def drop_view(name)
|
52
|
-
execute "DROP VIEW #{name};"
|
92
|
+
execute "DROP VIEW #{quote_table_name(name)};"
|
53
93
|
end
|
54
94
|
|
55
95
|
# Creates a materialized view in the database
|
56
96
|
#
|
57
97
|
# @param name The name of the materialized view to create
|
58
98
|
# @param sql_definition The SQL schema that defines the materialized view.
|
99
|
+
#
|
100
|
+
# This is typically called in a migration via {Statements#create_view}.
|
101
|
+
#
|
102
|
+
# @raise [MaterializedViewsNotSupportedError] if the version of Postgres
|
103
|
+
# in use does not support materialized views.
|
104
|
+
#
|
59
105
|
# @return [void]
|
60
106
|
def create_materialized_view(name, sql_definition)
|
61
|
-
|
107
|
+
raise_unless_materialized_views_supported
|
108
|
+
execute "CREATE MATERIALIZED VIEW #{quote_table_name(name)} AS #{sql_definition};"
|
109
|
+
end
|
110
|
+
|
111
|
+
# Updates a materialized view in the database.
|
112
|
+
#
|
113
|
+
# Drops and recreates the materialized view. Attempts to maintain all
|
114
|
+
# previously existing and still applicable indexes on the materialized
|
115
|
+
# view after the view is recreated.
|
116
|
+
#
|
117
|
+
# This is typically called in a migration via {Statements#update_view}.
|
118
|
+
#
|
119
|
+
# @param name The name of the view to update
|
120
|
+
# @param sql_definition The SQL schema for the updated view.
|
121
|
+
#
|
122
|
+
# @raise [MaterializedViewsNotSupportedError] if the version of Postgres
|
123
|
+
# in use does not support materialized views.
|
124
|
+
#
|
125
|
+
# @return [void]
|
126
|
+
def update_materialized_view(name, sql_definition)
|
127
|
+
raise_unless_materialized_views_supported
|
128
|
+
|
129
|
+
IndexReapplication.new(connection: connection).on(name) do
|
130
|
+
drop_materialized_view(name)
|
131
|
+
create_materialized_view(name, sql_definition)
|
132
|
+
end
|
62
133
|
end
|
63
134
|
|
64
135
|
# Drops a materialized view in the database
|
65
136
|
#
|
137
|
+
# This is typically called in a migration via {Statements#update_view}.
|
138
|
+
#
|
66
139
|
# @param name The name of the materialized view to drop.
|
140
|
+
# @raise [MaterializedViewsNotSupportedError] if the version of Postgres
|
141
|
+
# in use does not support materialized views.
|
142
|
+
#
|
67
143
|
# @return [void]
|
68
144
|
def drop_materialized_view(name)
|
69
|
-
|
145
|
+
raise_unless_materialized_views_supported
|
146
|
+
execute "DROP MATERIALIZED VIEW #{quote_table_name(name)};"
|
70
147
|
end
|
71
148
|
|
72
149
|
# Refreshes a materialized view from its SQL schema.
|
73
150
|
#
|
74
|
-
#
|
151
|
+
# This is typically called from application code via {Scenic.database}.
|
152
|
+
#
|
153
|
+
# @param name The name of the materialized view to refresh.
|
154
|
+
# @param concurrently [Boolean] Whether the refreshs hould happen
|
155
|
+
# concurrently or not. A concurrent refresh allows the view to be
|
156
|
+
# refreshed without locking the view for select but requires that the
|
157
|
+
# table have at least one unique index that covers all rows. Attempts to
|
158
|
+
# refresh concurrently without a unique index will raise a descriptive
|
159
|
+
# error.
|
160
|
+
#
|
161
|
+
# @raise [MaterializedViewsNotSupportedError] if the version of Postgres
|
162
|
+
# in use does not support materialized views.
|
163
|
+
# @raise [ConcurrentRefreshesNotSupportedError] when attempting a
|
164
|
+
# concurrent refresh on version of Postgres that does not support
|
165
|
+
# concurrent materialized view refreshes.
|
166
|
+
#
|
167
|
+
# @example Non-concurrent refresh
|
168
|
+
# Scenic.database.refresh_materialized_view(:search_results)
|
169
|
+
# @example Concurrent refresh
|
170
|
+
# Scenic.database.refresh_materialized_view(:posts, concurrent: true)
|
171
|
+
#
|
75
172
|
# @return [void]
|
76
|
-
def refresh_materialized_view(name)
|
77
|
-
|
173
|
+
def refresh_materialized_view(name, concurrently: false)
|
174
|
+
raise_unless_materialized_views_supported
|
175
|
+
|
176
|
+
if concurrently
|
177
|
+
raise_unless_concurrent_refresh_supported
|
178
|
+
execute "REFRESH MATERIALIZED VIEW CONCURRENTLY #{quote_table_name(name)};"
|
179
|
+
else
|
180
|
+
execute "REFRESH MATERIALIZED VIEW #{quote_table_name(name)};"
|
181
|
+
end
|
78
182
|
end
|
79
183
|
|
80
184
|
private
|
81
185
|
|
82
|
-
|
83
|
-
|
186
|
+
attr_reader :connection
|
187
|
+
delegate :execute, :quote_table_name, to: :connection
|
188
|
+
|
189
|
+
def raise_unless_materialized_views_supported
|
190
|
+
unless connection.supports_materialized_views?
|
191
|
+
raise MaterializedViewsNotSupportedError
|
192
|
+
end
|
84
193
|
end
|
85
194
|
|
86
|
-
def
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
materialized: result["materialized"] == "t",
|
91
|
-
)
|
195
|
+
def raise_unless_concurrent_refresh_supported
|
196
|
+
unless connection.supports_concurrent_refreshes?
|
197
|
+
raise ConcurrentRefreshesNotSupportedError
|
198
|
+
end
|
92
199
|
end
|
93
200
|
end
|
94
201
|
end
|