mysql2 0.3.1 → 0.5.2

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 (81) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +1 -151
  3. data/LICENSE +21 -0
  4. data/README.md +634 -0
  5. data/examples/eventmachine.rb +1 -3
  6. data/examples/threaded.rb +5 -9
  7. data/ext/mysql2/client.c +1154 -342
  8. data/ext/mysql2/client.h +20 -33
  9. data/ext/mysql2/extconf.rb +229 -37
  10. data/ext/mysql2/infile.c +122 -0
  11. data/ext/mysql2/infile.h +1 -0
  12. data/ext/mysql2/mysql2_ext.c +3 -1
  13. data/ext/mysql2/mysql2_ext.h +18 -16
  14. data/ext/mysql2/mysql_enc_name_to_ruby.h +168 -0
  15. data/ext/mysql2/mysql_enc_to_ruby.h +259 -0
  16. data/ext/mysql2/result.c +708 -191
  17. data/ext/mysql2/result.h +15 -6
  18. data/ext/mysql2/statement.c +602 -0
  19. data/ext/mysql2/statement.h +17 -0
  20. data/ext/mysql2/wait_for_single_fd.h +37 -0
  21. data/lib/mysql2.rb +69 -7
  22. data/lib/mysql2/client.rb +126 -211
  23. data/lib/mysql2/console.rb +5 -0
  24. data/lib/mysql2/em.rb +24 -8
  25. data/lib/mysql2/error.rb +93 -8
  26. data/lib/mysql2/field.rb +3 -0
  27. data/lib/mysql2/result.rb +2 -0
  28. data/lib/mysql2/statement.rb +11 -0
  29. data/lib/mysql2/version.rb +2 -2
  30. data/spec/configuration.yml.example +11 -0
  31. data/spec/em/em_spec.rb +101 -15
  32. data/spec/my.cnf.example +9 -0
  33. data/spec/mysql2/client_spec.rb +874 -232
  34. data/spec/mysql2/error_spec.rb +55 -46
  35. data/spec/mysql2/result_spec.rb +306 -154
  36. data/spec/mysql2/statement_spec.rb +712 -0
  37. data/spec/spec_helper.rb +103 -57
  38. data/spec/ssl/ca-cert.pem +17 -0
  39. data/spec/ssl/ca-key.pem +27 -0
  40. data/spec/ssl/ca.cnf +22 -0
  41. data/spec/ssl/cert.cnf +22 -0
  42. data/spec/ssl/client-cert.pem +17 -0
  43. data/spec/ssl/client-key.pem +27 -0
  44. data/spec/ssl/client-req.pem +15 -0
  45. data/spec/ssl/gen_certs.sh +48 -0
  46. data/spec/ssl/pkcs8-client-key.pem +28 -0
  47. data/spec/ssl/pkcs8-server-key.pem +28 -0
  48. data/spec/ssl/server-cert.pem +17 -0
  49. data/spec/ssl/server-key.pem +27 -0
  50. data/spec/ssl/server-req.pem +15 -0
  51. data/spec/test_data +1 -0
  52. data/support/5072E1F5.asc +432 -0
  53. data/support/libmysql.def +219 -0
  54. data/support/mysql_enc_to_ruby.rb +81 -0
  55. data/support/ruby_enc_to_mysql.rb +61 -0
  56. metadata +82 -188
  57. data/.gitignore +0 -12
  58. data/.rspec +0 -2
  59. data/.rvmrc +0 -1
  60. data/Gemfile +0 -3
  61. data/MIT-LICENSE +0 -20
  62. data/README.rdoc +0 -257
  63. data/Rakefile +0 -5
  64. data/benchmark/active_record.rb +0 -51
  65. data/benchmark/active_record_threaded.rb +0 -42
  66. data/benchmark/allocations.rb +0 -33
  67. data/benchmark/escape.rb +0 -36
  68. data/benchmark/query_with_mysql_casting.rb +0 -80
  69. data/benchmark/query_without_mysql_casting.rb +0 -47
  70. data/benchmark/sequel.rb +0 -37
  71. data/benchmark/setup_db.rb +0 -119
  72. data/benchmark/threaded.rb +0 -44
  73. data/lib/active_record/connection_adapters/em_mysql2_adapter.rb +0 -64
  74. data/lib/active_record/fiber_patches.rb +0 -104
  75. data/lib/mysql2/em_fiber.rb +0 -31
  76. data/mysql2.gemspec +0 -32
  77. data/spec/em/em_fiber_spec.rb +0 -22
  78. data/tasks/benchmarks.rake +0 -20
  79. data/tasks/compile.rake +0 -71
  80. data/tasks/rspec.rake +0 -16
  81. data/tasks/vendor_mysql.rake +0 -40
data/ext/mysql2/client.h CHANGED
@@ -1,41 +1,28 @@
1
1
  #ifndef MYSQL2_CLIENT_H
2
2
  #define MYSQL2_CLIENT_H
3
3
 
4
- /*
5
- * partial emulation of the 1.9 rb_thread_blocking_region under 1.8,
6
- * this is enough for dealing with blocking I/O functions in the
7
- * presence of threads.
8
- */
9
- #ifndef HAVE_RB_THREAD_BLOCKING_REGION
10
-
11
- #include <rubysig.h>
12
- #define RUBY_UBF_IO ((rb_unblock_function_t *)-1)
13
- typedef void rb_unblock_function_t(void *);
14
- typedef VALUE rb_blocking_function_t(void *);
15
- static VALUE
16
- rb_thread_blocking_region(
17
- rb_blocking_function_t *func, void *data1,
18
- RB_MYSQL_UNUSED rb_unblock_function_t *ubf,
19
- RB_MYSQL_UNUSED void *data2)
20
- {
21
- VALUE rv;
22
-
23
- TRAP_BEG;
24
- rv = func(data1);
25
- TRAP_END;
26
-
27
- return rv;
28
- }
29
-
30
- #endif /* ! HAVE_RB_THREAD_BLOCKING_REGION */
31
-
32
- void init_mysql2_client();
33
-
34
4
  typedef struct {
35
5
  VALUE encoding;
36
- char active;
37
- char closed;
6
+ VALUE active_thread; /* rb_thread_current() or Qnil */
7
+ long server_version;
8
+ int reconnect_enabled;
9
+ unsigned int connect_timeout;
10
+ int active;
11
+ int automatic_close;
12
+ int initialized;
13
+ int refcount;
14
+ int closed;
38
15
  MYSQL *client;
39
16
  } mysql_client_wrapper;
40
17
 
41
- #endif
18
+ void rb_mysql_client_set_active_thread(VALUE self);
19
+ void rb_mysql_set_server_query_flags(MYSQL *client, VALUE result);
20
+
21
+ #define GET_CLIENT(self) \
22
+ mysql_client_wrapper *wrapper; \
23
+ Data_Get_Struct(self, mysql_client_wrapper, wrapper);
24
+
25
+ void init_mysql2_client(void);
26
+ void decr_mysql2_client(mysql_client_wrapper *wrapper);
27
+
28
+ #endif
@@ -1,72 +1,264 @@
1
- # encoding: UTF-8
2
1
  require 'mkmf'
2
+ require 'English'
3
3
 
4
- def asplode lib
5
- abort "-----\n#{lib} is missing. please check your installation of mysql and try again.\n-----"
4
+ def asplode(lib)
5
+ if RUBY_PLATFORM =~ /mingw|mswin/
6
+ abort "-----\n#{lib} is missing. Check your installation of MySQL or Connector/C, and try again.\n-----"
7
+ elsif RUBY_PLATFORM =~ /darwin/
8
+ abort "-----\n#{lib} is missing. You may need to 'brew install mysql' or 'port install mysql', and try again.\n-----"
9
+ else
10
+ abort "-----\n#{lib} is missing. You may need to 'apt-get install libmysqlclient-dev' or 'yum install mysql-devel', and try again.\n-----"
11
+ end
6
12
  end
7
13
 
8
- # 1.9-only
9
- have_func('rb_thread_blocking_region')
14
+ def add_ssl_defines(header)
15
+ all_modes_found = %w[SSL_MODE_DISABLED SSL_MODE_PREFERRED SSL_MODE_REQUIRED SSL_MODE_VERIFY_CA SSL_MODE_VERIFY_IDENTITY].inject(true) do |m, ssl_mode|
16
+ m && have_const(ssl_mode, header)
17
+ end
18
+ $CFLAGS << ' -DFULL_SSL_MODE_SUPPORT' if all_modes_found
19
+ # if we only have ssl toggle (--ssl,--disable-ssl) from 5.7.3 to 5.7.10
20
+ has_no_support = all_modes_found ? false : !have_const('MYSQL_OPT_SSL_ENFORCE', header)
21
+ $CFLAGS << ' -DNO_SSL_MODE_SUPPORT' if has_no_support
22
+ end
23
+
24
+ # 2.1+
25
+ have_func('rb_absint_size')
26
+ have_func('rb_absint_singlebit_p')
27
+
28
+ # Missing in RBX (https://github.com/rubinius/rubinius/issues/3771)
29
+ have_func('rb_wait_for_single_fd')
10
30
 
11
31
  # borrowed from mysqlplus
12
32
  # http://github.com/oldmoe/mysqlplus/blob/master/ext/extconf.rb
13
- dirs = ENV['PATH'].split(File::PATH_SEPARATOR) + %w[
33
+ dirs = ENV.fetch('PATH').split(File::PATH_SEPARATOR) + %w[
14
34
  /opt
15
35
  /opt/local
16
36
  /opt/local/mysql
17
- /opt/local/lib/mysql5
37
+ /opt/local/lib/mysql5*
18
38
  /usr
39
+ /usr/mysql
19
40
  /usr/local
20
41
  /usr/local/mysql
21
42
  /usr/local/mysql-*
22
- /usr/local/lib/mysql5
23
- ].map{|dir| "#{dir}/bin" }
43
+ /usr/local/lib/mysql5*
44
+ /usr/local/opt/mysql5*
45
+ ].map { |dir| dir << '/bin' }
24
46
 
25
- GLOB = "{#{dirs.join(',')}}/{mysql_config,mysql_config5}"
47
+ # For those without HOMEBREW_ROOT in PATH
48
+ dirs << "#{ENV['HOMEBREW_ROOT']}/bin" if ENV['HOMEBREW_ROOT']
26
49
 
27
- if RUBY_PLATFORM =~ /mswin|mingw/
28
- inc, lib = dir_config('mysql')
29
- exit 1 unless have_library("libmysql")
30
- elsif mc = (with_config('mysql-config') || Dir[GLOB].first) then
50
+ GLOB = "{#{dirs.join(',')}}/{mysql_config,mysql_config5,mariadb_config}".freeze
51
+
52
+ # If the user has provided a --with-mysql-dir argument, we must respect it or fail.
53
+ inc, lib = dir_config('mysql')
54
+ if inc && lib
55
+ # Ruby versions below 2.0 on Unix and below 2.1 on Windows
56
+ # do not properly search for lib directories, and must be corrected:
57
+ # https://bugs.ruby-lang.org/projects/ruby-trunk/repository/revisions/39717
58
+ unless lib && lib[-3, 3] == 'lib'
59
+ @libdir_basename = 'lib'
60
+ inc, lib = dir_config('mysql')
61
+ end
62
+ abort "-----\nCannot find include dir(s) #{inc}\n-----" unless inc && inc.split(File::PATH_SEPARATOR).any? { |dir| File.directory?(dir) }
63
+ abort "-----\nCannot find library dir(s) #{lib}\n-----" unless lib && lib.split(File::PATH_SEPARATOR).any? { |dir| File.directory?(dir) }
64
+ warn "-----\nUsing --with-mysql-dir=#{File.dirname inc}\n-----"
65
+ rpath_dir = lib
66
+ have_library('mysqlclient')
67
+ elsif (mc = (with_config('mysql-config') || Dir[GLOB].first))
68
+ # If the user has provided a --with-mysql-config argument, we must respect it or fail.
69
+ # If the user gave --with-mysql-config with no argument means we should try to find it.
31
70
  mc = Dir[GLOB].first if mc == true
32
- cflags = `#{mc} --cflags`.chomp
33
- exit 1 if $? != 0
71
+ abort "-----\nCannot find mysql_config at #{mc}\n-----" unless mc && File.exist?(mc)
72
+ abort "-----\nCannot execute mysql_config at #{mc}\n-----" unless File.executable?(mc)
73
+ warn "-----\nUsing mysql_config at #{mc}\n-----"
74
+ ver = `#{mc} --version`.chomp.to_f
75
+ includes = `#{mc} --include`.chomp
76
+ abort unless $CHILD_STATUS.success?
34
77
  libs = `#{mc} --libs_r`.chomp
35
- if libs.empty?
36
- libs = `#{mc} --libs`.chomp
37
- end
38
- exit 1 if $? != 0
39
- $CPPFLAGS += ' ' + cflags
78
+ # MySQL 5.5 and above already have re-entrant code in libmysqlclient (no _r).
79
+ libs = `#{mc} --libs`.chomp if ver >= 5.5 || libs.empty?
80
+ abort unless $CHILD_STATUS.success?
81
+ $INCFLAGS += ' ' + includes
40
82
  $libs = libs + " " + $libs
83
+ rpath_dir = libs
41
84
  else
42
- inc, lib = dir_config('mysql', '/usr/local')
43
- libs = ['m', 'z', 'socket', 'nsl', 'mygcc']
44
- while not find_library('mysqlclient', 'mysql_query', lib, "#{lib}/mysql") do
45
- exit 1 if libs.empty?
46
- have_library(libs.shift)
47
- end
85
+ _, usr_local_lib = dir_config('mysql', '/usr/local')
86
+
87
+ asplode("mysql client") unless find_library('mysqlclient', nil, usr_local_lib, "#{usr_local_lib}/mysql")
88
+
89
+ rpath_dir = usr_local_lib
48
90
  end
49
91
 
50
- if have_header('mysql.h') then
92
+ if have_header('mysql.h')
51
93
  prefix = nil
52
- elsif have_header('mysql/mysql.h') then
94
+ elsif have_header('mysql/mysql.h')
53
95
  prefix = 'mysql'
54
96
  else
55
97
  asplode 'mysql.h'
56
98
  end
57
99
 
58
- %w{ errmsg.h mysqld_error.h }.each do |h|
59
- header = [prefix, h].compact.join '/'
60
- asplode h unless have_header h
100
+ %w[errmsg.h].each do |h|
101
+ header = [prefix, h].compact.join('/')
102
+ asplode h unless have_header header
61
103
  end
62
104
 
63
- unless RUBY_PLATFORM =~ /mswin/ or RUBY_PLATFORM =~ /sparc/
64
- $CFLAGS << ' -Wall -funroll-loops'
105
+ mysql_h = [prefix, 'mysql.h'].compact.join('/')
106
+ add_ssl_defines(mysql_h)
107
+ have_struct_member('MYSQL', 'net.vio', mysql_h)
108
+ have_struct_member('MYSQL', 'net.pvio', mysql_h)
109
+
110
+ # These constants are actually enums, so they cannot be detected by #ifdef in C code.
111
+ have_const('MYSQL_ENABLE_CLEARTEXT_PLUGIN', mysql_h)
112
+ have_const('SERVER_QUERY_NO_GOOD_INDEX_USED', mysql_h)
113
+ have_const('SERVER_QUERY_NO_INDEX_USED', mysql_h)
114
+ have_const('SERVER_QUERY_WAS_SLOW', mysql_h)
115
+ have_const('MYSQL_OPTION_MULTI_STATEMENTS_ON', mysql_h)
116
+ have_const('MYSQL_OPTION_MULTI_STATEMENTS_OFF', mysql_h)
117
+
118
+ # my_bool is replaced by C99 bool in MySQL 8.0, but we want
119
+ # to retain compatibility with the typedef in earlier MySQLs.
120
+ have_type('my_bool', mysql_h)
121
+
122
+ # This is our wishlist. We use whichever flags work on the host.
123
+ # -Wall and -Wextra are included by default.
124
+ wishlist = [
125
+ '-Weverything',
126
+ '-Wno-bad-function-cast', # rb_thread_call_without_gvl returns void * that we cast to VALUE
127
+ '-Wno-conditional-uninitialized', # false positive in client.c
128
+ '-Wno-covered-switch-default', # result.c -- enum_field_types (when fully covered, e.g. mysql 5.5)
129
+ '-Wno-declaration-after-statement', # GET_CLIENT followed by GET_STATEMENT in statement.c
130
+ '-Wno-disabled-macro-expansion', # rubby :(
131
+ '-Wno-documentation-unknown-command', # rubby :(
132
+ '-Wno-missing-field-initializers', # gperf generates bad code
133
+ '-Wno-missing-variable-declarations', # missing symbols due to ruby native ext initialization
134
+ '-Wno-padded', # mysql :(
135
+ '-Wno-reserved-id-macro', # rubby :(
136
+ '-Wno-sign-conversion', # gperf generates bad code
137
+ '-Wno-static-in-inline', # gperf generates bad code
138
+ '-Wno-switch-enum', # result.c -- enum_field_types (when not fully covered, e.g. mysql 5.6+)
139
+ '-Wno-undef', # rubinius :(
140
+ '-Wno-unreachable-code', # rubby :(
141
+ '-Wno-used-but-marked-unused', # rubby :(
142
+ ]
143
+
144
+ usable_flags = wishlist.select do |flag|
145
+ try_link('int main() {return 0;}', "-Werror #{flag}")
65
146
  end
66
- # $CFLAGS << ' -O0 -ggdb3 -Wextra'
67
147
 
68
- if hard_mysql_path = $libs[%r{-L(/[^ ]+)}, 1]
69
- $LDFLAGS << " -Wl,-rpath,#{hard_mysql_path}"
148
+ $CFLAGS << ' ' << usable_flags.join(' ')
149
+
150
+ enabled_sanitizers = disabled_sanitizers = []
151
+ # Specify a commna-separated list of sanitizers, or try them all by default
152
+ sanitizers = with_config('sanitize')
153
+ case sanitizers
154
+ when true
155
+ # Try them all, turn on whatever we can
156
+ enabled_sanitizers = %w[address cfi integer memory thread undefined].select do |s|
157
+ try_link('int main() {return 0;}', "-Werror -fsanitize=#{s}")
158
+ end
159
+ abort "-----\nCould not enable any sanitizers!\n-----" if enabled_sanitizers.empty?
160
+ when String
161
+ # Figure out which sanitizers are supported
162
+ enabled_sanitizers, disabled_sanitizers = sanitizers.split(',').partition do |s|
163
+ try_link('int main() {return 0;}', "-Werror -fsanitize=#{s}")
164
+ end
165
+ end
166
+
167
+ unless disabled_sanitizers.empty?
168
+ abort "-----\nCould not enable requested sanitizers: #{disabled_sanitizers.join(',')}\n-----"
169
+ end
170
+
171
+ unless enabled_sanitizers.empty?
172
+ warn "-----\nEnabling sanitizers: #{enabled_sanitizers.join(',')}\n-----"
173
+ enabled_sanitizers.each do |s|
174
+ # address sanitizer requires runtime support
175
+ if s == 'address' # rubocop:disable Style/IfUnlessModifier
176
+ have_library('asan') || $LDFLAGS << ' -fsanitize=address'
177
+ end
178
+ $CFLAGS << " -fsanitize=#{s}"
179
+ end
180
+ # Options for line numbers in backtraces
181
+ $CFLAGS << ' -g -fno-omit-frame-pointer'
182
+ end
183
+
184
+ if RUBY_PLATFORM =~ /mswin|mingw/ && !defined?(RubyInstaller)
185
+ # Build libmysql.a interface link library
186
+ require 'rake'
187
+
188
+ # Build libmysql.a interface link library
189
+ # Use rake to rebuild only if these files change
190
+ deffile = File.expand_path('../../../support/libmysql.def', __FILE__)
191
+ libfile = File.expand_path(File.join(rpath_dir, 'libmysql.lib'))
192
+ file 'libmysql.a' => [deffile, libfile] do
193
+ when_writing 'building libmysql.a' do
194
+ # Ruby kindly shows us where dllwrap is, but that tool does more than we want.
195
+ # Maybe in the future Ruby could provide RbConfig::CONFIG['DLLTOOL'] directly.
196
+ dlltool = RbConfig::CONFIG['DLLWRAP'].gsub('dllwrap', 'dlltool')
197
+ sh dlltool, '--kill-at',
198
+ '--dllname', 'libmysql.dll',
199
+ '--output-lib', 'libmysql.a',
200
+ '--input-def', deffile, libfile
201
+ end
202
+ end
203
+
204
+ Rake::Task['libmysql.a'].invoke
205
+ $LOCAL_LIBS << ' ' << 'libmysql.a'
206
+
207
+ # Make sure the generated interface library works (if cross-compiling, trust without verifying)
208
+ unless RbConfig::CONFIG['host_os'] =~ /mswin|mingw/
209
+ abort "-----\nCannot find libmysql.a\n-----" unless have_library('libmysql')
210
+ abort "-----\nCannot link to libmysql.a (my_init)\n-----" unless have_func('my_init')
211
+ end
212
+
213
+ # Vendor libmysql.dll
214
+ vendordir = File.expand_path('../../../vendor/', __FILE__)
215
+ directory vendordir
216
+
217
+ vendordll = File.join(vendordir, 'libmysql.dll')
218
+ dllfile = File.expand_path(File.join(rpath_dir, 'libmysql.dll'))
219
+ file vendordll => [dllfile, vendordir] do
220
+ when_writing 'copying libmysql.dll' do
221
+ cp dllfile, vendordll
222
+ end
223
+ end
224
+
225
+ # Copy libmysql.dll to the local vendor directory by default
226
+ if arg_config('--no-vendor-libmysql')
227
+ # Fine, don't.
228
+ puts "--no-vendor-libmysql"
229
+ else # Default: arg_config('--vendor-libmysql')
230
+ # Let's do it!
231
+ Rake::Task[vendordll].invoke
232
+ end
233
+ else
234
+ case explicit_rpath = with_config('mysql-rpath')
235
+ when true
236
+ abort "-----\nOption --with-mysql-rpath must have an argument\n-----"
237
+ when false
238
+ warn "-----\nOption --with-mysql-rpath has been disabled at your request\n-----"
239
+ when String
240
+ # The user gave us a value so use it
241
+ rpath_flags = " -Wl,-rpath,#{explicit_rpath}"
242
+ warn "-----\nSetting mysql rpath to #{explicit_rpath}\n-----"
243
+ $LDFLAGS << rpath_flags
244
+ else
245
+ if (libdir = rpath_dir[%r{(-L)?(/[^ ]+)}, 2])
246
+ rpath_flags = " -Wl,-rpath,#{libdir}"
247
+ if RbConfig::CONFIG["RPATHFLAG"].to_s.empty? && try_link('int main() {return 0;}', rpath_flags)
248
+ # Usually Ruby sets RPATHFLAG the right way for each system, but not on OS X.
249
+ warn "-----\nSetting rpath to #{libdir}\n-----"
250
+ $LDFLAGS << rpath_flags
251
+ else
252
+ if RbConfig::CONFIG["RPATHFLAG"].to_s.empty?
253
+ # If we got here because try_link failed, warn the user
254
+ warn "-----\nDon't know how to set rpath on your system, if MySQL libraries are not in path mysql2 may not load\n-----"
255
+ end
256
+ # Make sure that LIBPATH gets set if we didn't explicitly set the rpath.
257
+ warn "-----\nSetting libpath to #{libdir}\n-----"
258
+ $LIBPATH << libdir unless $LIBPATH.include?(libdir)
259
+ end
260
+ end
261
+ end
70
262
  end
71
263
 
72
264
  create_makefile('mysql2/mysql2')
@@ -0,0 +1,122 @@
1
+ #include <mysql2_ext.h>
2
+
3
+ #include <errno.h>
4
+ #ifndef _MSC_VER
5
+ #include <unistd.h>
6
+ #endif
7
+ #include <fcntl.h>
8
+
9
+ #define ERROR_LEN 1024
10
+ typedef struct
11
+ {
12
+ int fd;
13
+ char *filename;
14
+ char error[ERROR_LEN];
15
+ mysql_client_wrapper *wrapper;
16
+ } mysql2_local_infile_data;
17
+
18
+ /* MySQL calls this function when a user begins a LOAD DATA LOCAL INFILE query.
19
+ *
20
+ * Allocate a data struct and pass it back through the data pointer.
21
+ *
22
+ * Returns:
23
+ * 0 on success
24
+ * 1 on error
25
+ */
26
+ static int
27
+ mysql2_local_infile_init(void **ptr, const char *filename, void *userdata)
28
+ {
29
+ mysql2_local_infile_data *data = malloc(sizeof(mysql2_local_infile_data));
30
+ if (!data) return 1;
31
+
32
+ *ptr = data;
33
+ data->error[0] = 0;
34
+ data->wrapper = userdata;
35
+
36
+ data->filename = strdup(filename);
37
+ if (!data->filename) {
38
+ snprintf(data->error, ERROR_LEN, "%s: %s", strerror(errno), filename);
39
+ return 1;
40
+ }
41
+
42
+ data->fd = open(filename, O_RDONLY);
43
+ if (data->fd < 0) {
44
+ snprintf(data->error, ERROR_LEN, "%s: %s", strerror(errno), filename);
45
+ return 1;
46
+ }
47
+
48
+ return 0;
49
+ }
50
+
51
+ /* MySQL calls this function to read data from the local file.
52
+ *
53
+ * Returns:
54
+ * > 0 number of bytes read
55
+ * == 0 end of file
56
+ * < 0 error
57
+ */
58
+ static int
59
+ mysql2_local_infile_read(void *ptr, char *buf, unsigned int buf_len)
60
+ {
61
+ int count;
62
+ mysql2_local_infile_data *data = (mysql2_local_infile_data *)ptr;
63
+
64
+ count = (int)read(data->fd, buf, buf_len);
65
+ if (count < 0) {
66
+ snprintf(data->error, ERROR_LEN, "%s: %s", strerror(errno), data->filename);
67
+ }
68
+
69
+ return count;
70
+ }
71
+
72
+ /* MySQL calls this function when we're done with the LOCAL INFILE query.
73
+ *
74
+ * ptr will be null if the init function failed.
75
+ */
76
+ static void
77
+ mysql2_local_infile_end(void *ptr)
78
+ {
79
+ mysql2_local_infile_data *data = (mysql2_local_infile_data *)ptr;
80
+ if (data) {
81
+ if (data->fd >= 0)
82
+ close(data->fd);
83
+ if (data->filename)
84
+ free(data->filename);
85
+ free(data);
86
+ }
87
+ }
88
+
89
+ /* MySQL calls this function if any of the functions above returned an error.
90
+ *
91
+ * This function is called even if init failed, with whatever ptr value
92
+ * init has set, regardless of the return value of the init function.
93
+ *
94
+ * Returns:
95
+ * Error message number (see http://dev.mysql.com/doc/refman/5.0/en/error-messages-client.html)
96
+ */
97
+ static int
98
+ mysql2_local_infile_error(void *ptr, char *error_msg, unsigned int error_msg_len)
99
+ {
100
+ mysql2_local_infile_data *data = (mysql2_local_infile_data *) ptr;
101
+
102
+ if (data) {
103
+ snprintf(error_msg, error_msg_len, "%s", data->error);
104
+ return CR_UNKNOWN_ERROR;
105
+ }
106
+
107
+ snprintf(error_msg, error_msg_len, "Out of memory");
108
+ return CR_OUT_OF_MEMORY;
109
+ }
110
+
111
+ /* Tell MySQL Client to use our own local_infile functions.
112
+ * This is both due to bugginess in the default handlers,
113
+ * and to improve the Rubyness of the handlers here.
114
+ */
115
+ void mysql2_set_local_infile(MYSQL *mysql, void *userdata)
116
+ {
117
+ mysql_set_local_infile_handler(mysql,
118
+ mysql2_local_infile_init,
119
+ mysql2_local_infile_read,
120
+ mysql2_local_infile_end,
121
+ mysql2_local_infile_error, userdata);
122
+ }