tmtm-ruby-mysql 3.0.0 → 3.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/ChangeLog CHANGED
@@ -1,3 +1,20 @@
1
- == 3.0.0 / 2009-03-22
1
+ 2009-07-12 TOMITA Masahiro <tommy@tmtm.org>
2
2
 
3
- * initial release for v3.0.
3
+ * version 3.0.1-alpha
4
+
5
+ 2009-07-10 TOMITA Masahiro <tommy@tmtm.org>
6
+
7
+ * lib/mysql/compat.rb: updated.
8
+
9
+ * lib/mysql.rb: Mysql#query without parameter no longer use prepared statement.
10
+ Mysql::Statement#execute returns Mysql::Result.
11
+ Mysql::Statement no longer have some methods: fetch_row(), fetch_hash(), each(), each_hash().
12
+ Mysql::Result#fetch returns value that converted to Ruby object.
13
+
14
+ 2009-04-12 TOMITA Masahiro <tommy@tmtm.org>
15
+
16
+ * for Ruby 1.8.6
17
+
18
+ 2009-03-22 TOMITA Masahiro <tommy@tmtm.org>
19
+
20
+ * Version 3.0.0-alpha
data/README CHANGED
@@ -3,7 +3,7 @@
3
3
  == Description
4
4
  MySQL connector for Ruby.
5
5
 
6
- ALPHA バージョンです。将来のバージョンで互換がない変更がされる可能性あります。
6
+ ALPHA バージョンです。将来のバージョンで互換がない変更がされる可能性があります。
7
7
 
8
8
  == Installation
9
9
 
@@ -18,13 +18,14 @@ ALPHA バージョンです。将来のバージョンで互換がない変更
18
18
  * Ruby だけで書かれているのでコンパイル不要です。
19
19
  * Ruby 1.9 の M17N に対応しています。
20
20
  * Ruby/MySQL 0.x, MySQL/Ruby 2.x とは互換がありません。
21
+ require "mysql/compat" すれば、MySQL/Ruby 2.x 用のプログラムもそこそこ動くかもしれません。
21
22
  * 英語ドキュメントがありません。
22
23
 
23
24
  == Synopsis
24
25
 
25
26
  使用例:
26
27
 
27
- Mysql.connect("mysql://username@password:hostname:3306/dbname") do |my|
28
+ Mysql.connect("mysql://username:password@hostname:3306/dbname") do |my|
28
29
  my.query("select col1, col2 from tblname").each do |col1, col2|
29
30
  p col1, col2
30
31
  end
data/lib/mysql/charset.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # Copyright (C) 2008 TOMITA Masahiro
2
2
  # mailto:tommy@tmtm.org
3
3
 
4
- require "mysql/error"
4
+ require "#{File.dirname __FILE__}/error"
5
5
 
6
6
  class Mysql
7
7
  class Charset
data/lib/mysql/compat.rb CHANGED
@@ -5,11 +5,20 @@
5
5
 
6
6
  class Mysql
7
7
  class << self
8
- alias connect new
9
- alias real_connect new
8
+
9
+ def connect(*args)
10
+ my = self.allocate
11
+ my.instance_eval{initialize}
12
+ my.connect(*args)
13
+ my
14
+ end
15
+ alias new connect
16
+ alias real_connect connect
10
17
 
11
18
  def init
12
- self.allocate
19
+ my = self.allocate
20
+ my.instance_eval{initialize}
21
+ my
13
22
  end
14
23
 
15
24
  def client_version
@@ -35,24 +44,31 @@ class Mysql
35
44
  alias quote escape_string
36
45
  end
37
46
 
38
- alias orig_initialize initialize
39
- alias stmt_init stmt
40
- alias query simple_query
47
+ attr_accessor :query_with_result, :reconnect
48
+
49
+ alias stmt_init statement
50
+ alias real_connect connect
51
+ alias initialize_orig initialize
41
52
 
42
53
  def initialize(*args)
43
- if args.first.is_a? Hash || defined?(URI) && args.first.is_a?(URI) || args.first =~ /\Amysql:\/\//
44
- orig_initialize *args
45
- else
46
- host, user, password, db, port, socket, flag = args
47
- orig_initialize :host=>host, :user=>user, :password=>password, :db=>db, :port=>port, :socket=>socket, :flag=>flag
48
- end
54
+ initialize_orig(*args)
55
+ @query_with_result = true
56
+ @reconnect = false
49
57
  end
50
58
 
51
- def connect(host, user, password, db, port, socket, flag)
52
- initialize :host=>host, :user=>user, :password=>password, :db=>db, :port=>port, :socket=>socket, :flag=>flag
53
- self
59
+ def query(str)
60
+ res = simple_query str
61
+ if res
62
+ res.each do |rec|
63
+ rec.map!{|v| v && v.to_s}
64
+ rec.each_index do |i|
65
+ @fields[i].max_length = [rec[i] ? rec[i].length : 0, @fields[i].max_length||0].max
66
+ end
67
+ end
68
+ res.data_seek 0
69
+ end
70
+ res
54
71
  end
55
- alias real_connect connect
56
72
 
57
73
  def client_version
58
74
  self.class.client_version
@@ -102,10 +118,6 @@ class Mysql
102
118
  self
103
119
  end
104
120
 
105
- def sqlstate
106
- @stream ? @stream.sqlstate : "00000"
107
- end
108
-
109
121
  def store_result
110
122
  raise ClientError, "no result set" unless @fields
111
123
  Result.new @fields, @stream
@@ -117,6 +129,12 @@ class Mysql
117
129
  end
118
130
 
119
131
  class Result
132
+ alias initialize_orig initialize
133
+ def initialize(*args)
134
+ initialize_orig *args
135
+ @field_index = 0
136
+ end
137
+
120
138
  def num_rows
121
139
  @records.length
122
140
  end
@@ -139,6 +157,11 @@ class Mysql
139
157
  # do nothing
140
158
  end
141
159
 
160
+ alias fetch_row_orig fetch_row
161
+ def fetch_row
162
+ @fetched_record = fetch_row_orig
163
+ end
164
+
142
165
  def fetch_field
143
166
  return nil if @field_index >= @fields.length
144
167
  ret = @fields[@field_index]
@@ -173,23 +196,54 @@ class Mysql
173
196
  end
174
197
  end
175
198
 
199
+ class Field
200
+ attr_accessor :max_length
201
+ def hash
202
+ {
203
+ "name" => @name,
204
+ "table" => @table,
205
+ "def" => @default,
206
+ "type" => @type,
207
+ "length" => @length,
208
+ "max_length" => @max_length,
209
+ "flags" => @flags,
210
+ "decimals" => @decimals
211
+ }
212
+ end
213
+ def inspect
214
+ "#<Mysql::Field:#{@name}>"
215
+ end
216
+ end
217
+
176
218
  class Statement
219
+ alias execute_orig execute
220
+ def execute(*args)
221
+ @res = execute_orig *args
222
+ end
223
+
224
+ def fetch
225
+ @res.fetch
226
+ end
227
+ alias fetch_row fetch
228
+
229
+ def each(*args, &block)
230
+ @res.each(*args, &block)
231
+ end
232
+
177
233
  def num_rows
178
- @records.length
234
+ @res.num_rows
179
235
  end
180
236
 
181
237
  def data_seek(n)
182
- @index = n
238
+ @res.data_seek(n)
183
239
  end
184
240
 
185
241
  def row_tell
186
- @index
242
+ @res.row_tell
187
243
  end
188
244
 
189
245
  def row_seek(n)
190
- ret = @index
191
- @index = n
192
- ret
246
+ @res.row_seek(n)
193
247
  end
194
248
 
195
249
  def field_count
@@ -202,7 +256,11 @@ class Mysql
202
256
 
203
257
  def result_metadata
204
258
  return nil if @fields.empty?
205
- Result.new @fields, nil, false
259
+ res = Result.allocate
260
+ res.instance_variable_set :@mysql, @mysql
261
+ res.instance_variable_set :@fields, @fields
262
+ res.instance_variable_set :@records, []
263
+ res
206
264
  end
207
265
  end
208
266
  Stmt = Statement
@@ -52,7 +52,7 @@ class Mysql
52
52
  lcb[0, 9] = ""
53
53
  return (v2 << 32) | v1
54
54
  else
55
- return ord! lcb
55
+ return ord!(lcb)
56
56
  end
57
57
  end
58
58
 
@@ -432,9 +432,9 @@ class Mysql
432
432
  affected_rows = Protocol.lcb2int! data
433
433
  insert_id = Protocol.lcb2int!(data)
434
434
  server_status, warning_count, message = data.unpack("vva*")
435
- return self.new field_count, affected_rows, insert_id, server_status, warning_count, message
435
+ return self.new(field_count, affected_rows, insert_id, server_status, warning_count, message)
436
436
  else
437
- return self.new field_count
437
+ return self.new(field_count)
438
438
  end
439
439
  end
440
440
 
@@ -457,7 +457,7 @@ class Mysql
457
457
  f0, charsetnr, length, type, flags, decimals, f1, data = data.unpack("CvVCvCva*")
458
458
  raise ProtocolError, "invalid packet: f1=#{f1}" unless f1 == 0
459
459
  default = Protocol.lcs2str! data
460
- return self.new db, table, org_table, name, org_name, charsetnr, length, type, flags, decimals, default
460
+ return self.new(db, table, org_table, name, org_name, charsetnr, length, type, flags, decimals, default)
461
461
  end
462
462
 
463
463
  attr_accessor :db, :table, :org_table, :name, :org_name, :charsetnr, :length, :type, :flags, :decimals, :default
data/lib/mysql.rb CHANGED
@@ -1,16 +1,26 @@
1
- # Copyright (C) 2008 TOMITA Masahiro
1
+ # Copyright (C) 2008-2009 TOMITA Masahiro
2
2
  # mailto:tommy@tmtm.org
3
3
 
4
- $LOAD_PATH.unshift File.dirname(__FILE__)
5
- require "mysql/constants"
6
- require "mysql/error"
7
- require "mysql/charset"
8
- require "mysql/protocol"
9
- require "mysql/cache"
10
-
4
+ require "enumerator"
5
+ require "uri"
6
+
7
+ # MySQL connection class.
8
+ # === Example
9
+ # Mysql.connect("mysql://user:password@hostname:port/dbname") do |my|
10
+ # res = my.query "select col1,col2 from tbl where id=?", 123
11
+ # res.each do |c1, c2|
12
+ # p c1, c2
13
+ # end
14
+ # end
11
15
  class Mysql
12
16
 
13
- VERSION = 30000 # Version number of this library
17
+ dir = File.dirname __FILE__
18
+ require "#{dir}/mysql/constants"
19
+ require "#{dir}/mysql/error"
20
+ require "#{dir}/mysql/charset"
21
+ require "#{dir}/mysql/protocol"
22
+
23
+ VERSION = 30001 # Version number of this library
14
24
  MYSQL_UNIX_PORT = "/tmp/mysql.sock" # UNIX domain socket filename
15
25
  MYSQL_TCP_PORT = 3306 # TCP socket port number
16
26
 
@@ -35,7 +45,6 @@ class Mysql
35
45
  # :report_data_truncation => x,
36
46
  # :reconnect => x,
37
47
  # :ssl_verify_server_cert => x,
38
- :prepared_statement_cache_size => Integer,
39
48
  } # :nodoc:
40
49
 
41
50
  OPT2FLAG = {
@@ -58,13 +67,14 @@ class Mysql
58
67
  attr_reader :warning_count #
59
68
  attr_reader :server_version #
60
69
  attr_reader :protocol #
70
+ attr_reader :sqlstate
61
71
 
62
72
  def self.new(*args, &block) # :nodoc:
63
73
  my = self.allocate
64
74
  my.instance_eval{initialize(*args)}
65
75
  return my unless block
66
76
  begin
67
- return block.call my
77
+ return block.call(my)
68
78
  ensure
69
79
  my.close
70
80
  end
@@ -74,11 +84,11 @@ class Mysql
74
84
  # The value that block returns if block is specified.
75
85
  # Otherwise this returns Mysql object.
76
86
  def self.connect(*args, &block)
77
- my = self.new *args
87
+ my = self.new(*args)
78
88
  my.connect
79
89
  return my unless block
80
90
  begin
81
- return block.call my
91
+ return block.call(my)
82
92
  ensure
83
93
  my.close
84
94
  end
@@ -120,13 +130,19 @@ class Mysql
120
130
  @init_command = nil
121
131
  @affected_rows = nil
122
132
  @server_version = nil
123
- @param, opt = conninfo *args
133
+ @sqlstate = "00000"
134
+ @param, opt = conninfo(*args)
124
135
  @connected = false
125
136
  set_option opt
126
137
  end
127
138
 
139
+ # :call-seq:
140
+ # connect(conninfo, opt={})
141
+ #
142
+ # connect to mysql server.
143
+ # arguments are same as new().
128
144
  def connect(*args)
129
- param, opt = conninfo *args
145
+ param, opt = conninfo(*args)
130
146
  set_option opt
131
147
  param = @param.merge param
132
148
  @protocol = Protocol.new param[:host], param[:port], param[:socket], @connect_timeout, @read_timeout, @write_timeout
@@ -145,11 +161,11 @@ class Mysql
145
161
  @protocol.send_packet auth_packet
146
162
  @protocol.read # skip OK packet
147
163
  end
148
- @stmt_cache = Cache.new(@prepared_statement_cache_size)
149
164
  simple_query @init_command if @init_command
150
165
  return self
151
166
  end
152
167
 
168
+ # disconnect from mysql.
153
169
  def close
154
170
  if @protocol
155
171
  @protocol.synchronize do
@@ -174,78 +190,82 @@ class Mysql
174
190
  end
175
191
 
176
192
  # Execute query string.
177
- # If str begin with "sel" or params is specified, then the query is executed as prepared-statement automatically.
178
- # So the values in result set are not only String.
193
+ # If params is specified, then the query is executed as prepared-statement automatically.
179
194
  # === Argument
180
195
  # str :: [String] Query.
181
196
  # params :: Parameters corresponding to place holder (`?') in str.
197
+ # block :: If it is given then it is evaluated with Result object as argument.
182
198
  # === Return
183
- # Mysql::Statement :: If result set exist when str begin with "sel".
184
- # Mysql::Result :: If result set exist when str does not begin with "sel".
185
- # nil :: If result set does not exist.
199
+ # Mysql::Result :: If result set exist.
200
+ # nil :: If the query does not return result set.
201
+ # self :: If block is specified.
202
+ # === Block parameter
203
+ # [ Mysql::Result ]
186
204
  # === Example
187
205
  # my.query("select 1,NULL,'abc'").fetch # => [1, nil, "abc"]
188
- def query(str, *params)
189
- if not params.empty? or str =~ /\A\s*sel/i
190
- st = @stmt_cache.get str do |s|
191
- prepare s
192
- end
193
- st.execute(*params)
194
- if st.fields.empty?
195
- @affected_rows = st.affected_rows
196
- @insert_id = st.insert_id
197
- @server_status = st.server_status
198
- @warning_count = st.warning_count
199
- return nil
200
- end
201
- return st
206
+ def query(str, *params, &block)
207
+ if params.empty?
208
+ res = simple_query(str, &block)
202
209
  else
203
- return simple_query(str)
210
+ res = prepare_query(str, *params, &block)
211
+ end
212
+ if res && block
213
+ yield res
214
+ return self
204
215
  end
216
+ return res
205
217
  end
206
218
 
207
- # Execute query string.
208
- # The values in result set are String even if it is numeric.
209
- # === Argument
210
- # str :: [String] query string
211
- # === Return
212
- # Mysql::Result :: If result set is exist.
213
- # nil :: If result set is not eixst.
214
- # === Example
215
- # my.simple_query("select 1,NULL,'abc'").fetch # => ["1", nil, "abc"]
216
- def simple_query(str, &block)
219
+ def simple_query(str) # :nodoc:
217
220
  @affected_rows = @insert_id = @server_status = @warning_count = 0
218
- @fields = nil
219
221
  @protocol.synchronize do
220
- @protocol.reset
221
- @protocol.send_packet Protocol::QueryPacket.new @charset.convert(str)
222
- res_packet = @protocol.read_result_packet
223
- if res_packet.field_count == 0
224
- @affected_rows, @insert_id, @server_status, @warning_conut =
225
- res_packet.affected_rows, res_packet.insert_id, res_packet.server_status, res_packet.warning_count
226
- else
227
- @fields = (1..res_packet.field_count).map{Field.new @protocol.read_field_packet}
228
- @protocol.read_eof_packet
229
- end
230
- if block
231
- yield Result.new(self, @fields)
232
- return self
222
+ begin
223
+ @protocol.reset
224
+ @protocol.send_packet Protocol::QueryPacket.new(@charset.convert(str))
225
+ res_packet = @protocol.read_result_packet
226
+ if res_packet.field_count == 0
227
+ @affected_rows, @insert_id, @server_status, @warning_conut =
228
+ res_packet.affected_rows, res_packet.insert_id, res_packet.server_status, res_packet.warning_count
229
+ return nil
230
+ else
231
+ @fields = (1..res_packet.field_count).map{Field.new @protocol.read_field_packet}
232
+ @protocol.read_eof_packet
233
+ return SimpleQueryResult.new self, @fields
234
+ end
235
+ rescue ServerError => e
236
+ @sqlstate = e.sqlstate
237
+ raise
233
238
  end
234
- return @fields && Result.new(self, @fields)
235
239
  end
236
240
  end
237
241
 
242
+ def prepare_query(str, *params) # :nodoc:
243
+ st = prepare(str)
244
+ res = st.execute(*params)
245
+ if st.fields.empty?
246
+ @affected_rows = st.affected_rows
247
+ @insert_id = st.insert_id
248
+ @server_status = st.server_status
249
+ @warning_count = st.warning_count
250
+ end
251
+ st.close
252
+ return res
253
+ end
254
+
238
255
  # Parse prepared-statement.
256
+ # If block is specified then prepared-statement is closed when exiting the block.
239
257
  # === Argument
240
- # str :: [String] query string
258
+ # str :: [String] query string
259
+ # block :: If it is given then it is evaluated with Mysql::Statement object as argument.
241
260
  # === Return
242
261
  # Mysql::Statement :: Prepared-statement object
262
+ # The block value if block is given.
243
263
  def prepare(str, &block)
244
264
  st = Statement.new self
245
265
  st.prepare str
246
266
  if block
247
267
  begin
248
- return block.call st
268
+ return block.call(st)
249
269
  ensure
250
270
  st.close
251
271
  end
@@ -285,7 +305,7 @@ class Mysql
285
305
  st = Statement.new self
286
306
  if block
287
307
  begin
288
- return block.call st
308
+ return block.call(st)
289
309
  ensure
290
310
  st.close
291
311
  end
@@ -330,9 +350,8 @@ class Mysql
330
350
  end
331
351
  else
332
352
  if args.first =~ /\Amysql:/
333
- require "uri" unless defined? URI
334
353
  uri = URI.parse args.first
335
- elsif defined? URI and args.first.is_a? URI
354
+ elsif args.first.is_a? URI
336
355
  uri = args.first
337
356
  else
338
357
  raise ArgumentError, "Invalid argument: #{args.first.inspect}"
@@ -370,8 +389,6 @@ class Mysql
370
389
  return param, opt
371
390
  end
372
391
 
373
- private
374
-
375
392
  def set_option(opt)
376
393
  opt.each do |k,v|
377
394
  raise ClientError, "unknown option: #{k.inspect}" unless OPTIONS.key? k
@@ -386,9 +403,9 @@ class Mysql
386
403
  @init_command = opt[:init_command] || @init_command
387
404
  @read_timeout = opt[:read_timeout] || @read_timeout
388
405
  @write_timeout = opt[:write_timeout] || @write_timeout
389
- @prepared_statement_cache_size = opt[:prepared_statement_cache_size] || @prepared_statement_cache_size || 10
390
406
  end
391
407
 
408
+ # Field class
392
409
  class Field
393
410
  attr_reader :db, :table, :org_table, :name, :org_name, :charsetnr, :length, :type, :flags, :decimals, :default
394
411
  alias :def :default
@@ -401,14 +418,17 @@ class Mysql
401
418
  @flags |= NUM_FLAG if is_num_type?
402
419
  end
403
420
 
421
+ # Return true if numeric field.
404
422
  def is_num?
405
423
  @flags & NUM_FLAG != 0
406
424
  end
407
425
 
426
+ # Return true if not null field.
408
427
  def is_not_null?
409
428
  @flags & NOT_NULL_FLAG != 0
410
429
  end
411
430
 
431
+ # Return true if primary key field.
412
432
  def is_pri_key?
413
433
  @flags & PRI_KEY_FLAG != 0
414
434
  end
@@ -421,26 +441,30 @@ class Mysql
421
441
 
422
442
  end
423
443
 
444
+ # Result set
424
445
  class Result
425
-
426
446
  include Enumerable
427
447
 
428
448
  attr_reader :fields
429
449
 
430
450
  def initialize(mysql, fields)
431
- @mysql = mysql
432
451
  @fields = fields
433
452
  @fieldname_with_table = nil
434
- @field_index = 0
435
- @records = recv_all_records mysql.protocol, @fields, mysql.charset
436
453
  @index = 0
454
+ @records = recv_all_records mysql.protocol, fields, mysql.charset
455
+ end
456
+
457
+ def size
458
+ @records.size
437
459
  end
438
460
 
439
461
  def fetch_row
462
+ return nil if @index >= @records.size
440
463
  rec = @records[@index]
441
- @index += 1 if @index < @records.length
464
+ @index += 1
442
465
  return rec
443
466
  end
467
+
444
468
  alias fetch fetch_row
445
469
 
446
470
  def fetch_hash(with_table=nil)
@@ -472,6 +496,10 @@ class Mysql
472
496
  end
473
497
  self
474
498
  end
499
+ end
500
+
501
+ # Result set for simple query
502
+ class SimpleQueryResult < Result
475
503
 
476
504
  private
477
505
 
@@ -482,59 +510,83 @@ class Mysql
482
510
  break if Protocol.eof_packet? data
483
511
  rec = fields.map do |f|
484
512
  v = Protocol.lcs2str! data
485
- v.nil? ? nil : f.flags & Field::BINARY_FLAG == 0 ? charset.force_encoding(v) : Charset.to_binary(v)
513
+ convert_str_to_ruby_value f, v, charset
486
514
  end
487
515
  ret.push rec
488
516
  end
489
517
  ret
490
518
  end
491
- end
492
519
 
493
- class Time
494
- def initialize(year=0, month=0, day=0, hour=0, minute=0, second=0, neg=false, second_part=0)
495
- @year, @month, @day, @hour, @minute, @second, @neg, @second_part =
496
- year.to_i, month.to_i, day.to_i, hour.to_i, minute.to_i, second.to_i, neg, second_part.to_i
520
+ def convert_str_to_ruby_value(field, value, charset)
521
+ return nil if value.nil?
522
+ case field.type
523
+ when Field::TYPE_BIT, Field::TYPE_DECIMAL, Field::TYPE_VARCHAR,
524
+ Field::TYPE_NEWDECIMAL, Field::TYPE_TINY_BLOB,
525
+ Field::TYPE_MEDIUM_BLOB, Field::TYPE_LONG_BLOB,
526
+ Field::TYPE_BLOB, Field::TYPE_VAR_STRING, Field::TYPE_STRING
527
+ field.flags & Field::BINARY_FLAG == 0 ? charset.force_encoding(value) : Charset.to_binary(value)
528
+ when Field::TYPE_TINY, Field::TYPE_SHORT, Field::TYPE_LONG,
529
+ Field::TYPE_LONGLONG, Field::TYPE_INT24, Field::TYPE_YEAR
530
+ value.to_i
531
+ when Field::TYPE_FLOAT, Field::TYPE_DOUBLE
532
+ value.to_f
533
+ when Field::TYPE_TIMESTAMP, Field::TYPE_DATE, Field::TYPE_DATETIME, Field::TYPE_NEWDATE
534
+ unless value =~ /\A(\d\d\d\d).(\d\d).(\d\d)(?:.(\d\d).(\d\d).(\d\d))?\z/
535
+ raise "unsupported format date type: #{value}"
536
+ end
537
+ Time.new($1, $2, $3, $4, $5, $6)
538
+ when Field::TYPE_TIME
539
+ unless value =~ /\A(-?)(\d+).(\d\d).(\d\d)?\z/
540
+ raise "unsupported format time type: #{value}"
541
+ end
542
+ Time.new(0, 0, 0, $2, $3, $4, $1=="-")
543
+ else
544
+ raise "unknown mysql type: #{field.type}"
545
+ end
497
546
  end
498
- attr_accessor :year, :month, :day, :hour, :minute, :second, :neg, :second_part
499
- alias mon month
500
- alias min minute
501
- alias sec second
547
+ end
502
548
 
503
- def ==(other)
504
- other.is_a?(Mysql::Time) &&
505
- @year == other.year && @month == other.month && @day == other.day &&
506
- @hour == other.hour && @minute == other.minute && @second == other.second &&
507
- @neg == neg && @second_part == other.second_part
508
- end
549
+ # Result set for prepared statement
550
+ class StatementResult < Result
509
551
 
510
- def eql?(other)
511
- self == other
512
- end
552
+ private
513
553
 
514
- def to_s
515
- if year == 0 and mon == 0 and day == 0
516
- sprintf "%02d:%02d:%02d", hour, min, sec
517
- else
518
- sprintf "%04d-%02d-%02d %02d:%02d:%02d", year, mon, day, hour, min, sec
554
+ def recv_all_records(protocol, fields, charset)
555
+ ret = []
556
+ while rec = parse_data(protocol.read, fields, charset)
557
+ ret.push rec
519
558
  end
559
+ ret
520
560
  end
521
561
 
562
+ def parse_data(data, fields, charset)
563
+ return nil if Protocol.eof_packet? data
564
+ data.slice!(0) # skip first byte
565
+ null_bit_map = data.slice!(0, (fields.length+7+2)/8).unpack("C*")
566
+ ret = (0...fields.length).map do |i|
567
+ if null_bit_map[(i+2)/8][(i+2)%8] == 1
568
+ nil
569
+ else
570
+ unsigned = fields[i].flags & Field::UNSIGNED_FLAG != 0
571
+ v = Protocol.net2value(data, fields[i].type, unsigned)
572
+ fields[i].flags & Field::BINARY_FLAG == 0 ? charset.force_encoding(v) : Charset.to_binary(v)
573
+ end
574
+ end
575
+ ret
576
+ end
522
577
  end
523
578
 
579
+ # Prepared statement
524
580
  class Statement
525
-
526
- include Enumerable
527
-
528
581
  attr_reader :affected_rows, :insert_id, :server_status, :warning_count
529
582
  attr_reader :param_count, :fields, :sqlstate
530
- attr_accessor :cursor_type
531
583
 
532
584
  def self.finalizer(protocol, statement_id)
533
585
  proc do
534
586
  Thread.new do
535
587
  protocol.synchronize do
536
588
  protocol.reset
537
- protocol.send_packet Protocol::StmtClosePacket.new statement_id
589
+ protocol.send_packet Protocol::StmtClosePacket.new(statement_id)
538
590
  end
539
591
  end
540
592
  end
@@ -545,9 +597,7 @@ class Mysql
545
597
  @protocol = mysql.protocol
546
598
  @statement_id = nil
547
599
  @affected_rows = @insert_id = @server_status = @warning_count = 0
548
- @eof = false
549
600
  @sqlstate = "00000"
550
- @cursor_type = CURSOR_TYPE_NO_CURSOR
551
601
  @param_count = nil
552
602
  end
553
603
 
@@ -562,7 +612,7 @@ class Mysql
562
612
  begin
563
613
  @sqlstate = "00000"
564
614
  @protocol.reset
565
- @protocol.send_packet Protocol::PreparePacket.new @mysql.charset.convert(str)
615
+ @protocol.send_packet Protocol::PreparePacket.new(@mysql.charset.convert(str))
566
616
  res_packet = @protocol.read_prepare_result_packet
567
617
  if res_packet.param_count > 0
568
618
  res_packet.param_count.times{@protocol.read} # skip parameter packet
@@ -586,6 +636,9 @@ class Mysql
586
636
  self
587
637
  end
588
638
 
639
+ # execute prepared-statement.
640
+ # === Return
641
+ # Mysql::Result
589
642
  def execute(*values)
590
643
  raise ClientError, "not prepared" unless @param_count
591
644
  raise ClientError, "parameter count mismatch" if values.length != @param_count
@@ -594,28 +647,18 @@ class Mysql
594
647
  begin
595
648
  @sqlstate = "00000"
596
649
  @protocol.reset
597
- cursor_type = @fields.empty? ? CURSOR_TYPE_NO_CURSOR : @cursor_type
598
- @protocol.send_packet Protocol::ExecutePacket.new @statement_id, cursor_type, values
650
+ @protocol.send_packet Protocol::ExecutePacket.new(@statement_id, CURSOR_TYPE_NO_CURSOR, values)
599
651
  res_packet = @protocol.read_result_packet
600
652
  raise ProtocolError, "invalid field_count" unless res_packet.field_count == @fields.length
601
653
  @fieldname_with_table = nil
602
654
  if res_packet.field_count == 0
603
655
  @affected_rows, @insert_id, @server_status, @warning_conut =
604
656
  res_packet.affected_rows, res_packet.insert_id, res_packet.server_status, res_packet.warning_count
605
- @records = nil
606
- else
607
- @fields = (1..res_packet.field_count).map{Field.new @protocol.read_field_packet}
608
- @protocol.read_eof_packet
609
- @eof = false
610
- @index = 0
611
- if @cursor_type == CURSOR_TYPE_NO_CURSOR
612
- @records = []
613
- while rec = parse_data(@protocol.read)
614
- @records.push rec
615
- end
616
- end
657
+ return nil
617
658
  end
618
- return self
659
+ @fields = (1..res_packet.field_count).map{Field.new @protocol.read_field_packet}
660
+ @protocol.read_eof_packet
661
+ return StatementResult.new(@mysql, @fields)
619
662
  rescue ServerError => e
620
663
  @sqlstate = e.sqlstate
621
664
  raise
@@ -623,86 +666,48 @@ class Mysql
623
666
  end
624
667
  end
625
668
 
626
- def fetch_row
627
- return nil if @fields.empty?
628
- if @records
629
- rec = @records[@index]
630
- @index += 1 if @index < @records.length
631
- return rec
632
- end
633
- return nil if @eof
669
+ def close
670
+ ObjectSpace.undefine_finalizer(self)
634
671
  @protocol.synchronize do
635
672
  @protocol.reset
636
- @protocol.send_packet Protocol::FetchPacket.new @statement_id, 1
637
- data = @protocol.read
638
- if Protocol.eof_packet? data
639
- @eof = true
640
- return nil
673
+ if @statement_id
674
+ @protocol.send_packet Protocol::StmtClosePacket.new(@statement_id)
675
+ @statement_id = nil
641
676
  end
642
- @protocol.read_eof_packet
643
- return parse_data data
644
- end
645
- end
646
- alias fetch fetch_row
647
-
648
- def fetch_hash(with_table=nil)
649
- row = fetch_row
650
- return nil unless row
651
- if with_table and @fieldname_with_table.nil?
652
- @fieldname_with_table = @fields.map{|f| [f.table, f.name].join(".")}
653
- end
654
- ret = {}
655
- @fields.each_index do |i|
656
- fname = with_table ? @fieldname_with_table[i] : @fields[i].name
657
- ret[fname] = row[i]
658
677
  end
659
- ret
660
678
  end
679
+ end
661
680
 
662
- def each(&block)
663
- return enum_for(:each) unless block
664
- while rec = fetch_row
665
- block.call rec
666
- end
667
- self
681
+ class Time
682
+ def initialize(year=0, month=0, day=0, hour=0, minute=0, second=0, neg=false, second_part=0)
683
+ @year, @month, @day, @hour, @minute, @second, @neg, @second_part =
684
+ year.to_i, month.to_i, day.to_i, hour.to_i, minute.to_i, second.to_i, neg, second_part.to_i
668
685
  end
686
+ attr_accessor :year, :month, :day, :hour, :minute, :second, :neg, :second_part
687
+ alias mon month
688
+ alias min minute
689
+ alias sec second
669
690
 
670
- def each_hash(with_table=nil, &block)
671
- return enum_for(:each_hash, with_table) unless block
672
- while rec = fetch_hash(with_table)
673
- block.call rec
674
- end
675
- self
691
+ def ==(other)
692
+ other.is_a?(Mysql::Time) &&
693
+ @year == other.year && @month == other.month && @day == other.day &&
694
+ @hour == other.hour && @minute == other.minute && @second == other.second &&
695
+ @neg == neg && @second_part == other.second_part
676
696
  end
677
697
 
678
- def close
679
- ObjectSpace.undefine_finalizer(self)
680
- @protocol.synchronize do
681
- @protocol.reset
682
- if @statement_id
683
- @protocol.send_packet Protocol::StmtClosePacket.new @statement_id
684
- @statement_id = nil
685
- end
686
- end
698
+ def eql?(other)
699
+ self == other
687
700
  end
688
701
 
689
- private
690
-
691
- def parse_data(data)
692
- return nil if Protocol.eof_packet? data
693
- data.slice!(0) # skip first byte
694
- null_bit_map = data.slice!(0, (@fields.length+7+2)/8).unpack("C*")
695
- ret = (0...@fields.length).map do |i|
696
- if null_bit_map[(i+2)/8][(i+2)%8] == 1
697
- nil
698
- else
699
- unsigned = @fields[i].flags & Field::UNSIGNED_FLAG != 0
700
- v = Protocol.net2value(data, @fields[i].type, unsigned)
701
- @fields[i].flags & Field::BINARY_FLAG == 0 ? @mysql.charset.force_encoding(v) : Charset.to_binary(v)
702
- end
702
+ def to_s
703
+ if year == 0 and mon == 0 and day == 0
704
+ h = neg ? hour * -1 : hour
705
+ sprintf "%02d:%02d:%02d", h, min, sec
706
+ else
707
+ sprintf "%04d-%02d-%02d %02d:%02d:%02d", year, mon, day, hour, min, sec
703
708
  end
704
- ret
705
709
  end
706
710
 
707
711
  end
712
+
708
713
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tmtm-ruby-mysql
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 3.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - tommy
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2009-03-22 00:00:00 -07:00
12
+ date: 2009-07-12 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -25,15 +25,12 @@ extra_rdoc_files:
25
25
  files:
26
26
  - README
27
27
  - ChangeLog
28
- - Rakefile
29
28
  - lib/mysql
30
29
  - lib/mysql/constants.rb
31
30
  - lib/mysql/compat.rb
32
31
  - lib/mysql/protocol.rb
33
- - lib/mysql/cache.rb
34
32
  - lib/mysql/charset.rb
35
33
  - lib/mysql/error.rb
36
- - lib/mysql.rb~
37
34
  - lib/mysql.rb
38
35
  has_rdoc: true
39
36
  homepage: http://github.com/tmtm/ruby-mysql
data/Rakefile DELETED
@@ -1,144 +0,0 @@
1
- require 'rubygems'
2
- require 'rake'
3
- require 'rake/clean'
4
- require 'rake/testtask'
5
- require 'rake/packagetask'
6
- require 'rake/gempackagetask'
7
- require 'rake/rdoctask'
8
- require 'rake/contrib/rubyforgepublisher'
9
- require 'rake/contrib/sshpublisher'
10
- require 'fileutils'
11
- require 'lib/mysql'
12
- include FileUtils
13
-
14
- NAME = "ruby-mysql"
15
- AUTHOR = "tommy"
16
- EMAIL = "tommy@tmtm.org"
17
- DESCRIPTION = "MySQL connector for Ruby"
18
- RUBYFORGE_PROJECT = "rubymysql"
19
- HOMEPATH = "http://github.com/tmtm/ruby-mysql"
20
- BIN_FILES = %w( )
21
-
22
- VERS = "3.0.0"
23
- REV = File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
24
- CLEAN.include ['**/.*.sw?', '*.gem', '.config']
25
- RDOC_OPTS = [
26
- '--title', "#{NAME} documentation",
27
- "--charset", "utf-8",
28
- "--opname", "index.html",
29
- "--line-numbers",
30
- "--main", "README",
31
- "--inline-source",
32
- ]
33
-
34
- task :default => [:test]
35
- task :package => [:clean]
36
-
37
- Rake::TestTask.new("test") do |t|
38
- t.libs << "test"
39
- t.pattern = "test/**/*_test.rb"
40
- t.verbose = true
41
- end
42
-
43
- spec = Gem::Specification.new do |s|
44
- s.name = NAME
45
- s.version = VERS
46
- s.platform = Gem::Platform::RUBY
47
- s.has_rdoc = true
48
- s.extra_rdoc_files = ["README", "ChangeLog"]
49
- s.rdoc_options += RDOC_OPTS + ['--exclude', '^(examples|extras)/']
50
- s.summary = DESCRIPTION
51
- s.description = DESCRIPTION
52
- s.author = AUTHOR
53
- s.email = EMAIL
54
- s.homepage = HOMEPATH
55
- s.executables = BIN_FILES
56
- s.rubyforge_project = RUBYFORGE_PROJECT
57
- s.bindir = "bin"
58
- s.require_path = "lib"
59
- #s.autorequire = ""
60
- s.test_files = Dir["test/*_test.rb"]
61
-
62
- #s.add_dependency('activesupport', '>=1.3.1')
63
- s.required_ruby_version = '>= 1.8.7'
64
-
65
- s.files = %w(README ChangeLog Rakefile) +
66
- Dir.glob("{bin,doc,test,lib,templates,generator,extras,website,script}/**/*") +
67
- Dir.glob("ext/**/*.{h,c,rb}") +
68
- Dir.glob("examples/**/*.rb") +
69
- Dir.glob("tools/*.rb") +
70
- Dir.glob("rails/*.rb")
71
-
72
- s.extensions = FileList["ext/**/extconf.rb"].to_a
73
- end
74
-
75
- Rake::GemPackageTask.new(spec) do |p|
76
- p.need_tar = true
77
- p.gem_spec = spec
78
- end
79
-
80
- task :install do
81
- name = "#{NAME}-#{VERS}.gem"
82
- sh %{rake package}
83
- sh %{sudo gem install pkg/#{name}}
84
- end
85
-
86
- task :uninstall => [:clean] do
87
- sh %{sudo gem uninstall #{NAME}}
88
- end
89
-
90
-
91
- Rake::RDocTask.new do |rdoc|
92
- rdoc.rdoc_dir = 'html'
93
- rdoc.options += RDOC_OPTS
94
- rdoc.template = "resh"
95
- #rdoc.template = "#{ENV['template']}.rb" if ENV['template']
96
- if ENV['DOC_FILES']
97
- rdoc.rdoc_files.include(ENV['DOC_FILES'].split(/,\s*/))
98
- else
99
- rdoc.rdoc_files.include('README', 'ChangeLog')
100
- rdoc.rdoc_files.include('lib/**/*.rb')
101
- rdoc.rdoc_files.include('ext/**/*.c')
102
- end
103
- end
104
-
105
- desc "Publish to RubyForge"
106
- task :rubyforge => [:rdoc, :package] do
107
- require 'rubyforge'
108
- Rake::RubyForgePublisher.new(RUBYFORGE_PROJECT, 'tommy').upload
109
- end
110
-
111
- desc 'Package and upload the release to rubyforge.'
112
- task :release => [:clean, :package] do |t|
113
- v = ENV["VERSION"] or abort "Must supply VERSION=x.y.z"
114
- abort "Versions don't match #{v} vs #{VERS}" unless v == VERS
115
- pkg = "pkg/#{NAME}-#{VERS}"
116
-
117
- require 'rubyforge'
118
- rf = RubyForge.new.configure
119
- puts "Logging in"
120
- rf.login
121
-
122
- c = rf.userconfig
123
- # c["release_notes"] = description if description
124
- # c["release_changes"] = changes if changes
125
- c["preformatted"] = true
126
-
127
- files = [
128
- "#{pkg}.tgz",
129
- "#{pkg}.gem"
130
- ].compact
131
-
132
- puts "Releasing #{NAME} v. #{VERS}"
133
- rf.add_release RUBYFORGE_PROJECT, NAME, VERS, *files
134
- end
135
-
136
- desc 'Show information about the gem.'
137
- task :debug_gem do
138
- puts spec.to_ruby
139
- end
140
-
141
- desc 'Update gem spec'
142
- task :gemspec do
143
- open("#{NAME}.gemspec", 'w').write spec.to_ruby
144
- end
data/lib/mysql/cache.rb DELETED
@@ -1,26 +0,0 @@
1
- # Copyright (C) 2008 TOMITA Masahiro
2
- # mailto:tommy@tmtm.org
3
-
4
- class Mysql
5
- class Cache
6
- def initialize(size)
7
- @size = size || 0
8
- @cache = {}
9
- @timestamp = {}
10
- end
11
- def get(key)
12
- if @size <= 0
13
- return yield key
14
- end
15
- if @cache.key? key
16
- @timestamp[key] = ::Time.now
17
- return @cache[key]
18
- end
19
- if @cache.size >= @size
20
- oldest_key = @timestamp.min_by{|k,v| v}.first
21
- @cache.delete oldest_key
22
- end
23
- @cache[key] = yield key
24
- end
25
- end
26
- end