|
From: Elias K. <el...@us...> - 2002-12-28 14:41:30
|
Update of /cvsroot/ruby-dbi/subprojects/ruby-db2/ext/db2
In directory sc8-pr-cvs1:/tmp/cvs-serv29074
Modified Files:
Tag: DB2BLOBS
constants.h db2cli.c
Log Message:
experimental support for BLOBs via SQLBindParameter
Index: constants.h
===================================================================
RCS file: /cvsroot/ruby-dbi/subprojects/ruby-db2/ext/db2/constants.h,v
retrieving revision 1.1
retrieving revision 1.1.2.1
diff -u -r1.1 -r1.1.2.1
--- constants.h 5 Sep 2002 09:57:13 -0000 1.1
+++ constants.h 28 Dec 2002 14:41:26 -0000 1.1.2.1
@@ -5,6 +5,7 @@
rb_define_const(mDB2CLI, "SQL_ERROR", INT2NUM(SQL_ERROR));
rb_define_const(mDB2CLI, "SQL_NO_DATA_FOUND", INT2NUM(SQL_NO_DATA_FOUND));
rb_define_const(mDB2CLI, "SQL_NULL_DATA", INT2NUM(SQL_NULL_DATA));
+rb_define_const(mDB2CLI, "SQL_NEED_DATA", INT2NUM(SQL_NEED_DATA));
rb_define_const(mDB2CLI, "SQL_HANDLE_ENV", INT2NUM(SQL_HANDLE_ENV));
rb_define_const(mDB2CLI, "SQL_HANDLE_DBC", INT2NUM(SQL_HANDLE_DBC));
rb_define_const(mDB2CLI, "SQL_HANDLE_STMT", INT2NUM(SQL_HANDLE_STMT));
@@ -87,4 +88,7 @@
rb_define_const(mDB2CLI, "SQL_DESC_LOCAL_TYPE_NAME", INT2NUM(SQL_DESC_LOCAL_TYPE_NAME));
rb_define_const(mDB2CLI, "SQL_DESC_NUM_PREC_RADIX", INT2NUM(SQL_DESC_NUM_PREC_RADIX));
rb_define_const(mDB2CLI, "SQL_DESC_UNNAMED", INT2NUM(SQL_DESC_UNNAMED));
-
+// parameter direction
+rb_define_const(mDB2CLI, "SQL_PARAM_INPUT", INT2NUM(SQL_PARAM_INPUT));
+rb_define_const(mDB2CLI, "SQL_PARAM_OUTPUT", INT2NUM(SQL_PARAM_OUTPUT));
+rb_define_const(mDB2CLI, "SQL_PARAM_INPUT_OUTPUT", INT2NUM(SQL_PARAM_INPUT_OUTPUT));
Index: db2cli.c
===================================================================
RCS file: /cvsroot/ruby-dbi/subprojects/ruby-db2/ext/db2/db2cli.c,v
retrieving revision 1.2
retrieving revision 1.2.2.1
diff -u -r1.2 -r1.2.2.1
--- db2cli.c 20 Dec 2002 10:04:58 -0000 1.2
+++ db2cli.c 28 Dec 2002 14:41:27 -0000 1.2.2.1
@@ -8,7 +8,7 @@
* Contributors:
*
* Songsu Yun (yu...@us...)
- *
+ * Elias Karakoulakis (ek...@na...)
*
* Copyright (c) 2001, 2002 by Michael Neumann.
*
@@ -36,7 +36,25 @@
system (in Germany correct would be "5,7"). Fault of Linux ????
**********************************************************************/
-
+/**********************************************************************
+ A little note about memory management:
+ ======================================================================
+ When the old and the new programming paradigms (C & Ruby) are forced
+ to cooperate (as in our case), strange things can happen in the memory
+ management arena.
+ In detail: we need to be able to allocate memory from C routines AND
+ free them from the CLI AND Ruby's Garbage cleaner. Otherwise, memory
+ leaks will cause your available memory to drain.
+ We can't just use Ruby variables to store our intermediate buffers,
+ since the CLI will fiddle with them, regardless of any GC in place.
+ So we use a hash class variable, 'param_buffers' that contains all
+ memory buffer pointers allocated for CLI's use. Its form is:
+ key: Object.id of object holding buffers e.g. a Statement
+ value: array of pointer pairs (buffer, buffer size), one for each
+ parameter bound to this statement.
+ all buffers for a statement are freed whenever a new statement is
+ initialized, OR whenever a Statement object gets finalized from GC.
+**********************************************************************/
/* Includes */
#include <stdio.h>
@@ -44,8 +62,6 @@
#include "sqlcli1.h"
#include "ruby.h"
-
-
/* Macros */
#define TO_C_INT(val) NUM2INT(val)
#define TO_RUBY_INT(val) INT2NUM(val)
@@ -55,11 +71,11 @@
#define MIN(a,b) ((a) < (b) ? (a) : (b))
/* Global Variables */
-static VALUE mDB2CLI;
+static VALUE mDB2CLI, cDBI_Binary;
static VALUE cDate, cTime, cTimestamp;
static VALUE objNull; /* object for a SQL NULL value */
-
-
+static VALUE param_buffers;
+static VALUE paranoid_debug;
/* Functions */
@@ -445,7 +461,6 @@
(SQLSMALLINT*) &nullable
);
-
retval = rb_ary_new3(
5,
TO_RUBY_INT(rc),
@@ -1039,51 +1054,177 @@
}
-/*******************************************************
- SQLBindParameter added by yun
- =======================================================
- PARAMS: statement_handle : Integer,
- parameter_number : Integer,
- parameter_type : Integer,
- parameter_size : Integer,
- decimal_digits : Integer
- parameter_value : String,
-
- RETURNS: rc : Integer
-
- Note
- ValueType parameter to SQLBindParameter call is set to
- SQL_C_CHAR. DB2 will convert it to proper type
- based on ParameterType parameter(parameter_type)
-********************************************************/
-
-static VALUE
-db2_SQLBindParameter(self, statement_handle, parameter_number, parameter_type,
- parameter_size, decimal_digits, parameter_value)
- VALUE self;
- VALUE statement_handle, parameter_number, parameter_type, parameter_value;
- VALUE parameter_size, decimal_digits;
-{
- SQLRETURN rc;
-
- MUST_BE_STRING(parameter_value);
- rc = SQLBindParameter(
- (SQLHSTMT) TO_C_INT(statement_handle),
- (SQLUSMALLINT) TO_C_INT(parameter_number),
- (SQLSMALLINT) SQL_PARAM_INPUT, /* support input parameter only */
- (SQLSMALLINT) SQL_C_CHAR, /* ValueType. Always string */
- (SQLSMALLINT) TO_C_INT(parameter_type), /* SQL data type of the parameter */
- (SQLUINTEGER) TO_C_INT(parameter_size), /* column size */
- (SQLSMALLINT) TO_C_INT(decimal_digits), /* decimal digits */
- (SQLCHAR *FAR) RSTRING(parameter_value)->ptr, /* parameter value pointer */
- (SQLINTEGER) RSTRING(parameter_value)->len, /* buffer length */
- (SQLINTEGER *FAR) &(RSTRING(parameter_value)->len) /*StrLen */
- );
-
- return TO_RUBY_INT(rc);
+//~ /*******************************************************
+ //~ SQLBindParameter added by yun
+ //~ =======================================================
+ //~ PARAMS: statement_handle : Integer,
+ //~ parameter_number : Integer,
+ //~ parameter_type : Integer,
+ //~ parameter_size : Integer,
+ //~ decimal_digits : Integer
+ //~ parameter_value : String,
+
+ //~ RETURNS: rc : Integer
+
+ //~ Note
+ //~ ValueType parameter to SQLBindParameter call is set to
+ //~ SQL_C_CHAR. DB2 will convert it to proper type
+ //~ based on ParameterType parameter(parameter_type)
+//~ ********************************************************/
+
+//~ static VALUE
+//~ db2_SQLBindParameter(self, statement_handle, parameter_number, parameter_type,
+ //~ parameter_size, decimal_digits, parameter_value)
+ //~ VALUE self;
+ //~ VALUE statement_handle, parameter_number, parameter_type, parameter_value;
+ //~ VALUE parameter_size, decimal_digits;
+//~ {
+ //~ SQLRETURN rc;
+
+ //~ MUST_BE_STRING(parameter_value);
+ //~ rc = SQLBindParameter(
+ //~ (SQLHSTMT) TO_C_INT(statement_handle),
+ //~ (SQLUSMALLINT) TO_C_INT(parameter_number),
+ //~ (SQLSMALLINT) SQL_PARAM_INPUT, /* support input parameter only */
+ //~ (SQLSMALLINT) SQL_C_CHAR, /* ValueType. Always string */
+ //~ (SQLSMALLINT) TO_C_INT(parameter_type), /* SQL data type of the parameter */
+ //~ (SQLUINTEGER) TO_C_INT(parameter_size), /* column size */
+ //~ (SQLSMALLINT) TO_C_INT(decimal_digits), /* decimal digits */
+ //~ (SQLCHAR *FAR) RSTRING(parameter_value)->ptr, /* parameter value pointer */
+ //~ (SQLINTEGER) RSTRING(parameter_value)->len, /* buffer length */
+ //~ (SQLINTEGER *FAR) &(RSTRING(parameter_value)->len) /*StrLen */
+ //~ );
+
+ //~ return TO_RUBY_INT(rc);
+//~ }
+
+/*******************************************************
+ SQLBindParameter: bind a buffer to a parameter marker
+ =======================================================
+ PARAMS: SQLHSTMT StatementHandle (in)
+ SQLUSMALLINT ParameterNumber (in)
+ SQLSMALLINT InputOutputType (in)
+ SQLSMALLINT ValueType (in)
+ SQLSMALLINT ParameterType (in)
+ SQLUINTEGER ColumnSize (in)
+ SQLSMALLINT DecimalDigits (in)
+ SQLPOINTER ParameterValuePtr (in DEFERRED/out DEFERRED)
+ SQLINTEGER BufferLength, (in)
+ SQLINTEGER *FAR StrLen_or_IndPtr (in DEFERRED/out DEFERRED)
+ RETURNS: rc : Integer
+ ParameterValuePtr: Integer
+ StrLen_or_IndPtr: Integer
+********************************************************/
+static VALUE
+db2_SQLBindParameter(self, hstmt, param_id, param_direction, db2_sql_type, ruby_value)
+ VALUE self, hstmt, param_id, param_direction, db2_sql_type, ruby_value;
+{
+ SQLSMALLINT fCType, ibScale=0;
+ SQLUINTEGER cbColDef = 0;
+ SQLPOINTER rgbValue;
+ SQLINTEGER* pcbValue = ALLOC_N(SQLINTEGER, 1);
+ SQLRETURN rc;
+ VALUE tmp, id, hash, buffers;
+ int paramType;
+
+ if ((SQL_PARAM_OUTPUT == TO_C_INT(param_direction)) ||
+ (SQL_PARAM_INPUT_OUTPUT == TO_C_INT(param_direction))) {
+ // TODO: alloc buffer for returned value
+ }
+
+ if (NIL_P(ruby_value)) { // parameter is null
+ *pcbValue = SQL_NULL_DATA;
+ rgbValue = NULL;
+ fCType = SQL_C_DEFAULT;
+ paramType = SQL_NULL_DATA;
+ } else {
+ if (NIL_P(db2_sql_type)) {
+ rb_raise(rb_eTypeError, "DB2 CLI: db2_sql_type must be set to a valid type when a not-nil value is bound");
+ }
+ paramType = TO_C_INT(db2_sql_type);
+ switch (TYPE(ruby_value)) {
+ case T_OBJECT:
+ if (rb_obj_is_instance_of(ruby_value, cDBI_Binary)) {
+ ruby_value = rb_funcall(ruby_value, rb_intern("to_s"), 0);
+ } else {
+ rb_raise(rb_eTypeError, "DB2 CLI: large objects must be wrapped inside a DBI::Binary instance");
+ }
+ fCType = SQL_C_BINARY;
+ *pcbValue = RSTRING(ruby_value)->len;
+ rgbValue = ALLOC_N(char, (*pcbValue));
+ memcpy(rgbValue, RSTRING(ruby_value)->ptr, *pcbValue);
+ break;
+ case T_FIXNUM:
+ fCType = SQL_C_LONG;
+ *pcbValue = sizeof(long);
+ rgbValue = ALLOC(long);
+ *((long *)rgbValue) = (long) FIX2INT(ruby_value);
+ break;
+ case T_BIGNUM: case T_FLOAT: // dirty hack
+ ruby_value = rb_funcall(ruby_value, rb_intern("to_s"), 0);
+ case T_STRING:
+ fCType = SQL_C_CHAR;
+ *pcbValue = RSTRING(ruby_value)->len + 1; // to accomodate the terminating nil
+ rgbValue = ALLOC_N(char, (*pcbValue));
+ memcpy(rgbValue, RSTRING(ruby_value)->ptr, *pcbValue);
+ break;
+ default:
+ rb_raise(rb_eTypeError, "Cannot bind parameter to this kind of value!");
+ }
+ }
+
+ if (paranoid_debug) {
+ printf("SQLBindParameter: self=%x, hstmt=%x, param_id=%d, rgbValue=0x%08x, pcbValue=0x%08x, indPtr=%d\n", self, TO_C_INT(hstmt), TO_C_INT(param_id), rgbValue, pcbValue, ((SQLINTEGER) *pcbValue));
+ }
+
+ rc = SQLBindParameter(
+ (SQLHSTMT) TO_C_INT(hstmt), // StatementHandle
+ (SQLUSMALLINT) TO_C_INT(param_id), // ParameterNumber
+ (SQLSMALLINT) TO_C_INT(param_direction), // InputOutputType
+ (SQLSMALLINT) fCType, // ValueType
+ (SQLSMALLINT) paramType, // ParameterType
+ (SQLUINTEGER) cbColDef, // ColumnSize
+ (SQLSMALLINT) ibScale, // DecimalDigits
+ (SQLPOINTER) rgbValue, // ParameterValuePtr
+ (SQLINTEGER) sizeof(rgbValue), /* cbValueMax */ // BufferLength
+ (SQLINTEGER *FAR) pcbValue
+ );
+
+ // store buffer addresses for later disposal
+ //if (paranoid_debug) printf("param_buffers: %s\n", RSTRING(rb_funcall(param_buffers, rb_intern("inspect"), 0))->ptr);
+ id = rb_funcall(self, rb_intern("__id__"), 0);
+ if (NIL_P(buffers = rb_hash_aref(param_buffers, id))) {
+ buffers = rb_ary_new(); // create array of parameter buffer pointers
+ rb_hash_aset(param_buffers, id, buffers);
+ }
+ rb_ary_store(buffers, TO_C_INT(param_id)-1, rb_ary_new3(
+ 2, TO_RUBY_INT((int) rgbValue), TO_RUBY_INT((int) pcbValue)
+ ));
+ if (paranoid_debug) printf("param_buffers: %s\n", RSTRING(rb_funcall(param_buffers, rb_intern("inspect"), 0))->ptr);
+ return TO_RUBY_INT(rc);
+}
+
+// free parameter buffers
+static VALUE
+db2_free_bound_params(VALUE self, VALUE id) {
+ VALUE arr, pointer_pair, p1, p2;
+ if (paranoid_debug) printf("DB2CLI: cleaning up id %d, buffers: %s\n", TO_C_INT(id), RSTRING(rb_funcall(param_buffers, rb_intern("inspect"), 0))->ptr);
+ if (!NIL_P(arr = rb_hash_aref(param_buffers, id))) {
+ if (paranoid_debug) printf("DB2CLI: found hash key\n");
+ while (!NIL_P(pointer_pair = rb_ary_pop(arr))) {
+ p1 = rb_ary_pop(pointer_pair);
+ p2 = rb_ary_pop(pointer_pair);
+ free((void*) TO_C_INT(p1));
+ free((void*) TO_C_INT(p2));
+ if (paranoid_debug) printf("DB2CLI: freed buffers 0x%08x, 0x%08x\n", TO_C_INT(p1), TO_C_INT(p2));
+ }
+ if (paranoid_debug) printf("DB2CLI: deleting key %d from hash\n", TO_C_INT(id));
+ rb_funcall(param_buffers, rb_intern("delete"), 1, id);
+ }
+ if (paranoid_debug) printf("DB2CLI: buffers AFTER cleanup: %s\n", RSTRING(rb_funcall(param_buffers, rb_intern("inspect"), 0))->ptr);
+ return TO_RUBY_INT(0);
}
-
/*******************************************************
SQLGetCursorName added by yun
=======================================================
@@ -1144,11 +1285,65 @@
return TO_RUBY_INT(rc);
}
+
+
+/*******************************************************
+ SQLParamData added by ekarak
+ =======================================================
+ PARAMS: SQLHSTMT StatementHandle,
+ SQLPOINTER FAR *ValuePtrPtr
+ RETURNS: rc : Integer
+********************************************************/
+static VALUE
+db2_SQLParamData(self, hstmt, valueptrptr)
+VALUE self, hstmt, valueptrptr;
+{
+ SQLRETURN rc;
+
+ rc = SQLParamData(
+ (SQLHSTMT) TO_C_INT(hstmt),
+ (SQLPOINTER FAR*) TO_C_INT(valueptrptr)
+ );
+
+ return TO_RUBY_INT(rc);
+}
+
+/*******************************************************
+ SQLPutData added by ekarak
+ =======================================================
+ PARAMS: SQLHSTMT StatementHandle
+ SQLPOINTER DataPtr
+ SQLINTEGER StrLen_or_Ind
+ RETURNS: rc : Integer
+********************************************************/
+static VALUE
+db2_SQLPutData(self, hstmt, dataptr, strlen_or_ind)
+VALUE self, hstmt, dataptr, strlen_or_ind;
+{
+ SQLRETURN rc;
+
+ rc = SQLPutData(
+ (SQLHSTMT) TO_C_INT(hstmt),
+ (SQLPOINTER) TO_C_INT(dataptr),
+ (SQLINTEGER) TO_C_INT(strlen_or_ind)
+ );
+
+ return TO_RUBY_INT(rc);
+}
+
/* Init */
void Init_db2cli() {
+ rb_require("dbi");
+ // FIXME: is eval_string the most efficient way?
mDB2CLI = rb_eval_string("DB2CLI");
-
+ cDBI_Binary = rb_eval_string("DBI::Binary");
+
+ param_buffers = rb_hash_new();
+ rb_cv_set(mDB2CLI, "@@param_buffers", param_buffers); // store it as a class variable so that GC never frees it
+ printf("INIT: param_buffers: 0x%08x\n", param_buffers);
+ paranoid_debug = RTEST(rb_gv_get("$PARANOID_DEBUG"));
+
#include "constants.h"
rb_define_module_function(mDB2CLI, "SQLAllocHandle", db2_SQLAllocHandle, 2);
@@ -1178,15 +1373,17 @@
rb_define_module_function(mDB2CLI, "SQLGetDiagRec", db2_SQLGetDiagRec, 4);
rb_define_module_function(mDB2CLI, "SQLTables", db2_SQLTables, 5);
- rb_define_module_function(mDB2CLI, "SQLBindParameter", db2_SQLBindParameter, 6);
+ rb_define_module_function(mDB2CLI, "SQLBindParameter", db2_SQLBindParameter, 5);
rb_define_module_function(mDB2CLI, "SQLSetCursorName", db2_SQLSetCursorName, 2);
rb_define_module_function(mDB2CLI, "SQLGetCursorName", db2_SQLGetCursorName, 1);
+ rb_define_module_function(mDB2CLI, "SQLParamData", db2_SQLParamData, 2);
+ rb_define_module_function(mDB2CLI, "SQLPutData", db2_SQLPutData, 3);
+ //
+ rb_define_singleton_method(mDB2CLI, "free_bound_params",db2_free_bound_params,1);
+
/* Datatype classes or objects */
-
cDate = rb_eval_string("DB2CLI::Date");
cTime = rb_eval_string("DB2CLI::Time");
cTimestamp = rb_eval_string("DB2CLI::Timestamp");
objNull = rb_eval_string("DB2CLI::Null");
}
-
-
|