mysql-pr 2.9.11

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