scenic-jets 1.5.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +78 -0
- data/.gitignore +19 -0
- data/.hound.yml +2 -0
- data/.rubocop.yml +129 -0
- data/.yardopts +4 -0
- data/CHANGELOG.md +223 -0
- data/CODE_OF_CONDUCT.md +76 -0
- data/CONTRIBUTING.md +24 -0
- data/Gemfile +16 -0
- data/LICENSE.txt +22 -0
- data/README.md +5 -0
- data/Rakefile +29 -0
- data/SECURITY.md +14 -0
- data/bin/rake +17 -0
- data/bin/rspec +17 -0
- data/bin/setup +18 -0
- data/bin/yard +16 -0
- data/lib/generators/scenic/generators.rb +12 -0
- data/lib/generators/scenic/materializable.rb +31 -0
- data/lib/generators/scenic/model/USAGE +12 -0
- data/lib/generators/scenic/model/model_generator.rb +52 -0
- data/lib/generators/scenic/model/templates/model.erb +3 -0
- data/lib/generators/scenic/view/USAGE +20 -0
- data/lib/generators/scenic/view/templates/db/migrate/create_view.erb +5 -0
- data/lib/generators/scenic/view/templates/db/migrate/update_view.erb +12 -0
- data/lib/generators/scenic/view/view_generator.rb +127 -0
- data/lib/scenic.rb +31 -0
- data/lib/scenic/adapters/postgres.rb +256 -0
- 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/refresh_dependencies.rb +116 -0
- data/lib/scenic/adapters/postgres/views.rb +74 -0
- data/lib/scenic/command_recorder.rb +52 -0
- data/lib/scenic/command_recorder/statement_arguments.rb +51 -0
- data/lib/scenic/configuration.rb +37 -0
- data/lib/scenic/definition.rb +35 -0
- data/lib/scenic/index.rb +36 -0
- data/lib/scenic/schema_dumper.rb +44 -0
- data/lib/scenic/statements.rb +163 -0
- data/lib/scenic/version.rb +3 -0
- data/lib/scenic/view.rb +54 -0
- data/scenic.gemspec +36 -0
- data/spec/acceptance/user_manages_views_spec.rb +88 -0
- data/spec/acceptance_helper.rb +33 -0
- data/spec/dummy/.gitignore +16 -0
- data/spec/dummy/Rakefile +13 -0
- data/spec/dummy/app/models/application_record.rb +5 -0
- data/spec/dummy/bin/bundle +3 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +15 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/database.yml +14 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/db/migrate/.keep +0 -0
- data/spec/dummy/db/views/.keep +0 -0
- data/spec/generators/scenic/model/model_generator_spec.rb +36 -0
- data/spec/generators/scenic/view/view_generator_spec.rb +57 -0
- data/spec/integration/revert_spec.rb +74 -0
- data/spec/scenic/adapters/postgres/connection_spec.rb +79 -0
- data/spec/scenic/adapters/postgres/refresh_dependencies_spec.rb +82 -0
- data/spec/scenic/adapters/postgres/views_spec.rb +37 -0
- data/spec/scenic/adapters/postgres_spec.rb +209 -0
- data/spec/scenic/command_recorder/statement_arguments_spec.rb +41 -0
- data/spec/scenic/command_recorder_spec.rb +111 -0
- data/spec/scenic/configuration_spec.rb +27 -0
- data/spec/scenic/definition_spec.rb +62 -0
- data/spec/scenic/schema_dumper_spec.rb +115 -0
- data/spec/scenic/statements_spec.rb +199 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/support/generator_spec_setup.rb +14 -0
- data/spec/support/view_definition_helpers.rb +10 -0
- metadata +307 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
require File.expand_path("../boot", __FILE__)
|
2
|
+
|
3
|
+
# Pick the frameworks you want:
|
4
|
+
require "active_record/railtie"
|
5
|
+
|
6
|
+
Bundler.require(*Rails.groups)
|
7
|
+
require "scenic"
|
8
|
+
|
9
|
+
module Dummy
|
10
|
+
class Application < Rails::Application
|
11
|
+
config.cache_classes = true
|
12
|
+
config.eager_load = false
|
13
|
+
config.active_support.deprecation = :stderr
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
development: &default
|
2
|
+
adapter: postgresql
|
3
|
+
database: dummy_development
|
4
|
+
encoding: unicode
|
5
|
+
host: localhost
|
6
|
+
pool: 5
|
7
|
+
<% if ENV.fetch("GITHUB_ACTIONS", false) %>
|
8
|
+
username: <%= ENV.fetch("POSTGRES_USER") %>
|
9
|
+
password: <%= ENV.fetch("POSTGRES_PASSWORD") %>
|
10
|
+
<% end %>
|
11
|
+
|
12
|
+
test:
|
13
|
+
<<: *default
|
14
|
+
database: dummy_test
|
File without changes
|
File without changes
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "generators/scenic/model/model_generator"
|
3
|
+
|
4
|
+
module Scenic::Generators
|
5
|
+
describe ModelGenerator, :generator do
|
6
|
+
before do
|
7
|
+
allow(ViewGenerator).to receive(:new)
|
8
|
+
.and_return(
|
9
|
+
instance_double("Scenic::Generators::ViewGenerator").as_null_object,
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "invokes the view generator" do
|
14
|
+
run_generator ["current_customer"]
|
15
|
+
|
16
|
+
expect(ViewGenerator).to have_received(:new)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "creates a migration to create the view" do
|
20
|
+
run_generator ["current_customer"]
|
21
|
+
model_definition = file("app/models/current_customer.rb")
|
22
|
+
expect(model_definition).to exist
|
23
|
+
expect(model_definition).to have_correct_syntax
|
24
|
+
expect(model_definition).not_to contain("self.refresh")
|
25
|
+
expect(model_definition).to have_correct_syntax
|
26
|
+
end
|
27
|
+
|
28
|
+
it "adds a refresh method to materialized models" do
|
29
|
+
run_generator ["active_user", "--materialized"]
|
30
|
+
model_definition = file("app/models/active_user.rb")
|
31
|
+
|
32
|
+
expect(model_definition).to contain("self.refresh")
|
33
|
+
expect(model_definition).to have_correct_syntax
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "generators/scenic/view/view_generator"
|
3
|
+
|
4
|
+
describe Scenic::Generators::ViewGenerator, :generator do
|
5
|
+
it "creates view definition and migration files" do
|
6
|
+
migration = file("db/migrate/create_searches.rb")
|
7
|
+
view_definition = file("db/views/searches_v01.sql")
|
8
|
+
|
9
|
+
run_generator ["search"]
|
10
|
+
|
11
|
+
expect(migration).to be_a_migration
|
12
|
+
expect(view_definition).to exist
|
13
|
+
end
|
14
|
+
|
15
|
+
it "updates an existing view" do
|
16
|
+
with_view_definition("searches", 1, "hello") do
|
17
|
+
migration = file("db/migrate/update_searches_to_version_2.rb")
|
18
|
+
view_definition = file("db/views/searches_v02.sql")
|
19
|
+
allow(Dir).to receive(:entries).and_return(["searches_v01.sql"])
|
20
|
+
|
21
|
+
run_generator ["search"]
|
22
|
+
|
23
|
+
expect(migration).to be_a_migration
|
24
|
+
expect(view_definition).to exist
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it "adds 'materialized: true' to the migration if view is materialized" do
|
29
|
+
with_view_definition("aired_episodes", 1, "hello") do
|
30
|
+
allow(Dir).to receive(:entries).and_return(["aired_episodes_v01.sql"])
|
31
|
+
|
32
|
+
run_generator ["aired_episode", "--materialized"]
|
33
|
+
migration = migration_file(
|
34
|
+
"db/migrate/update_aired_episodes_to_version_2.rb",
|
35
|
+
)
|
36
|
+
expect(migration).to contain "materialized: true"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context "for views created in a schema other than 'public'" do
|
41
|
+
it "creates a view definition" do
|
42
|
+
view_definition = file("db/views/non_public_searches_v01.sql")
|
43
|
+
|
44
|
+
run_generator ["non_public.search"]
|
45
|
+
|
46
|
+
expect(view_definition).to exist
|
47
|
+
end
|
48
|
+
|
49
|
+
it "creates a migration file" do
|
50
|
+
run_generator ["non_public.search"]
|
51
|
+
|
52
|
+
migration = migration_file("db/migrate/create_non_public_searches.rb")
|
53
|
+
expect(migration).to contain(/class CreateNonPublicSearches/)
|
54
|
+
expect(migration).to contain(/create_view "non_public.searches"/)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe "Reverting scenic schema statements", :db do
|
4
|
+
around do |example|
|
5
|
+
with_view_definition :greetings, 1, "SELECT text 'hola' AS greeting" do
|
6
|
+
example.run
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
it "reverts dropped view to specified version" do
|
11
|
+
run_migration(migration_for_create, :up)
|
12
|
+
run_migration(migration_for_drop, :up)
|
13
|
+
run_migration(migration_for_drop, :down)
|
14
|
+
|
15
|
+
expect { execute("SELECT * from greetings") }
|
16
|
+
.not_to raise_error
|
17
|
+
end
|
18
|
+
|
19
|
+
it "reverts updated view to specified version" do
|
20
|
+
with_view_definition :greetings, 2, "SELECT text 'good day' AS greeting" do
|
21
|
+
run_migration(migration_for_create, :up)
|
22
|
+
run_migration(migration_for_update, :up)
|
23
|
+
run_migration(migration_for_update, :down)
|
24
|
+
|
25
|
+
greeting = execute("SELECT * from greetings")[0]["greeting"]
|
26
|
+
|
27
|
+
expect(greeting).to eq "hola"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def migration_for_create
|
32
|
+
Class.new(migration_class) do
|
33
|
+
def change
|
34
|
+
create_view :greetings
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def migration_for_drop
|
40
|
+
Class.new(migration_class) do
|
41
|
+
def change
|
42
|
+
drop_view :greetings, revert_to_version: 1
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def migration_for_update
|
48
|
+
Class.new(migration_class) do
|
49
|
+
def change
|
50
|
+
update_view :greetings, version: 2, revert_to_version: 1
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def migration_class
|
56
|
+
if Rails::VERSION::MAJOR >= 5
|
57
|
+
::ActiveRecord::Migration[5.0]
|
58
|
+
else
|
59
|
+
::ActiveRecord::Migration
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def run_migration(migration, directions)
|
64
|
+
silence_stream(STDOUT) do
|
65
|
+
Array.wrap(directions).each do |direction|
|
66
|
+
migration.migrate(direction)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def execute(sql)
|
72
|
+
ActiveRecord::Base.connection.execute(sql)
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Scenic
|
4
|
+
module Adapters
|
5
|
+
describe Postgres::Connection do
|
6
|
+
describe "supports_materialized_views?" do
|
7
|
+
context "supports_materialized_views? was defined on connection" do
|
8
|
+
it "uses the previously defined version" do
|
9
|
+
base_response = double("response from base connection")
|
10
|
+
base_connection = double(
|
11
|
+
"Connection",
|
12
|
+
supports_materialized_views?: base_response,
|
13
|
+
)
|
14
|
+
|
15
|
+
connection = Postgres::Connection.new(base_connection)
|
16
|
+
|
17
|
+
expect(connection.supports_materialized_views?).to be base_response
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "supports_materialized_views? is not already defined" do
|
22
|
+
it "is true if postgres version is at least than 9.3.0" do
|
23
|
+
base_connection = double("Connection", postgresql_version: 90300)
|
24
|
+
|
25
|
+
connection = Postgres::Connection.new(base_connection)
|
26
|
+
|
27
|
+
expect(connection.supports_materialized_views?).to be true
|
28
|
+
end
|
29
|
+
|
30
|
+
it "is false if postgres version is less than 9.3.0" do
|
31
|
+
base_connection = double("Connection", postgresql_version: 90299)
|
32
|
+
|
33
|
+
connection = Postgres::Connection.new(base_connection)
|
34
|
+
|
35
|
+
expect(connection.supports_materialized_views?).to be false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "#postgresql_version" do
|
41
|
+
it "uses the public method on the provided connection if defined" do
|
42
|
+
base_connection = Class.new do
|
43
|
+
def postgresql_version
|
44
|
+
123
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
connection = Postgres::Connection.new(base_connection.new)
|
49
|
+
|
50
|
+
expect(connection.postgresql_version).to eq 123
|
51
|
+
end
|
52
|
+
|
53
|
+
it "uses the protected method if the underlying method is not public" do
|
54
|
+
base_connection = Class.new do
|
55
|
+
protected
|
56
|
+
|
57
|
+
def postgresql_version
|
58
|
+
123
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
connection = Postgres::Connection.new(base_connection.new)
|
63
|
+
|
64
|
+
expect(connection.postgresql_version).to eq 123
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "#supports_concurrent_refresh" do
|
69
|
+
it "is true if postgres version is at least 9.4.0" do
|
70
|
+
base_connection = double("Connection", postgresql_version: 90400)
|
71
|
+
|
72
|
+
connection = Postgres::Connection.new(base_connection)
|
73
|
+
|
74
|
+
expect(connection.supports_concurrent_refreshes?).to be true
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Scenic
|
4
|
+
module Adapters
|
5
|
+
describe Postgres::RefreshDependencies, :db do
|
6
|
+
context "view has dependencies" do
|
7
|
+
let(:adapter) { Postgres.new }
|
8
|
+
|
9
|
+
before do
|
10
|
+
adapter.create_materialized_view(
|
11
|
+
"first",
|
12
|
+
"SELECT text 'hi' AS greeting",
|
13
|
+
)
|
14
|
+
adapter.create_materialized_view(
|
15
|
+
"second",
|
16
|
+
"SELECT * FROM first",
|
17
|
+
)
|
18
|
+
adapter.create_materialized_view(
|
19
|
+
"third",
|
20
|
+
"SELECT * FROM first UNION SELECT * FROM second",
|
21
|
+
)
|
22
|
+
adapter.create_materialized_view(
|
23
|
+
"fourth_1",
|
24
|
+
"SELECT * FROM third",
|
25
|
+
)
|
26
|
+
adapter.create_materialized_view(
|
27
|
+
"x_fourth",
|
28
|
+
"SELECT * FROM fourth_1",
|
29
|
+
)
|
30
|
+
adapter.create_materialized_view(
|
31
|
+
"fourth",
|
32
|
+
"SELECT * FROM fourth_1 UNION SELECT * FROM x_fourth",
|
33
|
+
)
|
34
|
+
|
35
|
+
expect(adapter).to receive(:refresh_materialized_view)
|
36
|
+
.with("public.first", concurrently: true).ordered
|
37
|
+
expect(adapter).to receive(:refresh_materialized_view)
|
38
|
+
.with("public.second", concurrently: true).ordered
|
39
|
+
expect(adapter).to receive(:refresh_materialized_view)
|
40
|
+
.with("public.third", concurrently: true).ordered
|
41
|
+
expect(adapter).to receive(:refresh_materialized_view)
|
42
|
+
.with("public.fourth_1", concurrently: true).ordered
|
43
|
+
expect(adapter).to receive(:refresh_materialized_view)
|
44
|
+
.with("public.x_fourth", concurrently: true).ordered
|
45
|
+
end
|
46
|
+
|
47
|
+
it "refreshes in the right order when called without namespace" do
|
48
|
+
described_class.call(
|
49
|
+
:fourth,
|
50
|
+
adapter,
|
51
|
+
ActiveRecord::Base.connection,
|
52
|
+
concurrently: true,
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "refreshes in the right order when called with namespace" do
|
57
|
+
described_class.call(
|
58
|
+
"public.fourth",
|
59
|
+
adapter,
|
60
|
+
ActiveRecord::Base.connection,
|
61
|
+
concurrently: true,
|
62
|
+
)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context "view has no dependencies" do
|
67
|
+
it "does not raise an error" do
|
68
|
+
adapter = Postgres.new
|
69
|
+
|
70
|
+
adapter.create_materialized_view(
|
71
|
+
"first",
|
72
|
+
"SELECT text 'hi' AS greeting",
|
73
|
+
)
|
74
|
+
|
75
|
+
expect {
|
76
|
+
described_class.call(:first, adapter, ActiveRecord::Base.connection)
|
77
|
+
}.not_to raise_error
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Scenic
|
4
|
+
module Adapters
|
5
|
+
describe Postgres::Views, :db do
|
6
|
+
it "returns scenic view objects for plain old views" do
|
7
|
+
connection = ActiveRecord::Base.connection
|
8
|
+
connection.execute <<-SQL
|
9
|
+
CREATE VIEW children AS SELECT text 'Elliot' AS name
|
10
|
+
SQL
|
11
|
+
|
12
|
+
views = Postgres::Views.new(connection).all
|
13
|
+
first = views.first
|
14
|
+
|
15
|
+
expect(views.size).to eq 1
|
16
|
+
expect(first.name).to eq "children"
|
17
|
+
expect(first.materialized).to be false
|
18
|
+
expect(first.definition).to eq "SELECT 'Elliot'::text AS name;"
|
19
|
+
end
|
20
|
+
|
21
|
+
it "returns scenic view objects for materialized views" do
|
22
|
+
connection = ActiveRecord::Base.connection
|
23
|
+
connection.execute <<-SQL
|
24
|
+
CREATE MATERIALIZED VIEW children AS SELECT text 'Owen' AS name
|
25
|
+
SQL
|
26
|
+
|
27
|
+
views = Postgres::Views.new(connection).all
|
28
|
+
first = views.first
|
29
|
+
|
30
|
+
expect(views.size).to eq 1
|
31
|
+
expect(first.name).to eq "children"
|
32
|
+
expect(first.materialized).to be true
|
33
|
+
expect(first.definition).to eq "SELECT 'Owen'::text AS name;"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,209 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Scenic
|
4
|
+
module Adapters
|
5
|
+
describe Postgres, :db do
|
6
|
+
describe "#create_view" do
|
7
|
+
it "successfully creates a view" do
|
8
|
+
adapter = Postgres.new
|
9
|
+
|
10
|
+
adapter.create_view("greetings", "SELECT text 'hi' AS greeting")
|
11
|
+
|
12
|
+
expect(adapter.views.map(&:name)).to include("greetings")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#create_materialized_view" do
|
17
|
+
it "successfully creates a materialized view" do
|
18
|
+
adapter = Postgres.new
|
19
|
+
|
20
|
+
adapter.create_materialized_view(
|
21
|
+
"greetings",
|
22
|
+
"SELECT text 'hi' AS greeting",
|
23
|
+
)
|
24
|
+
|
25
|
+
view = adapter.views.first
|
26
|
+
expect(view.name).to eq("greetings")
|
27
|
+
expect(view.materialized).to eq true
|
28
|
+
end
|
29
|
+
|
30
|
+
it "handles semicolon in definition when using `with no data`" do
|
31
|
+
adapter = Postgres.new
|
32
|
+
|
33
|
+
adapter.create_materialized_view(
|
34
|
+
"greetings",
|
35
|
+
"SELECT text 'hi' AS greeting; \n",
|
36
|
+
no_data: true,
|
37
|
+
)
|
38
|
+
|
39
|
+
view = adapter.views.first
|
40
|
+
expect(view.name).to eq("greetings")
|
41
|
+
expect(view.materialized).to eq true
|
42
|
+
end
|
43
|
+
|
44
|
+
it "raises an exception if the version of PostgreSQL is too old" do
|
45
|
+
connection = double("Connection", supports_materialized_views?: false)
|
46
|
+
connectable = double("Connectable", connection: connection)
|
47
|
+
adapter = Postgres.new(connectable)
|
48
|
+
err = Scenic::Adapters::Postgres::MaterializedViewsNotSupportedError
|
49
|
+
|
50
|
+
expect { adapter.create_materialized_view("greetings", "select 1") }
|
51
|
+
.to raise_error err
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "#replace_view" do
|
56
|
+
it "successfully replaces a view" do
|
57
|
+
adapter = Postgres.new
|
58
|
+
|
59
|
+
adapter.create_view("greetings", "SELECT text 'hi' AS greeting")
|
60
|
+
|
61
|
+
view = adapter.views.first.definition
|
62
|
+
expect(view).to eql "SELECT 'hi'::text AS greeting;"
|
63
|
+
|
64
|
+
adapter.replace_view("greetings", "SELECT text 'hello' AS greeting")
|
65
|
+
|
66
|
+
view = adapter.views.first.definition
|
67
|
+
expect(view).to eql "SELECT 'hello'::text AS greeting;"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "#drop_view" do
|
72
|
+
it "successfully drops a view" do
|
73
|
+
adapter = Postgres.new
|
74
|
+
|
75
|
+
adapter.create_view("greetings", "SELECT text 'hi' AS greeting")
|
76
|
+
adapter.drop_view("greetings")
|
77
|
+
|
78
|
+
expect(adapter.views.map(&:name)).not_to include("greetings")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "#drop_materialized_view" do
|
83
|
+
it "successfully drops a materialized view" do
|
84
|
+
adapter = Postgres.new
|
85
|
+
|
86
|
+
adapter.create_materialized_view(
|
87
|
+
"greetings",
|
88
|
+
"SELECT text 'hi' AS greeting",
|
89
|
+
)
|
90
|
+
adapter.drop_materialized_view("greetings")
|
91
|
+
|
92
|
+
expect(adapter.views.map(&:name)).not_to include("greetings")
|
93
|
+
end
|
94
|
+
|
95
|
+
it "raises an exception if the version of PostgreSQL is too old" do
|
96
|
+
connection = double("Connection", supports_materialized_views?: false)
|
97
|
+
connectable = double("Connectable", connection: connection)
|
98
|
+
adapter = Postgres.new(connectable)
|
99
|
+
err = Scenic::Adapters::Postgres::MaterializedViewsNotSupportedError
|
100
|
+
|
101
|
+
expect { adapter.drop_materialized_view("greetings") }
|
102
|
+
.to raise_error err
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe "#refresh_materialized_view" do
|
107
|
+
it "raises an exception if the version of PostgreSQL is too old" do
|
108
|
+
connection = double("Connection", supports_materialized_views?: false)
|
109
|
+
connectable = double("Connectable", connection: connection)
|
110
|
+
adapter = Postgres.new(connectable)
|
111
|
+
err = Scenic::Adapters::Postgres::MaterializedViewsNotSupportedError
|
112
|
+
|
113
|
+
expect { adapter.refresh_materialized_view(:tests) }
|
114
|
+
.to raise_error err
|
115
|
+
end
|
116
|
+
|
117
|
+
it "can refresh the views dependencies first" do
|
118
|
+
connection = double("Connection").as_null_object
|
119
|
+
connectable = double("Connectable", connection: connection)
|
120
|
+
adapter = Postgres.new(connectable)
|
121
|
+
expect(Scenic::Adapters::Postgres::RefreshDependencies)
|
122
|
+
.to receive(:call)
|
123
|
+
.with(:tests, adapter, connection, concurrently: true)
|
124
|
+
|
125
|
+
adapter.refresh_materialized_view(
|
126
|
+
:tests,
|
127
|
+
cascade: true,
|
128
|
+
concurrently: true,
|
129
|
+
)
|
130
|
+
end
|
131
|
+
|
132
|
+
context "refreshing concurrently" do
|
133
|
+
it "raises descriptive error if concurrent refresh is not possible" do
|
134
|
+
adapter = Postgres.new
|
135
|
+
adapter.create_materialized_view(:tests, "SELECT text 'hi' as text")
|
136
|
+
|
137
|
+
expect {
|
138
|
+
adapter.refresh_materialized_view(:tests, concurrently: true)
|
139
|
+
}.to raise_error(/Create a unique index with no WHERE clause/)
|
140
|
+
end
|
141
|
+
|
142
|
+
it "raises an exception if the version of PostgreSQL is too old" do
|
143
|
+
connection = double("Connection", postgresql_version: 90300)
|
144
|
+
connectable = double("Connectable", connection: connection)
|
145
|
+
adapter = Postgres.new(connectable)
|
146
|
+
e = Scenic::Adapters::Postgres::ConcurrentRefreshesNotSupportedError
|
147
|
+
|
148
|
+
expect {
|
149
|
+
adapter.refresh_materialized_view(:tests, concurrently: true)
|
150
|
+
}.to raise_error e
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
describe "#views" do
|
156
|
+
it "returns the views defined on this connection" do
|
157
|
+
adapter = Postgres.new
|
158
|
+
|
159
|
+
ActiveRecord::Base.connection.execute <<-SQL
|
160
|
+
CREATE VIEW parents AS SELECT text 'Joe' AS name
|
161
|
+
SQL
|
162
|
+
|
163
|
+
ActiveRecord::Base.connection.execute <<-SQL
|
164
|
+
CREATE VIEW children AS SELECT text 'Owen' AS name
|
165
|
+
SQL
|
166
|
+
|
167
|
+
ActiveRecord::Base.connection.execute <<-SQL
|
168
|
+
CREATE MATERIALIZED VIEW people AS
|
169
|
+
SELECT name FROM parents UNION SELECT name FROM children
|
170
|
+
SQL
|
171
|
+
|
172
|
+
ActiveRecord::Base.connection.execute <<-SQL
|
173
|
+
CREATE VIEW people_with_names AS
|
174
|
+
SELECT name FROM people
|
175
|
+
WHERE name IS NOT NULL
|
176
|
+
SQL
|
177
|
+
|
178
|
+
expect(adapter.views.map(&:name)).to eq [
|
179
|
+
"parents",
|
180
|
+
"children",
|
181
|
+
"people",
|
182
|
+
"people_with_names",
|
183
|
+
]
|
184
|
+
end
|
185
|
+
|
186
|
+
context "with views in non public schemas" do
|
187
|
+
it "returns also the non public views" do
|
188
|
+
adapter = Postgres.new
|
189
|
+
|
190
|
+
ActiveRecord::Base.connection.execute <<-SQL
|
191
|
+
CREATE VIEW parents AS SELECT text 'Joe' AS name
|
192
|
+
SQL
|
193
|
+
|
194
|
+
ActiveRecord::Base.connection.execute <<-SQL
|
195
|
+
CREATE SCHEMA scenic;
|
196
|
+
CREATE VIEW scenic.parents AS SELECT text 'Maarten' AS name;
|
197
|
+
SET search_path TO scenic, public;
|
198
|
+
SQL
|
199
|
+
|
200
|
+
expect(adapter.views.map(&:name)).to eq [
|
201
|
+
"parents",
|
202
|
+
"scenic.parents",
|
203
|
+
]
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|