rubyfb 0.5.9 → 0.6

Sign up to get free protection for your applications and to get access to all the features.
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));