mysql_framework 0.0.4 → 0.0.5

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: 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