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