mysql_framework 0.0.4 → 0.0.5

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: a74ebb1d0b4fb35020acec587f1eeacffa3304b5c1dc5fc79d3029c67a729e49
4
- data.tar.gz: 2d0bc5074297085431f4def95bd8273c200eef9ecf3b4ddf26d7b936640b2978
3
+ metadata.gz: efd0a31b4662bc56a6c491b52d176f7400d26b848c7580593dd3aec47406754e
4
+ data.tar.gz: 3f76b0e9586b56d5c0293590decdbd2240831ff03f5015c159b440d10b25b59b
5
5
  SHA512:
6
- metadata.gz: b954371d4415354b0c48624109c38107d8d132f3ecf104a94afa8503f04bedae5f89e034d87dd2d8632a31a7208f6a8a551ee59fb7fecc5035b17d09c39de21d
7
- data.tar.gz: dca66fff5384d6e9fb324fc271fdc9c546d0f2bf68a7347314f1a22e68cda22505c4cd4758b2775bd2aaf1c6b054daebb74929ba9b71b05b3c0a6651d76e0c52
6
+ metadata.gz: b84eb01ebca2b310102b0dc84b4242d464349ac55c9f4f3b9133a6c06a16b73900c71de2b663e77c28553b9ad38b897036fc72344b2bbb969ab71e983dbd0595
7
+ data.tar.gz: 9be88e92dd00c7b12c6e936b93767badef60c3582599c2c093751f40dd5ba5f02c2e94170bb2cd17c8f8f799be8ebef68ffa23526df818c7b81778ad7d4fc051
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'mysql2'
2
4
  require 'redlock'
3
5
 
@@ -11,5 +13,4 @@ require_relative 'mysql_framework/sql_table'
11
13
  require_relative 'mysql_framework/version'
12
14
 
13
15
  module MysqlFramework
14
-
15
16
  end
@@ -3,19 +3,50 @@
3
3
  module MysqlFramework
4
4
  class Connector
5
5
  def initialize(options = {})
6
- @connection_pool = ::Queue.new
7
-
8
6
  @options = default_options.merge(options)
9
7
 
10
8
  Mysql2::Client.default_query_options.merge!(symbolize_keys: true, cast_booleans: true)
11
9
  end
12
10
 
13
- # This method is called to fetch a client from the connection pool or create a new client if no idle clients
14
- # are available.
11
+ # This method is called to setup a pool of MySQL connections.
12
+ def setup
13
+ @connection_pool = ::Queue.new
14
+
15
+ start_pool_size.times { @connection_pool.push(Mysql2::Client.new(@options)) }
16
+
17
+ @created_connections = start_pool_size
18
+ end
19
+
20
+ # This method is called to close all MySQL connections in the pool and dispose of the pool itself.
21
+ def dispose
22
+ return if @connection_pool.nil?
23
+
24
+ until @connection_pool.empty?
25
+ conn = @connection_pool.pop(true)
26
+ conn.close
27
+ end
28
+
29
+ @connection_pool = nil
30
+ end
31
+
32
+ # This method is called to get the idle connection queue for this connector.
33
+ def connections
34
+ @connection_pool
35
+ end
36
+
37
+ # This method is called to fetch a client from the connection pool.
15
38
  def check_out
16
39
  @connection_pool.pop(true)
17
- rescue StandardError
18
- Mysql2::Client.new(@options)
40
+ rescue ThreadError
41
+ if @created_connections < max_pool_size
42
+ conn = Mysql2::Client.new(@options)
43
+ @created_connections += 1
44
+ return conn
45
+ end
46
+
47
+ MysqlFramework.logger.error { "[#{self.class}] - Database connection pool depleted." }
48
+
49
+ raise 'Database connection pool depleted.'
19
50
  end
20
51
 
21
52
  # This method is called to check a client back in to the connection when no longer needed.
@@ -70,6 +101,8 @@ module MysqlFramework
70
101
  end
71
102
  end
72
103
 
104
+ private
105
+
73
106
  def default_options
74
107
  {
75
108
  host: ENV.fetch('MYSQL_HOST'),
@@ -80,5 +113,13 @@ module MysqlFramework
80
113
  reconnect: true
81
114
  }
82
115
  end
116
+
117
+ def start_pool_size
118
+ @start_pool_size ||= Integer(ENV.fetch('MYSQL_START_POOL_SIZE', 1))
119
+ end
120
+
121
+ def max_pool_size
122
+ @max_pool_size ||= Integer(ENV.fetch('MYSQL_MAX_POOL_SIZE', 5))
123
+ end
83
124
  end
84
125
  end
@@ -4,12 +4,12 @@ require 'logger'
4
4
 
5
5
  module MysqlFramework
6
6
  def self.logger
7
- return @@logger
7
+ @@logger
8
8
  end
9
9
 
10
- def self.set_logger(logger)
10
+ def self.logger=(logger)
11
11
  @@logger = logger
12
12
  end
13
13
 
14
- MysqlFramework.set_logger(Logger.new(STDOUT))
14
+ MysqlFramework.logger = Logger.new(STDOUT)
15
15
  end
@@ -3,31 +3,19 @@
3
3
  module MysqlFramework
4
4
  module Scripts
5
5
  class Base
6
- def partitions
7
- ENV.fetch('MYSQL_PARTITIONS', '500').to_i
8
- end
9
-
10
- def database_name
11
- @database_name ||= ENV.fetch('MYSQL_DATABASE')
12
- end
13
-
14
6
  def identifier
15
7
  raise NotImplementedError if @identifier.nil?
16
8
  @identifier
17
9
  end
18
10
 
19
- def apply
11
+ def apply(_client)
20
12
  raise NotImplementedError
21
13
  end
22
14
 
23
- def rollback
15
+ def rollback(_client)
24
16
  raise NotImplementedError
25
17
  end
26
18
 
27
- def generate_partition_sql
28
- (1..partitions).each_with_index.map { |_, i| "PARTITION p#{i} VALUES IN (#{i})" }.join(",\n\t")
29
- end
30
-
31
19
  def self.descendants
32
20
  ObjectSpace.each_object(Class).select { |klass| klass < self }
33
21
  end
@@ -36,22 +24,26 @@ module MysqlFramework
36
24
  []
37
25
  end
38
26
 
39
- def update_procedure(proc_name, proc_file)
40
- mysql_connector.transaction do
41
- mysql_connector.query(<<~SQL)
42
- DROP PROCEDURE IF EXISTS #{proc_name};
43
- SQL
27
+ def update_procedure(client, proc_name, proc_file)
28
+ client.query(<<~SQL)
29
+ DROP PROCEDURE IF EXISTS #{proc_name};
30
+ SQL
44
31
 
45
- proc_sql = File.read(proc_file)
32
+ proc_sql = File.read(proc_file)
46
33
 
47
- mysql_connector.query(proc_sql)
48
- end
34
+ client.query(proc_sql)
35
+ end
36
+
37
+ protected
38
+
39
+ def generate_partition_sql
40
+ (1..partitions).each_with_index.map { |_, i| "PARTITION p#{i} VALUES IN (#{i})" }.join(",\n\t")
49
41
  end
50
42
 
51
43
  private
52
44
 
53
- def mysql_connector
54
- @mysql_connector ||= MysqlFramework::Connector.new
45
+ def partitions
46
+ @partitions ||= Integer(ENV.fetch('MYSQL_PARTITIONS', '500'))
55
47
  end
56
48
  end
57
49
  end
@@ -3,40 +3,48 @@
3
3
  module MysqlFramework
4
4
  module Scripts
5
5
  class Manager
6
+ def initialize(mysql_connector)
7
+ @mysql_connector = mysql_connector
8
+ end
9
+
6
10
  def execute
7
- lock_manager.lock(self.class, 2000) do |locked|
11
+ lock_manager.lock(self.class, migration_ttl) do |locked|
8
12
  raise unless locked
9
13
 
10
14
  initialize_script_history
11
15
 
12
16
  last_executed_script = retrieve_last_executed_script
13
17
 
14
- mysql_connector.transaction do
18
+ mysql_connector.transaction do |client|
15
19
  pending_scripts = calculate_pending_scripts(last_executed_script)
16
- MysqlFramework.logger.info { "[#{self.class}] - #{pending_scripts.length} pending data store scripts found." }
20
+ MysqlFramework.logger.info do
21
+ "[#{self.class}] - #{pending_scripts.length} pending data store scripts found."
22
+ end
17
23
 
18
- pending_scripts.each { |script| apply(script) }
24
+ pending_scripts.each { |script| apply(script, client) }
19
25
  end
20
26
 
21
- MysqlFramework.logger.info { "[#{self.class}] - Migration script execution complete." }
27
+ MysqlFramework.logger.debug { "[#{self.class}] - Migration script execution complete." }
22
28
  end
23
29
  end
24
30
 
25
31
  def apply_by_tag(tags)
26
- lock_manager.lock(self.class, 2000) do |locked|
32
+ lock_manager.lock(self.class, migration_ttl) do |locked|
27
33
  raise unless locked
28
34
 
29
35
  initialize_script_history
30
36
 
31
- mysql_connector.transaction do
37
+ mysql_connector.transaction do |client|
32
38
  pending_scripts = calculate_pending_scripts(0)
33
- MysqlFramework.logger.info { "[#{self.class}] - #{pending_scripts.length} pending data store scripts found." }
39
+ MysqlFramework.logger.info do
40
+ "[#{self.class}] - #{pending_scripts.length} pending data store scripts found."
41
+ end
34
42
 
35
43
  pending_scripts.reject { |script| (script.tags & tags).empty? }.sort_by(&:identifier)
36
- .each { |script| apply(script) }
44
+ .each { |script| apply(script, client) }
37
45
  end
38
46
 
39
- MysqlFramework.logger.info { "[#{self.class}] - Migration script execution complete." }
47
+ MysqlFramework.logger.debug { "[#{self.class}] - Migration script execution complete." }
40
48
  end
41
49
  end
42
50
 
@@ -46,10 +54,10 @@ module MysqlFramework
46
54
  end
47
55
 
48
56
  def retrieve_last_executed_script
49
- MysqlFramework.logger.info { "[#{self.class}] - Retrieving last executed script from history." }
57
+ MysqlFramework.logger.debug { "[#{self.class}] - Retrieving last executed script from history." }
50
58
 
51
59
  result = mysql_connector.query(<<~SQL)
52
- SELECT `identifier` FROM #{migration_table_name} ORDER BY `identifier` DESC
60
+ SELECT `identifier` FROM `#{migration_table_name}` ORDER BY `identifier` DESC
53
61
  SQL
54
62
 
55
63
  if result.each.to_a.length.zero?
@@ -60,10 +68,10 @@ module MysqlFramework
60
68
  end
61
69
 
62
70
  def initialize_script_history
63
- MysqlFramework.logger.info { "[#{self.class}] - Initializing script history." }
71
+ MysqlFramework.logger.debug { "[#{self.class}] - Initializing script history." }
64
72
 
65
73
  mysql_connector.query(<<~SQL)
66
- CREATE TABLE IF NOT EXISTS #{migration_table_name} (
74
+ CREATE TABLE IF NOT EXISTS `#{migration_table_name}` (
67
75
  `identifier` CHAR(15) NOT NULL,
68
76
  `timestamp` DATETIME NULL DEFAULT CURRENT_TIMESTAMP,
69
77
  PRIMARY KEY (`identifier`),
@@ -73,10 +81,9 @@ module MysqlFramework
73
81
  end
74
82
 
75
83
  def calculate_pending_scripts(last_executed_script)
76
- MysqlFramework.logger.info { "[#{self.class}] - Calculating pending data store scripts." }
84
+ MysqlFramework.logger.debug { "[#{self.class}] - Calculating pending data store scripts." }
77
85
 
78
- MysqlFramework::Scripts::Base.descendants.map(&:new)
79
- .select { |script| script.identifier > last_executed_script }.sort_by(&:identifier)
86
+ migrations.map(&:new).select { |script| script.identifier > last_executed_script }.sort_by(&:identifier)
80
87
  end
81
88
 
82
89
  def table_exists?(table_name)
@@ -92,7 +99,7 @@ module MysqlFramework
92
99
 
93
100
  def drop_table(table_name)
94
101
  mysql_connector.query(<<~SQL)
95
- DROP TABLE IF EXISTS #{table_name}
102
+ DROP TABLE IF EXISTS `#{table_name}`
96
103
  SQL
97
104
  end
98
105
 
@@ -106,30 +113,30 @@ module MysqlFramework
106
113
 
107
114
  private
108
115
 
109
- def mysql_connector
110
- @mysql_connector ||= MysqlFramework::Connector.new
111
- end
116
+ attr_reader :mysql_connector
112
117
 
113
118
  def lock_manager
114
119
  @lock_manager ||= Redlock::Client.new([ENV.fetch('REDIS_URL')])
115
120
  end
116
121
 
117
- def database
118
- @database ||= ENV.fetch('MYSQL_DATABASE')
122
+ def migration_ttl
123
+ @migration_ttl ||= ENV.fetch('MYSQL_MIGRATION_LOCK_TTL', 2000)
119
124
  end
120
125
 
121
126
  def migration_table_name
122
- return @migration_table_name if @migration_table_name
127
+ @migration_table_name ||= ENV.fetch('MYSQL_MIGRATION_TABLE', 'migration_script_history')
128
+ end
123
129
 
124
- @migration_table_name = "`#{database}`.`migration_script_history`"
130
+ def migrations
131
+ @migrations ||= MysqlFramework::Scripts::Base.descendants
125
132
  end
126
133
 
127
- def apply(script)
134
+ def apply(script, client)
128
135
  MysqlFramework.logger.info { "[#{self.class}] - Applying script: #{script}." }
129
136
 
130
- script.apply
131
- mysql_connector.query(<<~SQL)
132
- INSERT INTO #{migration_table_name} (`identifier`, `timestamp`) VALUES ('#{script.identifier}', NOW())
137
+ script.apply(client)
138
+ client.query(<<~SQL)
139
+ INSERT INTO `#{migration_table_name}` (`identifier`, `timestamp`) VALUES ('#{script.identifier}', NOW())
133
140
  SQL
134
141
  end
135
142
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MysqlFramework
4
- VERSION = '0.0.4'
4
+ VERSION = '0.0.5'
5
5
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'spec_helper'
4
-
5
3
  describe MysqlFramework::Connector do
4
+ let(:start_pool_size) { Integer(ENV.fetch('MYSQL_START_POOL_SIZE')) }
5
+ let(:max_pool_size) { Integer(ENV.fetch('MYSQL_MAX_POOL_SIZE')) }
6
6
  let(:default_options) do
7
7
  {
8
8
  host: ENV.fetch('MYSQL_HOST'),
@@ -15,26 +15,28 @@ describe MysqlFramework::Connector do
15
15
  end
16
16
  let(:options) do
17
17
  {
18
- host: 'host',
19
- port: 'port',
20
- database: 'database',
21
- username: 'username',
22
- password: 'password',
23
- reconnect: true
18
+ host: ENV.fetch('MYSQL_HOST'),
19
+ port: ENV.fetch('MYSQL_PORT'),
20
+ database: "#{ENV.fetch('MYSQL_DATABASE')}_2",
21
+ username: ENV.fetch('MYSQL_USERNAME'),
22
+ password: ENV.fetch('MYSQL_PASSWORD'),
23
+ reconnect: false
24
24
  }
25
25
  end
26
- let(:client) { double }
26
+ let(:client) { double(close: true) }
27
27
  let(:gems) { MysqlFramework::SqlTable.new('gems') }
28
28
  let(:existing_client) { Mysql2::Client.new(default_options) }
29
29
 
30
30
  subject { described_class.new }
31
31
 
32
- describe '#initialize' do
33
- it 'sets default query options on the Mysql2 client' do
34
- subject
32
+ before(:each) { subject.setup }
33
+ after(:each) { subject.dispose }
35
34
 
36
- expect(Mysql2::Client.default_query_options[:symbolize_keys]).to eq(true)
37
- expect(Mysql2::Client.default_query_options[:cast_booleans]).to eq(true)
35
+ describe '#initialize' do
36
+ context 'when options are not provided' do
37
+ it 'returns the default options' do
38
+ expect(subject.instance_variable_get(:@options)).to eq(default_options)
39
+ end
38
40
  end
39
41
 
40
42
  context 'when options are provided' do
@@ -44,26 +46,82 @@ describe MysqlFramework::Connector do
44
46
  expect(subject.instance_variable_get(:@options)).to eq(options)
45
47
  end
46
48
  end
49
+
50
+ it 'sets default query options on the Mysql2 client' do
51
+ subject
52
+
53
+ expect(Mysql2::Client.default_query_options[:symbolize_keys]).to eq(true)
54
+ expect(Mysql2::Client.default_query_options[:cast_booleans]).to eq(true)
55
+ end
47
56
  end
48
57
 
49
- describe '#check_out' do
50
- it 'returns a Mysql2::Client instance from the pool' do
51
- expect(Mysql2::Client).to receive(:new).with(default_options).and_return(client)
52
- expect(subject.check_out).to eq(client)
58
+ describe '#setup' do
59
+ it 'creates a connection pool with the specified number of conections' do
60
+ subject.setup
61
+
62
+ expect(subject.connections.length).to eq(start_pool_size)
63
+ end
64
+ end
65
+
66
+ describe '#dispose' do
67
+ before do
68
+ subject.connections.clear
69
+ subject.connections.push(client)
53
70
  end
54
71
 
55
- context 'when the connection pool has a client available' do
72
+ it 'closes the idle connections and disposes of the queue' do
73
+ expect(client).to receive(:close)
74
+
75
+ subject.dispose
76
+
77
+ expect(subject.connections).to be_nil
78
+ end
79
+ end
80
+
81
+ describe '#check_out' do
82
+ context 'when there are available connections' do
83
+ before do
84
+ subject.connections.clear
85
+ subject.connections.push(client)
86
+ end
87
+
56
88
  it 'returns a client instance from the pool' do
57
- subject.instance_variable_get(:@connection_pool).push(client)
89
+ expect(subject.check_out).to eq(client)
90
+ end
91
+ end
92
+
93
+ context "when there are no available connections, and the pool's max size has not been reached" do
94
+ before do
95
+ subject.connections.clear
96
+ subject.connections.push(client)
97
+ end
98
+
99
+ it 'instantiates a new connection and returns it' do
100
+ subject.check_out
58
101
 
102
+ expect(Mysql2::Client).to receive(:new).with(default_options).and_return(client)
59
103
  expect(subject.check_out).to eq(client)
60
104
  end
61
105
  end
106
+
107
+ context "when there are no available connections, and the pool's max size has been reached" do
108
+ before do
109
+ subject.connections.clear
110
+ subject.instance_variable_set(:@created_connections, 5)
111
+
112
+ 5.times { subject.check_in(client) }
113
+ 5.times { subject.check_out }
114
+ end
115
+
116
+ it 'throws a RuntimeError' do
117
+ expect { subject.check_out }.to raise_error(RuntimeError)
118
+ end
119
+ end
62
120
  end
63
121
 
64
122
  describe '#check_in' do
65
123
  it 'returns the provided client to the connection pool' do
66
- expect(subject.instance_variable_get(:@connection_pool)).to receive(:push).with(client)
124
+ expect(subject.connections).to receive(:push).with(client)
67
125
 
68
126
  subject.check_in(client)
69
127
  end
@@ -84,20 +142,8 @@ describe MysqlFramework::Connector do
84
142
  describe '#execute' do
85
143
  let(:insert_query) do
86
144
  MysqlFramework::SqlQuery.new.insert(gems)
87
- .into(
88
- gems[:id],
89
- gems[:name],
90
- gems[:author],
91
- gems[:created_at],
92
- gems[:updated_at]
93
- )
94
- .values(
95
- SecureRandom.uuid,
96
- 'mysql_framework',
97
- 'sage',
98
- Time.now,
99
- Time.now
100
- )
145
+ .into(gems[:id], gems[:name], gems[:author], gems[:created_at], gems[:updated_at])
146
+ .values(SecureRandom.uuid, 'mysql_framework', 'sage', Time.now, Time.now)
101
147
  end
102
148
 
103
149
  it 'executes the query with parameters' do
@@ -139,43 +185,14 @@ describe MysqlFramework::Connector do
139
185
  end
140
186
 
141
187
  describe '#query_multiple_results' do
142
- let(:test) { MysqlFramework::SqlTable.new('test') }
143
- let(:manager) { MysqlFramework::Scripts::Manager.new }
144
- let(:connector) { MysqlFramework::Connector.new }
145
- let(:timestamp) { Time.at(628_232_400) } # 1989-11-28 00:00:00 -0500
146
- let(:guid) { 'a3ccb138-48ae-437a-be52-f673beb12b51' }
147
- let(:insert) do
148
- MysqlFramework::SqlQuery.new.insert(test)
149
- .into(test[:id], test[:name], test[:action], test[:created_at], test[:updated_at])
150
- .values(guid, 'name', 'action', timestamp, timestamp)
151
- end
152
- let(:obj) do
153
- {
154
- id: guid,
155
- name: 'name',
156
- action: 'action',
157
- created_at: timestamp,
158
- updated_at: timestamp
159
- }
160
- end
161
-
162
- before :each do
163
- manager.initialize_script_history
164
- manager.execute
165
-
166
- connector.execute(insert)
167
- end
168
-
169
- after(:each) { manager.drop_all_tables }
170
-
171
188
  it 'returns the results from the stored procedure' do
172
189
  query = 'call test_procedure'
173
190
  result = subject.query_multiple_results(query)
174
191
 
175
192
  expect(result).to be_a(Array)
176
193
  expect(result.length).to eq(2)
177
- expect(result[0]).to eq([])
178
- expect(result[1]).to eq([obj])
194
+ expect(result[0].length).to eq(0)
195
+ expect(result[1].length).to eq(4)
179
196
  end
180
197
 
181
198
  it 'does not check out a new client when one is provided' do
@@ -186,8 +203,8 @@ describe MysqlFramework::Connector do
186
203
 
187
204
  expect(result).to be_a(Array)
188
205
  expect(result.length).to eq(2)
189
- expect(result[0]).to eq([])
190
- expect(result[1]).to eq([obj])
206
+ expect(result[0].length).to eq(0)
207
+ expect(result[1].length).to eq(4)
191
208
  end
192
209
  end
193
210
 
@@ -208,18 +225,11 @@ describe MysqlFramework::Connector do
208
225
  expect(client).to receive(:query).with('ROLLBACK')
209
226
 
210
227
  begin
211
- subject.transaction do
212
- raise
213
- end
214
- rescue StandardError
228
+ subject.transaction { raise }
229
+ rescue StandardError => e
230
+ e.message
215
231
  end
216
232
  end
217
233
  end
218
234
  end
219
-
220
- describe '#default_options' do
221
- it 'returns the default options' do
222
- expect(subject.default_options).to eq(default_options)
223
- end
224
- end
225
235
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'spec_helper'
4
-
5
3
  describe MysqlFramework do
6
4
  describe 'logger' do
7
5
  it 'returns the logger' do
@@ -9,11 +7,11 @@ describe MysqlFramework do
9
7
  end
10
8
  end
11
9
 
12
- describe 'set_logger' do
10
+ describe 'logger=' do
13
11
  let(:logger) { Logger.new(STDOUT) }
14
12
 
15
13
  it 'sets the logger' do
16
- subject.set_logger(logger)
14
+ subject.logger = logger
17
15
  expect(subject.logger).to eq(logger)
18
16
  end
19
17
  end
@@ -1,25 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'spec_helper'
4
-
5
3
  describe MysqlFramework::Scripts::Base do
6
- subject { described_class.new }
4
+ let(:client) { double }
7
5
 
8
- describe '#partitions' do
9
- it 'returns the number of paritions' do
10
- expect(subject.partitions).to eq(5)
11
- end
12
- end
13
-
14
- describe '#database_name' do
15
- it 'returns the database name' do
16
- expect(subject.database_name).to eq('test_database')
17
- end
18
- end
6
+ subject { described_class.new }
19
7
 
20
8
  describe '#identifier' do
21
9
  it 'throws a NotImplementedError' do
22
- expect{ subject.identifier }.to raise_error(NotImplementedError)
10
+ expect { subject.identifier }.to raise_error(NotImplementedError)
23
11
  end
24
12
 
25
13
  context 'when @identifier is set' do
@@ -32,29 +20,24 @@ describe MysqlFramework::Scripts::Base do
32
20
 
33
21
  describe '#apply' do
34
22
  it 'throws a NotImplementedError' do
35
- expect{ subject.apply }.to raise_error(NotImplementedError)
23
+ expect { subject.apply(client) }.to raise_error(NotImplementedError)
36
24
  end
37
25
  end
38
26
 
39
27
  describe '#rollback' do
40
28
  it 'throws a NotImplementedError' do
41
- expect{ subject.rollback }.to raise_error(NotImplementedError)
42
- end
43
- end
44
-
45
- describe '#generate_partition_sql' do
46
- it 'generates the partition sql statement' do
47
- expected = "PARTITION p0 VALUES IN (0),\n\tPARTITION p1 VALUES IN (1),\n\tPARTITION p2 VALUES IN (2),\n\tPARTITION p3 VALUES IN (3),\n\tPARTITION p4 VALUES IN (4)"
48
- expect(subject.generate_partition_sql).to eq(expected)
29
+ expect { subject.rollback(client) }.to raise_error(NotImplementedError)
49
30
  end
50
31
  end
51
32
 
52
33
  describe '.descendants' do
53
34
  it 'returns all descendant classes' do
54
35
  expect(described_class.descendants.length).to eq(3)
55
- expect(described_class.descendants).to include(MysqlFramework::Support::Scripts::CreateTestTable,
56
- MysqlFramework::Support::Scripts::CreateDemoTable,
57
- MysqlFramework::Support::Scripts::CreateTestProc)
36
+ expect(described_class.descendants).to include(
37
+ MysqlFramework::Support::Scripts::CreateTestTable,
38
+ MysqlFramework::Support::Scripts::CreateDemoTable,
39
+ MysqlFramework::Support::Scripts::CreateTestProc
40
+ )
58
41
  end
59
42
  end
60
43
 
@@ -65,7 +48,6 @@ describe MysqlFramework::Scripts::Base do
65
48
  end
66
49
 
67
50
  describe '#update_procedure' do
68
- let(:connector) { MysqlFramework::Connector.new }
69
51
  let(:proc_file_path) { 'spec/support/procedure.sql' }
70
52
  let(:drop_sql) do
71
53
  <<~SQL
@@ -73,19 +55,11 @@ describe MysqlFramework::Scripts::Base do
73
55
  SQL
74
56
  end
75
57
 
76
- before :each do
77
- subject.instance_variable_set(:@mysql_connector, connector)
78
- end
79
-
80
58
  it 'drops and then creates the named procedure' do
81
- expect(connector).to receive(:query).with(drop_sql).once
82
- expect(connector).to receive(:query).with(File.read(proc_file_path)).once
83
- subject.update_procedure('test_procedure', proc_file_path)
84
- end
59
+ expect(client).to receive(:query).with(drop_sql).once
60
+ expect(client).to receive(:query).with(File.read(proc_file_path)).once
85
61
 
86
- it 'wraps the call in a transaction' do
87
- expect(connector).to receive(:transaction)
88
- subject.update_procedure('test_procedure', proc_file_path)
62
+ subject.update_procedure(client, 'test_procedure', proc_file_path)
89
63
  end
90
64
  end
91
65
  end
@@ -1,18 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'spec_helper'
4
-
5
3
  describe MysqlFramework::Scripts::Manager do
6
- let(:connector) { MysqlFramework::Connector.new }
7
-
8
- before :each do
9
- subject.instance_variable_set(:@mysql_connector, connector)
4
+ let(:connector) do
5
+ connector = MysqlFramework::Connector.new
6
+ connector.setup
7
+ connector
10
8
  end
11
9
 
10
+ subject { described_class.new(connector) }
11
+
12
12
  describe '#execute' do
13
- before :each do
13
+ before(:each) do
14
+ subject.drop_all_tables
14
15
  subject.initialize_script_history
15
16
  end
17
+ after(:each) { subject.drop_all_tables }
16
18
 
17
19
  it 'executes all pending scripts' do
18
20
  expect(subject.table_exists?('demo')).to eq(false)
@@ -23,16 +25,14 @@ describe MysqlFramework::Scripts::Manager do
23
25
  expect(subject.table_exists?('demo')).to eq(true)
24
26
  expect(subject.table_exists?('test')).to eq(true)
25
27
  end
26
-
27
- after :each do
28
- subject.drop_all_tables
29
- end
30
28
  end
31
29
 
32
30
  describe '#apply_by_tag' do
33
- before :each do
31
+ before(:each) do
32
+ subject.drop_all_tables
34
33
  subject.initialize_script_history
35
34
  end
35
+ after(:each) { subject.drop_all_tables }
36
36
 
37
37
  it 'executes all pending scripts that match the tag' do
38
38
  expect(subject.table_exists?('demo')).to eq(false)
@@ -43,10 +43,6 @@ describe MysqlFramework::Scripts::Manager do
43
43
  expect(subject.table_exists?('demo')).to eq(false)
44
44
  expect(subject.table_exists?('test')).to eq(true)
45
45
  end
46
-
47
- after :each do
48
- subject.drop_all_tables
49
- end
50
46
  end
51
47
 
52
48
  describe '#drop_all_tables' do
@@ -60,9 +56,7 @@ describe MysqlFramework::Scripts::Manager do
60
56
  end
61
57
 
62
58
  describe '#retrieve_last_executed_script' do
63
- before :each do
64
- subject.initialize_script_history
65
- end
59
+ before(:each) { subject.initialize_script_history }
66
60
 
67
61
  context 'when no scripts have been executed' do
68
62
  it 'returns 0' do
@@ -71,18 +65,13 @@ describe MysqlFramework::Scripts::Manager do
71
65
  end
72
66
 
73
67
  context 'when scripts have been executed previously' do
74
- before :each do
75
- subject.apply_by_tag([MysqlFramework::Support::Tables::TestTable::NAME])
76
- end
68
+ before(:each) { subject.apply_by_tag([MysqlFramework::Support::Tables::TestTable::NAME]) }
69
+ after(:each) { subject.drop_script_history }
77
70
 
78
71
  it 'returns the last executed script' do
79
72
  expect(subject.retrieve_last_executed_script).to eq(201807031200)
80
73
  end
81
74
  end
82
-
83
- after :each do
84
- subject.drop_script_history
85
- end
86
75
  end
87
76
 
88
77
  describe '#initialize_script_history' do
@@ -131,7 +120,7 @@ describe MysqlFramework::Scripts::Manager do
131
120
  describe '#drop_script_history' do
132
121
  it 'drops the migration script history table' do
133
122
  query = <<~SQL
134
- DROP TABLE IF EXISTS `#{ENV.fetch('MYSQL_DATABASE')}`.`migration_script_history`
123
+ DROP TABLE IF EXISTS `#{ENV.fetch('MYSQL_MIGRATION_TABLE', 'migration_script_history')}`
135
124
  SQL
136
125
  expect(connector).to receive(:query).with(query)
137
126
  subject.drop_script_history
@@ -141,21 +130,21 @@ describe MysqlFramework::Scripts::Manager do
141
130
  describe '#drop_table' do
142
131
  it 'drops the given table' do
143
132
  expect(connector).to receive(:query).with(<<~SQL)
144
- DROP TABLE IF EXISTS `some_database`.`some_table`
133
+ DROP TABLE IF EXISTS `some_table`
145
134
  SQL
146
- subject.drop_table('`some_database`.`some_table`')
135
+ subject.drop_table('some_table')
147
136
  end
148
137
  end
149
138
 
150
139
  describe '#all_tables' do
151
140
  it 'returns all registered tables' do
152
- expect(subject.all_tables).to eq(['test', 'demo'])
141
+ expect(subject.all_tables).to eq(%w(test demo))
153
142
  end
154
143
  end
155
144
 
156
145
  describe '.all_tables' do
157
146
  it 'stores a class level array of tables' do
158
- expect(described_class.all_tables).to eq(['test', 'demo'])
147
+ expect(described_class.all_tables).to eq(%w(test demo))
159
148
  end
160
149
  end
161
150
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'spec_helper'
4
-
5
3
  describe MysqlFramework::SqlColumn do
6
4
  subject { described_class.new(table: 'gems', column: 'version') }
7
5
 
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'spec_helper'
4
-
5
3
  describe MysqlFramework::SqlCondition do
6
4
  subject { described_class.new(column: 'version', comparison: '=', value: '1.0.0') }
7
5
 
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'spec_helper'
4
-
5
3
  describe MysqlFramework::SqlQuery do
6
4
  let(:gems) { MysqlFramework::SqlTable.new('gems') }
7
5
  let(:versions) { MysqlFramework::SqlTable.new('versions') }
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'spec_helper'
4
-
5
3
  describe MysqlFramework::SqlTable do
6
4
  let(:table) { described_class.new('gems') }
7
5
 
data/spec/spec_helper.rb CHANGED
@@ -6,12 +6,18 @@ SimpleCov.start do
6
6
  add_filter 'spec/'
7
7
  end
8
8
 
9
- ENV['MYSQL_DATABASE'] ||= 'test_database'
10
- ENV['MYSQL_HOST'] ||= '127.0.0.1'
9
+ ENV['RACK_ENV'] = 'test'
10
+
11
+ ENV['MYSQL_START_POOL_SIZE'] ||= '1'
12
+ ENV['MYSQL_MAX_POOL_SIZE'] ||= '5'
11
13
  ENV['MYSQL_PARTITIONS'] ||= '5'
12
- ENV['MYSQL_PASSWORD'] ||= ''
14
+
15
+ ENV['MYSQL_HOST'] ||= '127.0.0.1'
13
16
  ENV['MYSQL_PORT'] ||= '3306'
17
+ ENV['MYSQL_DATABASE'] ||= 'test_database'
14
18
  ENV['MYSQL_USERNAME'] ||= 'root'
19
+ ENV['MYSQL_PASSWORD'] ||= ''
20
+
15
21
  ENV['REDIS_URL'] ||= 'redis://127.0.0.1:6379'
16
22
 
17
23
  require 'bundler'
@@ -23,10 +29,11 @@ require_relative 'support/scripts/create_demo_table'
23
29
  require_relative 'support/scripts/create_test_proc'
24
30
  require_relative 'support/tables/test'
25
31
  require_relative 'support/tables/demo'
32
+ require_relative 'support/fixtures'
26
33
 
27
- RSpec.configure do |config|
28
- config.before(:each) { MysqlFramework.logger.level = Logger::ERROR }
34
+ MysqlFramework::Support::Fixtures.execute
29
35
 
36
+ RSpec.configure do |config|
30
37
  config.expect_with :rspec do |expectations|
31
38
  expectations.include_chain_clauses_in_custom_matcher_descriptions = true
32
39
  end
@@ -35,25 +42,3 @@ RSpec.configure do |config|
35
42
  mocks.verify_partial_doubles = true
36
43
  end
37
44
  end
38
-
39
- client = Mysql2::Client.new(
40
- host: ENV.fetch('MYSQL_HOST'),
41
- port: ENV.fetch('MYSQL_PORT'),
42
- username: ENV.fetch('MYSQL_USERNAME'),
43
- password: ENV.fetch('MYSQL_PASSWORD')
44
- )
45
- client.query("DROP DATABASE IF EXISTS `#{ENV.fetch('MYSQL_DATABASE')}`;")
46
- client.query("CREATE DATABASE `#{ENV.fetch('MYSQL_DATABASE')}`;")
47
-
48
- connector = MysqlFramework::Connector.new
49
- connector.query("DROP TABLE IF EXISTS `#{ENV.fetch('MYSQL_DATABASE')}`.`gems`")
50
- connector.query(<<~SQL)
51
- CREATE TABLE `#{ENV.fetch('MYSQL_DATABASE')}`.`gems` (
52
- `id` CHAR(36) NOT NULL,
53
- `name` VARCHAR(255) NULL,
54
- `author` VARCHAR(255) NULL,
55
- `created_at` DATETIME,
56
- `updated_at` DATETIME,
57
- PRIMARY KEY (`id`)
58
- )
59
- SQL
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MysqlFramework
4
+ module Support
5
+ class Fixtures
6
+ def self.execute
7
+ connector = MysqlFramework::Connector.new(
8
+ host: ENV.fetch('MYSQL_HOST'),
9
+ port: ENV.fetch('MYSQL_PORT'),
10
+ database: nil,
11
+ username: ENV.fetch('MYSQL_USERNAME'),
12
+ password: ENV.fetch('MYSQL_PASSWORD')
13
+ )
14
+ connector.setup
15
+
16
+ client = connector.check_out
17
+
18
+ client.query("DROP DATABASE IF EXISTS `#{ENV.fetch('MYSQL_DATABASE')}`;")
19
+ client.query("DROP DATABASE IF EXISTS `#{ENV.fetch('MYSQL_DATABASE')}_2`;")
20
+ client.query("CREATE DATABASE `#{ENV.fetch('MYSQL_DATABASE')}`;")
21
+ client.query("CREATE DATABASE `#{ENV.fetch('MYSQL_DATABASE')}_2`;")
22
+ client.query("USE `#{ENV.fetch('MYSQL_DATABASE')}`;")
23
+ client.query(<<~SQL)
24
+ CREATE TABLE `gems` (
25
+ `id` CHAR(36) NOT NULL,
26
+ `name` VARCHAR(255) NULL,
27
+ `author` VARCHAR(255) NULL,
28
+ `created_at` DATETIME,
29
+ `updated_at` DATETIME,
30
+ PRIMARY KEY (`id`)
31
+ )
32
+ SQL
33
+ client.query(<<~SQL)
34
+ INSERT INTO `gems`
35
+ (`id`, `name`, `author`, `created_at`, `updated_at`)
36
+ VALUES
37
+ ('#{SecureRandom.uuid}', 'mysql_framework', 'Sage', NOW(), NOW()),
38
+ ('#{SecureRandom.uuid}', 'sinject', 'Sage', NOW(), NOW())
39
+ SQL
40
+
41
+ connector.check_in(client)
42
+
43
+ manager = MysqlFramework::Scripts::Manager.new(connector)
44
+ manager.execute
45
+
46
+ connector.dispose
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,5 +1,5 @@
1
1
  CREATE PROCEDURE `test_procedure`()
2
2
  BEGIN
3
3
  SELECT * FROM demo;
4
- SELECT * FROM test;
5
- END
4
+ SELECT * FROM gems;
5
+ END
@@ -8,25 +8,34 @@ module MysqlFramework
8
8
  @identifier = 201806021520 # 15:20 02/06/2018
9
9
  end
10
10
 
11
- def apply
12
- mysql_connector.query(<<~SQL)
13
- CREATE TABLE IF NOT EXISTS `#{database_name}`.`demo` (
11
+ def apply(client)
12
+ client.query(<<~SQL)
13
+ CREATE TABLE IF NOT EXISTS `#{table_name}` (
14
14
  `id` CHAR(36) NOT NULL,
15
15
  `name` VARCHAR(255) NULL,
16
16
  `created_at` DATETIME NOT NULL,
17
-
18
17
  `updated_at` DATETIME NOT NULL,
19
- PRIMARY KEY (`id`)
18
+ `partition` INT NOT NULL,
19
+ PRIMARY KEY (`id`, `partition`)
20
+ )
21
+ PARTITION BY LIST(`partition`) (
22
+ #{generate_partition_sql}
20
23
  )
21
24
  SQL
22
25
  end
23
26
 
24
- def rollback
27
+ def rollback(_client)
25
28
  raise 'Rollback not supported in test.'
26
29
  end
27
30
 
28
31
  def tags
29
- [MysqlFramework::Support::Tables::DemoTable::NAME]
32
+ [table_name]
33
+ end
34
+
35
+ private
36
+
37
+ def table_name
38
+ MysqlFramework::Support::Tables::DemoTable::NAME
30
39
  end
31
40
  end
32
41
  end
@@ -10,16 +10,22 @@ module MysqlFramework
10
10
 
11
11
  PROC_FILE = 'spec/support/procedure.sql'
12
12
 
13
- def apply
14
- update_procedure('test_procedure', PROC_FILE)
13
+ def apply(client)
14
+ update_procedure(client, 'test_procedure', PROC_FILE)
15
15
  end
16
16
 
17
- def rollback
17
+ def rollback(_client)
18
18
  raise 'Rollback not supported in test.'
19
19
  end
20
20
 
21
21
  def tags
22
- [MysqlFramework::Support::Tables::TestTable::NAME, 'TestProc']
22
+ [table_name, 'TestProc']
23
+ end
24
+
25
+ private
26
+
27
+ def table_name
28
+ MysqlFramework::Support::Tables::TestTable::NAME
23
29
  end
24
30
  end
25
31
  end
@@ -8,9 +8,9 @@ module MysqlFramework
8
8
  @identifier = 201801011030 # 10:30 01/01/2018
9
9
  end
10
10
 
11
- def apply
12
- mysql_connector.query(<<~SQL)
13
- CREATE TABLE IF NOT EXISTS `#{database_name}`.`test` (
11
+ def apply(client)
12
+ client.query(<<~SQL)
13
+ CREATE TABLE IF NOT EXISTS `#{table_name}` (
14
14
  `id` CHAR(36) NOT NULL,
15
15
  `name` VARCHAR(255) NULL,
16
16
  `action` VARCHAR(255) NULL,
@@ -21,12 +21,18 @@ module MysqlFramework
21
21
  SQL
22
22
  end
23
23
 
24
- def rollback
24
+ def rollback(_client)
25
25
  raise 'Rollback not supported in test.'
26
26
  end
27
27
 
28
28
  def tags
29
- [MysqlFramework::Support::Tables::TestTable::NAME]
29
+ [table_name]
30
+ end
31
+
32
+ private
33
+
34
+ def table_name
35
+ MysqlFramework::Support::Tables::TestTable::NAME
30
36
  end
31
37
  end
32
38
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mysql_framework
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sage
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-07-18 00:00:00.000000000 Z
11
+ date: 2018-07-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -124,6 +124,7 @@ files:
124
124
  - spec/lib/mysql_framework/sql_query_spec.rb
125
125
  - spec/lib/mysql_framework/sql_table_spec.rb
126
126
  - spec/spec_helper.rb
127
+ - spec/support/fixtures.rb
127
128
  - spec/support/procedure.sql
128
129
  - spec/support/scripts/create_demo_table.rb
129
130
  - spec/support/scripts/create_test_proc.rb