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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +11 -0
  3. data/LICENSE +21 -0
  4. data/README.md +29 -3
  5. data/lib/outback/archive.rb +6 -17
  6. data/lib/outback/backup.rb +38 -20
  7. data/lib/outback/cli.rb +6 -2
  8. data/lib/outback/configuration.rb +15 -10
  9. data/lib/outback/directory_source.rb +8 -7
  10. data/lib/outback/directory_target.rb +18 -11
  11. data/lib/outback/encryption_processor.rb +34 -0
  12. data/lib/outback/errors.rb +7 -0
  13. data/lib/outback/logging.rb +7 -0
  14. data/lib/outback/mysql_source.rb +9 -9
  15. data/lib/outback/processor.rb +17 -0
  16. data/lib/outback/s3_target.rb +18 -9
  17. data/lib/outback/sftp_target.rb +70 -0
  18. data/lib/outback/source.rb +9 -2
  19. data/lib/outback/source_archive.rb +17 -0
  20. data/lib/outback/support/attr_setter.rb +1 -1
  21. data/lib/outback/support/configurable.rb +5 -3
  22. data/lib/outback/target.rb +52 -14
  23. data/lib/outback/target_archive.rb +30 -0
  24. data/lib/outback/version.rb +3 -0
  25. data/lib/outback.rb +16 -11
  26. data/lib/vendor/enumerable_ext.rb +9 -0
  27. data/lib/{outback/vendor → vendor}/metaclass.rb +1 -1
  28. data/lib/vendor/methodphitamine.rb +28 -0
  29. data/lib/vendor/mysql/charset.rb +325 -0
  30. data/lib/vendor/mysql/constants.rb +165 -0
  31. data/lib/vendor/mysql/error.rb +989 -0
  32. data/lib/vendor/mysql/packet.rb +78 -0
  33. data/lib/vendor/mysql/protocol.rb +770 -0
  34. data/lib/vendor/mysql.rb +1093 -0
  35. data/lib/vendor/numeric_ext.rb +49 -0
  36. data/lib/vendor/string_ext.rb +19 -0
  37. metadata +84 -43
  38. data/MIT-LICENSE +0 -20
  39. data/VERSION +0 -1
  40. data/lib/outback/configuration_error.rb +0 -4
  41. data/lib/outback/directory_archive.rb +0 -8
  42. data/lib/outback/local_archive.rb +0 -6
  43. data/lib/outback/s3_archive.rb +0 -18
  44. data/lib/outback/temp_archive.rb +0 -5
  45. data/lib/outback/vendor/methodphitamine.rb +0 -25
  46. data/lib/outback/vendor/mysql.rb +0 -1214
@@ -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