do_mysql 0.10.0-java → 0.10.1-java

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.
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
+ }