mysql-pr 2.9.11

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.
@@ -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