sequent 3.2.2 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/notices.rb +4 -0
- data/lib/sequent.rb +3 -0
- data/lib/sequent/application_record.rb +7 -0
- data/lib/sequent/configuration.rb +13 -0
- data/lib/sequent/core/aggregate_repository.rb +7 -1
- data/lib/sequent/core/command.rb +13 -2
- data/lib/sequent/core/command_record.rb +5 -2
- data/lib/sequent/core/command_service.rb +28 -12
- data/lib/sequent/core/event_publisher.rb +4 -0
- data/lib/sequent/core/event_record.rb +2 -1
- data/lib/sequent/core/event_store.rb +23 -4
- data/lib/sequent/core/helpers/attribute_support.rb +28 -7
- data/lib/sequent/core/helpers/mergable.rb +1 -0
- data/lib/sequent/core/persistors/active_record_persistor.rb +1 -1
- data/lib/sequent/core/persistors/replay_optimized_postgres_persistor.rb +2 -2
- data/lib/sequent/core/projector.rb +23 -1
- data/lib/sequent/core/stream_record.rb +1 -1
- data/lib/sequent/core/transactions/active_record_transaction_provider.rb +6 -4
- data/lib/sequent/generator.rb +1 -4
- data/lib/sequent/generator/generator.rb +4 -0
- data/lib/sequent/generator/project.rb +1 -1
- data/lib/sequent/generator/template_project/Gemfile +1 -1
- data/lib/sequent/generator/template_project/app/records/post_record.rb +1 -1
- data/lib/sequent/generator/template_project/spec/app/projectors/post_projector_spec.rb +1 -1
- data/lib/sequent/generator/template_project/spec/lib/post/post_command_handler_spec.rb +1 -1
- data/lib/sequent/migrations/executor.rb +78 -0
- data/lib/sequent/migrations/functions.rb +76 -0
- data/lib/sequent/migrations/migrations.rb +1 -0
- data/lib/sequent/migrations/planner.rb +118 -0
- data/lib/sequent/migrations/projectors.rb +6 -5
- data/lib/sequent/migrations/sql.rb +17 -0
- data/lib/sequent/migrations/view_schema.rb +74 -73
- data/lib/sequent/rake/migration_tasks.rb +2 -2
- data/lib/sequent/rake/tasks.rb +1 -1
- data/lib/sequent/sequent.rb +5 -1
- data/lib/sequent/support/database.rb +11 -6
- data/lib/sequent/test/command_handler_helpers.rb +4 -0
- data/lib/sequent/util/dry_run.rb +191 -0
- data/lib/sequent/util/skip_if_already_processing.rb +19 -5
- data/lib/sequent/util/util.rb +1 -0
- data/lib/version.rb +1 -1
- metadata +77 -36
@@ -4,10 +4,12 @@ module Sequent
|
|
4
4
|
|
5
5
|
class ActiveRecordTransactionProvider
|
6
6
|
def transactional
|
7
|
-
|
7
|
+
Sequent::ApplicationRecord.transaction(requires_new: true) do
|
8
8
|
yield
|
9
9
|
end
|
10
|
-
after_commit_queue.
|
10
|
+
while(!after_commit_queue.empty?) do
|
11
|
+
after_commit_queue.pop.call
|
12
|
+
end
|
11
13
|
ensure
|
12
14
|
clear_after_commit_queue
|
13
15
|
end
|
@@ -19,11 +21,11 @@ module Sequent
|
|
19
21
|
private
|
20
22
|
|
21
23
|
def after_commit_queue
|
22
|
-
Thread.current[:after_commit_queue] ||=
|
24
|
+
Thread.current[:after_commit_queue] ||= Queue.new
|
23
25
|
end
|
24
26
|
|
25
27
|
def clear_after_commit_queue
|
26
|
-
|
28
|
+
after_commit_queue.clear
|
27
29
|
end
|
28
30
|
end
|
29
31
|
|
data/lib/sequent/generator.rb
CHANGED
@@ -1,2 +1,2 @@
|
|
1
|
-
class PostRecord <
|
1
|
+
class PostRecord < Sequent::ApplicationRecord
|
2
2
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require_relative 'sql'
|
2
|
+
|
3
|
+
module Sequent
|
4
|
+
module Migrations
|
5
|
+
|
6
|
+
##
|
7
|
+
# The executor is the implementation of the 3-phase deploy in Sequent.
|
8
|
+
# is responsible for executing the `Planner::Plan`.
|
9
|
+
#
|
10
|
+
class Executor
|
11
|
+
include Sql
|
12
|
+
|
13
|
+
def execute_online(plan)
|
14
|
+
plan.replay_tables.each do |migration|
|
15
|
+
table = migration.record_class
|
16
|
+
sql_file = "#{Sequent.configuration.migration_sql_files_directory}/#{table.table_name}.sql"
|
17
|
+
statements = sql_file_to_statements(sql_file) { |raw_sql| raw_sql.gsub('%SUFFIX%', "_#{migration.version}") }
|
18
|
+
statements.each(&method(:exec_sql))
|
19
|
+
table.table_name = "#{table.table_name}_#{migration.version}"
|
20
|
+
table.reset_column_information
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def create_indexes_after_execute_online(plan)
|
25
|
+
plan.replay_tables.each do |migration|
|
26
|
+
table = migration.record_class
|
27
|
+
original_table_name = table.table_name.gsub("_#{migration.version}", '')
|
28
|
+
indexes_file_name = "#{Sequent.configuration.migration_sql_files_directory}/#{original_table_name}.indexes.sql"
|
29
|
+
if File.exist?(indexes_file_name)
|
30
|
+
statements = sql_file_to_statements(indexes_file_name) { |raw_sql| raw_sql.gsub('%SUFFIX%', "_#{migration.version}") }
|
31
|
+
statements.each(&method(:exec_sql))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def execute_offline(plan, current_version)
|
37
|
+
plan.replay_tables.each do |migration|
|
38
|
+
table = migration.record_class
|
39
|
+
current_table_name = table.table_name.gsub("_#{migration.version}", "")
|
40
|
+
# 2 Rename old table
|
41
|
+
exec_sql("ALTER TABLE IF EXISTS #{current_table_name} RENAME TO #{current_table_name}_#{current_version}")
|
42
|
+
# 3 Rename new table
|
43
|
+
exec_sql("ALTER TABLE #{table.table_name} RENAME TO #{current_table_name}")
|
44
|
+
# Use new table from now on
|
45
|
+
table.table_name = current_table_name
|
46
|
+
table.reset_column_information
|
47
|
+
end
|
48
|
+
|
49
|
+
plan.alter_tables.each do |migration|
|
50
|
+
table = migration.record_class
|
51
|
+
sql_file = "#{Sequent.configuration.migration_sql_files_directory}/#{table.table_name}_#{migration.version}.sql"
|
52
|
+
statements = sql_file_to_statements(sql_file)
|
53
|
+
statements.each(&method(:exec_sql))
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def reset_table_names(plan)
|
58
|
+
plan.replay_tables.each do |migration|
|
59
|
+
table = migration.record_class
|
60
|
+
table.table_name = table.table_name.gsub("_#{migration.version}", "")
|
61
|
+
table.reset_column_information
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def set_table_names_to_new_version(plan)
|
66
|
+
plan.replay_tables.each do |migration|
|
67
|
+
table = migration.record_class
|
68
|
+
unless table.table_name.end_with?("_#{migration.version}")
|
69
|
+
table.table_name = "#{table.table_name}_#{migration.version}"
|
70
|
+
table.reset_column_information
|
71
|
+
fail MigrationError.new("Table #{table.table_name} does not exist. Did you run ViewSchema.migrate_online first?") unless table.table_exists?
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Sequent
|
2
|
+
module Migrations
|
3
|
+
class Migration
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def create(record_class, version)
|
7
|
+
migration = new(record_class)
|
8
|
+
migration.version = version
|
9
|
+
migration
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.inherited(child_class)
|
14
|
+
class << child_class
|
15
|
+
include ClassMethods
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :record_class
|
20
|
+
attr_accessor :version
|
21
|
+
|
22
|
+
def initialize(record_class)
|
23
|
+
@record_class = record_class
|
24
|
+
@version = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def table_name
|
28
|
+
@record_class.table_name
|
29
|
+
end
|
30
|
+
|
31
|
+
def copy(with_version)
|
32
|
+
self.class.create(record_class, with_version)
|
33
|
+
end
|
34
|
+
|
35
|
+
def ==(other)
|
36
|
+
return false unless other.class == self.class
|
37
|
+
|
38
|
+
self.table_name == other.table_name && version == other.version
|
39
|
+
end
|
40
|
+
|
41
|
+
def hash
|
42
|
+
self.table_name.hash + (version&.hash || 0)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class AlterTable < Migration; end
|
47
|
+
|
48
|
+
class ReplayTable < Migration; end
|
49
|
+
|
50
|
+
module Functions
|
51
|
+
|
52
|
+
module ClassMethods
|
53
|
+
def alter_table(name)
|
54
|
+
AlterTable.new(name)
|
55
|
+
end
|
56
|
+
|
57
|
+
def replay_table(name)
|
58
|
+
ReplayTable.new(name)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Short hand for Sequent::Core::Migratable.all
|
62
|
+
def all_projectors
|
63
|
+
Sequent::Core::Migratable.all
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.included(base)
|
69
|
+
base.extend(ClassMethods)
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
include Functions
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module Sequent
|
2
|
+
module Migrations
|
3
|
+
class Planner
|
4
|
+
Plan = Struct.new(:projectors, :migrations) do
|
5
|
+
def replay_tables
|
6
|
+
migrations.select { |m| m.class == ReplayTable }
|
7
|
+
end
|
8
|
+
|
9
|
+
def alter_tables
|
10
|
+
migrations.select { |m| m.class == AlterTable }
|
11
|
+
end
|
12
|
+
|
13
|
+
def empty?
|
14
|
+
migrations.empty?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :versions
|
19
|
+
|
20
|
+
def initialize(versions)
|
21
|
+
@versions = versions
|
22
|
+
end
|
23
|
+
|
24
|
+
def plan(old, new)
|
25
|
+
migrations = versions.slice(*Range.new(old + 1, new).to_a.map(&:to_s))
|
26
|
+
|
27
|
+
Plan.new(
|
28
|
+
migrations.yield_self(&method(:select_projectors)),
|
29
|
+
migrations
|
30
|
+
.yield_self(&method(:create_migrations))
|
31
|
+
.yield_self(&method(:remove_redundant_migrations))
|
32
|
+
)
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def select_projectors(migrations)
|
38
|
+
migrations
|
39
|
+
.values
|
40
|
+
.flatten
|
41
|
+
.select { |v| v.is_a?(Class) && v < Sequent::Projector }.uniq
|
42
|
+
end
|
43
|
+
|
44
|
+
def remove_redundant_migrations(migrations)
|
45
|
+
redundant_migrations = migrations
|
46
|
+
.yield_self(&method(:group_identical_migrations))
|
47
|
+
.yield_self(&method(:select_redundant_migrations))
|
48
|
+
.yield_self(&method(:remove_redundancy))
|
49
|
+
.values
|
50
|
+
.flatten
|
51
|
+
|
52
|
+
(migrations - redundant_migrations)
|
53
|
+
.yield_self(&method(:remove_alter_tables_before_replay_table))
|
54
|
+
end
|
55
|
+
|
56
|
+
def group_identical_migrations(migrations)
|
57
|
+
migrations
|
58
|
+
.group_by { |migration| {migration_type: migration.class, record_class: migration.record_class} }
|
59
|
+
end
|
60
|
+
|
61
|
+
def select_redundant_migrations(grouped_migrations)
|
62
|
+
grouped_migrations.select { |type, ms| type[:migration_type] == ReplayTable && ms.length > 1 }
|
63
|
+
end
|
64
|
+
|
65
|
+
def remove_alter_tables_before_replay_table(migrations)
|
66
|
+
migrations - migrations
|
67
|
+
.each_with_index
|
68
|
+
.select { |migration, _index| migration.class == AlterTable }
|
69
|
+
.select { |migration, index| migrations
|
70
|
+
.slice((index + 1)..-1)
|
71
|
+
.find { |m| m.class == ReplayTable && m.record_class == migration.record_class }
|
72
|
+
}.map(&:first)
|
73
|
+
end
|
74
|
+
|
75
|
+
def remove_redundancy(grouped_migrations)
|
76
|
+
grouped_migrations.reduce({}) { |memo, (key, ms)|
|
77
|
+
memo[key] = ms
|
78
|
+
.yield_self(&method(:order_by_version_desc))
|
79
|
+
.slice(1..-1)
|
80
|
+
memo
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
84
|
+
def order_by_version_desc(migrations)
|
85
|
+
migrations.sort_by { |m| m.version.to_i }
|
86
|
+
.reverse
|
87
|
+
end
|
88
|
+
|
89
|
+
def create_migrations(migrations)
|
90
|
+
migrations
|
91
|
+
.yield_self(&method(:map_to_migrations))
|
92
|
+
.values
|
93
|
+
.compact
|
94
|
+
.flatten
|
95
|
+
end
|
96
|
+
|
97
|
+
def map_to_migrations(migrations)
|
98
|
+
migrations.reduce({}) do |memo, (version, _migrations)|
|
99
|
+
fail "Declared migrations for version #{version} must be an Array. For example: {'3' => [FooProjector]}" unless _migrations.is_a?(Array)
|
100
|
+
|
101
|
+
memo[version] = _migrations.flat_map do |migration|
|
102
|
+
if migration.is_a?(AlterTable)
|
103
|
+
alter_table_sql_file_name = "#{Sequent.configuration.migration_sql_files_directory}/#{migration.table_name}_#{version}.sql"
|
104
|
+
fail "Missing file #{alter_table_sql_file_name} to apply for version #{version}" unless File.exist?(alter_table_sql_file_name)
|
105
|
+
migration.copy(version)
|
106
|
+
elsif migration < Sequent::Projector
|
107
|
+
migration.managed_tables.map { |table| ReplayTable.create(table, version) }
|
108
|
+
else
|
109
|
+
fail "Unknown Migration #{migration}"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
memo
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -1,18 +1,19 @@
|
|
1
|
+
require_relative 'planner'
|
1
2
|
module Sequent
|
2
3
|
module Migrations
|
3
4
|
class Projectors
|
4
5
|
def self.versions
|
5
|
-
fail "Define your own Sequent::Migrations::
|
6
|
+
fail "Define your own Sequent::Migrations::List class that extends this class and implements this method"
|
6
7
|
end
|
7
8
|
|
8
9
|
def self.version
|
9
|
-
fail "Define your own Sequent::Migrations::
|
10
|
+
fail "Define your own Sequent::Migrations::List class that extends this class and implements this method"
|
10
11
|
end
|
11
12
|
|
12
|
-
def self.
|
13
|
-
|
13
|
+
def self.migrations_between(old, new)
|
14
|
+
Planner.new(versions).plan(old, new)
|
14
15
|
end
|
15
16
|
end
|
17
|
+
|
16
18
|
end
|
17
19
|
end
|
18
|
-
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative '../application_record'
|
2
|
+
|
3
|
+
module Sequent
|
4
|
+
module Migrations
|
5
|
+
module Sql
|
6
|
+
def sql_file_to_statements(file_location)
|
7
|
+
sql_string = File.read(file_location, encoding: 'bom|utf-8')
|
8
|
+
sql_string = yield(sql_string) if block_given?
|
9
|
+
sql_string.split(/;$/).reject { |statement| statement.remove("\n").blank? }
|
10
|
+
end
|
11
|
+
|
12
|
+
def exec_sql(sql)
|
13
|
+
Sequent::ApplicationRecord.connection.execute(sql)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -6,30 +6,56 @@ require_relative '../sequent'
|
|
6
6
|
require_relative '../util/timer'
|
7
7
|
require_relative '../util/printer'
|
8
8
|
require_relative './projectors'
|
9
|
+
require_relative 'planner'
|
10
|
+
require_relative 'executor'
|
11
|
+
require_relative 'sql'
|
9
12
|
|
10
13
|
module Sequent
|
11
14
|
module Migrations
|
12
15
|
class MigrationError < RuntimeError; end
|
13
16
|
|
14
|
-
|
15
17
|
##
|
16
|
-
#
|
18
|
+
# ViewSchema is used for migration of you view_schema. For instance
|
19
|
+
# when you create new Projectors or change existing Projectors.
|
17
20
|
#
|
18
|
-
#
|
21
|
+
# The following migrations are supported:
|
19
22
|
#
|
20
|
-
# -
|
21
|
-
# -
|
23
|
+
# - ReplayTable (Projector migrations)
|
24
|
+
# - AlterTable (For instance if you introduce a new column)
|
22
25
|
#
|
23
26
|
# To maintain your migrations you need to:
|
24
27
|
# 1. Create a class that extends `Sequent::Migrations::Projectors` and specify in `Sequent.configuration.migrations_class_name`
|
25
|
-
# 2. Define per version which
|
28
|
+
# 2. Define per version which migrations you want to execute
|
26
29
|
# See the definition of `Sequent::Migrations::Projectors.versions` and `Sequent::Migrations::Projectors.version`
|
27
30
|
# 3. Specify in Sequent where your sql files reside (Sequent.configuration.migration_sql_files_directory)
|
28
31
|
# 4. Ensure that you add %SUFFIX% to each name that needs to be unique in postgres (like TABLE names, INDEX names, PRIMARY KEYS)
|
29
32
|
# E.g. `create table foo%SUFFIX% (id serial NOT NULL, CONSTRAINT foo_pkey%SUFFIX% PRIMARY KEY (id))`
|
33
|
+
# 5. If you want to run an `alter_table` migration ensure that
|
34
|
+
# a sql file named `table_name_VERSION.sql` exists.
|
35
|
+
#
|
36
|
+
# Example:
|
37
|
+
#
|
38
|
+
# class AppMigrations < Sequent::Migrations::Projectors
|
39
|
+
# def self.version
|
40
|
+
# '3'
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# def self.versions
|
44
|
+
# {
|
45
|
+
# '1' => [Sequent.all_projectors],
|
46
|
+
# '2' => [
|
47
|
+
# UserProjector,
|
48
|
+
# InvoiceProjector,
|
49
|
+
# ],
|
50
|
+
# '3' => [
|
51
|
+
# Sequent::Migrations.alter_table(UserRecord)
|
52
|
+
# ]
|
53
|
+
#
|
54
|
+
# }
|
55
|
+
# end
|
30
56
|
#
|
57
|
+
# end
|
31
58
|
class ViewSchema
|
32
|
-
|
33
59
|
# Corresponds with the index on aggregate_id column in the event_records table
|
34
60
|
#
|
35
61
|
# Since we replay in batches of the first 3 chars of the uuid we created an index on
|
@@ -42,9 +68,10 @@ module Sequent
|
|
42
68
|
|
43
69
|
include Sequent::Util::Timer
|
44
70
|
include Sequent::Util::Printer
|
71
|
+
include Sql
|
45
72
|
|
46
|
-
class Versions <
|
47
|
-
class ReplayedIds <
|
73
|
+
class Versions < Sequent::ApplicationRecord; end
|
74
|
+
class ReplayedIds < Sequent::ApplicationRecord; end
|
48
75
|
|
49
76
|
attr_reader :view_schema, :db_config, :logger
|
50
77
|
|
@@ -71,6 +98,12 @@ module Sequent
|
|
71
98
|
Sequent::Core::Migratable.all.flat_map(&:managed_tables).each do |table|
|
72
99
|
statements = sql_file_to_statements("#{Sequent.configuration.migration_sql_files_directory}/#{table.table_name}.sql") { |raw_sql| raw_sql.remove('%SUFFIX%') }
|
73
100
|
statements.each { |statement| exec_sql(statement) }
|
101
|
+
|
102
|
+
indexes_file_name = "#{Sequent.configuration.migration_sql_files_directory}/#{table.table_name}.indexes.sql"
|
103
|
+
if File.exist?(indexes_file_name)
|
104
|
+
statements = sql_file_to_statements(indexes_file_name) { |raw_sql| raw_sql.remove('%SUFFIX%') }
|
105
|
+
statements.each(&method(:exec_sql))
|
106
|
+
end
|
74
107
|
end
|
75
108
|
end
|
76
109
|
end
|
@@ -80,7 +113,7 @@ module Sequent
|
|
80
113
|
#
|
81
114
|
# This method is mainly useful in test scenario's or development tasks
|
82
115
|
def replay_all!
|
83
|
-
replay!(Sequent
|
116
|
+
replay!(Sequent.configuration.online_replay_persistor_class.new)
|
84
117
|
end
|
85
118
|
|
86
119
|
##
|
@@ -95,6 +128,14 @@ module Sequent
|
|
95
128
|
end
|
96
129
|
end
|
97
130
|
|
131
|
+
def plan
|
132
|
+
@plan ||= Planner.new(Sequent.migration_class.versions).plan(current_version, Sequent.new_version)
|
133
|
+
end
|
134
|
+
|
135
|
+
def executor
|
136
|
+
@executor ||= Executor.new
|
137
|
+
end
|
138
|
+
|
98
139
|
##
|
99
140
|
# First part of a view schema migration
|
100
141
|
#
|
@@ -118,14 +159,16 @@ module Sequent
|
|
118
159
|
truncate_replay_ids_table!
|
119
160
|
|
120
161
|
drop_old_tables(Sequent.new_version)
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
162
|
+
executor.execute_online(plan)
|
163
|
+
end
|
164
|
+
|
165
|
+
if plan.projectors.any?
|
166
|
+
replay!(Sequent.configuration.online_replay_persistor_class.new)
|
167
|
+
end
|
168
|
+
|
169
|
+
in_view_schema do
|
170
|
+
executor.create_indexes_after_execute_online(plan)
|
127
171
|
end
|
128
|
-
replay!(projectors_to_migrate, Sequent.configuration.online_replay_persistor_class.new)
|
129
172
|
rescue Exception => e
|
130
173
|
rollback_migration
|
131
174
|
raise e
|
@@ -155,28 +198,20 @@ module Sequent
|
|
155
198
|
|
156
199
|
ensure_version_correct!
|
157
200
|
|
158
|
-
set_table_names_to_new_version
|
201
|
+
executor.set_table_names_to_new_version(plan)
|
159
202
|
|
160
203
|
# 1 replay events not yet replayed
|
161
|
-
replay!(
|
204
|
+
replay!(Sequent.configuration.offline_replay_persistor_class.new, exclude_ids: true, group_exponent: 1) if plan.projectors.any?
|
162
205
|
|
163
206
|
in_view_schema do
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
exec_sql("ALTER TABLE IF EXISTS #{current_table_name} RENAME TO #{current_table_name}_#{current_version}")
|
169
|
-
# 3 Rename new table
|
170
|
-
exec_sql("ALTER TABLE #{table.table_name} RENAME TO #{current_table_name}")
|
171
|
-
# Use new table from now on
|
172
|
-
table.table_name = current_table_name
|
173
|
-
table.reset_column_information
|
174
|
-
end
|
175
|
-
# 4. Create migration record
|
207
|
+
Sequent::ApplicationRecord.transaction do
|
208
|
+
# 2.1, 2.2
|
209
|
+
executor.execute_offline(plan, current_version)
|
210
|
+
# 2.3 Create migration record
|
176
211
|
Versions.create!(version: Sequent.new_version)
|
177
212
|
end
|
178
213
|
|
179
|
-
#
|
214
|
+
# 3. Truncate replayed ids
|
180
215
|
truncate_replay_ids_table!
|
181
216
|
end
|
182
217
|
logger.info "Migrated to version #{Sequent.new_version}"
|
@@ -186,41 +221,27 @@ module Sequent
|
|
186
221
|
end
|
187
222
|
|
188
223
|
private
|
189
|
-
def set_table_names_to_new_version
|
190
|
-
for_each_table_to_migrate do |table|
|
191
|
-
unless table.table_name.end_with?("_#{Sequent.new_version}")
|
192
|
-
table.table_name = "#{table.table_name}_#{Sequent.new_version}"
|
193
|
-
table.reset_column_information
|
194
|
-
fail MigrationError.new("Table #{table.table_name} does not exist. Did you run migrate_online first?") unless table.table_exists?
|
195
|
-
end
|
196
|
-
end
|
197
|
-
end
|
198
224
|
|
199
|
-
def reset_table_names
|
200
|
-
for_each_table_to_migrate do |table|
|
201
|
-
table.table_name = table.table_name.gsub("_#{Sequent.new_version}", "")
|
202
|
-
table.reset_column_information
|
203
|
-
end
|
204
|
-
end
|
205
225
|
|
206
226
|
def ensure_version_correct!
|
207
227
|
create_view_schema_if_not_exists
|
208
228
|
new_version = Sequent.new_version
|
209
229
|
|
210
230
|
fail ArgumentError.new("new_version [#{new_version}] must be greater or equal to current_version [#{current_version}]") if new_version < current_version
|
231
|
+
|
211
232
|
end
|
212
233
|
|
213
|
-
def replay!(
|
234
|
+
def replay!(replay_persistor, projectors: plan.projectors, exclude_ids: false, group_exponent: 3)
|
214
235
|
logger.info "group_exponent: #{group_exponent.inspect}"
|
215
236
|
|
216
237
|
with_sequent_config(replay_persistor, projectors) do
|
217
238
|
logger.info "Start replaying events"
|
218
239
|
|
219
|
-
time("#{16**group_exponent} groups replayed") do
|
240
|
+
time("#{16 ** group_exponent} groups replayed") do
|
220
241
|
event_types = projectors.flat_map { |projector| projector.message_mapping.keys }.uniq.map(&:name)
|
221
242
|
disconnect!
|
222
243
|
|
223
|
-
number_of_groups = 16**group_exponent
|
244
|
+
number_of_groups = 16 ** group_exponent
|
224
245
|
groups = groups_of_aggregate_id_prefixes(number_of_groups)
|
225
246
|
|
226
247
|
@connected = false
|
@@ -264,7 +285,7 @@ module Sequent
|
|
264
285
|
drop_old_tables(Sequent.new_version)
|
265
286
|
|
266
287
|
truncate_replay_ids_table!
|
267
|
-
reset_table_names
|
288
|
+
executor.reset_table_names(plan)
|
268
289
|
end
|
269
290
|
|
270
291
|
def truncate_replay_ids_table!
|
@@ -272,7 +293,7 @@ module Sequent
|
|
272
293
|
end
|
273
294
|
|
274
295
|
def groups_of_aggregate_id_prefixes(number_of_groups)
|
275
|
-
all_prefixes = (0...16**LENGTH_OF_SUBSTRING_INDEX_ON_AGGREGATE_ID_IN_EVENT_STORE).to_a.map { |i| i.to_s(16) } # first x digits of hex
|
296
|
+
all_prefixes = (0...16 ** LENGTH_OF_SUBSTRING_INDEX_ON_AGGREGATE_ID_IN_EVENT_STORE).to_a.map { |i| i.to_s(16) } # first x digits of hex
|
276
297
|
all_prefixes = all_prefixes.map { |s| s.length == 3 ? s : "#{"0" * (3 - s.length)}#{s}" }
|
277
298
|
|
278
299
|
logger.info "Number of groups #{number_of_groups}"
|
@@ -280,17 +301,7 @@ module Sequent
|
|
280
301
|
logger.debug "Prefixes: #{all_prefixes.length}"
|
281
302
|
fail "Can not have more groups #{number_of_groups} than number of prefixes #{all_prefixes.length}" if number_of_groups > all_prefixes.length
|
282
303
|
|
283
|
-
all_prefixes.each_slice(all_prefixes.length/number_of_groups).to_a
|
284
|
-
end
|
285
|
-
|
286
|
-
def for_each_table_to_migrate
|
287
|
-
projectors_to_migrate.flat_map(&:managed_tables).each do |managed_table|
|
288
|
-
yield(managed_table)
|
289
|
-
end
|
290
|
-
end
|
291
|
-
|
292
|
-
def projectors_to_migrate
|
293
|
-
Sequent.migration_class.projectors_between(current_version, Sequent.new_version)
|
304
|
+
all_prefixes.each_slice(all_prefixes.length / number_of_groups).to_a
|
294
305
|
end
|
295
306
|
|
296
307
|
def in_view_schema
|
@@ -299,12 +310,6 @@ module Sequent
|
|
299
310
|
end
|
300
311
|
end
|
301
312
|
|
302
|
-
def sql_file_to_statements(file_location)
|
303
|
-
raw_sql_string = File.read(file_location, encoding: 'bom|utf-8')
|
304
|
-
sql_string = yield(raw_sql_string)
|
305
|
-
sql_string.split(/;$/).reject { |statement| statement.remove("\n").blank? }
|
306
|
-
end
|
307
|
-
|
308
313
|
def drop_old_tables(new_version)
|
309
314
|
versions_to_check = (current_version - 10)..new_version
|
310
315
|
old_tables = versions_to_check.flat_map do |old_version|
|
@@ -358,10 +363,6 @@ module Sequent
|
|
358
363
|
def establish_connection
|
359
364
|
Sequent::Support::Database.establish_connection(db_config)
|
360
365
|
end
|
361
|
-
|
362
|
-
def exec_sql(sql)
|
363
|
-
ActiveRecord::Base.connection.execute(sql)
|
364
|
-
end
|
365
366
|
end
|
366
367
|
end
|
367
368
|
end
|