ext/Statement.c in rubyfb-0.5.9 vs ext/Statement.c in rubyfb-0.6
- old
+ new
@@ -32,26 +32,200 @@
#include "TypeMap.h"
#include "ResultSet.h"
/* Function prototypes. */
static VALUE allocateStatement(VALUE);
-static VALUE initializeStatement(VALUE, VALUE, VALUE, VALUE, VALUE);
+static VALUE initializeStatement(VALUE, VALUE, VALUE);
static VALUE getStatementSQL(VALUE);
-static VALUE getStatementTransaction(VALUE);
static VALUE getStatementConnection(VALUE);
static VALUE getStatementDialect(VALUE);
static VALUE getStatementType(VALUE);
static VALUE getStatementParameterCount(VALUE);
-static VALUE executeStatement(VALUE);
-static VALUE executeStatementFor(VALUE, VALUE);
+static VALUE execStatement(int, VALUE*, VALUE);
+static VALUE execAndCloseStatement(int, VALUE*, VALUE);
static VALUE closeStatement(VALUE);
+static VALUE getStatementPrepared(VALUE);
+static VALUE prepareStatement(int, VALUE*, VALUE);
+VALUE execAndManageTransaction(VALUE, VALUE, VALUE);
+VALUE execAndManageStatement(VALUE, VALUE, VALUE);
+VALUE rescueLocalTransaction(VALUE, VALUE);
+VALUE execStatementFromArray(VALUE);
+VALUE rescueStatement(VALUE, VALUE);
+VALUE execInTransactionFromArray(VALUE);
+VALUE execInTransaction(VALUE, VALUE, VALUE);
+void prepareInTransaction(VALUE, VALUE);
+VALUE prepareFromArray(VALUE);
+void statementFree(void *);
+StatementHandle* getPreparedHandle(VALUE self);
+
/* Globals. */
VALUE cStatement;
+/**
+ * This function prepares a Firebird SQL statement for execution.
+ *
+ * @param connection A pointer to the database connection that will be used
+ * to prepare the statement.
+ * @param transaction A pointer to the database transaction that will be used
+ * to prepare the statement.
+ * @param sql A string containing the SQL statement to be executed.
+ * @param statement A pointer to a Firebird statement that will be prepared.
+ * @param dialect A short integer containing the SQL dialect to be used in
+ * preparing the statement.
+ * @param type A pointer to an integer that will be assigned the type
+ * of the SQL statement prepared.
+ * @param inputs A pointer to an integer that will be assigned a count of
+ * the parameters for the SQL statement.
+ * @param outputs A pointer to an integer that will be assigned a count of
+ * the output columns for the SQL statement.
+ *
+ */
+void fb_prepare(isc_db_handle *connection, isc_tr_handle *transaction,
+ char *sql, isc_stmt_handle *statement, short dialect,
+ int *type, int *inputs, int *outputs) {
+ ISC_STATUS status[ISC_STATUS_LENGTH];
+ XSQLDA *da = NULL;
+ char list[] = {isc_info_sql_stmt_type},
+ info[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+ /* Prepare the statement. */
+ if(isc_dsql_allocate_statement(status, connection, statement)) {
+ rb_fireruby_raise(status, "Error allocating a SQL statement.");
+ }
+
+ da = (XSQLDA *)ALLOC_N(char, XSQLDA_LENGTH(1));
+ if(da == NULL) {
+ rb_raise(rb_eNoMemError,
+ "Memory allocation failure preparing a statement.");
+ }
+ da->version = SQLDA_VERSION1;
+ da->sqln = 1;
+ if(isc_dsql_prepare(status, transaction, statement, 0, sql, dialect,
+ da)) {
+ free(da);
+ rb_fireruby_raise(status, "Error preparing a SQL statement.");
+ }
+ *outputs = da->sqld;
+
+ /* Get the parameter count. */
+ if(isc_dsql_describe_bind(status, statement, dialect, da)) {
+ free(da);
+ rb_fireruby_raise(status, "Error determining statement parameters.");
+ }
+ *inputs = da->sqld;
+ free(da);
+
+ /* Get the statement type details. */
+ if(isc_dsql_sql_info(status, statement, 1, list, 20, info) ||
+ info[0] != isc_info_sql_stmt_type) {
+ rb_fireruby_raise(status, "Error determining SQL statement type.");
+ }
+ *type = isc_vax_integer(&info[3], isc_vax_integer(&info[1], 2));
+}
+
/**
+ * Return affected row count for DML statements
+ *
+ * @param statement A pointer to the statement handle
+ */
+long fb_query_affected(StatementHandle *statement) {
+ ISC_STATUS status[ISC_STATUS_LENGTH];
+ ISC_STATUS execute_result;
+ long result = 0;
+ int info = 0,
+ done = 0;
+ char items[] = {isc_info_sql_records},
+ buffer[40],
+ *position = buffer + 3;
+
+ switch(statement->type) {
+ case isc_info_sql_stmt_update:
+ info = isc_info_req_update_count;
+ break;
+ case isc_info_sql_stmt_delete:
+ info = isc_info_req_delete_count;
+ break;
+ case isc_info_sql_stmt_insert:
+ info = isc_info_req_insert_count;
+ break;
+ default:
+ return (result);
+ }
+
+ if(isc_dsql_sql_info(status, &statement->handle, sizeof(items), items,
+ sizeof(buffer), buffer)) {
+ rb_fireruby_raise(status, "Error retrieving affected row count.");
+ }
+
+ while(*position != isc_info_end && done == 0) {
+ char current = *position++;
+ long temp[] = {0, 0};
+
+ temp[0] = isc_vax_integer(position, 2);
+ position += 2;
+ temp[1] = isc_vax_integer(position, temp[0]);
+ position += temp[0];
+
+ if(current == info) {
+ result = temp[1];
+ done = 1;
+ }
+ }
+ return (result);
+}
+
+/**
+ * Prepare statement parsing arguments array
+ *
+ * @param args Array containing statement and transaction objects
+ *
+ * @return Qnil
+ */
+VALUE prepareFromArray(VALUE args) {
+ VALUE self = rb_ary_entry(args, 0);
+ VALUE transaction = rb_ary_entry(args, 1);
+ prepareInTransaction(self, transaction);
+ return (Qnil);
+}
+
+/**
+ * Prepare statement within transaction context
+ *
+ * @param self A reference to the statement object
+ *
+ * @param transaction A reference to the transaction object
+ *
+ */
+void prepareInTransaction(VALUE self, VALUE transaction) {
+ StatementHandle *hStatement = NULL;
+ Data_Get_Struct(self, StatementHandle, hStatement);
+
+ if(0 == hStatement->handle) {
+ ConnectionHandle *hConnection = NULL;
+ TransactionHandle *hTransaction = NULL;
+ VALUE sql = rb_iv_get(self, "@sql");
+ Data_Get_Struct(getStatementConnection(self), ConnectionHandle, hConnection);
+ Data_Get_Struct(transaction, TransactionHandle, hTransaction);
+
+
+ fb_prepare(&hConnection->handle, &hTransaction->handle,
+ StringValuePtr(sql), &hStatement->handle,
+ hStatement->dialect, &hStatement->type, &hStatement->inputs,
+ &hStatement->outputs);
+ if(hStatement->outputs > 0) {
+ /* Allocate the XSQLDA */
+ hStatement->output = allocateOutXSQLDA(hStatement->outputs,
+ &hStatement->handle,
+ hStatement->dialect);
+ prepareDataArea(hStatement->output);
+ }
+ }
+}
+
+/**
* This function integrates with the Ruby memory control system to provide for
* the allocation of Statement objects.
*
* @param klass A reference to the Statement Class object.
*
@@ -67,12 +241,13 @@
}
statement->handle = 0;
statement->type = -1;
statement->inputs = 0;
+ statement->outputs = 0;
statement->dialect = 0;
- statement->parameters = NULL;
+ statement->output = NULL;
return(Data_Wrap_Struct(klass, NULL, statementFree, statement));
}
@@ -80,66 +255,36 @@
* This function provides the initialize method for the Statement class.
*
* @param self A reference to the Statement object to be initialized.
* @param connection A reference to the Connection object that the statement
* will execute through.
- * @param transaction A reference to the Transaction object that the statement
- * will work under.
* @param sql A reference to a String object containing the text of
* the SQL statement to be executed.
- * @param dialect A reference to an integer object specifying the dialect
- * to be used in executing the statement. This should be a
- * value of between 1 and 3.
*
* @return A reference to the newly initialized Statement object.
*
*/
-VALUE initializeStatement(VALUE self, VALUE connection, VALUE transaction,
- VALUE sql, VALUE dialect) {
- StatementHandle *statement = NULL;
+VALUE initializeStatement(VALUE self, VALUE connection, VALUE sql) {
+ StatementHandle *hStatement = NULL;
short setting = 0;
- VALUE value = Qnil;
+ sql = rb_funcall(sql, rb_intern("to_s"), 0);
+
/* Validate the inputs. */
if(TYPE(connection) == T_DATA &&
RDATA(connection)->dfree == (RUBY_DATA_FUNC)connectionFree) {
if(rb_funcall(connection, rb_intern("open?"), 0) == Qfalse) {
rb_fireruby_raise(NULL, "Closed connection specified for statement.");
}
} else {
rb_fireruby_raise(NULL, "Invalid connection specified for statement.");
}
- if(TYPE(transaction) == T_DATA &&
- RDATA(transaction)->dfree == (RUBY_DATA_FUNC)transactionFree) {
- if(rb_funcall(transaction, rb_intern("active?"), 0) == Qfalse) {
- rb_fireruby_raise(NULL, "Inactive transaction specified for statement.");
- }
- } else {
- rb_fireruby_raise(NULL, "Invalid transaction specified for statement.");
- }
-
- value = rb_funcall(dialect, rb_intern("to_i"), 0);
- if(TYPE(value) == T_FIXNUM) {
- setting = FIX2INT(value);
- if(setting < 1 || setting > 3) {
- rb_fireruby_raise(NULL,
- "Invalid dialect value specified for statement. " \
- "The dialect value must be between 1 and 3.");
- }
- } else {
- rb_fireruby_raise(NULL,
- "Invalid dialect value specified for statement. The " \
- "dialect value must be between 1 and 3.");
- }
-
- Data_Get_Struct(self, StatementHandle, statement);
+ Data_Get_Struct(self, StatementHandle, hStatement);
rb_iv_set(self, "@connection", connection);
- rb_iv_set(self, "@transaction", transaction);
- rb_iv_set(self, "@sql", rb_funcall(sql, rb_intern("to_s"), 0));
- rb_iv_set(self, "@dialect", value);
- statement->dialect = setting;
+ rb_iv_set(self, "@sql", sql);
+ hStatement->dialect = 3; //FIXME - from connection
return(self);
}
@@ -165,25 +310,13 @@
*
* @return A reference to a Connection object.
*
*/
VALUE getStatementConnection(VALUE self) {
- return(rb_iv_get(self, "@connection"));
-}
+ VALUE connection = rb_iv_get(self, "@connection");
-
-/**
- * This function provides the transaction sttribute accessor method for the
- * Statement class.
- *
- * @param self A reference to the Statement object to call the method on.
- *
- * @return A reference to a Transaction object.
- *
- */
-VALUE getStatementTransaction(VALUE self) {
- return(rb_iv_get(self, "@transaction"));
+ return(connection);
}
/**
* This function provides the dialect sttribute accessor method for the
@@ -193,542 +326,478 @@
*
* @return A reference to an integer containing the SQL dialect setting.
*
*/
VALUE getStatementDialect(VALUE self) {
- return(rb_iv_get(self, "@dialect"));
+ StatementHandle *hStatement = NULL;
+ Data_Get_Struct(self, StatementHandle, hStatement);
+ return(INT2FIX(hStatement->dialect));
}
-
-
/**
* This function provides the type attribute accessor method for the Statement
* class.
*
* @param self A reference to the Statement object to call the method on.
*
* @return A reference to an integer containing the SQL type details.
*
*/
VALUE getStatementType(VALUE self) {
- StatementHandle *statement = NULL;
- ConnectionHandle *connection = NULL;
- TransactionHandle *transaction = NULL;
- int outputs = 0;
- VALUE tmp_str = Qnil;
-
- Data_Get_Struct(self, StatementHandle, statement);
- Data_Get_Struct(rb_iv_get(self, "@connection"), ConnectionHandle, connection);
- Data_Get_Struct(rb_iv_get(self, "@transaction"), TransactionHandle, transaction);
- if(statement->handle == 0) {
- tmp_str = rb_iv_get(self, "@sql");
- prepare(&connection->handle, &transaction->handle,
- StringValuePtr(tmp_str), &statement->handle,
- statement->dialect, &statement->type, &statement->inputs,
- &outputs);
- }
-
- return(INT2FIX(statement->type));
+ StatementHandle *hStatement = getPreparedHandle(self);
+ return(INT2FIX(hStatement->type));
}
-
/**
* This function provides the parameter count sttribute accessor method for the
* Statement class.
*
* @param self A reference to the Statement object to call the method on.
*
* @return A reference to an integer containing the statement parameter count.
*
*/
VALUE getStatementParameterCount(VALUE self) {
- StatementHandle *statement = NULL;
-
- Data_Get_Struct(self, StatementHandle, statement);
- if(statement->handle == 0) {
- getStatementType(self);
- }
-
+ StatementHandle *statement = getPreparedHandle(self);
return(INT2NUM(statement->inputs));
}
-
/**
- * This method provides the execute method for the Statement class.
+ * Execute statement and take care of implicit transaction management
*
- * @param self A reference to the Statement object to call the method on.
+ * @param self A reference to the statement object
*
+ * @param parameters A reference to the parameter bindings object, can be Qnil
+ *
+ * @param transaction A reference to the transaction object, can be Qnil,
+ * if so - an implicit transaction is started and resolved when appropriate
+ *
* @return One of a count of the number of rows affected by the SQL statement,
* a ResultSet object for a query or nil.
*
*/
-VALUE executeStatement(VALUE self) {
- VALUE result;
- int type = FIX2INT(getStatementType(self));
- long affected = 0;
- StatementHandle *statement = NULL;
- TransactionHandle *transaction = NULL;
-
- switch(type) {
- case isc_info_sql_stmt_select:
- case isc_info_sql_stmt_select_for_upd:
- case isc_info_sql_stmt_exec_procedure:
- result = rb_result_set_new(rb_iv_get(self, "@connection"),
- rb_iv_get(self, "@transaction"),
- rb_iv_get(self, "@sql"),
- rb_iv_get(self, "@dialect"),
- rb_ary_new());
- break;
-
- case isc_info_sql_stmt_insert:
- case isc_info_sql_stmt_update:
- case isc_info_sql_stmt_delete:
- Data_Get_Struct(self, StatementHandle, statement);
- Data_Get_Struct(rb_iv_get(self, "@transaction"), TransactionHandle,
- transaction);
- execute(&transaction->handle, &statement->handle, statement->dialect,
- NULL, statement->type, &affected);
- result = INT2NUM(affected);
- break;
-
- default:
- Data_Get_Struct(self, StatementHandle, statement);
- Data_Get_Struct(rb_iv_get(self, "@transaction"), TransactionHandle,
- transaction);
- execute(&transaction->handle, &statement->handle, statement->dialect,
- NULL, statement->type, &affected);
- result = Qnil;
+VALUE execAndManageTransaction(VALUE self, VALUE parameters, VALUE transaction) {
+ VALUE result = Qnil;
+
+ if(Qnil == transaction) {
+ VALUE args = rb_ary_new();
+
+ transaction = rb_transaction_new(getStatementConnection(self));
+ rb_ary_push(args, self);
+ rb_ary_push(args, transaction);
+ rb_ary_push(args, parameters);
+
+ result = rb_rescue(execInTransactionFromArray, args, rescueLocalTransaction, transaction);
+ if(isActiveResultSet(result)) {
+ resultSetManageTransaction(result);
+ } else {
+ rb_funcall(transaction, rb_intern("commit"), 0);
+ }
+ } else {
+ result = execInTransaction(self, transaction, parameters);
}
-
- return(result);
+ return (result);
}
-
/**
- * This method provides the execute method for the Statement class.
+ * Execute statement and take care of closing it when appropriate
*
- * @param self A reference to the Statement object to call the method
- * on.
- * @param parameters An array containing the parameters to be used in
- * executing the statement.
+ * @param self A reference to the statement object
*
+ * @param parameters A reference to the parameter bindings object, can be Qnil
+ *
+ * @param transaction A reference to the transaction object, can be Qnil,
+ * if so - an implicit transaction is started and resolved when appropriate
+ *
* @return One of a count of the number of rows affected by the SQL statement,
* a ResultSet object for a query or nil.
*
*/
-VALUE executeStatementFor(VALUE self, VALUE parameters) {
- VALUE result = Qnil;
- int type = FIX2INT(getStatementType(self));
- long affected = 0;
- StatementHandle *statement = NULL;
- TransactionHandle *transaction = NULL;
+VALUE execAndManageStatement(VALUE self, VALUE parameters, VALUE transaction) {
+ VALUE result = Qnil,
+ args = rb_ary_new();
- if(type == isc_info_sql_stmt_select ||
- type == isc_info_sql_stmt_select_for_upd) {
- /* Execute the statement via a ResultSet object. */
- result = rb_result_set_new(rb_iv_get(self, "@connection"),
- rb_iv_get(self, "@transaction"),
- rb_iv_get(self, "@sql"),
- rb_iv_get(self, "@dialect"),
- parameters);
+ rb_ary_push(args, self);
+ rb_ary_push(args, parameters);
+ rb_ary_push(args, transaction);
+ result = rb_rescue(execStatementFromArray, args, rescueStatement, self);
+ if(isActiveResultSet(result)) {
+ resultSetManageStatement(result);
} else {
- /* Check that sufficient parameters have been specified. */
- Data_Get_Struct(self, StatementHandle, statement);
- if(statement->inputs > 0) {
- VALUE value = Qnil;
- int size = 0;
-
- if(parameters == Qnil) {
- rb_fireruby_raise(NULL,
- "Empty parameter list specified for statement.");
- }
-
- value = rb_funcall(parameters, rb_intern("size"), 0);
- size = TYPE(value) == T_FIXNUM ? FIX2INT(value) : NUM2INT(value);
- if(size < statement->inputs) {
- rb_fireruby_raise(NULL,
- "Insufficient parameters specified for statement.");
- }
-
- /* Allocate the XSQLDA and populate it. */
- statement->parameters = allocateInXSQLDA(statement->inputs,
- &statement->handle,
- statement->dialect);
- prepareDataArea(statement->parameters);
- setParameters(statement->parameters, parameters, self);
- }
-
- /* Execute the statement. */
- Data_Get_Struct(self, StatementHandle, statement);
- Data_Get_Struct(rb_iv_get(self, "@transaction"), TransactionHandle,
- transaction);
- execute(&transaction->handle, &statement->handle, statement->dialect,
- statement->parameters, statement->type, &affected);
- if(type == isc_info_sql_stmt_insert ||
- type == isc_info_sql_stmt_update ||
- type == isc_info_sql_stmt_delete) {
- result = INT2NUM(affected);
- }
+ closeStatement(self);
}
+ return (result);
+}
- return(result);
+/**
+ * Resolve (rollback) transaction in rescue block
+ *
+ * @param transaction A reference to the transaction object
+ *
+ * @param error A reference to the exception object
+ *
+ * @return Qnil
+ *
+ */
+VALUE rescueLocalTransaction(VALUE transaction, VALUE error) {
+ rb_funcall(transaction, rb_intern("rollback"), 0);
+ rb_exc_raise(error);
+ return(Qnil);
}
-
-
/**
- * This function provides the close method for the Statement class.
+ * Close statement in rescue block
*
- * @param self A reference to the Statement object to call the method on.
+ * @param statement A reference to the statement object
*
- * @return A reference to the newly closed Statement object.
+ * @param error A reference to the exception object
*
+ * @return Qnil
+ *
*/
-VALUE closeStatement(VALUE self) {
- StatementHandle *statement = NULL;
+VALUE rescueStatement(VALUE statement, VALUE error) {
+ rb_funcall(statement, rb_intern("close"), 0);
+ rb_exc_raise(error);
+ return(Qnil);
+}
- Data_Get_Struct(self, StatementHandle, statement);
- if(statement->handle != 0) {
- ISC_STATUS status[ISC_STATUS_LENGTH];
+/**
+ * Execute a statement within a transaction context parsing arguments array
+ *
+ * @param args Array containing statement, transaction and parameter bindings objects
+ *
+ * @return One of a count of the number of rows affected by the SQL statement,
+ * a ResultSet object for a query or nil.
+ */
+VALUE execInTransactionFromArray(VALUE args) {
+ VALUE self = rb_ary_entry(args, 0);
+ VALUE transaction = rb_ary_entry(args, 1);
+ VALUE parameters = rb_ary_entry(args, 2);
- if(isc_dsql_free_statement(status, &statement->handle, DSQL_drop)) {
- rb_fireruby_raise(status, "Error closing statement.");
- }
-
- if(statement->parameters != NULL) {
- releaseDataArea(statement->parameters);
- }
- }
-
- return(self);
+ return(execInTransaction(self, transaction, parameters));
}
-
/**
- * This function prepares a Firebird SQL statement for execution.
+ * Check if the statement is a select statement
*
- * @param connection A pointer to the database connection that will be used
- * to prepare the statement.
- * @param transaction A pointer to the database transaction that will be used
- * to prepare the statement.
- * @param sql A string containing the SQL statement to be executed.
- * @param statement A pointer to a Firebird statement that will be prepared.
- * @param dialect A short integer containing the SQL dialect to be used in
- * preparing the statement.
- * @param type A pointer to an integer that will be assigned the type
- * of the SQL statement prepared.
- * @param inputs A pointer to an integer that will be assigned a count of
- * the parameters for the SQL statement.
- * @param outputs A pointer to an integer that will be assigned a count of
- * the output columns for the SQL statement.
+ * @param hStatement A pointer to the statement handle
*
+ * @return 1 if the statement is a select statement, 0 otherwise
*/
-void prepare(isc_db_handle *connection, isc_tr_handle *transaction,
- char *sql, isc_stmt_handle *statement, short dialect,
- int *type, int *inputs, int *outputs) {
- ISC_STATUS status[ISC_STATUS_LENGTH];
- XSQLDA *da = NULL;
- char list[] = {isc_info_sql_stmt_type},
- info[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
-
- /* Prepare the statement. */
- if(isc_dsql_allocate_statement(status, connection, statement)) {
- rb_fireruby_raise(status, "Error allocating a SQL statement.");
+short isCursorStatement(StatementHandle *hStatement) {
+ switch(hStatement->type) {
+ case isc_info_sql_stmt_select:
+ case isc_info_sql_stmt_select_for_upd:
+ return 1;
+ default:
+ return 0;
}
-
- da = (XSQLDA *)ALLOC_N(char, XSQLDA_LENGTH(1));
- if(da == NULL) {
- rb_raise(rb_eNoMemError,
- "Memory allocation failure preparing a statement.");
- }
- da->version = SQLDA_VERSION1;
- da->sqln = 1;
- if(isc_dsql_prepare(status, transaction, statement, 0, sql, dialect,
- da)) {
- free(da);
- rb_fireruby_raise(status, "Error preparing a SQL statement.");
- }
- *outputs = da->sqld;
-
- /* Get the parameter count. */
- if(isc_dsql_describe_bind(status, statement, dialect, da)) {
- free(da);
- rb_fireruby_raise(status, "Error determining statement parameters.");
- }
- *inputs = da->sqld;
- free(da);
-
- /* Get the statement type details. */
- if(isc_dsql_sql_info(status, statement, 1, list, 20, info) ||
- info[0] != isc_info_sql_stmt_type) {
- rb_fireruby_raise(status, "Error determining SQL statement type.");
- }
- *type = isc_vax_integer(&info[3], isc_vax_integer(&info[1], 2));
}
-
/**
- * This function executes a previously prepare SQL statement.
+ * Execute a statement within a transaction context
*
- * @param transaction A pointer to the Firebird transaction handle to be used in
- * executing the statement.
- * @param statement A pointer to the Firebird statement handle to be used in
- * executing the statement.
- * @param dialect Database dialect used in the statement
+ * @param self A reference to the statement object
*
- * @param parameters A pointer to the XSQLDA block that contains the input
- * parameters for the SQL statement.
- * @param type A integer containing the type details relating to the
- * statement being executed.
- * @param affected A pointer to a long integer that will be assigned a count
- * of rows affected by inserts, updates or deletes.
- * @param output A pointer to the XSQLDA block that will hold the output
- * data generated by the execution.
+ * @param transaction A reference to the transaction object
+ *
+ * @param parameters A reference to the parameter bindings object
+ *
+ * @return One of a count of the number of rows affected by the SQL statement,
+ * a ResultSet object for a query or nil.
*/
-void execute_2(isc_tr_handle *transaction, isc_stmt_handle *statement,
- short dialect, XSQLDA *parameters, int type, long *affected, XSQLDA *output) {
+VALUE execInTransaction(VALUE self, VALUE transaction, VALUE parameters) {
+ VALUE result = Qnil;
+ long affected = 0;
+ StatementHandle *hStatement = NULL;
+ TransactionHandle *hTransaction = NULL;
+ XSQLDA *bindings = NULL;
ISC_STATUS status[ISC_STATUS_LENGTH];
ISC_STATUS execute_result;
- if(output) {
- execute_result = isc_dsql_execute2(status, transaction, statement, dialect, parameters, output);
+ prepareInTransaction(self, transaction);
+ Data_Get_Struct(self, StatementHandle, hStatement);
+ if(hStatement->inputs > 0) {
+ VALUE value = Qnil;
+ int size = 0;
+
+ if(parameters == Qnil) {
+ rb_fireruby_raise(NULL,
+ "Empty parameter list specified for statement.");
+ }
+
+ value = rb_funcall(parameters, rb_intern("size"), 0);
+ size = TYPE(value) == T_FIXNUM ? FIX2INT(value) : NUM2INT(value);
+ if(size < hStatement->inputs) {
+ rb_fireruby_raise(NULL,
+ "Insufficient parameters specified for statement.");
+ }
+
+ /* Allocate the XSQLDA */
+ bindings = allocateInXSQLDA(hStatement->inputs, &hStatement->handle, hStatement->dialect);
+ prepareDataArea(bindings);
+ setParameters(bindings, parameters, transaction, getStatementConnection(self));
+ }
+
+ /* Execute the statement. */
+ Data_Get_Struct(transaction, TransactionHandle, hTransaction);
+
+ if (isCursorStatement(hStatement)) {
+ execute_result = isc_dsql_execute(status, &hTransaction->handle, &hStatement->handle, hStatement->dialect, bindings);
} else {
- execute_result = isc_dsql_execute(status, transaction, statement, dialect, parameters);
+ execute_result = isc_dsql_execute2(status, &hTransaction->handle, &hStatement->handle, hStatement->dialect, bindings, hStatement->output);
}
+ if(bindings) {
+ releaseDataArea(bindings);
+ }
if(execute_result) {
rb_fireruby_raise(status, "Error executing SQL statement.");
}
+ if (hStatement->output) {
+ result = rb_result_set_new(self, transaction);
+ if(rb_block_given_p()) {
+ result = yieldResultsRows(result);
+ }
+ } else {
+ result = INT2NUM(fb_query_affected(hStatement));
+ }
- /* Check if a row count is needed. */
- if(type == isc_info_sql_stmt_update || type == isc_info_sql_stmt_delete ||
- type == isc_info_sql_stmt_insert) {
- int info = 0,
- done = 0;
- char items[] = {isc_info_sql_records},
- buffer[40],
- *position = buffer + 3;
+ return(result);
+}
- switch(type) {
- case isc_info_sql_stmt_update:
- info = isc_info_req_update_count;
- break;
+/**
+ * This method provides the exec method for the Statement class.
+ *
+ * @param self A reference to the Statement object to call the method on.
+ *
+ * @param argc Parameters count
+ *
+ * @param argv Parameters array
+ *
+ * @return One of a count of the number of rows affected by the SQL statement,
+ * a ResultSet object for a query or nil.
+ *
+ */
+VALUE execStatement(int argc, VALUE *argv, VALUE self) {
+ VALUE transaction, parameters = Qnil;
- case isc_info_sql_stmt_delete:
- info = isc_info_req_delete_count;
- break;
+ rb_scan_args(argc, argv, "02", ¶meters, &transaction);
+ return execAndManageTransaction(self, parameters, transaction);
+}
- case isc_info_sql_stmt_insert:
- info = isc_info_req_insert_count;
- break;
- }
+/**
+ * This method provides the exec_and_close method for the Statement class.
+ *
+ * @param self A reference to the Statement object to call the method on.
+ *
+ * @param argc Parameters count
+ *
+ * @param argv Parameters array
+ *
+ * @return One of a count of the number of rows affected by the SQL statement,
+ * a ResultSet object for a query or nil.
+ *
+ */
+VALUE execAndCloseStatement(int argc, VALUE *argv, VALUE self) {
+ VALUE parameters, transaction = Qnil;
- if(isc_dsql_sql_info(status, statement, sizeof(items), items,
- sizeof(buffer), buffer)) {
- rb_fireruby_raise(status, "Error retrieving affected row count.");
- }
+ rb_scan_args(argc, argv, "02", ¶meters, &transaction);
+ return execAndManageStatement(self, parameters, transaction);
+}
- while(*position != isc_info_end && done == 0) {
- char current = *position++;
- long temp[] = {0, 0};
-
- temp[0] = isc_vax_integer(position, 2);
- position += 2;
- temp[1] = isc_vax_integer(position, temp[0]);
- position += temp[0];
-
- if(current == info) {
- *affected = temp[1];
- done = 1;
+/**
+ * Clean up statement handle - release allocated resources
+ *
+ * @param statement A pointer to the statement handle
+ *
+ * @param raise_errors If this parameter is 0 - no exceptions are raised from this function,
+ * otherwise an exception is raised whenever a problem occurs while releasing resources.
+ *
+ */
+void cleanUpStatement(StatementHandle *statement, int raise_errors) {
+ if(statement->handle) {
+ ISC_STATUS status[ISC_STATUS_LENGTH];
+ if(isc_dsql_free_statement(status, &statement->handle, DSQL_drop)) {
+ if(raise_errors) {
+ rb_fireruby_raise(status, "Error closing statement.");
}
}
+ if(statement->output != NULL) {
+ releaseDataArea(statement->output);
+ statement->output = NULL;
+ }
+ statement->handle = 0;
}
}
+/**
+ * This function provides the close method for the Statement class.
+ *
+ * @param self A reference to the Statement object to call the method on.
+ *
+ * @return A reference to the newly closed Statement object.
+ *
+ */
+VALUE closeStatement(VALUE self) {
+ StatementHandle *statement = NULL;
+ Data_Get_Struct(self, StatementHandle, statement);
+ cleanUpStatement(statement, 1);
+ return(self);
+}
/**
- * This function executes a previously prepare SQL statement.
+ * This function provides the prepared? method for the Statement class.
*
- * @param transaction A pointer to the Firebird transaction handle to be used in
- * executing the statement.
- * @param statement A pointer to the Firebird statement handle to be used in
- * executing the statement.
- * @param dialect Database dialect used in the statement
+ * @param self A reference to the Statement object to call the method on.
*
- * @param parameters A pointer to the XSQLDA block that contains the input
- * parameters for the SQL statement.
- * @param type A integer containing the type details relating to the
- * statement being executed.
- * @param affected A pointer to a long integer that will be assigned a count
- * of rows affected by inserts, updates or deletes.
+ * @return Qtrue if the statement is prepared, Qfalse otherwise.
+ *
*/
-void execute(isc_tr_handle *transaction, isc_stmt_handle *statement,
- short dialect, XSQLDA *parameters, int type, long *affected) {
- execute_2(transaction, statement, dialect, parameters, type, affected, NULL);
+VALUE getStatementPrepared(VALUE self) {
+ VALUE result = Qfalse;
+ StatementHandle *statement = NULL;
+ Data_Get_Struct(self, StatementHandle, statement);
+
+ if(statement->handle) {
+ result = Qtrue;
+ }
+
+ return(result);
}
/**
+ * This function provides the prepare method for the Statement class.
+ *
+ * @param self A reference to the Statement object to call the method on.
+ *
+ * @param argc Parameters count
+ *
+ * @param argv Parameters array
+ *
+ * @return A reference to the statement object
+ *
+ */
+static VALUE prepareStatement(int argc, VALUE *argv, VALUE self) {
+ VALUE transaction = Qnil;
+ rb_scan_args(argc, argv, "01", &transaction);
+ if(Qnil == transaction) {
+ getPreparedHandle(self);
+ } else {
+ prepareInTransaction(self, transaction);
+ }
+ return (self);
+}
+
+/**
* This function provides a programmatic means of creating a Statement object.
*
* @param connection A reference to a Connection object that will be used by
* the Statement.
- * @param transaction A reference to a Transaction object that will be used
- * by the Statement.
* @param sql A reference to a String object containing the text of
* of a SQL statement for the Statement.
- * @param dialect A reference to an integer object that contains the SQL
- * dialect setting for the Statement.
*
* @return A reference to the newly created Statement object.
*
*/
-VALUE rb_statement_new(VALUE connection, VALUE transaction, VALUE sql,
- VALUE dialect) {
+VALUE rb_statement_new(VALUE connection, VALUE sql) {
VALUE statement = allocateStatement(cStatement);
- initializeStatement(statement, connection, transaction, sql, dialect);
+ initializeStatement(statement, connection, sql);
return(statement);
}
-
/**
- * This function provides a programmatic way of executing a Statement object
- * without parameters.
+ * Execute statement parsing arguments array
*
- * @param statement A reference to the statement object to be executed.
+ * @param args Array containing statement, parameters and transaction objects
*
- * @return A reference to the results of executing the statement.
- *
+ * @return One of a count of the number of rows affected by the SQL statement,
+ * a ResultSet object for a query or nil.
*/
-VALUE rb_execute_statement(VALUE statement) {
- return(executeStatement(statement));
-}
+VALUE execStatementFromArray(VALUE args) {
+ VALUE self = rb_ary_entry(args, 0);
+ VALUE params = rb_ary_entry(args, 1);
+ VALUE transaction = rb_ary_entry(args, 2);
-
-/**
- * This function provides a programmatic way of executing a Statement object
- * with parameters.
- *
- * @param statement A reference to the statement object to be executed.
- * @param parameters A reference to an array of parameters to be used in the
- * execution of the statement.
- *
- * @return A reference to the results of executing the statement.
- *
- */
-VALUE rb_execute_statement_for(VALUE statement, VALUE parameters) {
- return(executeStatementFor(statement, parameters));
+ return execAndManageTransaction(self, params, transaction);
}
-
/**
- * This function provides a programmatic way of executing a SQL
+ * This function provides a programmatic way of executing a parametrized SQL
* within transaction
*
* @param connection A reference to the connection object.
- * @param transaction A reference to the transaction object.
* @param sql SQL text.
+ * @param params Array containing parameter values for the statement.
+ * @param transaction A reference to the transaction object.
*
* @return A reference to the results of executing the statement.
*
*/
-VALUE rb_execute_sql(VALUE connection, VALUE transaction, VALUE sql) {
- VALUE results = Qnil,
- statement = rb_statement_new(connection, transaction, sql, INT2FIX(3));
-
- results = rb_execute_statement(statement);
- if(results != Qnil && rb_obj_is_kind_of(results, rb_cInteger) == Qfalse) {
- if(rb_block_given_p()) {
- VALUE row = rb_funcall(results, rb_intern("fetch"), 0),
- last = Qnil;
-
- while(row != Qnil) {
- last = rb_yield(row);
- row = rb_funcall(results, rb_intern("fetch"), 0);
- }
- rb_funcall(results, rb_intern("close"), 0);
- results = last;
- }
- }
- rb_statement_close(statement);
-
- return(results);
+VALUE rb_execute_sql(VALUE connection, VALUE sql, VALUE params, VALUE transaction) {
+ return execAndManageStatement(rb_statement_new(connection, sql), params, transaction);
}
/**
- * This method retrieves the type information for a Statement object.
+ * This function guarantess that the returned statement handle is prepared
*
- * @param statement A reference to a Statement object.
+ * @param self A reference to the Statement object to call the method on.
*
- * @return A reference to an integer containing the statement type details.
- *
*/
-VALUE rb_get_statement_type(VALUE statement) {
- return(getStatementType(statement));
+StatementHandle* getPreparedHandle(VALUE self) {
+ StatementHandle *hStatement;
+ Data_Get_Struct(self, StatementHandle, hStatement);
+ if(0 == hStatement->handle) {
+ VALUE transaction = rb_transaction_new(getStatementConnection(self));
+ VALUE args = rb_ary_new();
+
+ rb_ary_push(args, self);
+ rb_ary_push(args, transaction);
+
+ rb_rescue(prepareFromArray, args, rescueLocalTransaction, transaction);
+ rb_funcall(transaction, rb_intern("commit"), 0);
+ }
+ return (hStatement);
}
-
/**
- * This function provides a programmatic means of closing a Statement object.
- *
- * @param statement A reference to the Statement object to be closed.
- *
- */
-void rb_statement_close(VALUE statement) {
- closeStatement(statement);
-}
-
-
-/**
* This function integrates with the Ruby garbage collector to release the
* resources associated with a Statement object that is being collected.
*
* @param handle A pointer to the StatementHandle structure for the Statement
* object being collected.
*
*/
void statementFree(void *handle) {
if(handle != NULL) {
- StatementHandle *statement = (StatementHandle *)handle;
-
- if(statement->handle != 0) {
- ISC_STATUS status[ISC_STATUS_LENGTH];
-
- isc_dsql_free_statement(status, &statement->handle, DSQL_drop);
- }
-
- if(statement->parameters) {
- releaseDataArea(statement->parameters);
- }
- free(statement);
+ cleanUpStatement((StatementHandle *)handle, 0);
+ free(handle);
}
}
-
-
-
/**
* This function initializes the Statement class within the Ruby environment.
* The class is established under the module specified to the function.
*
* @param module A reference to the module to create the class within.
*
*/
void Init_Statement(VALUE module) {
cStatement = rb_define_class_under(module, "Statement", rb_cObject);
rb_define_alloc_func(cStatement, allocateStatement);
- rb_define_method(cStatement, "initialize", initializeStatement, 4);
+ rb_define_method(cStatement, "initialize", initializeStatement, 2);
rb_define_method(cStatement, "initialize_copy", forbidObjectCopy, 1);
rb_define_method(cStatement, "sql", getStatementSQL, 0);
rb_define_method(cStatement, "connection", getStatementConnection, 0);
- rb_define_method(cStatement, "transaction", getStatementTransaction, 0);
rb_define_method(cStatement, "dialect", getStatementDialect, 0);
rb_define_method(cStatement, "type", getStatementType, 0);
- rb_define_method(cStatement, "execute", executeStatement, 0);
- rb_define_method(cStatement, "execute_for", executeStatementFor, 1);
+ rb_define_method(cStatement, "exec", execStatement, -1);
+ rb_define_method(cStatement, "exec_and_close", execAndCloseStatement, -1);
rb_define_method(cStatement, "close", closeStatement, 0);
rb_define_method(cStatement, "parameter_count", getStatementParameterCount, 0);
+ rb_define_method(cStatement, "prepare", prepareStatement, -1);
+ rb_define_method(cStatement, "prepared?", getStatementPrepared, 0);
rb_define_const(cStatement, "SELECT_STATEMENT",
INT2FIX(isc_info_sql_stmt_select));
rb_define_const(cStatement, "INSERT_STATEMENT",
INT2FIX(isc_info_sql_stmt_insert));