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.
Files changed (154) hide show
  1. data/CHANGELOG +8 -0
  2. data/bin/config.rb +61 -0
  3. data/bin/create_database.rb +13 -6
  4. data/bin/gendata.rb +37 -0
  5. data/bin/migrate.rb +66 -0
  6. data/bin/{rcov-preprocess-files.rb → rcov_preprocess_files.rb} +1 -1
  7. data/bin/run_tests.rb +15 -5
  8. data/bin/update_rdoc.rb +28 -0
  9. data/dohapp_home +0 -0
  10. data/lib/doh/app/activate_database.rb +5 -12
  11. data/lib/doh/app/activate_logger.rb +29 -13
  12. data/lib/doh/app/config.rb +18 -0
  13. data/lib/doh/app/home.rb +3 -8
  14. data/lib/doh/app/init_runnable.rb +39 -8
  15. data/lib/doh/app/init_script.rb +19 -0
  16. data/lib/doh/app/init_unit_test.rb +4 -1
  17. data/lib/doh/app_no_stdio.rb +1 -1
  18. data/lib/doh/boot/app.rb +2 -0
  19. data/lib/doh/boot/app_pwd.rb +2 -0
  20. data/lib/doh/boot/find_dohruby.rb +5 -0
  21. data/lib/doh/boot/find_dohruby_18.rb +37 -0
  22. data/lib/doh/boot/find_dohruby_19.rb +58 -0
  23. data/lib/doh/boot/init_runnable.rb +2 -0
  24. data/lib/doh/boot/options.rb +2 -0
  25. data/lib/doh/core/array.rb +10 -0
  26. data/lib/doh/core/bigdecimal.rb +15 -0
  27. data/lib/doh/core/date.rb +73 -0
  28. data/lib/doh/core/deep_dup.rb +12 -0
  29. data/lib/doh/core/hash.rb +21 -0
  30. data/lib/doh/core/object.rb +6 -0
  31. data/lib/doh/core/require_local.rb +5 -0
  32. data/lib/doh/core/socket.rb +25 -0
  33. data/lib/doh/core/string.rb +34 -4
  34. data/lib/doh/core.rb +4 -1
  35. data/lib/doh/data/basic.rb +24 -19
  36. data/lib/doh/data/bulk.rb +28 -88
  37. data/lib/doh/data/catalog.rb +44 -0
  38. data/lib/doh/data/human.rb +50 -42
  39. data/lib/doh/data/make_global.rb +3 -0
  40. data/lib/doh/data/require_datagen.rb +2 -1
  41. data/lib/doh/home.rb +16 -0
  42. data/lib/doh/logger/email_acceptor.rb +6 -3
  43. data/lib/doh/logger/event.rb +19 -6
  44. data/lib/doh/logger/formatter.rb +22 -16
  45. data/lib/doh/logger/interface.rb +13 -0
  46. data/lib/doh/logger/iostream_acceptor.rb +3 -2
  47. data/lib/doh/logger/null_interface.rb +1 -0
  48. data/lib/doh/logger/proxy.rb +53 -0
  49. data/lib/doh/logger/socket_acceptor.rb +53 -0
  50. data/lib/doh/logger/socket_viewer.rb +64 -0
  51. data/lib/doh/logger/standard_interface.rb +46 -17
  52. data/lib/doh/logger/util.rb +18 -0
  53. data/lib/doh/logger.rb +2 -1
  54. data/lib/doh/logger_configure.rb +1 -1
  55. data/lib/doh/merb/db_session.rb +136 -0
  56. data/lib/doh/merb/form_helpers.rb +16 -0
  57. data/lib/doh/merb/login.rb +27 -12
  58. data/lib/doh/merb/merb_dohsession.rb +4 -0
  59. data/lib/doh/merb/notify_on_exception.rb +1 -1
  60. data/lib/doh/merb/session.rb +7 -0
  61. data/lib/doh/merb/source_ip.rb +10 -0
  62. data/lib/doh/merb.rb +0 -1
  63. data/lib/doh/mysql/abstract_row.rb +81 -0
  64. data/lib/doh/mysql/cache_connector.rb +11 -8
  65. data/lib/doh/mysql/connector_instance.rb +32 -5
  66. data/lib/doh/mysql/connector_util.rb +1 -0
  67. data/lib/doh/mysql/convert.rb +18 -0
  68. data/lib/doh/mysql/database_creator.rb +18 -5
  69. data/lib/doh/mysql/db_date.rb +2 -2
  70. data/lib/doh/mysql/default_type_guesser.rb +21 -4
  71. data/lib/doh/mysql/error.rb +3 -2
  72. data/lib/doh/mysql/handle.rb +144 -18
  73. data/lib/doh/mysql/hash_row.rb +13 -0
  74. data/lib/doh/mysql/load_sql.rb +1 -0
  75. data/lib/doh/mysql/metadata_util.rb +60 -19
  76. data/lib/doh/mysql/migrate.rb +122 -0
  77. data/lib/doh/mysql/migrate_check.rb +139 -0
  78. data/lib/doh/mysql/parse.rb +2 -0
  79. data/lib/doh/mysql/readonly_row.rb +11 -48
  80. data/lib/doh/mysql/require_dbtypes.rb +8 -0
  81. data/lib/doh/mysql/smart_row.rb +156 -0
  82. data/lib/doh/mysql/to_sql.rb +12 -0
  83. data/lib/doh/mysql/typed_row_builder.rb +4 -3
  84. data/lib/doh/mysql/types.rb +33 -0
  85. data/lib/doh/mysql/unquoted.rb +8 -0
  86. data/lib/doh/mysql/version.rb +102 -0
  87. data/lib/doh/mysql/virtual.rb +17 -0
  88. data/lib/doh/mysql/writable_row.rb +58 -0
  89. data/lib/doh/mysql.rb +2 -1
  90. data/lib/doh/paypal/paypal.rb +20 -0
  91. data/lib/doh/paypal/pdt.rb +14 -0
  92. data/lib/doh/paypal.rb +1 -0
  93. data/lib/doh/rails/form_helpers.rb +53 -0
  94. data/lib/doh/rails/login.rb +143 -0
  95. data/lib/doh/test/error_acceptor.rb +1 -1
  96. data/lib/doh/test/run_tests.rb +48 -43
  97. data/lib/doh/test/setup_once.rb +15 -0
  98. data/lib/doh/test/test_result.rb +7 -0
  99. data/lib/doh/unit_test.rb +6 -0
  100. data/lib/doh/util/banking_workday.rb +16 -12
  101. data/lib/doh/util/class_basename.rb +10 -0
  102. data/lib/doh/util/current_date.rb +18 -41
  103. data/lib/doh/util/doh_socket.rb +56 -0
  104. data/lib/doh/util/email.rb +18 -0
  105. data/lib/doh/util/file_edit.rb +64 -0
  106. data/lib/doh/util/http_helper.rb +107 -0
  107. data/lib/doh/util/internal_ip.rb +1 -1
  108. data/lib/doh/util/jsval.rb +13 -0
  109. data/lib/doh/util/post_hash.rb +14 -0
  110. data/lib/doh/util/xml_util.rb +48 -0
  111. data/test/core/tc_array.rb +12 -0
  112. data/test/core/tc_date.rb +53 -0
  113. data/test/core/tc_deep_dup.rb +69 -0
  114. data/test/core/tc_hash.rb +28 -0
  115. data/test/core/tc_socket.rb +30 -0
  116. data/test/core/tc_string.rb +15 -22
  117. data/test/local_tests.rb +3 -0
  118. data/test/local_tests_including_slow.rb +4 -0
  119. data/test/logger/tc_acceptor.rb +23 -6
  120. data/test/logger/tc_event.rb +1 -1
  121. data/test/logger/tc_formatter.rb +3 -2
  122. data/test/logger/tc_socket_viewer_acceptor.rb +48 -0
  123. data/test/mysql/001_down.sql +1 -0
  124. data/test/mysql/001_up.sql +4 -0
  125. data/test/mysql/002_down.sql +1 -0
  126. data/test/mysql/002_up.sql +1 -0
  127. data/test/mysql/tc_connector_instance.rb +8 -8
  128. data/test/mysql/tc_convert.rb +45 -0
  129. data/test/mysql/tc_handle.rb +94 -2
  130. data/test/mysql/tc_metadata_util.rb +50 -0
  131. data/test/mysql/tc_migrate.rb +50 -0
  132. data/test/mysql/tc_parse.rb +3 -1
  133. data/test/mysql/tc_readonly_row.rb +14 -10
  134. data/test/mysql/tc_smart_row.rb +22 -0
  135. data/test/mysql/tc_to_sql.rb +20 -0
  136. data/test/mysql/tc_types.rb +32 -0
  137. data/test/mysql/tc_unquoted.rb +1 -0
  138. data/test/mysql/tc_writable_row.rb +22 -0
  139. data/test/ts_core.rb +4 -0
  140. data/test/ts_logger.rb +4 -0
  141. data/test/ts_mysql.rb +6 -0
  142. data/test/ts_util.rb +6 -0
  143. data/test/util/slow_doh_socket.rb +102 -0
  144. data/test/util/tc_banking_workday.rb +18 -0
  145. data/test/util/tc_file_edit.rb +54 -0
  146. data/test/util/tc_jsval.rb +12 -0
  147. data/test/util/tc_to_display.rb +14 -0
  148. data/test/util/tc_xml_util.rb +17 -0
  149. metadata +130 -39
  150. data/README +0 -4
  151. data/lib/doh/merb/post_hash.rb +0 -26
  152. data/lib/doh/mysql/db_null.rb +0 -24
  153. data/lib/doh/mysql/hash_util.rb +0 -56
  154. data/test/mysql/tc_hash_util.rb +0 -23
@@ -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/raw_row_builder'
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 || RawRowBuilder
14
+ @row_builder = row_builder || TypedRowBuilder.new
11
15
  end
12
16
 
13
17
  def close
14
18
  unless closed?
15
- Doh::log.info("closing raw mysql handle: #@mysqlh")
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
- Doh::log.info("affected #{retval} rows")
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
- Doh::log.info("updated #{retval} rows")
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
- Doh::log.info("insert_id was #{retval}")
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
- Doh::log.info("selected #{result_set.num_rows} rows")
49
- rows = (row_builder || @row_builder.new).build_rows(result_set)
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
- def update_row(statement)
55
- retval = update(statement)
56
- raise QueryResultError.new("updated #{retval} rows; expected 1") unless retval == 1
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 QueryResultError.new("selected #{rows.size} rows; expected 1") unless rows.size == 1
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 QueryResultError.new("selected #{rows.size} rows; expected 0 or 1") if rows.size > 1
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
- Doh::log.info(sqlstr)
180
+ dohlog.info(sqlstr)
85
181
  @mysqlh.query(sqlstr)
182
+ @mysqlh.store_result if @mysqlh.field_count > 0
86
183
  rescue Exception => excpt
87
- Doh::log.error("caught exception during query: #{sqlstr}", excpt)
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
@@ -0,0 +1,13 @@
1
+ module DohDb
2
+
3
+ class HashRow
4
+ def self.new(keys, values)
5
+ hash = {}
6
+ keys.each_with_index do |key, index|
7
+ hash[key] = values.at(index)
8
+ end
9
+ hash
10
+ end
11
+ end
12
+
13
+ end
@@ -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 Doh
4
+ module DohDb
4
5
 
5
- @@cached_table_info = {}
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.chop_strings(table, row, ignore_non_db_fields = true, log = false)
8
- result = ignore_non_db_fields ? {} : row.dup
9
- self.column_info(table).each do |field, size, data_type|
10
- Doh::log.debug("processing field: #{field.inspect}, size: #{size.inspect}, data_type: #{data_type.inspect}") if log
11
- result[field] = row[field]
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
- result
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.column_info(table)
21
- return @@cached_table_info[table] if @@cached_table_info[table]
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
- def self.query_column_info(table)
26
- column_info = DohDb::select("SELECT column_name, character_maximum_length, data_type FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='#{table}'")
27
- column_info = column_info.collect {|column| [column['column_name'], column['character_maximum_length'], column['data_type']]}
28
- Doh::log.debug("got column info for table: #{table}, info: #{column_info.inspect}")
29
- column_info
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
@@ -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
- module DohDb
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
- def to_h
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
- def each_pair
46
- @keys.size.times do |index|
47
- yield(@keys.at(index), @values.at(index))
48
- end
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 method or field name: " + key)
21
+ raise RuntimeError.new("unknown field: " + key)
59
22
  end
60
23
  end
61
24
  end