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 +4 -4
- data/lib/mysql_framework.rb +2 -1
- data/lib/mysql_framework/connector.rb +47 -6
- data/lib/mysql_framework/logger.rb +3 -3
- data/lib/mysql_framework/scripts/base.rb +16 -24
- data/lib/mysql_framework/scripts/manager.rb +36 -29
- data/lib/mysql_framework/version.rb +1 -1
- data/spec/lib/mysql_framework/connector_spec.rb +88 -78
- data/spec/lib/mysql_framework/logger_spec.rb +2 -4
- data/spec/lib/mysql_framework/scripts/base_spec.rb +13 -39
- data/spec/lib/mysql_framework/scripts/manager_spec.rb +20 -31
- data/spec/lib/mysql_framework/sql_column_spec.rb +0 -2
- data/spec/lib/mysql_framework/sql_condition_spec.rb +0 -2
- data/spec/lib/mysql_framework/sql_query_spec.rb +0 -2
- data/spec/lib/mysql_framework/sql_table_spec.rb +0 -2
- data/spec/spec_helper.rb +12 -27
- data/spec/support/fixtures.rb +50 -0
- data/spec/support/procedure.sql +2 -2
- data/spec/support/scripts/create_demo_table.rb +16 -7
- data/spec/support/scripts/create_test_proc.rb +10 -4
- data/spec/support/scripts/create_test_table.rb +11 -5
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: efd0a31b4662bc56a6c491b52d176f7400d26b848c7580593dd3aec47406754e
|
4
|
+
data.tar.gz: 3f76b0e9586b56d5c0293590decdbd2240831ff03f5015c159b440d10b25b59b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b84eb01ebca2b310102b0dc84b4242d464349ac55c9f4f3b9133a6c06a16b73900c71de2b663e77c28553b9ad38b897036fc72344b2bbb969ab71e983dbd0595
|
7
|
+
data.tar.gz: 9be88e92dd00c7b12c6e936b93767badef60c3582599c2c093751f40dd5ba5f02c2e94170bb2cd17c8f8f799be8ebef68ffa23526df818c7b81778ad7d4fc051
|
data/lib/mysql_framework.rb
CHANGED
@@ -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
|
14
|
-
|
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
|
18
|
-
|
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
|
-
|
7
|
+
@@logger
|
8
8
|
end
|
9
9
|
|
10
|
-
def self.
|
10
|
+
def self.logger=(logger)
|
11
11
|
@@logger = logger
|
12
12
|
end
|
13
13
|
|
14
|
-
MysqlFramework.
|
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
|
-
|
41
|
-
|
42
|
-
|
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
|
-
|
32
|
+
proc_sql = File.read(proc_file)
|
46
33
|
|
47
|
-
|
48
|
-
|
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
|
54
|
-
@
|
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,
|
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
|
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.
|
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,
|
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
|
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.
|
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.
|
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
|
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.
|
71
|
+
MysqlFramework.logger.debug { "[#{self.class}] - Initializing script history." }
|
64
72
|
|
65
73
|
mysql_connector.query(<<~SQL)
|
66
|
-
CREATE TABLE IF NOT EXISTS
|
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.
|
84
|
+
MysqlFramework.logger.debug { "[#{self.class}] - Calculating pending data store scripts." }
|
77
85
|
|
78
|
-
|
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
|
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
|
-
|
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
|
118
|
-
@
|
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
|
-
|
127
|
+
@migration_table_name ||= ENV.fetch('MYSQL_MIGRATION_TABLE', 'migration_script_history')
|
128
|
+
end
|
123
129
|
|
124
|
-
|
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
|
-
|
132
|
-
INSERT INTO
|
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,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: '
|
19
|
-
port: '
|
20
|
-
database: '
|
21
|
-
username: '
|
22
|
-
password: '
|
23
|
-
reconnect:
|
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
|
-
|
33
|
-
|
34
|
-
subject
|
32
|
+
before(:each) { subject.setup }
|
33
|
+
after(:each) { subject.dispose }
|
35
34
|
|
36
|
-
|
37
|
-
|
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 '#
|
50
|
-
it '
|
51
|
-
|
52
|
-
|
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
|
-
|
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.
|
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.
|
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
|
-
|
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(
|
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(
|
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
|
212
|
-
|
213
|
-
|
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 '
|
10
|
+
describe 'logger=' do
|
13
11
|
let(:logger) { Logger.new(STDOUT) }
|
14
12
|
|
15
13
|
it 'sets the logger' do
|
16
|
-
subject.
|
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
|
-
|
4
|
+
let(:client) { double }
|
7
5
|
|
8
|
-
|
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(
|
56
|
-
|
57
|
-
|
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(
|
82
|
-
expect(
|
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
|
-
|
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)
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
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
|
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
|
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
|
75
|
-
|
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('
|
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 `
|
133
|
+
DROP TABLE IF EXISTS `some_table`
|
145
134
|
SQL
|
146
|
-
subject.drop_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(
|
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(
|
147
|
+
expect(described_class.all_tables).to eq(%w(test demo))
|
159
148
|
end
|
160
149
|
end
|
161
150
|
end
|
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['
|
10
|
-
|
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
|
-
|
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
|
-
|
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
|
data/spec/support/procedure.sql
CHANGED
@@ -8,25 +8,34 @@ module MysqlFramework
|
|
8
8
|
@identifier = 201806021520 # 15:20 02/06/2018
|
9
9
|
end
|
10
10
|
|
11
|
-
def apply
|
12
|
-
|
13
|
-
CREATE TABLE IF NOT EXISTS `#{
|
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
|
-
|
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
|
-
[
|
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
|
-
[
|
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
|
-
|
13
|
-
CREATE TABLE IF NOT EXISTS `#{
|
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
|
-
[
|
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
|
+
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-
|
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
|