dohruby 0.2.1 → 0.3
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.
- data/CHANGELOG +8 -0
- data/bin/config.rb +61 -0
- data/bin/create_database.rb +13 -6
- data/bin/gendata.rb +37 -0
- data/bin/migrate.rb +66 -0
- data/bin/{rcov-preprocess-files.rb → rcov_preprocess_files.rb} +1 -1
- data/bin/run_tests.rb +15 -5
- data/bin/update_rdoc.rb +28 -0
- data/dohapp_home +0 -0
- data/lib/doh/app/activate_database.rb +5 -12
- data/lib/doh/app/activate_logger.rb +29 -13
- data/lib/doh/app/config.rb +18 -0
- data/lib/doh/app/home.rb +3 -8
- data/lib/doh/app/init_runnable.rb +39 -8
- data/lib/doh/app/init_script.rb +19 -0
- data/lib/doh/app/init_unit_test.rb +4 -1
- data/lib/doh/app_no_stdio.rb +1 -1
- data/lib/doh/boot/app.rb +2 -0
- data/lib/doh/boot/app_pwd.rb +2 -0
- data/lib/doh/boot/find_dohruby.rb +5 -0
- data/lib/doh/boot/find_dohruby_18.rb +37 -0
- data/lib/doh/boot/find_dohruby_19.rb +58 -0
- data/lib/doh/boot/init_runnable.rb +2 -0
- data/lib/doh/boot/options.rb +2 -0
- data/lib/doh/core/array.rb +10 -0
- data/lib/doh/core/bigdecimal.rb +15 -0
- data/lib/doh/core/date.rb +73 -0
- data/lib/doh/core/deep_dup.rb +12 -0
- data/lib/doh/core/hash.rb +21 -0
- data/lib/doh/core/object.rb +6 -0
- data/lib/doh/core/require_local.rb +5 -0
- data/lib/doh/core/socket.rb +25 -0
- data/lib/doh/core/string.rb +34 -4
- data/lib/doh/core.rb +4 -1
- data/lib/doh/data/basic.rb +24 -19
- data/lib/doh/data/bulk.rb +28 -88
- data/lib/doh/data/catalog.rb +44 -0
- data/lib/doh/data/human.rb +50 -42
- data/lib/doh/data/make_global.rb +3 -0
- data/lib/doh/data/require_datagen.rb +2 -1
- data/lib/doh/home.rb +16 -0
- data/lib/doh/logger/email_acceptor.rb +6 -3
- data/lib/doh/logger/event.rb +19 -6
- data/lib/doh/logger/formatter.rb +22 -16
- data/lib/doh/logger/interface.rb +13 -0
- data/lib/doh/logger/iostream_acceptor.rb +3 -2
- data/lib/doh/logger/null_interface.rb +1 -0
- data/lib/doh/logger/proxy.rb +53 -0
- data/lib/doh/logger/socket_acceptor.rb +53 -0
- data/lib/doh/logger/socket_viewer.rb +64 -0
- data/lib/doh/logger/standard_interface.rb +46 -17
- data/lib/doh/logger/util.rb +18 -0
- data/lib/doh/logger.rb +2 -1
- data/lib/doh/logger_configure.rb +1 -1
- data/lib/doh/merb/db_session.rb +136 -0
- data/lib/doh/merb/form_helpers.rb +16 -0
- data/lib/doh/merb/login.rb +27 -12
- data/lib/doh/merb/merb_dohsession.rb +4 -0
- data/lib/doh/merb/notify_on_exception.rb +1 -1
- data/lib/doh/merb/session.rb +7 -0
- data/lib/doh/merb/source_ip.rb +10 -0
- data/lib/doh/merb.rb +0 -1
- data/lib/doh/mysql/abstract_row.rb +81 -0
- data/lib/doh/mysql/cache_connector.rb +11 -8
- data/lib/doh/mysql/connector_instance.rb +32 -5
- data/lib/doh/mysql/connector_util.rb +1 -0
- data/lib/doh/mysql/convert.rb +18 -0
- data/lib/doh/mysql/database_creator.rb +18 -5
- data/lib/doh/mysql/db_date.rb +2 -2
- data/lib/doh/mysql/default_type_guesser.rb +21 -4
- data/lib/doh/mysql/error.rb +3 -2
- data/lib/doh/mysql/handle.rb +144 -18
- data/lib/doh/mysql/hash_row.rb +13 -0
- data/lib/doh/mysql/load_sql.rb +1 -0
- data/lib/doh/mysql/metadata_util.rb +60 -19
- data/lib/doh/mysql/migrate.rb +122 -0
- data/lib/doh/mysql/migrate_check.rb +139 -0
- data/lib/doh/mysql/parse.rb +2 -0
- data/lib/doh/mysql/readonly_row.rb +11 -48
- data/lib/doh/mysql/require_dbtypes.rb +8 -0
- data/lib/doh/mysql/smart_row.rb +156 -0
- data/lib/doh/mysql/to_sql.rb +12 -0
- data/lib/doh/mysql/typed_row_builder.rb +4 -3
- data/lib/doh/mysql/types.rb +33 -0
- data/lib/doh/mysql/unquoted.rb +8 -0
- data/lib/doh/mysql/version.rb +102 -0
- data/lib/doh/mysql/virtual.rb +17 -0
- data/lib/doh/mysql/writable_row.rb +58 -0
- data/lib/doh/mysql.rb +2 -1
- data/lib/doh/paypal/paypal.rb +20 -0
- data/lib/doh/paypal/pdt.rb +14 -0
- data/lib/doh/paypal.rb +1 -0
- data/lib/doh/rails/form_helpers.rb +53 -0
- data/lib/doh/rails/login.rb +143 -0
- data/lib/doh/test/error_acceptor.rb +1 -1
- data/lib/doh/test/run_tests.rb +48 -43
- data/lib/doh/test/setup_once.rb +15 -0
- data/lib/doh/test/test_result.rb +7 -0
- data/lib/doh/unit_test.rb +6 -0
- data/lib/doh/util/banking_workday.rb +16 -12
- data/lib/doh/util/class_basename.rb +10 -0
- data/lib/doh/util/current_date.rb +18 -41
- data/lib/doh/util/doh_socket.rb +56 -0
- data/lib/doh/util/email.rb +18 -0
- data/lib/doh/util/file_edit.rb +64 -0
- data/lib/doh/util/http_helper.rb +107 -0
- data/lib/doh/util/internal_ip.rb +1 -1
- data/lib/doh/util/jsval.rb +13 -0
- data/lib/doh/util/post_hash.rb +14 -0
- data/lib/doh/util/xml_util.rb +48 -0
- data/test/core/tc_array.rb +12 -0
- data/test/core/tc_date.rb +53 -0
- data/test/core/tc_deep_dup.rb +69 -0
- data/test/core/tc_hash.rb +28 -0
- data/test/core/tc_socket.rb +30 -0
- data/test/core/tc_string.rb +15 -22
- data/test/local_tests.rb +3 -0
- data/test/local_tests_including_slow.rb +4 -0
- data/test/logger/tc_acceptor.rb +23 -6
- data/test/logger/tc_event.rb +1 -1
- data/test/logger/tc_formatter.rb +3 -2
- data/test/logger/tc_socket_viewer_acceptor.rb +48 -0
- data/test/mysql/001_down.sql +1 -0
- data/test/mysql/001_up.sql +4 -0
- data/test/mysql/002_down.sql +1 -0
- data/test/mysql/002_up.sql +1 -0
- data/test/mysql/tc_connector_instance.rb +8 -8
- data/test/mysql/tc_convert.rb +45 -0
- data/test/mysql/tc_handle.rb +94 -2
- data/test/mysql/tc_metadata_util.rb +50 -0
- data/test/mysql/tc_migrate.rb +50 -0
- data/test/mysql/tc_parse.rb +3 -1
- data/test/mysql/tc_readonly_row.rb +14 -10
- data/test/mysql/tc_smart_row.rb +22 -0
- data/test/mysql/tc_to_sql.rb +20 -0
- data/test/mysql/tc_types.rb +32 -0
- data/test/mysql/tc_unquoted.rb +1 -0
- data/test/mysql/tc_writable_row.rb +22 -0
- data/test/ts_core.rb +4 -0
- data/test/ts_logger.rb +4 -0
- data/test/ts_mysql.rb +6 -0
- data/test/ts_util.rb +6 -0
- data/test/util/slow_doh_socket.rb +102 -0
- data/test/util/tc_banking_workday.rb +18 -0
- data/test/util/tc_file_edit.rb +54 -0
- data/test/util/tc_jsval.rb +12 -0
- data/test/util/tc_to_display.rb +14 -0
- data/test/util/tc_xml_util.rb +17 -0
- metadata +130 -39
- data/README +0 -4
- data/lib/doh/merb/post_hash.rb +0 -26
- data/lib/doh/mysql/db_null.rb +0 -24
- data/lib/doh/mysql/hash_util.rb +0 -56
- data/test/mysql/tc_hash_util.rb +0 -23
data/lib/doh/mysql/handle.rb
CHANGED
|
@@ -1,18 +1,22 @@
|
|
|
1
|
+
require 'doh/core/array'
|
|
1
2
|
require 'doh/logger/interface'
|
|
2
3
|
require 'doh/mysql/error'
|
|
3
|
-
require 'doh/mysql/
|
|
4
|
+
require 'doh/mysql/typed_row_builder'
|
|
5
|
+
require 'doh/mysql/writable_row'
|
|
6
|
+
require 'doh/mysql/hash_row'
|
|
7
|
+
require 'doh/mysql/smart_row'
|
|
4
8
|
|
|
5
9
|
module DohDb
|
|
6
10
|
|
|
7
11
|
class Handle
|
|
8
12
|
def initialize(mysqlh, row_builder = nil)
|
|
9
13
|
@mysqlh = mysqlh
|
|
10
|
-
@row_builder = row_builder ||
|
|
14
|
+
@row_builder = row_builder || TypedRowBuilder.new
|
|
11
15
|
end
|
|
12
16
|
|
|
13
17
|
def close
|
|
14
18
|
unless closed?
|
|
15
|
-
|
|
19
|
+
dohlog.info("closing raw mysql handle: #@mysqlh")
|
|
16
20
|
@mysqlh.close
|
|
17
21
|
@mysqlh = nil
|
|
18
22
|
end
|
|
@@ -25,68 +29,190 @@ class Handle
|
|
|
25
29
|
def query(statement)
|
|
26
30
|
generic_query(statement)
|
|
27
31
|
retval = @mysqlh.affected_rows
|
|
28
|
-
|
|
32
|
+
dohlog.info("affected #{retval} rows")
|
|
29
33
|
retval
|
|
30
34
|
end
|
|
31
35
|
|
|
32
36
|
def update(statement)
|
|
33
37
|
generic_query(statement)
|
|
34
38
|
retval = @mysqlh.affected_rows
|
|
35
|
-
|
|
39
|
+
dohlog.info("updated #{retval} rows")
|
|
36
40
|
retval
|
|
37
41
|
end
|
|
38
42
|
|
|
43
|
+
def update_row(statement)
|
|
44
|
+
retval = update(statement)
|
|
45
|
+
raise UnexpectedQueryResult, "updated #{retval} rows; expected 1" unless retval == 1
|
|
46
|
+
retval
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def update_hash(hash, table, primary_key_value, primary_key_name)
|
|
50
|
+
items = hash.keys.collect {|key| key + ' = ' + hash[key].to_sql}
|
|
51
|
+
query("UPDATE #{table} SET #{items.join(', ')} WHERE #{primary_key_name} = #{primary_key_value}")
|
|
52
|
+
end
|
|
53
|
+
|
|
39
54
|
def insert(statement)
|
|
40
55
|
generic_query(statement)
|
|
41
56
|
retval = @mysqlh.insert_id
|
|
42
|
-
|
|
57
|
+
dohlog.info("insert_id was #{retval}")
|
|
43
58
|
retval
|
|
44
59
|
end
|
|
45
60
|
|
|
61
|
+
def insert_hash(hash, table, ignore = false)
|
|
62
|
+
if ignore then keyword = 'INSERT IGNORE' else keyword = 'INSERT' end
|
|
63
|
+
insert_hash_helper(hash, table, keyword)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def replace_hash(hash, table)
|
|
67
|
+
insert_hash_helper(hash, table, 'REPLACE')
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# The most generic form of select.
|
|
71
|
+
# It calls to_s on the statement object to facilitate the use of sql builder objects.
|
|
46
72
|
def select(statement, row_builder = nil)
|
|
47
73
|
result_set = generic_query(statement)
|
|
48
|
-
|
|
49
|
-
rows = (row_builder
|
|
74
|
+
dohlog.info("selected #{result_set.num_rows} rows")
|
|
75
|
+
rows = get_row_builder(row_builder).build_rows(result_set)
|
|
50
76
|
result_set.free
|
|
51
77
|
rows
|
|
52
78
|
end
|
|
53
79
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
retval
|
|
58
|
-
end
|
|
59
|
-
|
|
80
|
+
# Simple convenience wrapper around the generic select call.
|
|
81
|
+
# Throws an exception unless the result set is a single row.
|
|
82
|
+
# Returns the row selected.
|
|
60
83
|
def select_row(statement, row_builder = nil)
|
|
61
84
|
rows = select(statement, row_builder)
|
|
62
|
-
raise
|
|
85
|
+
raise UnexpectedQueryResult, "selected #{rows.size} rows; expected 1" unless rows.size == 1
|
|
63
86
|
rows[0]
|
|
64
87
|
end
|
|
65
88
|
|
|
89
|
+
# Simple convenience wrapper around the generic select call.
|
|
90
|
+
# Throws an exception unless the result set is empty or a single row.
|
|
91
|
+
# Returns nil if the result set is empty, or the row selected.
|
|
66
92
|
def select_optional_row(statement, row_builder = nil)
|
|
67
93
|
rows = select(statement, row_builder)
|
|
68
|
-
raise
|
|
94
|
+
raise UnexpectedQueryResult, "selected #{rows.size} rows; expected 0 or 1" if rows.size > 1
|
|
69
95
|
if rows.empty? then nil else rows[0] end
|
|
70
96
|
end
|
|
71
97
|
|
|
98
|
+
# Simple convenience wrapper around select_row.
|
|
99
|
+
# Returns the first (and typically, the only) field from the selected row.
|
|
72
100
|
def select_field(statement, row_builder = nil)
|
|
73
101
|
select_row(statement, row_builder).at(0)
|
|
74
102
|
end
|
|
75
103
|
|
|
104
|
+
# Simple convenience wrapper around select_optional_row.
|
|
105
|
+
# Returns the first (and typically, the only) field from the selected row, if any, or nil.
|
|
76
106
|
def select_optional_field(statement, row_builder = nil)
|
|
77
107
|
row = select_optional_row(statement, row_builder)
|
|
78
108
|
row && row.at(0)
|
|
79
109
|
end
|
|
80
110
|
|
|
111
|
+
# Rows in the result set must have 2 or more fields.
|
|
112
|
+
# If there are 2 fields, returns a hash where each key is the first field in the result set, and the value is the second field.
|
|
113
|
+
# If there are more than 2 fields, returns a hash where each key is the first field in the result set,
|
|
114
|
+
# and the value is the row itself, as a Hash, and without the field used as a key.
|
|
115
|
+
def select_transpose(statement, row_builder = nil)
|
|
116
|
+
rows = select(statement, row_builder)
|
|
117
|
+
return {} if rows.empty?
|
|
118
|
+
field_count = rows.first.size
|
|
119
|
+
if field_count < 2
|
|
120
|
+
raise UnexpectedQueryResult, "must select at least 2 fields in order to transpose"
|
|
121
|
+
elsif field_count == 2
|
|
122
|
+
rows.build_hash { |row| [row.at(0), row.at(1)] }
|
|
123
|
+
else
|
|
124
|
+
key_field = rows.first.keys.first
|
|
125
|
+
rows.build_hash do |row|
|
|
126
|
+
value = row.to_h
|
|
127
|
+
value.delete(key_field)
|
|
128
|
+
[row.at(0), value]
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Returns an array of arrays, where the individual arrays contain just the values from each database row -- they lack field names.
|
|
134
|
+
def select_values(statement, row_builder = nil)
|
|
135
|
+
select(statement, row_builder).collect { |row| row.values }
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Returns an array of the first (and typically, the only) field of every row in the result set.
|
|
139
|
+
def select_list(statement, row_builder = nil)
|
|
140
|
+
select(statement, row_builder).collect { |row| row.at(0) }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def multi_select(statement_infos, dflt_row_builder = nil)
|
|
144
|
+
statements = []
|
|
145
|
+
row_builders = []
|
|
146
|
+
statement_infos.each do |info|
|
|
147
|
+
if info.is_a?(Array)
|
|
148
|
+
statements.push(info.first)
|
|
149
|
+
row_builders.push(info.last)
|
|
150
|
+
else
|
|
151
|
+
statements.push(info)
|
|
152
|
+
row_builders.push(nil)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
sqlstr = statements.join(';')
|
|
156
|
+
|
|
157
|
+
dohlog.info(sqlstr)
|
|
158
|
+
@mysqlh.query(sqlstr)
|
|
159
|
+
retval = []
|
|
160
|
+
while true
|
|
161
|
+
custom_builder = row_builders.shift
|
|
162
|
+
if @mysqlh.field_count > 0
|
|
163
|
+
result_set = @mysqlh.store_result
|
|
164
|
+
dohlog.info("selected #{result_set.num_rows} rows")
|
|
165
|
+
rows = get_row_builder(custom_builder || dflt_row_builder).build_rows(result_set)
|
|
166
|
+
result_set.free
|
|
167
|
+
retval.push(rows)
|
|
168
|
+
end
|
|
169
|
+
break unless @mysqlh.next_result
|
|
170
|
+
end
|
|
171
|
+
retval
|
|
172
|
+
rescue Exception => excpt
|
|
173
|
+
dohlog.error("caught exception during query: #{sqlstr}", excpt)
|
|
174
|
+
raise
|
|
175
|
+
end
|
|
176
|
+
|
|
81
177
|
private
|
|
82
178
|
def generic_query(statement)
|
|
83
179
|
sqlstr = statement.to_s
|
|
84
|
-
|
|
180
|
+
dohlog.info(sqlstr)
|
|
85
181
|
@mysqlh.query(sqlstr)
|
|
182
|
+
@mysqlh.store_result if @mysqlh.field_count > 0
|
|
86
183
|
rescue Exception => excpt
|
|
87
|
-
|
|
184
|
+
dohlog.error("caught exception during query: #{sqlstr}", excpt)
|
|
88
185
|
raise
|
|
89
186
|
end
|
|
187
|
+
|
|
188
|
+
def insert_hash_helper(hash, table, keyword)
|
|
189
|
+
names = []
|
|
190
|
+
values = []
|
|
191
|
+
hash.each_pair do |key, value|
|
|
192
|
+
names.push(key)
|
|
193
|
+
values.push(value.to_sql)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
insert("#{keyword} INTO #{table} (#{names.join(',')}) VALUES (#{values.join(',')})")
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def get_row_builder(row_builder = nil)
|
|
200
|
+
if row_builder.nil?
|
|
201
|
+
@row_builder
|
|
202
|
+
elsif row_builder == :read
|
|
203
|
+
TypedRowBuilder.new(ReadOnlyRow)
|
|
204
|
+
elsif row_builder == :hash
|
|
205
|
+
TypedRowBuilder.new(HashRow)
|
|
206
|
+
elsif row_builder == :write
|
|
207
|
+
TypedRowBuilder.new(WritableRow)
|
|
208
|
+
elsif row_builder == :smart
|
|
209
|
+
TypedRowBuilder.new(SmartRow)
|
|
210
|
+
elsif row_builder.respond_to?('build_rows')
|
|
211
|
+
row_builder
|
|
212
|
+
else
|
|
213
|
+
TypedRowBuilder.new(row_builder)
|
|
214
|
+
end
|
|
215
|
+
end
|
|
90
216
|
end
|
|
91
217
|
|
|
92
218
|
end
|
data/lib/doh/mysql/load_sql.rb
CHANGED
|
@@ -10,6 +10,7 @@ end
|
|
|
10
10
|
def self.load_sql(filenames, host, username, password, database)
|
|
11
11
|
mysqlcmd = 'mysql' + mysql_arg(host, 'h') + mysql_arg(username, 'u') + mysql_arg(password, 'p') + ' ' + database
|
|
12
12
|
io = IO::popen(mysqlcmd, 'r+')
|
|
13
|
+
dohlog.debug("loading sql file: " + filenames.first) if filenames.size == 1
|
|
13
14
|
filenames.each do |elem|
|
|
14
15
|
open(elem) {|file| io << file.read}
|
|
15
16
|
end
|
|
@@ -1,32 +1,73 @@
|
|
|
1
1
|
require 'doh/mysql'
|
|
2
|
+
require 'doh/mysql/to_sql'
|
|
2
3
|
|
|
3
|
-
module
|
|
4
|
+
module DohDb
|
|
4
5
|
|
|
5
|
-
@@
|
|
6
|
+
@@cached_column_info = {}
|
|
7
|
+
def self.column_info(table, database = nil)
|
|
8
|
+
database ||= DohDb::connector_instance.database
|
|
9
|
+
lookup_str = database + '.' + table
|
|
10
|
+
return @@cached_column_info[lookup_str] if @@cached_column_info[lookup_str]
|
|
11
|
+
stmt = "SELECT column_name, is_nullable, data_type, character_maximum_length, numeric_scale, column_type FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=#{database.to_sql} AND TABLE_NAME=#{table.to_sql}"
|
|
12
|
+
@@cached_column_info[lookup_str] = DohDb::select_transpose(stmt)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.field_character_size(table, field, database = nil)
|
|
16
|
+
column_info(table, database).fetch(field, {}).fetch('character_maximum_length')
|
|
17
|
+
end
|
|
6
18
|
|
|
7
|
-
def self.
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if row.keys.include?(field) && ['char', 'varchar'].include?(data_type.downcase) && row[field] && row[field].size > size.to_i
|
|
13
|
-
Doh::log.info("chopping field: #{row[field].inspect} to #{size} chars.") if log
|
|
14
|
-
result[field] = result[field].to_s[0, size.to_i]
|
|
19
|
+
def self.chop_character_fields!(table, row)
|
|
20
|
+
column_info(table).each do |field, attribs|
|
|
21
|
+
maxlen = attribs['character_maximum_length']
|
|
22
|
+
if maxlen && row[field].to_s.size > maxlen
|
|
23
|
+
row[field] = row[field].to_s[0, maxlen]
|
|
15
24
|
end
|
|
16
25
|
end
|
|
17
|
-
|
|
26
|
+
row
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.chop_character_fields(table, row)
|
|
30
|
+
chop_character_fields!(table, row.dup)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.field_exist?(table, field, database = nil)
|
|
34
|
+
column_info(table, database).key?(field)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
@@primary_keys = {}
|
|
38
|
+
def self.find_primary_key(table, database = nil)
|
|
39
|
+
if table.index('.')
|
|
40
|
+
database = table.before('.')
|
|
41
|
+
table = table.after('.')
|
|
42
|
+
else
|
|
43
|
+
database ||= DohDb::connector_instance.database
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
dbhash = @@primary_keys[database]
|
|
47
|
+
if dbhash.nil?
|
|
48
|
+
dbhash = DohDb::select_transpose("SELECT table_name, column_name FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=#{database.to_sql} AND ORDINAL_POSITION=1")
|
|
49
|
+
raise "no information found for database #{database}" if dbhash.empty?
|
|
50
|
+
@@primary_keys[database] = dbhash
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
retval = dbhash[table]
|
|
54
|
+
raise "attempting to find_primary_key for table that doesn't exist: #{database}.#{table}" if retval.nil?
|
|
55
|
+
retval
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.table_exist?(table, database = nil)
|
|
59
|
+
!find_primary_key(table, database).nil?
|
|
18
60
|
end
|
|
19
61
|
|
|
20
|
-
def self.
|
|
21
|
-
|
|
22
|
-
@@cached_table_info[table] = query_column_info(table)
|
|
62
|
+
def self.field_list(table, database = nil)
|
|
63
|
+
column_info(table, database).keys
|
|
23
64
|
end
|
|
24
65
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
66
|
+
@@tables_by_database = {}
|
|
67
|
+
def self.all_tables(database = nil)
|
|
68
|
+
database ||= DohDb::connector_instance.database
|
|
69
|
+
@@tables_by_database[database] ||
|
|
70
|
+
@@tables_by_database[database] ||= DohDb::select_list("SELECT table_name FROM information_schema.tables WHERE table_schema = '#{database}'")
|
|
30
71
|
end
|
|
31
72
|
|
|
32
73
|
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
require 'doh/mysql/load_sql'
|
|
2
|
+
require 'doh/mysql/version'
|
|
3
|
+
|
|
4
|
+
module DohDb
|
|
5
|
+
|
|
6
|
+
class DatabaseMigrator
|
|
7
|
+
def initialize(dest_db = nil, source_db = nil, directory = nil)
|
|
8
|
+
@dest_db = dest_db || DohApp::config['primary_database']
|
|
9
|
+
@source_db = source_db || @dest_db
|
|
10
|
+
@table = @dest_db + '.version'
|
|
11
|
+
@directory = directory || File.join(DohApp::home, 'database', @source_db, 'migrate')
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# Migrates to the latest version.
|
|
15
|
+
# Returns nil if true if no migrations took place, or [original_version, new_version].
|
|
16
|
+
def update
|
|
17
|
+
get_original_version
|
|
18
|
+
make_latest_version
|
|
19
|
+
migrate_result
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Rolls back one version.
|
|
23
|
+
# Returns nil if true if no migrations took place, or [original_version, new_version].
|
|
24
|
+
def rollback
|
|
25
|
+
get_original_version
|
|
26
|
+
make_earlier_version(@original_version - 1)
|
|
27
|
+
migrate_result
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Migrates to a specific version.
|
|
31
|
+
# Returns nil if true if no migrations took place, or [original_version, new_version].
|
|
32
|
+
def make_version(new_version)
|
|
33
|
+
get_original_version
|
|
34
|
+
if new_version > @original_version
|
|
35
|
+
make_later_version(new_version)
|
|
36
|
+
else
|
|
37
|
+
make_earlier_version(new_version)
|
|
38
|
+
end
|
|
39
|
+
migrate_result
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def up_filename(version)
|
|
43
|
+
filename_prefix(version) + '_up.sql'
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def down_filename(version)
|
|
47
|
+
filename_prefix(version) + '_down.sql'
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def get_unlocked_migration
|
|
51
|
+
latest_version = DohDb::latest_database_version(@dest_db)
|
|
52
|
+
previous_version, previous_svn_revision = DohDb::locked_version_info(@dest_db)
|
|
53
|
+
raise 'you need to lock the database before adding a migration (migrate.rb -l)' if !previous_svn_revision
|
|
54
|
+
if latest_version == previous_version
|
|
55
|
+
upfile = up_filename(latest_version + 1)
|
|
56
|
+
downfile = down_filename(latest_version + 1)
|
|
57
|
+
`touch #{upfile}`
|
|
58
|
+
`touch #{downfile}`
|
|
59
|
+
`svn add #{upfile}`
|
|
60
|
+
`svn add #{downfile}`
|
|
61
|
+
else
|
|
62
|
+
upfile = up_filename(latest_version)
|
|
63
|
+
downfile = down_filename(latest_version)
|
|
64
|
+
end
|
|
65
|
+
[true, "up:#{upfile} down:#{downfile}"]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
def get_original_version
|
|
70
|
+
@original_version = DohDb::select_field("SELECT version FROM #@table").to_i
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def migrate_result
|
|
74
|
+
if @new_version.nil? || @new_version == @original_version
|
|
75
|
+
nil
|
|
76
|
+
else
|
|
77
|
+
[@original_version, @new_version]
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def filename_prefix(version)
|
|
82
|
+
@directory + '/' + ('000' + version.to_s).lastn(3)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def load_sql_file(filename)
|
|
86
|
+
DohDb::load_sql_connector([filename], DohDb::connector_instance, @dest_db)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def set_version(number)
|
|
90
|
+
DohDb::query("UPDATE #@table SET version = #{number}")
|
|
91
|
+
@new_version = number
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def make_latest_version
|
|
95
|
+
next_version = @original_version + 1
|
|
96
|
+
while File.exist?(filename = up_filename(next_version))
|
|
97
|
+
load_sql_file(filename)
|
|
98
|
+
set_version(next_version)
|
|
99
|
+
next_version += 1
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def make_later_version(new_version)
|
|
104
|
+
next_version = @original_version + 1
|
|
105
|
+
while next_version <= new_version
|
|
106
|
+
load_sql_file(up_filename(next_version))
|
|
107
|
+
set_version(next_version)
|
|
108
|
+
next_version += 1
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def make_earlier_version(new_version)
|
|
113
|
+
next_version = @original_version
|
|
114
|
+
while next_version > new_version
|
|
115
|
+
load_sql_file(down_filename(next_version))
|
|
116
|
+
next_version -= 1
|
|
117
|
+
set_version(next_version)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
require 'doh/mysql/migrate'
|
|
2
|
+
require 'doh/mysql/database_creator'
|
|
3
|
+
require 'doh/mysql/version'
|
|
4
|
+
require 'open3'
|
|
5
|
+
|
|
6
|
+
module DohDb
|
|
7
|
+
|
|
8
|
+
class MigrateChecker
|
|
9
|
+
def initialize(database = nil)
|
|
10
|
+
@database = database || DohApp::config['primary_database']
|
|
11
|
+
@data_directory = File.join(DohApp::home, 'database', @database)
|
|
12
|
+
@check_database = 'migration_check_database'
|
|
13
|
+
@sql_previous = "/tmp/check_migration_#{@database}_previous.sql"
|
|
14
|
+
@sql_previous_up = "/tmp/check_migration_#{@database}_previous_up.sql"
|
|
15
|
+
@sql_latest = "/tmp/check_migration_#{@database}_latest.sql"
|
|
16
|
+
@sql_latest_down = "/tmp/check_migration_#{@database}_latest_down.sql"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def run
|
|
20
|
+
svnout = `svn st #@data_directory`
|
|
21
|
+
unless svnout.strip.empty?
|
|
22
|
+
return [false, "svn status shows local changes in #@data_directory -- this needs to be resolved before migration can be checked"]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
@latest_version = DohDb::latest_database_version(@database)
|
|
26
|
+
if @latest_version == 0
|
|
27
|
+
return [true, "no migrations to check, database version is still at 0"]
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
@previous_version, @previous_svn_revision = DohDb::locked_version_info(@database)
|
|
31
|
+
if @latest_version == @previous_version
|
|
32
|
+
return [true, "no unlocked migration exists, nothing to check"]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
dbmigrator = DohDb::DatabaseMigrator.new(@check_database, @database)
|
|
36
|
+
if File.exist?(dbmigrator.up_filename(@latest_version + 1))
|
|
37
|
+
puts "multiple unlocked versions exist, should merge into version: #{@latest_version}"
|
|
38
|
+
exit 1
|
|
39
|
+
end
|
|
40
|
+
dohlog.debug("comparing database revisions #@previous_version and #@latest_version")
|
|
41
|
+
|
|
42
|
+
dohlog.debug("svn updating to revision #@previous_svn_revision, building database, dumping as #{@sql_previous}")
|
|
43
|
+
execute_cmd("svn up -r#@previous_svn_revision #@data_directory")
|
|
44
|
+
|
|
45
|
+
DohDb::DatabaseCreator.new.exclude_scripts.create_database_copy(@check_database, @database, true)
|
|
46
|
+
dump_sql(@sql_previous)
|
|
47
|
+
|
|
48
|
+
dohlog.debug("svn updating to current revision, migrating to latest database version, dumping as #{@sql_previous_up}")
|
|
49
|
+
execute_cmd("svn up #@data_directory")
|
|
50
|
+
dbmigrator.update
|
|
51
|
+
dump_sql(@sql_previous_up)
|
|
52
|
+
|
|
53
|
+
dohlog.debug("re-creating database from current database files, dumping as #{@sql_latest}")
|
|
54
|
+
begin
|
|
55
|
+
DohDb::DatabaseCreator.new.exclude_scripts.create_database_copy(@check_database, @database, true)
|
|
56
|
+
rescue Errno::ENOENT => e
|
|
57
|
+
puts 'got exception during database creation -- possible view in order.yml that doesn\'t exist?'
|
|
58
|
+
raise e
|
|
59
|
+
end
|
|
60
|
+
dump_sql(@sql_latest)
|
|
61
|
+
|
|
62
|
+
dohlog.debug("migrating to previous version, dumping as #{@sql_latest_down}")
|
|
63
|
+
DohDb::DatabaseMigrator.new(@check_database, @database).make_version(@previous_version)
|
|
64
|
+
|
|
65
|
+
dump_sql(@sql_latest_down)
|
|
66
|
+
|
|
67
|
+
diffstr = diff(@sql_previous, @sql_latest_down)
|
|
68
|
+
return [false, diffstr] unless diffstr.empty?
|
|
69
|
+
|
|
70
|
+
diffstr = diff(@sql_previous_up, @sql_latest)
|
|
71
|
+
return [false, diffstr] unless diffstr.empty?
|
|
72
|
+
|
|
73
|
+
File.delete(@sql_previous)
|
|
74
|
+
File.delete(@sql_previous_up)
|
|
75
|
+
File.delete(@sql_latest)
|
|
76
|
+
File.delete(@sql_latest_down)
|
|
77
|
+
|
|
78
|
+
[true, 'unlocked migration compared successfully']
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
def execute_cmd(cmd)
|
|
83
|
+
dohlog.debug("executing: #{cmd}")
|
|
84
|
+
stdin, stdout, stderr = Open3.popen3(cmd)
|
|
85
|
+
stdoutstr = stdout.read
|
|
86
|
+
stdout.close
|
|
87
|
+
stderrstr = stderr.read
|
|
88
|
+
raise "stderr: #{stderrstr}" if !stderrstr.empty?
|
|
89
|
+
stdoutstr
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def dump_sql(fname)
|
|
94
|
+
sql = execute_cmd("mysqldump -uroot -d #{@check_database}")
|
|
95
|
+
sql = remove_unwanted_comparisons(sql)
|
|
96
|
+
sql = sql.split(/[\r|\n]+/)
|
|
97
|
+
output = []
|
|
98
|
+
current_keys = []
|
|
99
|
+
#ignore order of keys
|
|
100
|
+
sql.each do |line|
|
|
101
|
+
if line =~ /(.*KEY[^,]*),?/
|
|
102
|
+
current_keys.push($1)
|
|
103
|
+
else
|
|
104
|
+
output.concat(current_keys.sort)
|
|
105
|
+
output.push(line)
|
|
106
|
+
current_keys = []
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
File.open(fname, 'wb') do |file|
|
|
111
|
+
output.each do |line|
|
|
112
|
+
file.write(line + "\n")
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def remove_unwanted_comparisons(sql)
|
|
118
|
+
regexp = /--.-- Temporary table structure for view[^;]*;[^;]*;[^;]*;/m
|
|
119
|
+
regexp2 = /--.-- Table structure for table `[^;]*_deleteme`[^;]*;[^;]*;[^;]*;[^;]*;[^;]*;/m
|
|
120
|
+
sql.gsub(regexp, '').gsub(regexp2, '')
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def remove_safe_diff_lines(result)
|
|
124
|
+
result.dup.delete_if do |elem|
|
|
125
|
+
elem =~ /\d+c\d+/ || elem =~ /-- Dump completed on.*/ || elem == '---' || elem =~ /.*ENGINE.*AUTO_INCREMENT=.*/ || elem =~ /.*ENGINE.*DEFAULT CHARSET=.*/
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def diff(file1, file2)
|
|
130
|
+
result = remove_safe_diff_lines(execute_cmd("diff -b #{file1} #{file2}").split(/[\r|\n]+/))
|
|
131
|
+
if result.empty?
|
|
132
|
+
''
|
|
133
|
+
else
|
|
134
|
+
"#{file1} and #{file2} differ\n" + result.join("\n")
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
end
|
data/lib/doh/mysql/parse.rb
CHANGED
|
@@ -15,11 +15,13 @@ end
|
|
|
15
15
|
|
|
16
16
|
def self.parse_datetime(str)
|
|
17
17
|
raise ArgumentError.new("unexpected value: " + str) unless str.size == 19
|
|
18
|
+
return nil if str == '0000-00-00 00:00:00'
|
|
18
19
|
DateTime.parse(str)
|
|
19
20
|
end
|
|
20
21
|
|
|
21
22
|
def self.parse_date(str)
|
|
22
23
|
raise ArgumentError.new("unexpected value: " + str) unless str.size == 10
|
|
24
|
+
return nil if str == '0000-00-00'
|
|
23
25
|
Date.new(str[0..3].to_i, str[5..6].to_i, str[8..9].to_i)
|
|
24
26
|
end
|
|
25
27
|
|
|
@@ -1,61 +1,24 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
class ReadOnlyRow
|
|
4
|
-
attr_reader :keys, :values
|
|
5
|
-
|
|
6
|
-
def initialize(keys, values)
|
|
7
|
-
@keys = keys
|
|
8
|
-
@values = values
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
def at(index)
|
|
12
|
-
@values.at(index)
|
|
13
|
-
end
|
|
14
|
-
|
|
15
|
-
def get(key)
|
|
16
|
-
index = @keys.index(key)
|
|
17
|
-
if index
|
|
18
|
-
@values.at(index)
|
|
19
|
-
else
|
|
20
|
-
nil
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
alias [] get
|
|
24
|
-
|
|
25
|
-
def key?(key)
|
|
26
|
-
!@keys.index(key).nil?
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def to_a
|
|
30
|
-
retval = []
|
|
31
|
-
@keys.size.times {|index| retval.push([@keys[index], @values[index]])}
|
|
32
|
-
retval
|
|
33
|
-
end
|
|
1
|
+
require 'doh/mysql/abstract_row'
|
|
34
2
|
|
|
35
|
-
|
|
36
|
-
retval = {}
|
|
37
|
-
@keys.each_with_index {|key, index| retval[key] = @values.at(index)}
|
|
38
|
-
retval
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def inspect
|
|
42
|
-
to_h.inspect
|
|
43
|
-
end
|
|
3
|
+
module DohDb
|
|
44
4
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
5
|
+
class ReadOnlyRow < AbstractRow
|
|
6
|
+
# can accept 2 arguments: keys array, values array
|
|
7
|
+
# or 1 argument: a hash
|
|
8
|
+
def initialize(*args)
|
|
9
|
+
keys, values = parse_initialize_args(*args)
|
|
10
|
+
@keys = keys.freeze
|
|
11
|
+
@values = values.freeze
|
|
12
|
+
freeze
|
|
49
13
|
end
|
|
50
14
|
|
|
51
|
-
undef id
|
|
52
15
|
def method_missing(sym, *ignore)
|
|
53
16
|
key = sym.to_s
|
|
54
17
|
index = @keys.index(key)
|
|
55
18
|
if index
|
|
56
19
|
@values.at(index)
|
|
57
20
|
else
|
|
58
|
-
raise RuntimeError.new("unknown
|
|
21
|
+
raise RuntimeError.new("unknown field: " + key)
|
|
59
22
|
end
|
|
60
23
|
end
|
|
61
24
|
end
|