sequent 3.2.2 → 3.3.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/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
|
- - ">="
|