ruby-dm 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/ext/result.c ADDED
@@ -0,0 +1,1052 @@
1
+ #include <dm_ext.h>
2
+
3
+ static rb_encoding *binaryEncoding;
4
+
5
+ /* on 64bit platforms we can handle dates way outside 2038-01-19T03:14:07
6
+ *
7
+ * (9999*31557600) + (12*2592000) + (31*86400) + (11*3600) + (59*60) + 59
8
+ */
9
+ #define dm_MAX_TIME 315578267999ULL
10
+
11
+ /* 0000-1-1 00:00:00 UTC
12
+ *
13
+ * (0*31557600) + (1*2592000) + (1*86400) + (0*3600) + (0*60) + 0
14
+ */
15
+ #define dm_MIN_TIME 2678400ULL
16
+
17
+ #define dm_MAX_BYTES_PER_CHAR 3
18
+
19
+ /* From dm documentations:
20
+ * To distinguish between binary and nonbinary data for string data types,
21
+ * check whether the charsetnr value is 63. If so, the character set is binary,
22
+ * which indicates binary rather than nonbinary data. This enables you to distinguish BINARY
23
+ * from CHAR, VARBINARY from VARCHAR, and the BLOB types from the TEXT types.
24
+ */
25
+ #define dm_BINARY_CHARSET 63
26
+
27
+ #ifndef DSQL_JSON
28
+ #define DSQL_JSON 245
29
+ #endif
30
+
31
+ #ifndef NEW_TYPEDDATA_WRAPPER
32
+ #define TypedData_Get_Struct(obj, type, ignore, sval) Data_Get_Struct(obj, type, sval)
33
+ #endif
34
+
35
+ #define GET_RESULT(self) \
36
+ dm_result_wrapper *wrapper; \
37
+ TypedData_Get_Struct(self, dm_result_wrapper, &rb_dm_result_type, wrapper);
38
+
39
+ typedef struct {
40
+ int symbolizeKeys;
41
+ int asArray;
42
+ int castBool;
43
+ int cacheRows;
44
+ int cast;
45
+ int streaming;
46
+ ID db_timezone;
47
+ ID app_timezone;
48
+ int block_given; /* boolean */
49
+ } result_each_args;
50
+
51
+ extern VALUE mdm, cdmClient, cdmError;
52
+ static VALUE cdmResult, cDateTime, cDate;
53
+ static VALUE opt_decimal_zero, opt_float_zero, opt_time_year, opt_time_month, opt_utc_offset;
54
+ static ID intern_new, intern_utc, intern_local, intern_localtime, intern_local_offset,
55
+ intern_civil, intern_new_offset, intern_merge, intern_BigDecimal,
56
+ intern_query_options;
57
+ static VALUE sym_symbolize_keys, sym_as, sym_array, sym_database_timezone,
58
+ sym_application_timezone, sym_local, sym_utc, sym_cast_booleans,
59
+ sym_cache_rows, sym_cast, sym_stream, sym_name;
60
+
61
+ struct nogvl_get_col_args
62
+ {
63
+ dm_result_wrapper *wrapper;
64
+ };
65
+
66
+ static void *nogvl_get_col_desc(void *ptr) {
67
+ struct nogvl_get_col_args* ptr1 = ptr;
68
+ dm_result_wrapper *wrapper = ptr1->wrapper;
69
+ DPIRETURN rt;
70
+ dhdesc hdesc_col;
71
+ int iParam;
72
+
73
+ rt = dpi_number_columns(wrapper->statement, &(wrapper->numberOfFields));
74
+ if(!DSQL_SUCCEEDED(rt))
75
+ return (void*)Qfalse;
76
+
77
+ rt = dpi_row_count(wrapper->statement, &(wrapper->numberOfRows));
78
+ if(!DSQL_SUCCEEDED(rt))
79
+ return (void*)Qfalse;
80
+
81
+ wrapper->lobs = (dhloblctr*)xcalloc(wrapper->numberOfFields, sizeof(dhloblctr));
82
+ wrapper->length = (slength*)xcalloc(wrapper->numberOfFields, sizeof(slength));
83
+ wrapper->col_desc = (DmColDesc*)xcalloc(wrapper->numberOfFields, sizeof(DmColDesc));
84
+ wrapper->result = (char**)xcalloc(wrapper->numberOfFields, sizeof(char*));
85
+
86
+ rt = dpi_get_stmt_attr(wrapper->statement, DSQL_ATTR_IMP_ROW_DESC,
87
+ (dpointer)&hdesc_col, 0, NULL);
88
+ if(!DSQL_SUCCEEDED(rt))
89
+ return (void*)Qfalse;
90
+
91
+ for (iParam = 0; iParam < wrapper->numberOfFields; iParam++)
92
+ {
93
+ rt = dpi_desc_column(wrapper->statement, (sdint2)iParam + 1, wrapper->col_desc[iParam].name,
94
+ sizeof(wrapper->col_desc[iParam].name), &wrapper->col_desc[iParam].name_length,
95
+ &wrapper->col_desc[iParam].sql_type, &wrapper->col_desc[iParam].prec, &wrapper->col_desc[iParam].scale,
96
+ &wrapper->col_desc[iParam].nullable);
97
+ if(!DSQL_SUCCEEDED(rt))
98
+ return (void*)Qfalse;
99
+ if (wrapper->col_desc[iParam].sql_type == DSQL_BLOB || wrapper->col_desc[iParam].sql_type == DSQL_CLOB)
100
+ {
101
+ rt = dpi_alloc_lob_locator(wrapper->statement, &(wrapper->lobs[iParam]));
102
+ if(!DSQL_SUCCEEDED(rt))
103
+ return (void*)Qfalse;
104
+ rt = dpi_bind_col(wrapper->statement, (udint2)iParam + 1, DSQL_C_LOB_HANDLE,
105
+ &(wrapper->lobs[iParam]),sizeof(wrapper->lobs[iParam]), &wrapper->length[iParam]);
106
+ }
107
+ else
108
+ {
109
+ rt = dpi_get_desc_field(
110
+ hdesc_col, (sdint2)iParam + 1, DSQL_DESC_DISPLAY_SIZE,
111
+ (dpointer)&(wrapper->col_desc[iParam].length), 0, NULL);
112
+ if(!DSQL_SUCCEEDED(rt))
113
+ return (void*)Qfalse;
114
+ wrapper->result[iParam] = (char*)xmalloc(wrapper->col_desc[iParam].length+2);
115
+ if(wrapper->col_desc[iParam].sql_type == DSQL_BINARY || wrapper->col_desc[iParam].sql_type == DSQL_VARBINARY)
116
+ {
117
+ rt = dpi_bind_col(wrapper->statement, (udint2)iParam + 1, DSQL_C_BINARY,
118
+ (dpointer)wrapper->result[iParam],
119
+ wrapper->col_desc[iParam].length + 1, &wrapper->length[iParam]);
120
+ }
121
+ else
122
+ {
123
+ rt = dpi_bind_col(wrapper->statement, (udint2)iParam + 1, DSQL_C_NCHAR,
124
+ (dpointer)wrapper->result[iParam],
125
+ wrapper->col_desc[iParam].length + 1, &wrapper->length[iParam]);
126
+ }
127
+ }
128
+ }
129
+ wrapper->is_bind = 1;
130
+ return (void*)Qtrue;
131
+ }
132
+
133
+ /* Mark any VALUEs that are only referenced in C, so the GC won't get them. */
134
+ static void rb_dm_result_mark(void * wrapper) {
135
+ dm_result_wrapper * w = wrapper;
136
+ if (w) {
137
+ rb_gc_mark_movable(w->fields);
138
+ rb_gc_mark_movable(w->rows);
139
+ rb_gc_mark_movable(w->encoding);
140
+ rb_gc_mark_movable(w->client);
141
+ }
142
+ }
143
+
144
+ /* this may be called manually or during GC */
145
+ static void rb_dm_result_free_result(dm_result_wrapper * wrapper) {
146
+ if (!wrapper) return;
147
+ int i = 0;
148
+ if (wrapper->resultFreed != 1 && wrapper->is_bind == 1)
149
+ {
150
+
151
+ for(i = 0; i < wrapper->numberOfFields; i++)
152
+ {
153
+ if(wrapper->col_desc[i].sql_type == DSQL_BLOB || wrapper->col_desc[i].sql_type == DSQL_CLOB)
154
+ dpi_free_lob_locator(wrapper->lobs[i]);
155
+ if(wrapper->result[i])
156
+ {
157
+ xfree(wrapper->result[i]);
158
+ wrapper->result[i] = NULL;
159
+ }
160
+ }
161
+
162
+ xfree(wrapper->result);
163
+ xfree(wrapper->length);
164
+ xfree(wrapper->lobs);
165
+ xfree(wrapper->col_desc);
166
+ /* FIXME: this may call flush_use_result, which can hit the socket */
167
+ /* For prepared statements, wrapper->result is the result metadata */
168
+ }
169
+ wrapper->resultFreed = 1;
170
+ }
171
+
172
+ /* this is called during GC */
173
+ static void rb_dm_result_free(void *ptr) {
174
+ dm_result_wrapper *wrapper = ptr;
175
+ if (wrapper->statement != NULL && wrapper->is_prepare == 0) {
176
+ dpi_free_stmt(wrapper->statement);
177
+ wrapper->statement = NULL;
178
+ }
179
+ rb_dm_result_free_result(wrapper);
180
+
181
+ // If the GC gets to client first it will be nil
182
+ if (wrapper->client != Qnil) {
183
+ decr_dm_client(wrapper->client_wrapper);
184
+ }
185
+
186
+ xfree(wrapper);
187
+ }
188
+
189
+ static size_t rb_dm_result_memsize(const void * wrapper) {
190
+ const dm_result_wrapper * w = wrapper;
191
+ size_t memsize = sizeof(*w);
192
+ if (w->client_wrapper) {
193
+ memsize += sizeof(*w->client_wrapper);
194
+ }
195
+ return memsize;
196
+ }
197
+
198
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
199
+ static void rb_dm_result_compact(void * wrapper) {
200
+ dm_result_wrapper * w = wrapper;
201
+ if (w) {
202
+ rb_dm_gc_location(w->fields);
203
+ rb_dm_gc_location(w->rows);
204
+ rb_dm_gc_location(w->encoding);
205
+ rb_dm_gc_location(w->client);
206
+ }
207
+ }
208
+ #endif
209
+
210
+ static const rb_data_type_t rb_dm_result_type = {
211
+ "rb_dm_result",
212
+ {
213
+ rb_dm_result_mark,
214
+ rb_dm_result_free,
215
+ rb_dm_result_memsize,
216
+ #ifdef HAVE_RB_GC_MARK_MOVABLE
217
+ rb_dm_result_compact,
218
+ #endif
219
+ },
220
+ 0,
221
+ 0,
222
+ #ifdef RUBY_TYPED_FREE_IMMEDIATELY
223
+ RUBY_TYPED_FREE_IMMEDIATELY,
224
+ #endif
225
+ };
226
+
227
+ static VALUE rb_dm_result_free_(VALUE self) {
228
+ GET_RESULT(self);
229
+
230
+ if (wrapper->statement != NULL && wrapper->is_prepare == 0) {
231
+ dpi_free_stmt(wrapper->statement);
232
+ wrapper->statement = NULL;
233
+ }
234
+
235
+ rb_dm_result_free_result(wrapper);
236
+ wrapper->resultFreed = 1;
237
+ return Qnil;
238
+ }
239
+
240
+ /*
241
+ * for small results, this won't hit the network, but there's no
242
+ * reliable way for us to tell this so we'll always release the GVL
243
+ * to be safe
244
+ */
245
+ static void* nogvl_stmt_fetch(void *ptr) {
246
+ dm_result_wrapper* wrapper = ptr;
247
+ DPIRETURN rt = DSQL_SUCCESS;
248
+ ulength row_num;
249
+ slength data_get = 0;
250
+ int iparam;
251
+ rt = dpi_fetch(wrapper->statement, &row_num);
252
+ if(rt == DSQL_NO_DATA || row_num == 0)
253
+ return (void*)Qnil;
254
+ if(!DSQL_SUCCEEDED(rt))
255
+ return (void*)Qfalse;
256
+ for(iparam = 0; iparam < wrapper->numberOfFields; iparam++)
257
+ {
258
+ if(wrapper->col_desc[iparam].sql_type == DSQL_BLOB || wrapper->col_desc[iparam].sql_type == DSQL_CLOB)
259
+ {
260
+ rt = dpi_lob_get_length((dhloblctr)(wrapper->lobs[iparam]), &wrapper->length[iparam]);
261
+ if(wrapper->result[iparam])
262
+ xfree(wrapper->result[iparam]);
263
+ if(!DSQL_SUCCEEDED(rt) || wrapper->length[iparam] == -1)
264
+ {
265
+ wrapper->result[iparam] = NULL;
266
+ continue;
267
+ }
268
+ if(wrapper->col_desc[iparam].sql_type == DSQL_CLOB)
269
+ wrapper->result[iparam] = xmalloc(wrapper->length[iparam] * 4 + 2);
270
+ else
271
+ wrapper->result[iparam] = xmalloc(wrapper->length[iparam] + 2);
272
+ if(wrapper->col_desc[iparam].sql_type == DSQL_BLOB)
273
+ {
274
+ rt = dpi_lob_read((dhloblctr)(wrapper->lobs[iparam]), 1, DSQL_C_BINARY,
275
+ 0, wrapper->result[iparam], wrapper->length[iparam] + 1, NULL);
276
+ }
277
+ else
278
+ {
279
+ rt = dpi_lob_read((dhloblctr)(wrapper->lobs[iparam]), 1, DSQL_C_NCHAR,
280
+ 0, wrapper->result[iparam], wrapper->length[iparam] * 4 + 1, &data_get);
281
+ }
282
+ }
283
+ }
284
+
285
+ return (void*)Qtrue;
286
+ }
287
+
288
+ static VALUE rb_dm_result_fetch_field(VALUE self, unsigned int idx, int symbolize_keys) {
289
+ VALUE rb_field;
290
+ VALUE rt;
291
+ GET_RESULT(self);
292
+ if(wrapper->is_bind == 0)
293
+ {
294
+ struct nogvl_get_col_args args;
295
+ args.wrapper = wrapper;
296
+ rt = (VALUE)rb_thread_call_without_gvl(nogvl_get_col_desc, &args, RUBY_UBF_IO, 0);
297
+ if (rt == Qfalse)
298
+ rb_raise(cdmError, "failed to get col_desc");
299
+ }
300
+
301
+ if (wrapper->fields == Qnil) {
302
+ wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
303
+ }
304
+
305
+ rb_field = rb_ary_entry(wrapper->fields, idx);
306
+ if (rb_field == Qnil) {
307
+ DmColDesc field;
308
+ rb_encoding *default_internal_enc = rb_default_internal_encoding();
309
+ rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
310
+
311
+ field = wrapper->col_desc[idx];
312
+ if (symbolize_keys) {
313
+ rb_field = rb_intern3(field.name, field.name_length, rb_utf8_encoding());
314
+ rb_field = ID2SYM(rb_field);
315
+ } else {
316
+ #ifdef HAVE_RB_ENC_INTERNED_STR
317
+ rb_field = rb_enc_interned_str(field.name, field.name_length, conn_enc);
318
+ if (default_internal_enc && default_internal_enc != conn_enc) {
319
+ rb_field = rb_str_to_interned_str(rb_str_export_to_enc(rb_field, default_internal_enc));
320
+ }
321
+ #else
322
+ rb_field = rb_enc_str_new(field.name, field.name_length, conn_enc);
323
+ if (default_internal_enc && default_internal_enc != conn_enc) {
324
+ rb_field = rb_str_export_to_enc(rb_field, default_internal_enc);
325
+ }
326
+ rb_obj_freeze(rb_field);
327
+ #endif
328
+ }
329
+ rb_ary_store(wrapper->fields, idx, rb_field);
330
+ }
331
+
332
+ return rb_field;
333
+ }
334
+
335
+ static VALUE rb_dm_result_fetch_field_type(VALUE self, unsigned int idx) {
336
+ VALUE rb_field_type;
337
+ VALUE rv;
338
+ GET_RESULT(self);
339
+ struct nogvl_get_col_args args;
340
+ args.wrapper = wrapper;
341
+
342
+ if(wrapper->is_bind == 0)
343
+ {
344
+ struct nogvl_get_col_args args;
345
+ args.wrapper = wrapper;
346
+ rv = (VALUE)rb_thread_call_without_gvl(nogvl_get_col_desc, &args, RUBY_UBF_IO, 0);
347
+ if (rv == Qfalse)
348
+ rb_raise(cdmError, "failed to get col_desc");
349
+ }
350
+
351
+ if (wrapper->fieldTypes == Qnil) {
352
+ wrapper->fieldTypes = rb_ary_new2(wrapper->numberOfFields);
353
+ }
354
+
355
+ rb_field_type = rb_ary_entry(wrapper->fieldTypes, idx);
356
+ if (rb_field_type == Qnil) {
357
+ DmColDesc field ;
358
+ rb_encoding *default_internal_enc = rb_default_internal_encoding();
359
+ rb_encoding *conn_enc = rb_to_encoding(wrapper->encoding);
360
+ int precision;
361
+
362
+ field = wrapper->col_desc[idx];
363
+
364
+ switch(field.sql_type) {
365
+ case DSQL_TINYINT: // signed char
366
+ rb_field_type = rb_sprintf("tinyint(%ld)", field.length);
367
+ break;
368
+ case DSQL_SMALLINT: // short int
369
+ rb_field_type = rb_sprintf("smallint(%ld)", field.length);
370
+ break;
371
+ case DSQL_INTERVAL_YEAR: // short int
372
+ rb_field_type = rb_str_new_cstr("year");
373
+ break;
374
+ case DSQL_INTERVAL_MONTH: // short int
375
+ rb_field_type = rb_str_new_cstr("month");
376
+ break;
377
+ case DSQL_INTERVAL_DAY: // short int
378
+ rb_field_type = rb_str_new_cstr("day");
379
+ break;
380
+ case DSQL_INTERVAL_HOUR: // short int
381
+ rb_field_type = rb_str_new_cstr("hour");
382
+ break;
383
+ case DSQL_INTERVAL_MINUTE: // short int
384
+ rb_field_type = rb_str_new_cstr("minute");
385
+ break;
386
+ case DSQL_INTERVAL_SECOND: // short int
387
+ rb_field_type = rb_str_new_cstr("second");
388
+ break;
389
+ case DSQL_INTERVAL_YEAR_TO_MONTH: // short int
390
+ rb_field_type = rb_str_new_cstr("year to month");
391
+ break;
392
+ case DSQL_INTERVAL_DAY_TO_HOUR: // short int
393
+ rb_field_type = rb_str_new_cstr("day to hour");
394
+ break;
395
+ case DSQL_INTERVAL_DAY_TO_MINUTE: // short int
396
+ rb_field_type = rb_str_new_cstr("day to minute");
397
+ break;
398
+ case DSQL_INTERVAL_DAY_TO_SECOND: // short int
399
+ rb_field_type = rb_str_new_cstr("day to second");
400
+ break;
401
+ case DSQL_INTERVAL_HOUR_TO_MINUTE: // short int
402
+ rb_field_type = rb_str_new_cstr("hour to minute");
403
+ break;
404
+ case DSQL_INTERVAL_HOUR_TO_SECOND: // short int
405
+ rb_field_type = rb_str_new_cstr("hour to second");
406
+ break;
407
+ case DSQL_INTERVAL_MINUTE_TO_SECOND: // short int
408
+ rb_field_type = rb_str_new_cstr("minute to second");
409
+ break;
410
+ case DSQL_INT: // int
411
+ rb_field_type = rb_sprintf("int(%ld)", field.length);
412
+ break;
413
+ case DSQL_BIGINT: // long long int
414
+ rb_field_type = rb_sprintf("bigint(%ld)", field.length);
415
+ break;
416
+ case DSQL_FLOAT: // float
417
+ rb_field_type = rb_sprintf("float(%ld,%d)", field.prec, field.scale);
418
+ break;
419
+ case DSQL_DOUBLE: // double
420
+ rb_field_type = rb_sprintf("double(%ld,%d)", field.prec, field.scale);
421
+ break;
422
+ case DSQL_TIME: // dm_TIME
423
+ rb_field_type = rb_str_new_cstr("time");
424
+ break;
425
+ case DSQL_DATE: // dm_TIME
426
+ rb_field_type = rb_str_new_cstr("date");
427
+ break;
428
+ case DSQL_TIMESTAMP: // dm_TIME
429
+ rb_field_type = rb_str_new_cstr("timestamp");
430
+ break;
431
+ case DSQL_TIME_TZ: // dm_TIME
432
+ rb_field_type = rb_str_new_cstr("time with time zone");
433
+ break;
434
+ case DSQL_TIMESTAMP_TZ: // dm_TIME
435
+ rb_field_type = rb_str_new_cstr("timestamp with time zone");
436
+ break;
437
+ case DSQL_DEC: // char[]
438
+ rb_field_type = rb_sprintf("decimal(%d,%d)", field.prec, field.scale);
439
+ break;
440
+ case DSQL_BINARY: // char[]
441
+ rb_field_type = rb_str_new_cstr("binary");
442
+ break;
443
+ case DSQL_VARBINARY: // char[]
444
+ rb_field_type = rb_str_new_cstr("varbinary");
445
+ break;
446
+ case DSQL_VARCHAR: // char[]
447
+ rb_field_type = rb_sprintf("varchar(%ld)", field.length);
448
+ break;
449
+ case DSQL_CHAR: // char[]
450
+ rb_field_type = rb_sprintf("char(%ld)", field.length);
451
+ break;
452
+ case DSQL_BLOB: // char[]
453
+ rb_field_type = rb_str_new_cstr("blob");
454
+ break;
455
+ case DSQL_CLOB: // char[]
456
+ rb_field_type = rb_str_new_cstr("clob");
457
+ break;
458
+ case DSQL_BIT: // char[]
459
+ rb_field_type = rb_sprintf("bit(%ld)", field.length);
460
+ break;
461
+ case DSQL_ROWID: // char[]
462
+ rb_field_type = rb_str_new_cstr("rowid");
463
+ break;
464
+ default:
465
+ rb_field_type = rb_str_new_cstr("unknown");
466
+ break;
467
+ }
468
+
469
+ rb_enc_associate(rb_field_type, conn_enc);
470
+ if (default_internal_enc) {
471
+ rb_field_type = rb_str_export_to_enc(rb_field_type, default_internal_enc);
472
+ }
473
+
474
+ rb_ary_store(wrapper->fieldTypes, idx, rb_field_type);
475
+ }
476
+
477
+ return rb_field_type;
478
+ }
479
+
480
+ static VALUE dm_set_field_string_encoding(VALUE val, rb_encoding *default_internal_enc, rb_encoding *conn_enc)
481
+ {
482
+ rb_enc_associate(val, conn_enc);
483
+
484
+ if (default_internal_enc) {
485
+ val = rb_str_export_to_enc(val, default_internal_enc);
486
+ }
487
+
488
+ return val;
489
+ }
490
+
491
+ /* Interpret microseconds digits left-aligned in fixed-width field.
492
+ * e.g. 10.123 seconds means 10 seconds and 123000 microseconds,
493
+ * because the microseconds are to the right of the decimal point.
494
+ */
495
+ static unsigned int msec_char_to_uint(char *msec_char, size_t len)
496
+ {
497
+ size_t i;
498
+ for (i = 0; i < (len - 1); i++) {
499
+ if (msec_char[i] == '\0') {
500
+ msec_char[i] = '0';
501
+ }
502
+ }
503
+ return (unsigned int)strtoul(msec_char, NULL, 10);
504
+ }
505
+
506
+ static VALUE rb_dm_result_fetch_row(VALUE self,const result_each_args *args)
507
+ {
508
+ VALUE rowVal;
509
+ char** row;
510
+ unsigned int i = 0;
511
+ slength * fieldLengths;
512
+ void * ptr;
513
+ rb_encoding *default_internal_enc;
514
+ rb_encoding *conn_enc;
515
+ VALUE rv;
516
+ GET_RESULT(self);
517
+
518
+ default_internal_enc = rb_default_internal_encoding();
519
+ conn_enc = rb_to_encoding(wrapper->encoding);
520
+
521
+ if(wrapper->is_bind == 0)
522
+ {
523
+ struct nogvl_get_col_args args;
524
+ args.wrapper = wrapper;
525
+ rv = (VALUE)rb_thread_call_without_gvl(nogvl_get_col_desc, &args, RUBY_UBF_IO, 0);
526
+ if (rv == Qfalse)
527
+ rb_raise(cdmError, "failed to get col_desc");
528
+ }
529
+
530
+ ptr = wrapper->result;
531
+ {
532
+ rv = (VALUE)rb_thread_call_without_gvl(nogvl_stmt_fetch, wrapper, RUBY_UBF_IO, 0);
533
+ if(rv == Qnil)
534
+ return Qnil;
535
+ else if(rv == Qfalse)
536
+ rb_raise(cdmError, "failed to fetch row");
537
+ }
538
+
539
+ row = wrapper->result;
540
+
541
+ if (wrapper->fields == Qnil) {
542
+ wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
543
+ }
544
+ if (args->asArray) {
545
+ rowVal = rb_ary_new2(wrapper->numberOfFields);
546
+ } else {
547
+ rowVal = rb_hash_new();
548
+ }
549
+ fieldLengths = wrapper->length;
550
+
551
+ for (i = 0; i < wrapper->numberOfFields; i++) {
552
+ VALUE field = rb_dm_result_fetch_field(self, i, args->symbolizeKeys);
553
+ if (row[i] && fieldLengths[i]>0) {
554
+ VALUE val = Qnil;
555
+ sdint2 type = wrapper->col_desc[i].sql_type;
556
+
557
+ if (!args->cast) {
558
+ val = rb_str_new(row[i], fieldLengths[i]);
559
+ val = dm_set_field_string_encoding(val, default_internal_enc, conn_enc);
560
+ } else {
561
+ switch(type) {
562
+ case DSQL_BIT:
563
+ if (args->castBool && fieldLengths[i] == 1) {
564
+ val = *row[i] == '1' ? Qtrue : Qfalse;
565
+ }else{
566
+ val = rb_str_new(row[i], fieldLengths[i]);
567
+ }
568
+ break;
569
+ case DSQL_TINYINT: /* TINYINT field */
570
+ if (args->castBool && fieldLengths[i] == 1) {
571
+ val = *row[i] != '0' ? Qtrue : Qfalse;
572
+ break;
573
+ }
574
+ case DSQL_SMALLINT: /* SMALLINT field */
575
+ case DSQL_INT: /* INTEGER field */
576
+ case DSQL_BIGINT: /* BIGINT field */
577
+ val = rb_cstr2inum(row[i], 10);
578
+ break;
579
+ case DSQL_DEC: /* DECIMAL or NUMERIC field */
580
+ if (wrapper->col_desc[i].scale == 0) {
581
+ val = rb_cstr2inum(row[i], 10);
582
+ } else if (strtod(row[i], NULL) == 0.000000){
583
+ val = rb_funcall(rb_mKernel, intern_BigDecimal, 1, opt_decimal_zero);
584
+ }else{
585
+ val = rb_funcall(rb_mKernel, intern_BigDecimal, 1, rb_str_new(row[i], fieldLengths[i]));
586
+ }
587
+ break;
588
+ case DSQL_FLOAT: /* FLOAT field */
589
+ case DSQL_DOUBLE: { /* DOUBLE or REAL field */
590
+ double column_to_double;
591
+ column_to_double = strtod(row[i], NULL);
592
+ if (column_to_double == 0.000000){
593
+ val = opt_float_zero;
594
+ }else{
595
+ val = rb_float_new(column_to_double);
596
+ }
597
+ break;
598
+ }
599
+ case DSQL_TIME: { /* TIME field */
600
+ int tokens;
601
+ unsigned int hour=0, min=0, sec=0, msec=0;
602
+ char msec_char[7] = {'0','0','0','0','0','0','\0'};
603
+
604
+ tokens = sscanf(row[i], "%2u:%2u:%2u.%6s", &hour, &min, &sec, msec_char);
605
+ if (tokens < 3) {
606
+ val = rb_str_new(row[i], fieldLengths[i]);
607
+ val = dm_set_field_string_encoding(val, default_internal_enc, conn_enc);
608
+ break;
609
+ }
610
+ msec = msec_char_to_uint(msec_char, sizeof(msec_char));
611
+ val = rb_funcall(rb_cTime, args->db_timezone, 7, opt_time_year, opt_time_month, opt_time_month, UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec));
612
+ if (!NIL_P(args->app_timezone)) {
613
+ if (args->app_timezone == intern_local) {
614
+ val = rb_funcall(val, intern_localtime, 0);
615
+ } else { /* utc */
616
+ val = rb_funcall(val, intern_utc, 0);
617
+ }
618
+ }
619
+ break;
620
+ }
621
+ case DSQL_TIMESTAMP: {/* TIMESTAMP field */
622
+ int tokens;
623
+ unsigned int year=0, month=0, day=0, hour=0, min=0, sec=0, msec=0;
624
+ char msec_char[7] = {'0','0','0','0','0','0','\0'};
625
+ uint64_t seconds;
626
+
627
+ tokens = sscanf(row[i], "%4u-%2u-%2u %2u:%2u:%2u.%6s", &year, &month, &day, &hour, &min, &sec, msec_char);
628
+ if (tokens < 6) { /* msec might be empty */
629
+ val = rb_str_new(row[i], fieldLengths[i]);
630
+ val = dm_set_field_string_encoding(val, default_internal_enc, conn_enc);
631
+ break;
632
+ }
633
+ seconds = (year*31557600ULL) + (month*2592000ULL) + (day*86400ULL) + (hour*3600ULL) + (min*60ULL) + sec;
634
+
635
+ if (seconds == 0) {
636
+ val = Qnil;
637
+ } else {
638
+ if (month < 1 || day < 1) {
639
+ rb_raise(cdmError, "Invalid date in field '%.*s': %s", wrapper->col_desc[i].name_length, wrapper->col_desc[i].name, row[i]);
640
+ val = Qnil;
641
+ } else {
642
+ if (seconds < dm_MIN_TIME || seconds > dm_MAX_TIME) { /* use DateTime for larger date range, does not support microseconds */
643
+ VALUE offset = INT2NUM(0);
644
+ if (args->db_timezone == intern_local) {
645
+ offset = rb_funcall(cdmClient, intern_local_offset, 0);
646
+ }
647
+ val = rb_funcall(cDateTime, intern_civil, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), offset);
648
+ if (!NIL_P(args->app_timezone)) {
649
+ if (args->app_timezone == intern_local) {
650
+ offset = rb_funcall(cdmClient, intern_local_offset, 0);
651
+ val = rb_funcall(val, intern_new_offset, 1, offset);
652
+ } else { /* utc */
653
+ val = rb_funcall(val, intern_new_offset, 1, opt_utc_offset);
654
+ }
655
+ }
656
+ } else {
657
+ msec = msec_char_to_uint(msec_char, sizeof(msec_char));
658
+ val = rb_funcall(rb_cTime, args->db_timezone, 7, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day), UINT2NUM(hour), UINT2NUM(min), UINT2NUM(sec), UINT2NUM(msec));
659
+ if (!NIL_P(args->app_timezone)) {
660
+ if (args->app_timezone == intern_local) {
661
+ val = rb_funcall(val, intern_localtime, 0);
662
+ } else { /* utc */
663
+ val = rb_funcall(val, intern_utc, 0);
664
+ }
665
+ }
666
+ }
667
+ }
668
+ }
669
+ break;
670
+ }
671
+ case DSQL_DATE:{ /* DATE field */
672
+ int tokens;
673
+ unsigned int year=0, month=0, day=0;
674
+ tokens = sscanf(row[i], "%4u-%2u-%2u", &year, &month, &day);
675
+ if (tokens < 3) {
676
+ val = rb_str_new(row[i], fieldLengths[i]);
677
+ val = dm_set_field_string_encoding(val, default_internal_enc, conn_enc);
678
+ break;
679
+ }
680
+ if (year+month+day == 0) {
681
+ val = Qnil;
682
+ } else {
683
+ if (month < 1 || day < 1) {
684
+ rb_raise(cdmError, "Invalid date in field '%.*s': %s", wrapper->col_desc[i].name_length, wrapper->col_desc[i].name, row[i]);
685
+ val = Qnil;
686
+ } else {
687
+ val = rb_funcall(cDate, intern_new, 3, UINT2NUM(year), UINT2NUM(month), UINT2NUM(day));
688
+ }
689
+ }
690
+ break;
691
+ }
692
+ case DSQL_BLOB:
693
+ case DSQL_VARCHAR:
694
+ default:
695
+ val = rb_str_new(row[i], strlen(row[i]));
696
+ val = dm_set_field_string_encoding(val, default_internal_enc, conn_enc);
697
+ break;
698
+ }
699
+ }
700
+ if (args->asArray) {
701
+ rb_ary_push(rowVal, val);
702
+ } else {
703
+ rb_hash_aset(rowVal, field, val);
704
+ }
705
+ } else {
706
+ if (args->asArray) {
707
+ rb_ary_push(rowVal, Qnil);
708
+ } else {
709
+ rb_hash_aset(rowVal, field, Qnil);
710
+ }
711
+ }
712
+ }
713
+ return rowVal;
714
+ }
715
+
716
+ static VALUE rb_dm_result_fetch_fields(VALUE self) {
717
+ unsigned int i = 0;
718
+ short int symbolizeKeys = 0;
719
+ VALUE rv;
720
+
721
+ GET_RESULT(self);
722
+
723
+ if(wrapper->resultFreed)
724
+ {
725
+ rb_raise(cdmError, "result is freed");
726
+ }
727
+
728
+ if(wrapper->is_bind == 0)
729
+ {
730
+ struct nogvl_get_col_args args;
731
+ args.wrapper = wrapper;
732
+ rv = (VALUE)rb_thread_call_without_gvl(nogvl_get_col_desc, &args, RUBY_UBF_IO, 0);
733
+ if (rv == Qfalse)
734
+ rb_raise(cdmError, "failed to get col_desc");
735
+ }
736
+
737
+ if (wrapper->fields == Qnil) {
738
+ wrapper->fields = rb_ary_new2(wrapper->numberOfFields);
739
+ }
740
+
741
+ if (RARRAY_LEN(wrapper->fields) != wrapper->numberOfFields) {
742
+ for (i=0; i<wrapper->numberOfFields; i++) {
743
+ rb_dm_result_fetch_field(self, i, symbolizeKeys);
744
+ }
745
+ }
746
+
747
+ return wrapper->fields;
748
+ }
749
+
750
+ static VALUE rb_dm_result_fetch_field_types(VALUE self) {
751
+ unsigned int i = 0;
752
+ VALUE rv;
753
+ GET_RESULT(self);
754
+
755
+ if(wrapper->resultFreed)
756
+ {
757
+ rb_raise(cdmError, "result is freed");
758
+ }
759
+
760
+ if(wrapper->is_bind == 0)
761
+ {
762
+ struct nogvl_get_col_args args;
763
+ args.wrapper = wrapper;
764
+ rv = (VALUE)rb_thread_call_without_gvl(nogvl_get_col_desc, &args, RUBY_UBF_IO, 0);
765
+ if (rv == Qfalse)
766
+ rb_raise(cdmError, "failed to get col_desc");
767
+ }
768
+
769
+ if (wrapper->fieldTypes == Qnil) {
770
+ wrapper->fieldTypes = rb_ary_new2(wrapper->numberOfFields);
771
+ }
772
+
773
+ if (RARRAY_LEN(wrapper->fieldTypes) != wrapper->numberOfFields) {
774
+ for (i=0; i<wrapper->numberOfFields; i++) {
775
+ rb_dm_result_fetch_field_type(self, i);
776
+ }
777
+ }
778
+
779
+ return wrapper->fieldTypes;
780
+ }
781
+
782
+ static VALUE rb_dm_result_each_(VALUE self,
783
+ VALUE(*fetch_row_func)(VALUE, const result_each_args *args),
784
+ const result_each_args *args)
785
+ {
786
+ unsigned long i;
787
+ const char *errstr;
788
+
789
+ GET_RESULT(self);
790
+
791
+ if (args->cacheRows && wrapper->lastRowProcessed == wrapper->numberOfRows) {
792
+ /* we've already read the entire dataset from the C result into our */
793
+ /* internal array. Lets hand that over to the user since it's ready to go */
794
+ for (i = 0; i < wrapper->numberOfRows; i++) {
795
+ rb_yield(rb_ary_entry(wrapper->rows, i));
796
+ }
797
+ } else {
798
+ unsigned long rowsProcessed = 0;
799
+ rowsProcessed = RARRAY_LEN(wrapper->rows);
800
+
801
+ for (i = 0; i < wrapper->numberOfRows; i++) {
802
+ VALUE row;
803
+ if (args->cacheRows && i < rowsProcessed) {
804
+ row = rb_ary_entry(wrapper->rows, i);
805
+ } else {
806
+ row = fetch_row_func(self, args);
807
+ if (args->cacheRows) {
808
+ rb_ary_store(wrapper->rows, i, row);
809
+ }
810
+ wrapper->lastRowProcessed++;
811
+ }
812
+
813
+ if (row == Qnil) {
814
+ /* we don't need the dm C dataset around anymore, peace it */
815
+ return Qnil;
816
+ }
817
+
818
+ if (args->block_given) {
819
+ rb_yield(row);
820
+ }
821
+ }
822
+ }
823
+
824
+ // FIXME return Enumerator instead?
825
+ // return rb_ary_each(wrapper->rows);
826
+ return wrapper->rows;
827
+ }
828
+
829
+ static VALUE rb_dm_result_each(int argc, VALUE * argv, VALUE self) {
830
+ result_each_args args;
831
+ VALUE rv;
832
+ VALUE defaults, opts, (*fetch_row_func)(VALUE, const result_each_args *args);
833
+ ID db_timezone, app_timezone, dbTz, appTz;
834
+ int symbolizeKeys, asArray, castBool, cacheRows, cast;
835
+
836
+ GET_RESULT(self);
837
+
838
+ if(wrapper->resultFreed)
839
+ {
840
+ rb_raise(cdmError, "result is freed");
841
+ }
842
+
843
+ if (!wrapper->statement) {
844
+ rb_raise(cdmError, "Statement handle already closed");
845
+ }
846
+
847
+ if(wrapper->is_bind == 0)
848
+ {
849
+ struct nogvl_get_col_args args;
850
+ args.wrapper = wrapper;
851
+ rv = (VALUE)rb_thread_call_without_gvl(nogvl_get_col_desc, &args, RUBY_UBF_IO, 0);
852
+ if (rv == Qfalse)
853
+ rb_raise(cdmError, "failed to get col_desc");
854
+ }
855
+ defaults = rb_ivar_get(self, intern_query_options);
856
+ // A block can be passed to this method, but since we don't call the block directly from C,
857
+ // we don't need to capture it into a variable here with the "&" scan arg.
858
+ if (rb_scan_args(argc, argv, "01", &opts) == 1)
859
+ {
860
+ opts = rb_funcall(defaults, intern_merge, 1, opts);
861
+ symbolizeKeys = RTEST(rb_hash_aref(opts, sym_symbolize_keys));
862
+ asArray = rb_hash_aref(opts, sym_as) == sym_array;
863
+ castBool = RTEST(rb_hash_aref(opts, sym_cast_booleans));
864
+ cacheRows = RTEST(rb_hash_aref(opts, sym_cache_rows));
865
+ cast = RTEST(rb_hash_aref(opts, sym_cast));
866
+ }
867
+ else if(defaults != Qnil)
868
+ {
869
+ opts = defaults;
870
+ symbolizeKeys = RTEST(rb_hash_aref(opts, sym_symbolize_keys));
871
+ asArray = rb_hash_aref(opts, sym_as) == sym_array;
872
+ castBool = RTEST(rb_hash_aref(opts, sym_cast_booleans));
873
+ cacheRows = 1;
874
+ cast = 1;
875
+ }
876
+ else
877
+ {
878
+
879
+ symbolizeKeys = 0;
880
+ asArray = 0;
881
+ castBool = 0;
882
+ cacheRows = 1;
883
+ cast = 1;
884
+ }
885
+
886
+ db_timezone = intern_local;
887
+ app_timezone = Qnil;
888
+
889
+ if (wrapper->rows == Qnil ) {
890
+ wrapper->rows = rb_ary_new2(wrapper->numberOfRows);
891
+ }
892
+
893
+ // Backward compat
894
+ args.symbolizeKeys = symbolizeKeys;
895
+ args.asArray = asArray;
896
+ args.castBool = castBool;
897
+ args.cacheRows = cacheRows;
898
+ args.cast = cast;
899
+ args.db_timezone = db_timezone;
900
+ args.app_timezone = app_timezone;
901
+ args.block_given = rb_block_given_p();
902
+
903
+
904
+ fetch_row_func = rb_dm_result_fetch_row;
905
+
906
+ return rb_dm_result_each_(self, fetch_row_func, &args);
907
+ }
908
+
909
+ static VALUE rb_dm_result_count(VALUE self) {
910
+ GET_RESULT(self);
911
+ VALUE rt;
912
+ if(wrapper->resultFreed)
913
+ {
914
+ rb_raise(cdmError, "result is freed");
915
+ }
916
+ if(wrapper->is_bind == 0)
917
+ {
918
+ struct nogvl_get_col_args args;
919
+ args.wrapper = wrapper;
920
+ rt = (VALUE)rb_thread_call_without_gvl(nogvl_get_col_desc, &args, RUBY_UBF_IO, 0);
921
+ if (rt == Qfalse)
922
+ rb_raise(cdmError, "failed to get col_desc");
923
+ }
924
+
925
+ return ULONG2NUM(wrapper->numberOfRows);
926
+ }
927
+
928
+ static void *nogvl_get_next_result(void *ptr) {
929
+ dm_result_wrapper *wrapper = ptr;
930
+ DPIRETURN rt;
931
+ rt = dpi_more_results(wrapper->statement);
932
+ if(rt == DSQL_NO_DATA)
933
+ return (void*)Qnil;
934
+ else if(!DSQL_SUCCEEDED(rt))
935
+ return (void*)Qfalse;
936
+ else
937
+ return (void*)Qtrue;
938
+
939
+ }
940
+
941
+ static VALUE rb_dm_next_result(VALUE self) {
942
+ GET_RESULT(self);
943
+ VALUE rt,defaults,obj;
944
+
945
+ if(wrapper->resultFreed)
946
+ {
947
+ rb_raise(cdmError, "result is freed");
948
+ }
949
+
950
+ rt = (VALUE)rb_thread_call_without_gvl(nogvl_get_next_result, wrapper, RUBY_UBF_IO, 0);
951
+ if(rt == Qnil)
952
+ return Qnil;
953
+ else if(rt == Qfalse)
954
+ rb_raise(cdmError, "failed to get next result");
955
+
956
+ defaults = rb_ivar_get(self, intern_query_options);
957
+ obj = rb_dm_result_to_obj(wrapper->client, wrapper->encoding, wrapper->statement, 1, defaults);
958
+ return obj;
959
+ }
960
+
961
+ /* dm::Result */
962
+ VALUE rb_dm_result_to_obj(VALUE client, VALUE encoding, dhstmt statement, int flag, VALUE options) {
963
+ VALUE obj;
964
+ dm_result_wrapper * wrapper;
965
+ DPIRETURN rt;
966
+
967
+ #ifdef NEW_TYPEDDATA_WRAPPER
968
+ obj = TypedData_Make_Struct(cdmResult, dm_result_wrapper, &rb_dm_result_type, wrapper);
969
+ #else
970
+ obj = Data_Make_Struct(cdmResult, dm_result_wrapper, rb_dm_result_mark, rb_dm_result_free, wrapper);
971
+ #endif
972
+ wrapper->numberOfFields = 0;
973
+ wrapper->numberOfRows = 0;
974
+ wrapper->resultFreed = 0;
975
+ wrapper->fields = Qnil;
976
+ wrapper->fieldTypes = Qnil;
977
+ wrapper->rows = Qnil;
978
+ wrapper->encoding = encoding;
979
+ wrapper->client = client;
980
+ wrapper->client_wrapper = DATA_PTR(client);
981
+ wrapper->client_wrapper->refcount++;
982
+ wrapper->result = NULL;
983
+ wrapper->length = NULL;
984
+ wrapper->lobs = NULL;
985
+ wrapper->col_desc = NULL;
986
+ wrapper->is_bind = 0;
987
+ /* Keep a handle to the Statement to ensure it doesn't get garbage collected first */
988
+ wrapper->statement = statement;
989
+ wrapper->is_prepare = flag;
990
+
991
+ rb_obj_call_init(obj, 0, NULL);
992
+ rb_ivar_set(obj, intern_query_options, options);
993
+
994
+ /* Options that cannot be changed in results.each(...) { |row| }
995
+ * should be processed here. */
996
+
997
+ return obj;
998
+ }
999
+
1000
+ void init_dm_result() {
1001
+ cDate = rb_const_get(rb_cObject, rb_intern("Date"));
1002
+ rb_global_variable(&cDate);
1003
+ cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
1004
+ rb_global_variable(&cDateTime);
1005
+
1006
+ cdmResult = rb_define_class_under(mdm, "Result", rb_cObject);
1007
+ rb_undef_alloc_func(cdmResult);
1008
+ rb_global_variable(&cdmResult);
1009
+
1010
+ rb_define_method(cdmResult, "each", rb_dm_result_each, -1);
1011
+ rb_define_method(cdmResult, "fields", rb_dm_result_fetch_fields, 0);
1012
+ rb_define_method(cdmResult, "field_types", rb_dm_result_fetch_field_types, 0);
1013
+ rb_define_method(cdmResult, "free", rb_dm_result_free_, 0);
1014
+ rb_define_method(cdmResult, "count", rb_dm_result_count, 0);
1015
+ rb_define_alias(cdmResult, "size", "count");
1016
+ rb_define_method(cdmResult, "next_result", rb_dm_next_result, 0);
1017
+
1018
+ intern_new = rb_intern("new");
1019
+ intern_utc = rb_intern("utc");
1020
+ intern_local = rb_intern("local");
1021
+ intern_merge = rb_intern("merge");
1022
+ intern_localtime = rb_intern("localtime");
1023
+
1024
+ intern_local_offset = rb_intern("local_offset");
1025
+ intern_civil = rb_intern("civil");
1026
+ intern_new_offset = rb_intern("new_offset");
1027
+ intern_BigDecimal = rb_intern("BigDecimal");
1028
+ intern_query_options = rb_intern("@query_options");
1029
+
1030
+ sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys"));
1031
+ sym_as = ID2SYM(rb_intern("as"));
1032
+ sym_array = ID2SYM(rb_intern("array"));
1033
+ sym_local = ID2SYM(rb_intern("local"));
1034
+ sym_utc = ID2SYM(rb_intern("utc"));
1035
+ sym_cast_booleans = ID2SYM(rb_intern("cast_booleans"));
1036
+ sym_database_timezone = ID2SYM(rb_intern("database_timezone"));
1037
+ sym_application_timezone = ID2SYM(rb_intern("application_timezone"));
1038
+ sym_cache_rows = ID2SYM(rb_intern("cache_rows"));
1039
+ sym_cast = ID2SYM(rb_intern("cast"));
1040
+ sym_stream = ID2SYM(rb_intern("stream"));
1041
+ sym_name = ID2SYM(rb_intern("name"));
1042
+
1043
+ opt_decimal_zero = rb_str_new2("0.0");
1044
+ rb_global_variable(&opt_decimal_zero); /*never GC */
1045
+ opt_float_zero = rb_float_new((double)0);
1046
+ rb_global_variable(&opt_float_zero);
1047
+ opt_time_year = INT2NUM(2000);
1048
+ opt_time_month = INT2NUM(1);
1049
+ opt_utc_offset = INT2NUM(0);
1050
+
1051
+ binaryEncoding = rb_enc_find("binary");
1052
+ }