sequent 3.2.2 → 4.0.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/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
|