do_mysql 0.10.0-java → 0.10.1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/ChangeLog.markdown +27 -0
  2. data/LICENSE +1 -1
  3. data/README.markdown +104 -3
  4. data/Rakefile +56 -9
  5. data/ext/do_mysql/compat.h +55 -0
  6. data/ext/do_mysql/do_mysql.c +1065 -0
  7. data/ext/do_mysql/error.h +527 -0
  8. data/ext/do_mysql/extconf.rb +74 -0
  9. data/lib/do_mysql.rb +27 -11
  10. data/lib/do_mysql/do_mysql.jar +0 -0
  11. data/lib/do_mysql/version.rb +1 -1
  12. data/spec/command_spec.rb +2 -2
  13. data/spec/connection_spec.rb +16 -14
  14. data/spec/encoding_spec.rb +2 -1
  15. data/spec/reader_spec.rb +1 -1
  16. data/spec/result_spec.rb +3 -3
  17. data/spec/spec_helper.rb +21 -31
  18. data/spec/typecast/array_spec.rb +1 -1
  19. data/spec/typecast/bigdecimal_spec.rb +2 -2
  20. data/spec/typecast/boolean_spec.rb +2 -2
  21. data/spec/typecast/byte_array_spec.rb +1 -1
  22. data/spec/typecast/class_spec.rb +1 -1
  23. data/spec/typecast/date_spec.rb +2 -2
  24. data/spec/typecast/datetime_spec.rb +2 -2
  25. data/spec/typecast/float_spec.rb +2 -2
  26. data/spec/typecast/integer_spec.rb +1 -1
  27. data/spec/typecast/nil_spec.rb +3 -3
  28. data/spec/typecast/other_spec.rb +8 -0
  29. data/spec/typecast/range_spec.rb +1 -1
  30. data/spec/typecast/string_spec.rb +1 -1
  31. data/spec/typecast/time_spec.rb +1 -1
  32. data/tasks/compile.rake +65 -0
  33. data/tasks/release.rake +12 -71
  34. data/tasks/retrieve.rake +1 -1
  35. data/tasks/spec.rake +19 -15
  36. metadata +119 -107
  37. data/HISTORY.markdown +0 -17
  38. data/Manifest.txt +0 -32
  39. data/lib/do_mysql_ext.jar +0 -0
  40. data/spec/lib/rspec_immediate_feedback_formatter.rb +0 -3
  41. data/tasks/gem.rake +0 -8
  42. data/tasks/install.rake +0 -15
  43. data/tasks/native.rake +0 -31
@@ -0,0 +1,27 @@
1
+ ## 0.10.1 (unreleased, in git)
2
+
3
+ * Support for Ruby 1.8 and 1.9 on Windows.
4
+ * Switch to Jeweler for Gem building tasks (this change may be temporary).
5
+ * Switch to using Bacon for running specs: This should make specs friendlier to
6
+ new Ruby implementations that are not yet 100% MRI-compatible, and in turn,
7
+ prepared the road for our own IronRuby and MacRuby support.
8
+ * Switch to the newly added rake-compiler `JavaExtensionTask` for compiling
9
+ JRuby extensions, instead of our (broken) home-grown solution.
10
+
11
+ ## 0.10.0 2009-09-15
12
+ * Improvements
13
+ * JRuby Support (using *do_jdbc*)
14
+
15
+ ## 0.9.12 2009-05-17
16
+ * Improvements
17
+ * Windows support
18
+
19
+ ## 0.9.11 2009-01-19
20
+ * Improvements
21
+ * Ruby 1.9 support
22
+ * Fixes
23
+ * Reconnecting now works properly
24
+
25
+ ## 0.9.9 2008-11-27
26
+ * Improvements
27
+ * Added initial support for Ruby 1.9 [John Harrison]
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2007, 2008, 2009 Yehuda Katz, Dirkjan Bussink
1
+ Copyright (c) 2007 - 2010 Yehuda Katz, Dirkjan Bussink
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.markdown CHANGED
@@ -1,4 +1,105 @@
1
- do_mysql
2
- ========
1
+ # do_mysql
3
2
 
4
- A MySQL driver for DataObjects
3
+ * <http://dataobjects.info>
4
+
5
+ ## Description
6
+
7
+ A MySQL driver for DataObjects.
8
+
9
+ ## Features/Problems
10
+
11
+ This driver implements the DataObjects API for the MySQL relational database.
12
+
13
+ ## Synopsis
14
+
15
+ An example of usage:
16
+
17
+ # default user (root, no password); default port (3306)
18
+ DataObjects::Connection.new("mysql://host/database")
19
+ # specified user, specified port
20
+ DataObjects::Connection.new("mysql://user:pass@host:8888/database")
21
+
22
+ @connection = DataObjects::Connection.new("mysql://localhost/employees")
23
+ @reader = @connection.create_command('SELECT * FROM users').execute_reader
24
+ @reader.next!
25
+
26
+ In the future, the `Connection` constructor will be able to be passed either a
27
+ DataObjects-style URL or JDBC style URL, when using do\_mysql on JRuby. However,
28
+ this feature is not currently working reliably and is a known issue.
29
+
30
+ ## Requirements
31
+
32
+ This driver is provided for the following platforms:
33
+ * Ruby MRI (1.8.6/7), 1.9: tested on Linux, Mac OS X and Windows platforms.
34
+ * JRuby 1.3.1 + (1.4+ recommended).
35
+ * Rubinius (experimental).
36
+
37
+ Additionally you should have the following prerequisites:
38
+ * `data_objects` gem
39
+ * `do_jdbc` gem (shared library), if running on JRuby.
40
+
41
+ ## Install
42
+
43
+ To install the gem:
44
+
45
+ gem install do_mysql
46
+
47
+ If installing the MRI/1.9/Rubinius extension on OS X and you install a version
48
+ of MySQL that was built for only a single architecture, you will need to set
49
+ `ARCHFLAGS` appropriately:
50
+
51
+ sudo env ARCHFLAGS="-arch i386" gem install do_mysql
52
+
53
+ To compile and install from source:
54
+
55
+ * Install rake-compiler: `gem install rake-compiler`.
56
+
57
+ * For MRI/Rubinius extensions:
58
+ * Install the `gcc` compiler. On OS X, you should install XCode tools. On
59
+ Ubuntu, run `apt-get install build-essential`.
60
+ * Install Ruby and MySQL.
61
+ * Install the Ruby and MySQL development headers.
62
+ * On Debian-Linux distributions, you can install the following packages
63
+ with `apt`: `ruby-dev` `libmysqlclient15-dev`.
64
+ * If you want to cross-compile for Windows:
65
+ * Install MinGW:
66
+ * On Debian-Linux distributions, you can install the following package
67
+ with `apt`: `mingw32`.
68
+ * On OS X, this can install the following package with MacPorts: `i386-mingw32-gcc`.
69
+ * Run `rake-compiler cross-ruby`.
70
+ * Run `rake-compiler update-config`.
71
+
72
+ * Then, install this driver with `(jruby -S) rake install`.
73
+
74
+ For more information, see the MySQL driver wiki page:
75
+ <http://wiki.github.com/datamapper/do/mysql>.
76
+
77
+ ## Developers
78
+
79
+ Follow the above installation instructions. Additionally, you'll need:
80
+ * `bacon` gem for running specs.
81
+ * `YARD` gem for generating documentation.
82
+
83
+ See the DataObjects wiki for more comprehensive information on installing and
84
+ contributing to the JRuby-variant of this driver:
85
+ <http://wiki.github.com/datamapper/do/jruby>.
86
+
87
+ To run specs:
88
+
89
+ rake spec
90
+
91
+ To run specs without compiling extensions first:
92
+
93
+ rake spec_no_compile
94
+
95
+ To run individual specs:
96
+
97
+ rake spec TEST=spec/connection_spec.rb
98
+
99
+ (Note that the `rake` task uses a `TEST` parameter, not `SPEC`. This is because
100
+ the `Rake::TestTask` is used for executing the Bacon specs).
101
+
102
+ ## License
103
+
104
+ This code is licensed under an **MIT (X11) License**. Please see the
105
+ accompanying `LICENSE` file.
data/Rakefile CHANGED
@@ -1,16 +1,63 @@
1
+ require 'pathname'
1
2
  require 'rubygems'
2
3
  require 'rake'
3
4
  require 'rake/clean'
4
5
 
5
- require 'pathname'
6
- require 'lib/do_mysql/version'
6
+ ROOT = Pathname(__FILE__).dirname.expand_path
7
+
8
+ require ROOT + 'lib/do_mysql/version'
9
+
10
+ JRUBY = RUBY_PLATFORM =~ /java/
11
+ IRONRUBY = defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ironruby'
12
+ WINDOWS = Gem.win_platform? || (JRUBY && ENV_JAVA['os.name'] =~ /windows/i)
13
+ SUDO = WINDOWS ? '' : ('sudo' unless ENV['SUDOLESS'])
14
+ BINARY_VERSION = '5.0.89'
15
+
16
+ CLEAN.include(%w[ {tmp,pkg}/ **/*.{o,so,bundle,jar,log,a,gem,dSYM,obj,pdb,exp,DS_Store,rbc,db} ext/do_mysql/Makefile ext-java/target ])
17
+
18
+ begin
19
+ gem 'jeweler', '~> 1.4'
20
+ require 'jeweler'
21
+
22
+ Jeweler::Tasks.new do |gem|
23
+ gem.name = 'do_mysql'
24
+ gem.version = DataObjects::Mysql::VERSION
25
+ gem.summary = 'DataObjects MySQL Driver'
26
+ gem.description = 'Implements the DataObjects API for MySQL'
27
+ gem.platform = Gem::Platform::RUBY
28
+ gem.files = Dir['lib/**/*.rb', 'spec/**/*.rb', 'tasks/**/*.rake',
29
+ 'ext/**/*.{rb,c,h}', 'LICENSE', 'Rakefile',
30
+ '*.{markdown,rdoc,txt,yml}']
31
+ gem.extra_rdoc_files = FileList['README*', 'ChangeLog*', 'LICENSE']
32
+ gem.test_files = FileList['spec/**/*.rb']
33
+
34
+ # rake-compiler should generate gemspecs for other platforms (e.g. 'java')
35
+ # and modify dependencies and extensions appropriately
36
+ gem.extensions << 'ext/do_mysql/extconf.rb'
37
+
38
+ gem.add_dependency 'data_objects', DataObjects::Mysql::VERSION
39
+
40
+ gem.add_development_dependency 'bacon', '~>1.1'
41
+ gem.add_development_dependency 'rake-compiler', '~>0.7'
42
+
43
+ gem.has_rdoc = false
44
+ gem.rubyforge_project = 'dorb'
45
+ gem.authors = [ 'Dirkjan Bussink' ]
46
+ gem.email = 'd.bussink@gmail.com'
47
+ end
7
48
 
8
- ROOT = Pathname(__FILE__).dirname.expand_path
9
- JRUBY = RUBY_PLATFORM =~ /java/
10
- WINDOWS = Gem.win_platform?
11
- SUDO = WINDOWS ? '' : ('sudo' unless ENV['SUDOLESS'])
12
- BINARY_VERSION = '5.0.85'
49
+ if JRUBY
50
+ Rake::Task['build'].clear_actions if Rake::Task.task_defined?('build')
51
+ Rake::Task['install'].clear_actions if Rake::Task.task_defined?('install')
52
+ task :build => [ :java, :gem ]
53
+ task :install do
54
+ sh "#{Config::CONFIG['RUBY_INSTALL_NAME']} -S gem install pkg/do_mysql-#{DataObjects::Mysql::VERSION}-java.gem"
55
+ end
56
+ end
13
57
 
14
- Dir['tasks/*.rake'].sort.each { |f| import f }
58
+ Jeweler::GemcutterTasks.new
15
59
 
16
- CLEAN.include(%w[ {tmp,pkg}/ **/*.{o,so,bundle,jar,log,a,gem,dSYM,obj,pdb,exp,DS_Store,rbc,db} ext/do_mysql_ext/Makefile ext-java/target ])
60
+ FileList['tasks/**/*.rake'].each { |task| import task }
61
+ rescue LoadError
62
+ puts 'Jeweler (or a dependency) not available. Install it with: gem install jeweler'
63
+ end
@@ -0,0 +1,55 @@
1
+ #ifndef RUBY_COMPAT_H
2
+ #define RUBY_COMPAT_H
3
+
4
+ /*
5
+ * Rules for better ruby C extensions:
6
+ *
7
+ * Never use the R<TYPE> macros directly, always use R<TYPE>_<FIELD>
8
+ *
9
+ * Never compare with RBASIC(obj)->klass, always use
10
+ * rb_obj_is_instance_of()
11
+ *
12
+ * Never use RHASH(obj)->tbl or RHASH_TBL().
13
+ *
14
+ */
15
+
16
+
17
+ // Array
18
+ #ifndef RARRAY_PTR
19
+ #define RARRAY_PTR(obj) RARRAY(obj)->ptr
20
+ #endif
21
+
22
+ #ifndef RARRAY_LEN
23
+ #define RARRAY_LEN(obj) RARRAY(obj)->len
24
+ #endif
25
+
26
+ // String
27
+ #ifndef RSTRING_PTR
28
+ #define RSTRING_PTR(obj) RSTRING(obj)->ptr
29
+ #endif
30
+
31
+ #ifndef RSTRING_LEN
32
+ #define RSTRING_LEN(obj) RSTRING(obj)->len
33
+ #endif
34
+
35
+ #ifndef rb_str_ptr
36
+ #define rb_str_ptr(str) RSTRING_PTR(str)
37
+ #endif
38
+
39
+ #ifndef rb_str_ptr_readonly
40
+ #define rb_str_ptr_readonly(str) RSTRING_PTR(str)
41
+ #endif
42
+
43
+ #ifndef rb_str_flush
44
+ #define rb_str_flush(str)
45
+ #endif
46
+
47
+ #ifndef rb_str_update
48
+ #define rb_str_update(str)
49
+ #endif
50
+
51
+ #ifndef rb_str_len
52
+ #define rb_str_len(str) RSTRING_LEN(str)
53
+ #endif
54
+
55
+ #endif
@@ -0,0 +1,1065 @@
1
+ #include <ruby.h>
2
+ #include <string.h>
3
+ #include <math.h>
4
+ #include <ctype.h>
5
+ #include <time.h>
6
+
7
+ #include <mysql.h>
8
+ #include <errmsg.h>
9
+ #include <mysqld_error.h>
10
+
11
+ #include "compat.h"
12
+ #include "error.h"
13
+ #define RUBY_CLASS(name) rb_const_get(rb_cObject, rb_intern(name))
14
+ #define DRIVER_CLASS(klass, parent) (rb_define_class_under(mDOMysql, klass, parent))
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, query) if (0 != mysql_result_value) { raise_error(self, db, query); }
17
+
18
+ #ifdef _WIN32
19
+ #define cCommand_execute cCommand_execute_sync
20
+ #define do_int64 signed __int64
21
+ #else
22
+ #define cCommand_execute cCommand_execute_async
23
+ #define do_int64 signed long long int
24
+ #endif
25
+
26
+ #ifdef HAVE_RUBY_ENCODING_H
27
+ #include <ruby/encoding.h>
28
+
29
+ #define DO_STR_NEW2(str, encoding) \
30
+ ({ \
31
+ VALUE _string = rb_str_new2((const char *)str); \
32
+ if(encoding != -1) { \
33
+ rb_enc_associate_index(_string, encoding); \
34
+ } \
35
+ _string; \
36
+ })
37
+
38
+ #define DO_STR_NEW(str, len, encoding) \
39
+ ({ \
40
+ VALUE _string = rb_str_new((const char *)str, (long)len); \
41
+ if(encoding != -1) { \
42
+ rb_enc_associate_index(_string, encoding); \
43
+ } \
44
+ _string; \
45
+ })
46
+
47
+ #else
48
+
49
+ #define DO_STR_NEW2(str, encoding) \
50
+ rb_str_new2((const char *)str)
51
+
52
+ #define DO_STR_NEW(str, len, encoding) \
53
+ rb_str_new((const char *)str, (long)len)
54
+ #endif
55
+
56
+
57
+ // To store rb_intern values
58
+ static ID ID_TO_I;
59
+ static ID ID_TO_F;
60
+ static ID ID_TO_S;
61
+ static ID ID_TO_TIME;
62
+ static ID ID_NEW;
63
+ static ID ID_NEW_DATE;
64
+ static ID ID_CONST_GET;
65
+ static ID ID_RATIONAL;
66
+ static ID ID_UTC;
67
+ static ID ID_ESCAPE_SQL;
68
+ static ID ID_STRFTIME;
69
+ static ID ID_LOGGER;
70
+ static ID ID_DEBUG;
71
+ static ID ID_LEVEL;
72
+
73
+ // Reference to Extlib module
74
+ static VALUE mExtlib;
75
+
76
+ // References to DataObjects base classes
77
+ static VALUE mDO;
78
+ static VALUE mEncoding;
79
+ static VALUE cDO_Quoting;
80
+ static VALUE cDO_Connection;
81
+ static VALUE cDO_Command;
82
+ static VALUE cDO_Result;
83
+ static VALUE cDO_Reader;
84
+
85
+ // References to Ruby classes that we'll need
86
+ static VALUE rb_cDate;
87
+ static VALUE rb_cDateTime;
88
+ static VALUE rb_cBigDecimal;
89
+ static VALUE rb_cByteArray;
90
+
91
+ // Classes that we'll build in Init
92
+ static VALUE mDOMysql;
93
+ static VALUE cConnection;
94
+ static VALUE cCommand;
95
+ static VALUE cResult;
96
+ static VALUE cReader;
97
+ static VALUE eArgumentError;
98
+ static VALUE eConnectionError;
99
+ static VALUE eDataError;
100
+
101
+ // Figures out what we should cast a given mysql field type to
102
+ static VALUE infer_ruby_type(MYSQL_FIELD *field) {
103
+ switch(field->type) {
104
+ case MYSQL_TYPE_NULL:
105
+ return Qnil;
106
+ case MYSQL_TYPE_TINY:
107
+ return rb_cTrueClass;
108
+ case MYSQL_TYPE_BIT:
109
+ case MYSQL_TYPE_SHORT:
110
+ case MYSQL_TYPE_LONG:
111
+ case MYSQL_TYPE_INT24:
112
+ case MYSQL_TYPE_LONGLONG:
113
+ case MYSQL_TYPE_YEAR:
114
+ return rb_cInteger;
115
+ case MYSQL_TYPE_NEWDECIMAL:
116
+ case MYSQL_TYPE_DECIMAL:
117
+ return rb_cBigDecimal;
118
+ case MYSQL_TYPE_FLOAT:
119
+ case MYSQL_TYPE_DOUBLE:
120
+ return rb_cFloat;
121
+ case MYSQL_TYPE_TIMESTAMP:
122
+ case MYSQL_TYPE_DATETIME:
123
+ return rb_cDateTime;
124
+ case MYSQL_TYPE_DATE:
125
+ case MYSQL_TYPE_NEWDATE:
126
+ return rb_cDate;
127
+ case MYSQL_TYPE_TINY_BLOB:
128
+ case MYSQL_TYPE_MEDIUM_BLOB:
129
+ case MYSQL_TYPE_LONG_BLOB:
130
+ case MYSQL_TYPE_BLOB:
131
+ return rb_cByteArray;
132
+ default:
133
+ return rb_cString;
134
+ }
135
+ }
136
+
137
+ // Find the greatest common denominator and reduce the provided numerator and denominator.
138
+ // This replaces calles to Rational.reduce! which does the same thing, but really slowly.
139
+ static void reduce( do_int64 *numerator, do_int64 *denominator ) {
140
+ do_int64 a, b, c;
141
+ a = *numerator;
142
+ b = *denominator;
143
+ while ( a != 0 ) {
144
+ c = a; a = b % a; b = c;
145
+ }
146
+ *numerator = *numerator / b;
147
+ *denominator = *denominator / b;
148
+ }
149
+
150
+ // Generate the date integer which Date.civil_to_jd returns
151
+ static int jd_from_date(int year, int month, int day) {
152
+ int a, b;
153
+ if ( month <= 2 ) {
154
+ year -= 1;
155
+ month += 12;
156
+ }
157
+ a = year / 100;
158
+ b = 2 - a + (a / 4);
159
+ return (int) (floor(365.25 * (year + 4716)) + floor(30.6001 * (month + 1)) + day + b - 1524);
160
+ }
161
+
162
+ static VALUE seconds_to_offset(long seconds_offset) {
163
+ do_int64 num = seconds_offset, den = 86400;
164
+ reduce(&num, &den);
165
+ return rb_funcall(rb_mKernel, ID_RATIONAL, 2, rb_ll2inum(num), rb_ll2inum(den));
166
+ }
167
+
168
+ static VALUE timezone_to_offset(int hour_offset, int minute_offset) {
169
+ do_int64 seconds = 0;
170
+
171
+ seconds += hour_offset * 3600;
172
+ seconds += minute_offset * 60;
173
+
174
+ return seconds_to_offset(seconds);
175
+ }
176
+
177
+ static VALUE parse_date(const char *date) {
178
+ int year, month, day;
179
+ int jd, ajd;
180
+ VALUE rational;
181
+
182
+ sscanf(date, "%4d-%2d-%2d", &year, &month, &day);
183
+
184
+ jd = jd_from_date(year, month, day);
185
+
186
+ // Math from Date.jd_to_ajd
187
+ ajd = jd * 2 - 1;
188
+ rational = rb_funcall(rb_mKernel, ID_RATIONAL, 2, INT2NUM(ajd), INT2NUM(2));
189
+ return rb_funcall(rb_cDate, ID_NEW_DATE, 3, rational, INT2NUM(0), INT2NUM(2299161));
190
+ }
191
+
192
+ static VALUE parse_time(const char *date) {
193
+
194
+ int year, month, day, hour, min, sec, usec, tokens;
195
+ char subsec[7];
196
+
197
+ if (0 != strchr(date, '.')) {
198
+ // right padding usec with 0. e.g. '012' will become 12000 microsecond, since Time#local use microsecond
199
+ sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d.%s", &year, &month, &day, &hour, &min, &sec, subsec);
200
+ sscanf(subsec, "%d", &usec);
201
+ } else {
202
+ tokens = sscanf(date, "%4d-%2d-%2d %2d:%2d:%2d", &year, &month, &day, &hour, &min, &sec);
203
+ if (tokens == 3) {
204
+ hour = 0;
205
+ min = 0;
206
+ sec = 0;
207
+ }
208
+ usec = 0;
209
+ }
210
+
211
+ if ( year + month + day + hour + min + sec + usec == 0 ) { // Mysql TIMESTAMPS can default to 0
212
+ return Qnil;
213
+ }
214
+
215
+ return rb_funcall(rb_cTime, rb_intern("local"), 7, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec), INT2NUM(usec));
216
+ }
217
+
218
+ static VALUE parse_date_time(const char *date) {
219
+ VALUE ajd, offset;
220
+
221
+ int year, month, day, hour, min, sec, usec, hour_offset, minute_offset;
222
+ int jd;
223
+ do_int64 num, den;
224
+
225
+
226
+ time_t gmt_offset;
227
+ int is_dst;
228
+
229
+ time_t rawtime;
230
+ struct tm * timeinfo;
231
+
232
+ int tokens_read, max_tokens;
233
+
234
+ if ( strcmp(date, "") == 0 ) {
235
+ return Qnil;
236
+ }
237
+
238
+ if (0 != strchr(date, '.')) {
239
+ // This is a datetime with sub-second precision
240
+ tokens_read = sscanf(date, "%4d-%2d-%2d%*c%2d:%2d:%2d.%d%3d:%2d", &year, &month, &day, &hour, &min, &sec, &usec, &hour_offset, &minute_offset);
241
+ max_tokens = 9;
242
+ } else {
243
+ // This is a datetime second precision
244
+ tokens_read = sscanf(date, "%4d-%2d-%2d%*c%2d:%2d:%2d%3d:%2d", &year, &month, &day, &hour, &min, &sec, &hour_offset, &minute_offset);
245
+ max_tokens = 8;
246
+ }
247
+
248
+ if (max_tokens == tokens_read) {
249
+ // We read the Date, Time, and Timezone info
250
+ minute_offset *= hour_offset < 0 ? -1 : 1;
251
+ } else if ((max_tokens - 1) == tokens_read) {
252
+ // We read the Date and Time, but no Minute Offset
253
+ minute_offset = 0;
254
+ } else if (tokens_read == 3 || tokens_read >= (max_tokens - 3)) {
255
+ if (tokens_read == 3) {
256
+ hour = 0;
257
+ min = 0;
258
+ hour_offset = 0;
259
+ minute_offset = 0;
260
+ sec = 0;
261
+ }
262
+ // We read the Date and Time, default to the current locale's offset
263
+
264
+ // Get localtime
265
+ time(&rawtime);
266
+ timeinfo = localtime(&rawtime);
267
+
268
+ is_dst = timeinfo->tm_isdst * 3600;
269
+
270
+ // Reset to GM Time
271
+ timeinfo = gmtime(&rawtime);
272
+
273
+ gmt_offset = mktime(timeinfo) - rawtime;
274
+
275
+ if ( is_dst > 0 )
276
+ gmt_offset -= is_dst;
277
+
278
+ hour_offset = -((int)gmt_offset / 3600);
279
+ minute_offset = -((int)gmt_offset % 3600 / 60);
280
+
281
+ } else {
282
+ // Something went terribly wrong
283
+ rb_raise(eDataError, "Couldn't parse date: %s", date);
284
+ }
285
+
286
+ jd = jd_from_date(year, month, day);
287
+
288
+ // Generate ajd with fractional days for the time
289
+ // Extracted from Date#jd_to_ajd, Date#day_fraction_to_time, and Rational#+ and #-
290
+ num = (hour * 1440) + (min * 24);
291
+
292
+ // Modify the numerator so when we apply the timezone everything works out
293
+ num -= (hour_offset * 1440) + (minute_offset * 24);
294
+
295
+ den = (24 * 1440);
296
+ reduce(&num, &den);
297
+
298
+ num = (num * 86400) + (sec * den);
299
+ den = den * 86400;
300
+ reduce(&num, &den);
301
+
302
+ num = (jd * den) + num;
303
+
304
+ num = num * 2;
305
+ num = num - den;
306
+ den = den * 2;
307
+
308
+ reduce(&num, &den);
309
+
310
+ ajd = rb_funcall(rb_mKernel, ID_RATIONAL, 2, rb_ull2inum(num), rb_ull2inum(den));
311
+ offset = timezone_to_offset(hour_offset, minute_offset);
312
+
313
+ return rb_funcall(rb_cDateTime, ID_NEW_DATE, 3, ajd, offset, INT2NUM(2299161));
314
+ }
315
+
316
+ // Convert C-string to a Ruby instance of Ruby type "type"
317
+ static VALUE typecast(const char *value, long length, const VALUE type, int encoding) {
318
+
319
+ if(NULL == value) {
320
+ return Qnil;
321
+ }
322
+
323
+ if (type == rb_cInteger) {
324
+ return rb_cstr2inum(value, 10);
325
+ } else if (type == rb_cString) {
326
+ return DO_STR_NEW(value, length, encoding);
327
+ } else if (type == rb_cFloat) {
328
+ return rb_float_new(rb_cstr_to_dbl(value, Qfalse));
329
+ } else if (type == rb_cBigDecimal) {
330
+ return rb_funcall(rb_cBigDecimal, ID_NEW, 1, rb_str_new(value, length));
331
+ } else if (type == rb_cDate) {
332
+ return parse_date(value);
333
+ } else if (type == rb_cDateTime) {
334
+ return parse_date_time(value);
335
+ } else if (type == rb_cTime) {
336
+ return parse_time(value);
337
+ } else if (type == rb_cTrueClass) {
338
+ return (0 == value || 0 == strcmp("0", value)) ? Qfalse : Qtrue;
339
+ } else if (type == rb_cByteArray) {
340
+ return rb_funcall(rb_cByteArray, ID_NEW, 1, rb_str_new(value, length));
341
+ } else if (type == rb_cClass) {
342
+ return rb_funcall(mDO, rb_intern("full_const_get"), 1, rb_str_new(value, length));
343
+ } else if (type == rb_cObject) {
344
+ return rb_marshal_load(rb_str_new(value, length));
345
+ } else if (type == rb_cNilClass) {
346
+ return Qnil;
347
+ } else {
348
+ return DO_STR_NEW(value, length, encoding);
349
+ }
350
+
351
+ }
352
+
353
+ static void data_objects_debug(VALUE string, struct timeval* start) {
354
+ struct timeval stop;
355
+ char *message;
356
+
357
+ const char *query = rb_str_ptr_readonly(string);
358
+ size_t length = rb_str_len(string);
359
+ char total_time[32];
360
+ do_int64 duration = 0;
361
+
362
+ VALUE logger = rb_funcall(mDOMysql, ID_LOGGER, 0);
363
+ int log_level = NUM2INT(rb_funcall(logger, ID_LEVEL, 0));
364
+
365
+ if (0 == log_level) {
366
+ gettimeofday(&stop, NULL);
367
+
368
+ duration = (stop.tv_sec - start->tv_sec) * 1000000 + stop.tv_usec - start->tv_usec;
369
+
370
+ snprintf(total_time, 32, "%.6f", duration / 1000000.0);
371
+ message = (char *)calloc(length + strlen(total_time) + 4, sizeof(char));
372
+ snprintf(message, length + strlen(total_time) + 4, "(%s) %s", total_time, query);
373
+ rb_funcall(logger, ID_DEBUG, 1, rb_str_new(message, length + strlen(total_time) + 3));
374
+ }
375
+ }
376
+
377
+ static void raise_error(VALUE self, MYSQL *db, VALUE query) {
378
+ VALUE exception;
379
+ const char *exception_type = "SQLError";
380
+ char *mysql_error_message = (char *)mysql_error(db);
381
+ int mysql_error_code = mysql_errno(db);
382
+
383
+ struct errcodes *errs;
384
+
385
+ for (errs = errors; errs->error_name; errs++) {
386
+ if(errs->error_no == mysql_error_code) {
387
+ exception_type = errs->exception;
388
+ break;
389
+ }
390
+ }
391
+
392
+ VALUE uri = rb_funcall(rb_iv_get(self, "@connection"), rb_intern("to_s"), 0);
393
+
394
+ exception = rb_funcall(CONST_GET(mDO, exception_type), ID_NEW, 5,
395
+ rb_str_new2(mysql_error_message),
396
+ INT2NUM(mysql_error_code),
397
+ rb_str_new2(mysql_sqlstate(db)),
398
+ query,
399
+ uri);
400
+ rb_exc_raise(exception);
401
+ }
402
+
403
+ static char * get_uri_option(VALUE query_hash, const char * key) {
404
+ VALUE query_value;
405
+ char * value = NULL;
406
+
407
+ if(!rb_obj_is_kind_of(query_hash, rb_cHash)) { return NULL; }
408
+
409
+ query_value = rb_hash_aref(query_hash, rb_str_new2(key));
410
+
411
+ if (Qnil != query_value) {
412
+ value = StringValuePtr(query_value);
413
+ }
414
+
415
+ return value;
416
+ }
417
+
418
+ static void assert_file_exists(char * file, const char * message) {
419
+ if (file == NULL) { return; }
420
+ if (rb_funcall(rb_cFile, rb_intern("exist?"), 1, rb_str_new2(file)) == Qfalse) {
421
+ rb_raise(eArgumentError, "%s", message);
422
+ }
423
+ }
424
+
425
+ static void full_connect(VALUE self, MYSQL *db);
426
+
427
+ #ifdef _WIN32
428
+ static MYSQL_RES* cCommand_execute_sync(VALUE self, MYSQL* db, VALUE query) {
429
+ int retval;
430
+ struct timeval start;
431
+ const char* str = rb_str_ptr_readonly(query);
432
+ int len = rb_str_len(query);
433
+
434
+ if(mysql_ping(db) && mysql_errno(db) == CR_SERVER_GONE_ERROR) {
435
+ // Ok, we do one more try here by doing a full connect
436
+ VALUE connection = rb_iv_get(self, "@connection");
437
+ full_connect(connection, db);
438
+ }
439
+ gettimeofday(&start, NULL);
440
+ retval = mysql_real_query(db, str, len);
441
+ data_objects_debug(query, &start);
442
+
443
+ CHECK_AND_RAISE(retval, query);
444
+
445
+ return mysql_store_result(db);
446
+ }
447
+ #else
448
+ static MYSQL_RES* cCommand_execute_async(VALUE self, MYSQL* db, VALUE query) {
449
+ int socket_fd;
450
+ int retval;
451
+ fd_set rset;
452
+ struct timeval start;
453
+ const char* str = rb_str_ptr_readonly(query);
454
+ size_t len = rb_str_len(query);
455
+
456
+ if((retval = mysql_ping(db)) && mysql_errno(db) == CR_SERVER_GONE_ERROR) {
457
+ VALUE connection = rb_iv_get(self, "@connection");
458
+ full_connect(connection, db);
459
+ }
460
+ retval = mysql_send_query(db, str, len);
461
+
462
+ CHECK_AND_RAISE(retval, query);
463
+ gettimeofday(&start, NULL);
464
+
465
+ socket_fd = db->net.fd;
466
+
467
+ data_objects_debug(query, &start);
468
+
469
+ for(;;) {
470
+ FD_ZERO(&rset);
471
+ FD_SET(socket_fd, &rset);
472
+
473
+ retval = rb_thread_select(socket_fd + 1, &rset, NULL, NULL, NULL);
474
+
475
+ if (retval < 0) {
476
+ rb_sys_fail(0);
477
+ }
478
+
479
+ if (retval == 0) {
480
+ continue;
481
+ }
482
+
483
+ if (db->status == MYSQL_STATUS_READY) {
484
+ break;
485
+ }
486
+ }
487
+
488
+ retval = mysql_read_query_result(db);
489
+ CHECK_AND_RAISE(retval, query);
490
+
491
+ return mysql_store_result(db);
492
+ }
493
+ #endif
494
+
495
+
496
+ static void full_connect(VALUE self, MYSQL* db) {
497
+ // Check to see if we're on the db machine. If so, try to use the socket
498
+ VALUE r_host, r_user, r_password, r_path, r_query, r_port;
499
+
500
+ const char *host = "localhost", *user = "root";
501
+ char *database = NULL, *socket = NULL, *password = NULL, *path = NULL;
502
+ VALUE encoding = Qnil;
503
+
504
+ MYSQL *result;
505
+
506
+ int port = 3306;
507
+ unsigned long client_flags = 0;
508
+ int encoding_error;
509
+
510
+ if((r_host = rb_iv_get(self, "@host")) != Qnil) {
511
+ host = StringValuePtr(r_host);
512
+ }
513
+
514
+ if((r_user = rb_iv_get(self, "@user")) != Qnil) {
515
+ user = StringValuePtr(r_user);
516
+ }
517
+
518
+ if((r_password = rb_iv_get(self, "@password")) != Qnil) {
519
+ password = StringValuePtr(r_password);
520
+ }
521
+
522
+ if((r_port = rb_iv_get(self, "@port")) != Qnil) {
523
+ port = NUM2INT(r_port);
524
+ }
525
+
526
+ if((r_path = rb_iv_get(self, "@path")) != Qnil) {
527
+ path = StringValuePtr(r_path);
528
+ database = strtok(path, "/");
529
+ }
530
+
531
+ if (NULL == database || 0 == strlen(database)) {
532
+ rb_raise(eConnectionError, "Database must be specified");
533
+ }
534
+
535
+ r_query = rb_iv_get(self, "@query");
536
+
537
+ if (0 == strcasecmp(host, "localhost")) {
538
+ socket = get_uri_option(r_query, "socket");
539
+ if (NULL != socket) {
540
+ rb_iv_set(self, "@using_socket", Qtrue);
541
+ }
542
+ }
543
+
544
+ #ifdef HAVE_MYSQL_SSL_SET
545
+ char *ssl_client_key, *ssl_client_cert, *ssl_ca_cert, *ssl_ca_path, *ssl_cipher;
546
+ VALUE r_ssl;
547
+
548
+ if(rb_obj_is_kind_of(r_query, rb_cHash)) {
549
+ r_ssl = rb_hash_aref(r_query, rb_str_new2("ssl"));
550
+
551
+ if(rb_obj_is_kind_of(r_ssl, rb_cHash)) {
552
+ ssl_client_key = get_uri_option(r_ssl, "client_key");
553
+ ssl_client_cert = get_uri_option(r_ssl, "client_cert");
554
+ ssl_ca_cert = get_uri_option(r_ssl, "ca_cert");
555
+ ssl_ca_path = get_uri_option(r_ssl, "ca_path");
556
+ ssl_cipher = get_uri_option(r_ssl, "cipher");
557
+
558
+ assert_file_exists(ssl_client_key, "client_key doesn't exist");
559
+ assert_file_exists(ssl_client_cert, "client_cert doesn't exist");
560
+ assert_file_exists(ssl_ca_cert, "ca_cert doesn't exist");
561
+
562
+ mysql_ssl_set(db, ssl_client_key, ssl_client_cert, ssl_ca_cert, ssl_ca_path, ssl_cipher);
563
+ } else if(r_ssl != Qnil) {
564
+ rb_raise(eArgumentError, "ssl must be passed a hash");
565
+ }
566
+ }
567
+ #endif
568
+
569
+ result = (MYSQL *)mysql_real_connect(
570
+ db,
571
+ host,
572
+ user,
573
+ password,
574
+ database,
575
+ port,
576
+ socket,
577
+ client_flags
578
+ );
579
+
580
+ if (NULL == result) {
581
+ raise_error(self, db, Qnil);
582
+ }
583
+
584
+ #ifdef HAVE_MYSQL_SSL_SET
585
+ const char *ssl_cipher_used = mysql_get_ssl_cipher(db);
586
+
587
+ if (NULL != ssl_cipher_used) {
588
+ rb_iv_set(self, "@ssl_cipher", rb_str_new2(ssl_cipher_used));
589
+ }
590
+ #endif
591
+
592
+ #ifdef MYSQL_OPT_RECONNECT
593
+ my_bool reconnect = 1;
594
+ mysql_options(db, MYSQL_OPT_RECONNECT, &reconnect);
595
+ #endif
596
+
597
+ // Set the connections character set
598
+ encoding = rb_iv_get(self, "@encoding");
599
+
600
+ VALUE my_encoding = rb_hash_aref(CONST_GET(mEncoding, "MAP"), encoding);
601
+ if(my_encoding != Qnil) {
602
+ encoding_error = mysql_set_character_set(db, rb_str_ptr_readonly(my_encoding));
603
+ if (0 != encoding_error) {
604
+ raise_error(self, db, Qnil);
605
+ } else {
606
+ #ifdef HAVE_RUBY_ENCODING_H
607
+ rb_iv_set(self, "@encoding_id", INT2FIX(rb_enc_find_index(rb_str_ptr_readonly(encoding))));
608
+ #endif
609
+ rb_iv_set(self, "@my_encoding", my_encoding);
610
+ }
611
+ } else {
612
+ rb_warn("Encoding %s is not a known Ruby encoding for MySQL\n", rb_str_ptr_readonly(encoding));
613
+ rb_iv_set(self, "@encoding", rb_str_new2("UTF-8"));
614
+ #ifdef HAVE_RUBY_ENCODING_H
615
+ rb_iv_set(self, "@encoding_id", INT2FIX(rb_enc_find_index("UTF-8")));
616
+ #endif
617
+ rb_iv_set(self, "@my_encoding", rb_str_new2("utf8"));
618
+ }
619
+
620
+ // Disable sql_auto_is_null
621
+ cCommand_execute(self, db, rb_str_new2("SET sql_auto_is_null = 0"));
622
+ // removed NO_AUTO_VALUE_ON_ZERO because of MySQL bug http://bugs.mysql.com/bug.php?id=42270
623
+ // added NO_BACKSLASH_ESCAPES so that backslashes should not be escaped as in other databases
624
+ cCommand_execute(self, db, rb_str_new2("SET SESSION sql_mode = 'ANSI,NO_BACKSLASH_ESCAPES,NO_DIR_IN_CREATE,NO_ENGINE_SUBSTITUTION,NO_UNSIGNED_SUBTRACTION,TRADITIONAL'"));
625
+
626
+ rb_iv_set(self, "@connection", Data_Wrap_Struct(rb_cObject, 0, 0, db));
627
+ }
628
+
629
+ static VALUE cConnection_initialize(VALUE self, VALUE uri) {
630
+ VALUE r_host, r_user, r_password, r_path, r_query, r_port;
631
+
632
+ MYSQL *db = 0;
633
+ db = (MYSQL *)mysql_init(NULL);
634
+
635
+ rb_iv_set(self, "@using_socket", Qfalse);
636
+ rb_iv_set(self, "@ssl_cipher", Qnil);
637
+
638
+ r_host = rb_funcall(uri, rb_intern("host"), 0);
639
+ if (Qnil != r_host) {
640
+ rb_iv_set(self, "@host", r_host);
641
+ }
642
+
643
+ r_user = rb_funcall(uri, rb_intern("user"), 0);
644
+ if (Qnil != r_user) {
645
+ rb_iv_set(self, "@user", r_user);
646
+ }
647
+
648
+ r_password = rb_funcall(uri, rb_intern("password"), 0);
649
+ if (Qnil != r_password) {
650
+ rb_iv_set(self, "@password", r_password);
651
+ }
652
+
653
+ r_path = rb_funcall(uri, rb_intern("path"), 0);
654
+ if (Qnil != r_path) {
655
+ rb_iv_set(self, "@path", r_path);
656
+ }
657
+
658
+ r_port = rb_funcall(uri, rb_intern("port"), 0);
659
+ if (Qnil != r_port) {
660
+ rb_iv_set(self, "@port", r_port);
661
+ }
662
+
663
+ // Pull the querystring off the URI
664
+ r_query = rb_funcall(uri, rb_intern("query"), 0);
665
+ rb_iv_set(self, "@query", r_query);
666
+
667
+ const char* encoding = get_uri_option(r_query, "encoding");
668
+ if (!encoding) { encoding = get_uri_option(r_query, "charset"); }
669
+ if (!encoding) { encoding = "UTF-8"; }
670
+
671
+ rb_iv_set(self, "@encoding", rb_str_new2(encoding));
672
+
673
+ full_connect(self, db);
674
+
675
+ rb_iv_set(self, "@uri", uri);
676
+
677
+ return Qtrue;
678
+ }
679
+
680
+ static VALUE cConnection_character_set(VALUE self) {
681
+ return rb_iv_get(self, "@encoding");
682
+ }
683
+
684
+ static VALUE cConnection_is_using_socket(VALUE self) {
685
+ return rb_iv_get(self, "@using_socket");
686
+ }
687
+
688
+ static VALUE cConnection_ssl_cipher(VALUE self) {
689
+ return rb_iv_get(self, "@ssl_cipher");
690
+ }
691
+
692
+ static VALUE cConnection_dispose(VALUE self) {
693
+ VALUE connection_container = rb_iv_get(self, "@connection");
694
+
695
+ MYSQL *db;
696
+
697
+ if (Qnil == connection_container)
698
+ return Qfalse;
699
+
700
+ db = DATA_PTR(connection_container);
701
+
702
+ if (NULL == db)
703
+ return Qfalse;
704
+
705
+ mysql_close(db);
706
+ rb_iv_set(self, "@connection", Qnil);
707
+
708
+ return Qtrue;
709
+ }
710
+
711
+ /*
712
+ Accepts an array of Ruby types (Fixnum, Float, String, etc...) and turns them
713
+ into Ruby-strings so we can easily typecast later
714
+ */
715
+ static VALUE cCommand_set_types(int argc, VALUE *argv, VALUE self) {
716
+ VALUE type_strings = rb_ary_new();
717
+ VALUE array = rb_ary_new();
718
+
719
+ int i, j;
720
+
721
+ for ( i = 0; i < argc; i++) {
722
+ rb_ary_push(array, argv[i]);
723
+ }
724
+
725
+ for (i = 0; i < RARRAY_LEN(array); i++) {
726
+ VALUE entry = rb_ary_entry(array, i);
727
+ if(TYPE(entry) == T_CLASS) {
728
+ rb_ary_push(type_strings, entry);
729
+ } else if (TYPE(entry) == T_ARRAY) {
730
+ for (j = 0; j < RARRAY_LEN(entry); j++) {
731
+ VALUE sub_entry = rb_ary_entry(entry, j);
732
+ if(TYPE(sub_entry) == T_CLASS) {
733
+ rb_ary_push(type_strings, sub_entry);
734
+ } else {
735
+ rb_raise(eArgumentError, "Invalid type given");
736
+ }
737
+ }
738
+ } else {
739
+ rb_raise(eArgumentError, "Invalid type given");
740
+ }
741
+ }
742
+
743
+ rb_iv_set(self, "@field_types", type_strings);
744
+
745
+ return array;
746
+ }
747
+
748
+ VALUE cConnection_quote_time(VALUE self, VALUE value) {
749
+ return rb_funcall(value, ID_STRFTIME, 1, rb_str_new2("'%Y-%m-%d %H:%M:%S'"));
750
+ }
751
+
752
+
753
+ VALUE cConnection_quote_date_time(VALUE self, VALUE value) {
754
+ // TODO: Support non-local dates. we need to call #new_offset on the date to be
755
+ // quoted and pass in the current locale's date offset (self.new_offset((hours * 3600).to_r / 86400)
756
+ return rb_funcall(value, ID_STRFTIME, 1, rb_str_new2("'%Y-%m-%d %H:%M:%S'"));
757
+ }
758
+
759
+ VALUE cConnection_quote_date(VALUE self, VALUE value) {
760
+ return rb_funcall(value, ID_STRFTIME, 1, rb_str_new2("'%Y-%m-%d'"));
761
+ }
762
+
763
+ static VALUE cConnection_quote_string(VALUE self, VALUE string) {
764
+ MYSQL *db = DATA_PTR(rb_iv_get(self, "@connection"));
765
+ const char *source = rb_str_ptr_readonly(string);
766
+ size_t source_len = rb_str_len(string);
767
+ char *escaped;
768
+ VALUE result;
769
+
770
+ size_t quoted_length = 0;
771
+
772
+ // Allocate space for the escaped version of 'string'. Use + 3 allocate space for null term.
773
+ // and the leading and trailing single-quotes.
774
+ // Thanks to http://www.browardphp.com/mysql_manual_en/manual_MySQL_APIs.html#mysql_real_escape_string
775
+ escaped = (char *)calloc(source_len * 2 + 3, sizeof(char));
776
+
777
+ // Escape 'source' using the current encoding in use on the conection 'db'
778
+ quoted_length = mysql_real_escape_string(db, escaped + 1, source, source_len);
779
+
780
+ // Wrap the escaped string in single-quotes, this is DO's convention
781
+ escaped[0] = escaped[quoted_length + 1] = '\'';
782
+ result = DO_STR_NEW(escaped, quoted_length + 2, FIX2INT(rb_iv_get(self, "@encoding_id")));
783
+
784
+ free(escaped);
785
+ return result;
786
+ }
787
+
788
+ static VALUE build_query_from_args(VALUE klass, int count, VALUE *args) {
789
+ VALUE query = rb_iv_get(klass, "@text");
790
+
791
+ int i;
792
+ VALUE array = rb_ary_new();
793
+ for ( i = 0; i < count; i++) {
794
+ rb_ary_push(array, (VALUE)args[i]);
795
+ }
796
+ query = rb_funcall(klass, ID_ESCAPE_SQL, 1, array);
797
+
798
+ return query;
799
+ }
800
+
801
+ static VALUE cCommand_execute_non_query(int argc, VALUE *argv, VALUE self) {
802
+ VALUE query;
803
+
804
+ MYSQL_RES *response = 0;
805
+
806
+ my_ulonglong affected_rows;
807
+ VALUE connection = rb_iv_get(self, "@connection");
808
+ VALUE mysql_connection = rb_iv_get(connection, "@connection");
809
+ if (Qnil == mysql_connection) {
810
+ rb_raise(eConnectionError, "This connection has already been closed.");
811
+ }
812
+
813
+ MYSQL *db = DATA_PTR(mysql_connection);
814
+ query = build_query_from_args(self, argc, argv);
815
+
816
+ response = cCommand_execute(self, db, query);
817
+
818
+ affected_rows = mysql_affected_rows(db);
819
+ mysql_free_result(response);
820
+
821
+ if ((my_ulonglong)-1 == affected_rows)
822
+ return Qnil;
823
+
824
+ return rb_funcall(cResult, ID_NEW, 3, self, INT2NUM(affected_rows), INT2NUM(mysql_insert_id(db)));
825
+ }
826
+
827
+ static VALUE cCommand_execute_reader(int argc, VALUE *argv, VALUE self) {
828
+ VALUE query, reader;
829
+ VALUE field_names, field_types;
830
+
831
+ unsigned int field_count;
832
+ unsigned int i;
833
+
834
+ char guess_default_field_types = 0;
835
+ VALUE connection = rb_iv_get(self, "@connection");
836
+ VALUE mysql_connection = rb_iv_get(connection, "@connection");
837
+ if (Qnil == mysql_connection) {
838
+ rb_raise(eConnectionError, "This connection has already been closed.");
839
+ }
840
+
841
+ MYSQL *db = DATA_PTR(mysql_connection);
842
+
843
+ MYSQL_RES *response = 0;
844
+ MYSQL_FIELD *field;
845
+
846
+ query = build_query_from_args(self, argc, argv);
847
+
848
+ response = cCommand_execute(self, db, query);
849
+
850
+ if (!response) {
851
+ return Qnil;
852
+ }
853
+
854
+ field_count = mysql_field_count(db);
855
+
856
+ reader = rb_funcall(cReader, ID_NEW, 0);
857
+ rb_iv_set(reader, "@connection", connection);
858
+ rb_iv_set(reader, "@reader", Data_Wrap_Struct(rb_cObject, 0, 0, response));
859
+ rb_iv_set(reader, "@opened", Qfalse);
860
+ rb_iv_set(reader, "@field_count", INT2NUM(field_count));
861
+
862
+ field_names = rb_ary_new();
863
+ field_types = rb_iv_get(self, "@field_types");
864
+
865
+ if ( field_types == Qnil || 0 == RARRAY_LEN(field_types) ) {
866
+ field_types = rb_ary_new();
867
+ guess_default_field_types = 1;
868
+ } else if (RARRAY_LEN(field_types) != field_count) {
869
+ // Whoops... wrong number of types passed to set_types. Close the reader and raise
870
+ // and error
871
+ rb_funcall(reader, rb_intern("close"), 0);
872
+ rb_raise(eArgumentError, "Field-count mismatch. Expected %ld fields, but the query yielded %d", RARRAY_LEN(field_types), field_count);
873
+ }
874
+
875
+ for(i = 0; i < field_count; i++) {
876
+ field = mysql_fetch_field_direct(response, i);
877
+ rb_ary_push(field_names, rb_str_new2(field->name));
878
+
879
+ if (1 == guess_default_field_types) {
880
+ rb_ary_push(field_types, infer_ruby_type(field));
881
+ }
882
+ }
883
+
884
+ rb_iv_set(reader, "@fields", field_names);
885
+ rb_iv_set(reader, "@field_types", field_types);
886
+
887
+ if (rb_block_given_p()) {
888
+ rb_yield(reader);
889
+ rb_funcall(reader, rb_intern("close"), 0);
890
+ }
891
+
892
+ return reader;
893
+ }
894
+
895
+ // This should be called to ensure that the internal result reader is freed
896
+ static VALUE cReader_close(VALUE self) {
897
+ // Get the reader from the instance variable, maybe refactor this?
898
+ VALUE reader_container = rb_iv_get(self, "@reader");
899
+
900
+ MYSQL_RES *reader;
901
+
902
+ if (Qnil == reader_container)
903
+ return Qfalse;
904
+
905
+ reader = DATA_PTR(reader_container);
906
+
907
+ // The Meat
908
+ if (NULL == reader)
909
+ return Qfalse;
910
+
911
+ mysql_free_result(reader);
912
+ rb_iv_set(self, "@reader", Qnil);
913
+ rb_iv_set(self, "@opened", Qfalse);
914
+
915
+ return Qtrue;
916
+ }
917
+
918
+ // Retrieve a single row
919
+ static VALUE cReader_next(VALUE self) {
920
+ // Get the reader from the instance variable, maybe refactor this?
921
+ VALUE reader_container = rb_iv_get(self, "@reader");
922
+ VALUE field_types, field_type, row;
923
+
924
+ MYSQL_RES *reader;
925
+ MYSQL_ROW result;
926
+ unsigned long *lengths;
927
+
928
+ unsigned int i;
929
+
930
+ if (Qnil == reader_container) {
931
+ return Qfalse;
932
+ }
933
+
934
+ reader = DATA_PTR(reader_container);
935
+
936
+ // The Meat
937
+ field_types = rb_iv_get(self, "@field_types");
938
+ row = rb_ary_new();
939
+ result = mysql_fetch_row(reader);
940
+ lengths = mysql_fetch_lengths(reader);
941
+
942
+ rb_iv_set(self, "@opened", result ? Qtrue : Qfalse);
943
+
944
+ if (!result) {
945
+ return Qfalse;
946
+ }
947
+
948
+ int enc = -1;
949
+ #ifdef HAVE_RUBY_ENCODING_H
950
+ VALUE encoding_id = rb_iv_get(rb_iv_get(self, "@connection"), "@encoding_id");
951
+ if (encoding_id != Qnil) {
952
+ enc = FIX2INT(encoding_id);
953
+ }
954
+ #endif
955
+
956
+ for (i = 0; i < reader->field_count; i++) {
957
+ // The field_type data could be cached in a c-array
958
+ field_type = rb_ary_entry(field_types, i);
959
+ rb_ary_push(row, typecast(result[i], lengths[i], field_type, enc));
960
+ }
961
+
962
+ rb_iv_set(self, "@values", row);
963
+
964
+ return Qtrue;
965
+ }
966
+
967
+ static VALUE cReader_values(VALUE self) {
968
+ VALUE state = rb_iv_get(self, "@opened");
969
+ if ( state == Qnil || state == Qfalse ) {
970
+ rb_raise(eDataError, "Reader is not initialized");
971
+ }
972
+ return rb_iv_get(self, "@values");
973
+ }
974
+
975
+ static VALUE cReader_fields(VALUE self) {
976
+ return rb_iv_get(self, "@fields");
977
+ }
978
+
979
+ static VALUE cReader_field_count(VALUE self) {
980
+ return rb_iv_get(self, "@field_count");
981
+ }
982
+
983
+ void Init_do_mysql() {
984
+ rb_require("bigdecimal");
985
+ rb_require("date");
986
+
987
+ rb_funcall(rb_mKernel, rb_intern("require"), 1, rb_str_new2("data_objects"));
988
+
989
+ ID_TO_I = rb_intern("to_i");
990
+ ID_TO_F = rb_intern("to_f");
991
+ ID_TO_S = rb_intern("to_s");
992
+ ID_TO_TIME = rb_intern("to_time");
993
+ ID_NEW = rb_intern("new");
994
+ #ifdef RUBY_LESS_THAN_186
995
+ ID_NEW_DATE = rb_intern("new0");
996
+ #else
997
+ ID_NEW_DATE = rb_intern("new!");
998
+ #endif
999
+ ID_CONST_GET = rb_intern("const_get");
1000
+ ID_RATIONAL = rb_intern("Rational");
1001
+ ID_UTC = rb_intern("utc");
1002
+ ID_ESCAPE_SQL = rb_intern("escape_sql");
1003
+ ID_STRFTIME = rb_intern("strftime");
1004
+ ID_LOGGER = rb_intern("logger");
1005
+ ID_DEBUG = rb_intern("debug");
1006
+ ID_LEVEL = rb_intern("level");
1007
+
1008
+ // Store references to a few helpful clases that aren't in Ruby Core
1009
+ rb_cDate = RUBY_CLASS("Date");
1010
+ rb_cDateTime = RUBY_CLASS("DateTime");
1011
+ rb_cBigDecimal = RUBY_CLASS("BigDecimal");
1012
+
1013
+ // Get references to the Extlib module
1014
+ mExtlib = CONST_GET(rb_mKernel, "Extlib");
1015
+ rb_cByteArray = CONST_GET(mExtlib, "ByteArray");
1016
+
1017
+ // Get references to the DataObjects module and its classes
1018
+ mDO = CONST_GET(rb_mKernel, "DataObjects");
1019
+ cDO_Quoting = CONST_GET(mDO, "Quoting");
1020
+ cDO_Connection = CONST_GET(mDO, "Connection");
1021
+ cDO_Command = CONST_GET(mDO, "Command");
1022
+ cDO_Result = CONST_GET(mDO, "Result");
1023
+ cDO_Reader = CONST_GET(mDO, "Reader");
1024
+
1025
+ // Top Level Module that all the classes live under
1026
+ mDOMysql = rb_define_module_under(mDO, "Mysql");
1027
+
1028
+ eArgumentError = CONST_GET(rb_mKernel, "ArgumentError");
1029
+ eConnectionError = CONST_GET(mDO, "ConnectionError");
1030
+ eDataError = CONST_GET(mDO, "DataError");
1031
+ mEncoding = rb_define_module_under(mDOMysql, "Encoding");
1032
+
1033
+ cConnection = DRIVER_CLASS("Connection", cDO_Connection);
1034
+ rb_define_method(cConnection, "initialize", cConnection_initialize, 1);
1035
+ rb_define_method(cConnection, "using_socket?", cConnection_is_using_socket, 0);
1036
+ rb_define_method(cConnection, "ssl_cipher", cConnection_ssl_cipher, 0);
1037
+ rb_define_method(cConnection, "character_set", cConnection_character_set , 0);
1038
+ rb_define_method(cConnection, "dispose", cConnection_dispose, 0);
1039
+ rb_define_method(cConnection, "quote_string", cConnection_quote_string, 1);
1040
+ rb_define_method(cConnection, "quote_date", cConnection_quote_date, 1);
1041
+ rb_define_method(cConnection, "quote_time", cConnection_quote_time, 1);
1042
+ rb_define_method(cConnection, "quote_datetime", cConnection_quote_date_time, 1);
1043
+
1044
+ cCommand = DRIVER_CLASS("Command", cDO_Command);
1045
+ rb_define_method(cCommand, "set_types", cCommand_set_types, -1);
1046
+ rb_define_method(cCommand, "execute_non_query", cCommand_execute_non_query, -1);
1047
+ rb_define_method(cCommand, "execute_reader", cCommand_execute_reader, -1);
1048
+
1049
+ // Non-Query result
1050
+ cResult = DRIVER_CLASS("Result", cDO_Result);
1051
+
1052
+ // Query result
1053
+ cReader = DRIVER_CLASS("Reader", cDO_Reader);
1054
+ rb_define_method(cReader, "close", cReader_close, 0);
1055
+ rb_define_method(cReader, "next!", cReader_next, 0);
1056
+ rb_define_method(cReader, "values", cReader_values, 0);
1057
+ rb_define_method(cReader, "fields", cReader_fields, 0);
1058
+ rb_define_method(cReader, "field_count", cReader_field_count, 0);
1059
+
1060
+ struct errcodes *errs;
1061
+
1062
+ for (errs = errors; errs->error_name; errs++) {
1063
+ rb_const_set(mDOMysql, rb_intern(errs->error_name), INT2NUM(errs->error_no));
1064
+ }
1065
+ }