#include #include VALUE cTinyTdsClient; extern VALUE mTinyTds, cTinyTdsError; static ID sym_username, sym_password, sym_dataserver, sym_database, sym_appname, sym_tds_version, sym_login_timeout, sym_timeout, sym_encoding, sym_azure; static ID intern_source_eql, intern_severity_eql, intern_db_error_number_eql, intern_os_error_number_eql; static ID intern_new, intern_dup, intern_transpose_iconv_encoding, intern_local_offset, intern_gsub; VALUE opt_escape_regex, opt_escape_dblquote; // Lib Macros #define GET_CLIENT_WRAPPER(self) \ tinytds_client_wrapper *cwrap; \ Data_Get_Struct(self, tinytds_client_wrapper, cwrap) #define REQUIRE_OPEN_CLIENT(cwrap) \ if (cwrap->closed || cwrap->userdata->closed) { \ rb_raise(cTinyTdsError, "closed connection"); \ return Qnil; \ } // Lib Backend (Helpers) static VALUE rb_tinytds_raise_error(DBPROCESS *dbproc, int cancel, char *error, char *source, int severity, int dberr, int oserr) { GET_CLIENT_USERDATA(dbproc); if (cancel && !dbdead(dbproc) && userdata && !userdata->closed) { userdata->dbsqlok_sent = 1; dbsqlok(dbproc); userdata->dbcancel_sent = 1; dbcancel(dbproc); } VALUE e = rb_exc_new2(cTinyTdsError, error); rb_funcall(e, intern_source_eql, 1, rb_str_new2(source)); if (severity) rb_funcall(e, intern_severity_eql, 1, INT2FIX(severity)); if (dberr) rb_funcall(e, intern_db_error_number_eql, 1, INT2FIX(dberr)); if (oserr) rb_funcall(e, intern_os_error_number_eql, 1, INT2FIX(oserr)); rb_exc_raise(e); return Qnil; } // Lib Backend (Memory Management & Handlers) int tinytds_err_handler(DBPROCESS *dbproc, int severity, int dberr, int oserr, char *dberrstr, char *oserrstr) { static char *source = "error"; GET_CLIENT_USERDATA(dbproc); int return_value = INT_CONTINUE; int cancel = 0; switch(dberr) { case 100: /* SYBEVERDOWN */ return INT_CANCEL; case SYBESMSG: return return_value; case SYBEICONVO: dbfreebuf(dbproc); break; case SYBEICONVI: return INT_CANCEL; case SYBEFCON: case SYBESOCK: case SYBECONN: case SYBEREAD: return_value = INT_EXIT; break; case SYBESEOF: { if (userdata && userdata->timing_out) return_value = INT_TIMEOUT; return INT_CANCEL; break; } case SYBETIME: { if (userdata) { if (userdata->timing_out) { return INT_CONTINUE; } else { userdata->timing_out = 1; } } cancel = 1; break; } case SYBEWRIT: { if (userdata && (userdata->dbsqlok_sent || userdata->dbcancel_sent)) return INT_CANCEL; cancel = 1; break; } } rb_tinytds_raise_error(dbproc, cancel, dberrstr, source, severity, dberr, oserr); return return_value; } int tinytds_msg_handler(DBPROCESS *dbproc, DBINT msgno, int msgstate, int severity, char *msgtext, char *srvname, char *procname, int line) { static char *source = "message"; if (severity > 10) rb_tinytds_raise_error(dbproc, 1, msgtext, source, severity, msgno, msgstate); return 0; } static void rb_tinytds_client_reset_userdata(tinytds_client_userdata *userdata) { userdata->timing_out = 0; userdata->dbsql_sent = 0; userdata->dbsqlok_sent = 0; userdata->dbcancel_sent = 0; } static void rb_tinytds_client_mark(void *ptr) { tinytds_client_wrapper *cwrap = (tinytds_client_wrapper *)ptr; if (cwrap) { rb_gc_mark(cwrap->charset); } } static void rb_tinytds_client_free(void *ptr) { tinytds_client_wrapper *cwrap = (tinytds_client_wrapper *)ptr; if (cwrap->login) dbloginfree(cwrap->login); if (cwrap->client && !cwrap->closed) { dbclose(cwrap->client); cwrap->closed = 1; cwrap->userdata->closed = 1; } xfree(cwrap->userdata); xfree(ptr); } static VALUE allocate(VALUE klass) { VALUE obj; tinytds_client_wrapper *cwrap; obj = Data_Make_Struct(klass, tinytds_client_wrapper, rb_tinytds_client_mark, rb_tinytds_client_free, cwrap); cwrap->closed = 1; cwrap->charset = Qnil; cwrap->userdata = malloc(sizeof(tinytds_client_userdata)); cwrap->userdata->closed = 1; rb_tinytds_client_reset_userdata(cwrap->userdata); return obj; } // TinyTds::Client (public) static VALUE rb_tinytds_tds_version(VALUE self) { GET_CLIENT_WRAPPER(self); return INT2FIX(dbtds(cwrap->client)); } static VALUE rb_tinytds_close(VALUE self) { GET_CLIENT_WRAPPER(self); if (cwrap->client && !cwrap->closed) { dbclose(cwrap->client); cwrap->closed = 1; cwrap->userdata->closed = 1; } return Qtrue; } static VALUE rb_tinytds_dead(VALUE self) { GET_CLIENT_WRAPPER(self); return dbdead(cwrap->client) ? Qtrue : Qfalse; } static VALUE rb_tinytds_closed(VALUE self) { GET_CLIENT_WRAPPER(self); return (cwrap->closed || cwrap->userdata->closed) ? Qtrue : Qfalse; } static VALUE rb_tinytds_canceled(VALUE self) { GET_CLIENT_WRAPPER(self); return cwrap->userdata->dbcancel_sent ? Qtrue : Qfalse; } static VALUE rb_tinytds_sqlsent(VALUE self) { GET_CLIENT_WRAPPER(self); return cwrap->userdata->dbsql_sent ? Qtrue : Qfalse; } static VALUE rb_tinytds_execute(VALUE self, VALUE sql) { GET_CLIENT_WRAPPER(self); rb_tinytds_client_reset_userdata(cwrap->userdata); REQUIRE_OPEN_CLIENT(cwrap); dbcmd(cwrap->client, StringValuePtr(sql)); if (dbsqlsend(cwrap->client) == FAIL) { rb_warn("TinyTds: dbsqlsend() returned FAIL.\n"); return Qfalse; } cwrap->userdata->dbsql_sent = 1; VALUE result = rb_tinytds_new_result_obj(cwrap); rb_iv_set(result, "@query_options", rb_funcall(rb_iv_get(self, "@query_options"), intern_dup, 0)); GET_RESULT_WRAPPER(result); rwrap->local_offset = rb_funcall(cTinyTdsClient, intern_local_offset, 0); rwrap->encoding = cwrap->encoding; return result; } static VALUE rb_tinytds_charset(VALUE self) { GET_CLIENT_WRAPPER(self); return cwrap->charset; } static VALUE rb_tinytds_encoding(VALUE self) { GET_CLIENT_WRAPPER(self); return rb_enc_from_encoding(cwrap->encoding); } static VALUE rb_tinytds_escape(VALUE self, VALUE string) { Check_Type(string, T_STRING); GET_CLIENT_WRAPPER(self); VALUE new_string = rb_funcall(string, intern_gsub, 2, opt_escape_regex, opt_escape_dblquote); rb_enc_associate(new_string, cwrap->encoding); return new_string; } /* Duplicated in result.c */ static VALUE rb_tinytds_return_code(VALUE self) { GET_CLIENT_WRAPPER(self); if (cwrap->client && dbhasretstat(cwrap->client)) { return LONG2NUM((long)dbretstatus(cwrap->client)); } else { return Qnil; } } static VALUE rb_tinytds_identity_sql(VALUE self) { GET_CLIENT_WRAPPER(self); return rb_str_new2(cwrap->identity_insert_sql); } static VALUE rb_tinytds_freetds_nine_one_or_higher(VALUE self) { #ifdef DBSETLDBNAME return Qtrue; #else return Qfalse; #endif } // TinyTds::Client (protected) static VALUE rb_tinytds_connect(VALUE self, VALUE opts) { /* Parsing options hash to local vars. */ VALUE user, pass, dataserver, database, app, version, ltimeout, timeout, charset, azure; user = rb_hash_aref(opts, sym_username); pass = rb_hash_aref(opts, sym_password); dataserver = rb_hash_aref(opts, sym_dataserver); database = rb_hash_aref(opts, sym_database); app = rb_hash_aref(opts, sym_appname); version = rb_hash_aref(opts, sym_tds_version); ltimeout = rb_hash_aref(opts, sym_login_timeout); timeout = rb_hash_aref(opts, sym_timeout); charset = rb_hash_aref(opts, sym_encoding); azure = rb_hash_aref(opts, sym_azure); /* Dealing with options. */ if (dbinit() == FAIL) { rb_raise(cTinyTdsError, "failed dbinit() function"); return self; } dberrhandle(tinytds_err_handler); dbmsghandle(tinytds_msg_handler); GET_CLIENT_WRAPPER(self); cwrap->login = dblogin(); if (!NIL_P(user)) dbsetluser(cwrap->login, StringValuePtr(user)); if (!NIL_P(pass)) dbsetlpwd(cwrap->login, StringValuePtr(pass)); if (!NIL_P(app)) dbsetlapp(cwrap->login, StringValuePtr(app)); if (!NIL_P(version)) dbsetlversion(cwrap->login, NUM2INT(version)); if (!NIL_P(ltimeout)) dbsetlogintime(NUM2INT(ltimeout)); if (!NIL_P(timeout)) dbsettime(NUM2INT(timeout)); if (!NIL_P(charset)) DBSETLCHARSET(cwrap->login, StringValuePtr(charset)); if (!NIL_P(database) && (azure == Qtrue)) { #ifdef DBSETLDBNAME DBSETLDBNAME(cwrap->login, StringValuePtr(database)); #else rb_warn("TinyTds: Azure connections not supported in this version of FreeTDS.\n"); #endif } cwrap->client = dbopen(cwrap->login, StringValuePtr(dataserver)); if (cwrap->client) { cwrap->closed = 0; cwrap->charset = charset; if (!NIL_P(version)) dbsetversion(NUM2INT(version)); dbsetuserdata(cwrap->client, (BYTE*)cwrap->userdata); cwrap->userdata->closed = 0; if (!NIL_P(database) && (azure != Qtrue)) { dbuse(cwrap->client, StringValuePtr(database)); } VALUE transposed_encoding = rb_funcall(cTinyTdsClient, intern_transpose_iconv_encoding, 1, charset); cwrap->encoding = rb_enc_find(StringValuePtr(transposed_encoding)); if (dbtds(cwrap->client) <= 7) { cwrap->identity_insert_sql = "SELECT CAST(@@IDENTITY AS bigint) AS Ident"; } else { cwrap->identity_insert_sql = "SELECT CAST(SCOPE_IDENTITY() AS bigint) AS Ident"; } } return self; } // Lib Init void init_tinytds_client() { cTinyTdsClient = rb_define_class_under(mTinyTds, "Client", rb_cObject); rb_define_alloc_func(cTinyTdsClient, allocate); /* Define TinyTds::Client Public Methods */ rb_define_method(cTinyTdsClient, "tds_version", rb_tinytds_tds_version, 0); rb_define_method(cTinyTdsClient, "close", rb_tinytds_close, 0); rb_define_method(cTinyTdsClient, "closed?", rb_tinytds_closed, 0); rb_define_method(cTinyTdsClient, "canceled?", rb_tinytds_canceled, 0); rb_define_method(cTinyTdsClient, "dead?", rb_tinytds_dead, 0); rb_define_method(cTinyTdsClient, "sqlsent?", rb_tinytds_sqlsent, 0); rb_define_method(cTinyTdsClient, "execute", rb_tinytds_execute, 1); rb_define_method(cTinyTdsClient, "charset", rb_tinytds_charset, 0); rb_define_method(cTinyTdsClient, "encoding", rb_tinytds_encoding, 0); rb_define_method(cTinyTdsClient, "escape", rb_tinytds_escape, 1); rb_define_method(cTinyTdsClient, "return_code", rb_tinytds_return_code, 0); rb_define_method(cTinyTdsClient, "identity_sql", rb_tinytds_identity_sql, 0); rb_define_method(cTinyTdsClient, "freetds_091_or_higer?", rb_tinytds_freetds_nine_one_or_higher, 0); /* Define TinyTds::Client Protected Methods */ rb_define_protected_method(cTinyTdsClient, "connect", rb_tinytds_connect, 1); /* Symbols For Connect */ sym_username = ID2SYM(rb_intern("username")); sym_password = ID2SYM(rb_intern("password")); sym_dataserver = ID2SYM(rb_intern("dataserver")); sym_database = ID2SYM(rb_intern("database")); sym_appname = ID2SYM(rb_intern("appname")); sym_tds_version = ID2SYM(rb_intern("tds_version")); sym_login_timeout = ID2SYM(rb_intern("login_timeout")); sym_timeout = ID2SYM(rb_intern("timeout")); sym_encoding = ID2SYM(rb_intern("encoding")); sym_azure = ID2SYM(rb_intern("azure")); /* Intern TinyTds::Error Accessors */ intern_source_eql = rb_intern("source="); intern_severity_eql = rb_intern("severity="); intern_db_error_number_eql = rb_intern("db_error_number="); intern_os_error_number_eql = rb_intern("os_error_number="); /* Intern Misc */ intern_new = rb_intern("new"); intern_dup = rb_intern("dup"); intern_transpose_iconv_encoding = rb_intern("transpose_iconv_encoding"); intern_local_offset = rb_intern("local_offset"); intern_gsub = rb_intern("gsub"); /* Escape Regexp Global */ opt_escape_regex = rb_funcall(rb_cRegexp, intern_new, 1, rb_str_new2("\\\'")); opt_escape_dblquote = rb_str_new2("''"); rb_global_variable(&opt_escape_regex); rb_global_variable(&opt_escape_dblquote); }