outback 0.0.14 → 1.1.0
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 +7 -0
- data/CHANGELOG +11 -0
- data/LICENSE +21 -0
- data/README.md +29 -3
- data/lib/outback/archive.rb +6 -17
- data/lib/outback/backup.rb +38 -20
- data/lib/outback/cli.rb +6 -2
- data/lib/outback/configuration.rb +15 -10
- data/lib/outback/directory_source.rb +8 -7
- data/lib/outback/directory_target.rb +18 -11
- data/lib/outback/encryption_processor.rb +34 -0
- data/lib/outback/errors.rb +7 -0
- data/lib/outback/logging.rb +7 -0
- data/lib/outback/mysql_source.rb +9 -9
- data/lib/outback/processor.rb +17 -0
- data/lib/outback/s3_target.rb +18 -9
- data/lib/outback/sftp_target.rb +70 -0
- data/lib/outback/source.rb +9 -2
- data/lib/outback/source_archive.rb +17 -0
- data/lib/outback/support/attr_setter.rb +1 -1
- data/lib/outback/support/configurable.rb +5 -3
- data/lib/outback/target.rb +52 -14
- data/lib/outback/target_archive.rb +30 -0
- data/lib/outback/version.rb +3 -0
- data/lib/outback.rb +16 -11
- data/lib/vendor/enumerable_ext.rb +9 -0
- data/lib/{outback/vendor → vendor}/metaclass.rb +1 -1
- data/lib/vendor/methodphitamine.rb +28 -0
- data/lib/vendor/mysql/charset.rb +325 -0
- data/lib/vendor/mysql/constants.rb +165 -0
- data/lib/vendor/mysql/error.rb +989 -0
- data/lib/vendor/mysql/packet.rb +78 -0
- data/lib/vendor/mysql/protocol.rb +770 -0
- data/lib/vendor/mysql.rb +1093 -0
- data/lib/vendor/numeric_ext.rb +49 -0
- data/lib/vendor/string_ext.rb +19 -0
- metadata +84 -43
- data/MIT-LICENSE +0 -20
- data/VERSION +0 -1
- data/lib/outback/configuration_error.rb +0 -4
- data/lib/outback/directory_archive.rb +0 -8
- data/lib/outback/local_archive.rb +0 -6
- data/lib/outback/s3_archive.rb +0 -18
- data/lib/outback/temp_archive.rb +0 -5
- data/lib/outback/vendor/methodphitamine.rb +0 -25
- data/lib/outback/vendor/mysql.rb +0 -1214
data/lib/vendor/mysql.rb
ADDED
@@ -0,0 +1,1093 @@
|
|
1
|
+
class Mysql
|
2
|
+
|
3
|
+
require_relative "mysql/constants"
|
4
|
+
require_relative "mysql/error"
|
5
|
+
require_relative "mysql/charset"
|
6
|
+
require_relative "mysql/protocol"
|
7
|
+
require_relative "mysql/packet.rb"
|
8
|
+
begin
|
9
|
+
require_relative "mysql/ext.so"
|
10
|
+
rescue LoadError
|
11
|
+
end
|
12
|
+
|
13
|
+
VERSION = 20913 # Version number of this library
|
14
|
+
MYSQL_UNIX_PORT = "/tmp/mysql.sock" # UNIX domain socket filename
|
15
|
+
MYSQL_TCP_PORT = 3306 # TCP socket port number
|
16
|
+
|
17
|
+
# @return [Mysql::Charset] character set of MySQL connection
|
18
|
+
attr_reader :charset
|
19
|
+
# @private
|
20
|
+
attr_reader :protocol
|
21
|
+
|
22
|
+
# @return [Boolean] if true, {#query} return {Mysql::Result}.
|
23
|
+
attr_accessor :query_with_result
|
24
|
+
|
25
|
+
class << self
|
26
|
+
# Make Mysql object without connecting.
|
27
|
+
# @return [Mysql]
|
28
|
+
def init
|
29
|
+
my = self.allocate
|
30
|
+
my.instance_eval{initialize}
|
31
|
+
my
|
32
|
+
end
|
33
|
+
|
34
|
+
# Make Mysql object and connect to mysqld.
|
35
|
+
# @param args same as arguments for {#connect}.
|
36
|
+
# @return [Mysql]
|
37
|
+
def new(...)
|
38
|
+
my = self.init
|
39
|
+
my.connect(...)
|
40
|
+
end
|
41
|
+
|
42
|
+
alias real_connect new
|
43
|
+
alias connect new
|
44
|
+
|
45
|
+
# Escape special character in string.
|
46
|
+
# @param [String] str
|
47
|
+
# @return [String]
|
48
|
+
def escape_string(str)
|
49
|
+
str.gsub(/[\0\n\r\\\'\"\x1a]/) do |s|
|
50
|
+
case s
|
51
|
+
when "\0" then "\\0"
|
52
|
+
when "\n" then "\\n"
|
53
|
+
when "\r" then "\\r"
|
54
|
+
when "\x1a" then "\\Z"
|
55
|
+
else "\\#{s}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
alias quote escape_string
|
60
|
+
|
61
|
+
# @return [String] client version. This value is dummy for MySQL/Ruby compatibility.
|
62
|
+
def client_info
|
63
|
+
"5.0.0"
|
64
|
+
end
|
65
|
+
alias get_client_info client_info
|
66
|
+
|
67
|
+
# @return [Integer] client version. This value is dummy for MySQL/Ruby compatibility.
|
68
|
+
def client_version
|
69
|
+
50000
|
70
|
+
end
|
71
|
+
alias get_client_version client_version
|
72
|
+
end
|
73
|
+
|
74
|
+
def initialize
|
75
|
+
@fields = nil
|
76
|
+
@protocol = nil
|
77
|
+
@charset = nil
|
78
|
+
@connect_timeout = nil
|
79
|
+
@read_timeout = nil
|
80
|
+
@write_timeout = nil
|
81
|
+
@init_command = nil
|
82
|
+
@sqlstate = "00000"
|
83
|
+
@query_with_result = true
|
84
|
+
@host_info = nil
|
85
|
+
@last_error = nil
|
86
|
+
@result_exist = false
|
87
|
+
@local_infile = nil
|
88
|
+
end
|
89
|
+
|
90
|
+
# Connect to mysqld.
|
91
|
+
# @param [String / nil] host hostname mysqld running
|
92
|
+
# @param [String / nil] user username to connect to mysqld
|
93
|
+
# @param [String / nil] passwd password to connect to mysqld
|
94
|
+
# @param [String / nil] db initial database name
|
95
|
+
# @param [Integer / nil] port port number (used if host is not 'localhost' or nil)
|
96
|
+
# @param [String / nil] socket socket file name (used if host is 'localhost' or nil)
|
97
|
+
# @param [Integer / nil] flag connection flag. Mysql::CLIENT_* ORed
|
98
|
+
# @return self
|
99
|
+
def connect(host=nil, user=nil, passwd=nil, db=nil, port=nil, socket=nil, flag=0)
|
100
|
+
if flag & CLIENT_COMPRESS != 0
|
101
|
+
warn 'unsupported flag: CLIENT_COMPRESS' if $VERBOSE
|
102
|
+
flag &= ~CLIENT_COMPRESS
|
103
|
+
end
|
104
|
+
@protocol = Protocol.new host, port, socket, @connect_timeout, @read_timeout, @write_timeout
|
105
|
+
@protocol.authenticate user, passwd, db, (@local_infile ? CLIENT_LOCAL_FILES : 0) | flag, @charset
|
106
|
+
@charset ||= @protocol.charset
|
107
|
+
@host_info = (host.nil? || host == "localhost") ? 'Localhost via UNIX socket' : "#{host} via TCP/IP"
|
108
|
+
query @init_command if @init_command
|
109
|
+
return self
|
110
|
+
end
|
111
|
+
alias real_connect connect
|
112
|
+
|
113
|
+
# Disconnect from mysql.
|
114
|
+
# @return [Mysql] self
|
115
|
+
def close
|
116
|
+
if @protocol
|
117
|
+
@protocol.quit_command
|
118
|
+
@protocol = nil
|
119
|
+
end
|
120
|
+
return self
|
121
|
+
end
|
122
|
+
|
123
|
+
# Disconnect from mysql without QUIT packet.
|
124
|
+
# @return [Mysql] self
|
125
|
+
def close!
|
126
|
+
if @protocol
|
127
|
+
@protocol.close
|
128
|
+
@protocol = nil
|
129
|
+
end
|
130
|
+
return self
|
131
|
+
end
|
132
|
+
|
133
|
+
# Set option for connection.
|
134
|
+
#
|
135
|
+
# Available options:
|
136
|
+
# Mysql::INIT_COMMAND, Mysql::OPT_CONNECT_TIMEOUT, Mysql::OPT_READ_TIMEOUT,
|
137
|
+
# Mysql::OPT_WRITE_TIMEOUT, Mysql::SET_CHARSET_NAME
|
138
|
+
# @param [Integer] opt option
|
139
|
+
# @param [Integer] value option value that is depend on opt
|
140
|
+
# @return [Mysql] self
|
141
|
+
def options(opt, value=nil)
|
142
|
+
case opt
|
143
|
+
when Mysql::INIT_COMMAND
|
144
|
+
@init_command = value.to_s
|
145
|
+
# when Mysql::OPT_COMPRESS
|
146
|
+
when Mysql::OPT_CONNECT_TIMEOUT
|
147
|
+
@connect_timeout = value
|
148
|
+
# when Mysql::GUESS_CONNECTION
|
149
|
+
when Mysql::OPT_LOCAL_INFILE
|
150
|
+
@local_infile = value
|
151
|
+
# when Mysql::OPT_NAMED_PIPE
|
152
|
+
# when Mysql::OPT_PROTOCOL
|
153
|
+
when Mysql::OPT_READ_TIMEOUT
|
154
|
+
@read_timeout = value.to_i
|
155
|
+
# when Mysql::OPT_RECONNECT
|
156
|
+
# when Mysql::SET_CLIENT_IP
|
157
|
+
# when Mysql::OPT_SSL_VERIFY_SERVER_CERT
|
158
|
+
# when Mysql::OPT_USE_EMBEDDED_CONNECTION
|
159
|
+
# when Mysql::OPT_USE_REMOTE_CONNECTION
|
160
|
+
when Mysql::OPT_WRITE_TIMEOUT
|
161
|
+
@write_timeout = value.to_i
|
162
|
+
# when Mysql::READ_DEFAULT_FILE
|
163
|
+
# when Mysql::READ_DEFAULT_GROUP
|
164
|
+
# when Mysql::REPORT_DATA_TRUNCATION
|
165
|
+
# when Mysql::SECURE_AUTH
|
166
|
+
# when Mysql::SET_CHARSET_DIR
|
167
|
+
when Mysql::SET_CHARSET_NAME
|
168
|
+
@charset = Charset.by_name value.to_s
|
169
|
+
# when Mysql::SHARED_MEMORY_BASE_NAME
|
170
|
+
else
|
171
|
+
warn "option not implemented: #{opt}" if $VERBOSE
|
172
|
+
end
|
173
|
+
self
|
174
|
+
end
|
175
|
+
|
176
|
+
# Escape special character in MySQL.
|
177
|
+
#
|
178
|
+
# In Ruby 1.8, this is not safe for multibyte charset such as 'SJIS'.
|
179
|
+
# You should use place-holder in prepared-statement.
|
180
|
+
# @param [String] str
|
181
|
+
# return [String]
|
182
|
+
def escape_string(str)
|
183
|
+
if not defined? Encoding and @charset.unsafe
|
184
|
+
raise ClientError, 'Mysql#escape_string is called for unsafe multibyte charset'
|
185
|
+
end
|
186
|
+
self.class.escape_string str
|
187
|
+
end
|
188
|
+
alias quote escape_string
|
189
|
+
|
190
|
+
# @return [String] client version
|
191
|
+
def client_info
|
192
|
+
self.class.client_info
|
193
|
+
end
|
194
|
+
alias get_client_info client_info
|
195
|
+
|
196
|
+
# @return [Integer] client version
|
197
|
+
def client_version
|
198
|
+
self.class.client_version
|
199
|
+
end
|
200
|
+
alias get_client_version client_version
|
201
|
+
|
202
|
+
# Set charset of MySQL connection.
|
203
|
+
# @param [String / Mysql::Charset] cs
|
204
|
+
def charset=(cs)
|
205
|
+
charset = cs.is_a?(Charset) ? cs : Charset.by_name(cs)
|
206
|
+
if @protocol
|
207
|
+
@protocol.charset = charset
|
208
|
+
query "SET NAMES #{charset.name}"
|
209
|
+
end
|
210
|
+
@charset = charset
|
211
|
+
cs
|
212
|
+
end
|
213
|
+
|
214
|
+
# @return [String] charset name
|
215
|
+
def character_set_name
|
216
|
+
@charset.name
|
217
|
+
end
|
218
|
+
|
219
|
+
# @return [Integer] last error number
|
220
|
+
def errno
|
221
|
+
@last_error ? @last_error.errno : 0
|
222
|
+
end
|
223
|
+
|
224
|
+
# @return [String] last error message
|
225
|
+
def error
|
226
|
+
@last_error && @last_error.error
|
227
|
+
end
|
228
|
+
|
229
|
+
# @return [String] sqlstate for last error
|
230
|
+
def sqlstate
|
231
|
+
@last_error ? @last_error.sqlstate : "00000"
|
232
|
+
end
|
233
|
+
|
234
|
+
# @return [Integer] number of columns for last query
|
235
|
+
def field_count
|
236
|
+
@fields.size
|
237
|
+
end
|
238
|
+
|
239
|
+
# @return [String] connection type
|
240
|
+
def host_info
|
241
|
+
@host_info
|
242
|
+
end
|
243
|
+
alias get_host_info host_info
|
244
|
+
|
245
|
+
# @return [Integer] protocol version
|
246
|
+
def proto_info
|
247
|
+
Mysql::Protocol::VERSION
|
248
|
+
end
|
249
|
+
alias get_proto_info proto_info
|
250
|
+
|
251
|
+
# @return [String] server version
|
252
|
+
def server_info
|
253
|
+
check_connection
|
254
|
+
@protocol.server_info
|
255
|
+
end
|
256
|
+
alias get_server_info server_info
|
257
|
+
|
258
|
+
# @return [Integer] server version
|
259
|
+
def server_version
|
260
|
+
check_connection
|
261
|
+
@protocol.server_version
|
262
|
+
end
|
263
|
+
alias get_server_version server_version
|
264
|
+
|
265
|
+
# @return [String] information for last query
|
266
|
+
def info
|
267
|
+
@protocol && @protocol.message
|
268
|
+
end
|
269
|
+
|
270
|
+
# @return [Integer] number of affected records by insert/update/delete.
|
271
|
+
def affected_rows
|
272
|
+
@protocol ? @protocol.affected_rows : 0
|
273
|
+
end
|
274
|
+
|
275
|
+
# @return [Integer] latest auto_increment value
|
276
|
+
def insert_id
|
277
|
+
@protocol ? @protocol.insert_id : 0
|
278
|
+
end
|
279
|
+
|
280
|
+
# @return [Integer] number of warnings for previous query
|
281
|
+
def warning_count
|
282
|
+
@protocol ? @protocol.warning_count : 0
|
283
|
+
end
|
284
|
+
|
285
|
+
# Kill query.
|
286
|
+
# @param [Integer] pid thread id
|
287
|
+
# @return [Mysql] self
|
288
|
+
def kill(pid)
|
289
|
+
check_connection
|
290
|
+
@protocol.kill_command pid
|
291
|
+
self
|
292
|
+
end
|
293
|
+
|
294
|
+
# database list.
|
295
|
+
# @param [String] db database name that may contain wild card.
|
296
|
+
# @return [Array<String>] database list
|
297
|
+
def list_dbs(db=nil)
|
298
|
+
db &&= db.gsub(/[\\\']/){"\\#{$&}"}
|
299
|
+
query(db ? "show databases like '#{db}'" : "show databases").map(&:first)
|
300
|
+
end
|
301
|
+
|
302
|
+
# Execute query string.
|
303
|
+
# @param [String] str Query.
|
304
|
+
# @yield [Mysql::Result] evaluated per query.
|
305
|
+
# @return [Mysql::Result] If {#query_with_result} is true and result set exist.
|
306
|
+
# @return [nil] If {#query_with_result} is true and the query does not return result set.
|
307
|
+
# @return [Mysql] If {#query_with_result} is false or block is specified
|
308
|
+
# @example
|
309
|
+
# my.query("select 1,NULL,'abc'").fetch # => [1, nil, "abc"]
|
310
|
+
def query(str, &block)
|
311
|
+
check_connection
|
312
|
+
@fields = nil
|
313
|
+
begin
|
314
|
+
nfields = @protocol.query_command str
|
315
|
+
if nfields
|
316
|
+
@fields = @protocol.retr_fields nfields
|
317
|
+
@result_exist = true
|
318
|
+
end
|
319
|
+
if block
|
320
|
+
while true
|
321
|
+
block.call store_result if @fields
|
322
|
+
break unless next_result
|
323
|
+
end
|
324
|
+
return self
|
325
|
+
end
|
326
|
+
if @query_with_result
|
327
|
+
return @fields ? store_result : nil
|
328
|
+
else
|
329
|
+
return self
|
330
|
+
end
|
331
|
+
rescue ServerError => e
|
332
|
+
@last_error = e
|
333
|
+
@sqlstate = e.sqlstate
|
334
|
+
raise
|
335
|
+
end
|
336
|
+
end
|
337
|
+
alias real_query query
|
338
|
+
|
339
|
+
# Get all data for last query if query_with_result is false.
|
340
|
+
# @return [Mysql::Result]
|
341
|
+
def store_result
|
342
|
+
check_connection
|
343
|
+
raise ClientError, 'invalid usage' unless @result_exist
|
344
|
+
res = Result.new @fields, @protocol
|
345
|
+
@result_exist = false
|
346
|
+
res
|
347
|
+
end
|
348
|
+
|
349
|
+
# @return [Integer] Thread ID
|
350
|
+
def thread_id
|
351
|
+
check_connection
|
352
|
+
@protocol.thread_id
|
353
|
+
end
|
354
|
+
|
355
|
+
# Use result of query. The result data is retrieved when you use Mysql::Result#fetch.
|
356
|
+
# @return [Mysql::Result]
|
357
|
+
def use_result
|
358
|
+
store_result
|
359
|
+
end
|
360
|
+
|
361
|
+
# Set server option.
|
362
|
+
# @param [Integer] opt {Mysql::OPTION_MULTI_STATEMENTS_ON} or {Mysql::OPTION_MULTI_STATEMENTS_OFF}
|
363
|
+
# @return [Mysql] self
|
364
|
+
def set_server_option(opt)
|
365
|
+
check_connection
|
366
|
+
@protocol.set_option_command opt
|
367
|
+
self
|
368
|
+
end
|
369
|
+
|
370
|
+
# @return [Boolean] true if multiple queries are specified and unexecuted queries exists.
|
371
|
+
def more_results
|
372
|
+
@protocol.server_status & SERVER_MORE_RESULTS_EXISTS != 0
|
373
|
+
end
|
374
|
+
alias more_results? more_results
|
375
|
+
|
376
|
+
# execute next query if multiple queries are specified.
|
377
|
+
# @return [Boolean] true if next query exists.
|
378
|
+
def next_result
|
379
|
+
return false unless more_results
|
380
|
+
check_connection
|
381
|
+
@fields = nil
|
382
|
+
nfields = @protocol.get_result
|
383
|
+
if nfields
|
384
|
+
@fields = @protocol.retr_fields nfields
|
385
|
+
@result_exist = true
|
386
|
+
end
|
387
|
+
return true
|
388
|
+
end
|
389
|
+
|
390
|
+
# Parse prepared-statement.
|
391
|
+
# @param [String] str query string
|
392
|
+
# @return [Mysql::Stmt] Prepared-statement object
|
393
|
+
def prepare(str)
|
394
|
+
st = Stmt.new @protocol, @charset
|
395
|
+
st.prepare str
|
396
|
+
st
|
397
|
+
end
|
398
|
+
|
399
|
+
# @private
|
400
|
+
# Make empty prepared-statement object.
|
401
|
+
# @return [Mysql::Stmt] If block is not specified.
|
402
|
+
def stmt_init
|
403
|
+
Stmt.new @protocol, @charset
|
404
|
+
end
|
405
|
+
|
406
|
+
# Returns Mysql::Result object that is empty.
|
407
|
+
# Use fetch_fields to get list of fields.
|
408
|
+
# @param [String] table table name.
|
409
|
+
# @param [String] field field name that may contain wild card.
|
410
|
+
# @return [Mysql::Result]
|
411
|
+
def list_fields(table, field=nil)
|
412
|
+
check_connection
|
413
|
+
begin
|
414
|
+
fields = @protocol.field_list_command table, field
|
415
|
+
return Result.new fields
|
416
|
+
rescue ServerError => e
|
417
|
+
@last_error = e
|
418
|
+
@sqlstate = e.sqlstate
|
419
|
+
raise
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
# @return [Mysql::Result] containing process list
|
424
|
+
def list_processes
|
425
|
+
check_connection
|
426
|
+
@fields = @protocol.process_info_command
|
427
|
+
@result_exist = true
|
428
|
+
store_result
|
429
|
+
end
|
430
|
+
|
431
|
+
# @note for Ruby 1.8: This is not multi-byte safe. Don't use for multi-byte charset such as cp932.
|
432
|
+
# @param [String] table database name that may contain wild card.
|
433
|
+
# @return [Array<String>] list of table name.
|
434
|
+
def list_tables(table=nil)
|
435
|
+
q = table ? "show tables like '#{quote table}'" : "show tables"
|
436
|
+
query(q).map(&:first)
|
437
|
+
end
|
438
|
+
|
439
|
+
# Check whether the connection is available.
|
440
|
+
# @return [Mysql] self
|
441
|
+
def ping
|
442
|
+
check_connection
|
443
|
+
@protocol.ping_command
|
444
|
+
self
|
445
|
+
end
|
446
|
+
|
447
|
+
# Flush tables or caches.
|
448
|
+
# @param [Integer] op operation. Use Mysql::REFRESH_* value.
|
449
|
+
# @return [Mysql] self
|
450
|
+
def refresh(op)
|
451
|
+
check_connection
|
452
|
+
@protocol.refresh_command op
|
453
|
+
self
|
454
|
+
end
|
455
|
+
|
456
|
+
# Reload grant tables.
|
457
|
+
# @return [Mysql] self
|
458
|
+
def reload
|
459
|
+
refresh Mysql::REFRESH_GRANT
|
460
|
+
end
|
461
|
+
|
462
|
+
# Select default database
|
463
|
+
# @return [Mysql] self
|
464
|
+
def select_db(db)
|
465
|
+
query "use #{db}"
|
466
|
+
self
|
467
|
+
end
|
468
|
+
|
469
|
+
# shutdown server.
|
470
|
+
# @return [Mysql] self
|
471
|
+
def shutdown(level=0)
|
472
|
+
check_connection
|
473
|
+
@protocol.shutdown_command level
|
474
|
+
self
|
475
|
+
end
|
476
|
+
|
477
|
+
# @return [String] statistics message
|
478
|
+
def stat
|
479
|
+
@protocol ? @protocol.statistics_command : 'MySQL server has gone away'
|
480
|
+
end
|
481
|
+
|
482
|
+
# Commit transaction
|
483
|
+
# @return [Mysql] self
|
484
|
+
def commit
|
485
|
+
query 'commit'
|
486
|
+
self
|
487
|
+
end
|
488
|
+
|
489
|
+
# Rollback transaction
|
490
|
+
# @return [Mysql] self
|
491
|
+
def rollback
|
492
|
+
query 'rollback'
|
493
|
+
self
|
494
|
+
end
|
495
|
+
|
496
|
+
# Set autocommit mode
|
497
|
+
# @param [Boolean] flag
|
498
|
+
# @return [Mysql] self
|
499
|
+
def autocommit(flag)
|
500
|
+
query "set autocommit=#{flag ? 1 : 0}"
|
501
|
+
self
|
502
|
+
end
|
503
|
+
|
504
|
+
private
|
505
|
+
|
506
|
+
def check_connection
|
507
|
+
raise ClientError::ServerGoneError, 'MySQL server has gone away' unless @protocol
|
508
|
+
end
|
509
|
+
|
510
|
+
# @!visibility public
|
511
|
+
# Field class
|
512
|
+
class Field
|
513
|
+
# @return [String] database name
|
514
|
+
attr_reader :db
|
515
|
+
# @return [String] table name
|
516
|
+
attr_reader :table
|
517
|
+
# @return [String] original table name
|
518
|
+
attr_reader :org_table
|
519
|
+
# @return [String] field name
|
520
|
+
attr_reader :name
|
521
|
+
# @return [String] original field name
|
522
|
+
attr_reader :org_name
|
523
|
+
# @return [Integer] charset id number
|
524
|
+
attr_reader :charsetnr
|
525
|
+
# @return [Integer] field length
|
526
|
+
attr_reader :length
|
527
|
+
# @return [Integer] field type
|
528
|
+
attr_reader :type
|
529
|
+
# @return [Integer] flag
|
530
|
+
attr_reader :flags
|
531
|
+
# @return [Integer] number of decimals
|
532
|
+
attr_reader :decimals
|
533
|
+
# @return [String] defualt value
|
534
|
+
attr_reader :default
|
535
|
+
alias :def :default
|
536
|
+
|
537
|
+
# @private
|
538
|
+
attr_accessor :result
|
539
|
+
|
540
|
+
# @attr [Protocol::FieldPacket] packet
|
541
|
+
def initialize(packet)
|
542
|
+
@db, @table, @org_table, @name, @org_name, @charsetnr, @length, @type, @flags, @decimals, @default =
|
543
|
+
packet.db, packet.table, packet.org_table, packet.name, packet.org_name, packet.charsetnr, packet.length, packet.type, packet.flags, packet.decimals, packet.default
|
544
|
+
@flags |= NUM_FLAG if is_num_type?
|
545
|
+
@max_length = nil
|
546
|
+
end
|
547
|
+
|
548
|
+
# @return [Hash] field information
|
549
|
+
def hash
|
550
|
+
{
|
551
|
+
"name" => @name,
|
552
|
+
"table" => @table,
|
553
|
+
"def" => @default,
|
554
|
+
"type" => @type,
|
555
|
+
"length" => @length,
|
556
|
+
"max_length" => max_length,
|
557
|
+
"flags" => @flags,
|
558
|
+
"decimals" => @decimals
|
559
|
+
}
|
560
|
+
end
|
561
|
+
|
562
|
+
# @private
|
563
|
+
def inspect
|
564
|
+
"#<Mysql::Field:#{@name}>"
|
565
|
+
end
|
566
|
+
|
567
|
+
# @return [Boolean] true if numeric field.
|
568
|
+
def is_num?
|
569
|
+
@flags & NUM_FLAG != 0
|
570
|
+
end
|
571
|
+
|
572
|
+
# @return [Boolean] true if not null field.
|
573
|
+
def is_not_null?
|
574
|
+
@flags & NOT_NULL_FLAG != 0
|
575
|
+
end
|
576
|
+
|
577
|
+
# @return [Boolean] true if primary key field.
|
578
|
+
def is_pri_key?
|
579
|
+
@flags & PRI_KEY_FLAG != 0
|
580
|
+
end
|
581
|
+
|
582
|
+
# @return [Integer] maximum width of the field for the result set
|
583
|
+
def max_length
|
584
|
+
return @max_length if @max_length
|
585
|
+
@max_length = 0
|
586
|
+
@result.calculate_field_max_length if @result
|
587
|
+
@max_length
|
588
|
+
end
|
589
|
+
|
590
|
+
attr_writer :max_length
|
591
|
+
|
592
|
+
private
|
593
|
+
|
594
|
+
def is_num_type?
|
595
|
+
[TYPE_DECIMAL, TYPE_TINY, TYPE_SHORT, TYPE_LONG, TYPE_FLOAT, TYPE_DOUBLE, TYPE_LONGLONG, TYPE_INT24].include?(@type) || (@type == TYPE_TIMESTAMP && (@length == 14 || @length == 8))
|
596
|
+
end
|
597
|
+
|
598
|
+
end
|
599
|
+
|
600
|
+
# @!visibility public
|
601
|
+
# Result set
|
602
|
+
class ResultBase
|
603
|
+
include Enumerable
|
604
|
+
|
605
|
+
# @return [Array<Mysql::Field>] field list
|
606
|
+
attr_reader :fields
|
607
|
+
|
608
|
+
# @param [Array of Mysql::Field] fields
|
609
|
+
def initialize(fields)
|
610
|
+
@fields = fields
|
611
|
+
@field_index = 0 # index of field
|
612
|
+
@records = [] # all records
|
613
|
+
@index = 0 # index of record
|
614
|
+
@fieldname_with_table = nil
|
615
|
+
@fetched_record = nil
|
616
|
+
end
|
617
|
+
|
618
|
+
# ignore
|
619
|
+
# @return [void]
|
620
|
+
def free
|
621
|
+
end
|
622
|
+
|
623
|
+
# @return [Integer] number of record
|
624
|
+
def size
|
625
|
+
@records.size
|
626
|
+
end
|
627
|
+
alias num_rows size
|
628
|
+
|
629
|
+
# @return [Array] current record data
|
630
|
+
def fetch
|
631
|
+
@fetched_record = nil
|
632
|
+
return nil if @index >= @records.size
|
633
|
+
@records[@index] = @records[@index].to_a unless @records[@index].is_a? Array
|
634
|
+
@fetched_record = @records[@index]
|
635
|
+
@index += 1
|
636
|
+
return @fetched_record
|
637
|
+
end
|
638
|
+
alias fetch_row fetch
|
639
|
+
|
640
|
+
# Return data of current record as Hash.
|
641
|
+
# The hash key is field name.
|
642
|
+
# @param [Boolean] with_table if true, hash key is "table_name.field_name".
|
643
|
+
# @return [Hash] current record data
|
644
|
+
def fetch_hash(with_table=nil)
|
645
|
+
row = fetch
|
646
|
+
return nil unless row
|
647
|
+
if with_table and @fieldname_with_table.nil?
|
648
|
+
@fieldname_with_table = @fields.map{|f| [f.table, f.name].join(".")}
|
649
|
+
end
|
650
|
+
ret = {}
|
651
|
+
@fields.each_index do |i|
|
652
|
+
fname = with_table ? @fieldname_with_table[i] : @fields[i].name
|
653
|
+
ret[fname] = row[i]
|
654
|
+
end
|
655
|
+
ret
|
656
|
+
end
|
657
|
+
|
658
|
+
# Iterate block with record.
|
659
|
+
# @yield [Array] record data
|
660
|
+
# @return [self] self. If block is not specified, this returns Enumerator.
|
661
|
+
def each(&block)
|
662
|
+
return enum_for(:each) unless block
|
663
|
+
while rec = fetch
|
664
|
+
block.call rec
|
665
|
+
end
|
666
|
+
self
|
667
|
+
end
|
668
|
+
|
669
|
+
# Iterate block with record as Hash.
|
670
|
+
# @param [Boolean] with_table if true, hash key is "table_name.field_name".
|
671
|
+
# @yield [Hash] record data
|
672
|
+
# @return [self] self. If block is not specified, this returns Enumerator.
|
673
|
+
def each_hash(with_table=nil, &block)
|
674
|
+
return enum_for(:each_hash, with_table) unless block
|
675
|
+
while rec = fetch_hash(with_table)
|
676
|
+
block.call rec
|
677
|
+
end
|
678
|
+
self
|
679
|
+
end
|
680
|
+
|
681
|
+
# Set record position
|
682
|
+
# @param [Integer] n record index
|
683
|
+
# @return [self] self
|
684
|
+
def data_seek(n)
|
685
|
+
@index = n
|
686
|
+
self
|
687
|
+
end
|
688
|
+
|
689
|
+
# @return [Integer] current record position
|
690
|
+
def row_tell
|
691
|
+
@index
|
692
|
+
end
|
693
|
+
|
694
|
+
# Set current position of record
|
695
|
+
# @param [Integer] n record index
|
696
|
+
# @return [Integer] previous position
|
697
|
+
def row_seek(n)
|
698
|
+
ret = @index
|
699
|
+
@index = n
|
700
|
+
ret
|
701
|
+
end
|
702
|
+
end
|
703
|
+
|
704
|
+
# @!visibility public
|
705
|
+
# Result set for simple query
|
706
|
+
class Result < ResultBase
|
707
|
+
# @private
|
708
|
+
# @param [Array<Mysql::Field>] fields
|
709
|
+
# @param [Mysql::Protocol] protocol
|
710
|
+
def initialize(fields, protocol=nil)
|
711
|
+
super fields
|
712
|
+
return unless protocol
|
713
|
+
@records = protocol.retr_all_records fields
|
714
|
+
fields.each{|f| f.result = self} # for calculating max_field
|
715
|
+
end
|
716
|
+
|
717
|
+
# @private
|
718
|
+
# calculate max_length of all fields
|
719
|
+
def calculate_field_max_length
|
720
|
+
max_length = Array.new(@fields.size, 0)
|
721
|
+
@records.each_with_index do |rec, i|
|
722
|
+
rec = @records[i] = rec.to_a if rec.is_a? RawRecord
|
723
|
+
max_length.each_index do |i|
|
724
|
+
max_length[i] = rec[i].length if rec[i] && rec[i].length > max_length[i]
|
725
|
+
end
|
726
|
+
end
|
727
|
+
max_length.each_with_index do |len, i|
|
728
|
+
@fields[i].max_length = len
|
729
|
+
end
|
730
|
+
end
|
731
|
+
|
732
|
+
# @return [Mysql::Field] current field
|
733
|
+
def fetch_field
|
734
|
+
return nil if @field_index >= @fields.length
|
735
|
+
ret = @fields[@field_index]
|
736
|
+
@field_index += 1
|
737
|
+
ret
|
738
|
+
end
|
739
|
+
|
740
|
+
# @return [Integer] current field position
|
741
|
+
def field_tell
|
742
|
+
@field_index
|
743
|
+
end
|
744
|
+
|
745
|
+
# Set field position
|
746
|
+
# @param [Integer] n field index
|
747
|
+
# @return [Integer] previous position
|
748
|
+
def field_seek(n)
|
749
|
+
ret = @field_index
|
750
|
+
@field_index = n
|
751
|
+
ret
|
752
|
+
end
|
753
|
+
|
754
|
+
# Return specified field
|
755
|
+
# @param [Integer] n field index
|
756
|
+
# @return [Mysql::Field] field
|
757
|
+
def fetch_field_direct(n)
|
758
|
+
raise ClientError, "invalid argument: #{n}" if n < 0 or n >= @fields.length
|
759
|
+
@fields[n]
|
760
|
+
end
|
761
|
+
|
762
|
+
# @return [Array<Mysql::Field>] all fields
|
763
|
+
def fetch_fields
|
764
|
+
@fields
|
765
|
+
end
|
766
|
+
|
767
|
+
# @return [Array<Integer>] length of each fields
|
768
|
+
def fetch_lengths
|
769
|
+
return nil unless @fetched_record
|
770
|
+
@fetched_record.map{|c|c.nil? ? 0 : c.length}
|
771
|
+
end
|
772
|
+
|
773
|
+
# @return [Integer] number of fields
|
774
|
+
def num_fields
|
775
|
+
@fields.size
|
776
|
+
end
|
777
|
+
end
|
778
|
+
|
779
|
+
# @!visibility private
|
780
|
+
# Result set for prepared statement
|
781
|
+
class StatementResult < ResultBase
|
782
|
+
# @private
|
783
|
+
# @param [Array<Mysql::Field>] fields
|
784
|
+
# @param [Mysql::Protocol] protocol
|
785
|
+
# @param [Mysql::Charset] charset
|
786
|
+
def initialize(fields, protocol, charset)
|
787
|
+
super fields
|
788
|
+
@records = protocol.stmt_retr_all_records @fields, charset
|
789
|
+
end
|
790
|
+
end
|
791
|
+
|
792
|
+
# @!visibility public
|
793
|
+
# Prepared statement
|
794
|
+
# @!attribute [r] affected_rows
|
795
|
+
# @return [Integer]
|
796
|
+
# @!attribute [r] insert_id
|
797
|
+
# @return [Integer]
|
798
|
+
# @!attribute [r] server_status
|
799
|
+
# @return [Integer]
|
800
|
+
# @!attribute [r] warning_count
|
801
|
+
# @return [Integer]
|
802
|
+
# @!attribute [r] param_count
|
803
|
+
# @return [Integer]
|
804
|
+
# @!attribute [r] fields
|
805
|
+
# @return [Array<Mysql::Field>]
|
806
|
+
# @!attribute [r] sqlstate
|
807
|
+
# @return [String]
|
808
|
+
class Stmt
|
809
|
+
include Enumerable
|
810
|
+
|
811
|
+
attr_reader :affected_rows, :insert_id, :server_status, :warning_count
|
812
|
+
attr_reader :param_count, :fields, :sqlstate
|
813
|
+
|
814
|
+
# @private
|
815
|
+
def self.finalizer(protocol, statement_id)
|
816
|
+
proc do
|
817
|
+
protocol.gc_stmt statement_id
|
818
|
+
end
|
819
|
+
end
|
820
|
+
|
821
|
+
# @private
|
822
|
+
# @param [Mysql::Protocol] protocol
|
823
|
+
# @param [Mysql::Charset] charset
|
824
|
+
def initialize(protocol, charset)
|
825
|
+
@protocol = protocol
|
826
|
+
@charset = charset
|
827
|
+
@statement_id = nil
|
828
|
+
@affected_rows = @insert_id = @server_status = @warning_count = 0
|
829
|
+
@sqlstate = "00000"
|
830
|
+
@param_count = nil
|
831
|
+
@bind_result = nil
|
832
|
+
end
|
833
|
+
|
834
|
+
# @private
|
835
|
+
# parse prepared-statement and return {Mysql::Stmt} object
|
836
|
+
# @param [String] str query string
|
837
|
+
# @return self
|
838
|
+
def prepare(str)
|
839
|
+
close
|
840
|
+
begin
|
841
|
+
@sqlstate = "00000"
|
842
|
+
@statement_id, @param_count, @fields = @protocol.stmt_prepare_command(str)
|
843
|
+
rescue ServerError => e
|
844
|
+
@last_error = e
|
845
|
+
@sqlstate = e.sqlstate
|
846
|
+
raise
|
847
|
+
end
|
848
|
+
ObjectSpace.define_finalizer(self, self.class.finalizer(@protocol, @statement_id))
|
849
|
+
self
|
850
|
+
end
|
851
|
+
|
852
|
+
# Execute prepared statement.
|
853
|
+
# @param [Object] values values passed to query
|
854
|
+
# @return [Mysql::Stmt] self
|
855
|
+
def execute(*values)
|
856
|
+
raise ClientError, "not prepared" unless @param_count
|
857
|
+
raise ClientError, "parameter count mismatch" if values.length != @param_count
|
858
|
+
values = values.map{|v| @charset.convert v}
|
859
|
+
begin
|
860
|
+
@sqlstate = "00000"
|
861
|
+
nfields = @protocol.stmt_execute_command @statement_id, values
|
862
|
+
if nfields
|
863
|
+
@fields = @protocol.retr_fields nfields
|
864
|
+
@result = StatementResult.new @fields, @protocol, @charset
|
865
|
+
else
|
866
|
+
@affected_rows, @insert_id, @server_status, @warning_count, @info =
|
867
|
+
@protocol.affected_rows, @protocol.insert_id, @protocol.server_status, @protocol.warning_count, @protocol.message
|
868
|
+
end
|
869
|
+
return self
|
870
|
+
rescue ServerError => e
|
871
|
+
@last_error = e
|
872
|
+
@sqlstate = e.sqlstate
|
873
|
+
raise
|
874
|
+
end
|
875
|
+
end
|
876
|
+
|
877
|
+
# Close prepared statement
|
878
|
+
# @return [void]
|
879
|
+
def close
|
880
|
+
ObjectSpace.undefine_finalizer(self)
|
881
|
+
@protocol.stmt_close_command @statement_id if @statement_id
|
882
|
+
@statement_id = nil
|
883
|
+
end
|
884
|
+
|
885
|
+
# @return [Array] current record data
|
886
|
+
def fetch
|
887
|
+
row = @result.fetch
|
888
|
+
return row unless @bind_result
|
889
|
+
row.zip(@bind_result).map do |col, type|
|
890
|
+
if col.nil?
|
891
|
+
nil
|
892
|
+
elsif [Numeric, Integer, Fixnum].include? type
|
893
|
+
col.to_i
|
894
|
+
elsif type == String
|
895
|
+
col.to_s
|
896
|
+
elsif type == Float && !col.is_a?(Float)
|
897
|
+
col.to_i.to_f
|
898
|
+
elsif type == Mysql::Time && !col.is_a?(Mysql::Time)
|
899
|
+
if col.to_s =~ /\A\d+\z/
|
900
|
+
i = col.to_s.to_i
|
901
|
+
if i < 100000000
|
902
|
+
y = i/10000
|
903
|
+
m = i/100%100
|
904
|
+
d = i%100
|
905
|
+
h, mm, s = 0
|
906
|
+
else
|
907
|
+
y = i/10000000000
|
908
|
+
m = i/100000000%100
|
909
|
+
d = i/1000000%100
|
910
|
+
h = i/10000%100
|
911
|
+
mm= i/100%100
|
912
|
+
s = i%100
|
913
|
+
end
|
914
|
+
if y < 70
|
915
|
+
y += 2000
|
916
|
+
elsif y < 100
|
917
|
+
y += 1900
|
918
|
+
end
|
919
|
+
Mysql::Time.new(y, m, d, h, mm, s)
|
920
|
+
else
|
921
|
+
Mysql::Time.new
|
922
|
+
end
|
923
|
+
else
|
924
|
+
col
|
925
|
+
end
|
926
|
+
end
|
927
|
+
end
|
928
|
+
|
929
|
+
# Return data of current record as Hash.
|
930
|
+
# The hash key is field name.
|
931
|
+
# @param [Boolean] with_table if true, hash key is "table_name.field_name".
|
932
|
+
# @return [Hash] record data
|
933
|
+
def fetch_hash(with_table=nil)
|
934
|
+
@result.fetch_hash with_table
|
935
|
+
end
|
936
|
+
|
937
|
+
# Set retrieve type of value
|
938
|
+
# @param [Numeric / Fixnum / Integer / Float / String / Mysql::Time / nil] args value type
|
939
|
+
# @return [Mysql::Stmt] self
|
940
|
+
def bind_result(*args)
|
941
|
+
if @fields.length != args.length
|
942
|
+
raise ClientError, "bind_result: result value count(#{@fields.length}) != number of argument(#{args.length})"
|
943
|
+
end
|
944
|
+
args.each do |a|
|
945
|
+
raise TypeError unless [Numeric, Fixnum, Integer, Float, String, Mysql::Time, nil].include? a
|
946
|
+
end
|
947
|
+
@bind_result = args
|
948
|
+
self
|
949
|
+
end
|
950
|
+
|
951
|
+
# Iterate block with record.
|
952
|
+
# @yield [Array] record data
|
953
|
+
# @return [Mysql::Stmt] self
|
954
|
+
# @return [Enumerator] If block is not specified
|
955
|
+
def each(&block)
|
956
|
+
return enum_for(:each) unless block
|
957
|
+
while rec = fetch
|
958
|
+
block.call rec
|
959
|
+
end
|
960
|
+
self
|
961
|
+
end
|
962
|
+
|
963
|
+
# Iterate block with record as Hash.
|
964
|
+
# @param [Boolean] with_table if true, hash key is "table_name.field_name".
|
965
|
+
# @yield [Hash] record data
|
966
|
+
# @return [Mysql::Stmt] self
|
967
|
+
# @return [Enumerator] If block is not specified
|
968
|
+
def each_hash(with_table=nil, &block)
|
969
|
+
return enum_for(:each_hash, with_table) unless block
|
970
|
+
while rec = fetch_hash(with_table)
|
971
|
+
block.call rec
|
972
|
+
end
|
973
|
+
self
|
974
|
+
end
|
975
|
+
|
976
|
+
# @return [Integer] number of record
|
977
|
+
def size
|
978
|
+
@result.size
|
979
|
+
end
|
980
|
+
alias num_rows size
|
981
|
+
|
982
|
+
# Set record position
|
983
|
+
# @param [Integer] n record index
|
984
|
+
# @return [void]
|
985
|
+
def data_seek(n)
|
986
|
+
@result.data_seek(n)
|
987
|
+
end
|
988
|
+
|
989
|
+
# @return [Integer] current record position
|
990
|
+
def row_tell
|
991
|
+
@result.row_tell
|
992
|
+
end
|
993
|
+
|
994
|
+
# Set current position of record
|
995
|
+
# @param [Integer] n record index
|
996
|
+
# @return [Integer] previous position
|
997
|
+
def row_seek(n)
|
998
|
+
@result.row_seek(n)
|
999
|
+
end
|
1000
|
+
|
1001
|
+
# @return [Integer] number of columns for last query
|
1002
|
+
def field_count
|
1003
|
+
@fields.length
|
1004
|
+
end
|
1005
|
+
|
1006
|
+
# ignore
|
1007
|
+
# @return [void]
|
1008
|
+
def free_result
|
1009
|
+
end
|
1010
|
+
|
1011
|
+
# Returns Mysql::Result object that is empty.
|
1012
|
+
# Use fetch_fields to get list of fields.
|
1013
|
+
# @return [Mysql::Result]
|
1014
|
+
def result_metadata
|
1015
|
+
return nil if @fields.empty?
|
1016
|
+
Result.new @fields
|
1017
|
+
end
|
1018
|
+
end
|
1019
|
+
|
1020
|
+
# @!visibility public
|
1021
|
+
# @!attribute [rw] year
|
1022
|
+
# @return [Integer]
|
1023
|
+
# @!attribute [rw] month
|
1024
|
+
# @return [Integer]
|
1025
|
+
# @!attribute [rw] day
|
1026
|
+
# @return [Integer]
|
1027
|
+
# @!attribute [rw] hour
|
1028
|
+
# @return [Integer]
|
1029
|
+
# @!attribute [rw] minute
|
1030
|
+
# @return [Integer]
|
1031
|
+
# @!attribute [rw] second
|
1032
|
+
# @return [Integer]
|
1033
|
+
# @!attribute [rw] neg
|
1034
|
+
# @return [Boolean] negative flag
|
1035
|
+
# @!attribute [rw] second_part
|
1036
|
+
# @return [Integer]
|
1037
|
+
class Time
|
1038
|
+
# @param [Integer] year
|
1039
|
+
# @param [Integer] month
|
1040
|
+
# @param [Integer] day
|
1041
|
+
# @param [Integer] hour
|
1042
|
+
# @param [Integer] minute
|
1043
|
+
# @param [Integer] second
|
1044
|
+
# @param [Boolean] neg negative flag
|
1045
|
+
# @param [Integer] second_part
|
1046
|
+
def initialize(year=0, month=0, day=0, hour=0, minute=0, second=0, neg=false, second_part=0)
|
1047
|
+
@date_flag = !(hour && minute && second)
|
1048
|
+
@year, @month, @day, @hour, @minute, @second, @neg, @second_part =
|
1049
|
+
year.to_i, month.to_i, day.to_i, hour.to_i, minute.to_i, second.to_i, neg, second_part.to_i
|
1050
|
+
end
|
1051
|
+
attr_accessor :year, :month, :day, :hour, :minute, :second, :neg, :second_part
|
1052
|
+
alias mon month
|
1053
|
+
alias min minute
|
1054
|
+
alias sec second
|
1055
|
+
|
1056
|
+
# @private
|
1057
|
+
def ==(other)
|
1058
|
+
other.is_a?(Mysql::Time) &&
|
1059
|
+
@year == other.year && @month == other.month && @day == other.day &&
|
1060
|
+
@hour == other.hour && @minute == other.minute && @second == other.second &&
|
1061
|
+
@neg == neg && @second_part == other.second_part
|
1062
|
+
end
|
1063
|
+
|
1064
|
+
# @private
|
1065
|
+
def eql?(other)
|
1066
|
+
self == other
|
1067
|
+
end
|
1068
|
+
|
1069
|
+
# @return [String] "yyyy-mm-dd HH:MM:SS"
|
1070
|
+
def to_s
|
1071
|
+
if @date_flag
|
1072
|
+
sprintf "%04d-%02d-%02d", year, mon, day
|
1073
|
+
elsif year == 0 and mon == 0 and day == 0
|
1074
|
+
h = neg ? hour * -1 : hour
|
1075
|
+
sprintf "%02d:%02d:%02d", h, min, sec
|
1076
|
+
else
|
1077
|
+
sprintf "%04d-%02d-%02d %02d:%02d:%02d", year, mon, day, hour, min, sec
|
1078
|
+
end
|
1079
|
+
end
|
1080
|
+
|
1081
|
+
# @return [Integer] yyyymmddHHMMSS
|
1082
|
+
def to_i
|
1083
|
+
sprintf("%04d%02d%02d%02d%02d%02d", year, mon, day, hour, min, sec).to_i
|
1084
|
+
end
|
1085
|
+
|
1086
|
+
# @private
|
1087
|
+
def inspect
|
1088
|
+
sprintf "#<#{self.class.name}:%04d-%02d-%02d %02d:%02d:%02d>", year, mon, day, hour, min, sec
|
1089
|
+
end
|
1090
|
+
|
1091
|
+
end
|
1092
|
+
|
1093
|
+
end
|