scenic 1.7.0 → 1.9.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/.github/workflows/ci.yml +29 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +55 -17
- data/CONTRIBUTING.md +1 -0
- data/FUNDING.yml +1 -0
- data/Gemfile +4 -4
- data/README.md +84 -25
- data/Rakefile +1 -1
- data/bin/standardrb +27 -0
- data/lib/generators/scenic/materializable.rb +27 -1
- data/lib/generators/scenic/model/model_generator.rb +7 -16
- data/lib/generators/scenic/model/templates/model.erb +4 -0
- data/lib/generators/scenic/view/templates/db/migrate/update_view.erb +2 -2
- data/lib/generators/scenic/view/view_generator.rb +5 -5
- data/lib/scenic/adapters/postgres/index_creation.rb +68 -0
- data/lib/scenic/adapters/postgres/index_migration.rb +70 -0
- data/lib/scenic/adapters/postgres/index_reapplication.rb +3 -28
- data/lib/scenic/adapters/postgres/indexes.rb +1 -1
- data/lib/scenic/adapters/postgres/refresh_dependencies.rb +3 -3
- data/lib/scenic/adapters/postgres/side_by_side.rb +50 -0
- data/lib/scenic/adapters/postgres/temporary_name.rb +34 -0
- data/lib/scenic/adapters/postgres/views.rb +85 -12
- data/lib/scenic/adapters/postgres.rb +64 -16
- data/lib/scenic/definition.rb +1 -1
- data/lib/scenic/schema_dumper.rb +0 -14
- data/lib/scenic/statements.rb +49 -16
- data/lib/scenic/version.rb +1 -1
- data/scenic.gemspec +15 -11
- data/spec/acceptance/user_manages_views_spec.rb +11 -0
- data/spec/dummy/Rakefile +5 -5
- data/spec/dummy/bin/bundle +2 -2
- data/spec/dummy/bin/rails +3 -3
- data/spec/dummy/bin/rake +2 -2
- data/spec/dummy/config/application.rb +4 -0
- data/spec/dummy/config.ru +1 -1
- data/spec/dummy/db/migrate/20220112154220_add_pg_stat_statements_extension.rb +1 -1
- data/spec/dummy/db/schema.rb +0 -2
- data/spec/generators/scenic/model/model_generator_spec.rb +9 -1
- data/spec/generators/scenic/view/view_generator_spec.rb +28 -2
- data/spec/integration/revert_spec.rb +1 -1
- data/spec/scenic/adapters/postgres/connection_spec.rb +1 -1
- data/spec/scenic/adapters/postgres/index_creation_spec.rb +54 -0
- data/spec/scenic/adapters/postgres/index_migration_spec.rb +24 -0
- data/spec/scenic/adapters/postgres/refresh_dependencies_spec.rb +9 -9
- data/spec/scenic/adapters/postgres/side_by_side_spec.rb +24 -0
- data/spec/scenic/adapters/postgres/temporary_name_spec.rb +23 -0
- data/spec/scenic/adapters/postgres_spec.rb +95 -8
- data/spec/scenic/command_recorder/statement_arguments_spec.rb +4 -4
- data/spec/scenic/command_recorder_spec.rb +30 -12
- data/spec/scenic/schema_dumper_spec.rb +35 -14
- data/spec/scenic/statements_spec.rb +66 -8
- data/spec/spec_helper.rb +19 -4
- data/spec/support/database_schema_helpers.rb +28 -0
- data/spec/support/generator_spec_setup.rb +2 -2
- data/spec/support/view_definition_helpers.rb +1 -1
- metadata +35 -48
- data/.hound.yml +0 -2
- data/.rubocop.yml +0 -129
data/lib/scenic/statements.rb
CHANGED
@@ -9,9 +9,11 @@ module Scenic
|
|
9
9
|
# @param sql_definition [String] The SQL query for the view schema. An error
|
10
10
|
# will be raised if `sql_definition` and `version` are both set,
|
11
11
|
# as they are mutually exclusive.
|
12
|
-
# @param materialized [Boolean, Hash] Set to
|
13
|
-
#
|
14
|
-
#
|
12
|
+
# @param materialized [Boolean, Hash] Set to a truthy value to create a
|
13
|
+
# materialized view. Hash
|
14
|
+
# @option materialized [Boolean] :no_data (false) Set to true to create
|
15
|
+
# materialized view without running the associated query. You will need
|
16
|
+
# to perform a non-concurrent refresh to populate with data.
|
15
17
|
# @return The database response from executing the create statement.
|
16
18
|
#
|
17
19
|
# @example Create from `db/views/searches_v02.sql`
|
@@ -26,7 +28,7 @@ module Scenic
|
|
26
28
|
if version.present? && sql_definition.present?
|
27
29
|
raise(
|
28
30
|
ArgumentError,
|
29
|
-
"sql_definition and version cannot both be set"
|
31
|
+
"sql_definition and version cannot both be set"
|
30
32
|
)
|
31
33
|
end
|
32
34
|
|
@@ -37,10 +39,12 @@ module Scenic
|
|
37
39
|
sql_definition ||= definition(name, version)
|
38
40
|
|
39
41
|
if materialized
|
42
|
+
options = materialized_options(materialized)
|
43
|
+
|
40
44
|
Scenic.database.create_materialized_view(
|
41
45
|
name,
|
42
46
|
sql_definition,
|
43
|
-
no_data: no_data
|
47
|
+
no_data: options[:no_data]
|
44
48
|
)
|
45
49
|
else
|
46
50
|
Scenic.database.create_view(name, sql_definition)
|
@@ -80,36 +84,59 @@ module Scenic
|
|
80
84
|
# as they are mutually exclusive.
|
81
85
|
# @param revert_to_version [Fixnum] The version number to rollback to on
|
82
86
|
# `rake db rollback`
|
83
|
-
# @param materialized [Boolean, Hash] True if updating a
|
84
|
-
#
|
85
|
-
#
|
87
|
+
# @param materialized [Boolean, Hash] True or a Hash if updating a
|
88
|
+
# materialized view.
|
89
|
+
# @option materialized [Boolean] :no_data (false) Set to true to update
|
90
|
+
# a materialized view without loading data. You will need to perform a
|
91
|
+
# refresh to populate with data. Cannot be combined with the :side_by_side
|
92
|
+
# option.
|
93
|
+
# @option materialized [Boolean] :side_by_side (false) Set to true to update
|
94
|
+
# update a materialized view using our side-by-side strategy, which will
|
95
|
+
# limit the time the view is locked at the cost of increasing disk usage.
|
96
|
+
# The view is initially updated with a temporary name and atomically
|
97
|
+
# swapped once it is successfully created with data. Cannot be combined
|
98
|
+
# with the :no_data option.
|
86
99
|
# @return The database response from executing the create statement.
|
87
100
|
#
|
88
101
|
# @example
|
89
102
|
# update_view :engagement_reports, version: 3, revert_to_version: 2
|
90
|
-
#
|
103
|
+
# update_view :comments, version: 2, revert_to_version: 1, materialized: { side_by_side: true }
|
91
104
|
def update_view(name, version: nil, sql_definition: nil, revert_to_version: nil, materialized: false)
|
92
105
|
if version.blank? && sql_definition.blank?
|
93
106
|
raise(
|
94
107
|
ArgumentError,
|
95
|
-
"sql_definition or version must be specified"
|
108
|
+
"sql_definition or version must be specified"
|
96
109
|
)
|
97
110
|
end
|
98
111
|
|
99
112
|
if version.present? && sql_definition.present?
|
100
113
|
raise(
|
101
114
|
ArgumentError,
|
102
|
-
"sql_definition and version cannot both be set"
|
115
|
+
"sql_definition and version cannot both be set"
|
103
116
|
)
|
104
117
|
end
|
105
118
|
|
106
119
|
sql_definition ||= definition(name, version)
|
107
120
|
|
108
121
|
if materialized
|
122
|
+
options = materialized_options(materialized)
|
123
|
+
|
124
|
+
if options[:no_data] && options[:side_by_side]
|
125
|
+
raise(
|
126
|
+
ArgumentError,
|
127
|
+
"no_data and side_by_side options cannot be combined"
|
128
|
+
)
|
129
|
+
end
|
130
|
+
|
131
|
+
if options[:side_by_side] && !transaction_open?
|
132
|
+
raise "a transaction is required to perform a side-by-side update"
|
133
|
+
end
|
134
|
+
|
109
135
|
Scenic.database.update_materialized_view(
|
110
136
|
name,
|
111
137
|
sql_definition,
|
112
|
-
no_data: no_data
|
138
|
+
no_data: options[:no_data],
|
139
|
+
side_by_side: options[:side_by_side]
|
113
140
|
)
|
114
141
|
else
|
115
142
|
Scenic.database.update_view(name, sql_definition)
|
@@ -152,11 +179,17 @@ module Scenic
|
|
152
179
|
Scenic::Definition.new(name, version).to_sql
|
153
180
|
end
|
154
181
|
|
155
|
-
def
|
156
|
-
if materialized.is_a?
|
157
|
-
|
182
|
+
def materialized_options(materialized)
|
183
|
+
if materialized.is_a? Hash
|
184
|
+
{
|
185
|
+
no_data: materialized.fetch(:no_data, false),
|
186
|
+
side_by_side: materialized.fetch(:side_by_side, false)
|
187
|
+
}
|
158
188
|
else
|
159
|
-
|
189
|
+
{
|
190
|
+
no_data: false,
|
191
|
+
side_by_side: false
|
192
|
+
}
|
160
193
|
end
|
161
194
|
end
|
162
195
|
end
|
data/lib/scenic/version.rb
CHANGED
data/scenic.gemspec
CHANGED
@@ -3,31 +3,35 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
3
3
|
require "scenic/version"
|
4
4
|
|
5
5
|
Gem::Specification.new do |spec|
|
6
|
-
spec.name
|
7
|
-
spec.version
|
8
|
-
spec.authors
|
9
|
-
spec.email
|
10
|
-
spec.summary
|
11
|
-
spec.description
|
6
|
+
spec.name = "scenic"
|
7
|
+
spec.version = Scenic::VERSION
|
8
|
+
spec.authors = ["Derek Prior", "Caleb Hearth"]
|
9
|
+
spec.email = ["derekprior@gmail.com", "caleb@calebhearth.com"]
|
10
|
+
spec.summary = "Support for database views in Rails migrations"
|
11
|
+
spec.description = <<-DESCRIPTION
|
12
12
|
Adds methods to ActiveRecord::Migration to create and manage database views
|
13
13
|
in Rails
|
14
14
|
DESCRIPTION
|
15
|
-
spec.homepage
|
16
|
-
spec.license
|
15
|
+
spec.homepage = "https://github.com/scenic-views/scenic"
|
16
|
+
spec.license = "MIT"
|
17
17
|
|
18
|
-
spec.files
|
19
|
-
spec.test_files = spec.files.grep(%r{^spec/})
|
18
|
+
spec.files = `git ls-files -z`.split("\x0")
|
20
19
|
spec.require_paths = ["lib"]
|
21
20
|
|
21
|
+
spec.metadata = {
|
22
|
+
"funding-uri" => "https://github.com/scenic-views/scenic"
|
23
|
+
}
|
24
|
+
|
22
25
|
spec.add_development_dependency "bundler", ">= 1.5"
|
23
26
|
spec.add_development_dependency "database_cleaner"
|
24
27
|
spec.add_development_dependency "rake"
|
25
28
|
spec.add_development_dependency "rspec", ">= 3.3"
|
26
|
-
spec.add_development_dependency "pg"
|
29
|
+
spec.add_development_dependency "pg"
|
27
30
|
spec.add_development_dependency "pry"
|
28
31
|
spec.add_development_dependency "ammeter", ">= 1.1.3"
|
29
32
|
spec.add_development_dependency "yard"
|
30
33
|
spec.add_development_dependency "redcarpet"
|
34
|
+
spec.add_development_dependency "standard"
|
31
35
|
|
32
36
|
spec.add_dependency "activerecord", ">= 4.0.0"
|
33
37
|
spec.add_dependency "railties", ">= 4.0.0"
|
@@ -45,6 +45,17 @@ describe "User manages views" do
|
|
45
45
|
verify_result "Child.take.name", "Elliot"
|
46
46
|
verify_schema_contains 'add_index "children"'
|
47
47
|
|
48
|
+
successfully "rails generate scenic:view child --materialized --side-by-side"
|
49
|
+
verify_identical_view_definitions "children_v02", "children_v03"
|
50
|
+
|
51
|
+
write_definition "children_v03", "SELECT 'Juniper'::text AS name"
|
52
|
+
successfully "rake db:migrate"
|
53
|
+
|
54
|
+
successfully "rake db:reset"
|
55
|
+
verify_result "Child.take.name", "Juniper"
|
56
|
+
verify_schema_contains 'add_index "children"'
|
57
|
+
|
58
|
+
successfully "rake db:rollback"
|
48
59
|
successfully "rake db:rollback"
|
49
60
|
successfully "rake db:rollback"
|
50
61
|
successfully "rails destroy scenic:model child"
|
data/spec/dummy/Rakefile
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
2
2
|
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
3
3
|
|
4
|
-
require File.expand_path(
|
4
|
+
require File.expand_path("../config/application", __FILE__)
|
5
5
|
|
6
6
|
Rails.application.load_tasks
|
7
7
|
|
8
|
-
unless Rake::Task.task_defined?(
|
9
|
-
desc
|
10
|
-
task
|
11
|
-
#no op
|
8
|
+
unless Rake::Task.task_defined?("db:environment:set")
|
9
|
+
desc "dummy task for rails versions where this task does not exist"
|
10
|
+
task "db:environment:set" do
|
11
|
+
# no op
|
12
12
|
end
|
13
13
|
end
|
data/spec/dummy/bin/bundle
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
ENV[
|
3
|
-
load Gem.bin_path(
|
2
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile", __FILE__)
|
3
|
+
load Gem.bin_path("bundler", "bundle")
|
data/spec/dummy/bin/rails
CHANGED
@@ -1,4 +1,4 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
APP_PATH = File.expand_path(
|
3
|
-
require_relative
|
4
|
-
require
|
2
|
+
APP_PATH = File.expand_path("../../config/application", __FILE__)
|
3
|
+
require_relative "../config/boot"
|
4
|
+
require "rails/commands"
|
data/spec/dummy/bin/rake
CHANGED
@@ -11,5 +11,9 @@ module Dummy
|
|
11
11
|
config.cache_classes = true
|
12
12
|
config.eager_load = false
|
13
13
|
config.active_support.deprecation = :stderr
|
14
|
+
|
15
|
+
if config.active_support.respond_to?(:to_time_preserves_timezone)
|
16
|
+
config.active_support.to_time_preserves_timezone = :zone
|
17
|
+
end
|
14
18
|
end
|
15
19
|
end
|
data/spec/dummy/config.ru
CHANGED
data/spec/dummy/db/schema.rb
CHANGED
@@ -11,9 +11,7 @@
|
|
11
11
|
# It's strongly recommended that you check this file into your version control system.
|
12
12
|
|
13
13
|
ActiveRecord::Schema.define(version: 2022_01_12_154220) do
|
14
|
-
|
15
14
|
# These are extensions that must be enabled in order to support this database
|
16
15
|
enable_extension "pg_stat_statements"
|
17
16
|
enable_extension "plpgsql"
|
18
|
-
|
19
17
|
end
|
@@ -6,7 +6,7 @@ module Scenic::Generators
|
|
6
6
|
before do
|
7
7
|
allow(ViewGenerator).to receive(:new)
|
8
8
|
.and_return(
|
9
|
-
instance_double("Scenic::Generators::ViewGenerator").as_null_object
|
9
|
+
instance_double("Scenic::Generators::ViewGenerator").as_null_object
|
10
10
|
)
|
11
11
|
end
|
12
12
|
|
@@ -32,5 +32,13 @@ module Scenic::Generators
|
|
32
32
|
expect(model_definition).to contain("self.refresh")
|
33
33
|
expect(model_definition).to have_correct_syntax
|
34
34
|
end
|
35
|
+
|
36
|
+
it "adds a populated? method to materialized models" do
|
37
|
+
run_generator ["active_user", "--materialized"]
|
38
|
+
model_definition = file("app/models/active_user.rb")
|
39
|
+
|
40
|
+
expect(model_definition).to contain("self.populated?")
|
41
|
+
expect(model_definition).to have_correct_syntax
|
42
|
+
end
|
35
43
|
end
|
36
44
|
end
|
@@ -31,19 +31,45 @@ describe Scenic::Generators::ViewGenerator, :generator do
|
|
31
31
|
|
32
32
|
run_generator ["aired_episode", "--materialized"]
|
33
33
|
migration = migration_file(
|
34
|
-
"db/migrate/update_aired_episodes_to_version_2.rb"
|
34
|
+
"db/migrate/update_aired_episodes_to_version_2.rb"
|
35
35
|
)
|
36
36
|
expect(migration).to contain "materialized: true"
|
37
37
|
end
|
38
38
|
end
|
39
39
|
|
40
|
+
it "sets the no_data option when updating a materialized view" do
|
41
|
+
with_view_definition("aired_episodes", 1, "hello") do
|
42
|
+
allow(Dir).to receive(:entries).and_return(["aired_episodes_v01.sql"])
|
43
|
+
|
44
|
+
run_generator ["aired_episode", "--materialized", "--no-data"]
|
45
|
+
migration = migration_file(
|
46
|
+
"db/migrate/update_aired_episodes_to_version_2.rb"
|
47
|
+
)
|
48
|
+
expect(migration).to contain "materialized: { no_data: true }"
|
49
|
+
expect(migration).not_to contain "side_by_side"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it "sets the side-by-side option when updating a materialized view" do
|
54
|
+
with_view_definition("aired_episodes", 1, "hello") do
|
55
|
+
allow(Dir).to receive(:entries).and_return(["aired_episodes_v01.sql"])
|
56
|
+
|
57
|
+
run_generator ["aired_episode", "--materialized", "--side-by-side"]
|
58
|
+
migration = migration_file(
|
59
|
+
"db/migrate/update_aired_episodes_to_version_2.rb"
|
60
|
+
)
|
61
|
+
expect(migration).to contain "materialized: { side_by_side: true }"
|
62
|
+
expect(migration).not_to contain "no_data"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
40
66
|
it "uses 'replace_view' instead of 'update_view' if replace flag is set" do
|
41
67
|
with_view_definition("aired_episodes", 1, "hello") do
|
42
68
|
allow(Dir).to receive(:entries).and_return(["aired_episodes_v01.sql"])
|
43
69
|
|
44
70
|
run_generator ["aired_episode", "--replace"]
|
45
71
|
migration = migration_file(
|
46
|
-
"db/migrate/update_aired_episodes_to_version_2.rb"
|
72
|
+
"db/migrate/update_aired_episodes_to_version_2.rb"
|
47
73
|
)
|
48
74
|
expect(migration).to contain "replace_view"
|
49
75
|
end
|
@@ -9,7 +9,7 @@ module Scenic
|
|
9
9
|
base_response = double("response from base connection")
|
10
10
|
base_connection = double(
|
11
11
|
"Connection",
|
12
|
-
supports_materialized_views?: base_response
|
12
|
+
supports_materialized_views?: base_response
|
13
13
|
)
|
14
14
|
|
15
15
|
connection = Postgres::Connection.new(base_connection)
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Scenic
|
4
|
+
module Adapters
|
5
|
+
describe Postgres::IndexCreation, :db do
|
6
|
+
it "successfully recreates applicable indexes" do
|
7
|
+
create_materialized_view("hi", "SELECT 'hi' AS greeting")
|
8
|
+
speaker = DummySpeaker.new
|
9
|
+
|
10
|
+
index = Scenic::Index.new(
|
11
|
+
object_name: "hi",
|
12
|
+
index_name: "hi_greeting_idx",
|
13
|
+
definition: "CREATE INDEX hi_greeting_idx ON hi (greeting)"
|
14
|
+
)
|
15
|
+
|
16
|
+
Postgres::IndexCreation
|
17
|
+
.new(connection: ActiveRecord::Base.connection, speaker: speaker)
|
18
|
+
.try_create([index])
|
19
|
+
|
20
|
+
expect(indexes_for("hi")).not_to be_empty
|
21
|
+
expect(speaker.messages).to include(/index 'hi_greeting_idx' .* has been created/)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "skips indexes that are not applicable" do
|
25
|
+
create_materialized_view("hi", "SELECT 'hi' AS greeting")
|
26
|
+
speaker = DummySpeaker.new
|
27
|
+
index = Scenic::Index.new(
|
28
|
+
object_name: "hi",
|
29
|
+
index_name: "hi_person_idx",
|
30
|
+
definition: "CREATE INDEX hi_person_idx ON hi (person)"
|
31
|
+
)
|
32
|
+
|
33
|
+
Postgres::IndexCreation
|
34
|
+
.new(connection: ActiveRecord::Base.connection, speaker: speaker)
|
35
|
+
.try_create([index])
|
36
|
+
|
37
|
+
expect(indexes_for("hi")).to be_empty
|
38
|
+
expect(speaker.messages).to include(/index 'hi_person_idx' .* has been dropped/)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class DummySpeaker
|
43
|
+
attr_reader :messages
|
44
|
+
|
45
|
+
def initialize
|
46
|
+
@messages = []
|
47
|
+
end
|
48
|
+
|
49
|
+
def say(message, bool = false)
|
50
|
+
@messages << message
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Scenic
|
4
|
+
module Adapters
|
5
|
+
describe Postgres::IndexMigration, :db, :silence do
|
6
|
+
it "moves indexes from the old view to the new view" do
|
7
|
+
create_materialized_view("hi", "SELECT 'hi' AS greeting")
|
8
|
+
create_materialized_view("hi_temp", "SELECT 'hi' AS greeting")
|
9
|
+
add_index(:hi, :greeting, name: "hi_greeting_idx")
|
10
|
+
|
11
|
+
Postgres::IndexMigration
|
12
|
+
.new(connection: ActiveRecord::Base.connection)
|
13
|
+
.migrate(from: "hi", to: "hi_temp")
|
14
|
+
indexes_for_original = indexes_for("hi")
|
15
|
+
indexes_for_temporary = indexes_for("hi_temp")
|
16
|
+
|
17
|
+
expect(indexes_for_original.length).to eq 1
|
18
|
+
expect(indexes_for_original.first.index_name).not_to eq "hi_greeting_idx"
|
19
|
+
expect(indexes_for_temporary.length).to eq 1
|
20
|
+
expect(indexes_for_temporary.first.index_name).to eq "hi_greeting_idx"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -9,27 +9,27 @@ module Scenic
|
|
9
9
|
before do
|
10
10
|
adapter.create_materialized_view(
|
11
11
|
"first",
|
12
|
-
"SELECT text 'hi' AS greeting"
|
12
|
+
"SELECT text 'hi' AS greeting"
|
13
13
|
)
|
14
14
|
adapter.create_materialized_view(
|
15
15
|
"second",
|
16
|
-
"SELECT * FROM first"
|
16
|
+
"SELECT * FROM first"
|
17
17
|
)
|
18
18
|
adapter.create_materialized_view(
|
19
19
|
"third",
|
20
|
-
"SELECT * FROM first UNION SELECT * FROM second"
|
20
|
+
"SELECT * FROM first UNION SELECT * FROM second"
|
21
21
|
)
|
22
22
|
adapter.create_materialized_view(
|
23
23
|
"fourth_1",
|
24
|
-
"SELECT * FROM third"
|
24
|
+
"SELECT * FROM third"
|
25
25
|
)
|
26
26
|
adapter.create_materialized_view(
|
27
27
|
"x_fourth",
|
28
|
-
"SELECT * FROM fourth_1"
|
28
|
+
"SELECT * FROM fourth_1"
|
29
29
|
)
|
30
30
|
adapter.create_materialized_view(
|
31
31
|
"fourth",
|
32
|
-
"SELECT * FROM fourth_1 UNION SELECT * FROM x_fourth"
|
32
|
+
"SELECT * FROM fourth_1 UNION SELECT * FROM x_fourth"
|
33
33
|
)
|
34
34
|
|
35
35
|
expect(adapter).to receive(:refresh_materialized_view)
|
@@ -49,7 +49,7 @@ module Scenic
|
|
49
49
|
:fourth,
|
50
50
|
adapter,
|
51
51
|
ActiveRecord::Base.connection,
|
52
|
-
concurrently: true
|
52
|
+
concurrently: true
|
53
53
|
)
|
54
54
|
end
|
55
55
|
|
@@ -58,7 +58,7 @@ module Scenic
|
|
58
58
|
"public.fourth",
|
59
59
|
adapter,
|
60
60
|
ActiveRecord::Base.connection,
|
61
|
-
concurrently: true
|
61
|
+
concurrently: true
|
62
62
|
)
|
63
63
|
end
|
64
64
|
end
|
@@ -69,7 +69,7 @@ module Scenic
|
|
69
69
|
|
70
70
|
adapter.create_materialized_view(
|
71
71
|
"first",
|
72
|
-
"SELECT text 'hi' AS greeting"
|
72
|
+
"SELECT text 'hi' AS greeting"
|
73
73
|
)
|
74
74
|
|
75
75
|
expect {
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Scenic
|
4
|
+
module Adapters
|
5
|
+
describe Postgres::SideBySide, :db, :silence do
|
6
|
+
it "updates the materialized view to the new version" do
|
7
|
+
adapter = Postgres.new
|
8
|
+
create_materialized_view("hi", "SELECT 'hi' AS greeting")
|
9
|
+
add_index(:hi, :greeting, name: "hi_greeting_idx")
|
10
|
+
new_definition = "SELECT 'hola' AS greeting"
|
11
|
+
|
12
|
+
Postgres::SideBySide
|
13
|
+
.new(adapter: adapter, name: "hi", definition: new_definition)
|
14
|
+
.update
|
15
|
+
result = ar_connection.execute("SELECT * FROM hi").first["greeting"]
|
16
|
+
indexes = indexes_for("hi")
|
17
|
+
|
18
|
+
expect(result).to eq "hola"
|
19
|
+
expect(indexes.length).to eq 1
|
20
|
+
expect(indexes.first.index_name).to eq "hi_greeting_idx"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Scenic
|
4
|
+
module Adapters
|
5
|
+
describe Postgres::TemporaryName do
|
6
|
+
it "generates a temporary name based on a SHA1 hash of the original" do
|
7
|
+
name = "my_materialized_view"
|
8
|
+
|
9
|
+
temporary_name = Postgres::TemporaryName.new(name).to_s
|
10
|
+
|
11
|
+
expect(temporary_name).to match(/_scenic_sbs_[0-9a-f]{40}/)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "does not overflow the 63 character limit for object names" do
|
15
|
+
name = "long_view_name_" * 10
|
16
|
+
|
17
|
+
temporary_name = Postgres::TemporaryName.new(name).to_s
|
18
|
+
|
19
|
+
expect(temporary_name.length).to eq 52
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|