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 +19 -2
- data/README +3 -2
- data/lib/mysql/charset.rb +1 -1
- data/lib/mysql/compat.rb +85 -27
- data/lib/mysql/protocol.rb +4 -4
- data/lib/mysql.rb +192 -187
- metadata +2 -5
- data/Rakefile +0 -144
- data/lib/mysql/cache.rb +0 -26
data/ChangeLog
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
-
|
1
|
+
2009-07-12 TOMITA Masahiro <tommy@tmtm.org>
|
2
2
|
|
3
|
-
*
|
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@
|
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
data/lib/mysql/compat.rb
CHANGED
@@ -5,11 +5,20 @@
|
|
5
5
|
|
6
6
|
class Mysql
|
7
7
|
class << self
|
8
|
-
|
9
|
-
|
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
|
-
|
39
|
-
|
40
|
-
alias
|
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
|
-
|
44
|
-
|
45
|
-
|
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
|
52
|
-
|
53
|
-
|
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
|
-
@
|
234
|
+
@res.num_rows
|
179
235
|
end
|
180
236
|
|
181
237
|
def data_seek(n)
|
182
|
-
@
|
238
|
+
@res.data_seek(n)
|
183
239
|
end
|
184
240
|
|
185
241
|
def row_tell
|
186
|
-
@
|
242
|
+
@res.row_tell
|
187
243
|
end
|
188
244
|
|
189
245
|
def row_seek(n)
|
190
|
-
|
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.
|
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
|
data/lib/mysql/protocol.rb
CHANGED
@@ -52,7 +52,7 @@ class Mysql
|
|
52
52
|
lcb[0, 9] = ""
|
53
53
|
return (v2 << 32) | v1
|
54
54
|
else
|
55
|
-
return ord!
|
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
|
435
|
+
return self.new(field_count, affected_rows, insert_id, server_status, warning_count, message)
|
436
436
|
else
|
437
|
-
return self.new
|
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
|
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
|
-
|
5
|
-
require "
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
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
|
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
|
87
|
+
my = self.new(*args)
|
78
88
|
my.connect
|
79
89
|
return my unless block
|
80
90
|
begin
|
81
|
-
return block.call
|
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
|
-
@
|
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
|
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
|
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::
|
184
|
-
#
|
185
|
-
#
|
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
|
190
|
-
|
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
|
-
|
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
|
-
#
|
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
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
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
|
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
|
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
|
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
|
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
|
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
|
-
|
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
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
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
|
-
|
499
|
-
alias mon month
|
500
|
-
alias min minute
|
501
|
-
alias sec second
|
547
|
+
end
|
502
548
|
|
503
|
-
|
504
|
-
|
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
|
-
|
511
|
-
self == other
|
512
|
-
end
|
552
|
+
private
|
513
553
|
|
514
|
-
def
|
515
|
-
|
516
|
-
|
517
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
627
|
-
|
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
|
-
|
637
|
-
|
638
|
-
|
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
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
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
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
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
|
679
|
-
|
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
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
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.
|
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-
|
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
|