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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +21 -0
- data/README.md +39 -0
- data/Rakefile +6 -0
- data/SYSDBA) +0 -0
- data/dm-0.1.0.gem +0 -0
- data/dm.gemspec +29 -0
- data/ext/client.c +520 -0
- data/ext/client.h +40 -0
- data/ext/dm_enc_name_to_ruby.h +63 -0
- data/ext/dm_ext.c +17 -0
- data/ext/dm_ext.h +43 -0
- data/ext/dm_lib_path.rb +3 -0
- data/ext/extconf.h +11 -0
- data/ext/extconf.rb +123 -0
- data/ext/result.c +1052 -0
- data/ext/result.h +39 -0
- data/ext/statement.c +666 -0
- data/ext/statement.h +36 -0
- data/lib/dm/client.rb +54 -0
- data/lib/dm/console.rb +5 -0
- data/lib/dm/error.rb +59 -0
- data/lib/dm/field.rb +3 -0
- data/lib/dm/result.rb +7 -0
- data/lib/dm/statement.rb +9 -0
- data/lib/dm/version.rb +5 -0
- data/lib/dm.rb +65 -0
- metadata +94 -0
data/ext/result.h
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
#ifndef DM_RESULT_H
|
2
|
+
#define DM_RESULT_H
|
3
|
+
|
4
|
+
void init_dm_result(void);
|
5
|
+
VALUE rb_dm_result_to_obj(VALUE client, VALUE encoding, dhstmt statement, int flag, VALUE options);
|
6
|
+
|
7
|
+
typedef struct {
|
8
|
+
sdbyte name[128 + 1];
|
9
|
+
sdint2 name_length;
|
10
|
+
sdint2 sql_type;
|
11
|
+
ulength prec;
|
12
|
+
sdint2 scale;
|
13
|
+
sdint2 nullable;
|
14
|
+
slength length;
|
15
|
+
}DmColDesc;
|
16
|
+
|
17
|
+
typedef struct {
|
18
|
+
VALUE fields;
|
19
|
+
VALUE fieldTypes;
|
20
|
+
VALUE rows;
|
21
|
+
VALUE client;
|
22
|
+
VALUE encoding;
|
23
|
+
dhstmt statement;
|
24
|
+
sdint2 numberOfFields;
|
25
|
+
sdint8 numberOfRows;
|
26
|
+
sdint8 lastRowProcessed;
|
27
|
+
dhloblctr* lobs;
|
28
|
+
char **result;
|
29
|
+
int resultFreed;
|
30
|
+
int is_bind;
|
31
|
+
int streamingComplete;
|
32
|
+
dm_client_wrapper *client_wrapper;
|
33
|
+
/* statement result bind buffers */
|
34
|
+
slength *length;
|
35
|
+
DmColDesc *col_desc;
|
36
|
+
int is_prepare;
|
37
|
+
} dm_result_wrapper;
|
38
|
+
|
39
|
+
#endif
|
data/ext/statement.c
ADDED
@@ -0,0 +1,666 @@
|
|
1
|
+
#include <dm_ext.h>
|
2
|
+
|
3
|
+
extern VALUE mdm, cdmError;
|
4
|
+
static VALUE cdmStatement, cBigDecimal, cDateTime, cDate;
|
5
|
+
static VALUE sym_stream, intern_new_with_args, intern_each, intern_to_s, intern_merge_bang;
|
6
|
+
static VALUE intern_sec_fraction, intern_usec, intern_sec, intern_min, intern_hour, intern_day, intern_month, intern_year,
|
7
|
+
intern_query_options;
|
8
|
+
|
9
|
+
#ifndef NEW_TYPEDDATA_WRAPPER
|
10
|
+
#define TypedData_Get_Struct(obj, type, ignore, sval) Data_Get_Struct(obj, type, sval)
|
11
|
+
#endif
|
12
|
+
|
13
|
+
#define GET_STATEMENT(self) \
|
14
|
+
dm_stmt_wrapper *stmt_wrapper; \
|
15
|
+
TypedData_Get_Struct(self, dm_stmt_wrapper, &rb_dm_statement_type, stmt_wrapper); \
|
16
|
+
if (!stmt_wrapper->stmt) { rb_raise(cdmError, "Invalid statement handle"); } \
|
17
|
+
if (stmt_wrapper->closed) { rb_raise(cdmError, "Statement handle already closed"); }
|
18
|
+
|
19
|
+
static void rb_dm_stmt_mark(void * ptr) {
|
20
|
+
dm_stmt_wrapper *stmt_wrapper = ptr;
|
21
|
+
if (!stmt_wrapper) return;
|
22
|
+
|
23
|
+
rb_gc_mark_movable(stmt_wrapper->client);
|
24
|
+
}
|
25
|
+
|
26
|
+
static void rb_dm_stmt_free(void *ptr) {
|
27
|
+
dm_stmt_wrapper *stmt_wrapper = ptr;
|
28
|
+
decr_dm_stmt(stmt_wrapper);
|
29
|
+
}
|
30
|
+
|
31
|
+
static size_t rb_dm_stmt_memsize(const void * ptr) {
|
32
|
+
const dm_stmt_wrapper *stmt_wrapper = ptr;
|
33
|
+
return sizeof(*stmt_wrapper);
|
34
|
+
}
|
35
|
+
|
36
|
+
#ifdef HAVE_RB_GC_MARK_MOVABLE
|
37
|
+
static void rb_dm_stmt_compact(void * ptr) {
|
38
|
+
dm_stmt_wrapper *stmt_wrapper = ptr;
|
39
|
+
if (!stmt_wrapper) return;
|
40
|
+
|
41
|
+
rb_dm_gc_location(stmt_wrapper->client);
|
42
|
+
}
|
43
|
+
#endif
|
44
|
+
|
45
|
+
static const rb_data_type_t rb_dm_statement_type = {
|
46
|
+
"rb_dm_statement",
|
47
|
+
{
|
48
|
+
rb_dm_stmt_mark,
|
49
|
+
rb_dm_stmt_free,
|
50
|
+
rb_dm_stmt_memsize,
|
51
|
+
#ifdef HAVE_RB_GC_MARK_MOVABLE
|
52
|
+
rb_dm_stmt_compact,
|
53
|
+
#endif
|
54
|
+
},
|
55
|
+
0,
|
56
|
+
0,
|
57
|
+
#ifdef RUBY_TYPED_FREE_IMMEDIATELY
|
58
|
+
RUBY_TYPED_FREE_IMMEDIATELY,
|
59
|
+
#endif
|
60
|
+
};
|
61
|
+
|
62
|
+
static void *nogvl_stmt_close(void *ptr) {
|
63
|
+
dm_stmt_wrapper *stmt_wrapper = ptr;
|
64
|
+
{
|
65
|
+
GET_CLIENT(stmt_wrapper->client);
|
66
|
+
if (stmt_wrapper->stmt && wrapper->closed != 1) {
|
67
|
+
dpi_free_stmt(stmt_wrapper->stmt);
|
68
|
+
stmt_wrapper->stmt = NULL;
|
69
|
+
}
|
70
|
+
return NULL;
|
71
|
+
}
|
72
|
+
}
|
73
|
+
|
74
|
+
void decr_dm_stmt(dm_stmt_wrapper *stmt_wrapper) {
|
75
|
+
stmt_wrapper->refcount--;
|
76
|
+
|
77
|
+
if (stmt_wrapper->refcount == 0) {
|
78
|
+
if(stmt_wrapper->paramdesc != NULL)
|
79
|
+
{
|
80
|
+
xfree(stmt_wrapper->paramdesc);
|
81
|
+
stmt_wrapper->paramdesc = NULL;
|
82
|
+
}
|
83
|
+
xfree(stmt_wrapper);
|
84
|
+
}
|
85
|
+
}
|
86
|
+
|
87
|
+
struct nogvl_errors_args {
|
88
|
+
void* hndl;
|
89
|
+
sdbyte errormsg[4096];
|
90
|
+
sdint4 errorCode;
|
91
|
+
sdint2 hndl_type;
|
92
|
+
};
|
93
|
+
|
94
|
+
static void *nogvl_get_error(void *ptr) {
|
95
|
+
struct nogvl_errors_args *args = ptr;
|
96
|
+
sdbyte error_buf[4096];
|
97
|
+
dpi_get_diag_rec(args->hndl_type, args->hndl, 1, &args->errorCode, error_buf, sizeof(error_buf), NULL);
|
98
|
+
sprintf(args->errormsg,"[CODE:%d]%s",args->errorCode, error_buf);
|
99
|
+
return (void*)Qtrue;
|
100
|
+
}
|
101
|
+
|
102
|
+
|
103
|
+
static VALUE rb_raise_dm_stmt_error(dm_stmt_wrapper *wrapper, void* hndl, sdint2 hndl_type) {
|
104
|
+
VALUE rb_error_msg;
|
105
|
+
VALUE e;
|
106
|
+
struct nogvl_errors_args err;
|
107
|
+
err.hndl = hndl;
|
108
|
+
err.hndl_type = hndl_type;
|
109
|
+
|
110
|
+
rb_thread_call_without_gvl(nogvl_get_error, &err, RUBY_UBF_IO, 0);
|
111
|
+
rb_error_msg = rb_str_new2(err.errormsg);
|
112
|
+
|
113
|
+
rb_enc_associate(rb_error_msg, rb_utf8_encoding());
|
114
|
+
e = rb_funcall(cdmError, intern_new_with_args, 2,
|
115
|
+
rb_error_msg,
|
116
|
+
UINT2NUM(err.errorCode));
|
117
|
+
rb_exc_raise(e);
|
118
|
+
}
|
119
|
+
|
120
|
+
/*
|
121
|
+
* used to pass all arguments to dm_stmt_prepare while inside
|
122
|
+
* nogvl_prepare_statement_args
|
123
|
+
*/
|
124
|
+
struct nogvl_prepare_statement_args {
|
125
|
+
dhstmt stmt;
|
126
|
+
VALUE sql;
|
127
|
+
const char *sql_ptr;
|
128
|
+
unsigned long sql_len;
|
129
|
+
};
|
130
|
+
|
131
|
+
static void *nogvl_prepare_statement(void *ptr) {
|
132
|
+
struct nogvl_prepare_statement_args *args = ptr;
|
133
|
+
DPIRETURN rt;
|
134
|
+
rt = dpi_prepare(args->stmt,args->sql_ptr);
|
135
|
+
if (!(DSQL_SUCCEEDED(rt))) {
|
136
|
+
return (void*)Qfalse;
|
137
|
+
} else {
|
138
|
+
return (void*)Qtrue;
|
139
|
+
}
|
140
|
+
}
|
141
|
+
|
142
|
+
static void *nogvl_alloc_stmt(void *ptr) {
|
143
|
+
dm_stmt_wrapper *stmt_wrapper = ptr;
|
144
|
+
DPIRETURN rt;
|
145
|
+
GET_CLIENT(stmt_wrapper->client);
|
146
|
+
rt = dpi_alloc_stmt(wrapper->client,&stmt_wrapper->stmt);
|
147
|
+
if(!DSQL_SUCCEEDED(rt))
|
148
|
+
{
|
149
|
+
return (void*)Qfalse;
|
150
|
+
}
|
151
|
+
rt = dpi_set_stmt_attr(stmt_wrapper->stmt,DSQL_ATTR_CURSOR_TYPE,(dpointer)DSQL_CURSOR_DYNAMIC,0);
|
152
|
+
if(!DSQL_SUCCEEDED(rt))
|
153
|
+
{
|
154
|
+
return (void*)Qfalse;
|
155
|
+
}
|
156
|
+
return (void*)Qtrue;
|
157
|
+
}
|
158
|
+
|
159
|
+
static void *nogvl_get_param_desc(void *ptr) {
|
160
|
+
dm_stmt_wrapper *stmt_wrapper = ptr;
|
161
|
+
DPIRETURN rt;
|
162
|
+
udint2 i;
|
163
|
+
rt = dpi_number_params(stmt_wrapper->stmt,&stmt_wrapper->param_num);
|
164
|
+
if(!DSQL_SUCCEEDED(rt))
|
165
|
+
{
|
166
|
+
return (void*)Qfalse;
|
167
|
+
}
|
168
|
+
|
169
|
+
stmt_wrapper->paramdesc = (ParamDesc*)xcalloc(stmt_wrapper->param_num,sizeof(ParamDesc));
|
170
|
+
for(i = 0; i < stmt_wrapper->param_num; i++)
|
171
|
+
{
|
172
|
+
rt = dpi_desc_param(
|
173
|
+
stmt_wrapper->stmt, i + 1, &stmt_wrapper->paramdesc[i].sql_type,
|
174
|
+
&stmt_wrapper->paramdesc[i].prec, &stmt_wrapper->paramdesc[i].scale,
|
175
|
+
&stmt_wrapper->paramdesc[i].nullable);
|
176
|
+
if(!DSQL_SUCCEEDED(rt))
|
177
|
+
{
|
178
|
+
return (void*)Qfalse;
|
179
|
+
}
|
180
|
+
}
|
181
|
+
|
182
|
+
rt = dpi_number_columns(stmt_wrapper->stmt,&stmt_wrapper->col_num);
|
183
|
+
if(!DSQL_SUCCEEDED(rt))
|
184
|
+
{
|
185
|
+
return (void*)Qfalse;
|
186
|
+
}
|
187
|
+
return (void*)Qtrue;
|
188
|
+
}
|
189
|
+
|
190
|
+
VALUE rb_dm_stmt_new(VALUE rb_client, VALUE sql) {
|
191
|
+
dm_stmt_wrapper *stmt_wrapper;
|
192
|
+
VALUE rb_stmt;
|
193
|
+
rb_encoding *conn_enc;
|
194
|
+
|
195
|
+
Check_Type(sql, T_STRING);
|
196
|
+
|
197
|
+
#ifdef NEW_TYPEDDATA_WRAPPER
|
198
|
+
rb_stmt = TypedData_Make_Struct(cdmStatement, dm_stmt_wrapper, &rb_dm_statement_type, stmt_wrapper);
|
199
|
+
#else
|
200
|
+
rb_stmt = Data_Make_Struct(cdmStatement, dm_stmt_wrapper, rb_dm_stmt_mark, rb_dm_stmt_free, stmt_wrapper);
|
201
|
+
#endif
|
202
|
+
{
|
203
|
+
stmt_wrapper->client = rb_client;
|
204
|
+
stmt_wrapper->refcount = 1;
|
205
|
+
stmt_wrapper->closed = 0;
|
206
|
+
stmt_wrapper->stmt = NULL;
|
207
|
+
stmt_wrapper->param_num = 0;
|
208
|
+
stmt_wrapper->paramdesc = NULL;
|
209
|
+
stmt_wrapper->affected_rows = 0;
|
210
|
+
stmt_wrapper->is_select = 0;
|
211
|
+
}
|
212
|
+
|
213
|
+
// instantiate stmt
|
214
|
+
{
|
215
|
+
GET_CLIENT(rb_client);
|
216
|
+
rb_thread_call_without_gvl(nogvl_alloc_stmt, stmt_wrapper, RUBY_UBF_IO, 0);
|
217
|
+
conn_enc = rb_to_encoding(wrapper->encoding);
|
218
|
+
}
|
219
|
+
if (stmt_wrapper->stmt == NULL) {
|
220
|
+
rb_raise(cdmError, "Unable to initialize prepared statement: out of memory");
|
221
|
+
}
|
222
|
+
|
223
|
+
// call dm_stmt_prepare w/o gvl
|
224
|
+
{
|
225
|
+
struct nogvl_prepare_statement_args args;
|
226
|
+
args.stmt = stmt_wrapper->stmt;
|
227
|
+
// ensure the string is in the encoding the connection is expecting
|
228
|
+
args.sql = rb_str_export_to_enc(sql, conn_enc);
|
229
|
+
args.sql_ptr = RSTRING_PTR(sql);
|
230
|
+
args.sql_len = RSTRING_LEN(sql);
|
231
|
+
|
232
|
+
if ((VALUE)rb_thread_call_without_gvl(nogvl_prepare_statement, &args, RUBY_UBF_IO, 0) == Qfalse) {
|
233
|
+
rb_raise_dm_stmt_error(stmt_wrapper, stmt_wrapper->stmt, DSQL_HANDLE_STMT);
|
234
|
+
}
|
235
|
+
|
236
|
+
if ((VALUE)rb_thread_call_without_gvl(nogvl_get_param_desc, stmt_wrapper, RUBY_UBF_IO, 0) == Qfalse) {
|
237
|
+
rb_raise(cdmError, "failed to get param desc");
|
238
|
+
}
|
239
|
+
}
|
240
|
+
|
241
|
+
return rb_stmt;
|
242
|
+
}
|
243
|
+
|
244
|
+
/* call-seq: stmt.param_count # => Numeric
|
245
|
+
*
|
246
|
+
* Returns the number of parameters the prepared statement expects.
|
247
|
+
*/
|
248
|
+
static VALUE rb_dm_stmt_param_count(VALUE self) {
|
249
|
+
GET_STATEMENT(self);
|
250
|
+
|
251
|
+
return UINT2NUM(stmt_wrapper->param_num);
|
252
|
+
}
|
253
|
+
|
254
|
+
/* call-seq: stmt.field_count # => Numeric
|
255
|
+
*
|
256
|
+
* Returns the number of fields the prepared statement returns.
|
257
|
+
*/
|
258
|
+
static VALUE rb_dm_stmt_field_count(VALUE self) {
|
259
|
+
GET_STATEMENT(self);
|
260
|
+
|
261
|
+
return UINT2NUM(stmt_wrapper->col_num);
|
262
|
+
}
|
263
|
+
|
264
|
+
static void *nogvl_stmt_execute(void *ptr) {
|
265
|
+
dm_stmt_wrapper *stmt_wrapper = ptr;
|
266
|
+
GET_CLIENT(stmt_wrapper->client);
|
267
|
+
DPIRETURN rt;
|
268
|
+
sdint4 nStmtType;
|
269
|
+
udint2 col_num;
|
270
|
+
sdbyte lastrowid[12];
|
271
|
+
udint4 len;
|
272
|
+
|
273
|
+
rt = dpi_close_cursor(stmt_wrapper->stmt);
|
274
|
+
|
275
|
+
rt = dpi_exec(stmt_wrapper->stmt);
|
276
|
+
|
277
|
+
if (!DSQL_SUCCEEDED(rt)) {
|
278
|
+
return (void*)Qfalse;
|
279
|
+
}
|
280
|
+
rt = dpi_get_diag_field(DSQL_HANDLE_STMT, stmt_wrapper->stmt, 0,
|
281
|
+
DSQL_DIAG_DYNAMIC_FUNCTION_CODE,
|
282
|
+
(dpointer)&nStmtType, 0, NULL);
|
283
|
+
|
284
|
+
if(nStmtType == DSQL_DIAG_FUNC_CODE_INSERT ||
|
285
|
+
nStmtType == DSQL_DIAG_FUNC_CODE_UPDATE ||
|
286
|
+
nStmtType == DSQL_DIAG_FUNC_CODE_DELETE)
|
287
|
+
{
|
288
|
+
rt = dpi_get_diag_field(DSQL_HANDLE_STMT, stmt_wrapper->stmt, 0, DSQL_DIAG_ROWID, &lastrowid, sizeof(lastrowid), NULL);
|
289
|
+
if (!DSQL_SUCCEEDED(rt)) {
|
290
|
+
return (void*)Qfalse;
|
291
|
+
}
|
292
|
+
rt = dpi_rowid_to_char(wrapper->client,lastrowid,sizeof(lastrowid),stmt_wrapper->lastrowid,sizeof(stmt_wrapper->lastrowid),&len);
|
293
|
+
if (!DSQL_SUCCEEDED(rt)) {
|
294
|
+
return (void*)Qfalse;
|
295
|
+
}
|
296
|
+
}
|
297
|
+
else
|
298
|
+
{
|
299
|
+
strncpy(stmt_wrapper->lastrowid, "", 20);
|
300
|
+
}
|
301
|
+
|
302
|
+
// affected_rows
|
303
|
+
if(nStmtType == DSQL_DIAG_FUNC_CODE_INSERT ||
|
304
|
+
nStmtType == DSQL_DIAG_FUNC_CODE_UPDATE ||
|
305
|
+
nStmtType == DSQL_DIAG_FUNC_CODE_DELETE ||
|
306
|
+
nStmtType == DSQL_DIAG_FUNC_CODE_CALL)
|
307
|
+
{
|
308
|
+
rt = dpi_row_count(stmt_wrapper->stmt,&stmt_wrapper->affected_rows);
|
309
|
+
if (!DSQL_SUCCEEDED(rt)) {
|
310
|
+
return (void*)Qfalse;
|
311
|
+
}
|
312
|
+
}
|
313
|
+
|
314
|
+
rt = dpi_number_columns(stmt_wrapper->stmt, &stmt_wrapper->col_num);
|
315
|
+
if (!DSQL_SUCCEEDED(rt)) {
|
316
|
+
return (void*)Qfalse;
|
317
|
+
}
|
318
|
+
if(stmt_wrapper->col_num > 0)
|
319
|
+
stmt_wrapper->is_select = 1;
|
320
|
+
|
321
|
+
return (void*)Qtrue;
|
322
|
+
}
|
323
|
+
|
324
|
+
static void set_buffer_for_string(dm_BIND* bind_buffer, unsigned long *length_buffer, VALUE string) {
|
325
|
+
unsigned long length;
|
326
|
+
|
327
|
+
bind_buffer->buffer = RSTRING_PTR(string);
|
328
|
+
|
329
|
+
length = RSTRING_LEN(string);
|
330
|
+
bind_buffer->buffer_length = length;
|
331
|
+
*length_buffer = length;
|
332
|
+
}
|
333
|
+
|
334
|
+
/* Free each bind_buffer[i].buffer except when params_enc is non-nil, this means
|
335
|
+
* the buffer is a Ruby string pointer and not our memory to manage.
|
336
|
+
*/
|
337
|
+
#define FREE_BINDS \
|
338
|
+
for (i = 0; i < bind_count; i++) { \
|
339
|
+
if (bind_buffers[i].buffer && NIL_P(params_enc[i])) { \
|
340
|
+
xfree(bind_buffers[i].buffer); \
|
341
|
+
} \
|
342
|
+
} \
|
343
|
+
if (argc > 0) { \
|
344
|
+
xfree(bind_buffers); \
|
345
|
+
xfree(length_buffers); \
|
346
|
+
}
|
347
|
+
|
348
|
+
/* return 0 if the given bignum can cast as LONG_LONG, otherwise 1 */
|
349
|
+
static int my_big2ll(VALUE bignum, sdint8 *ptr)
|
350
|
+
{
|
351
|
+
sdint8 num;
|
352
|
+
size_t len;
|
353
|
+
// rb_absint_size was added in 2.1.0. See:
|
354
|
+
// https://github.com/ruby/ruby/commit/9fea875
|
355
|
+
int nlz_bits = 0;
|
356
|
+
len = rb_absint_size(bignum, &nlz_bits);
|
357
|
+
|
358
|
+
if (len > sizeof(sdint8)) goto overflow;
|
359
|
+
if (RBIGNUM_POSITIVE_P(bignum)) {
|
360
|
+
num = rb_big2ull(bignum);
|
361
|
+
if (num > LLONG_MAX)
|
362
|
+
goto overflow;
|
363
|
+
*ptr = num;
|
364
|
+
}
|
365
|
+
else {
|
366
|
+
if (len == 8 &&
|
367
|
+
#ifdef HAVE_RB_ABSINT_SIZE
|
368
|
+
nlz_bits == 0 &&
|
369
|
+
#endif
|
370
|
+
// rb_absint_singlebit_p was added in 2.1.0. See:
|
371
|
+
// https://github.com/ruby/ruby/commit/e5ff9d5
|
372
|
+
#if defined(HAVE_RB_ABSINT_SIZE) && defined(HAVE_RB_ABSINT_SINGLEBIT_P)
|
373
|
+
/* Optimized to avoid object allocation for Ruby 2.1+
|
374
|
+
* only -0x8000000000000000 is safe if `len == 8 && nlz_bits == 0`
|
375
|
+
*/
|
376
|
+
!rb_absint_singlebit_p(bignum)
|
377
|
+
#else
|
378
|
+
rb_big_cmp(bignum, LL2NUM(LLONG_MIN)) == INT2FIX(-1)
|
379
|
+
#endif
|
380
|
+
) {
|
381
|
+
goto overflow;
|
382
|
+
}
|
383
|
+
*ptr = rb_big2ll(bignum);
|
384
|
+
}
|
385
|
+
return 0;
|
386
|
+
overflow:
|
387
|
+
return 1;
|
388
|
+
}
|
389
|
+
|
390
|
+
/* call-seq: stmt.execute
|
391
|
+
*
|
392
|
+
* Executes the current prepared statement, returns +result+.
|
393
|
+
*/
|
394
|
+
static VALUE rb_dm_stmt_execute(int argc, VALUE *argv, VALUE self) {
|
395
|
+
dm_BIND *bind_buffers = NULL;
|
396
|
+
unsigned long *length_buffers = NULL;
|
397
|
+
udint2 bind_count;
|
398
|
+
udint2 i;
|
399
|
+
dhstmt stmt;
|
400
|
+
VALUE opts;
|
401
|
+
VALUE current;
|
402
|
+
VALUE resultObj;
|
403
|
+
VALUE *params_enc = NULL;
|
404
|
+
rb_encoding *conn_enc;
|
405
|
+
DPIRETURN rt;
|
406
|
+
|
407
|
+
GET_STATEMENT(self);
|
408
|
+
GET_CLIENT(stmt_wrapper->client);
|
409
|
+
|
410
|
+
conn_enc = rb_to_encoding(wrapper->encoding);
|
411
|
+
|
412
|
+
if(wrapper->closed == 1)
|
413
|
+
rb_raise(cdmError, "Client is closed");
|
414
|
+
|
415
|
+
stmt = stmt_wrapper->stmt;
|
416
|
+
bind_count = stmt_wrapper->param_num;
|
417
|
+
|
418
|
+
// Get count of ordinary arguments, and extract hash opts/keyword arguments
|
419
|
+
// Use a local scope to avoid leaking the temporary count variable
|
420
|
+
{
|
421
|
+
int c = rb_scan_args(argc, argv, "*:", NULL, &opts);
|
422
|
+
if (c != (long)bind_count) {
|
423
|
+
rb_raise(cdmError, "Bind parameter count (%ld) doesn't match number of arguments (%d)", bind_count, c);
|
424
|
+
}
|
425
|
+
}
|
426
|
+
|
427
|
+
// setup any bind variables in the query
|
428
|
+
if (bind_count > 0) {
|
429
|
+
// Scratch space for string encoding exports, allocate on the stack
|
430
|
+
params_enc = alloca(sizeof(VALUE) * bind_count);
|
431
|
+
bind_buffers = xcalloc(bind_count, sizeof(dm_BIND));
|
432
|
+
length_buffers = xcalloc(bind_count, sizeof(unsigned long));
|
433
|
+
|
434
|
+
for (i = 0; i < bind_count; i++) {
|
435
|
+
bind_buffers[i].buffer = NULL;
|
436
|
+
params_enc[i] = Qnil;
|
437
|
+
|
438
|
+
switch (TYPE(argv[i])) {
|
439
|
+
case T_NIL:
|
440
|
+
bind_buffers[i].buffer = xmalloc(8192);
|
441
|
+
strncpy(bind_buffers[i].buffer, "", 8192);
|
442
|
+
rt = dpi_bind_param(stmt_wrapper->stmt, i + 1, DSQL_PARAM_INPUT, DSQL_C_NCHAR, stmt_wrapper->paramdesc[i].sql_type, stmt_wrapper->paramdesc[i].prec, stmt_wrapper->paramdesc[i].scale, bind_buffers[i].buffer, 8192, NULL);
|
443
|
+
if(!DSQL_SUCCEEDED(rt))
|
444
|
+
rb_raise(cdmError, "failed to bind param %d", i);
|
445
|
+
break;
|
446
|
+
case T_FIXNUM:
|
447
|
+
#if SIZEOF_INT < SIZEOF_LONG
|
448
|
+
bind_buffers[i].buffer = xmalloc(sizeof(long long int));
|
449
|
+
*(long*)(bind_buffers[i].buffer) = FIX2LONG(argv[i]);
|
450
|
+
rt = dpi_bind_param(stmt_wrapper->stmt, i + 1, DSQL_PARAM_INPUT, DSQL_C_SBIGINT, stmt_wrapper->paramdesc[i].sql_type, stmt_wrapper->paramdesc[i].prec, stmt_wrapper->paramdesc[i].scale, bind_buffers[i].buffer, sizeof(long long int), NULL);
|
451
|
+
if(!DSQL_SUCCEEDED(rt))
|
452
|
+
rb_raise(cdmError, "failed to bind param %d", i);
|
453
|
+
#else
|
454
|
+
bind_buffers[i].buffer = xmalloc(sizeof(int));
|
455
|
+
*(long*)(bind_buffers[i].buffer) = FIX2INT(argv[i]);
|
456
|
+
rt = dpi_bind_param(stmt_wrapper->stmt, i + 1, DSQL_PARAM_INPUT, DSQL_C_SLONG, stmt_wrapper->paramdesc[i].sql_type, stmt_wrapper->paramdesc[i].prec, stmt_wrapper->paramdesc[i].scale, bind_buffers[i].buffer, sizeof(int), NULL);
|
457
|
+
if(!DSQL_SUCCEEDED(rt))
|
458
|
+
rb_raise(cdmError, "failed to bind param %d", i);
|
459
|
+
#endif
|
460
|
+
break;
|
461
|
+
case T_BIGNUM:
|
462
|
+
{
|
463
|
+
sdint8 num;
|
464
|
+
if (my_big2ll(argv[i], &num) == 0) {
|
465
|
+
bind_buffers[i].buffer = xmalloc(sizeof(sdint8));
|
466
|
+
*(sdint8*)(bind_buffers[i].buffer) = num;
|
467
|
+
rt = dpi_bind_param(stmt_wrapper->stmt, i + 1, DSQL_PARAM_INPUT, DSQL_C_SBIGINT, stmt_wrapper->paramdesc[i].sql_type, stmt_wrapper->paramdesc[i].prec, stmt_wrapper->paramdesc[i].scale, bind_buffers[i].buffer, sizeof(sdint8), NULL);
|
468
|
+
} else {
|
469
|
+
/* The bignum was larger than we can fit in LONG_LONG, send it as a string */
|
470
|
+
params_enc[i] = rb_str_export_to_enc(rb_big2str(argv[i], 10), conn_enc);
|
471
|
+
set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
|
472
|
+
rt = dpi_bind_param(stmt_wrapper->stmt, i + 1, DSQL_PARAM_INPUT, DSQL_C_NCHAR, stmt_wrapper->paramdesc[i].sql_type, stmt_wrapper->paramdesc[i].prec, stmt_wrapper->paramdesc[i].scale, bind_buffers[i].buffer, bind_buffers[i].buffer_length, NULL);
|
473
|
+
}
|
474
|
+
}
|
475
|
+
if(!DSQL_SUCCEEDED(rt))
|
476
|
+
rb_raise(cdmError, "failed to bind param %d", i);
|
477
|
+
break;
|
478
|
+
case T_FLOAT:
|
479
|
+
bind_buffers[i].buffer = xmalloc(sizeof(double));
|
480
|
+
*(double*)(bind_buffers[i].buffer) = NUM2DBL(argv[i]);
|
481
|
+
rt = dpi_bind_param(stmt_wrapper->stmt, i + 1, DSQL_PARAM_INPUT, DSQL_C_DOUBLE, stmt_wrapper->paramdesc[i].sql_type, stmt_wrapper->paramdesc[i].prec, stmt_wrapper->paramdesc[i].scale, bind_buffers[i].buffer, sizeof(double), NULL);
|
482
|
+
if(!DSQL_SUCCEEDED(rt))
|
483
|
+
rb_raise(cdmError, "failed to bind param %d", i);
|
484
|
+
break;
|
485
|
+
case T_STRING:
|
486
|
+
params_enc[i] = argv[i];
|
487
|
+
params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
|
488
|
+
set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
|
489
|
+
if(stmt_wrapper->paramdesc[i].sql_type == DSQL_BLOB || stmt_wrapper->paramdesc[i].sql_type == DSQL_BINARY || stmt_wrapper->paramdesc[i].sql_type == DSQL_VARBINARY)
|
490
|
+
rt = dpi_bind_param(stmt_wrapper->stmt, i + 1, DSQL_PARAM_INPUT, DSQL_C_BINARY, stmt_wrapper->paramdesc[i].sql_type, stmt_wrapper->paramdesc[i].prec, stmt_wrapper->paramdesc[i].scale, bind_buffers[i].buffer, bind_buffers[i].buffer_length, NULL);
|
491
|
+
else
|
492
|
+
rt = dpi_bind_param(stmt_wrapper->stmt, i + 1, DSQL_PARAM_INPUT, DSQL_C_NCHAR, stmt_wrapper->paramdesc[i].sql_type, stmt_wrapper->paramdesc[i].prec, stmt_wrapper->paramdesc[i].scale, bind_buffers[i].buffer, bind_buffers[i].buffer_length, NULL);
|
493
|
+
if(!DSQL_SUCCEEDED(rt))
|
494
|
+
rb_raise(cdmError, "failed to bind param %d", i);
|
495
|
+
break;
|
496
|
+
case T_TRUE:
|
497
|
+
bind_buffers[i].buffer = xmalloc(sizeof(int));
|
498
|
+
*(int*)(bind_buffers[i].buffer) = 1;
|
499
|
+
rt = dpi_bind_param(stmt_wrapper->stmt, i + 1, DSQL_PARAM_INPUT, DSQL_C_SLONG, stmt_wrapper->paramdesc[i].sql_type, stmt_wrapper->paramdesc[i].prec, stmt_wrapper->paramdesc[i].scale, bind_buffers[i].buffer, sizeof(int), NULL);
|
500
|
+
if(!DSQL_SUCCEEDED(rt))
|
501
|
+
rb_raise(cdmError, "failed to bind param %d", i);
|
502
|
+
break;
|
503
|
+
case T_FALSE:
|
504
|
+
bind_buffers[i].buffer = xmalloc(sizeof(int));
|
505
|
+
*(int*)(bind_buffers[i].buffer) = 0;
|
506
|
+
rt = dpi_bind_param(stmt_wrapper->stmt, i + 1, DSQL_PARAM_INPUT, DSQL_C_SLONG, stmt_wrapper->paramdesc[i].sql_type, stmt_wrapper->paramdesc[i].prec, stmt_wrapper->paramdesc[i].scale, bind_buffers[i].buffer, sizeof(int), NULL);
|
507
|
+
if(!DSQL_SUCCEEDED(rt))
|
508
|
+
rb_raise(cdmError, "failed to bind param %d", i);
|
509
|
+
break;
|
510
|
+
default:
|
511
|
+
// TODO: what Ruby type should support dm_TYPE_TIME
|
512
|
+
if (CLASS_OF(argv[i]) == rb_cTime || CLASS_OF(argv[i]) == cDateTime) {
|
513
|
+
dpi_timestamp_t t;
|
514
|
+
VALUE rb_time = argv[i];
|
515
|
+
|
516
|
+
bind_buffers[i].buffer = xmalloc(sizeof(dpi_timestamp_t));
|
517
|
+
|
518
|
+
memset(&t, 0, sizeof(dpi_timestamp_t));
|
519
|
+
|
520
|
+
if (CLASS_OF(argv[i]) == rb_cTime) {
|
521
|
+
t.fraction = FIX2INT(rb_funcall(rb_time, intern_usec, 0));
|
522
|
+
} else if (CLASS_OF(argv[i]) == cDateTime) {
|
523
|
+
t.fraction = NUM2DBL(rb_funcall(rb_time, intern_sec_fraction, 0)) * 1000000;
|
524
|
+
}
|
525
|
+
|
526
|
+
t.second = FIX2INT(rb_funcall(rb_time, intern_sec, 0));
|
527
|
+
t.minute = FIX2INT(rb_funcall(rb_time, intern_min, 0));
|
528
|
+
t.hour = FIX2INT(rb_funcall(rb_time, intern_hour, 0));
|
529
|
+
t.day = FIX2INT(rb_funcall(rb_time, intern_day, 0));
|
530
|
+
t.month = FIX2INT(rb_funcall(rb_time, intern_month, 0));
|
531
|
+
t.year = FIX2INT(rb_funcall(rb_time, intern_year, 0));
|
532
|
+
|
533
|
+
*(dpi_timestamp_t*)(bind_buffers[i].buffer) = t;
|
534
|
+
rt = dpi_bind_param(stmt_wrapper->stmt, i + 1, DSQL_PARAM_INPUT, DSQL_C_TIMESTAMP, stmt_wrapper->paramdesc[i].sql_type, stmt_wrapper->paramdesc[i].prec, stmt_wrapper->paramdesc[i].scale, bind_buffers[i].buffer, sizeof(dpi_timestamp_t), NULL);
|
535
|
+
} else if (CLASS_OF(argv[i]) == cDate) {
|
536
|
+
dpi_date_t t;
|
537
|
+
VALUE rb_time = argv[i];
|
538
|
+
|
539
|
+
bind_buffers[i].buffer = xmalloc(sizeof(dpi_date_t));
|
540
|
+
|
541
|
+
memset(&t, 0, sizeof(dpi_date_t));
|
542
|
+
t.day = FIX2INT(rb_funcall(rb_time, intern_day, 0));
|
543
|
+
t.month = FIX2INT(rb_funcall(rb_time, intern_month, 0));
|
544
|
+
t.year = FIX2INT(rb_funcall(rb_time, intern_year, 0));
|
545
|
+
|
546
|
+
*(dpi_date_t*)(bind_buffers[i].buffer) = t;
|
547
|
+
rt = dpi_bind_param(stmt_wrapper->stmt, i + 1, DSQL_PARAM_INPUT, DSQL_C_DATE, stmt_wrapper->paramdesc[i].sql_type, stmt_wrapper->paramdesc[i].prec, stmt_wrapper->paramdesc[i].scale, bind_buffers[i].buffer, sizeof(dpi_date_t), NULL);
|
548
|
+
} else if (CLASS_OF(argv[i]) == cBigDecimal) {
|
549
|
+
VALUE rb_val_as_string = rb_funcall(argv[i], intern_to_s, 0);
|
550
|
+
|
551
|
+
params_enc[i] = rb_val_as_string;
|
552
|
+
params_enc[i] = rb_str_export_to_enc(params_enc[i], conn_enc);
|
553
|
+
set_buffer_for_string(&bind_buffers[i], &length_buffers[i], params_enc[i]);
|
554
|
+
rt = dpi_bind_param(stmt_wrapper->stmt, i + 1, DSQL_PARAM_INPUT, DSQL_C_NCHAR, stmt_wrapper->paramdesc[i].sql_type, stmt_wrapper->paramdesc[i].prec, stmt_wrapper->paramdesc[i].scale, bind_buffers[i].buffer, bind_buffers[i].buffer_length, NULL);
|
555
|
+
}
|
556
|
+
if(!DSQL_SUCCEEDED(rt))
|
557
|
+
rb_raise(cdmError, "failed to bind param %d", i);
|
558
|
+
break;
|
559
|
+
}
|
560
|
+
}
|
561
|
+
}
|
562
|
+
|
563
|
+
|
564
|
+
// From stmt_execute to dm_stmt_result_metadata to stmt_store_result, no
|
565
|
+
// Ruby API calls are allowed so that GC is not invoked. If the connection is
|
566
|
+
// in results-streaming-mode for Statement A, and in the middle Statement B
|
567
|
+
// gets garbage collected, a message will be sent to the server notifying it
|
568
|
+
// to release Statement B, resulting in the following error:
|
569
|
+
// Commands out of sync; you can't run this command now
|
570
|
+
//
|
571
|
+
// In streaming mode, statement execute must return a cursor because we
|
572
|
+
// cannot prevent other Statement objects from being garbage collected
|
573
|
+
// between fetches of each row of the result set. The following error
|
574
|
+
// occurs if cursor mode is not set:
|
575
|
+
// Row retrieval was canceled by dm_stmt_close
|
576
|
+
|
577
|
+
if ((VALUE)rb_thread_call_without_gvl(nogvl_stmt_execute, stmt_wrapper, RUBY_UBF_IO, 0) == Qfalse) {
|
578
|
+
FREE_BINDS;
|
579
|
+
rb_raise_dm_stmt_error(stmt_wrapper, stmt_wrapper->stmt, DSQL_HANDLE_STMT);
|
580
|
+
}
|
581
|
+
|
582
|
+
FREE_BINDS;
|
583
|
+
|
584
|
+
|
585
|
+
if (stmt_wrapper->is_select != 1) {
|
586
|
+
return Qnil;
|
587
|
+
}
|
588
|
+
|
589
|
+
resultObj = rb_dm_result_to_obj(stmt_wrapper->client, wrapper->encoding, stmt_wrapper->stmt, 1 , Qnil);
|
590
|
+
return resultObj;
|
591
|
+
}
|
592
|
+
|
593
|
+
|
594
|
+
/* call-seq:
|
595
|
+
* stmt.last_id
|
596
|
+
*
|
597
|
+
* Returns the AUTO_INCREMENT value from the executed INSERT or UPDATE.
|
598
|
+
*/
|
599
|
+
static VALUE rb_dm_stmt_last_id(VALUE self) {
|
600
|
+
GET_STATEMENT(self);
|
601
|
+
return rb_str_new2(stmt_wrapper->lastrowid);
|
602
|
+
}
|
603
|
+
|
604
|
+
/* call-seq:
|
605
|
+
* stmt.affected_rows
|
606
|
+
*
|
607
|
+
* Returns the number of rows changed, deleted, or inserted.
|
608
|
+
*/
|
609
|
+
static VALUE rb_dm_stmt_affected_rows(VALUE self) {
|
610
|
+
GET_STATEMENT(self);
|
611
|
+
return ULL2NUM(stmt_wrapper->affected_rows);
|
612
|
+
}
|
613
|
+
|
614
|
+
/* call-seq:
|
615
|
+
* stmt.close
|
616
|
+
*
|
617
|
+
* Explicitly closing this will free up server resources immediately rather
|
618
|
+
* than waiting for the garbage collector. Useful if you're managing your
|
619
|
+
* own prepared statement cache.
|
620
|
+
*/
|
621
|
+
static VALUE rb_dm_stmt_close(VALUE self) {
|
622
|
+
GET_STATEMENT(self);
|
623
|
+
stmt_wrapper->closed = 1;
|
624
|
+
rb_thread_call_without_gvl(nogvl_stmt_close, stmt_wrapper, RUBY_UBF_IO, 0);
|
625
|
+
return Qnil;
|
626
|
+
}
|
627
|
+
|
628
|
+
void init_dm_statement() {
|
629
|
+
cDate = rb_const_get(rb_cObject, rb_intern("Date"));
|
630
|
+
rb_global_variable(&cDate);
|
631
|
+
|
632
|
+
cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime"));
|
633
|
+
rb_global_variable(&cDateTime);
|
634
|
+
|
635
|
+
cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal"));
|
636
|
+
rb_global_variable(&cBigDecimal);
|
637
|
+
|
638
|
+
cdmStatement = rb_define_class_under(mdm, "Statement", rb_cObject);
|
639
|
+
rb_undef_alloc_func(cdmStatement);
|
640
|
+
rb_global_variable(&cdmStatement);
|
641
|
+
|
642
|
+
rb_define_method(cdmStatement, "param_count", rb_dm_stmt_param_count, 0);
|
643
|
+
rb_define_method(cdmStatement, "field_count", rb_dm_stmt_field_count, 0);
|
644
|
+
rb_define_method(cdmStatement, "_execute", rb_dm_stmt_execute, -1);
|
645
|
+
rb_define_method(cdmStatement, "last_id", rb_dm_stmt_last_id, 0);
|
646
|
+
rb_define_method(cdmStatement, "affected_rows", rb_dm_stmt_affected_rows, 0);
|
647
|
+
rb_define_method(cdmStatement, "close", rb_dm_stmt_close, 0);
|
648
|
+
|
649
|
+
sym_stream = ID2SYM(rb_intern("stream"));
|
650
|
+
|
651
|
+
intern_new_with_args = rb_intern("new_with_args");
|
652
|
+
intern_each = rb_intern("each");
|
653
|
+
|
654
|
+
intern_sec_fraction = rb_intern("sec_fraction");
|
655
|
+
intern_usec = rb_intern("usec");
|
656
|
+
intern_sec = rb_intern("sec");
|
657
|
+
intern_min = rb_intern("min");
|
658
|
+
intern_hour = rb_intern("hour");
|
659
|
+
intern_day = rb_intern("day");
|
660
|
+
intern_month = rb_intern("month");
|
661
|
+
intern_year = rb_intern("year");
|
662
|
+
|
663
|
+
intern_to_s = rb_intern("to_s");
|
664
|
+
intern_merge_bang = rb_intern("merge!");
|
665
|
+
intern_query_options = rb_intern("@query_options");
|
666
|
+
}
|