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 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
  - - ">="