scenic 1.0.0 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
![Scenic Landscape](https://images.thoughtbot.com/announcing-scenic--versioned-database-views-for-rails/MRUcPsxrTGCeWKyE59Zg_landscape.png)
|
4
|
+
|
5
|
+
[![Build Status](https://travis-ci.org/thoughtbot/scenic.svg)](https://travis-ci.org/thoughtbot/scenic)
|
6
|
+
[![Code Climate](https://codeclimate.com/repos/53c9736269568066a3000c35/badges/85aa9b19f3037252c55d/gpa.svg)](https://codeclimate.com/repos/53c9736269568066a3000c35/feed)
|
7
|
+
[![Documentation Quality](http://inch-ci.org/github/thoughtbot/scenic.svg?branch=master)](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
|