#include VALUE cTinyTdsResult; extern VALUE mTinyTds, cTinyTdsClient, cTinyTdsError; VALUE cBigDecimal, cDate, cDateTime; VALUE opt_decimal_zero, opt_float_zero, opt_one, opt_zero, opt_four, opt_19hdr, opt_tenk, opt_onemil; int opt_ruby_186; static ID intern_new, intern_utc, intern_local, intern_localtime, intern_merge, intern_civil, intern_new_offset, intern_plus, intern_divide, intern_Rational; static ID sym_symbolize_keys, sym_as, sym_array, sym_cache_rows, sym_first, sym_timezone, sym_local, sym_utc; // Lib Macros #ifdef HAVE_RUBY_ENCODING_H rb_encoding *binaryEncoding; #define ENCODED_STR_NEW(_data, _len) ({ \ VALUE _val = rb_str_new((char *)_data, (long)_len); \ rb_enc_associate(_val, rwrap->encoding); \ _val; \ }) #define ENCODED_STR_NEW2(_data2) ({ \ VALUE _val = rb_str_new2((char *)_data2); \ rb_enc_associate(_val, rwrap->encoding); \ _val; \ }) #else #define ENCODED_STR_NEW(_data, _len) \ rb_str_new((char *)_data, (long)_len) #define ENCODED_STR_NEW2(_data2) \ rb_str_new2((char *)_data2) #endif // Lib Backend (Memory Management) static void rb_tinytds_result_mark(void *ptr) { tinytds_result_wrapper *rwrap = (tinytds_result_wrapper *)ptr; if (rwrap) { rb_gc_mark(rwrap->local_offset); rb_gc_mark(rwrap->fields); rb_gc_mark(rwrap->fields_processed); rb_gc_mark(rwrap->results); rb_gc_mark(rwrap->dbresults_retcodes); } } static void rb_tinytds_result_free(void *ptr) { tinytds_result_wrapper *rwrap = (tinytds_result_wrapper *)ptr; xfree(ptr); } VALUE rb_tinytds_new_result_obj(DBPROCESS *c) { VALUE obj; tinytds_result_wrapper *rwrap; obj = Data_Make_Struct(cTinyTdsResult, tinytds_result_wrapper, rb_tinytds_result_mark, rb_tinytds_result_free, rwrap); rwrap->client = c; rwrap->local_offset = Qnil; rwrap->fields = rb_ary_new(); rwrap->fields_processed = rb_ary_new(); rwrap->results = Qnil; rwrap->dbresults_retcodes = rb_ary_new(); rwrap->number_of_results = 0; rwrap->number_of_fields = 0; rwrap->number_of_rows = 0; rb_obj_call_init(obj, 0, NULL); return obj; } // Lib Backend (Helpers) static RETCODE rb_tinytds_result_dbresults_retcode(VALUE self) { GET_RESULT_WRAPPER(self); VALUE ruby_rc; RETCODE db_rc; ruby_rc = rb_ary_entry(rwrap->dbresults_retcodes, rwrap->number_of_results); if (NIL_P(ruby_rc)) { db_rc = dbresults(rwrap->client); ruby_rc = INT2FIX(db_rc); rb_ary_store(rwrap->dbresults_retcodes, rwrap->number_of_results, ruby_rc); } else { db_rc = FIX2INT(ruby_rc); } return db_rc; } static RETCODE rb_tinytds_result_ok_helper(DBPROCESS *client) { GET_CLIENT_USERDATA(client); if (userdata->dbsqlok_sent == 0) { userdata->dbsqlok_retcode = dbsqlok(client); userdata->dbsqlok_sent = 1; } return userdata->dbsqlok_retcode; } static void rb_tinytds_result_cancel_helper(DBPROCESS *client) { GET_CLIENT_USERDATA(client); rb_tinytds_result_ok_helper(client); dbcancel(client); userdata->dbcancel_sent = 1; userdata->dbsql_sent = 0; } static VALUE rb_tinytds_result_fetch_row(VALUE self, ID timezone, int symbolize_keys, int as_array) { /* Wrapper And Local Vars */ GET_RESULT_WRAPPER(self); /* Create Empty Row */ VALUE row = as_array ? rb_ary_new2(rwrap->number_of_fields) : rb_hash_new(); /* Storing Values */ unsigned int i = 0; for (i = 0; i < rwrap->number_of_fields; i++) { VALUE val = Qnil; int col = i+1; int coltype = dbcoltype(rwrap->client, col); BYTE *data = dbdata(rwrap->client, col); DBINT data_len = dbdatlen(rwrap->client, col); int null_val = ((data == NULL) && (data_len == 0)); if (!null_val) { switch(coltype) { case SYBINT1: val = INT2FIX(*(DBTINYINT *)data); break; case SYBINT2: val = INT2FIX(*(DBSMALLINT *)data); break; case SYBINT4: val = INT2NUM(*(DBINT *)data); break; case SYBINT8: val = LL2NUM(*(DBBIGINT *)data); break; case SYBBIT: val = *(int *)data ? Qtrue : Qfalse; break; case SYBNUMERIC: case SYBDECIMAL: { DBTYPEINFO *data_info = dbcoltypeinfo(rwrap->client, col); int data_slength = (int)data_info->precision + (int)data_info->scale + 1; char converted_decimal[data_slength]; dbconvert(rwrap->client, coltype, data, data_len, SYBVARCHAR, (BYTE *)converted_decimal, -1); val = rb_funcall(cBigDecimal, intern_new, 1, rb_str_new2((char *)converted_decimal)); break; } case SYBFLT8: { double col_to_double = *(double *)data; val = (col_to_double == 0.000000) ? opt_float_zero : rb_float_new(col_to_double); break; } case SYBREAL: { float col_to_float = *(float *)data; val = (col_to_float == 0.0) ? opt_float_zero : rb_float_new(col_to_float); break; } case SYBMONEY: { DBMONEY *money = (DBMONEY *)data; char converted_money[25]; long long money_value = ((long long)money->mnyhigh << 32) | money->mnylow; sprintf(converted_money, "%lld", money_value); val = rb_funcall(cBigDecimal, intern_new, 2, rb_str_new2(converted_money), opt_four); val = rb_funcall(val, intern_divide, 1, opt_tenk); break; } case SYBMONEY4: { DBMONEY4 *money = (DBMONEY4 *)data; char converted_money[20]; sprintf(converted_money, "%f", money->mny4 / 10000.0); val = rb_funcall(cBigDecimal, intern_new, 1, rb_str_new2(converted_money)); break; } case SYBBINARY: case SYBIMAGE: val = rb_str_new((char *)data, (long)data_len); #ifdef HAVE_RUBY_ENCODING_H rb_enc_associate(val, binaryEncoding); #endif break; case 36: { // SYBUNIQUE char converted_unique[37]; dbconvert(rwrap->client, coltype, data, 37, SYBVARCHAR, (BYTE *)converted_unique, -1); val = ENCODED_STR_NEW2(converted_unique); break; } case SYBDATETIME4: { DBDATETIME new_data; dbconvert(rwrap->client, coltype, data, data_len, SYBDATETIME, (BYTE *)&new_data, sizeof(new_data)); data = (BYTE *)&new_data; data_len = sizeof(new_data); } case SYBDATETIME: { DBDATEREC date_rec; dbdatecrack(rwrap->client, &date_rec, (DBDATETIME *)data); int year = date_rec.dateyear, month = date_rec.datemonth+1, day = date_rec.datedmonth, hour = date_rec.datehour, min = date_rec.dateminute, sec = date_rec.datesecond, msec = date_rec.datemsecond; if (year+month+day+hour+min+sec+msec != 0) { VALUE offset = (timezone == intern_local) ? rwrap->local_offset : opt_zero; /* Use DateTime */ if (year < 1902 || year+month+day > 2058) { VALUE datetime_sec = INT2NUM(sec); if (msec != 0) { if ((opt_ruby_186 == 1 && sec < 59) || (opt_ruby_186 != 1)) { #ifdef HAVE_RUBY_ENCODING_H VALUE rational_msec = rb_Rational2(INT2NUM(msec*1000), opt_onemil); #else VALUE rational_msec = rb_funcall(rb_cObject, intern_Rational, 2, INT2NUM(msec*1000), opt_onemil); #endif datetime_sec = rb_funcall(datetime_sec, intern_plus, 1, rational_msec); } } val = rb_funcall(cDateTime, intern_civil, 7, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), datetime_sec, offset); val = rb_funcall(val, intern_new_offset, 1, offset); /* Use Time */ } else { val = rb_funcall(rb_cTime, timezone, 7, INT2NUM(year), INT2NUM(month), INT2NUM(day), INT2NUM(hour), INT2NUM(min), INT2NUM(sec), INT2NUM(msec*1000)); } } break; } case SYBCHAR: case SYBTEXT: val = ENCODED_STR_NEW(data, data_len); break; default: val = ENCODED_STR_NEW(data, data_len); break; } } if (as_array) { rb_ary_store(row, i, val); } else { VALUE key; if (rwrap->number_of_results == 0) { key = rb_ary_entry(rwrap->fields, i); } else { key = rb_ary_entry(rb_ary_entry(rwrap->fields, rwrap->number_of_results), i); } rb_hash_aset(row, key, val); } } return row; } // TinyTds::Client (public) static VALUE rb_tinytds_result_fields(VALUE self) { GET_RESULT_WRAPPER(self); RETCODE dbsqlok_rc = rb_tinytds_result_ok_helper(rwrap->client); RETCODE dbresults_rc = rb_tinytds_result_dbresults_retcode(self); VALUE fields_processed = rb_ary_entry(rwrap->fields_processed, rwrap->number_of_results); if ((dbsqlok_rc == SUCCEED) && (dbresults_rc == SUCCEED) && (fields_processed == Qnil)) { /* Default query options. */ VALUE qopts = rb_iv_get(self, "@query_options"); int symbolize_keys = (rb_hash_aref(qopts, sym_symbolize_keys) == Qtrue) ? 1 : 0; /* Set number_of_fields count for this result set. */ rwrap->number_of_fields = dbnumcols(rwrap->client); if (rwrap->number_of_fields > 0) { /* Create fields for this result set. */ unsigned int fldi = 0; VALUE fields = rb_ary_new2(rwrap->number_of_fields); for (fldi = 0; fldi < rwrap->number_of_fields; fldi++) { char *colname = dbcolname(rwrap->client, fldi+1); VALUE field = symbolize_keys ? rb_str_intern(ENCODED_STR_NEW2(colname)) : rb_obj_freeze(ENCODED_STR_NEW2(colname)); rb_ary_store(fields, fldi, field); } /* Store the fields. */ if (rwrap->number_of_results == 0) { rwrap->fields = fields; } else if (rwrap->number_of_results == 1) { VALUE multi_rs_fields = rb_ary_new(); rb_ary_store(multi_rs_fields, 0, rwrap->fields); rb_ary_store(multi_rs_fields, 1, fields); rwrap->fields = multi_rs_fields; } else { rb_ary_store(rwrap->fields, rwrap->number_of_results, fields); } } rb_ary_store(rwrap->fields_processed, rwrap->number_of_results, Qtrue); } return rwrap->fields; } static VALUE rb_tinytds_result_each(int argc, VALUE * argv, VALUE self) { GET_RESULT_WRAPPER(self); GET_CLIENT_USERDATA(rwrap->client); /* Local Vars */ VALUE qopts, opts, block; ID timezone; int symbolize_keys = 0, as_array = 0, cache_rows = 0, first = 0; /* Merge Options Hash To Query Options. Populate Opts & Block Var. */ qopts = rb_iv_get(self, "@query_options"); if (rb_scan_args(argc, argv, "01&", &opts, &block) == 1) qopts = rb_funcall(qopts, intern_merge, 1, opts); rb_iv_set(self, "@query_options", qopts); /* Locals From Options */ if (rb_hash_aref(qopts, sym_first) == Qtrue) first = 1; if (rb_hash_aref(qopts, sym_symbolize_keys) == Qtrue) symbolize_keys = 1; if (rb_hash_aref(qopts, sym_as) == sym_array) as_array = 1; if (rb_hash_aref(qopts, sym_cache_rows) == Qtrue) cache_rows = 1; if (rb_hash_aref(qopts, sym_timezone) == sym_local) { timezone = intern_local; } else if (rb_hash_aref(qopts, sym_timezone) == sym_utc) { timezone = intern_utc; } else { rb_warn(":timezone option must be :utc or :local - defaulting to :local"); timezone = intern_local; } /* Make The Results Or Yield Existing */ if (NIL_P(rwrap->results)) { rwrap->results = rb_ary_new(); RETCODE dbsqlok_rc = rb_tinytds_result_ok_helper(rwrap->client); RETCODE dbresults_rc = rb_tinytds_result_dbresults_retcode(self); while ((dbsqlok_rc == SUCCEED) && (dbresults_rc == SUCCEED)) { int has_rows = (DBROWS(rwrap->client) == SUCCEED) ? 1 : 0; rb_tinytds_result_fields(self); if (has_rows && rwrap->number_of_fields > 0) { /* Create rows for this result set. */ unsigned long rowi = 0; VALUE result = rb_ary_new(); while (dbnextrow(rwrap->client) != NO_MORE_ROWS) { VALUE row = rb_tinytds_result_fetch_row(self, timezone, symbolize_keys, as_array); if (cache_rows) rb_ary_store(result, rowi, row); if (!NIL_P(block)) rb_yield(row); if (first) { dbcanquery(rwrap->client); userdata->dbcancel_sent = 1; } rowi++; } rwrap->number_of_rows = rowi; /* Store the result. */ if (cache_rows) { if (rwrap->number_of_results == 0) { rwrap->results = result; } else if (rwrap->number_of_results == 1) { VALUE multi_resultsets = rb_ary_new(); rb_ary_store(multi_resultsets, 0, rwrap->results); rb_ary_store(multi_resultsets, 1, result); rwrap->results = multi_resultsets; } else { rb_ary_store(rwrap->results, rwrap->number_of_results, result); } } // If we find results increment the counter that helpers use and setup the next loop. rwrap->number_of_results = rwrap->number_of_results + 1; dbresults_rc = rb_tinytds_result_dbresults_retcode(self); } else { // If we do not find results, side step the rb_tinytds_result_dbresults_retcode helper and // manually populate its memoized array while nullifing any memoized fields too before loop. dbresults_rc = dbresults(rwrap->client); rb_ary_store(rwrap->dbresults_retcodes, rwrap->number_of_results, INT2FIX(dbresults_rc)); rb_ary_store(rwrap->fields_processed, rwrap->number_of_results, Qnil); } } if (dbresults_rc == FAIL) rb_warn("TinyTDS: Something in the dbresults() while loop set the return code to FAIL.\n"); userdata->dbsql_sent = 0; } else if (!NIL_P(block)) { unsigned long i; for (i = 0; i < rwrap->number_of_rows; i++) { rb_yield(rb_ary_entry(rwrap->results, i)); } } return rwrap->results; } static VALUE rb_tinytds_result_cancel(VALUE self) { GET_RESULT_WRAPPER(self); GET_CLIENT_USERDATA(rwrap->client); if (rwrap->client && !userdata->dbcancel_sent) rb_tinytds_result_cancel_helper(rwrap->client); return Qtrue; } static VALUE rb_tinytds_result_do(VALUE self) { GET_RESULT_WRAPPER(self); if (rwrap->client) { rb_tinytds_result_cancel_helper(rwrap->client); return LONG2NUM((long)dbcount(rwrap->client)); } else { return Qnil; } } static VALUE rb_tinytds_result_affected_rows(VALUE self) { GET_RESULT_WRAPPER(self); if (rwrap->client) { return LONG2NUM((long)dbcount(rwrap->client)); } else { return Qnil; } } /* Duplicated in client.c */ static VALUE rb_tinytds_result_return_code(VALUE self) { GET_RESULT_WRAPPER(self); if (rwrap->client && dbhasretstat(rwrap->client)) { return LONG2NUM((long)dbretstatus(rwrap->client)); } else { return Qnil; } } static VALUE rb_tinytds_result_insert(VALUE self) { GET_RESULT_WRAPPER(self); if (rwrap->client) { rb_tinytds_result_cancel_helper(rwrap->client); VALUE identity = Qnil; dbcmd(rwrap->client, "SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident"); if (dbsqlexec(rwrap->client) != FAIL && dbresults(rwrap->client) != FAIL && DBROWS(rwrap->client) != FAIL) { while (dbnextrow(rwrap->client) != NO_MORE_ROWS) { int col = 1; BYTE *data = dbdata(rwrap->client, col); DBINT data_len = dbdatlen(rwrap->client, col); int null_val = ((data == NULL) && (data_len == 0)); if (!null_val) identity = LL2NUM(*(DBBIGINT *)data); } } return identity; } else { return Qnil; } } // Lib Init void init_tinytds_result() { /* Data Classes */ cBigDecimal = rb_const_get(rb_cObject, rb_intern("BigDecimal")); cDate = rb_const_get(rb_cObject, rb_intern("Date")); cDateTime = rb_const_get(rb_cObject, rb_intern("DateTime")); /* Define TinyTds::Result */ cTinyTdsResult = rb_define_class_under(mTinyTds, "Result", rb_cObject); /* Define TinyTds::Result Public Methods */ rb_define_method(cTinyTdsResult, "fields", rb_tinytds_result_fields, 0); rb_define_method(cTinyTdsResult, "each", rb_tinytds_result_each, -1); rb_define_method(cTinyTdsResult, "cancel", rb_tinytds_result_cancel, 0); rb_define_method(cTinyTdsResult, "do", rb_tinytds_result_do, 0); rb_define_method(cTinyTdsResult, "affected_rows", rb_tinytds_result_affected_rows, 0); rb_define_method(cTinyTdsResult, "return_code", rb_tinytds_result_return_code, 0); rb_define_method(cTinyTdsResult, "insert", rb_tinytds_result_insert, 0); /* Intern String Helpers */ intern_new = rb_intern("new"); intern_utc = rb_intern("utc"); intern_local = rb_intern("local"); intern_merge = rb_intern("merge"); intern_localtime = rb_intern("localtime"); intern_civil = rb_intern("civil"); intern_new_offset = rb_intern("new_offset"); intern_plus = rb_intern("+"); intern_divide = rb_intern("/"); intern_Rational = rb_intern("Rational"); /* Symbol Helpers */ sym_symbolize_keys = ID2SYM(rb_intern("symbolize_keys")); sym_as = ID2SYM(rb_intern("as")); sym_array = ID2SYM(rb_intern("array")); sym_cache_rows = ID2SYM(rb_intern("cache_rows")); sym_first = ID2SYM(rb_intern("first")); sym_local = ID2SYM(intern_local); sym_utc = ID2SYM(intern_utc); sym_timezone = ID2SYM(rb_intern("timezone")); /* Data Conversion Options */ opt_decimal_zero = rb_str_new2("0.0"); rb_global_variable(&opt_decimal_zero); opt_float_zero = rb_float_new((double)0); rb_global_variable(&opt_float_zero); opt_one = INT2NUM(1); opt_zero = INT2NUM(0); opt_four = INT2NUM(4); opt_19hdr = INT2NUM(1900); opt_tenk = INT2NUM(10000); opt_onemil = INT2NUM(1000000); /* Ruby version flags */ opt_ruby_186 = (rb_eval_string("RUBY_VERSION == '1.8.6'") == Qtrue) ? 1 : 0; /* Encoding */ #ifdef HAVE_RUBY_ENCODING_H binaryEncoding = rb_enc_find("binary"); #endif }