do_mysql 0.9.2 → 0.9.3
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/History.txt +1 -0
- data/Manifest.txt +20 -0
- data/README.txt +3 -0
- data/Rakefile +23 -34
- data/buildfile +26 -0
- data/ext-java/src/main/java/DoMysqlExtService.java +23 -0
- data/ext-java/src/main/java/do_mysql/MySqlDriverDefinition.java +12 -0
- data/ext/do_mysql_ext.c +615 -583
- data/lib/do_mysql/version.rb +5 -0
- data/spec/integration/do_mysql_spec.rb +70 -10
- data/spec/integration/logging_spec.rb +9 -4
- data/spec/integration/quoting_spec.rb +5 -1
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +12 -6
- metadata +35 -12
data/History.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
data/Manifest.txt
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
History.txt
|
|
2
|
+
LICENSE
|
|
3
|
+
Manifest.txt
|
|
4
|
+
README.txt
|
|
5
|
+
Rakefile
|
|
6
|
+
TODO
|
|
7
|
+
buildfile
|
|
8
|
+
ext-java/src/main/java/DoMysqlExtService.java
|
|
9
|
+
ext-java/src/main/java/do_mysql/MySqlDriverDefinition.java
|
|
10
|
+
ext/do_mysql_ext.c
|
|
11
|
+
ext/extconf.rb
|
|
12
|
+
lib/do_mysql.rb
|
|
13
|
+
lib/do_mysql/transaction.rb
|
|
14
|
+
lib/do_mysql/version.rb
|
|
15
|
+
spec/integration/do_mysql_spec.rb
|
|
16
|
+
spec/integration/logging_spec.rb
|
|
17
|
+
spec/integration/quoting_spec.rb
|
|
18
|
+
spec/spec.opts
|
|
19
|
+
spec/spec_helper.rb
|
|
20
|
+
spec/unit/transaction_spec.rb
|
data/README.txt
ADDED
data/Rakefile
CHANGED
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
require 'rubygems'
|
|
2
|
-
require 'rake/clean'
|
|
3
|
-
require 'rake/gempackagetask'
|
|
4
2
|
require 'spec/rake/spectask'
|
|
5
3
|
require 'pathname'
|
|
6
|
-
require Pathname(__FILE__).dirname.expand_path.parent + 'tasks/ext_helper'
|
|
7
|
-
require Pathname(__FILE__).dirname.expand_path.parent + 'tasks/ext_helper_java'
|
|
8
4
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
'ext/Makefile'
|
|
5
|
+
ROOT = Pathname(__FILE__).dirname.expand_path
|
|
6
|
+
|
|
7
|
+
require "lib/do_mysql/version"
|
|
13
8
|
|
|
14
9
|
JRUBY = (RUBY_PLATFORM =~ /java/) rescue nil
|
|
15
10
|
WINDOWS = (RUBY_PLATFORM =~ /mswin|mingw|cygwin/) rescue nil
|
|
@@ -17,41 +12,35 @@ WINDOWS = (RUBY_PLATFORM =~ /mswin|mingw|cygwin/) rescue nil
|
|
|
17
12
|
# is not entirely correct.
|
|
18
13
|
SUDO = (WINDOWS || JRUBY) ? '' : ('sudo' unless ENV['SUDOLESS'])
|
|
19
14
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
s.description = s.summary
|
|
28
|
-
s.author = 'Scott Bauer'
|
|
29
|
-
s.email = 'bauer.mail@gmail.com'
|
|
30
|
-
s.homepage = 'http://rubyforge.org/projects/dorb'
|
|
31
|
-
s.rubyforge_project = 'dorb'
|
|
32
|
-
s.require_path = 'lib'
|
|
33
|
-
s.extensions = %w[ ext/extconf.rb ]
|
|
34
|
-
s.files = FileList[ '{ext,lib,spec}/**/*.{c,rb}', 'Rakefile', *s.extra_rdoc_files ]
|
|
35
|
-
s.add_dependency('data_objects', "=#{s.version}")
|
|
36
|
-
end
|
|
15
|
+
AUTHOR = "Scott Bauer"
|
|
16
|
+
EMAIL = "bauer.mail@gmail.com"
|
|
17
|
+
GEM_NAME = "do_mysql"
|
|
18
|
+
GEM_VERSION = DataObjects::Mysql::VERSION
|
|
19
|
+
GEM_DEPENDENCIES = [["data_objects", GEM_VERSION]]
|
|
20
|
+
GEM_CLEAN = ['**/*.{o,so,bundle,log,a,gem,dSYM,obj,pdb,lib,def,exp,DS_Store}', 'ext/Makefile']
|
|
21
|
+
GEM_EXTRAS = { :extensions => %w[ ext/extconf.rb ], :has_rdoc => false }
|
|
37
22
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
23
|
+
PROJECT_NAME = "dorb"
|
|
24
|
+
PROJECT_URL = "http://rubyforge.org/projects/dorb"
|
|
25
|
+
PROJECT_DESCRIPTION = PROJECT_SUMMARY = "A DataObject.rb driver for MySQL"
|
|
41
26
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
27
|
+
DRIVER = true
|
|
28
|
+
|
|
29
|
+
require ROOT.parent + 'tasks/hoe'
|
|
30
|
+
|
|
31
|
+
# Installation
|
|
45
32
|
|
|
46
33
|
task :install => [ :package ] do
|
|
47
|
-
sh %{#{SUDO} gem install --local pkg/#{
|
|
34
|
+
sh %{#{SUDO} gem install --local pkg/#{GEM_NAME}-#{GEM_VERSION} --no-update-sources}, :verbose => false
|
|
48
35
|
end
|
|
49
36
|
|
|
50
|
-
desc "Uninstall #{
|
|
37
|
+
desc "Uninstall #{GEM_NAME} #{GEM_VERSION} (default ruby)"
|
|
51
38
|
task :uninstall => [ :clobber ] do
|
|
52
|
-
sh "#{SUDO} gem uninstall #{
|
|
39
|
+
sh "#{SUDO} gem uninstall #{GEM_NAME} -v#{GEM_VERSION} -I -x", :verbose => false
|
|
53
40
|
end
|
|
54
41
|
|
|
42
|
+
# Specs
|
|
43
|
+
|
|
55
44
|
desc 'Run specifications'
|
|
56
45
|
Spec::Rake::SpecTask.new(:spec => [ :compile ]) do |t|
|
|
57
46
|
t.spec_opts << '--options' << 'spec/spec.opts' if File.exists?('spec/spec.opts')
|
data/buildfile
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Apache Buildr buildfile for do_derby
|
|
2
|
+
# see http://incubator.apache.org/buildr/ for more information on Apache Buildr
|
|
3
|
+
require 'buildr'
|
|
4
|
+
require 'pathname'
|
|
5
|
+
|
|
6
|
+
VERSION_NUMBER = '1.0'
|
|
7
|
+
JDBC_SUPPORT = ['data_objects:jdbc:jar:1.0']
|
|
8
|
+
repositories.remote << 'http://www.ibiblio.org/maven2/'
|
|
9
|
+
|
|
10
|
+
define 'do_mysql' do
|
|
11
|
+
project.version = VERSION_NUMBER
|
|
12
|
+
project.group = 'data_objects.rb'
|
|
13
|
+
|
|
14
|
+
manifest['Copyright'] = 'Alex Coles (C) 2008'
|
|
15
|
+
|
|
16
|
+
compile.using :target => '1.5', :lint => 'all', :deprecation => 'true'
|
|
17
|
+
|
|
18
|
+
define 'ext-java' do
|
|
19
|
+
package :jar
|
|
20
|
+
|
|
21
|
+
jdbc_support_jar = file('../../do_jdbc-support/lib/do_jdbc_internal.jar')
|
|
22
|
+
jdbc_support = artifact('data_objects:jdbc:jar:1.0').from(jdbc_support_jar)
|
|
23
|
+
|
|
24
|
+
compile.with JDBC_SUPPORT
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import data_objects.drivers.DriverDefinition;
|
|
2
|
+
import do_mysql.MySqlDriverDefinition;
|
|
3
|
+
|
|
4
|
+
public class DoMysqlExtService extends AbstractDataObjectsExtService {
|
|
5
|
+
|
|
6
|
+
private final static DriverDefinition driver = new MySqlDriverDefinition();
|
|
7
|
+
public final static String RUBY_MODULE_NAME = "Mysql";
|
|
8
|
+
public final static String RUBY_ERROR_NAME = "MysqlError";
|
|
9
|
+
|
|
10
|
+
public String getModuleName() {
|
|
11
|
+
return RUBY_MODULE_NAME;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@Override
|
|
15
|
+
public String getErrorName() {
|
|
16
|
+
return RUBY_ERROR_NAME;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
public DriverDefinition getDriverDefinition() {
|
|
20
|
+
return driver;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
}
|
data/ext/do_mysql_ext.c
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
#define TAINTED_STRING(name) rb_tainted_str_new2(name)
|
|
14
14
|
#define DRIVER_CLASS(klass, parent) (rb_define_class_under(mDOMysql, klass, parent))
|
|
15
15
|
#define CONST_GET(scope, constant) (rb_funcall(scope, ID_CONST_GET, 1, rb_str_new2(constant)))
|
|
16
|
-
#define CHECK_AND_RAISE(mysql_result_value) if (0 != mysql_result_value) { raise_mysql_error(db, mysql_result_value); }
|
|
16
|
+
#define CHECK_AND_RAISE(mysql_result_value) if (0 != mysql_result_value) { raise_mysql_error(connection, db, mysql_result_value); }
|
|
17
17
|
#define PUTS(string) rb_funcall(rb_mKernel, rb_intern("puts"), 1, RUBY_STRING(string))
|
|
18
18
|
|
|
19
19
|
#ifdef _WIN32
|
|
@@ -61,55 +61,55 @@ static VALUE cCommand;
|
|
|
61
61
|
static VALUE cResult;
|
|
62
62
|
static VALUE cReader;
|
|
63
63
|
static VALUE eMysqlError;
|
|
64
|
-
|
|
64
|
+
|
|
65
65
|
// Figures out what we should cast a given mysql field type to
|
|
66
66
|
static char * ruby_type_from_mysql_type(MYSQL_FIELD *field) {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
67
|
+
|
|
68
|
+
char* ruby_type_name;
|
|
69
|
+
|
|
70
|
+
switch(field->type) {
|
|
71
|
+
case MYSQL_TYPE_NULL: {
|
|
72
|
+
ruby_type_name = NULL;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
case MYSQL_TYPE_TINY: {
|
|
76
|
+
ruby_type_name = "TrueClass";
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
case MYSQL_TYPE_SHORT:
|
|
80
|
+
case MYSQL_TYPE_LONG:
|
|
81
|
+
case MYSQL_TYPE_INT24:
|
|
82
|
+
case MYSQL_TYPE_LONGLONG:
|
|
83
|
+
case MYSQL_TYPE_YEAR: {
|
|
84
|
+
ruby_type_name = "Fixnum";
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
case MYSQL_TYPE_DECIMAL:
|
|
88
|
+
case MYSQL_TYPE_FLOAT:
|
|
89
|
+
case MYSQL_TYPE_DOUBLE: {
|
|
90
|
+
ruby_type_name = "BigDecimal";
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
case MYSQL_TYPE_TIMESTAMP:
|
|
94
|
+
case MYSQL_TYPE_DATETIME: {
|
|
95
|
+
ruby_type_name = "DateTime";
|
|
96
|
+
break;
|
|
97
|
+
}
|
|
98
|
+
case MYSQL_TYPE_TIME: {
|
|
99
|
+
ruby_type_name = "DateTime";
|
|
100
|
+
break;
|
|
101
|
+
}
|
|
102
|
+
case MYSQL_TYPE_DATE: {
|
|
103
|
+
ruby_type_name = "Date";
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
default: {
|
|
107
|
+
// printf("Falling to default: %s - %d\n", field->name, field->type);
|
|
108
|
+
ruby_type_name = "String";
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return ruby_type_name;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
// Find the greatest common denominator and reduce the provided numerator and denominator.
|
|
@@ -119,18 +119,18 @@ static void reduce( do_int64 *numerator, do_int64 *denominator ) {
|
|
|
119
119
|
a = *numerator;
|
|
120
120
|
b = *denominator;
|
|
121
121
|
while ( a != 0 ) {
|
|
122
|
-
|
|
122
|
+
c = a; a = b % a; b = c;
|
|
123
123
|
}
|
|
124
124
|
*numerator = *numerator / b;
|
|
125
125
|
*denominator = *denominator / b;
|
|
126
126
|
}
|
|
127
|
-
|
|
127
|
+
|
|
128
128
|
// Generate the date integer which Date.civil_to_jd returns
|
|
129
129
|
static int jd_from_date(int year, int month, int day) {
|
|
130
130
|
int a, b;
|
|
131
131
|
if ( month <= 2 ) {
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
year -= 1;
|
|
133
|
+
month += 12;
|
|
134
134
|
}
|
|
135
135
|
a = year / 100;
|
|
136
136
|
b = 2 - a + (a / 4);
|
|
@@ -138,366 +138,388 @@ static int jd_from_date(int year, int month, int day) {
|
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
static VALUE seconds_to_offset(long seconds_offset) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
141
|
+
do_int64 num = seconds_offset, den = 86400;
|
|
142
|
+
reduce(&num, &den);
|
|
143
|
+
return rb_funcall(rb_cRational, rb_intern("new!"), 2, rb_ll2inum(num), rb_ll2inum(den));
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
static VALUE parse_date(const char *date) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
147
|
+
int year, month, day;
|
|
148
|
+
int jd, ajd;
|
|
149
|
+
VALUE rational;
|
|
150
|
+
|
|
151
|
+
sscanf(date, "%4d-%2d-%2d", &year, &month, &day);
|
|
152
|
+
|
|
153
|
+
jd = jd_from_date(year, month, day);
|
|
154
|
+
|
|
155
|
+
// Math from Date.jd_to_ajd
|
|
156
|
+
ajd = jd * 2 - 1;
|
|
157
|
+
rational = rb_funcall(rb_cRational, ID_NEW_RATIONAL, 2, INT2NUM(ajd), INT2NUM(2));
|
|
158
|
+
return rb_funcall(rb_cDate, ID_NEW_DATE, 3, rational, INT2NUM(0), INT2NUM(2299161));
|
|
159
159
|
}
|
|
160
160
|
|
|
161
161
|
static VALUE parse_time(const char *date) {
|
|
162
162
|
|
|
163
|
-
|
|
164
|
-
|
|
163
|
+
int year, month, day, hour, min, sec, usec;
|
|
164
|
+
char subsec[7];
|
|
165
165
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
166
|
+
if (0 != strchr(date, '.')) {
|
|
167
|
+
// right padding usec with 0. e.g. '012' will become 12000 microsecond, since Time#local use microsecond
|
|
168
|
+
sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d.%s", &year, &month, &day, &hour, &min, &sec, subsec);
|
|
169
|
+
sscanf(subsec, "%d", &usec);
|
|
170
|
+
} else {
|
|
171
|
+
sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
|
|
172
|
+
usec = 0;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if ( year + month + day + hour + min + sec + usec == 0 ) { // Mysql TIMESTAMPS can default to 0
|
|
176
|
+
return Qnil;
|
|
177
|
+
}
|
|
174
178
|
|
|
175
|
-
|
|
179
|
+
return rb_funcall(rb_cTime, rb_intern("local"), 7, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec), INT2NUM(usec));
|
|
176
180
|
}
|
|
177
181
|
|
|
178
182
|
static VALUE parse_date_time(const char *date_time) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
183
|
+
VALUE ajd, offset;
|
|
184
|
+
|
|
185
|
+
int year, month, day, hour, min, sec;
|
|
186
|
+
int jd;
|
|
187
|
+
do_int64 num, den;
|
|
188
|
+
|
|
189
|
+
time_t rawtime;
|
|
190
|
+
struct tm * timeinfo;
|
|
191
|
+
|
|
192
|
+
// Mysql date format: 2008-05-03 14:43:00
|
|
193
|
+
sscanf(date_time, "%4d-%2d-%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
|
|
194
|
+
|
|
195
|
+
jd = jd_from_date(year, month, day);
|
|
196
|
+
|
|
197
|
+
// Generate ajd with fractional days for the time
|
|
198
|
+
// Extracted from Date#jd_to_ajd, Date#day_fraction_to_time, and Rational#+ and #-
|
|
199
|
+
num = ((hour) * 1440) + ((min) * 24); // (Hour * Minutes in a day) + (minutes * 24)
|
|
200
|
+
|
|
201
|
+
// Get localtime
|
|
202
|
+
time(&rawtime);
|
|
203
|
+
timeinfo = localtime(&rawtime);
|
|
204
|
+
|
|
205
|
+
// TODO: Refactor the following few lines to do the calculation with the *seconds*
|
|
206
|
+
// value instead of having to do the hour/minute math
|
|
207
|
+
int hour_offset = abs(timeinfo->tm_gmtoff) / 3600;
|
|
208
|
+
int minute_offset = abs(timeinfo->tm_gmtoff) % 3600 / 60;
|
|
209
|
+
|
|
210
|
+
// Modify the numerator so when we apply the timezone everything works out
|
|
211
|
+
if (timeinfo->tm_gmtoff < 0) {
|
|
212
|
+
// If the Timezone is behind UTC, we need to add the time offset
|
|
213
|
+
num += (hour_offset * 1440) + (minute_offset * 24);
|
|
214
|
+
} else {
|
|
215
|
+
// If the Timezone is ahead of UTC, we need to subtract the time offset
|
|
216
|
+
num -= (hour_offset * 1440) + (minute_offset * 24);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
den = (24 * 1440);
|
|
220
|
+
reduce(&num, &den);
|
|
221
|
+
|
|
222
|
+
num = (num * 86400) + (sec * den);
|
|
223
|
+
den = den * 86400;
|
|
224
|
+
reduce(&num, &den);
|
|
225
|
+
|
|
226
|
+
num = (jd * den) + num;
|
|
227
|
+
|
|
228
|
+
num = num * 2 - den;
|
|
229
|
+
den = den * 2;
|
|
230
|
+
reduce(&num, &den);
|
|
231
|
+
|
|
232
|
+
ajd = rb_funcall(rb_cRational, rb_intern("new!"), 2, rb_ull2inum(num), rb_ull2inum(den));
|
|
233
|
+
|
|
234
|
+
// Calculate the offset using the seconds from GMT
|
|
235
|
+
offset = seconds_to_offset(timeinfo->tm_gmtoff);
|
|
236
|
+
|
|
237
|
+
return rb_funcall(rb_cDateTime, ID_NEW_DATE, 3, ajd, offset, INT2NUM(2299161));
|
|
234
238
|
}
|
|
235
239
|
|
|
236
240
|
// Convert C-string to a Ruby instance of Ruby type "type"
|
|
237
241
|
static VALUE typecast(const char* value, char* type) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
242
|
+
if (NULL == value)
|
|
243
|
+
return Qnil;
|
|
244
|
+
|
|
245
|
+
if ( strcmp(type, "Class") == 0) {
|
|
246
|
+
return rb_funcall(mDO, rb_intern("find_const"), 1, TAINTED_STRING(value));
|
|
247
|
+
} else if ( strcmp(type, "Integer") == 0 || strcmp(type, "Fixnum") == 0 || strcmp(type, "Bignum") == 0 ) {
|
|
248
|
+
return rb_cstr2inum(value, 10);
|
|
249
|
+
} else if (0 == strcmp("String", type)) {
|
|
250
|
+
return TAINTED_STRING(value);
|
|
251
|
+
} else if (0 == strcmp("Float", type) ) {
|
|
252
|
+
return rb_float_new(rb_cstr_to_dbl(value, Qfalse));
|
|
253
|
+
} else if (0 == strcmp("BigDecimal", type) ) {
|
|
254
|
+
return rb_funcall(rb_cBigDecimal, ID_NEW, 1, TAINTED_STRING(value));
|
|
255
|
+
} else if (0 == strcmp("TrueClass", type) || 0 == strcmp("FalseClass", type)) {
|
|
256
|
+
return (0 == value || 0 == strcmp("0", value)) ? Qfalse : Qtrue;
|
|
257
|
+
} else if (0 == strcmp("Date", type)) {
|
|
258
|
+
return parse_date(value);
|
|
259
|
+
} else if (0 == strcmp("DateTime", type)) {
|
|
260
|
+
return parse_date_time(value);
|
|
261
|
+
} else if (0 == strcmp("Time", type)) {
|
|
262
|
+
return parse_time(value);
|
|
263
|
+
} else {
|
|
264
|
+
return TAINTED_STRING(value);
|
|
265
|
+
}
|
|
262
266
|
}
|
|
263
267
|
|
|
264
268
|
static void data_objects_debug(VALUE string) {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
269
|
+
VALUE logger = rb_funcall(mDOMysql, ID_LOGGER, 0);
|
|
270
|
+
int log_level = NUM2INT(rb_funcall(logger, ID_LEVEL, 0));
|
|
271
|
+
|
|
272
|
+
if (0 == log_level) {
|
|
273
|
+
rb_funcall(logger, ID_DEBUG, 1, string);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
static void flush_pool(VALUE connection) {
|
|
278
|
+
data_objects_debug(rb_funcall(connection, rb_intern("inspect"), 0));
|
|
279
|
+
if ( Qnil != connection ) {
|
|
280
|
+
VALUE pool = rb_iv_get(connection, "@__pool");
|
|
281
|
+
rb_funcall(pool, rb_intern("flush!"), 0);
|
|
282
|
+
rb_funcall(pool, rb_intern("delete"), 1, connection);
|
|
283
|
+
rb_funcall(connection, rb_intern("dispose"), 0);
|
|
284
|
+
}
|
|
271
285
|
}
|
|
272
286
|
|
|
273
287
|
// We can add custom information to error messages using this function
|
|
274
288
|
// if we think it matters
|
|
275
|
-
static void raise_mysql_error(MYSQL *db, int mysql_error_code) {
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
289
|
+
static void raise_mysql_error(VALUE connection, MYSQL *db, int mysql_error_code) {
|
|
290
|
+
char *mysql_error_message = (char *)mysql_error(db);
|
|
291
|
+
int length = strlen(mysql_error_message) + 25; // length of " (mysql_error_code=0000)"
|
|
292
|
+
char *error_message = (char *)calloc(length, sizeof(char));
|
|
293
|
+
|
|
294
|
+
sprintf(error_message, "%s (mysql_error_code=%04d)", mysql_error_message, mysql_error_code);
|
|
295
|
+
|
|
296
|
+
data_objects_debug(rb_str_new2(error_message));
|
|
297
|
+
|
|
298
|
+
switch(mysql_error_code) {
|
|
299
|
+
case CR_UNKNOWN_ERROR:
|
|
300
|
+
case CR_SOCKET_CREATE_ERROR:
|
|
301
|
+
case CR_CONNECTION_ERROR:
|
|
302
|
+
case CR_CONN_HOST_ERROR:
|
|
303
|
+
case CR_IPSOCK_ERROR:
|
|
304
|
+
case CR_UNKNOWN_HOST:
|
|
305
|
+
case CR_SERVER_GONE_ERROR:
|
|
306
|
+
case CR_VERSION_ERROR:
|
|
307
|
+
case CR_OUT_OF_MEMORY:
|
|
308
|
+
case CR_WRONG_HOST_INFO:
|
|
309
|
+
case CR_LOCALHOST_CONNECTION:
|
|
310
|
+
case CR_TCP_CONNECTION:
|
|
311
|
+
case CR_SERVER_HANDSHAKE_ERR:
|
|
312
|
+
case CR_SERVER_LOST:
|
|
313
|
+
case CR_COMMANDS_OUT_OF_SYNC:
|
|
314
|
+
case CR_NAMEDPIPE_CONNECTION:
|
|
315
|
+
case CR_NAMEDPIPEWAIT_ERROR:
|
|
316
|
+
case CR_NAMEDPIPEOPEN_ERROR:
|
|
317
|
+
case CR_NAMEDPIPESETSTATE_ERROR:
|
|
318
|
+
case CR_CANT_READ_CHARSET:
|
|
319
|
+
case CR_NET_PACKET_TOO_LARGE:
|
|
320
|
+
case CR_EMBEDDED_CONNECTION:
|
|
321
|
+
case CR_PROBE_SLAVE_STATUS:
|
|
322
|
+
case CR_PROBE_SLAVE_HOSTS:
|
|
323
|
+
case CR_PROBE_SLAVE_CONNECT:
|
|
324
|
+
case CR_PROBE_MASTER_CONNECT:
|
|
325
|
+
case CR_SSL_CONNECTION_ERROR:
|
|
326
|
+
case CR_MALFORMED_PACKET:
|
|
327
|
+
case CR_WRONG_LICENSE:
|
|
328
|
+
case CR_NULL_POINTER:
|
|
329
|
+
case CR_NO_PREPARE_STMT:
|
|
330
|
+
case CR_PARAMS_NOT_BOUND:
|
|
331
|
+
case CR_DATA_TRUNCATED:
|
|
332
|
+
case CR_NO_PARAMETERS_EXISTS:
|
|
333
|
+
case CR_INVALID_PARAMETER_NO:
|
|
334
|
+
case CR_INVALID_BUFFER_USE:
|
|
335
|
+
case CR_UNSUPPORTED_PARAM_TYPE:
|
|
336
|
+
case CR_SHARED_MEMORY_CONNECTION:
|
|
337
|
+
case CR_SHARED_MEMORY_CONNECT_REQUEST_ERROR:
|
|
338
|
+
case CR_SHARED_MEMORY_CONNECT_ANSWER_ERROR:
|
|
339
|
+
case CR_SHARED_MEMORY_CONNECT_FILE_MAP_ERROR:
|
|
340
|
+
case CR_SHARED_MEMORY_CONNECT_MAP_ERROR:
|
|
341
|
+
case CR_SHARED_MEMORY_FILE_MAP_ERROR:
|
|
342
|
+
case CR_SHARED_MEMORY_MAP_ERROR:
|
|
343
|
+
case CR_SHARED_MEMORY_EVENT_ERROR:
|
|
344
|
+
case CR_SHARED_MEMORY_CONNECT_ABANDONED_ERROR:
|
|
345
|
+
case CR_SHARED_MEMORY_CONNECT_SET_ERROR:
|
|
346
|
+
case CR_CONN_UNKNOW_PROTOCOL:
|
|
347
|
+
case CR_INVALID_CONN_HANDLE:
|
|
348
|
+
case CR_SECURE_AUTH:
|
|
349
|
+
case CR_FETCH_CANCELED:
|
|
350
|
+
case CR_NO_DATA:
|
|
351
|
+
case CR_NO_STMT_METADATA:
|
|
332
352
|
#if MYSQL_VERSION_ID >= 50000
|
|
333
|
-
|
|
334
|
-
|
|
353
|
+
case CR_NO_RESULT_SET:
|
|
354
|
+
case CR_NOT_IMPLEMENTED:
|
|
335
355
|
#endif
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
356
|
+
{
|
|
357
|
+
break;
|
|
358
|
+
}
|
|
359
|
+
default: {
|
|
360
|
+
// Hmmm
|
|
361
|
+
break;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
flush_pool(connection);
|
|
366
|
+
rb_raise(eMysqlError, error_message);
|
|
346
367
|
}
|
|
347
368
|
|
|
348
369
|
// Pull an option out of a querystring-formmated option list using CGI::parse
|
|
349
370
|
static char * get_uri_option(VALUE querystring, char * key) {
|
|
350
|
-
|
|
371
|
+
VALUE options_hash, option_value;
|
|
351
372
|
|
|
352
|
-
|
|
373
|
+
char * value = NULL;
|
|
353
374
|
|
|
354
|
-
|
|
355
|
-
|
|
375
|
+
// Ensure that we're dealing with a string
|
|
376
|
+
querystring = rb_funcall(querystring, ID_TO_S, 0);
|
|
356
377
|
|
|
357
|
-
|
|
378
|
+
options_hash = rb_funcall(rb_cCGI, ID_PARSE, 1, querystring);
|
|
358
379
|
|
|
359
|
-
|
|
360
|
-
|
|
380
|
+
// TODO: rb_hash_aref always returns an array?
|
|
381
|
+
option_value = rb_ary_entry(rb_hash_aref(options_hash, RUBY_STRING(key)), 0);
|
|
361
382
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
383
|
+
if (Qnil != option_value) {
|
|
384
|
+
value = StringValuePtr(option_value);
|
|
385
|
+
}
|
|
365
386
|
|
|
366
|
-
|
|
387
|
+
return value;
|
|
367
388
|
}
|
|
368
389
|
|
|
369
390
|
static VALUE cConnection_initialize(VALUE self, VALUE uri) {
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
391
|
+
VALUE r_host, r_user, r_password, r_path, r_options, r_port;
|
|
392
|
+
|
|
393
|
+
char *host = "localhost", *user = "root", *password = NULL, *path;
|
|
394
|
+
char *database = "", *socket = NULL;
|
|
395
|
+
char *charset = NULL;
|
|
396
|
+
|
|
397
|
+
int port = 3306;
|
|
398
|
+
unsigned long client_flags = 0;
|
|
399
|
+
int charset_error;
|
|
400
|
+
|
|
401
|
+
MYSQL *db = 0, *result;
|
|
402
|
+
db = (MYSQL *)mysql_init(NULL);
|
|
403
|
+
|
|
404
|
+
rb_iv_set(self, "@using_socket", Qfalse);
|
|
405
|
+
|
|
406
|
+
r_host = rb_funcall(uri, rb_intern("host"), 0);
|
|
407
|
+
if (Qnil != r_host) {
|
|
408
|
+
host = StringValuePtr(r_host);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
r_user = rb_funcall(uri, rb_intern("user"), 0);
|
|
412
|
+
if (Qnil != r_user) {
|
|
413
|
+
user = StringValuePtr(r_user);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
r_password = rb_funcall(uri, rb_intern("password"), 0);
|
|
417
|
+
if (Qnil != r_password) {
|
|
418
|
+
password = StringValuePtr(r_password);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
r_path = rb_funcall(uri, rb_intern("path"), 0);
|
|
423
|
+
path = StringValuePtr(r_path);
|
|
424
|
+
if (Qnil != r_path) {
|
|
425
|
+
database = strtok(path, "/");
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
if (NULL == database || 0 == strlen(database)) {
|
|
429
|
+
rb_raise(eMysqlError, "Database must be specified");
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Pull the querystring off the URI
|
|
433
|
+
r_options = rb_funcall(uri, rb_intern("query"), 0);
|
|
434
|
+
|
|
435
|
+
// Check to see if we're on the db machine. If so, try to use the socket
|
|
436
|
+
if (0 == strcasecmp(host, "localhost")) {
|
|
437
|
+
socket = get_uri_option(r_options, "socket");
|
|
438
|
+
if (NULL != socket) {
|
|
439
|
+
rb_iv_set(self, "@using_socket", Qtrue);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
r_port = rb_funcall(uri, rb_intern("port"), 0);
|
|
444
|
+
if (Qnil != r_port) {
|
|
445
|
+
port = NUM2INT(r_port);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
charset = get_uri_option(r_options, "charset");
|
|
449
|
+
|
|
450
|
+
// If ssl? {
|
|
451
|
+
// mysql_ssl_set(db, key, cert, ca, capath, cipher)
|
|
452
|
+
// }
|
|
453
|
+
|
|
454
|
+
result = (MYSQL *)mysql_real_connect(
|
|
455
|
+
db,
|
|
456
|
+
host,
|
|
457
|
+
user,
|
|
458
|
+
password,
|
|
459
|
+
database,
|
|
460
|
+
port,
|
|
461
|
+
socket,
|
|
462
|
+
client_flags
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
if (NULL == result) {
|
|
466
|
+
raise_mysql_error(Qnil, db, -1);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
if (NULL == charset) {
|
|
470
|
+
charset = (char*)calloc(5, sizeof(char));
|
|
471
|
+
strcpy(charset, "utf8");
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Set the connections character set
|
|
475
|
+
charset_error = mysql_set_character_set(db, charset);
|
|
476
|
+
if (0 != charset_error) {
|
|
477
|
+
raise_mysql_error(Qnil, db, charset_error);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
rb_iv_set(self, "@uri", uri);
|
|
481
|
+
rb_iv_set(self, "@connection", Data_Wrap_Struct(rb_cObject, 0, 0, db));
|
|
482
|
+
|
|
483
|
+
return Qtrue;
|
|
462
484
|
}
|
|
463
485
|
|
|
464
486
|
static VALUE cConnection_character_set(VALUE self) {
|
|
465
|
-
|
|
466
|
-
|
|
487
|
+
VALUE connection_container = rb_iv_get(self, "@connection");
|
|
488
|
+
MYSQL *db;
|
|
467
489
|
|
|
468
|
-
|
|
490
|
+
const char *charset;
|
|
469
491
|
|
|
470
|
-
|
|
471
|
-
|
|
492
|
+
if (Qnil == connection_container)
|
|
493
|
+
return Qfalse;
|
|
472
494
|
|
|
473
|
-
|
|
495
|
+
db = DATA_PTR(connection_container);
|
|
474
496
|
|
|
475
|
-
|
|
497
|
+
charset = mysql_character_set_name(db);
|
|
476
498
|
|
|
477
|
-
|
|
499
|
+
return RUBY_STRING(charset);
|
|
478
500
|
}
|
|
479
501
|
|
|
480
502
|
static VALUE cConnection_is_using_socket(VALUE self) {
|
|
481
|
-
|
|
503
|
+
return rb_iv_get(self, "@using_socket");
|
|
482
504
|
}
|
|
483
505
|
|
|
484
506
|
static VALUE cConnection_dispose(VALUE self) {
|
|
485
|
-
|
|
507
|
+
VALUE connection_container = rb_iv_get(self, "@connection");
|
|
486
508
|
|
|
487
|
-
|
|
509
|
+
MYSQL *db;
|
|
488
510
|
|
|
489
|
-
|
|
490
|
-
|
|
511
|
+
if (Qnil == connection_container)
|
|
512
|
+
return Qfalse;
|
|
491
513
|
|
|
492
|
-
|
|
514
|
+
db = DATA_PTR(connection_container);
|
|
493
515
|
|
|
494
|
-
|
|
495
|
-
|
|
516
|
+
if (NULL == db)
|
|
517
|
+
return Qfalse;
|
|
496
518
|
|
|
497
|
-
|
|
498
|
-
|
|
519
|
+
mysql_close(db);
|
|
520
|
+
rb_iv_set(self, "@connection", Qnil);
|
|
499
521
|
|
|
500
|
-
|
|
522
|
+
return Qtrue;
|
|
501
523
|
}
|
|
502
524
|
|
|
503
525
|
/*
|
|
@@ -505,303 +527,313 @@ Accepts an array of Ruby types (Fixnum, Float, String, etc...) and turns them
|
|
|
505
527
|
into Ruby-strings so we can easily typecast later
|
|
506
528
|
*/
|
|
507
529
|
static VALUE cCommand_set_types(VALUE self, VALUE array) {
|
|
508
|
-
|
|
509
|
-
|
|
530
|
+
VALUE type_strings = rb_ary_new();
|
|
531
|
+
int i;
|
|
510
532
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
533
|
+
for (i = 0; i < RARRAY(array)->len; i++) {
|
|
534
|
+
rb_ary_push(type_strings, RUBY_STRING(rb_class2name(rb_ary_entry(array, i))));
|
|
535
|
+
}
|
|
514
536
|
|
|
515
|
-
|
|
537
|
+
rb_iv_set(self, "@field_types", type_strings);
|
|
516
538
|
|
|
517
|
-
|
|
539
|
+
return array;
|
|
518
540
|
}
|
|
519
541
|
|
|
520
542
|
VALUE cCommand_quote_time(VALUE self, VALUE value) {
|
|
521
|
-
|
|
543
|
+
return rb_funcall(value, ID_STRFTIME, 1, RUBY_STRING("'%Y-%m-%d %H:%M:%S'"));
|
|
522
544
|
}
|
|
523
545
|
|
|
524
546
|
|
|
525
547
|
VALUE cCommand_quote_date_time(VALUE self, VALUE value) {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
548
|
+
// TODO: Support non-local dates. we need to call #new_offset on the date to be
|
|
549
|
+
// quoted and pass in the current locale's date offset (self.new_offset((hours * 3600).to_r / 86400)
|
|
550
|
+
return rb_funcall(value, ID_STRFTIME, 1, RUBY_STRING("'%Y-%m-%d %H:%M:%S'"));
|
|
529
551
|
}
|
|
530
552
|
|
|
531
553
|
VALUE cCommand_quote_date(VALUE self, VALUE value) {
|
|
532
|
-
|
|
554
|
+
return rb_funcall(value, ID_STRFTIME, 1, RUBY_STRING("'%Y-%m-%d'"));
|
|
533
555
|
}
|
|
534
556
|
|
|
535
557
|
static VALUE cCommand_quote_string(VALUE self, VALUE string) {
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
558
|
+
MYSQL *db = DATA_PTR(rb_iv_get(rb_iv_get(self, "@connection"), "@connection"));
|
|
559
|
+
const char *source = StringValuePtr(string);
|
|
560
|
+
char *escaped;
|
|
561
|
+
VALUE result;
|
|
562
|
+
|
|
563
|
+
int quoted_length = 0;
|
|
564
|
+
|
|
565
|
+
// Allocate space for the escaped version of 'string'. Use + 3 allocate space for null term.
|
|
566
|
+
// and the leading and trailing single-quotes.
|
|
567
|
+
// Thanks to http://www.browardphp.com/mysql_manual_en/manual_MySQL_APIs.html#mysql_real_escape_string
|
|
568
|
+
escaped = (char *)calloc(strlen(source) * 3 + 3, sizeof(char));
|
|
569
|
+
|
|
570
|
+
// Escape 'source' using the current charset in use on the conection 'db'
|
|
571
|
+
quoted_length = mysql_real_escape_string(db, escaped + 1, source, strlen(source));
|
|
572
|
+
|
|
573
|
+
// Wrap the escaped string in single-quotes, this is DO's convention
|
|
574
|
+
escaped[0] = escaped[quoted_length + 1] = '\'';
|
|
575
|
+
result = rb_str_new(escaped, quoted_length + 2);
|
|
576
|
+
free(escaped);
|
|
577
|
+
return result;
|
|
556
578
|
}
|
|
557
579
|
|
|
558
580
|
static VALUE build_query_from_args(VALUE klass, int count, VALUE *args) {
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
581
|
+
VALUE query = rb_iv_get(klass, "@text");
|
|
582
|
+
if ( count > 0 ) {
|
|
583
|
+
int i;
|
|
584
|
+
VALUE array = rb_ary_new();
|
|
585
|
+
for ( i = 0; i < count; i++) {
|
|
586
|
+
rb_ary_push(array, (VALUE)args[i]);
|
|
587
|
+
}
|
|
588
|
+
query = rb_funcall(klass, ID_ESCAPE_SQL, 1, array);
|
|
589
|
+
}
|
|
590
|
+
return query;
|
|
569
591
|
}
|
|
570
592
|
|
|
571
593
|
static VALUE cCommand_execute_non_query(int argc, VALUE *argv, VALUE self) {
|
|
572
|
-
|
|
594
|
+
VALUE query;
|
|
573
595
|
|
|
574
|
-
|
|
575
|
-
|
|
596
|
+
MYSQL_RES *response = 0;
|
|
597
|
+
int query_result = 0;
|
|
576
598
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
599
|
+
my_ulonglong affected_rows;
|
|
600
|
+
VALUE connection = rb_iv_get(self, "@connection");
|
|
601
|
+
VALUE mysql_connection = rb_iv_get(connection, "@connection");
|
|
602
|
+
if (Qnil == mysql_connection)
|
|
603
|
+
rb_raise(eMysqlError, "This connection has already been closed.");
|
|
580
604
|
|
|
581
|
-
|
|
605
|
+
MYSQL *db = DATA_PTR(mysql_connection);
|
|
606
|
+
query = build_query_from_args(self, argc, argv);
|
|
582
607
|
|
|
583
|
-
|
|
584
|
-
CHECK_AND_RAISE(query_result);
|
|
608
|
+
data_objects_debug(query);
|
|
585
609
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
mysql_free_result(response);
|
|
610
|
+
query_result = mysql_query(db, StringValuePtr(query));
|
|
611
|
+
CHECK_AND_RAISE(query_result);
|
|
589
612
|
|
|
590
|
-
|
|
591
|
-
|
|
613
|
+
response = (MYSQL_RES *)mysql_store_result(db);
|
|
614
|
+
affected_rows = mysql_affected_rows(db);
|
|
615
|
+
mysql_free_result(response);
|
|
592
616
|
|
|
593
|
-
|
|
617
|
+
if (-1 == affected_rows)
|
|
618
|
+
return Qnil;
|
|
619
|
+
|
|
620
|
+
return rb_funcall(cResult, ID_NEW, 3, self, INT2NUM(affected_rows), INT2NUM(mysql_insert_id(db)));
|
|
594
621
|
}
|
|
595
622
|
|
|
596
623
|
static VALUE cCommand_execute_reader(int argc, VALUE *argv, VALUE self) {
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
624
|
+
VALUE query, reader;
|
|
625
|
+
VALUE field_names, field_types;
|
|
626
|
+
|
|
627
|
+
int query_result = 0;
|
|
628
|
+
int field_count;
|
|
629
|
+
int i;
|
|
630
|
+
|
|
631
|
+
char guess_default_field_types = 0;
|
|
632
|
+
VALUE connection = rb_iv_get(self, "@connection");
|
|
633
|
+
VALUE mysql_connection = rb_iv_get(connection, "@connection");
|
|
634
|
+
if (Qnil == mysql_connection)
|
|
635
|
+
rb_raise(eMysqlError, "This connection has already been closed.");
|
|
636
|
+
|
|
637
|
+
MYSQL *db = DATA_PTR(mysql_connection);
|
|
638
|
+
|
|
639
|
+
MYSQL_RES *response = 0;
|
|
640
|
+
MYSQL_FIELD *field;
|
|
641
|
+
|
|
642
|
+
query = build_query_from_args(self, argc, argv);
|
|
643
|
+
data_objects_debug(query);
|
|
644
|
+
|
|
645
|
+
query_result = mysql_query(db, StringValuePtr(query));
|
|
646
|
+
CHECK_AND_RAISE(query_result);
|
|
647
|
+
|
|
648
|
+
response = (MYSQL_RES *)mysql_use_result(db);
|
|
649
|
+
|
|
650
|
+
if (!response) {
|
|
651
|
+
return Qnil;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
field_count = (int)mysql_field_count(db);
|
|
655
|
+
|
|
656
|
+
reader = rb_funcall(cReader, ID_NEW, 0);
|
|
657
|
+
rb_iv_set(reader, "@reader", Data_Wrap_Struct(rb_cObject, 0, 0, response));
|
|
658
|
+
rb_iv_set(reader, "@opened", Qtrue);
|
|
659
|
+
rb_iv_set(reader, "@field_count", INT2NUM(field_count));
|
|
660
|
+
|
|
661
|
+
field_names = rb_ary_new();
|
|
662
|
+
field_types = rb_iv_get(self, "@field_types");
|
|
663
|
+
|
|
664
|
+
if ( field_types == Qnil || 0 == RARRAY(field_types)->len ) {
|
|
665
|
+
field_types = rb_ary_new();
|
|
666
|
+
guess_default_field_types = 1;
|
|
667
|
+
} else if (RARRAY(field_types)->len != field_count) {
|
|
668
|
+
// Whoops... wrong number of types passed to set_types. Close the reader and raise
|
|
669
|
+
// and error
|
|
670
|
+
rb_funcall(reader, rb_intern("close"), 0);
|
|
671
|
+
flush_pool(connection);
|
|
672
|
+
rb_raise(eMysqlError, "Field-count mismatch. Expected %d fields, but the query yielded %d", RARRAY(field_types)->len, field_count);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
for(i = 0; i < field_count; i++) {
|
|
676
|
+
field = mysql_fetch_field_direct(response, i);
|
|
677
|
+
rb_ary_push(field_names, RUBY_STRING(field->name));
|
|
678
|
+
|
|
679
|
+
if (1 == guess_default_field_types) {
|
|
680
|
+
VALUE field_ruby_type_name = RUBY_STRING(ruby_type_from_mysql_type(field));
|
|
681
|
+
rb_ary_push(field_types, field_ruby_type_name);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
rb_iv_set(reader, "@fields", field_names);
|
|
686
|
+
rb_iv_set(reader, "@field_types", field_types);
|
|
687
|
+
|
|
688
|
+
if (rb_block_given_p()) {
|
|
689
|
+
rb_yield(reader);
|
|
690
|
+
rb_funcall(reader, rb_intern("close"), 0);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
return reader;
|
|
662
694
|
}
|
|
663
695
|
|
|
664
696
|
// This should be called to ensure that the internal result reader is freed
|
|
665
697
|
static VALUE cReader_close(VALUE self) {
|
|
666
|
-
|
|
667
|
-
|
|
698
|
+
// Get the reader from the instance variable, maybe refactor this?
|
|
699
|
+
VALUE reader_container = rb_iv_get(self, "@reader");
|
|
668
700
|
|
|
669
|
-
|
|
701
|
+
MYSQL_RES *reader;
|
|
670
702
|
|
|
671
|
-
|
|
672
|
-
|
|
703
|
+
if (Qnil == reader_container)
|
|
704
|
+
return Qfalse;
|
|
673
705
|
|
|
674
|
-
|
|
706
|
+
reader = DATA_PTR(reader_container);
|
|
675
707
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
708
|
+
// The Meat
|
|
709
|
+
if (NULL == reader)
|
|
710
|
+
return Qfalse;
|
|
679
711
|
|
|
680
|
-
|
|
681
|
-
|
|
712
|
+
mysql_free_result(reader);
|
|
713
|
+
rb_iv_set(self, "@reader", Qnil);
|
|
682
714
|
|
|
683
|
-
|
|
715
|
+
return Qtrue;
|
|
684
716
|
}
|
|
685
717
|
|
|
686
718
|
// Retrieve a single row
|
|
687
719
|
static VALUE cReader_next(VALUE self) {
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
720
|
+
// Get the reader from the instance variable, maybe refactor this?
|
|
721
|
+
VALUE reader_container = rb_iv_get(self, "@reader");
|
|
722
|
+
VALUE ruby_field_type_strings, row;
|
|
691
723
|
|
|
692
|
-
|
|
693
|
-
|
|
724
|
+
MYSQL_RES *reader;
|
|
725
|
+
MYSQL_ROW result;
|
|
694
726
|
|
|
695
|
-
|
|
696
|
-
|
|
727
|
+
int i;
|
|
728
|
+
char *field_type;
|
|
697
729
|
|
|
698
|
-
|
|
699
|
-
|
|
730
|
+
if (Qnil == reader_container)
|
|
731
|
+
return Qfalse;
|
|
700
732
|
|
|
701
|
-
|
|
733
|
+
reader = DATA_PTR(reader_container);
|
|
702
734
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
735
|
+
// The Meat
|
|
736
|
+
ruby_field_type_strings = rb_iv_get(self, "@field_types");
|
|
737
|
+
row = rb_ary_new();
|
|
738
|
+
result = (MYSQL_ROW)mysql_fetch_row(reader);
|
|
707
739
|
|
|
708
|
-
|
|
740
|
+
rb_iv_set(self, "@state", result ? Qtrue : Qfalse);
|
|
709
741
|
|
|
710
|
-
|
|
711
|
-
|
|
742
|
+
if (!result)
|
|
743
|
+
return Qnil;
|
|
712
744
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
745
|
+
for (i = 0; i < reader->field_count; i++) {
|
|
746
|
+
// The field_type data could be cached in a c-array
|
|
747
|
+
field_type = RSTRING(rb_ary_entry(ruby_field_type_strings, i))->ptr;
|
|
748
|
+
rb_ary_push(row, typecast(result[i], field_type));
|
|
749
|
+
}
|
|
718
750
|
|
|
719
|
-
|
|
751
|
+
rb_iv_set(self, "@values", row);
|
|
720
752
|
|
|
721
|
-
|
|
753
|
+
return Qtrue;
|
|
722
754
|
}
|
|
723
755
|
|
|
724
756
|
static VALUE cReader_values(VALUE self) {
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
757
|
+
VALUE state = rb_iv_get(self, "@state");
|
|
758
|
+
if ( state == Qnil || state == Qfalse ) {
|
|
759
|
+
rb_raise(eMysqlError, "Reader is not initialized");
|
|
760
|
+
}
|
|
761
|
+
else {
|
|
762
|
+
return rb_iv_get(self, "@values");
|
|
763
|
+
}
|
|
732
764
|
}
|
|
733
765
|
|
|
734
766
|
static VALUE cReader_fields(VALUE self) {
|
|
735
|
-
|
|
767
|
+
return rb_iv_get(self, "@fields");
|
|
736
768
|
}
|
|
737
769
|
|
|
738
770
|
void Init_do_mysql_ext() {
|
|
739
|
-
|
|
740
|
-
|
|
771
|
+
rb_require("rubygems");
|
|
772
|
+
rb_require("bigdecimal");
|
|
741
773
|
rb_require("date");
|
|
742
774
|
rb_require("cgi");
|
|
743
775
|
|
|
744
776
|
rb_funcall(rb_mKernel, rb_intern("require"), 1, RUBY_STRING("data_objects"));
|
|
745
777
|
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
778
|
+
ID_TO_I = rb_intern("to_i");
|
|
779
|
+
ID_TO_F = rb_intern("to_f");
|
|
780
|
+
ID_TO_S = rb_intern("to_s");
|
|
781
|
+
ID_PARSE = rb_intern("parse");
|
|
782
|
+
ID_TO_TIME = rb_intern("to_time");
|
|
783
|
+
ID_NEW = rb_intern("new");
|
|
784
|
+
ID_NEW_RATIONAL = rb_intern("new!");
|
|
785
|
+
ID_NEW_DATE = RUBY_VERSION_CODE < 186 ? rb_intern("new0") : rb_intern("new!");
|
|
786
|
+
ID_CONST_GET = rb_intern("const_get");
|
|
787
|
+
ID_UTC = rb_intern("utc");
|
|
788
|
+
ID_ESCAPE_SQL = rb_intern("escape_sql");
|
|
789
|
+
ID_STRFTIME = rb_intern("strftime");
|
|
790
|
+
ID_LOGGER = rb_intern("logger");
|
|
791
|
+
ID_DEBUG = rb_intern("debug");
|
|
792
|
+
ID_LEVEL = rb_intern("level");
|
|
793
|
+
|
|
794
|
+
// Store references to a few helpful clases that aren't in Ruby Core
|
|
795
|
+
rb_cDate = RUBY_CLASS("Date");
|
|
796
|
+
rb_cDateTime = RUBY_CLASS("DateTime");
|
|
797
|
+
rb_cRational = RUBY_CLASS("Rational");
|
|
798
|
+
rb_cBigDecimal = RUBY_CLASS("BigDecimal");
|
|
799
|
+
rb_cCGI = RUBY_CLASS("CGI");
|
|
800
|
+
|
|
801
|
+
// Get references to the DataObjects module and its classes
|
|
802
|
+
mDO = CONST_GET(rb_mKernel, "DataObjects");
|
|
803
|
+
cDO_Quoting = CONST_GET(mDO, "Quoting");
|
|
804
|
+
cDO_Connection = CONST_GET(mDO, "Connection");
|
|
805
|
+
cDO_Command = CONST_GET(mDO, "Command");
|
|
806
|
+
cDO_Result = CONST_GET(mDO, "Result");
|
|
807
|
+
cDO_Reader = CONST_GET(mDO, "Reader");
|
|
808
|
+
|
|
809
|
+
// Top Level Module that all the classes live under
|
|
810
|
+
mDOMysql = rb_define_module_under(mDO, "Mysql");
|
|
811
|
+
|
|
812
|
+
eMysqlError = rb_define_class("MysqlError", rb_eStandardError);
|
|
813
|
+
|
|
814
|
+
cConnection = DRIVER_CLASS("Connection", cDO_Connection);
|
|
815
|
+
rb_define_method(cConnection, "initialize", cConnection_initialize, 1);
|
|
816
|
+
rb_define_method(cConnection, "using_socket?", cConnection_is_using_socket, 0);
|
|
817
|
+
rb_define_method(cConnection, "character_set", cConnection_character_set , 0);
|
|
818
|
+
rb_define_method(cConnection, "dispose", cConnection_dispose, 0);
|
|
819
|
+
|
|
820
|
+
cCommand = DRIVER_CLASS("Command", cDO_Command);
|
|
821
|
+
rb_include_module(cCommand, cDO_Quoting);
|
|
822
|
+
rb_define_method(cCommand, "set_types", cCommand_set_types, 1);
|
|
823
|
+
rb_define_method(cCommand, "execute_non_query", cCommand_execute_non_query, -1);
|
|
824
|
+
rb_define_method(cCommand, "execute_reader", cCommand_execute_reader, -1);
|
|
825
|
+
rb_define_method(cCommand, "quote_string", cCommand_quote_string, 1);
|
|
826
|
+
rb_define_method(cCommand, "quote_date", cCommand_quote_date, 1);
|
|
827
|
+
rb_define_method(cCommand, "quote_time", cCommand_quote_time, 1);
|
|
828
|
+
rb_define_method(cCommand, "quote_datetime", cCommand_quote_date_time, 1);
|
|
829
|
+
|
|
830
|
+
// Non-Query result
|
|
831
|
+
cResult = DRIVER_CLASS("Result", cDO_Result);
|
|
832
|
+
|
|
833
|
+
// Query result
|
|
834
|
+
cReader = DRIVER_CLASS("Reader", cDO_Reader);
|
|
835
|
+
rb_define_method(cReader, "close", cReader_close, 0);
|
|
836
|
+
rb_define_method(cReader, "next!", cReader_next, 0);
|
|
837
|
+
rb_define_method(cReader, "values", cReader_values, 0);
|
|
838
|
+
rb_define_method(cReader, "fields", cReader_fields, 0);
|
|
807
839
|
}
|