ruby-mysql 2.9.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.

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