tmtm-ruby-mysql 3.0.0 → 3.0.1
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.
- 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
|