rubyfb 0.5.9 → 0.6

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/Statement.c CHANGED
@@ -34,20 +34,194 @@
34
34
 
35
35
  /* Function prototypes. */
36
36
  static VALUE allocateStatement(VALUE);
37
- static VALUE initializeStatement(VALUE, VALUE, VALUE, VALUE, VALUE);
37
+ static VALUE initializeStatement(VALUE, VALUE, VALUE);
38
38
  static VALUE getStatementSQL(VALUE);
39
- static VALUE getStatementTransaction(VALUE);
40
39
  static VALUE getStatementConnection(VALUE);
41
40
  static VALUE getStatementDialect(VALUE);
42
41
  static VALUE getStatementType(VALUE);
43
42
  static VALUE getStatementParameterCount(VALUE);
44
- static VALUE executeStatement(VALUE);
45
- static VALUE executeStatementFor(VALUE, VALUE);
43
+ static VALUE execStatement(int, VALUE*, VALUE);
44
+ static VALUE execAndCloseStatement(int, VALUE*, VALUE);
46
45
  static VALUE closeStatement(VALUE);
46
+ static VALUE getStatementPrepared(VALUE);
47
+ static VALUE prepareStatement(int, VALUE*, VALUE);
48
+
49
+ VALUE execAndManageTransaction(VALUE, VALUE, VALUE);
50
+ VALUE execAndManageStatement(VALUE, VALUE, VALUE);
51
+ VALUE rescueLocalTransaction(VALUE, VALUE);
52
+ VALUE execStatementFromArray(VALUE);
53
+ VALUE rescueStatement(VALUE, VALUE);
54
+ VALUE execInTransactionFromArray(VALUE);
55
+ VALUE execInTransaction(VALUE, VALUE, VALUE);
56
+ void prepareInTransaction(VALUE, VALUE);
57
+ VALUE prepareFromArray(VALUE);
58
+ void statementFree(void *);
59
+ StatementHandle* getPreparedHandle(VALUE self);
47
60
 
48
61
  /* Globals. */
49
62
  VALUE cStatement;
50
63
 
64
+ /**
65
+ * This function prepares a Firebird SQL statement for execution.
66
+ *
67
+ * @param connection A pointer to the database connection that will be used
68
+ * to prepare the statement.
69
+ * @param transaction A pointer to the database transaction that will be used
70
+ * to prepare the statement.
71
+ * @param sql A string containing the SQL statement to be executed.
72
+ * @param statement A pointer to a Firebird statement that will be prepared.
73
+ * @param dialect A short integer containing the SQL dialect to be used in
74
+ * preparing the statement.
75
+ * @param type A pointer to an integer that will be assigned the type
76
+ * of the SQL statement prepared.
77
+ * @param inputs A pointer to an integer that will be assigned a count of
78
+ * the parameters for the SQL statement.
79
+ * @param outputs A pointer to an integer that will be assigned a count of
80
+ * the output columns for the SQL statement.
81
+ *
82
+ */
83
+ void fb_prepare(isc_db_handle *connection, isc_tr_handle *transaction,
84
+ char *sql, isc_stmt_handle *statement, short dialect,
85
+ int *type, int *inputs, int *outputs) {
86
+ ISC_STATUS status[ISC_STATUS_LENGTH];
87
+ XSQLDA *da = NULL;
88
+ char list[] = {isc_info_sql_stmt_type},
89
+ info[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
90
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
91
+
92
+ /* Prepare the statement. */
93
+ if(isc_dsql_allocate_statement(status, connection, statement)) {
94
+ rb_fireruby_raise(status, "Error allocating a SQL statement.");
95
+ }
96
+
97
+ da = (XSQLDA *)ALLOC_N(char, XSQLDA_LENGTH(1));
98
+ if(da == NULL) {
99
+ rb_raise(rb_eNoMemError,
100
+ "Memory allocation failure preparing a statement.");
101
+ }
102
+ da->version = SQLDA_VERSION1;
103
+ da->sqln = 1;
104
+ if(isc_dsql_prepare(status, transaction, statement, 0, sql, dialect,
105
+ da)) {
106
+ free(da);
107
+ rb_fireruby_raise(status, "Error preparing a SQL statement.");
108
+ }
109
+ *outputs = da->sqld;
110
+
111
+ /* Get the parameter count. */
112
+ if(isc_dsql_describe_bind(status, statement, dialect, da)) {
113
+ free(da);
114
+ rb_fireruby_raise(status, "Error determining statement parameters.");
115
+ }
116
+ *inputs = da->sqld;
117
+ free(da);
118
+
119
+ /* Get the statement type details. */
120
+ if(isc_dsql_sql_info(status, statement, 1, list, 20, info) ||
121
+ info[0] != isc_info_sql_stmt_type) {
122
+ rb_fireruby_raise(status, "Error determining SQL statement type.");
123
+ }
124
+ *type = isc_vax_integer(&info[3], isc_vax_integer(&info[1], 2));
125
+ }
126
+
127
+ /**
128
+ * Return affected row count for DML statements
129
+ *
130
+ * @param statement A pointer to the statement handle
131
+ */
132
+ long fb_query_affected(StatementHandle *statement) {
133
+ ISC_STATUS status[ISC_STATUS_LENGTH];
134
+ ISC_STATUS execute_result;
135
+ long result = 0;
136
+ int info = 0,
137
+ done = 0;
138
+ char items[] = {isc_info_sql_records},
139
+ buffer[40],
140
+ *position = buffer + 3;
141
+
142
+ switch(statement->type) {
143
+ case isc_info_sql_stmt_update:
144
+ info = isc_info_req_update_count;
145
+ break;
146
+ case isc_info_sql_stmt_delete:
147
+ info = isc_info_req_delete_count;
148
+ break;
149
+ case isc_info_sql_stmt_insert:
150
+ info = isc_info_req_insert_count;
151
+ break;
152
+ default:
153
+ return (result);
154
+ }
155
+
156
+ if(isc_dsql_sql_info(status, &statement->handle, sizeof(items), items,
157
+ sizeof(buffer), buffer)) {
158
+ rb_fireruby_raise(status, "Error retrieving affected row count.");
159
+ }
160
+
161
+ while(*position != isc_info_end && done == 0) {
162
+ char current = *position++;
163
+ long temp[] = {0, 0};
164
+
165
+ temp[0] = isc_vax_integer(position, 2);
166
+ position += 2;
167
+ temp[1] = isc_vax_integer(position, temp[0]);
168
+ position += temp[0];
169
+
170
+ if(current == info) {
171
+ result = temp[1];
172
+ done = 1;
173
+ }
174
+ }
175
+ return (result);
176
+ }
177
+
178
+ /**
179
+ * Prepare statement parsing arguments array
180
+ *
181
+ * @param args Array containing statement and transaction objects
182
+ *
183
+ * @return Qnil
184
+ */
185
+ VALUE prepareFromArray(VALUE args) {
186
+ VALUE self = rb_ary_entry(args, 0);
187
+ VALUE transaction = rb_ary_entry(args, 1);
188
+ prepareInTransaction(self, transaction);
189
+ return (Qnil);
190
+ }
191
+
192
+ /**
193
+ * Prepare statement within transaction context
194
+ *
195
+ * @param self A reference to the statement object
196
+ *
197
+ * @param transaction A reference to the transaction object
198
+ *
199
+ */
200
+ void prepareInTransaction(VALUE self, VALUE transaction) {
201
+ StatementHandle *hStatement = NULL;
202
+ Data_Get_Struct(self, StatementHandle, hStatement);
203
+
204
+ if(0 == hStatement->handle) {
205
+ ConnectionHandle *hConnection = NULL;
206
+ TransactionHandle *hTransaction = NULL;
207
+ VALUE sql = rb_iv_get(self, "@sql");
208
+ Data_Get_Struct(getStatementConnection(self), ConnectionHandle, hConnection);
209
+ Data_Get_Struct(transaction, TransactionHandle, hTransaction);
210
+
211
+
212
+ fb_prepare(&hConnection->handle, &hTransaction->handle,
213
+ StringValuePtr(sql), &hStatement->handle,
214
+ hStatement->dialect, &hStatement->type, &hStatement->inputs,
215
+ &hStatement->outputs);
216
+ if(hStatement->outputs > 0) {
217
+ /* Allocate the XSQLDA */
218
+ hStatement->output = allocateOutXSQLDA(hStatement->outputs,
219
+ &hStatement->handle,
220
+ hStatement->dialect);
221
+ prepareDataArea(hStatement->output);
222
+ }
223
+ }
224
+ }
51
225
 
52
226
  /**
53
227
  * This function integrates with the Ruby memory control system to provide for
@@ -69,8 +243,9 @@ VALUE allocateStatement(VALUE klass) {
69
243
  statement->handle = 0;
70
244
  statement->type = -1;
71
245
  statement->inputs = 0;
246
+ statement->outputs = 0;
72
247
  statement->dialect = 0;
73
- statement->parameters = NULL;
248
+ statement->output = NULL;
74
249
 
75
250
  return(Data_Wrap_Struct(klass, NULL, statementFree, statement));
76
251
  }
@@ -82,23 +257,18 @@ VALUE allocateStatement(VALUE klass) {
82
257
  * @param self A reference to the Statement object to be initialized.
83
258
  * @param connection A reference to the Connection object that the statement
84
259
  * will execute through.
85
- * @param transaction A reference to the Transaction object that the statement
86
- * will work under.
87
260
  * @param sql A reference to a String object containing the text of
88
261
  * the SQL statement to be executed.
89
- * @param dialect A reference to an integer object specifying the dialect
90
- * to be used in executing the statement. This should be a
91
- * value of between 1 and 3.
92
262
  *
93
263
  * @return A reference to the newly initialized Statement object.
94
264
  *
95
265
  */
96
- VALUE initializeStatement(VALUE self, VALUE connection, VALUE transaction,
97
- VALUE sql, VALUE dialect) {
98
- StatementHandle *statement = NULL;
266
+ VALUE initializeStatement(VALUE self, VALUE connection, VALUE sql) {
267
+ StatementHandle *hStatement = NULL;
99
268
  short setting = 0;
100
- VALUE value = Qnil;
101
269
 
270
+ sql = rb_funcall(sql, rb_intern("to_s"), 0);
271
+
102
272
  /* Validate the inputs. */
103
273
  if(TYPE(connection) == T_DATA &&
104
274
  RDATA(connection)->dfree == (RUBY_DATA_FUNC)connectionFree) {
@@ -109,35 +279,10 @@ VALUE initializeStatement(VALUE self, VALUE connection, VALUE transaction,
109
279
  rb_fireruby_raise(NULL, "Invalid connection specified for statement.");
110
280
  }
111
281
 
112
- if(TYPE(transaction) == T_DATA &&
113
- RDATA(transaction)->dfree == (RUBY_DATA_FUNC)transactionFree) {
114
- if(rb_funcall(transaction, rb_intern("active?"), 0) == Qfalse) {
115
- rb_fireruby_raise(NULL, "Inactive transaction specified for statement.");
116
- }
117
- } else {
118
- rb_fireruby_raise(NULL, "Invalid transaction specified for statement.");
119
- }
120
-
121
- value = rb_funcall(dialect, rb_intern("to_i"), 0);
122
- if(TYPE(value) == T_FIXNUM) {
123
- setting = FIX2INT(value);
124
- if(setting < 1 || setting > 3) {
125
- rb_fireruby_raise(NULL,
126
- "Invalid dialect value specified for statement. " \
127
- "The dialect value must be between 1 and 3.");
128
- }
129
- } else {
130
- rb_fireruby_raise(NULL,
131
- "Invalid dialect value specified for statement. The " \
132
- "dialect value must be between 1 and 3.");
133
- }
134
-
135
- Data_Get_Struct(self, StatementHandle, statement);
282
+ Data_Get_Struct(self, StatementHandle, hStatement);
136
283
  rb_iv_set(self, "@connection", connection);
137
- rb_iv_set(self, "@transaction", transaction);
138
- rb_iv_set(self, "@sql", rb_funcall(sql, rb_intern("to_s"), 0));
139
- rb_iv_set(self, "@dialect", value);
140
- statement->dialect = setting;
284
+ rb_iv_set(self, "@sql", sql);
285
+ hStatement->dialect = 3; //FIXME - from connection
141
286
 
142
287
  return(self);
143
288
  }
@@ -167,21 +312,9 @@ VALUE getStatementSQL(VALUE self) {
167
312
  *
168
313
  */
169
314
  VALUE getStatementConnection(VALUE self) {
170
- return(rb_iv_get(self, "@connection"));
171
- }
172
-
315
+ VALUE connection = rb_iv_get(self, "@connection");
173
316
 
174
- /**
175
- * This function provides the transaction sttribute accessor method for the
176
- * Statement class.
177
- *
178
- * @param self A reference to the Statement object to call the method on.
179
- *
180
- * @return A reference to a Transaction object.
181
- *
182
- */
183
- VALUE getStatementTransaction(VALUE self) {
184
- return(rb_iv_get(self, "@transaction"));
317
+ return(connection);
185
318
  }
186
319
 
187
320
 
@@ -195,11 +328,11 @@ VALUE getStatementTransaction(VALUE self) {
195
328
  *
196
329
  */
197
330
  VALUE getStatementDialect(VALUE self) {
198
- return(rb_iv_get(self, "@dialect"));
331
+ StatementHandle *hStatement = NULL;
332
+ Data_Get_Struct(self, StatementHandle, hStatement);
333
+ return(INT2FIX(hStatement->dialect));
199
334
  }
200
335
 
201
-
202
-
203
336
  /**
204
337
  * This function provides the type attribute accessor method for the Statement
205
338
  * class.
@@ -210,27 +343,10 @@ VALUE getStatementDialect(VALUE self) {
210
343
  *
211
344
  */
212
345
  VALUE getStatementType(VALUE self) {
213
- StatementHandle *statement = NULL;
214
- ConnectionHandle *connection = NULL;
215
- TransactionHandle *transaction = NULL;
216
- int outputs = 0;
217
- VALUE tmp_str = Qnil;
218
-
219
- Data_Get_Struct(self, StatementHandle, statement);
220
- Data_Get_Struct(rb_iv_get(self, "@connection"), ConnectionHandle, connection);
221
- Data_Get_Struct(rb_iv_get(self, "@transaction"), TransactionHandle, transaction);
222
- if(statement->handle == 0) {
223
- tmp_str = rb_iv_get(self, "@sql");
224
- prepare(&connection->handle, &transaction->handle,
225
- StringValuePtr(tmp_str), &statement->handle,
226
- statement->dialect, &statement->type, &statement->inputs,
227
- &outputs);
228
- }
229
-
230
- return(INT2FIX(statement->type));
346
+ StatementHandle *hStatement = getPreparedHandle(self);
347
+ return(INT2FIX(hStatement->type));
231
348
  }
232
349
 
233
-
234
350
  /**
235
351
  * This function provides the parameter count sttribute accessor method for the
236
352
  * Statement class.
@@ -241,329 +357,334 @@ VALUE getStatementType(VALUE self) {
241
357
  *
242
358
  */
243
359
  VALUE getStatementParameterCount(VALUE self) {
244
- StatementHandle *statement = NULL;
245
-
246
- Data_Get_Struct(self, StatementHandle, statement);
247
- if(statement->handle == 0) {
248
- getStatementType(self);
249
- }
250
-
360
+ StatementHandle *statement = getPreparedHandle(self);
251
361
  return(INT2NUM(statement->inputs));
252
362
  }
253
363
 
254
-
255
364
  /**
256
- * This method provides the execute method for the Statement class.
365
+ * Execute statement and take care of implicit transaction management
257
366
  *
258
- * @param self A reference to the Statement object to call the method on.
367
+ * @param self A reference to the statement object
368
+ *
369
+ * @param parameters A reference to the parameter bindings object, can be Qnil
370
+ *
371
+ * @param transaction A reference to the transaction object, can be Qnil,
372
+ * if so - an implicit transaction is started and resolved when appropriate
259
373
  *
260
374
  * @return One of a count of the number of rows affected by the SQL statement,
261
375
  * a ResultSet object for a query or nil.
262
376
  *
263
377
  */
264
- VALUE executeStatement(VALUE self) {
265
- VALUE result;
266
- int type = FIX2INT(getStatementType(self));
267
- long affected = 0;
268
- StatementHandle *statement = NULL;
269
- TransactionHandle *transaction = NULL;
270
-
271
- switch(type) {
272
- case isc_info_sql_stmt_select:
273
- case isc_info_sql_stmt_select_for_upd:
274
- case isc_info_sql_stmt_exec_procedure:
275
- result = rb_result_set_new(rb_iv_get(self, "@connection"),
276
- rb_iv_get(self, "@transaction"),
277
- rb_iv_get(self, "@sql"),
278
- rb_iv_get(self, "@dialect"),
279
- rb_ary_new());
280
- break;
281
-
282
- case isc_info_sql_stmt_insert:
283
- case isc_info_sql_stmt_update:
284
- case isc_info_sql_stmt_delete:
285
- Data_Get_Struct(self, StatementHandle, statement);
286
- Data_Get_Struct(rb_iv_get(self, "@transaction"), TransactionHandle,
287
- transaction);
288
- execute(&transaction->handle, &statement->handle, statement->dialect,
289
- NULL, statement->type, &affected);
290
- result = INT2NUM(affected);
291
- break;
292
-
293
- default:
294
- Data_Get_Struct(self, StatementHandle, statement);
295
- Data_Get_Struct(rb_iv_get(self, "@transaction"), TransactionHandle,
296
- transaction);
297
- execute(&transaction->handle, &statement->handle, statement->dialect,
298
- NULL, statement->type, &affected);
299
- result = Qnil;
378
+ VALUE execAndManageTransaction(VALUE self, VALUE parameters, VALUE transaction) {
379
+ VALUE result = Qnil;
380
+
381
+ if(Qnil == transaction) {
382
+ VALUE args = rb_ary_new();
383
+
384
+ transaction = rb_transaction_new(getStatementConnection(self));
385
+ rb_ary_push(args, self);
386
+ rb_ary_push(args, transaction);
387
+ rb_ary_push(args, parameters);
388
+
389
+ result = rb_rescue(execInTransactionFromArray, args, rescueLocalTransaction, transaction);
390
+ if(isActiveResultSet(result)) {
391
+ resultSetManageTransaction(result);
392
+ } else {
393
+ rb_funcall(transaction, rb_intern("commit"), 0);
394
+ }
395
+ } else {
396
+ result = execInTransaction(self, transaction, parameters);
300
397
  }
301
-
302
- return(result);
398
+ return (result);
303
399
  }
304
400
 
305
-
306
401
  /**
307
- * This method provides the execute method for the Statement class.
402
+ * Execute statement and take care of closing it when appropriate
403
+ *
404
+ * @param self A reference to the statement object
405
+ *
406
+ * @param parameters A reference to the parameter bindings object, can be Qnil
308
407
  *
309
- * @param self A reference to the Statement object to call the method
310
- * on.
311
- * @param parameters An array containing the parameters to be used in
312
- * executing the statement.
408
+ * @param transaction A reference to the transaction object, can be Qnil,
409
+ * if so - an implicit transaction is started and resolved when appropriate
313
410
  *
314
411
  * @return One of a count of the number of rows affected by the SQL statement,
315
412
  * a ResultSet object for a query or nil.
316
413
  *
317
414
  */
318
- VALUE executeStatementFor(VALUE self, VALUE parameters) {
319
- VALUE result = Qnil;
320
- int type = FIX2INT(getStatementType(self));
321
- long affected = 0;
322
- StatementHandle *statement = NULL;
323
- TransactionHandle *transaction = NULL;
324
-
325
- if(type == isc_info_sql_stmt_select ||
326
- type == isc_info_sql_stmt_select_for_upd) {
327
- /* Execute the statement via a ResultSet object. */
328
- result = rb_result_set_new(rb_iv_get(self, "@connection"),
329
- rb_iv_get(self, "@transaction"),
330
- rb_iv_get(self, "@sql"),
331
- rb_iv_get(self, "@dialect"),
332
- parameters);
415
+ VALUE execAndManageStatement(VALUE self, VALUE parameters, VALUE transaction) {
416
+ VALUE result = Qnil,
417
+ args = rb_ary_new();
418
+
419
+ rb_ary_push(args, self);
420
+ rb_ary_push(args, parameters);
421
+ rb_ary_push(args, transaction);
422
+ result = rb_rescue(execStatementFromArray, args, rescueStatement, self);
423
+ if(isActiveResultSet(result)) {
424
+ resultSetManageStatement(result);
333
425
  } else {
334
- /* Check that sufficient parameters have been specified. */
335
- Data_Get_Struct(self, StatementHandle, statement);
336
- if(statement->inputs > 0) {
337
- VALUE value = Qnil;
338
- int size = 0;
339
-
340
- if(parameters == Qnil) {
341
- rb_fireruby_raise(NULL,
342
- "Empty parameter list specified for statement.");
343
- }
344
-
345
- value = rb_funcall(parameters, rb_intern("size"), 0);
346
- size = TYPE(value) == T_FIXNUM ? FIX2INT(value) : NUM2INT(value);
347
- if(size < statement->inputs) {
348
- rb_fireruby_raise(NULL,
349
- "Insufficient parameters specified for statement.");
350
- }
351
-
352
- /* Allocate the XSQLDA and populate it. */
353
- statement->parameters = allocateInXSQLDA(statement->inputs,
354
- &statement->handle,
355
- statement->dialect);
356
- prepareDataArea(statement->parameters);
357
- setParameters(statement->parameters, parameters, self);
358
- }
359
-
360
- /* Execute the statement. */
361
- Data_Get_Struct(self, StatementHandle, statement);
362
- Data_Get_Struct(rb_iv_get(self, "@transaction"), TransactionHandle,
363
- transaction);
364
- execute(&transaction->handle, &statement->handle, statement->dialect,
365
- statement->parameters, statement->type, &affected);
366
- if(type == isc_info_sql_stmt_insert ||
367
- type == isc_info_sql_stmt_update ||
368
- type == isc_info_sql_stmt_delete) {
369
- result = INT2NUM(affected);
370
- }
426
+ closeStatement(self);
371
427
  }
372
-
373
- return(result);
428
+ return (result);
374
429
  }
375
430
 
376
-
431
+ /**
432
+ * Resolve (rollback) transaction in rescue block
433
+ *
434
+ * @param transaction A reference to the transaction object
435
+ *
436
+ * @param error A reference to the exception object
437
+ *
438
+ * @return Qnil
439
+ *
440
+ */
441
+ VALUE rescueLocalTransaction(VALUE transaction, VALUE error) {
442
+ rb_funcall(transaction, rb_intern("rollback"), 0);
443
+ rb_exc_raise(error);
444
+ return(Qnil);
445
+ }
377
446
 
378
447
  /**
379
- * This function provides the close method for the Statement class.
448
+ * Close statement in rescue block
380
449
  *
381
- * @param self A reference to the Statement object to call the method on.
450
+ * @param statement A reference to the statement object
382
451
  *
383
- * @return A reference to the newly closed Statement object.
452
+ * @param error A reference to the exception object
453
+ *
454
+ * @return Qnil
384
455
  *
385
456
  */
386
- VALUE closeStatement(VALUE self) {
387
- StatementHandle *statement = NULL;
457
+ VALUE rescueStatement(VALUE statement, VALUE error) {
458
+ rb_funcall(statement, rb_intern("close"), 0);
459
+ rb_exc_raise(error);
460
+ return(Qnil);
461
+ }
388
462
 
389
- Data_Get_Struct(self, StatementHandle, statement);
390
- if(statement->handle != 0) {
391
- ISC_STATUS status[ISC_STATUS_LENGTH];
463
+ /**
464
+ * Execute a statement within a transaction context parsing arguments array
465
+ *
466
+ * @param args Array containing statement, transaction and parameter bindings objects
467
+ *
468
+ * @return One of a count of the number of rows affected by the SQL statement,
469
+ * a ResultSet object for a query or nil.
470
+ */
471
+ VALUE execInTransactionFromArray(VALUE args) {
472
+ VALUE self = rb_ary_entry(args, 0);
473
+ VALUE transaction = rb_ary_entry(args, 1);
474
+ VALUE parameters = rb_ary_entry(args, 2);
392
475
 
393
- if(isc_dsql_free_statement(status, &statement->handle, DSQL_drop)) {
394
- rb_fireruby_raise(status, "Error closing statement.");
395
- }
476
+ return(execInTransaction(self, transaction, parameters));
477
+ }
396
478
 
397
- if(statement->parameters != NULL) {
398
- releaseDataArea(statement->parameters);
399
- }
479
+ /**
480
+ * Check if the statement is a select statement
481
+ *
482
+ * @param hStatement A pointer to the statement handle
483
+ *
484
+ * @return 1 if the statement is a select statement, 0 otherwise
485
+ */
486
+ short isCursorStatement(StatementHandle *hStatement) {
487
+ switch(hStatement->type) {
488
+ case isc_info_sql_stmt_select:
489
+ case isc_info_sql_stmt_select_for_upd:
490
+ return 1;
491
+ default:
492
+ return 0;
400
493
  }
401
-
402
- return(self);
403
494
  }
404
495
 
405
-
406
496
  /**
407
- * This function prepares a Firebird SQL statement for execution.
497
+ * Execute a statement within a transaction context
408
498
  *
409
- * @param connection A pointer to the database connection that will be used
410
- * to prepare the statement.
411
- * @param transaction A pointer to the database transaction that will be used
412
- * to prepare the statement.
413
- * @param sql A string containing the SQL statement to be executed.
414
- * @param statement A pointer to a Firebird statement that will be prepared.
415
- * @param dialect A short integer containing the SQL dialect to be used in
416
- * preparing the statement.
417
- * @param type A pointer to an integer that will be assigned the type
418
- * of the SQL statement prepared.
419
- * @param inputs A pointer to an integer that will be assigned a count of
420
- * the parameters for the SQL statement.
421
- * @param outputs A pointer to an integer that will be assigned a count of
422
- * the output columns for the SQL statement.
499
+ * @param self A reference to the statement object
500
+ *
501
+ * @param transaction A reference to the transaction object
423
502
  *
503
+ * @param parameters A reference to the parameter bindings object
504
+ *
505
+ * @return One of a count of the number of rows affected by the SQL statement,
506
+ * a ResultSet object for a query or nil.
424
507
  */
425
- void prepare(isc_db_handle *connection, isc_tr_handle *transaction,
426
- char *sql, isc_stmt_handle *statement, short dialect,
427
- int *type, int *inputs, int *outputs) {
508
+ VALUE execInTransaction(VALUE self, VALUE transaction, VALUE parameters) {
509
+ VALUE result = Qnil;
510
+ long affected = 0;
511
+ StatementHandle *hStatement = NULL;
512
+ TransactionHandle *hTransaction = NULL;
513
+ XSQLDA *bindings = NULL;
428
514
  ISC_STATUS status[ISC_STATUS_LENGTH];
429
- XSQLDA *da = NULL;
430
- char list[] = {isc_info_sql_stmt_type},
431
- info[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
432
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
515
+ ISC_STATUS execute_result;
433
516
 
434
- /* Prepare the statement. */
435
- if(isc_dsql_allocate_statement(status, connection, statement)) {
436
- rb_fireruby_raise(status, "Error allocating a SQL statement.");
437
- }
517
+ prepareInTransaction(self, transaction);
518
+ Data_Get_Struct(self, StatementHandle, hStatement);
519
+ if(hStatement->inputs > 0) {
520
+ VALUE value = Qnil;
521
+ int size = 0;
438
522
 
439
- da = (XSQLDA *)ALLOC_N(char, XSQLDA_LENGTH(1));
440
- if(da == NULL) {
441
- rb_raise(rb_eNoMemError,
442
- "Memory allocation failure preparing a statement.");
443
- }
444
- da->version = SQLDA_VERSION1;
445
- da->sqln = 1;
446
- if(isc_dsql_prepare(status, transaction, statement, 0, sql, dialect,
447
- da)) {
448
- free(da);
449
- rb_fireruby_raise(status, "Error preparing a SQL statement.");
450
- }
451
- *outputs = da->sqld;
523
+ if(parameters == Qnil) {
524
+ rb_fireruby_raise(NULL,
525
+ "Empty parameter list specified for statement.");
526
+ }
452
527
 
453
- /* Get the parameter count. */
454
- if(isc_dsql_describe_bind(status, statement, dialect, da)) {
455
- free(da);
456
- rb_fireruby_raise(status, "Error determining statement parameters.");
457
- }
458
- *inputs = da->sqld;
459
- free(da);
528
+ value = rb_funcall(parameters, rb_intern("size"), 0);
529
+ size = TYPE(value) == T_FIXNUM ? FIX2INT(value) : NUM2INT(value);
530
+ if(size < hStatement->inputs) {
531
+ rb_fireruby_raise(NULL,
532
+ "Insufficient parameters specified for statement.");
533
+ }
460
534
 
461
- /* Get the statement type details. */
462
- if(isc_dsql_sql_info(status, statement, 1, list, 20, info) ||
463
- info[0] != isc_info_sql_stmt_type) {
464
- rb_fireruby_raise(status, "Error determining SQL statement type.");
535
+ /* Allocate the XSQLDA */
536
+ bindings = allocateInXSQLDA(hStatement->inputs, &hStatement->handle, hStatement->dialect);
537
+ prepareDataArea(bindings);
538
+ setParameters(bindings, parameters, transaction, getStatementConnection(self));
465
539
  }
466
- *type = isc_vax_integer(&info[3], isc_vax_integer(&info[1], 2));
467
- }
468
540
 
541
+ /* Execute the statement. */
542
+ Data_Get_Struct(transaction, TransactionHandle, hTransaction);
469
543
 
470
- /**
471
- * This function executes a previously prepare SQL statement.
472
- *
473
- * @param transaction A pointer to the Firebird transaction handle to be used in
474
- * executing the statement.
475
- * @param statement A pointer to the Firebird statement handle to be used in
476
- * executing the statement.
477
- * @param dialect Database dialect used in the statement
478
- *
479
- * @param parameters A pointer to the XSQLDA block that contains the input
480
- * parameters for the SQL statement.
481
- * @param type A integer containing the type details relating to the
482
- * statement being executed.
483
- * @param affected A pointer to a long integer that will be assigned a count
484
- * of rows affected by inserts, updates or deletes.
485
- * @param output A pointer to the XSQLDA block that will hold the output
486
- * data generated by the execution.
487
- */
488
- void execute_2(isc_tr_handle *transaction, isc_stmt_handle *statement,
489
- short dialect, XSQLDA *parameters, int type, long *affected, XSQLDA *output) {
490
- ISC_STATUS status[ISC_STATUS_LENGTH];
491
- ISC_STATUS execute_result;
492
-
493
- if(output) {
494
- execute_result = isc_dsql_execute2(status, transaction, statement, dialect, parameters, output);
544
+ if (isCursorStatement(hStatement)) {
545
+ execute_result = isc_dsql_execute(status, &hTransaction->handle, &hStatement->handle, hStatement->dialect, bindings);
495
546
  } else {
496
- execute_result = isc_dsql_execute(status, transaction, statement, dialect, parameters);
547
+ execute_result = isc_dsql_execute2(status, &hTransaction->handle, &hStatement->handle, hStatement->dialect, bindings, hStatement->output);
548
+ }
549
+ if(bindings) {
550
+ releaseDataArea(bindings);
497
551
  }
498
552
  if(execute_result) {
499
553
  rb_fireruby_raise(status, "Error executing SQL statement.");
500
554
  }
501
-
502
- /* Check if a row count is needed. */
503
- if(type == isc_info_sql_stmt_update || type == isc_info_sql_stmt_delete ||
504
- type == isc_info_sql_stmt_insert) {
505
- int info = 0,
506
- done = 0;
507
- char items[] = {isc_info_sql_records},
508
- buffer[40],
509
- *position = buffer + 3;
510
-
511
- switch(type) {
512
- case isc_info_sql_stmt_update:
513
- info = isc_info_req_update_count;
514
- break;
515
-
516
- case isc_info_sql_stmt_delete:
517
- info = isc_info_req_delete_count;
518
- break;
519
-
520
- case isc_info_sql_stmt_insert:
521
- info = isc_info_req_insert_count;
522
- break;
555
+ if (hStatement->output) {
556
+ result = rb_result_set_new(self, transaction);
557
+ if(rb_block_given_p()) {
558
+ result = yieldResultsRows(result);
523
559
  }
560
+ } else {
561
+ result = INT2NUM(fb_query_affected(hStatement));
562
+ }
524
563
 
525
- if(isc_dsql_sql_info(status, statement, sizeof(items), items,
526
- sizeof(buffer), buffer)) {
527
- rb_fireruby_raise(status, "Error retrieving affected row count.");
528
- }
564
+ return(result);
565
+ }
529
566
 
530
- while(*position != isc_info_end && done == 0) {
531
- char current = *position++;
532
- long temp[] = {0, 0};
567
+ /**
568
+ * This method provides the exec method for the Statement class.
569
+ *
570
+ * @param self A reference to the Statement object to call the method on.
571
+ *
572
+ * @param argc Parameters count
573
+ *
574
+ * @param argv Parameters array
575
+ *
576
+ * @return One of a count of the number of rows affected by the SQL statement,
577
+ * a ResultSet object for a query or nil.
578
+ *
579
+ */
580
+ VALUE execStatement(int argc, VALUE *argv, VALUE self) {
581
+ VALUE transaction, parameters = Qnil;
533
582
 
534
- temp[0] = isc_vax_integer(position, 2);
535
- position += 2;
536
- temp[1] = isc_vax_integer(position, temp[0]);
537
- position += temp[0];
583
+ rb_scan_args(argc, argv, "02", &parameters, &transaction);
584
+ return execAndManageTransaction(self, parameters, transaction);
585
+ }
538
586
 
539
- if(current == info) {
540
- *affected = temp[1];
541
- done = 1;
587
+ /**
588
+ * This method provides the exec_and_close method for the Statement class.
589
+ *
590
+ * @param self A reference to the Statement object to call the method on.
591
+ *
592
+ * @param argc Parameters count
593
+ *
594
+ * @param argv Parameters array
595
+ *
596
+ * @return One of a count of the number of rows affected by the SQL statement,
597
+ * a ResultSet object for a query or nil.
598
+ *
599
+ */
600
+ VALUE execAndCloseStatement(int argc, VALUE *argv, VALUE self) {
601
+ VALUE parameters, transaction = Qnil;
602
+
603
+ rb_scan_args(argc, argv, "02", &parameters, &transaction);
604
+ return execAndManageStatement(self, parameters, transaction);
605
+ }
606
+
607
+ /**
608
+ * Clean up statement handle - release allocated resources
609
+ *
610
+ * @param statement A pointer to the statement handle
611
+ *
612
+ * @param raise_errors If this parameter is 0 - no exceptions are raised from this function,
613
+ * otherwise an exception is raised whenever a problem occurs while releasing resources.
614
+ *
615
+ */
616
+ void cleanUpStatement(StatementHandle *statement, int raise_errors) {
617
+ if(statement->handle) {
618
+ ISC_STATUS status[ISC_STATUS_LENGTH];
619
+ if(isc_dsql_free_statement(status, &statement->handle, DSQL_drop)) {
620
+ if(raise_errors) {
621
+ rb_fireruby_raise(status, "Error closing statement.");
542
622
  }
543
623
  }
624
+ if(statement->output != NULL) {
625
+ releaseDataArea(statement->output);
626
+ statement->output = NULL;
627
+ }
628
+ statement->handle = 0;
544
629
  }
545
630
  }
546
631
 
632
+ /**
633
+ * This function provides the close method for the Statement class.
634
+ *
635
+ * @param self A reference to the Statement object to call the method on.
636
+ *
637
+ * @return A reference to the newly closed Statement object.
638
+ *
639
+ */
640
+ VALUE closeStatement(VALUE self) {
641
+ StatementHandle *statement = NULL;
642
+ Data_Get_Struct(self, StatementHandle, statement);
643
+ cleanUpStatement(statement, 1);
644
+ return(self);
645
+ }
646
+
647
+ /**
648
+ * This function provides the prepared? method for the Statement class.
649
+ *
650
+ * @param self A reference to the Statement object to call the method on.
651
+ *
652
+ * @return Qtrue if the statement is prepared, Qfalse otherwise.
653
+ *
654
+ */
655
+ VALUE getStatementPrepared(VALUE self) {
656
+ VALUE result = Qfalse;
657
+ StatementHandle *statement = NULL;
658
+ Data_Get_Struct(self, StatementHandle, statement);
659
+
660
+ if(statement->handle) {
661
+ result = Qtrue;
662
+ }
663
+
664
+ return(result);
665
+ }
547
666
 
548
667
  /**
549
- * This function executes a previously prepare SQL statement.
550
- *
551
- * @param transaction A pointer to the Firebird transaction handle to be used in
552
- * executing the statement.
553
- * @param statement A pointer to the Firebird statement handle to be used in
554
- * executing the statement.
555
- * @param dialect Database dialect used in the statement
556
- *
557
- * @param parameters A pointer to the XSQLDA block that contains the input
558
- * parameters for the SQL statement.
559
- * @param type A integer containing the type details relating to the
560
- * statement being executed.
561
- * @param affected A pointer to a long integer that will be assigned a count
562
- * of rows affected by inserts, updates or deletes.
668
+ * This function provides the prepare method for the Statement class.
669
+ *
670
+ * @param self A reference to the Statement object to call the method on.
671
+ *
672
+ * @param argc Parameters count
673
+ *
674
+ * @param argv Parameters array
675
+ *
676
+ * @return A reference to the statement object
677
+ *
563
678
  */
564
- void execute(isc_tr_handle *transaction, isc_stmt_handle *statement,
565
- short dialect, XSQLDA *parameters, int type, long *affected) {
566
- execute_2(transaction, statement, dialect, parameters, type, affected, NULL);
679
+ static VALUE prepareStatement(int argc, VALUE *argv, VALUE self) {
680
+ VALUE transaction = Qnil;
681
+ rb_scan_args(argc, argv, "01", &transaction);
682
+ if(Qnil == transaction) {
683
+ getPreparedHandle(self);
684
+ } else {
685
+ prepareInTransaction(self, transaction);
686
+ }
687
+ return (self);
567
688
  }
568
689
 
569
690
  /**
@@ -571,114 +692,74 @@ void execute(isc_tr_handle *transaction, isc_stmt_handle *statement,
571
692
  *
572
693
  * @param connection A reference to a Connection object that will be used by
573
694
  * the Statement.
574
- * @param transaction A reference to a Transaction object that will be used
575
- * by the Statement.
576
695
  * @param sql A reference to a String object containing the text of
577
696
  * of a SQL statement for the Statement.
578
- * @param dialect A reference to an integer object that contains the SQL
579
- * dialect setting for the Statement.
580
697
  *
581
698
  * @return A reference to the newly created Statement object.
582
699
  *
583
700
  */
584
- VALUE rb_statement_new(VALUE connection, VALUE transaction, VALUE sql,
585
- VALUE dialect) {
701
+ VALUE rb_statement_new(VALUE connection, VALUE sql) {
586
702
  VALUE statement = allocateStatement(cStatement);
587
703
 
588
- initializeStatement(statement, connection, transaction, sql, dialect);
704
+ initializeStatement(statement, connection, sql);
589
705
 
590
706
  return(statement);
591
707
  }
592
708
 
593
-
594
709
  /**
595
- * This function provides a programmatic way of executing a Statement object
596
- * without parameters.
597
- *
598
- * @param statement A reference to the statement object to be executed.
710
+ * Execute statement parsing arguments array
599
711
  *
600
- * @return A reference to the results of executing the statement.
712
+ * @param args Array containing statement, parameters and transaction objects
601
713
  *
714
+ * @return One of a count of the number of rows affected by the SQL statement,
715
+ * a ResultSet object for a query or nil.
602
716
  */
603
- VALUE rb_execute_statement(VALUE statement) {
604
- return(executeStatement(statement));
605
- }
606
-
717
+ VALUE execStatementFromArray(VALUE args) {
718
+ VALUE self = rb_ary_entry(args, 0);
719
+ VALUE params = rb_ary_entry(args, 1);
720
+ VALUE transaction = rb_ary_entry(args, 2);
607
721
 
608
- /**
609
- * This function provides a programmatic way of executing a Statement object
610
- * with parameters.
611
- *
612
- * @param statement A reference to the statement object to be executed.
613
- * @param parameters A reference to an array of parameters to be used in the
614
- * execution of the statement.
615
- *
616
- * @return A reference to the results of executing the statement.
617
- *
618
- */
619
- VALUE rb_execute_statement_for(VALUE statement, VALUE parameters) {
620
- return(executeStatementFor(statement, parameters));
722
+ return execAndManageTransaction(self, params, transaction);
621
723
  }
622
724
 
623
-
624
725
  /**
625
- * This function provides a programmatic way of executing a SQL
726
+ * This function provides a programmatic way of executing a parametrized SQL
626
727
  * within transaction
627
728
  *
628
729
  * @param connection A reference to the connection object.
629
- * @param transaction A reference to the transaction object.
630
730
  * @param sql SQL text.
731
+ * @param params Array containing parameter values for the statement.
732
+ * @param transaction A reference to the transaction object.
631
733
  *
632
734
  * @return A reference to the results of executing the statement.
633
735
  *
634
736
  */
635
- VALUE rb_execute_sql(VALUE connection, VALUE transaction, VALUE sql) {
636
- VALUE results = Qnil,
637
- statement = rb_statement_new(connection, transaction, sql, INT2FIX(3));
638
-
639
- results = rb_execute_statement(statement);
640
- if(results != Qnil && rb_obj_is_kind_of(results, rb_cInteger) == Qfalse) {
641
- if(rb_block_given_p()) {
642
- VALUE row = rb_funcall(results, rb_intern("fetch"), 0),
643
- last = Qnil;
644
-
645
- while(row != Qnil) {
646
- last = rb_yield(row);
647
- row = rb_funcall(results, rb_intern("fetch"), 0);
648
- }
649
- rb_funcall(results, rb_intern("close"), 0);
650
- results = last;
651
- }
652
- }
653
- rb_statement_close(statement);
654
-
655
- return(results);
656
- }
657
-
658
- /**
659
- * This method retrieves the type information for a Statement object.
660
- *
661
- * @param statement A reference to a Statement object.
662
- *
663
- * @return A reference to an integer containing the statement type details.
664
- *
665
- */
666
- VALUE rb_get_statement_type(VALUE statement) {
667
- return(getStatementType(statement));
737
+ VALUE rb_execute_sql(VALUE connection, VALUE sql, VALUE params, VALUE transaction) {
738
+ return execAndManageStatement(rb_statement_new(connection, sql), params, transaction);
668
739
  }
669
740
 
670
-
671
741
  /**
672
- * This function provides a programmatic means of closing a Statement object.
742
+ * This function guarantess that the returned statement handle is prepared
673
743
  *
674
- * @param statement A reference to the Statement object to be closed.
744
+ * @param self A reference to the Statement object to call the method on.
675
745
  *
676
746
  */
677
- void rb_statement_close(VALUE statement) {
678
- closeStatement(statement);
747
+ StatementHandle* getPreparedHandle(VALUE self) {
748
+ StatementHandle *hStatement;
749
+ Data_Get_Struct(self, StatementHandle, hStatement);
750
+ if(0 == hStatement->handle) {
751
+ VALUE transaction = rb_transaction_new(getStatementConnection(self));
752
+ VALUE args = rb_ary_new();
753
+
754
+ rb_ary_push(args, self);
755
+ rb_ary_push(args, transaction);
756
+
757
+ rb_rescue(prepareFromArray, args, rescueLocalTransaction, transaction);
758
+ rb_funcall(transaction, rb_intern("commit"), 0);
759
+ }
760
+ return (hStatement);
679
761
  }
680
762
 
681
-
682
763
  /**
683
764
  * This function integrates with the Ruby garbage collector to release the
684
765
  * resources associated with a Statement object that is being collected.
@@ -689,24 +770,11 @@ void rb_statement_close(VALUE statement) {
689
770
  */
690
771
  void statementFree(void *handle) {
691
772
  if(handle != NULL) {
692
- StatementHandle *statement = (StatementHandle *)handle;
693
-
694
- if(statement->handle != 0) {
695
- ISC_STATUS status[ISC_STATUS_LENGTH];
696
-
697
- isc_dsql_free_statement(status, &statement->handle, DSQL_drop);
698
- }
699
-
700
- if(statement->parameters) {
701
- releaseDataArea(statement->parameters);
702
- }
703
- free(statement);
773
+ cleanUpStatement((StatementHandle *)handle, 0);
774
+ free(handle);
704
775
  }
705
776
  }
706
777
 
707
-
708
-
709
-
710
778
  /**
711
779
  * This function initializes the Statement class within the Ruby environment.
712
780
  * The class is established under the module specified to the function.
@@ -717,17 +785,18 @@ void statementFree(void *handle) {
717
785
  void Init_Statement(VALUE module) {
718
786
  cStatement = rb_define_class_under(module, "Statement", rb_cObject);
719
787
  rb_define_alloc_func(cStatement, allocateStatement);
720
- rb_define_method(cStatement, "initialize", initializeStatement, 4);
788
+ rb_define_method(cStatement, "initialize", initializeStatement, 2);
721
789
  rb_define_method(cStatement, "initialize_copy", forbidObjectCopy, 1);
722
790
  rb_define_method(cStatement, "sql", getStatementSQL, 0);
723
791
  rb_define_method(cStatement, "connection", getStatementConnection, 0);
724
- rb_define_method(cStatement, "transaction", getStatementTransaction, 0);
725
792
  rb_define_method(cStatement, "dialect", getStatementDialect, 0);
726
793
  rb_define_method(cStatement, "type", getStatementType, 0);
727
- rb_define_method(cStatement, "execute", executeStatement, 0);
728
- rb_define_method(cStatement, "execute_for", executeStatementFor, 1);
794
+ rb_define_method(cStatement, "exec", execStatement, -1);
795
+ rb_define_method(cStatement, "exec_and_close", execAndCloseStatement, -1);
729
796
  rb_define_method(cStatement, "close", closeStatement, 0);
730
797
  rb_define_method(cStatement, "parameter_count", getStatementParameterCount, 0);
798
+ rb_define_method(cStatement, "prepare", prepareStatement, -1);
799
+ rb_define_method(cStatement, "prepared?", getStatementPrepared, 0);
731
800
 
732
801
  rb_define_const(cStatement, "SELECT_STATEMENT",
733
802
  INT2FIX(isc_info_sql_stmt_select));