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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 10b5839af491a1fc854e512051185f6141b6bff465d4aea09fe7c6f20940ba4e
4
- data.tar.gz: 32bccd0cc5b44a760e544a48b3be9578abcc9f9de7fbd083ca9ed6f38c5e5932
3
+ metadata.gz: 0b1fb847d194b47667729bdcf1e9ec4f8c78214f23ff71ee7ce37f1c225dc952
4
+ data.tar.gz: 23080b89717f7f2ebb30db682295cf0c598b120b84f3aec7ae372d193d937feb
5
5
  SHA512:
6
- metadata.gz: 6c45431a49ad4100a7ad0ebe812aecb75144a9bf847823268a98b85c96689948e83fcfa76fd1d0c66dea5dabe923d41ab820ceefeb07cf2605d2b98296ebe7d6
7
- data.tar.gz: 3198cc99cf9bfa8ba955aecc4fd85fd7559f848a83c64c77d7a7092fafeb7c2aadfec4e31f587c929304537e306e2f5cebfcbbd780c14700c9b384daeaede68e
6
+ metadata.gz: 33403d55a2d2bb8afd4892e952dcfe26aaa5ae9d4d25fd49bbca48f12fd5b4b077fba819450856b6f1f7b3587f0462c652b8679749bde380dcf10b53c2167cd8
7
+ data.tar.gz: baf1dc0def17b1a90a6895dd94dfb9777f6c6a1f4292e9f44c956bc1a7b487d86687a6cbf06b5ac84c193c302af134871f813bae2e5476c491246f8103c3b3a1
@@ -0,0 +1,7 @@
1
+ require 'active_record'
2
+
3
+ module Sequent
4
+ class ApplicationRecord < ActiveRecord::Base
5
+ self.abstract_class = true
6
+ end
7
+ end
@@ -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 < ActiveRecord::Base
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 < ActiveRecord::Base
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
- end
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
@@ -104,7 +104,7 @@ module Sequent
104
104
  end
105
105
 
106
106
  def execute_sql(statement)
107
- ActiveRecord::Base.connection.execute(statement)
107
+ Sequent::ApplicationRecord.connection.execute(statement)
108
108
  end
109
109
 
110
110
  def commit
@@ -307,7 +307,7 @@ module Sequent
307
307
  end
308
308
 
309
309
  buf = ''
310
- conn = ActiveRecord::Base.connection.raw_connection
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
- ActiveRecord::Base.connection.type_cast(record[column_name.to_sym], @column_cache[clazz.name][column_name])
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
@@ -13,7 +13,7 @@ module Sequent
13
13
  end
14
14
  end
15
15
 
16
- class StreamRecord < ActiveRecord::Base
16
+ class StreamRecord < Sequent::ApplicationRecord
17
17
 
18
18
  self.table_name = "stream_records"
19
19
 
@@ -4,7 +4,7 @@ module Sequent
4
4
 
5
5
  class ActiveRecordTransactionProvider
6
6
  def transactional
7
- ActiveRecord::Base.transaction(requires_new: true) do
7
+ Sequent::ApplicationRecord.transaction(requires_new: true) do
8
8
  yield
9
9
  end
10
10
  after_commit_queue.each &:call
@@ -1,2 +1,2 @@
1
- class PostRecord < ActiveRecord::Base
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
@@ -7,3 +7,4 @@ end
7
7
  require_relative 'migrate_events'
8
8
  require_relative 'projectors'
9
9
  require_relative 'view_schema'
10
+ require_relative 'functions'
@@ -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::Projectors class that extends this class and implements this method"
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::Projectors class that extends this class and implements this method"
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.projectors_between(old, new)
13
- versions.values_at(*Range.new(old + 1, new).to_a.map(&:to_s)).compact.flatten.uniq
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
- # Responsible for migration of Projectors between view schema versions.
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
- # A Projector needs migration when for instance:
21
+ # The following migrations are supported:
19
22
  #
20
- # - New columns are added
21
- # - Structure is changed
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 Projectors you want to migrate
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 < ActiveRecord::Base; end
47
- class ReplayedIds < ActiveRecord::Base; end
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
- for_each_table_to_migrate do |table|
122
- statements = sql_file_to_statements("#{Sequent.configuration.migration_sql_files_directory}/#{table.table_name}.sql") { |raw_sql| raw_sql.gsub('%SUFFIX%', "_#{Sequent.new_version}") }
123
- statements.each { |statement| exec_sql(statement) }
124
- table.table_name = "#{table.table_name}_#{Sequent.new_version}"
125
- table.reset_column_information
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!(projectors_to_migrate, Sequent.configuration.offline_replay_persistor_class.new, exclude_ids: true, group_exponent: 1)
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
- ActiveRecord::Base.transaction do
165
- for_each_table_to_migrate do |table|
166
- current_table_name = table.table_name.gsub("_#{Sequent.new_version}", "")
167
- # 2 Rename old table
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
- # 5. Truncate replayed ids
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!(projectors, replay_persistor, exclude_ids: false, group_exponent: 3)
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 = ActiveRecord::Base.connection.execute("DELETE FROM #{Sequent.configuration.event_record_class.table_name} WHERE event_type = 'Sequent::Core::SnapshotEvent'")
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
@@ -57,7 +57,7 @@ module Sequent
57
57
 
58
58
  task :establish_connection do
59
59
  env_db = db_config(options.fetch(:environment))
60
- Sequent::Support::Database.establish_connection(env_db)
60
+ ActiveRecord::Base.establish_connection(env_db)
61
61
  end
62
62
 
63
63
  desc 'Migrate the database'
@@ -7,6 +7,7 @@ require_relative 'core/projector'
7
7
  require_relative 'core/workflow'
8
8
  require_relative 'core/value_object'
9
9
  require_relative 'generator'
10
+ require_relative 'migrations/migrations'
10
11
 
11
12
  module Sequent
12
13
  def self.new_uuid
@@ -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
@@ -1,3 +1,4 @@
1
+ require_relative 'sequent/application_record'
1
2
  require_relative 'sequent/sequent'
2
3
  require_relative 'sequent/core/core'
3
4
  require_relative 'sequent/util/util'
data/lib/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Sequent
2
- VERSION = '3.2.2'
2
+ VERSION = '3.3.0'
3
3
  end
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.2.2
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-06-04 00:00:00.000000000 Z
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: '0'
385
+ version: '2.5'
381
386
  required_rubygems_version: !ruby/object:Gem::Requirement
382
387
  requirements:
383
388
  - - ">="