ruby-mysql 2.9.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of ruby-mysql might be problematic. Click here for more details.

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