sequent 3.2.2 → 3.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/sequent/application_record.rb +7 -0
- data/lib/sequent/configuration.rb +6 -0
- data/lib/sequent/core/command_record.rb +1 -1
- data/lib/sequent/core/event_record.rb +2 -1
- data/lib/sequent/core/helpers/attribute_support.rb +9 -2
- 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 +1 -1
- data/lib/sequent/generator/template_project/app/records/post_record.rb +1 -1
- data/lib/sequent/migrations/executor.rb +66 -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 +65 -72
- data/lib/sequent/rake/migration_tasks.rb +1 -1
- data/lib/sequent/rake/tasks.rb +1 -1
- data/lib/sequent/sequent.rb +1 -0
- data/lib/sequent/support/database.rb +0 -2
- data/lib/sequent.rb +1 -0
- data/lib/version.rb +1 -1
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0b1fb847d194b47667729bdcf1e9ec4f8c78214f23ff71ee7ce37f1c225dc952
|
4
|
+
data.tar.gz: 23080b89717f7f2ebb30db682295cf0c598b120b84f3aec7ae372d193d937feb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 33403d55a2d2bb8afd4892e952dcfe26aaa5ae9d4d25fd49bbca48f12fd5b4b077fba819450856b6f1f7b3587f0462c652b8679749bde380dcf10b53c2167cd8
|
7
|
+
data.tar.gz: baf1dc0def17b1a90a6895dd94dfb9777f6c6a1f4292e9f44c956bc1a7b487d86687a6cbf06b5ac84c193c302af134871f813bae2e5476c491246f8103c3b3a1
|
@@ -26,6 +26,8 @@ module Sequent
|
|
26
26
|
|
27
27
|
DEFAULT_EVENT_RECORD_HOOKS_CLASS = Sequent::Core::EventRecordHooks
|
28
28
|
|
29
|
+
DEFAULT_STRICT_CHECK_ATTRIBUTES_ON_APPLY_EVENTS = false
|
30
|
+
|
29
31
|
attr_accessor :aggregate_repository
|
30
32
|
|
31
33
|
attr_accessor :event_store,
|
@@ -49,6 +51,7 @@ module Sequent
|
|
49
51
|
|
50
52
|
attr_accessor :logger
|
51
53
|
|
54
|
+
|
52
55
|
attr_accessor :migration_sql_files_directory,
|
53
56
|
:view_schema_name,
|
54
57
|
:offline_replay_persistor_class,
|
@@ -57,6 +60,8 @@ module Sequent
|
|
57
60
|
:database_config_directory,
|
58
61
|
:event_store_schema_name
|
59
62
|
|
63
|
+
attr_accessor :strict_check_attributes_on_apply_events
|
64
|
+
|
60
65
|
attr_reader :migrations_class_name,
|
61
66
|
:versions_table_name,
|
62
67
|
:replayed_ids_table_name
|
@@ -101,6 +106,7 @@ module Sequent
|
|
101
106
|
self.offline_replay_persistor_class = DEFAULT_OFFLINE_REPLAY_PERSISTOR_CLASS
|
102
107
|
self.online_replay_persistor_class = DEFAULT_ONLINE_REPLAY_PERSISTOR_CLASS
|
103
108
|
self.database_config_directory = DEFAULT_DATABASE_CONFIG_DIRECTORY
|
109
|
+
self.strict_check_attributes_on_apply_events = DEFAULT_STRICT_CHECK_ATTRIBUTES_ON_APPLY_EVENTS
|
104
110
|
|
105
111
|
self.logger = Logger.new(STDOUT).tap {|l| l.level = Logger::INFO }
|
106
112
|
end
|
@@ -32,7 +32,7 @@ module Sequent
|
|
32
32
|
end
|
33
33
|
|
34
34
|
# For storing Sequent::Core::Command in the database using active_record
|
35
|
-
class CommandRecord <
|
35
|
+
class CommandRecord < Sequent::ApplicationRecord
|
36
36
|
include SerializesCommand
|
37
37
|
|
38
38
|
self.table_name = "command_records"
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'active_record'
|
2
2
|
require_relative 'sequent_oj'
|
3
|
+
require_relative '../application_record.rb'
|
3
4
|
|
4
5
|
module Sequent
|
5
6
|
module Core
|
@@ -72,7 +73,7 @@ module Sequent
|
|
72
73
|
end
|
73
74
|
end
|
74
75
|
|
75
|
-
class EventRecord <
|
76
|
+
class EventRecord < Sequent::ApplicationRecord
|
76
77
|
include SerializesEvent
|
77
78
|
|
78
79
|
self.table_name = "event_records"
|
@@ -20,6 +20,8 @@ module Sequent
|
|
20
20
|
# get this functionality for free.
|
21
21
|
#
|
22
22
|
module AttributeSupport
|
23
|
+
class UnknownAttributeError < StandardError; end
|
24
|
+
|
23
25
|
# module containing class methods to be added
|
24
26
|
module ClassMethods
|
25
27
|
|
@@ -60,6 +62,7 @@ module Sequent
|
|
60
62
|
class_eval <<EOS
|
61
63
|
def update_all_attributes(attrs)
|
62
64
|
super if defined?(super)
|
65
|
+
ensure_known_attributes(attrs)
|
63
66
|
#{@types.map { |attribute, _|
|
64
67
|
"@#{attribute} = attrs[:#{attribute}]"
|
65
68
|
}.join("\n ")}
|
@@ -157,9 +160,13 @@ EOS
|
|
157
160
|
prefix ? HashWithIndifferentAccess[result.map { |k, v| ["#{prefix}_#{k}", v] }] : result
|
158
161
|
end
|
159
162
|
|
160
|
-
|
161
|
-
|
163
|
+
def ensure_known_attributes(attrs)
|
164
|
+
return unless Sequent.configuration.strict_check_attributes_on_apply_events
|
162
165
|
|
166
|
+
unknowns = attrs.keys.map(&:to_s) - self.class.types.keys.map(&:to_s)
|
167
|
+
raise UnknownAttributeError.new("#{self.class.name} does not specify attrs: #{unknowns.join(", ")}") if unknowns.any?
|
168
|
+
end
|
169
|
+
end
|
163
170
|
end
|
164
171
|
end
|
165
172
|
end
|
@@ -307,7 +307,7 @@ module Sequent
|
|
307
307
|
end
|
308
308
|
|
309
309
|
buf = ''
|
310
|
-
conn =
|
310
|
+
conn = Sequent::ApplicationRecord.connection.raw_connection
|
311
311
|
copy_data = StringIO.new csv.string
|
312
312
|
conn.transaction do
|
313
313
|
conn.copy_data("COPY #{clazz.table_name} (#{column_names.join(",")}) FROM STDIN WITH csv") do
|
@@ -346,7 +346,7 @@ module Sequent
|
|
346
346
|
private
|
347
347
|
|
348
348
|
def cast_value_to_column_type(clazz, column_name, record)
|
349
|
-
|
349
|
+
Sequent::ApplicationRecord.connection.type_cast(record[column_name.to_sym], @column_cache[clazz.name][column_name])
|
350
350
|
end
|
351
351
|
end
|
352
352
|
end
|
@@ -11,7 +11,26 @@ module Sequent
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def managed_tables
|
14
|
-
@managed_tables
|
14
|
+
@managed_tables || managed_tables_from_superclass
|
15
|
+
end
|
16
|
+
|
17
|
+
def manages_no_tables
|
18
|
+
@manages_no_tables = true
|
19
|
+
manages_tables *[]
|
20
|
+
end
|
21
|
+
|
22
|
+
def manages_no_tables?
|
23
|
+
!!@manages_no_tables || manages_no_tables_from_superclass?
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def managed_tables_from_superclass
|
29
|
+
self.superclass.managed_tables if self.superclass.respond_to?(:managed_tables)
|
30
|
+
end
|
31
|
+
|
32
|
+
def manages_no_tables_from_superclass?
|
33
|
+
self.superclass.manages_no_tables? if self.superclass.respond_to?(:manages_no_tables?)
|
15
34
|
end
|
16
35
|
end
|
17
36
|
|
@@ -96,7 +115,10 @@ module Sequent
|
|
96
115
|
:commit
|
97
116
|
|
98
117
|
private
|
118
|
+
|
99
119
|
def ensure_valid!
|
120
|
+
return if self.class.manages_no_tables?
|
121
|
+
|
100
122
|
fail "A Projector must manage at least one table. Did you forget to add `managed_tables` to #{self.class.name}?" if self.class.managed_tables.nil? || self.class.managed_tables.empty?
|
101
123
|
end
|
102
124
|
end
|
@@ -1,2 +1,2 @@
|
|
1
|
-
class PostRecord <
|
1
|
+
class PostRecord < Sequent::ApplicationRecord
|
2
2
|
end
|
@@ -0,0 +1,66 @@
|
|
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 execute_offline(plan, current_version)
|
25
|
+
plan.replay_tables.each do |migration|
|
26
|
+
table = migration.record_class
|
27
|
+
current_table_name = table.table_name.gsub("_#{migration.version}", "")
|
28
|
+
# 2 Rename old table
|
29
|
+
exec_sql("ALTER TABLE IF EXISTS #{current_table_name} RENAME TO #{current_table_name}_#{current_version}")
|
30
|
+
# 3 Rename new table
|
31
|
+
exec_sql("ALTER TABLE #{table.table_name} RENAME TO #{current_table_name}")
|
32
|
+
# Use new table from now on
|
33
|
+
table.table_name = current_table_name
|
34
|
+
table.reset_column_information
|
35
|
+
end
|
36
|
+
|
37
|
+
plan.alter_tables.each do |migration|
|
38
|
+
table = migration.record_class
|
39
|
+
sql_file = "#{Sequent.configuration.migration_sql_files_directory}/#{table.table_name}_#{migration.version}.sql"
|
40
|
+
statements = sql_file_to_statements(sql_file)
|
41
|
+
statements.each(&method(:exec_sql))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def reset_table_names(plan)
|
46
|
+
plan.replay_tables.each do |migration|
|
47
|
+
table = migration.record_class
|
48
|
+
table.table_name = table.table_name.gsub("_#{migration.version}", "")
|
49
|
+
table.reset_column_information
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def set_table_names_to_new_version(plan)
|
54
|
+
plan.replay_tables.each do |migration|
|
55
|
+
table = migration.record_class
|
56
|
+
unless table.table_name.end_with?("_#{migration.version}")
|
57
|
+
table.table_name = "#{table.table_name}_#{migration.version}"
|
58
|
+
table.reset_column_information
|
59
|
+
fail MigrationError.new("Table #{table.table_name} does not exist. Did you run ViewSchema.migrate_online first?") unless table.table_exists?
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
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
|
+
# ]
|
30
53
|
#
|
54
|
+
# }
|
55
|
+
# end
|
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
|
|
@@ -95,6 +122,14 @@ module Sequent
|
|
95
122
|
end
|
96
123
|
end
|
97
124
|
|
125
|
+
def plan
|
126
|
+
@plan ||= Planner.new(Sequent.migration_class.versions).plan(current_version, Sequent.new_version)
|
127
|
+
end
|
128
|
+
|
129
|
+
def executor
|
130
|
+
@executor ||= Executor.new
|
131
|
+
end
|
132
|
+
|
98
133
|
##
|
99
134
|
# First part of a view schema migration
|
100
135
|
#
|
@@ -118,14 +153,12 @@ module Sequent
|
|
118
153
|
truncate_replay_ids_table!
|
119
154
|
|
120
155
|
drop_old_tables(Sequent.new_version)
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
end
|
156
|
+
executor.execute_online(plan)
|
157
|
+
end
|
158
|
+
|
159
|
+
if plan.projectors.any?
|
160
|
+
replay!(Sequent.configuration.online_replay_persistor_class.new)
|
127
161
|
end
|
128
|
-
replay!(projectors_to_migrate, Sequent.configuration.online_replay_persistor_class.new)
|
129
162
|
rescue Exception => e
|
130
163
|
rollback_migration
|
131
164
|
raise e
|
@@ -155,28 +188,20 @@ module Sequent
|
|
155
188
|
|
156
189
|
ensure_version_correct!
|
157
190
|
|
158
|
-
set_table_names_to_new_version
|
191
|
+
executor.set_table_names_to_new_version(plan)
|
159
192
|
|
160
193
|
# 1 replay events not yet replayed
|
161
|
-
replay!(
|
194
|
+
replay!(Sequent.configuration.offline_replay_persistor_class.new, exclude_ids: true, group_exponent: 1) if plan.projectors.any?
|
162
195
|
|
163
196
|
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
|
197
|
+
Sequent::ApplicationRecord.transaction do
|
198
|
+
# 2.1, 2.2
|
199
|
+
executor.execute_offline(plan, current_version)
|
200
|
+
# 2.3 Create migration record
|
176
201
|
Versions.create!(version: Sequent.new_version)
|
177
202
|
end
|
178
203
|
|
179
|
-
#
|
204
|
+
# 3. Truncate replayed ids
|
180
205
|
truncate_replay_ids_table!
|
181
206
|
end
|
182
207
|
logger.info "Migrated to version #{Sequent.new_version}"
|
@@ -186,41 +211,29 @@ module Sequent
|
|
186
211
|
end
|
187
212
|
|
188
213
|
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
214
|
|
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
215
|
|
206
216
|
def ensure_version_correct!
|
207
217
|
create_view_schema_if_not_exists
|
208
218
|
new_version = Sequent.new_version
|
209
219
|
|
210
220
|
fail ArgumentError.new("new_version [#{new_version}] must be greater or equal to current_version [#{current_version}]") if new_version < current_version
|
221
|
+
|
211
222
|
end
|
212
223
|
|
213
|
-
def replay!(
|
224
|
+
def replay!(replay_persistor, exclude_ids: false, group_exponent: 3)
|
225
|
+
projectors = plan.projectors
|
226
|
+
|
214
227
|
logger.info "group_exponent: #{group_exponent.inspect}"
|
215
228
|
|
216
229
|
with_sequent_config(replay_persistor, projectors) do
|
217
230
|
logger.info "Start replaying events"
|
218
231
|
|
219
|
-
time("#{16**group_exponent} groups replayed") do
|
232
|
+
time("#{16 ** group_exponent} groups replayed") do
|
220
233
|
event_types = projectors.flat_map { |projector| projector.message_mapping.keys }.uniq.map(&:name)
|
221
234
|
disconnect!
|
222
235
|
|
223
|
-
number_of_groups = 16**group_exponent
|
236
|
+
number_of_groups = 16 ** group_exponent
|
224
237
|
groups = groups_of_aggregate_id_prefixes(number_of_groups)
|
225
238
|
|
226
239
|
@connected = false
|
@@ -264,7 +277,7 @@ module Sequent
|
|
264
277
|
drop_old_tables(Sequent.new_version)
|
265
278
|
|
266
279
|
truncate_replay_ids_table!
|
267
|
-
reset_table_names
|
280
|
+
executor.reset_table_names(plan)
|
268
281
|
end
|
269
282
|
|
270
283
|
def truncate_replay_ids_table!
|
@@ -272,7 +285,7 @@ module Sequent
|
|
272
285
|
end
|
273
286
|
|
274
287
|
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
|
288
|
+
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
289
|
all_prefixes = all_prefixes.map { |s| s.length == 3 ? s : "#{"0" * (3 - s.length)}#{s}" }
|
277
290
|
|
278
291
|
logger.info "Number of groups #{number_of_groups}"
|
@@ -280,17 +293,7 @@ module Sequent
|
|
280
293
|
logger.debug "Prefixes: #{all_prefixes.length}"
|
281
294
|
fail "Can not have more groups #{number_of_groups} than number of prefixes #{all_prefixes.length}" if number_of_groups > all_prefixes.length
|
282
295
|
|
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)
|
296
|
+
all_prefixes.each_slice(all_prefixes.length / number_of_groups).to_a
|
294
297
|
end
|
295
298
|
|
296
299
|
def in_view_schema
|
@@ -299,12 +302,6 @@ module Sequent
|
|
299
302
|
end
|
300
303
|
end
|
301
304
|
|
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
305
|
def drop_old_tables(new_version)
|
309
306
|
versions_to_check = (current_version - 10)..new_version
|
310
307
|
old_tables = versions_to_check.flat_map do |old_version|
|
@@ -358,10 +355,6 @@ module Sequent
|
|
358
355
|
def establish_connection
|
359
356
|
Sequent::Support::Database.establish_connection(db_config)
|
360
357
|
end
|
361
|
-
|
362
|
-
def exec_sql(sql)
|
363
|
-
ActiveRecord::Base.connection.execute(sql)
|
364
|
-
end
|
365
358
|
end
|
366
359
|
end
|
367
360
|
end
|
@@ -117,7 +117,7 @@ module Sequent
|
|
117
117
|
end
|
118
118
|
|
119
119
|
task :delete_all => ['sequent:init', :init] do
|
120
|
-
result =
|
120
|
+
result = Sequent::ApplicationRecord.connection.execute("DELETE FROM #{Sequent.configuration.event_record_class.table_name} WHERE event_type = 'Sequent::Core::SnapshotEvent'")
|
121
121
|
Sequent.logger.info "Deleted #{result.cmd_tuples} aggregate snapshots from the event store"
|
122
122
|
end
|
123
123
|
end
|
data/lib/sequent/rake/tasks.rb
CHANGED
data/lib/sequent/sequent.rb
CHANGED
@@ -66,7 +66,6 @@ module Sequent
|
|
66
66
|
ActiveRecord::Base.establish_connection db_config
|
67
67
|
|
68
68
|
yield
|
69
|
-
|
70
69
|
ensure
|
71
70
|
disconnect!
|
72
71
|
db_config['schema_search_path'] = original_search_paths
|
@@ -102,7 +101,6 @@ module Sequent
|
|
102
101
|
else
|
103
102
|
ActiveRecord::Migrator.migrate(migrations_path)
|
104
103
|
end
|
105
|
-
|
106
104
|
end
|
107
105
|
end
|
108
106
|
end
|
data/lib/sequent.rb
CHANGED
data/lib/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sequent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Lars Vonk
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date: 2019-
|
15
|
+
date: 2019-09-30 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: activerecord
|
@@ -265,6 +265,7 @@ files:
|
|
265
265
|
- bin/sequent
|
266
266
|
- db/sequent_schema.rb
|
267
267
|
- lib/sequent.rb
|
268
|
+
- lib/sequent/application_record.rb
|
268
269
|
- lib/sequent/configuration.rb
|
269
270
|
- lib/sequent/core/aggregate_repository.rb
|
270
271
|
- lib/sequent/core/aggregate_root.rb
|
@@ -344,9 +345,13 @@ files:
|
|
344
345
|
- lib/sequent/generator/template_project/spec/app/projectors/post_projector_spec.rb
|
345
346
|
- lib/sequent/generator/template_project/spec/lib/post/post_command_handler_spec.rb
|
346
347
|
- lib/sequent/generator/template_project/spec/spec_helper.rb
|
348
|
+
- lib/sequent/migrations/executor.rb
|
349
|
+
- lib/sequent/migrations/functions.rb
|
347
350
|
- lib/sequent/migrations/migrate_events.rb
|
348
351
|
- lib/sequent/migrations/migrations.rb
|
352
|
+
- lib/sequent/migrations/planner.rb
|
349
353
|
- lib/sequent/migrations/projectors.rb
|
354
|
+
- lib/sequent/migrations/sql.rb
|
350
355
|
- lib/sequent/migrations/view_schema.rb
|
351
356
|
- lib/sequent/rake/migration_tasks.rb
|
352
357
|
- lib/sequent/rake/tasks.rb
|
@@ -377,7 +382,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
377
382
|
requirements:
|
378
383
|
- - ">="
|
379
384
|
- !ruby/object:Gem::Version
|
380
|
-
version: '
|
385
|
+
version: '2.5'
|
381
386
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
382
387
|
requirements:
|
383
388
|
- - ">="
|