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"); } - - |