mysql2 0.3.21 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,13 +2,14 @@
2
2
  #define MYSQL2_RESULT_H
3
3
 
4
4
  void init_mysql2_result();
5
- VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r);
5
+ VALUE rb_mysql_result_to_obj(VALUE client, VALUE encoding, VALUE options, MYSQL_RES *r, VALUE statement);
6
6
 
7
7
  typedef struct {
8
8
  VALUE fields;
9
9
  VALUE rows;
10
10
  VALUE client;
11
11
  VALUE encoding;
12
+ VALUE statement;
12
13
  unsigned int numberOfFields;
13
14
  unsigned long numberOfRows;
14
15
  unsigned long lastRowProcessed;
@@ -16,7 +17,13 @@ typedef struct {
16
17
  char streamingComplete;
17
18
  char resultFreed;
18
19
  MYSQL_RES *result;
20
+ mysql_stmt_wrapper *stmt_wrapper;
19
21
  mysql_client_wrapper *client_wrapper;
22
+ /* statement result bind buffers */
23
+ MYSQL_BIND *result_buffers;
24
+ my_bool *is_null;
25
+ my_bool *error;
26
+ unsigned long *length;
20
27
  } mysql2_result_wrapper;
21
28
 
22
29
  #endif
@@ -0,0 +1,454 @@
1
+ #include <mysql2_ext.h>
2
+
3
+ VALUE cMysql2Statement;
4
+ extern VALUE mMysql2, cMysql2Error, cBigDecimal, cDateTime, cDate;
5
+ static VALUE sym_stream, intern_error_number_eql, intern_sql_state_eql, intern_each;
6
+ static VALUE intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year;
7
+
8
+ #define GET_STATEMENT(self) \
9
+ mysql_stmt_wrapper *stmt_wrapper; \
10
+ Data_Get_Struct(self, mysql_stmt_wrapper, stmt_wrapper);
11
+
12
+
13
+ static void rb_mysql_stmt_mark(void * ptr) {
14
+ mysql_stmt_wrapper* stmt_wrapper = (mysql_stmt_wrapper *)ptr;
15
+ if (!stmt_wrapper) return;
16
+
17
+ rb_gc_mark(stmt_wrapper->client);
18
+ }
19
+
20
+ static void rb_mysql_stmt_free(void * ptr) {
21
+ mysql_stmt_wrapper* stmt_wrapper = (mysql_stmt_wrapper *)ptr;
22
+ decr_mysql2_stmt(stmt_wrapper);
23
+ }
24
+
25
+ void decr_mysql2_stmt(mysql_stmt_wrapper *stmt_wrapper) {
26
+ stmt_wrapper->refcount--;
27
+
28
+ if (stmt_wrapper->refcount == 0) {
29
+ mysql_stmt_close(stmt_wrapper->stmt);
30
+ xfree(stmt_wrapper);
31
+ }
32
+ }
33
+
34
+ VALUE rb_raise_mysql2_stmt_error2(MYSQL_STMT *stmt
35
+ #ifdef HAVE_RUBY_ENCODING_H
36
+ , rb_encoding *conn_enc
37
+ #endif
38
+ ) {
39
+ VALUE rb_error_msg = rb_str_new2(mysql_stmt_error(stmt));
40
+ VALUE rb_sql_state = rb_tainted_str_new2(mysql_stmt_sqlstate(stmt));
41
+ VALUE e = rb_exc_new3(cMysql2Error, rb_error_msg);
42
+ #ifdef HAVE_RUBY_ENCODING_H
43
+ rb_encoding *default_internal_enc = rb_default_internal_encoding();
44
+
45
+ rb_enc_associate(rb_error_msg, conn_enc);
46
+ rb_enc_associate(rb_sql_state, conn_enc);
47
+ if (default_internal_enc) {
48
+ rb_error_msg = rb_str_export_to_enc(rb_error_msg, default_internal_enc);
49
+ rb_sql_state = rb_str_export_to_enc(rb_sql_state, default_internal_enc);
50
+ }
51
+ #endif
52
+ rb_funcall(e, intern_error_number_eql, 1, UINT2NUM(mysql_stmt_errno(stmt)));
53
+ rb_funcall(e, intern_sql_state_eql, 1, rb_sql_state);
54
+ rb_exc_raise(e);
55
+ return Qnil;
56
+ }
57
+
58
+ static void rb_raise_mysql2_stmt_error(VALUE self) {
59
+ #ifdef HAVE_RUBY_ENCODING_H
60
+ rb_encoding *conn_enc;
61
+ #endif
62
+ GET_STATEMENT(self);
63
+
64
+ #ifdef HAVE_RUBY_ENCODING_H
65
+ {
66
+ GET_CLIENT(stmt_wrapper->client);
67
+ conn_enc = rb_to_encoding(wrapper->encoding);
68
+ }
69
+ #endif
70
+
71
+ rb_raise_mysql2_stmt_error2(stmt_wrapper->stmt
72
+ #ifdef HAVE_RUBY_ENCODING_H
73
+ , conn_enc
74
+ #endif
75
+ );
76
+ }
77
+
78
+
79
+ /*
80
+ * used to pass all arguments to mysql_stmt_prepare while inside
81
+ * nogvl_prepare_statement_args
82
+ */
83
+ struct nogvl_prepare_statement_args {
84
+ MYSQL_STMT *stmt;
85
+ VALUE sql;
86
+ const char *sql_ptr;
87
+ unsigned long sql_len;
88
+ };
89
+
90
+ static void *nogvl_prepare_statement(void *ptr) {
91
+ struct nogvl_prepare_statement_args *args = ptr;
92
+
93
+ if (mysql_stmt_prepare(args->stmt, args->sql_ptr, args->sql_len)) {
94
+ return (void*)Qfalse;
95
+ } else {
96
+ return (void*)Qtrue;
97
+ }
98
+ }
99
+
100
+ VALUE rb_mysql_stmt_new(VALUE rb_client, VALUE sql) {
101
+ mysql_stmt_wrapper* stmt_wrapper;
102
+ VALUE rb_stmt;
103
+ #ifdef HAVE_RUBY_ENCODING_H
104
+ rb_encoding *conn_enc;
105
+ #endif
106
+
107
+ Check_Type(sql, T_STRING);
108
+
109
+ rb_stmt = Data_Make_Struct(cMysql2Statement, mysql_stmt_wrapper, rb_mysql_stmt_mark, rb_mysql_stmt_free, stmt_wrapper);
110
+ {
111
+ stmt_wrapper->client = rb_client;
112
+ stmt_wrapper->refcount = 1;
113
+ stmt_wrapper->stmt = NULL;
114
+ }
115
+
116
+ // instantiate stmt
117
+ {
118
+ GET_CLIENT(rb_client);
119
+ stmt_wrapper->stmt = mysql_stmt_init(wrapper->client);
120
+ #ifdef HAVE_RUBY_ENCODING_H
121
+ conn_enc = rb_to_encoding(wrapper->encoding);
122
+ #endif
123
+ }
124
+ if (stmt_wrapper->stmt == NULL) {
125
+ rb_raise(cMysql2Error, "Unable to initialize prepared statement: out of memory");
126
+ }
127
+
128
+ // set STMT_ATTR_UPDATE_MAX_LENGTH attr
129
+ {
130
+ my_bool truth = 1;
131
+ if (mysql_stmt_attr_set(stmt_wrapper->stmt, STMT_ATTR_UPDATE_MAX_LENGTH, &truth)) {
132
+ rb_raise(cMysql2Error, "Unable to initialize prepared statement: set STMT_ATTR_UPDATE_MAX_LENGTH");
133
+ }
134
+ }
135
+
136
+ // call mysql_stmt_prepare w/o gvl
137
+ {
138
+ struct nogvl_prepare_statement_args args;
139
+ args.stmt = stmt_wrapper->stmt;
140
+ args.sql = sql;
141
+ #ifdef HAVE_RUBY_ENCODING_H
142
+ // ensure the string is in the encoding the connection is expecting
143
+ args.sql = rb_str_export_to_enc(args.sql, conn_enc);
144
+ #endif
145
+ args.sql_ptr = RSTRING_PTR(sql);
146
+ args.sql_len = RSTRING_LEN(sql);
147
+
148
+ if ((VALUE)rb_thread_call_without_gvl(nogvl_prepare_statement, &args, RUBY_UBF_IO, 0) == Qfalse) {
149
+ rb_raise_mysql2_stmt_error(rb_stmt);
150
+ }
151
+ }
152
+
153
+ return rb_stmt;
154
+ }
155
+
156
+ /* call-seq: stmt.param_count # => Numeric
157
+ *
158
+ * Returns the number of parameters the prepared statement expects.
159
+ */
160
+ static VALUE param_count(VALUE self) {
161
+ GET_STATEMENT(self);
162
+
163
+ return ULL2NUM(mysql_stmt_param_count(stmt_wrapper->stmt));
164
+ }
165
+
166
+ /* call-seq: stmt.field_count # => Numeric
167
+ *
168
+ * Returns the number of fields the prepared statement returns.
169
+ */
170
+ static VALUE field_count(VALUE self) {
171
+ GET_STATEMENT(self);
172
+
173
+ return UINT2NUM(mysql_stmt_field_count(stmt_wrapper->stmt));
174
+ }
175
+
176
+ static void *nogvl_execute(void *ptr) {
177
+ MYSQL_STMT *stmt = ptr;
178
+
179
+ if (mysql_stmt_execute(stmt)) {
180
+ return (void*)Qfalse;
181
+ } else {
182
+ return (void*)Qtrue;
183
+ }
184
+ }
185
+
186
+ static void *nogvl_stmt_store_result(void *ptr) {
187
+ MYSQL_STMT *stmt = ptr;
188
+
189
+ if (mysql_stmt_store_result(stmt)) {
190
+ return (void *)Qfalse;
191
+ } else {
192
+ return (void *)Qtrue;
193
+ }
194
+ }
195
+
196
+ /* Free each bind_buffer[i].buffer except when params_enc is non-nil, this means
197
+ * the buffer is a Ruby string pointer and not our memory to manage.
198
+ */
199
+ #define FREE_BINDS \
200
+ for (i = 0; i < argc; i++) { \
201
+ if (bind_buffers[i].buffer && NIL_P(params_enc[i])) { \
202
+ xfree(bind_buffers[i].buffer); \
203
+ } \
204
+ } \
205
+ if (argc > 0) { \
206
+ xfree(bind_buffers); \
207
+ xfree(length_buffers); \
208
+ }
209
+
210
+ /* call-seq: stmt.execute
211
+ *
212
+ * Executes the current prepared statement, returns +result+.
213
+ */
214
+ static VALUE execute(int argc, VALUE *argv, VALUE self) {
215
+ MYSQL_BIND *bind_buffers = NULL;
216
+ unsigned long *length_buffers = NULL;
217
+ unsigned long bind_count;
218
+ long i;
219
+ MYSQL_STMT *stmt;
220
+ MYSQL_RES *metadata;
221
+ VALUE current;
222
+ VALUE resultObj;
223
+ VALUE *params_enc;
224
+ int is_streaming;
225
+ #ifdef HAVE_RUBY_ENCODING_H
226
+ rb_encoding *conn_enc;
227
+ #endif
228
+
229
+ GET_STATEMENT(self);
230
+ GET_CLIENT(stmt_wrapper->client);
231
+
232
+ #ifdef HAVE_RUBY_ENCODING_H
233
+ conn_enc = rb_to_encoding(wrapper->encoding);
234
+ #endif
235
+
236
+ /* Scratch space for string encoding exports, allocate on the stack. */
237
+ params_enc = alloca(sizeof(VALUE) * argc);
238
+
239
+ stmt = stmt_wrapper->stmt;
240
+
241
+ bind_count = mysql_stmt_param_count(stmt);
242
+ if (argc != (long)bind_count) {
243
+ rb_raise(cMysql2Error, "Bind parameter count (%ld) doesn't match number of arguments (%d)", bind_count, argc);
244
+ }
245
+
246
+ // setup any bind variables in the query
247
+ if (bind_count > 0) {
248
+ bind_buffers = xcalloc(bind_count, sizeof(MYSQL_BIND));
249
+ length_buffers = xcalloc(bind_count, sizeof(unsigned long));
250
+
251
+ for (i = 0; i < argc; i++) {
252
+ bind_buffers[i].buffer = NULL;
253
+ params_enc[i] = Qnil;
254
+
255
+ switch (TYPE(argv[i])) {
256
+ case T_NIL:
257
+ bind_buffers[i].buffer_type = MYSQL_TYPE_NULL;
258
+ break;
259
+ case T_FIXNUM:
260
+ #if SIZEOF_INT < SIZEOF_LONG
261
+ bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG;
262
+ bind_buffers[i].buffer = xmalloc(sizeof(long long int));
263
+ *(long*)(bind_buffers[i].buffer) = FIX2LONG(argv[i]);
264
+ #else
265
+ bind_buffers[i].buffer_type = MYSQL_TYPE_LONG;
266
+ bind_buffers[i].buffer = xmalloc(sizeof(int));
267
+ *(long*)(bind_buffers[i].buffer) = FIX2INT(argv[i]);
268
+ #endif
269
+ break;
270
+ case T_BIGNUM:
271
+ bind_buffers[i].buffer_type = MYSQL_TYPE_LONGLONG;
272
+ bind_buffers[i].buffer = xmalloc(sizeof(long long int));
273
+ *(LONG_LONG*)(bind_buffers[i].buffer) = rb_big2ll(argv[i]);
274
+ break;
275
+ case T_FLOAT:
276
+ bind_buffers[i].buffer_type = MYSQL_TYPE_DOUBLE;
277
+ bind_buffers[i].buffer = xmalloc(sizeof(double));
278
+ *(double*)(bind_buffers[i].buffer) = NUM2DBL(argv[i]);
279
+ break;
280
+ case T_STRING:
281
+ {
282
+ params_enc[i] = argv[i];
283
+ #ifdef HAVE_RUBY_ENCODING_H
284
+ params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
285
+ #endif
286
+ bind_buffers[i].buffer_type = MYSQL_TYPE_STRING;
287
+ bind_buffers[i].buffer = RSTRING_PTR(params_enc[i]);
288
+ bind_buffers[i].buffer_length = RSTRING_LEN(params_enc[i]);
289
+ length_buffers[i] = bind_buffers[i].buffer_length;
290
+ bind_buffers[i].length = &length_buffers[i];
291
+ }
292
+ break;
293
+ default:
294
+ // TODO: what Ruby type should support MYSQL_TYPE_TIME
295
+ if (CLASS_OF(argv[i]) == rb_cTime || CLASS_OF(argv[i]) == cDateTime) {
296
+ MYSQL_TIME t;
297
+ VALUE rb_time = argv[i];
298
+
299
+ bind_buffers[i].buffer_type = MYSQL_TYPE_DATETIME;
300
+ bind_buffers[i].buffer = xmalloc(sizeof(MYSQL_TIME));
301
+
302
+ memset(&t, 0, sizeof(MYSQL_TIME));
303
+ t.neg = 0;
304
+ t.second_part = FIX2INT(rb_funcall(rb_time, intern_usec, 0));
305
+ t.second = FIX2INT(rb_funcall(rb_time, intern_sec, 0));
306
+ t.minute = FIX2INT(rb_funcall(rb_time, intern_min, 0));
307
+ t.hour = FIX2INT(rb_funcall(rb_time, intern_hour, 0));
308
+ t.day = FIX2INT(rb_funcall(rb_time, intern_day, 0));
309
+ t.month = FIX2INT(rb_funcall(rb_time, intern_month, 0));
310
+ t.year = FIX2INT(rb_funcall(rb_time, intern_year, 0));
311
+
312
+ *(MYSQL_TIME*)(bind_buffers[i].buffer) = t;
313
+ } else if (CLASS_OF(argv[i]) == cDate) {
314
+ MYSQL_TIME t;
315
+ VALUE rb_time = argv[i];
316
+
317
+ bind_buffers[i].buffer_type = MYSQL_TYPE_DATE;
318
+ bind_buffers[i].buffer = xmalloc(sizeof(MYSQL_TIME));
319
+
320
+ memset(&t, 0, sizeof(MYSQL_TIME));
321
+ t.second_part = 0;
322
+ t.neg = 0;
323
+ t.day = FIX2INT(rb_funcall(rb_time, intern_day, 0));
324
+ t.month = FIX2INT(rb_funcall(rb_time, intern_month, 0));
325
+ t.year = FIX2INT(rb_funcall(rb_time, intern_year, 0));
326
+
327
+ *(MYSQL_TIME*)(bind_buffers[i].buffer) = t;
328
+ } else if (CLASS_OF(argv[i]) == cBigDecimal) {
329
+ bind_buffers[i].buffer_type = MYSQL_TYPE_NEWDECIMAL;
330
+ }
331
+ break;
332
+ }
333
+ }
334
+
335
+ // copies bind_buffers into internal storage
336
+ if (mysql_stmt_bind_param(stmt, bind_buffers)) {
337
+ FREE_BINDS;
338
+ rb_raise_mysql2_stmt_error(self);
339
+ }
340
+ }
341
+
342
+ if ((VALUE)rb_thread_call_without_gvl(nogvl_execute, stmt, RUBY_UBF_IO, 0) == Qfalse) {
343
+ FREE_BINDS;
344
+ rb_raise_mysql2_stmt_error(self);
345
+ }
346
+
347
+ FREE_BINDS;
348
+
349
+ metadata = mysql_stmt_result_metadata(stmt);
350
+ if (metadata == NULL) {
351
+ if (mysql_stmt_errno(stmt) != 0) {
352
+ // either CR_OUT_OF_MEMORY or CR_UNKNOWN_ERROR. both fatal.
353
+
354
+ MARK_CONN_INACTIVE(stmt_wrapper->client);
355
+ rb_raise_mysql2_stmt_error(self);
356
+ }
357
+ // no data and no error, so query was not a SELECT
358
+ return Qnil;
359
+ }
360
+
361
+ current = rb_hash_dup(rb_iv_get(stmt_wrapper->client, "@query_options"));
362
+ (void)RB_GC_GUARD(current);
363
+ Check_Type(current, T_HASH);
364
+
365
+ is_streaming = (Qtrue == rb_hash_aref(current, sym_stream));
366
+ if (!is_streaming) {
367
+ // recieve the whole result set from the server
368
+ if (rb_thread_call_without_gvl(nogvl_stmt_store_result, stmt, RUBY_UBF_IO, 0) == Qfalse) {
369
+ mysql_free_result(metadata);
370
+ rb_raise_mysql2_stmt_error(self);
371
+ }
372
+ MARK_CONN_INACTIVE(stmt_wrapper->client);
373
+ }
374
+
375
+ resultObj = rb_mysql_result_to_obj(stmt_wrapper->client, wrapper->encoding, current, metadata, self);
376
+
377
+ if (!is_streaming) {
378
+ // cache all result
379
+ rb_funcall(resultObj, intern_each, 0);
380
+ }
381
+
382
+ return resultObj;
383
+ }
384
+
385
+ /* call-seq: stmt.fields # => array
386
+ *
387
+ * Returns a list of fields that will be returned by this statement.
388
+ */
389
+ static VALUE fields(VALUE self) {
390
+ MYSQL_FIELD *fields;
391
+ MYSQL_RES *metadata;
392
+ unsigned int field_count;
393
+ unsigned int i;
394
+ VALUE field_list;
395
+ MYSQL_STMT* stmt;
396
+ #ifdef HAVE_RUBY_ENCODING_H
397
+ rb_encoding *default_internal_enc, *conn_enc;
398
+ #endif
399
+ GET_STATEMENT(self);
400
+ stmt = stmt_wrapper->stmt;
401
+
402
+ #ifdef HAVE_RUBY_ENCODING_H
403
+ default_internal_enc = rb_default_internal_encoding();
404
+ {
405
+ GET_CLIENT(stmt_wrapper->client);
406
+ conn_enc = rb_to_encoding(wrapper->encoding);
407
+ }
408
+ #endif
409
+
410
+ metadata = mysql_stmt_result_metadata(stmt);
411
+ fields = mysql_fetch_fields(metadata);
412
+ field_count = mysql_stmt_field_count(stmt);
413
+ field_list = rb_ary_new2((long)field_count);
414
+
415
+ for(i = 0; i < field_count; i++) {
416
+ VALUE rb_field;
417
+
418
+ rb_field = rb_str_new(fields[i].name, fields[i].name_length);
419
+ #ifdef HAVE_RUBY_ENCODING_H
420
+ rb_enc_associate(rb_field, conn_enc);
421
+ if (default_internal_enc) {
422
+ rb_field = rb_str_export_to_enc(rb_field, default_internal_enc);
423
+ }
424
+ #endif
425
+
426
+ rb_ary_store(field_list, (long)i, rb_field);
427
+ }
428
+
429
+ mysql_free_result(metadata);
430
+ return field_list;
431
+ }
432
+
433
+ void init_mysql2_statement() {
434
+ cMysql2Statement = rb_define_class_under(mMysql2, "Statement", rb_cObject);
435
+
436
+ rb_define_method(cMysql2Statement, "param_count", param_count, 0);
437
+ rb_define_method(cMysql2Statement, "field_count", field_count, 0);
438
+ rb_define_method(cMysql2Statement, "execute", execute, -1);
439
+ rb_define_method(cMysql2Statement, "fields", fields, 0);
440
+
441
+ sym_stream = ID2SYM(rb_intern("stream"));
442
+
443
+ intern_error_number_eql = rb_intern("error_number=");
444
+ intern_sql_state_eql = rb_intern("sql_state=");
445
+ intern_each = rb_intern("each");
446
+
447
+ intern_usec = rb_intern("usec");
448
+ intern_sec = rb_intern("sec");
449
+ intern_min = rb_intern("min");
450
+ intern_hour = rb_intern("hour");
451
+ intern_day = rb_intern("day");
452
+ intern_month = rb_intern("month");
453
+ intern_year = rb_intern("year");
454
+ }