From cc40bc66aaf44d5c1d63dddc25cbbf49c60bb587 Mon Sep 17 00:00:00 2001 From: Matt Conover Date: Thu, 16 Jan 2014 23:03:54 -0800 Subject: [PATCH 01/23] Update to 0.99995 --- ChangeLog | 13 ++-- doc/odbc.html | 8 ++- ext/extconf.rb | 8 +-- ext/odbc.c | 145 ++++++++++++++++++++++++++------------------ ext/utf8/extconf.rb | 20 +++--- 5 files changed, 114 insertions(+), 80 deletions(-) diff --git a/ChangeLog b/ChangeLog index 72a2c5d..4687627 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,8 +1,11 @@ ODBC binding for Ruby --------------------- -HEAD - * migrated to hoe and rake-compiler for the build process +Wed Mar 13 2013 version 0.99995 released + + * added ODBC::Database.use_sql_column_name= flag to switch + usage of SQLColAttributes(SQL_COLUMN_LABEL) to + SQLColAttributes(SQL_COLUMN_NAME) on per connection basis Sat Jan 15 2011 version 0.99994 released @@ -74,7 +77,7 @@ Fri Sep 15 2006 version 0.9993 released * more tweaks in extconf.rb for --enable-dlopen'ed utf8 version thanks Daigo Moriwaki for testing - * eliminated warnings for installer functions + * eliminated warnings for installer functions Mon Sep 11 2006 version 0.9992 released @@ -85,7 +88,7 @@ Mon Sep 11 2006 version 0.9992 released by Neville Burnell * handle SQL_NO_DATA for chunk-wise SQLGetData() * determine dynamically which API (UNICODE or ANSI) to - use for ODBC installer functions + use for ODBC installer functions * more 64 bit fixes * added missing SQLEndTran() in init.c @@ -143,7 +146,7 @@ Tue Sep 07 2004 version 0.995 released Wed Jul 14 2004 version 0.994 released - * fixed ODBC::Database.new|connect so that + * fixed ODBC::Database.new|connect so that ODBC::Database.new.drvconnect(string) works * fixed SQLTCHAR detect for Win32 in extconf.rb, thanks to Pedro Luis Castedo Cepeda diff --git a/doc/odbc.html b/doc/odbc.html index ec288f6..181c894 100644 --- a/doc/odbc.html +++ b/doc/odbc.html @@ -27,7 +27,7 @@

Ruby ODBC Reference

- Last update: Thu, 16 September 2010 + Last update: Wed, 13 March 2013

@@ -706,6 +706,12 @@

methods:

SQL_TIMESTAMP data types to Ruby objects. When true, Ruby Date and Time objects are represented in UTC, when false (default) in the local timezone. +
+ use_sql_column_name[=bool] +
Sets or queries the flag controlling how column names are + read from the data source. When false (default), the ODBC + API SQLColAttributes(SQL_COLUMN_LABEL) is used, + otherwise SQLColAttributes(SQL_COLUMN_NAME) is used.

singleton methods:

diff --git a/ext/extconf.rb b/ext/extconf.rb index 385ce28..33b1d7a 100644 --- a/ext/extconf.rb +++ b/ext/extconf.rb @@ -1,8 +1,6 @@ require 'mkmf' -if enable_config('win32-cross-compilation') - PLATFORM = 'mingw32' -elsif ! defined? PLATFORM +if ! defined? PLATFORM PLATFORM = RUBY_PLATFORM end @@ -19,7 +17,7 @@ def have_library_ex(lib, func="main", headers=nil) end end end - + dir_config("odbc") have_header("version.h") have_header("sql.h") || begin @@ -121,4 +119,4 @@ def have_library_ex(lib, func="main", headers=nil) end end -create_makefile("odbc_ext") +create_makefile("odbc") diff --git a/ext/odbc.c b/ext/odbc.c index 48594f8..8910579 100644 --- a/ext/odbc.c +++ b/ext/odbc.c @@ -1,6 +1,6 @@ /* * ODBC-Ruby binding - * Copyright (c) 2001-2011 Christian Werner + * Copyright (c) 2001-2013 Christian Werner * Portions copyright (c) 2004 Ryszard Niewisiewicz * Portions copyright (c) 2006 Carl Blakeley * @@ -8,7 +8,7 @@ * and redistribution of this file and for a * DISCLAIMER OF ALL WARRANTIES. * - * $Id: odbc.c,v 1.72 2011/01/15 08:02:55 chw Exp chw $ + * $Id: odbc.c,v 1.75 2013/03/13 19:31:13 chw Exp chw $ */ #undef ODBCVER @@ -40,8 +40,6 @@ #include #endif -static const char *VERSION = "0.99994"; - #ifndef HAVE_TYPE_SQLTCHAR #ifdef UNICODE typedef SQLWCHAR SQLTCHAR; @@ -170,6 +168,7 @@ typedef struct dbc { VALUE rbtime; VALUE gmtime; int upc; + VALUE use_sql_column_name; } DBC; typedef struct { @@ -352,7 +351,7 @@ static const char *colnamebuf[] = { #define LEN_ALIGN(x) \ ((x) + sizeof (double) - (((x) + sizeof (double)) % sizeof (double))) - + /* *---------------------------------------------------------------------- * @@ -626,7 +625,7 @@ uc_free(SQLWCHAR *str) #endif - + /* *---------------------------------------------------------------------- * @@ -653,7 +652,7 @@ dsn_init(VALUE self) rb_iv_set(self, "@descr", Qnil); return self; } - + /* *---------------------------------------------------------------------- * @@ -680,7 +679,7 @@ drv_init(VALUE self) rb_iv_set(self, "@attrs", rb_hash_new()); return self; } - + /* *---------------------------------------------------------------------- * @@ -929,7 +928,7 @@ mark_stmt(STMT *q) rb_gc_mark(q->dbc); } } - + /* *---------------------------------------------------------------------- * @@ -952,7 +951,7 @@ set_err(const char *msg, int warn) CVAR_SET(Cobj, warn ? IDatatinfo : IDataterror, a); return STR2CSTR(v); } - + /* *---------------------------------------------------------------------- * @@ -1283,7 +1282,7 @@ succeeded_nodata(SQLHENV henv, SQLHDBC hdbc, SQLHSTMT hstmt, SQLRETURN ret, } return succeeded_common(henv, hdbc, hstmt, ret, msgp); } - + /* *---------------------------------------------------------------------- * @@ -1324,7 +1323,7 @@ get_env(VALUE self) Data_Get_Struct(env_of(self), ENV, e); return e; } - + /* *---------------------------------------------------------------------- * @@ -1350,7 +1349,7 @@ get_dbc(VALUE self) Data_Get_Struct(self, DBC, p); return p; } - + /* *---------------------------------------------------------------------- * @@ -1381,7 +1380,7 @@ dbc_raise(VALUE self, VALUE msg) rb_raise(Cerror, "%s", buf); return Qnil; } - + /* *---------------------------------------------------------------------- * @@ -1419,7 +1418,7 @@ env_new(VALUE self) #endif return obj; } - + /* *---------------------------------------------------------------------- * @@ -1474,7 +1473,7 @@ dbc_dsns(VALUE self) } return aret; } - + /* *---------------------------------------------------------------------- * @@ -1550,7 +1549,7 @@ dbc_drivers(VALUE self) } return aret; } - + /* *---------------------------------------------------------------------- * @@ -1869,7 +1868,7 @@ dbc_rfdsn(int argc, VALUE *argv, VALUE self) return Qnil; #endif } - + /* *---------------------------------------------------------------------- * @@ -1897,7 +1896,7 @@ dbc_clrerror(VALUE self) CVAR_SET(Cobj, IDatatinfo, Qnil); return Qnil; } - + /* *---------------------------------------------------------------------- * @@ -1956,6 +1955,7 @@ dbc_new(int argc, VALUE *argv, VALUE self) list_init(&p->stmts, offsetof(STMT, link)); p->hdbc = SQL_NULL_HDBC; p->upc = 0; + p->use_sql_column_name = Qfalse; #endif if (env != Qnil) { ENV *e; @@ -1968,7 +1968,7 @@ dbc_new(int argc, VALUE *argv, VALUE self) } return obj; } - + /* *---------------------------------------------------------------------- * @@ -2197,6 +2197,20 @@ dbc_timeutc(int argc, VALUE *argv, VALUE self) return p->gmtime; } +static VALUE +dbc_use_scn(int argc, VALUE *argv, VALUE self) +{ + DBC *p = get_dbc(self); + VALUE val; + + if (argc > 0) { + rb_scan_args(argc, argv, "1", &val); + p->use_sql_column_name = + (val != Qnil && val != Qfalse) ? Qtrue : Qfalse; + } + return p->use_sql_column_name; +} + /* *---------------------------------------------------------------------- * @@ -2220,7 +2234,7 @@ dbc_dropall(VALUE self) } return self; } - + /* *---------------------------------------------------------------------- * @@ -2257,7 +2271,7 @@ dbc_disconnect(int argc, VALUE *argv, VALUE self) } return Qfalse; } - + /* *---------------------------------------------------------------------- * @@ -3171,7 +3185,7 @@ dbc_getinfo(int argc, VALUE *argv, VALUE self) } return Qnil; } - + /* *---------------------------------------------------------------------- * @@ -3185,7 +3199,7 @@ make_coltypes(SQLHSTMT hstmt, int ncols, char **msgp) { int i; COLTYPE *ret = NULL; - SQLLEN type, size; + SQLLEN type, size = 0; for (i = 0; i < ncols; i++) { SQLUSMALLINT ic = i + 1; @@ -3322,7 +3336,7 @@ make_coltypes(SQLHSTMT hstmt, int ncols, char **msgp) } return ret; } - + /* *---------------------------------------------------------------------- * @@ -3402,7 +3416,7 @@ retain_paraminfo_override(STMT *q, int nump, PARAMINFO *paraminfo) } } } - + /* *---------------------------------------------------------------------- * @@ -3447,7 +3461,7 @@ wrap_stmt(VALUE dbc, DBC *p, SQLHSTMT hstmt, STMT **qp) } return stmt; } - + /* *---------------------------------------------------------------------- * @@ -3557,7 +3571,7 @@ upcase_if(char *string, int upc) } return string; } - + /* *---------------------------------------------------------------------- * @@ -3567,7 +3581,7 @@ upcase_if(char *string, int upc) */ static VALUE -make_column(SQLHSTMT hstmt, int i, int upc) +make_column(SQLHSTMT hstmt, int i, int upc, int use_scn) { VALUE obj, v; SQLUSMALLINT ic = i + 1; @@ -3582,10 +3596,12 @@ make_column(SQLHSTMT hstmt, int i, int upc) name[0] = 0; if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt, - SQLColAttributes(hstmt, ic, SQL_COLUMN_LABEL, name, + SQLColAttributes(hstmt, ic, use_scn ? SQL_COLUMN_NAME : + SQL_COLUMN_LABEL, name, (SQLSMALLINT) sizeof (name), &name_len, NULL), - &msg, "SQLColAttributes(SQL_COLUMN_LABEL)")) { + &msg, use_scn ? "SQLColAttributes(SQL_COLUMN_NAME)" : + "SQLColAttributes(SQL_COLUMN_LABEL)")) { rb_raise(Cerror, "%s", msg); } obj = rb_obj_alloc(Ccolumn); @@ -3727,7 +3743,7 @@ make_column(SQLHSTMT hstmt, int i, int upc) rb_iv_set(obj, "@autoincrement", v); return obj; } - + /* *---------------------------------------------------------------------- * @@ -3767,7 +3783,7 @@ make_param(STMT *q, int i) rb_iv_set(obj, "@output_type", INT2NUM(v)); return obj; } - + /* *---------------------------------------------------------------------- * @@ -4051,7 +4067,7 @@ dbc_speccols(int argc, VALUE *argv, VALUE self) { return dbc_info(argc, argv, self, INFO_SPECCOLS); } - + /* *---------------------------------------------------------------------- * @@ -4146,7 +4162,7 @@ dbc_transaction(VALUE self) rb_funcall(ret, IDto_s, 0, 0))); return Qnil; } - + /* *---------------------------------------------------------------------- * @@ -4227,7 +4243,7 @@ env_odbcver(int argc, VALUE *argv, VALUE self) rb_raise(Cerror, "%s", set_err("Unsupported in ODBC < 3.0", 0)); #endif } - + /* *---------------------------------------------------------------------- * @@ -4519,7 +4535,7 @@ stmt_getsetoption(int argc, VALUE *argv, VALUE self) { return do_option(argc, argv, self, 1, -1); } - + /* *---------------------------------------------------------------------- * @@ -4597,7 +4613,7 @@ scan_dtts(VALUE str, int do_d, int do_t, TIMESTAMP_STRUCT *ts) } return 0; } - + /* *---------------------------------------------------------------------- * @@ -4832,7 +4848,7 @@ date_cmp(VALUE self, VALUE date) } return INT2FIX(1); } - + /* *---------------------------------------------------------------------- * @@ -5060,7 +5076,7 @@ time_cmp(VALUE self, VALUE time) } return INT2FIX(1); } - + /* *---------------------------------------------------------------------- * @@ -5403,7 +5419,7 @@ timestamp_cmp(VALUE self, VALUE timestamp) } return INT2FIX(1); } - + /* *---------------------------------------------------------------------- * @@ -5825,27 +5841,34 @@ stmt_column(int argc, VALUE *argv, VALUE self) { STMT *q; VALUE col; + int use_scn = 0; rb_scan_args(argc, argv, "1", &col); Check_Type(col, T_FIXNUM); Data_Get_Struct(self, STMT, q); check_ncols(q); - return make_column(q->hstmt, FIX2INT(col), q->upc); + if (q->dbcp != NULL && q->dbcp->use_sql_column_name == Qtrue) { + use_scn = 1; + } + return make_column(q->hstmt, FIX2INT(col), q->upc, use_scn); } static VALUE stmt_columns(int argc, VALUE *argv, VALUE self) { STMT *q; - int i; + int i, use_scn = 0; VALUE res, as_ary = Qfalse; rb_scan_args(argc, argv, "01", &as_ary); Data_Get_Struct(self, STMT, q); check_ncols(q); + if (q->dbcp != NULL && q->dbcp->use_sql_column_name == Qtrue) { + use_scn = 1; + } if (rb_block_given_p()) { for (i = 0; i < q->ncols; i++) { - rb_yield(make_column(q->hstmt, i, q->upc)); + rb_yield(make_column(q->hstmt, i, q->upc, use_scn)); } return self; } @@ -5857,7 +5880,7 @@ stmt_columns(int argc, VALUE *argv, VALUE self) for (i = 0; i < q->ncols; i++) { VALUE obj; - obj = make_column(q->hstmt, i, q->upc); + obj = make_column(q->hstmt, i, q->upc, use_scn); if (RTEST(as_ary)) { rb_ary_store(res, i, obj); } else { @@ -5920,7 +5943,7 @@ stmt_params(VALUE self) static VALUE do_fetch(STMT *q, int mode) { - int i, offc; + int i, use_scn = 0, offc; char **bufs, *msg; VALUE res; @@ -5960,6 +5983,9 @@ do_fetch(STMT *q, int mode) } } } + if (q->dbcp != NULL && q->dbcp->use_sql_column_name == Qtrue) { + use_scn = 1; + } switch (mode & DOFETCH_MODES) { case DOFETCH_HASH: case DOFETCH_HASH2: @@ -6010,10 +6036,13 @@ do_fetch(STMT *q, int mode) if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt, SQLColAttributes(q->hstmt, (SQLUSMALLINT) (i + 1), + use_scn ? SQL_COLUMN_NAME : SQL_COLUMN_LABEL, name, sizeof (name), &name_len, NULL), - &msg, "SQLColAttributes(SQL_COLUMN_LABEL)")) { + &msg, use_scn ? + "SQLColAttributes(SQL_COLUMN_NAME)" : + "SQLColAttributes(SQL_COLUMN_LABEL)")) { rb_raise(Cerror, "%s", msg); } if (name_len >= (SQLSMALLINT) sizeof (name)) { @@ -6066,8 +6095,10 @@ do_fetch(STMT *q, int mode) name[0] = 0; callsql(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt, SQLColAttributes(q->hstmt, (SQLUSMALLINT) (i + 1), + use_scn ? SQL_COLUMN_NAME : SQL_COLUMN_LABEL, name, sizeof (name), &name_len, NULL), + use_scn ? "SQLColAttributes(SQL_COLUMN_NAME)" : "SQLColAttributes(SQL_COLUMN_LABEL)"); if (name_len >= (SQLSMALLINT) sizeof (name)) { name_len = sizeof (name) - 1; @@ -7522,7 +7553,7 @@ stmt_ignorecase(int argc, VALUE *argv, VALUE self) } return *flag ? Qtrue : Qfalse; } - + /* *---------------------------------------------------------------------- * @@ -7545,7 +7576,7 @@ stmt_new(VALUE self) } return wrap_stmt(self, p, hstmt, NULL); } - + /* *---------------------------------------------------------------------- * @@ -7636,7 +7667,7 @@ stmt_procwrap(int argc, VALUE *argv, VALUE self) } return rb_funcall(Cproc, IDnew, 2, arg0, arg1); } - + /* *---------------------------------------------------------------------- * @@ -7819,7 +7850,7 @@ mod_trace(int argc, VALUE *argv, VALUE self) return INT2NUM(0); #endif } - + /* *---------------------------------------------------------------------- * @@ -8072,7 +8103,7 @@ static struct { { &IDlocal, "local" }, { &IDto_s, "to_s" } }; - + /* *---------------------------------------------------------------------- * @@ -8083,9 +8114,9 @@ static struct { void #ifdef UNICODE -Init_odbc_utf8_ext() +Init_odbc_utf8() #else -Init_odbc_ext() +Init_odbc() #endif { int i; @@ -8115,10 +8146,6 @@ Init_odbc_ext() } Modbc = rb_define_module(modname); - - /* Library version */ - rb_define_const(Modbc, "VERSION", rb_str_new2(VERSION) ); - Cobj = rb_define_class_under(Modbc, "Object", rb_cObject); rb_define_class_variable(Cobj, "@@error", Qnil); rb_define_class_variable(Cobj, "@@info", Qnil); @@ -8253,6 +8280,8 @@ Init_odbc_ext() rb_define_method(Cdbc, "use_time=", dbc_timefmt, -1); rb_define_method(Cdbc, "use_utc", dbc_timeutc, -1); rb_define_method(Cdbc, "use_utc=", dbc_timeutc, -1); + rb_define_method(Cdbc, "use_sql_column_name", dbc_use_scn, -1); + rb_define_method(Cdbc, "use_sql_column_name=", dbc_use_scn, -1); /* connection options */ rb_define_method(Cdbc, "get_option", dbc_getsetoption, -1); diff --git a/ext/utf8/extconf.rb b/ext/utf8/extconf.rb index d681767..344329f 100644 --- a/ext/utf8/extconf.rb +++ b/ext/utf8/extconf.rb @@ -1,8 +1,6 @@ require 'mkmf' -if enable_config('win32-cross-compilation') - PLATFORM = 'mingw32' -elsif ! defined? PLATFORM +if ! defined? PLATFORM PLATFORM = RUBY_PLATFORM end @@ -40,7 +38,7 @@ def have_func_nolink(func, headers = nil, &b) end end end - + dir_config("odbc") have_header("sql.h") || begin puts "ERROR: sql.h not found" @@ -114,16 +112,16 @@ def have_func_nolink(func, headers = nil, &b) have_func("SQLReadFileDSNW", "odbcinst.h") have_func("SQLInstallerError", "odbcinst.h") have_func("SQLInstallerErrorW", "odbcinst.h") -# mingw untested !!! elsif PLATFORM =~ /(mingw|cygwin)/ then have_library("odbc32", "") have_library("odbccp32", "") have_library("user32", "") - have_func("SQLConfigDataSourceW", "odbcinst.h") - have_func("SQLWriteFileDSNW", "odbcinst.h") - have_func("SQLReadFileDSNW", "odbcinst.h") - have_func("SQLInstallerError", "odbcinst.h") - have_func("SQLInstallerErrorW", "odbcinst.h") + header = ["windows.h", "odbcinst.h"] + have_func("SQLConfigDataSourceW", header) + have_func("SQLWriteFileDSNW", header) + have_func("SQLReadFileDSNW", header) + have_func("SQLInstallerError", header) + have_func("SQLInstallerErrorW", header) elsif (testdlopen && PLATFORM !~ /(macos|darwin)/ && CONFIG["CC"] =~ /gcc/ && have_func("dlopen", "dlfcn.h") && have_library("dl", "dlopen")) then $LDFLAGS+=" -Wl,-init -Wl,ruby_odbc_init -Wl,-fini -Wl,ruby_odbc_fini" $CPPFLAGS+=" -DHAVE_SQLCONFIGDATASOURCE" @@ -155,4 +153,4 @@ def have_func_nolink(func, headers = nil, &b) have_func("SQLInstallerErrorW", "odbcinst.h") end -create_makefile("odbc_utf8_ext") +create_makefile("odbc_utf8") From 4af7b48e056c7a00fa273e439d9cf7311b990f52 Mon Sep 17 00:00:00 2001 From: Matt Conover Date: Thu, 16 Jan 2014 23:05:41 -0800 Subject: [PATCH 02/23] Fix deadlock and performance issue Need to release global interpeter lock during SQL queries by using rb_thread_blocking_region Version bump to 0.99996 --- ChangeLog | 14 ++++++++ ext/odbc.c | 99 ++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 92 insertions(+), 21 deletions(-) diff --git a/ChangeLog b/ChangeLog index 4687627..9a0230d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,20 @@ ODBC binding for Ruby --------------------- +Wed Jan 16 2014 version 0.99996 released + + * Added call to rb_thread_blocking_region to release the global + interpreter lock during SQLExecute and SQLExecDirect and require it + afterwards. + + This improves performance by not blocking all Ruby threads during long + running queries. This also fixes a deadlock which can occur in SQLExecute and + SQLExecDirect which can happen when trying to read (if isolation level is + "read committed", "repeatable read", "serializable", etc.) or when trying to + modify a dirty row that has been modified by an uncommitted transaction. If this + transaction was initiated by a ruby thread in the same process, all ruby threads + will hang and the ruby process will become unresponsive. + Wed Mar 13 2013 version 0.99995 released * added ODBC::Database.use_sql_column_name= flag to switch diff --git a/ext/odbc.c b/ext/odbc.c index 8910579..befa95c 100644 --- a/ext/odbc.c +++ b/ext/odbc.c @@ -145,6 +145,56 @@ static SQLRETURN tracesql(SQLHENV henv, SQLHDBC hdbc, SQLHSTMT hstmt, #define SQL_NO_DATA SQL_NO_DATA_FOUND #endif +//////////////////////////////////////////////////////////////// + +/* emulate rb_thread_call_without_gvl with rb_thread_blocking_region */ +#define rb_thread_call_without_gvl(func, data1, ubf, data2) \ + rb_thread_blocking_region((rb_blocking_function_t *)func, data1, ubf, data2) + +typedef struct _SQLExecDirect_Args { + SQLHSTMT StatementHandle; +#ifdef _UNICODE + SQLWCHAR *StatementText; +#else + SQLCHAR *StatementText; +#endif + SQLINTEGER TextLength; +} SQLExecDirect_Args; + +typedef struct _SQLExecute_Args { + SQLHSTMT StatementHandle; +} SQLExecute_Args; + +VALUE +SQLExecute_wrapper(void *data) +{ + SQLExecute_Args *args = (SQLExecute_Args *)data; + return SQLExecute(args->StatementHandle); +} + +void +SQLExecute_unblock(void *data) +{ + SQLExecute_Args *args = (SQLExecute_Args *)data; + SQLCancel(args->StatementHandle); +} + +VALUE +SQLExecDirect_wrapper(void *data) +{ + SQLExecDirect_Args *args = (SQLExecDirect_Args *)data; + return SQLExecDirect(args->StatementHandle, args->StatementText, args->TextLength); +} + +void +SQLExecDirect_unblock(void *data) +{ + SQLExecDirect_Args *args = (SQLExecDirect_Args *)data; + SQLCancel(args->StatementHandle); +} + +//////////////////////////////////////////////////////////////// + typedef struct link { struct link *succ; struct link *pred; @@ -163,7 +213,7 @@ typedef struct dbc { VALUE self; VALUE env; struct env *envp; - LINK stmts; + LINK stmts; SQLHDBC hdbc; VALUE rbtime; VALUE gmtime; @@ -390,7 +440,7 @@ uc_strchr(SQLWCHAR *str, SQLWCHAR c) static int mkutf(char *dest, SQLWCHAR *src, int len) -{ +{ int i; char *cp = dest; @@ -745,7 +795,7 @@ list_first(LINK *head) static int list_empty(LINK *head) { - return head->succ == NULL; + return head->succ == NULL; } static void @@ -4385,8 +4435,8 @@ do_option(int argc, VALUE *argv, VALUE self, int isstmt, int op) if (val == Qnil) { return v ? Qtrue : Qfalse; } - v = (TYPE(val) == T_FIXNUM) ? - (FIX2INT(val) ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF) : + v = (TYPE(val) == T_FIXNUM) ? + (FIX2INT(val) ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF) : (RTEST(val) ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF); break; @@ -4394,8 +4444,8 @@ do_option(int argc, VALUE *argv, VALUE self, int isstmt, int op) if (val == Qnil) { return v ? Qtrue : Qfalse; } - v = (TYPE(val) == T_FIXNUM) ? - (FIX2INT(val) ? SQL_NOSCAN_ON : SQL_NOSCAN_OFF) : + v = (TYPE(val) == T_FIXNUM) ? + (FIX2INT(val) ? SQL_NOSCAN_ON : SQL_NOSCAN_OFF) : (RTEST(val) ? SQL_NOSCAN_ON : SQL_NOSCAN_OFF); break; @@ -4424,7 +4474,7 @@ do_option(int argc, VALUE *argv, VALUE self, int isstmt, int op) rb_raise(Cerror, "%s", msg); } } else { - if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt, + if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt, SQLSetStmtOption(q->hstmt, (SQLUSMALLINT) op, (SQLUINTEGER) v), &msg, "SQLSetStmtOption(%d)", op)) { @@ -4573,7 +4623,7 @@ scan_dtts(VALUE str, int do_d, int do_t, TIMESTAMP_STRUCT *ts) ts->day = dd; return 1; } - if (do_t && + if (do_t && (sscanf(cstr, "{t '%d:%d:%d' %c", &hh, &mmm, &ss, &c) == 4) && (c == '}')) { ts->hour = yy; @@ -4638,7 +4688,7 @@ date_new(int argc, VALUE *argv, VALUE self) { DATE_STRUCT *date; VALUE obj = Data_Make_Struct(self, DATE_STRUCT, 0, xfree, date); - + rb_obj_call_init(obj, argc, argv); return obj; } @@ -4873,7 +4923,7 @@ time_new(int argc, VALUE *argv, VALUE self) { TIME_STRUCT *time; VALUE obj = Data_Make_Struct(self, TIME_STRUCT, 0, xfree, time); - + rb_obj_call_init(obj, argc, argv); return obj; } @@ -4887,7 +4937,7 @@ time_load1(VALUE self, VALUE str, int load) if (scan_dtts(str, 0, 1, &tss)) { TIME_STRUCT *time; VALUE obj; - + if (load) { obj = Data_Make_Struct(self, TIME_STRUCT, 0, xfree, time); } else { @@ -5481,7 +5531,7 @@ check_ncols(STMT *q) (q->coltypes == NULL)) { COLTYPE *coltypes = NULL; SQLSMALLINT cols = 0; - + if (succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt, SQLNumResultCols(q->hstmt, &cols), NULL, "SQLNumResultCols") @@ -7005,10 +7055,14 @@ stmt_prep_int(int argc, VALUE *argv, VALUE self, int mode) #endif if ((mode & MAKERES_EXECD)) { SQLRETURN ret; + SQLExecDirect_Args args; - if (!succeeded_nodata(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt, - (ret = SQLExecDirect(hstmt, ssql, SQL_NTS)), - &msg, "SQLExecDirect('%s')", csql)) { + args.StatementHandle = hstmt; + args.StatementText = ssql; + args.TextLength = SQL_NTS; + ret = (SQLRETURN)rb_thread_call_without_gvl(SQLExecDirect_wrapper, &args, SQLExecDirect_unblock, &args); + + if (!succeeded_nodata(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt, ret, &msg, "SQLExecDirect('%s')", csql)) { goto sqlerr; } if (ret == SQL_NO_DATA) { @@ -7422,6 +7476,7 @@ stmt_exec_int(int argc, VALUE *argv, VALUE self, int mode) int i, argnum, has_out_parms = 0; char *msg = NULL; SQLRETURN ret; + SQLExecute_Args args; Data_Get_Struct(self, STMT, q); if (argc > q->nump - ((EXEC_PARMXOUT(mode) < 0) ? 0 : 1)) { @@ -7452,9 +7507,11 @@ stmt_exec_int(int argc, VALUE *argv, VALUE self, int mode) goto error; } } - if (!succeeded_nodata(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt, - (ret = SQLExecute(q->hstmt)), - &msg, "SQLExecute")) { + + args.StatementHandle = q->hstmt; + ret = (SQLRETURN)rb_thread_call_without_gvl(SQLExecute_wrapper, &args, SQLExecute_unblock, &args); + + if (!succeeded_nodata(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt, ret, &msg, "SQLExecute")) { error: #ifdef UNICODE for (i = 0; i < q->nump; i++) { @@ -7512,7 +7569,7 @@ static VALUE stmt_do(int argc, VALUE *argv, VALUE self) { VALUE stmt; - + if (argc < 1) { rb_raise(rb_eArgError, "wrong # of arguments"); } @@ -8128,7 +8185,7 @@ Init_odbc() rb_cDate = rb_eval_string("Date"); if (rb_const_defined(rb_cObject, modid)) { - v = rb_const_get(rb_cObject, modid); + v = rb_const_get(rb_cObject, modid); if (TYPE(v) != T_MODULE) { rb_raise(rb_eTypeError, "%s already defined", modname); } From ad69cbf63b4cb6e8eb07c312d2dec53610a38006 Mon Sep 17 00:00:00 2001 From: Matt Conover Date: Fri, 17 Jan 2014 00:02:01 -0800 Subject: [PATCH 03/23] Add gemspec --- .gemtest | 0 Manifest.txt | 27 --------------------------- ruby-odbc.gemspec | 16 ++++++++++++++++ 3 files changed, 16 insertions(+), 27 deletions(-) delete mode 100644 .gemtest delete mode 100644 Manifest.txt create mode 100644 ruby-odbc.gemspec diff --git a/.gemtest b/.gemtest deleted file mode 100644 index e69de29..0000000 diff --git a/Manifest.txt b/Manifest.txt deleted file mode 100644 index 0542a6c..0000000 --- a/Manifest.txt +++ /dev/null @@ -1,27 +0,0 @@ -.gemtest -COPYING -ChangeLog -GPL -MANIFEST -Manifest.txt -README.rdoc -Rakefile -doc/odbc.html -ext/extconf.rb -ext/init.c -ext/odbc.c -ext/utf8/extconf.rb -ext/utf8/init.c -ext/utf8/odbc.c -lib/cqgen.rb -lib/odbc.rb -lib/odbc_utf8.rb -test/00connect.rb -test/10create_table.rb -test/20insert.rb -test/30select.rb -test/40update.rb -test/50drop_table.rb -test/70close.rb -test/test.rb -test/utf8/test.rb diff --git a/ruby-odbc.gemspec b/ruby-odbc.gemspec new file mode 100644 index 0000000..a77aac1 --- /dev/null +++ b/ruby-odbc.gemspec @@ -0,0 +1,16 @@ +require 'date' +spec = Gem::Specification.new do |s| + s.name = "ruby-odbc" + s.version = "0.99996" + s.date = Date.today.to_s + s.author = "Christian Werner" + s.email = "chw @nospam@ ch-werner.de" + s.summary = "ODBC binding for Ruby" + s.homepage = "http://www.ch-werner.de/rubyodbc" + s.files = Dir.glob("**/*") + s.require_paths << 'lib' + s.test_files = Dir.glob('tests/*.rb') + s.has_rdoc = false + s.extra_rdoc_files = ["README.rdoc", "COPYING", "ChangeLog", "GPL", "doc/odbc.html"] + s.extensions = ["ext/extconf.rb", "ext/utf8/extconf.rb"] +end From c9d500d2ae2c19862efdb8690bb017ff5caea773 Mon Sep 17 00:00:00 2001 From: Matt Conover Date: Fri, 17 Jan 2014 00:23:04 -0800 Subject: [PATCH 04/23] Switch to 0.99996.cv to make it clear this isnt' the "official" version of ruby-odbc --- MANIFEST | 2 +- README.rdoc | 2 ++ ruby-odbc.gemspec | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/MANIFEST b/MANIFEST index 21b835a..b5c01e8 100644 --- a/MANIFEST +++ b/MANIFEST @@ -2,7 +2,7 @@ COPYING ChangeLog GPL MANIFEST -README +README.rd ruby-odbc.gemspec ext/extconf.rb ext/odbc.c diff --git a/README.rdoc b/README.rdoc index 4d5c174..4c2c19c 100644 --- a/README.rdoc +++ b/README.rdoc @@ -20,6 +20,8 @@ This is an ODBC binding for Ruby. So far it has been tested with - Ruby 1.8.*, SQLite/ODBC >= 0.67, libiodbc 3.52.4 on Fedora Core 3 x86 + - Ruby 2.0.0, SQLite/ODBC >= 0.93, unixODBC 2.2.14 on Ubuntu 12.04 x86 + Michael Neumann and Will Merrell reported successful compilation with Cygwin on Win32. diff --git a/ruby-odbc.gemspec b/ruby-odbc.gemspec index a77aac1..573dd8c 100644 --- a/ruby-odbc.gemspec +++ b/ruby-odbc.gemspec @@ -1,7 +1,7 @@ require 'date' spec = Gem::Specification.new do |s| s.name = "ruby-odbc" - s.version = "0.99996" + s.version = "0.99996.cv" s.date = Date.today.to_s s.author = "Christian Werner" s.email = "chw @nospam@ ch-werner.de" From 6dd0a9ef5029fae5024730bc55d9bf33f0975f4e Mon Sep 17 00:00:00 2001 From: Matt Conover Date: Fri, 17 Jan 2014 09:55:24 -0800 Subject: [PATCH 05/23] Remove these files because they cause problems --- lib/odbc.rb | 8 -------- lib/odbc_utf8.rb | 8 -------- 2 files changed, 16 deletions(-) delete mode 100644 lib/odbc.rb delete mode 100644 lib/odbc_utf8.rb diff --git a/lib/odbc.rb b/lib/odbc.rb deleted file mode 100644 index 90565ec..0000000 --- a/lib/odbc.rb +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env ruby - -# Extend the search path for Windows binary gem, depending of the current ruby version -major_minor = RUBY_VERSION[ /^(\d+\.\d+)/ ] or - raise "Oops, can't extract the major/minor version from #{RUBY_VERSION.dump}" -$: << File.join(File.dirname(__FILE__), major_minor) - -require 'odbc_ext' diff --git a/lib/odbc_utf8.rb b/lib/odbc_utf8.rb deleted file mode 100644 index 8057d60..0000000 --- a/lib/odbc_utf8.rb +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env ruby - -# Extend the search path for Windows binary gem, depending of the current ruby version -major_minor = RUBY_VERSION[ /^(\d+\.\d+)/ ] or - raise "Oops, can't extract the major/minor version from #{RUBY_VERSION.dump}" -$: << File.join(File.dirname(__FILE__), major_minor) - -require 'odbc_utf8_ext' From b63c6a6dd5fcf0ac8e883003a8f097bfcffee549 Mon Sep 17 00:00:00 2001 From: steve Date: Wed, 26 Feb 2014 13:33:01 -0800 Subject: [PATCH 06/23] Force encoding of all ruby strings in results to UTF8 if Encoding.default_external == 'UTF-8' See our wrapper function "utf8_tainted_str_new" for "rb_tainted_str_new" --- ChangeLog | 26 +++++++++-------- README.rdoc | 14 +++++++++ ext/odbc.c | 72 ++++++++++++++++++++++++++++++++--------------- ruby-odbc.gemspec | 2 +- 4 files changed, 80 insertions(+), 34 deletions(-) diff --git a/ChangeLog b/ChangeLog index 9a0230d..f16f4d5 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,19 +1,23 @@ ODBC binding for Ruby --------------------- +Wed Feb 26 2014 version 0.97 release + * Force encoding of all ruby strings in results to UTF8 if Encoding.default_external == 'UTF-8' + You must be saving and retrieving UTF8 compatible strings in the database. + Wed Jan 16 2014 version 0.99996 released - * Added call to rb_thread_blocking_region to release the global - interpreter lock during SQLExecute and SQLExecDirect and require it - afterwards. - - This improves performance by not blocking all Ruby threads during long - running queries. This also fixes a deadlock which can occur in SQLExecute and - SQLExecDirect which can happen when trying to read (if isolation level is - "read committed", "repeatable read", "serializable", etc.) or when trying to - modify a dirty row that has been modified by an uncommitted transaction. If this - transaction was initiated by a ruby thread in the same process, all ruby threads - will hang and the ruby process will become unresponsive. + * Added call to rb_thread_blocking_region to release the global + interpreter lock during SQLExecute and SQLExecDirect and require it + afterwards. + + This improves performance by not blocking all Ruby threads during long + running queries. This also fixes a deadlock which can occur in SQLExecute and + SQLExecDirect which can happen when trying to read (if isolation level is + "read committed", "repeatable read", "serializable", etc.) or when trying to + modify a dirty row that has been modified by an uncommitted transaction. If this + transaction was initiated by a ruby thread in the same process, all ruby threads + will hang and the ruby process will become unresponsive. Wed Mar 13 2013 version 0.99995 released diff --git a/README.rdoc b/README.rdoc index 4c2c19c..4af08da 100644 --- a/README.rdoc +++ b/README.rdoc @@ -2,6 +2,20 @@ * Homepage: http://www.ch-werner.de/rubyodbc/ +== CloudVolumes Improvements: + +This version releases the global interpreter lock during SQLExecute & SQLExecDirect. + +It also forces the encoding of all ruby result strings to UTF-8 if Encoding.default_external is 'UTF-8'. + +For example: +> Encoding.default_external = 'UTF-8' +> row = # some database query +> puts row[:some_string_column].encoding # UTF-8 + +Ensure you are saving UTF-8 encoded (or compatible) strings into the database. + + == DESCRIPTION: This is an ODBC binding for Ruby. So far it has been tested with diff --git a/ext/odbc.c b/ext/odbc.c index befa95c..7a14929 100644 --- a/ext/odbc.c +++ b/ext/odbc.c @@ -99,15 +99,17 @@ BOOL INSTAPI SQLReadFileDSNW(LPWSTR, LPWSTR, LPWSTR, LPWSTR, WORD, WORD *); BOOL INSTAPI SQLWriteFileDSNW(LPWSTR, LPWSTR, LPWSTR, LPWSTR); #endif +#endif /* UNICODE */ + #if defined(HAVE_RUBY_ENCODING_H) && HAVE_RUBY_ENCODING_H #define USE_RB_ENC 1 #include "ruby/encoding.h" static rb_encoding *rb_enc = NULL; +static rb_encoding *rb_external_encoding = NULL; +static int rb_encoding_is_utf8 = 0; static VALUE rb_encv = Qnil; #endif -#endif /* UNICODE */ - #ifndef HAVE_RB_DEFINE_ALLOC_FUNC #define rb_define_alloc_func(cls, func) \ rb_define_singleton_method(cls, "new", func, -1) @@ -192,7 +194,7 @@ SQLExecDirect_unblock(void *data) SQLExecDirect_Args *args = (SQLExecDirect_Args *)data; SQLCancel(args->StatementHandle); } - + //////////////////////////////////////////////////////////////// typedef struct link { @@ -213,7 +215,7 @@ typedef struct dbc { VALUE self; VALUE env; struct env *envp; - LINK stmts; + LINK stmts; SQLHDBC hdbc; VALUE rbtime; VALUE gmtime; @@ -440,7 +442,7 @@ uc_strchr(SQLWCHAR *str, SQLWCHAR c) static int mkutf(char *dest, SQLWCHAR *src, int len) -{ +{ int i; char *cp = dest; @@ -675,6 +677,22 @@ uc_free(SQLWCHAR *str) #endif +static VALUE +utf8_tainted_str_new(SQLCHAR *str, int len) +{ + VALUE v; + + v = rb_tainted_str_new(str, len); + +#ifdef USE_RB_ENC + if(rb_encoding_is_utf8) + { + rb_enc_associate(v, rb_enc); + } +#endif + + return v; +} /* *---------------------------------------------------------------------- @@ -795,7 +813,7 @@ list_first(LINK *head) static int list_empty(LINK *head) { - return head->succ == NULL; + return head->succ == NULL; } static void @@ -4435,8 +4453,8 @@ do_option(int argc, VALUE *argv, VALUE self, int isstmt, int op) if (val == Qnil) { return v ? Qtrue : Qfalse; } - v = (TYPE(val) == T_FIXNUM) ? - (FIX2INT(val) ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF) : + v = (TYPE(val) == T_FIXNUM) ? + (FIX2INT(val) ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF) : (RTEST(val) ? SQL_AUTOCOMMIT_ON : SQL_AUTOCOMMIT_OFF); break; @@ -4444,8 +4462,8 @@ do_option(int argc, VALUE *argv, VALUE self, int isstmt, int op) if (val == Qnil) { return v ? Qtrue : Qfalse; } - v = (TYPE(val) == T_FIXNUM) ? - (FIX2INT(val) ? SQL_NOSCAN_ON : SQL_NOSCAN_OFF) : + v = (TYPE(val) == T_FIXNUM) ? + (FIX2INT(val) ? SQL_NOSCAN_ON : SQL_NOSCAN_OFF) : (RTEST(val) ? SQL_NOSCAN_ON : SQL_NOSCAN_OFF); break; @@ -4474,7 +4492,7 @@ do_option(int argc, VALUE *argv, VALUE self, int isstmt, int op) rb_raise(Cerror, "%s", msg); } } else { - if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt, + if (!succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt, SQLSetStmtOption(q->hstmt, (SQLUSMALLINT) op, (SQLUINTEGER) v), &msg, "SQLSetStmtOption(%d)", op)) { @@ -4623,7 +4641,7 @@ scan_dtts(VALUE str, int do_d, int do_t, TIMESTAMP_STRUCT *ts) ts->day = dd; return 1; } - if (do_t && + if (do_t && (sscanf(cstr, "{t '%d:%d:%d' %c", &hh, &mmm, &ss, &c) == 4) && (c == '}')) { ts->hour = yy; @@ -4688,7 +4706,7 @@ date_new(int argc, VALUE *argv, VALUE self) { DATE_STRUCT *date; VALUE obj = Data_Make_Struct(self, DATE_STRUCT, 0, xfree, date); - + rb_obj_call_init(obj, argc, argv); return obj; } @@ -4923,7 +4941,7 @@ time_new(int argc, VALUE *argv, VALUE self) { TIME_STRUCT *time; VALUE obj = Data_Make_Struct(self, TIME_STRUCT, 0, xfree, time); - + rb_obj_call_init(obj, argc, argv); return obj; } @@ -4937,7 +4955,7 @@ time_load1(VALUE self, VALUE str, int load) if (scan_dtts(str, 0, 1, &tss)) { TIME_STRUCT *time; VALUE obj; - + if (load) { obj = Data_Make_Struct(self, TIME_STRUCT, 0, xfree, time); } else { @@ -5531,7 +5549,7 @@ check_ncols(STMT *q) (q->coltypes == NULL)) { COLTYPE *coltypes = NULL; SQLSMALLINT cols = 0; - + if (succeeded(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt, SQLNumResultCols(q->hstmt, &cols), NULL, "SQLNumResultCols") @@ -5779,7 +5797,7 @@ stmt_param_output_value(int argc, VALUE *argv, VALUE self) break; #endif case SQL_C_CHAR: - v = rb_tainted_str_new(q->paraminfo[vnum].outbuf, + v = utf8_tainted_str_new(q->paraminfo[vnum].outbuf, q->paraminfo[vnum].rlen); break; } @@ -6035,7 +6053,7 @@ do_fetch(STMT *q, int mode) } if (q->dbcp != NULL && q->dbcp->use_sql_column_name == Qtrue) { use_scn = 1; - } + } switch (mode & DOFETCH_MODES) { case DOFETCH_HASH: case DOFETCH_HASH2: @@ -6439,7 +6457,7 @@ do_fetch(STMT *q, int mode) break; #endif default: - v = rb_tainted_str_new(valp, curlen); + v = utf8_tainted_str_new(valp, curlen); break; } } @@ -7510,7 +7528,7 @@ stmt_exec_int(int argc, VALUE *argv, VALUE self, int mode) args.StatementHandle = q->hstmt; ret = (SQLRETURN)rb_thread_call_without_gvl(SQLExecute_wrapper, &args, SQLExecute_unblock, &args); - + if (!succeeded_nodata(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt, ret, &msg, "SQLExecute")) { error: #ifdef UNICODE @@ -7569,7 +7587,7 @@ static VALUE stmt_do(int argc, VALUE *argv, VALUE self) { VALUE stmt; - + if (argc < 1) { rb_raise(rb_eArgError, "wrong # of arguments"); } @@ -8185,7 +8203,7 @@ Init_odbc() rb_cDate = rb_eval_string("Date"); if (rb_const_defined(rb_cObject, modid)) { - v = rb_const_get(rb_cObject, modid); + v = rb_const_get(rb_cObject, modid); if (TYPE(v) != T_MODULE) { rb_raise(rb_eTypeError, "%s already defined", modname); } @@ -8519,6 +8537,16 @@ Init_odbc() #endif #else rb_define_const(Modbc, "UTF8", Qfalse); +#ifdef USE_RB_ENC + rb_enc = rb_utf8_encoding(); + rb_external_encoding = rb_default_external_encoding(); + + if( rb_enc && rb_external_encoding && rb_enc == rb_external_encoding ) + { + printf("Ruby-ODBC: Force Encoding ruby result strings to UTF-8\n"); + rb_encoding_is_utf8 = 1; + } +#endif #endif #ifdef TRACING diff --git a/ruby-odbc.gemspec b/ruby-odbc.gemspec index 573dd8c..4970dd5 100644 --- a/ruby-odbc.gemspec +++ b/ruby-odbc.gemspec @@ -1,7 +1,7 @@ require 'date' spec = Gem::Specification.new do |s| s.name = "ruby-odbc" - s.version = "0.99996.cv" + s.version = "0.97.cv" s.date = Date.today.to_s s.author = "Christian Werner" s.email = "chw @nospam@ ch-werner.de" From 901ea1cbc55e796d05a9ccaf4971c70a791b0bfc Mon Sep 17 00:00:00 2001 From: Steve Lawson Date: Wed, 26 Feb 2014 13:45:08 -0800 Subject: [PATCH 07/23] Fix some formatting on Readme --- README.rdoc | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.rdoc b/README.rdoc index 4af08da..3f0097b 100644 --- a/README.rdoc +++ b/README.rdoc @@ -9,9 +9,10 @@ This version releases the global interpreter lock during SQLExecute & SQLExecDir It also forces the encoding of all ruby result strings to UTF-8 if Encoding.default_external is 'UTF-8'. For example: -> Encoding.default_external = 'UTF-8' -> row = # some database query -> puts row[:some_string_column].encoding # UTF-8 + > Encoding.default_external = 'UTF-8' + > row = # some database query + > puts row[:some_string_column].encoding # UTF-8 + Ensure you are saving UTF-8 encoded (or compatible) strings into the database. From 822903cd4080fd843a2ed00b9fa9b125b30b9c18 Mon Sep 17 00:00:00 2001 From: Matt Conover Date: Tue, 8 Apr 2014 01:05:46 -0700 Subject: [PATCH 08/23] Increase login timeout --- ext/odbc.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ext/odbc.c b/ext/odbc.c index befa95c..bb577e3 100644 --- a/ext/odbc.c +++ b/ext/odbc.c @@ -2040,6 +2040,7 @@ dbc_connect(int argc, VALUE *argv, VALUE self) #endif char *msg; SQLHDBC dbc; + SQLUINTEGER nTimeout = 30; // Timeout rb_scan_args(argc, argv, "03", &dsn, &user, &passwd); if (dsn != Qnil) { @@ -2111,6 +2112,13 @@ dbc_connect(int argc, VALUE *argv, VALUE self) #endif rb_raise(Cerror, "%s", msg); } + + { + SQLRETURN nRet; + nRet = SQLSetConnectAttr(dbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER)nTimeout, SQL_IS_INTEGER); + if (!SQL_SUCCEEDED(nRet)) printf("Failed to set timeout to %d: error %d\n", nTimeout, nRet); + } + if (!succeeded(SQL_NULL_HENV, dbc, SQL_NULL_HSTMT, SQLConnect(dbc, (SQLTCHAR *) sdsn, SQL_NTS, (SQLTCHAR *) suser, From 109ab07865b9cad8f8cd1d21dfda5ab4a73b25ae Mon Sep 17 00:00:00 2001 From: Matt Conover Date: Tue, 8 Apr 2014 01:11:26 -0700 Subject: [PATCH 09/23] Version bump to 0.98 to increase connect timeout --- ChangeLog | 3 +++ ext/odbc.c | 13 ++++++++++--- ruby-odbc.gemspec | 2 +- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/ChangeLog b/ChangeLog index f16f4d5..f07d99e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,9 @@ ODBC binding for Ruby --------------------- +Wed April 8 2014 version 0.98 release + * Set login time to 30 seconds + Wed Feb 26 2014 version 0.97 release * Force encoding of all ruby strings in results to UTF8 if Encoding.default_external == 'UTF-8' You must be saving and retrieving UTF8 compatible strings in the database. diff --git a/ext/odbc.c b/ext/odbc.c index da18171..90de4ff 100644 --- a/ext/odbc.c +++ b/ext/odbc.c @@ -129,7 +129,7 @@ static VALUE rb_encv = Qnil; #endif #ifdef TRACING -static int tracing = 0; +static int tracing = 1; #define tracemsg(t, x) {if (tracing & t) { x }} static SQLRETURN tracesql(SQLHENV henv, SQLHDBC hdbc, SQLHSTMT hstmt, SQLRETURN ret, const char *m); @@ -1245,6 +1245,7 @@ tracesql(SQLHENV henv, SQLHDBC hdbc, SQLHSTMT hstmt, SQLRETURN ret, (long) henv, (long) hdbc, (long) hstmt); trace_sql_ret(ret); } + return ret; } #endif @@ -2058,7 +2059,6 @@ dbc_connect(int argc, VALUE *argv, VALUE self) #endif char *msg; SQLHDBC dbc; - SQLUINTEGER nTimeout = 30; // Timeout rb_scan_args(argc, argv, "03", &dsn, &user, &passwd); if (dsn != Qnil) { @@ -2133,8 +2133,15 @@ dbc_connect(int argc, VALUE *argv, VALUE self) { SQLRETURN nRet; + + SQLUINTEGER nTimeout = 30; // Timeout + SQLUINTEGER nOldTimeout = 0; + + nRet = SQLGetConnectAttr(dbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER)&nOldTimeout, SQL_IS_INTEGER, NULL); + printf("Old timeout is %d seconds, changing to %d\n", nOldTimeout, nTimeout); + nRet = SQLSetConnectAttr(dbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER)nTimeout, SQL_IS_INTEGER); - if (!SQL_SUCCEEDED(nRet)) printf("Failed to set timeout to %d: error %d\n", nTimeout, nRet); + if (!SQL_SUCCEEDED(nRet)) fprintf(stderr, "Failed to set timeout to %d: error %d\n", nTimeout, nRet); } if (!succeeded(SQL_NULL_HENV, dbc, SQL_NULL_HSTMT, diff --git a/ruby-odbc.gemspec b/ruby-odbc.gemspec index 4970dd5..ff9ad83 100644 --- a/ruby-odbc.gemspec +++ b/ruby-odbc.gemspec @@ -1,7 +1,7 @@ require 'date' spec = Gem::Specification.new do |s| s.name = "ruby-odbc" - s.version = "0.97.cv" + s.version = "0.98.cv" s.date = Date.today.to_s s.author = "Christian Werner" s.email = "chw @nospam@ ch-werner.de" From 7bc8d0e2881dfa4318852254aca5acb425ae8201 Mon Sep 17 00:00:00 2001 From: Matt Conover Date: Sat, 12 Apr 2014 02:43:21 -0700 Subject: [PATCH 10/23] Remove printf --- ext/odbc.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/odbc.c b/ext/odbc.c index 90de4ff..afa63f2 100644 --- a/ext/odbc.c +++ b/ext/odbc.c @@ -2135,10 +2135,10 @@ dbc_connect(int argc, VALUE *argv, VALUE self) SQLRETURN nRet; SQLUINTEGER nTimeout = 30; // Timeout - SQLUINTEGER nOldTimeout = 0; - nRet = SQLGetConnectAttr(dbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER)&nOldTimeout, SQL_IS_INTEGER, NULL); - printf("Old timeout is %d seconds, changing to %d\n", nOldTimeout, nTimeout); + //SQLUINTEGER nOldTimeout = 0; + //nRet = SQLGetConnectAttr(dbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER)&nOldTimeout, SQL_IS_INTEGER, NULL); + //printf("Old timeout is %d seconds, changing to %d\n", nOldTimeout, nTimeout); nRet = SQLSetConnectAttr(dbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER)nTimeout, SQL_IS_INTEGER); if (!SQL_SUCCEEDED(nRet)) fprintf(stderr, "Failed to set timeout to %d: error %d\n", nTimeout, nRet); From 9e3e74037388b5c6ac96647eee3132c6a828cbb4 Mon Sep 17 00:00:00 2001 From: Matt Conover Date: Sat, 12 Apr 2014 05:51:42 -0700 Subject: [PATCH 11/23] Bump version and remove debug message --- ChangeLog | 5 ++++- ruby-odbc.gemspec | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/ChangeLog b/ChangeLog index f07d99e..6d933cb 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,7 +1,10 @@ ODBC binding for Ruby --------------------- -Wed April 8 2014 version 0.98 release +Wed April 12 2014 version 0.99 release + * Remove debug log + +Wed April 8 2014 version 0.98 release * Set login time to 30 seconds Wed Feb 26 2014 version 0.97 release diff --git a/ruby-odbc.gemspec b/ruby-odbc.gemspec index ff9ad83..98c60dd 100644 --- a/ruby-odbc.gemspec +++ b/ruby-odbc.gemspec @@ -1,7 +1,7 @@ require 'date' spec = Gem::Specification.new do |s| s.name = "ruby-odbc" - s.version = "0.98.cv" + s.version = "0.99.cv" s.date = Date.today.to_s s.author = "Christian Werner" s.email = "chw @nospam@ ch-werner.de" From 0c49a60706b1ad3be557d609e5b7afb7ed06b707 Mon Sep 17 00:00:00 2001 From: Steve Lawson Date: Wed, 31 Aug 2016 11:50:54 -0700 Subject: [PATCH 12/23] Support for Ruby 2.x Don't redefine rb_thread_call_without_gvl when using Ruby 2.x --- ext/extconf.rb | 5 +++++ ext/odbc.c | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ext/extconf.rb b/ext/extconf.rb index 33b1d7a..4e00a6b 100644 --- a/ext/extconf.rb +++ b/ext/extconf.rb @@ -3,6 +3,11 @@ if ! defined? PLATFORM PLATFORM = RUBY_PLATFORM end +# 2.0+ +#adding this for backward compatible +have_header('ruby/thread.h') && have_func('rb_thread_call_without_gvl', 'ruby/thread.h') +# 1.9-only +have_func('rb_thread_blocking_region') def have_library_ex(lib, func="main", headers=nil) checking_for "#{func}() in -l#{lib}" do diff --git a/ext/odbc.c b/ext/odbc.c index afa63f2..aa510ff 100644 --- a/ext/odbc.c +++ b/ext/odbc.c @@ -149,9 +149,10 @@ static SQLRETURN tracesql(SQLHENV henv, SQLHDBC hdbc, SQLHSTMT hstmt, //////////////////////////////////////////////////////////////// -/* emulate rb_thread_call_without_gvl with rb_thread_blocking_region */ +#ifdef HAVE_RB_THREAD_BLOCKING_REGION #define rb_thread_call_without_gvl(func, data1, ubf, data2) \ rb_thread_blocking_region((rb_blocking_function_t *)func, data1, ubf, data2) +#endif typedef struct _SQLExecDirect_Args { SQLHSTMT StatementHandle; From 9867c132bf5de82b256c5a43242e4297404ad5da Mon Sep 17 00:00:00 2001 From: slawson Date: Wed, 31 Aug 2016 11:58:59 -0700 Subject: [PATCH 13/23] Add .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..923a6c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*.gem +/tmp/ +.idea/ From cfb12eb1c06fc74cdb01cceada7ab2484468e7cb Mon Sep 17 00:00:00 2001 From: slawson Date: Wed, 31 Aug 2016 12:09:33 -0700 Subject: [PATCH 14/23] Version bump to 0.100.cv --- ChangeLog | 3 +++ README.rdoc | 2 ++ ruby-odbc.gemspec | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 6d933cb..af56cca 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,9 @@ ODBC binding for Ruby --------------------- +Wed August 31 2016 version 0.100.cv release + * Add support for Ruby 2.x by using native "rb_thread_call_without_gvl" directly (thanks @shreyasahir) + Wed April 12 2014 version 0.99 release * Remove debug log diff --git a/README.rdoc b/README.rdoc index 3f0097b..b5cf82f 100644 --- a/README.rdoc +++ b/README.rdoc @@ -37,6 +37,8 @@ This is an ODBC binding for Ruby. So far it has been tested with - Ruby 2.0.0, SQLite/ODBC >= 0.93, unixODBC 2.2.14 on Ubuntu 12.04 x86 + - Ruby 2.2.5, MSSQL/ODBC on Windows 2008R2 (AppVolumes) + Michael Neumann and Will Merrell reported successful compilation with Cygwin on Win32. diff --git a/ruby-odbc.gemspec b/ruby-odbc.gemspec index 98c60dd..58b14a8 100644 --- a/ruby-odbc.gemspec +++ b/ruby-odbc.gemspec @@ -1,7 +1,7 @@ require 'date' spec = Gem::Specification.new do |s| s.name = "ruby-odbc" - s.version = "0.99.cv" + s.version = "0.100.cv" s.date = Date.today.to_s s.author = "Christian Werner" s.email = "chw @nospam@ ch-werner.de" From efd4596ebe4b2750bc1505f95fe60daa0d2e3d20 Mon Sep 17 00:00:00 2001 From: Ajith Kumar Date: Tue, 12 Feb 2019 15:55:38 +0530 Subject: [PATCH 15/23] [AVM-8825] Fixing Reference errors by removing empty params from have_func --- ext/extconf.rb | 6 +++--- ext/utf8/extconf.rb | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ext/extconf.rb b/ext/extconf.rb index 4e00a6b..77aafa1 100644 --- a/ext/extconf.rb +++ b/ext/extconf.rb @@ -95,9 +95,9 @@ def have_library_ex(lib, func="main", headers=nil) have_func("SQLInstallerError", "odbcinst.h") # mingw untested !!! elsif PLATFORM =~ /(mingw|cygwin)/ then - have_library("odbc32", "") - have_library("odbccp32", "") - have_library("user32", "") + have_library("odbc32") + have_library("odbccp32") + have_library("user32") elsif (testdlopen && PLATFORM !~ /(macos|darwin)/ && CONFIG["CC"] =~ /gcc/ && have_func("dlopen", "dlfcn.h") && have_library("dl", "dlopen")) then $LDFLAGS+=" -Wl,-init -Wl,ruby_odbc_init -Wl,-fini -Wl,ruby_odbc_fini" $CPPFLAGS+=" -DHAVE_SQLCONFIGDATASOURCE" diff --git a/ext/utf8/extconf.rb b/ext/utf8/extconf.rb index 344329f..aa99196 100644 --- a/ext/utf8/extconf.rb +++ b/ext/utf8/extconf.rb @@ -113,9 +113,9 @@ def have_func_nolink(func, headers = nil, &b) have_func("SQLInstallerError", "odbcinst.h") have_func("SQLInstallerErrorW", "odbcinst.h") elsif PLATFORM =~ /(mingw|cygwin)/ then - have_library("odbc32", "") - have_library("odbccp32", "") - have_library("user32", "") + have_library("odbc32") + have_library("odbccp32") + have_library("user32") header = ["windows.h", "odbcinst.h"] have_func("SQLConfigDataSourceW", header) have_func("SQLWriteFileDSNW", header) From bef5ef548087f0739907d12798872080c0836408 Mon Sep 17 00:00:00 2001 From: Ajith Kumar Date: Tue, 12 Feb 2019 15:37:04 +0530 Subject: [PATCH 16/23] [AVM-8821] Fixing invalid operands to binary / (have 'int' and 'char *') error when upgrading to ruby 2.4.5 --- ext/odbc.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ext/odbc.c b/ext/odbc.c index aa510ff..50b0f64 100644 --- a/ext/odbc.c +++ b/ext/odbc.c @@ -4381,8 +4381,11 @@ do_option(int argc, VALUE *argv, VALUE self, int isstmt, int op) SQLINTEGER v; char *msg; int level = isstmt ? OPT_LEVEL_STMT : OPT_LEVEL_DBC; - - rb_scan_args(argc, argv, (op == -1) ? "11" : "01", &val, &val2); + char *opvalue = "01"; + if(op == -1) { + opvalue == "11"; + } + rb_scan_args(argc, argv, opvalue, &val, &val2); if (isstmt) { Data_Get_Struct(self, STMT, q); if (q->dbc == Qnil) { From 669b8f8bee617f2565f5d2c2fa0152e5222e0322 Mon Sep 17 00:00:00 2001 From: Steve Lawson Date: Tue, 12 Feb 2019 17:34:46 -0800 Subject: [PATCH 17/23] Version bump to 0.101.cv to support Ruby 2.4.5 --- ChangeLog | 5 +++++ README.rdoc | 2 ++ ruby-odbc.gemspec | 2 +- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index af56cca..4869f18 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,11 @@ ODBC binding for Ruby --------------------- +Tue Feb 12 2019 version 0.101.cv release + * Minor syntax changes to support Ruby 2.4 + Don't pass "" as the second parameter to has_library() + Ensure char* are passed to rb_scan_args() + Wed August 31 2016 version 0.100.cv release * Add support for Ruby 2.x by using native "rb_thread_call_without_gvl" directly (thanks @shreyasahir) diff --git a/README.rdoc b/README.rdoc index b5cf82f..d8bc8c9 100644 --- a/README.rdoc +++ b/README.rdoc @@ -39,6 +39,8 @@ This is an ODBC binding for Ruby. So far it has been tested with - Ruby 2.2.5, MSSQL/ODBC on Windows 2008R2 (AppVolumes) + - Ruby 2.4.5, MSSQL/ODBC on Windows 2012R2 (AppVolumes) + Michael Neumann and Will Merrell reported successful compilation with Cygwin on Win32. diff --git a/ruby-odbc.gemspec b/ruby-odbc.gemspec index 58b14a8..ee60f1b 100644 --- a/ruby-odbc.gemspec +++ b/ruby-odbc.gemspec @@ -1,7 +1,7 @@ require 'date' spec = Gem::Specification.new do |s| s.name = "ruby-odbc" - s.version = "0.100.cv" + s.version = "0.101.cv" s.date = Date.today.to_s s.author = "Christian Werner" s.email = "chw @nospam@ ch-werner.de" From 3a5fab369dd7366ab157994eef6ce2568d022d6f Mon Sep 17 00:00:00 2001 From: Gayatri Sharan Rajwade Date: Mon, 3 Jul 2023 14:14:19 +0530 Subject: [PATCH 18/23] Support for Ruby 3.x The taint mechanism was deprecated in Ruby 2.7 and removed in Ruby 3.0 https://bugs.ruby-lang.org/issues/16131 ruby/ruby#2476 From Ruby 2.7, `Object#{taint,untaint,trust,untrust}` and related functions in the C-API no longer have an effect (all objects are always considered untainted), and are now removed. Changes taken from https://github.com/vhermecz/ruby-odbc --- ext/odbc.c | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/ext/odbc.c b/ext/odbc.c index 50b0f64..eceb959 100644 --- a/ext/odbc.c +++ b/ext/odbc.c @@ -510,7 +510,7 @@ uc_tainted_str_new(SQLWCHAR *str, int len) if ((cp != NULL) && (str != NULL)) { ulen = mkutf(cp, str, len); } - v = rb_tainted_str_new((cp != NULL) ? cp : "", ulen); + v = rb_str_new((cp != NULL) ? cp : "", ulen); #ifdef USE_RB_ENC rb_enc_associate(v, rb_enc); #endif @@ -679,11 +679,11 @@ uc_free(SQLWCHAR *str) #endif static VALUE -utf8_tainted_str_new(SQLCHAR *str, int len) +utf8_str_new(SQLCHAR *str, int len) { VALUE v; - v = rb_tainted_str_new(str, len); + v = rb_str_new(str, len); #ifdef USE_RB_ENC if(rb_encoding_is_utf8) @@ -1016,7 +1016,7 @@ set_err(const char *msg, int warn) rb_enc_associate(v, rb_enc); #endif a = rb_ary_new2(1); - rb_ary_push(a, rb_obj_taint(v)); + rb_ary_push(a, v); CVAR_SET(Cobj, warn ? IDatatinfo : IDataterror, a); return STR2CSTR(v); } @@ -1094,7 +1094,7 @@ get_err_or_info(SQLHENV henv, SQLHDBC hdbc, SQLHSTMT hstmt, int isinfo) v0 = v; a = rb_ary_new(); } - rb_ary_push(a, rb_obj_taint(v)); + rb_ary_push(a, v); tracemsg(1, fprintf(stderr, " | %s\n", STR2CSTR(v));); } } @@ -1190,7 +1190,7 @@ get_installer_err() v0 = v; a = rb_ary_new(); } - rb_ary_push(a, rb_obj_taint(v)); + rb_ary_push(a, v); tracemsg(1, fprintf(stderr, " | %s\n", STR2CSTR(v));); } } @@ -1445,7 +1445,7 @@ dbc_raise(VALUE self, VALUE msg) buf[SQL_MAX_MESSAGE_LENGTH] = '\0'; v = rb_str_new2(buf); a = rb_ary_new2(1); - rb_ary_push(a, rb_obj_taint(v)); + rb_ary_push(a, v); CVAR_SET(Cobj, IDataterror, a); rb_raise(Cerror, "%s", buf); return Qnil; @@ -1535,8 +1535,8 @@ dbc_dsns(VALUE self) #else dsnLen = (dsnLen == 0) ? (SQLSMALLINT) strlen(dsn) : dsnLen; descrLen = (descrLen == 0) ? (SQLSMALLINT) strlen(descr) : descrLen; - rb_iv_set(odsn, "@name", rb_tainted_str_new(dsn, dsnLen)); - rb_iv_set(odsn, "@descr", rb_tainted_str_new(descr, descrLen)); + rb_iv_set(odsn, "@name", rb_str_new(dsn, dsnLen)); + rb_iv_set(odsn, "@descr", rb_str_new(descr, descrLen)); #endif rb_ary_push(aret, odsn); first = dsnLen = descrLen = 0; @@ -1600,13 +1600,13 @@ dbc_drivers(VALUE self) } #else driverLen = (driverLen == 0) ? (SQLSMALLINT) strlen(driver) : driverLen; - rb_iv_set(odrv, "@name", rb_tainted_str_new(driver, driverLen)); + rb_iv_set(odrv, "@name", rb_str_new(driver, driverLen)); for (attr = attrs; *attr; attr += strlen(attr) + 1) { char *p = strchr(attr, '='); if ((p != NULL) && (p != attr)) { - rb_hash_aset(h, rb_tainted_str_new(attr, p - attr), - rb_tainted_str_new2(p + 1)); + rb_hash_aset(h, rb_str_new(attr, p - attr), + rb_str_new2(p + 1)); count++; } } @@ -1915,7 +1915,7 @@ dbc_rfdsn(int argc, VALUE *argv, VALUE self) if (SQLReadFileDSN((LPCSTR) sfname, (LPCSTR) saname, (LPCSTR) skname, (LPSTR) valbuf, sizeof (valbuf), NULL)) { - return rb_tainted_str_new2((char *) valbuf); + return rb_str_new2((char *) valbuf); } } #else @@ -1925,7 +1925,7 @@ dbc_rfdsn(int argc, VALUE *argv, VALUE self) valbuf[0] = '\0'; if (SQLReadFileDSN(sfname, saname, skname, valbuf, sizeof (valbuf), NULL)) { - return rb_tainted_str_new2(valbuf); + return rb_str_new2(valbuf); } #endif #if defined(HAVE_SQLINSTALLERERROR) || (defined(UNICODE) && defined(HAVE_SQLINSTALLERERRORW)) @@ -3707,7 +3707,7 @@ make_column(SQLHSTMT hstmt, int i, int upc, int use_scn) len = 0; } mkutf(tmp, name, len); - v = rb_tainted_str_new2(upcase_if(tmp, 1)); + v = rb_str_new2(upcase_if(tmp, 1)); #ifdef USE_RB_ENC rb_enc_associate(v, rb_enc); #endif @@ -3719,7 +3719,7 @@ make_column(SQLHSTMT hstmt, int i, int upc, int use_scn) rb_iv_set(obj, "@name", uc_tainted_str_new2(name)); } #else - rb_iv_set(obj, "@name", rb_tainted_str_new2(upcase_if(name, upc))); + rb_iv_set(obj, "@name", rb_str_new2(upcase_if(name, upc))); #endif v = Qnil; name[0] = 0; @@ -3737,7 +3737,7 @@ make_column(SQLHSTMT hstmt, int i, int upc, int use_scn) #ifdef UNICODE v = uc_tainted_str_new2(name); #else - v = rb_tainted_str_new2(name); + v = rb_str_new2(name); #endif } rb_iv_set(obj, "@table", v); @@ -5816,7 +5816,7 @@ stmt_param_output_value(int argc, VALUE *argv, VALUE self) break; #endif case SQL_C_CHAR: - v = utf8_tainted_str_new(q->paraminfo[vnum].outbuf, + v = utf8_str_new(q->paraminfo[vnum].outbuf, q->paraminfo[vnum].rlen); break; } @@ -5892,7 +5892,7 @@ stmt_cursorname(int argc, VALUE *argv, VALUE self) return uc_tainted_str_new(cname, cnLen); #else cnLen = (cnLen == 0) ? (SQLSMALLINT) strlen((char *) cname) : cnLen; - return rb_tainted_str_new((char *) cname, cnLen); + return rb_str_new((char *) cname, cnLen); #endif } if (TYPE(cn) != T_STRING) { @@ -5978,7 +5978,7 @@ stmt_columns(int argc, VALUE *argv, VALUE self) sprintf(buf, "#%d", i); name = rb_str_dup(name); - name = rb_obj_taint(rb_str_cat2(name, buf)); + name = rb_str_cat2(name, buf); } rb_hash_aset(res, name, obj); } @@ -6227,7 +6227,7 @@ do_fetch(STMT *q, int mode) } for (i = 0; i < 4 * q->ncols; i++) { res = colbuf[i / q->ncols]; - cname = rb_tainted_str_new2(q->colnames[i]); + cname = rb_str_new2(q->colnames[i]); #ifdef USE_RB_ENC rb_enc_associate(cname, rb_enc); #endif @@ -6235,7 +6235,7 @@ do_fetch(STMT *q, int mode) if (rb_funcall(res, IDkeyp, 1, cname) == Qtrue) { char *p; - cname = rb_tainted_str_new2(q->colnames[i]); + cname = rb_str_new2(q->colnames[i]); #ifdef USE_RB_ENC rb_enc_associate(cname, rb_enc); #endif @@ -6476,7 +6476,7 @@ do_fetch(STMT *q, int mode) break; #endif default: - v = utf8_tainted_str_new(valp, curlen); + v = utf8_str_new(valp, curlen); break; } } @@ -6489,14 +6489,14 @@ do_fetch(STMT *q, int mode) valp = q->colnames[i + offc]; name = (q->colvals == NULL) ? Qnil : q->colvals[i + offc]; if (name == Qnil) { - name = rb_tainted_str_new2(valp); + name = rb_str_new2(valp); #ifdef USE_RB_ENC rb_enc_associate(name, rb_enc); #endif if (rb_funcall(res, IDkeyp, 1, name) == Qtrue) { char *p; - name = rb_tainted_str_new2(valp); + name = rb_str_new2(valp); #ifdef USE_RB_ENC rb_enc_associate(name, rb_enc); #endif From 050702c1dc9c748119ff01114e323adef3e09b94 Mon Sep 17 00:00:00 2001 From: Gayatri Sharan Rajwade Date: Mon, 10 Jul 2023 21:09:26 +0530 Subject: [PATCH 19/23] Update gem version to 0.102.cv to support Ruby 3.x --- ruby-odbc.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ruby-odbc.gemspec b/ruby-odbc.gemspec index ee60f1b..b2cfa58 100644 --- a/ruby-odbc.gemspec +++ b/ruby-odbc.gemspec @@ -1,7 +1,7 @@ require 'date' spec = Gem::Specification.new do |s| s.name = "ruby-odbc" - s.version = "0.101.cv" + s.version = "0.102.cv" s.date = Date.today.to_s s.author = "Christian Werner" s.email = "chw @nospam@ ch-werner.de" From 8ff517c6544b9edfae0b375554af1e1b92a77ab0 Mon Sep 17 00:00:00 2001 From: Gayatri Sharan Rajwade Date: Mon, 21 Aug 2023 18:19:12 +0530 Subject: [PATCH 20/23] Fix error 'tried to create Proc object without a block (ArgumentError)' proc.c (proc_new): promoted lambda/proc/Proc.new with no block in a method called with a block to a warning/error --- ext/odbc.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/ext/odbc.c b/ext/odbc.c index eceb959..bd624af 100644 --- a/ext/odbc.c +++ b/ext/odbc.c @@ -7714,8 +7714,9 @@ stmt_proc_call(int argc, VALUE *argv, VALUE self) static VALUE stmt_proc(int argc, VALUE *argv, VALUE self) { - VALUE sql, ptype, psize, pnum = Qnil, stmt, args[2]; + VALUE sql, ptype, psize, pnum = Qnil, stmt, args[2], proc_args[2], proc_arg[1]; int parnum = 0; + VALUE block = rb_block_proc(); rb_scan_args(argc, argv, "13", &sql, &ptype, &psize, &pnum); if (!rb_block_given_p()) { @@ -7723,7 +7724,8 @@ stmt_proc(int argc, VALUE *argv, VALUE self) } stmt = stmt_prep_int(1, &sql, self, 0); if (argc == 1) { - return rb_funcall(Cproc, IDnew, 1, stmt); + proc_arg[0] = stmt; + return rb_funcall_with_block(Cproc, IDnew, 1, proc_arg, block); } if ((argc < 4) || (pnum == Qnil)) { pnum = INT2NUM(parnum); @@ -7741,13 +7743,18 @@ stmt_proc(int argc, VALUE *argv, VALUE self) args[1] = INT2NUM(256); } stmt_param_output_size(2, args, stmt); - return rb_funcall(Cproc, IDnew, 2, stmt, pnum); + + proc_args[0] = stmt; + proc_args[1] = pnum; + + return rb_funcall_with_block(Cproc, IDnew, 2, proc_args, block); } static VALUE stmt_procwrap(int argc, VALUE *argv, VALUE self) { - VALUE arg0 = Qnil, arg1 = Qnil; + VALUE arg0 = Qnil, arg1 = Qnil, proc_args[2]; + VALUE block = rb_block_proc(); rb_scan_args(argc, argv, "02", &arg0, &arg1); if (rb_obj_is_kind_of(self, Cstmt) == Qtrue) { @@ -7759,7 +7766,10 @@ stmt_procwrap(int argc, VALUE *argv, VALUE self) } else if (rb_obj_is_kind_of(arg0, Cstmt) != Qtrue) { rb_raise(rb_eTypeError, "need ODBC::Statement as 1st argument"); } - return rb_funcall(Cproc, IDnew, 2, arg0, arg1); + + proc_args[0] = arg0; + proc_args[1] = arg1; + return rb_funcall_with_block(Cproc, IDnew, 2, proc_args, block); } /* From 6b828157f2ba19cbbef79e5f53d96b9ce0055339 Mon Sep 17 00:00:00 2001 From: Gayatri Sharan Rajwade Date: Mon, 21 Aug 2023 21:30:20 +0530 Subject: [PATCH 21/23] Update ChangeLog to support Ruby 3.0+ --- ChangeLog | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ChangeLog b/ChangeLog index 4869f18..6da97c9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,9 @@ ODBC binding for Ruby --------------------- +Tue Aug 21 2023 version 0.102.cv release + * Syntax changes to support Ruby 3.2 by using native "rb_str_new" instead of "rb_tainted_str_new" + Remove tained usage (thanks @vhermecz) + Fix error 'tried to create Proc object without a block (ArgumentError)' Tue Feb 12 2019 version 0.101.cv release * Minor syntax changes to support Ruby 2.4 From fd9193fd0ed097c91ccc32bb2b96126c73667de4 Mon Sep 17 00:00:00 2001 From: Gayatri Sharan Rajwade Date: Mon, 21 Aug 2023 21:41:44 +0530 Subject: [PATCH 22/23] Correct release 0.102.cv date --- ChangeLog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index 6da97c9..5f11cd0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,6 +1,6 @@ ODBC binding for Ruby --------------------- -Tue Aug 21 2023 version 0.102.cv release +Mon Aug 21 2023 version 0.102.cv release * Syntax changes to support Ruby 3.2 by using native "rb_str_new" instead of "rb_tainted_str_new" Remove tained usage (thanks @vhermecz) Fix error 'tried to create Proc object without a block (ArgumentError)' From 28a02b43c89efcf0be7093727f6b9d9c690645f4 Mon Sep 17 00:00:00 2001 From: pandeyam Date: Tue, 25 Jun 2024 18:42:24 +0530 Subject: [PATCH 23/23] Support for Ruby 3.3 - rb_funcall method when called with 0 as a third argument does not support NULL as the 4th argument. It should be replaced with 0 https://blog.peterzhu.ca/ruby-c-ext-part-3/ - Include header ruby/thread.h for method rb_thread_call_without_gvl to fix implicit declaration of method error - First argument of rb_thread_call_without_gvl method should be type void *(*func)(void *). In the code the argument type was VALUE(*func)(void *). Implement SQLExecDirect_wrapper_with_gvl to type caste the result of SQLExecDirect_wrapper into void * --- ChangeLog | 5 ++ ext/odbc.c | 123 ++++++++++++++++++++++++++-------------------- ruby-odbc.gemspec | 2 +- 3 files changed, 76 insertions(+), 54 deletions(-) diff --git a/ChangeLog b/ChangeLog index 5f11cd0..506a958 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,10 @@ ODBC binding for Ruby --------------------- +Thu Jun 27 2024 version 0.103.cv release + * Argument data type fix to support Ruby 3.3 by replacing NULL with 0 in method rb_funcall + * Include ruby/thread.h to implicitly declare rb_thread_call_without_gvl + * Cast first argument of rb_thread_call_without_gvl to void * data type + Mon Aug 21 2023 version 0.102.cv release * Syntax changes to support Ruby 3.2 by using native "rb_str_new" instead of "rb_tainted_str_new" Remove tained usage (thanks @vhermecz) diff --git a/ext/odbc.c b/ext/odbc.c index bd624af..45cc666 100644 --- a/ext/odbc.c +++ b/ext/odbc.c @@ -19,6 +19,7 @@ #include #include #include "ruby.h" +#include "ruby/thread.h" #ifdef HAVE_VERSION_H #include "version.h" #endif @@ -175,6 +176,14 @@ SQLExecute_wrapper(void *data) return SQLExecute(args->StatementHandle); } +void * +SQLExecute_wrapper_with_gvl(void *data) { + // Call the original function + VALUE result = SQLExecute_wrapper(data); + // Cast the result to void* and return + return (void *)result; +} + void SQLExecute_unblock(void *data) { @@ -189,6 +198,14 @@ SQLExecDirect_wrapper(void *data) return SQLExecDirect(args->StatementHandle, args->StatementText, args->TextLength); } +void * +SQLExecDirect_wrapper_with_gvl(void *data) { + // Call the original function + VALUE result = SQLExecDirect_wrapper(data); + // Cast the result to void* and return + return (void *)result; +} + void SQLExecDirect_unblock(void *data) { @@ -979,7 +996,7 @@ free_stmt(STMT *q) static void start_gc() { - rb_funcall(rb_mGC, IDstart, 0, NULL); + rb_funcall(rb_mGC, IDstart, 0, 0); } static void @@ -1246,7 +1263,7 @@ tracesql(SQLHENV henv, SQLHDBC hdbc, SQLHSTMT hstmt, SQLRETURN ret, (long) henv, (long) hdbc, (long) hstmt); trace_sql_ret(ret); } - + return ret; } #endif @@ -1669,7 +1686,7 @@ conf_dsn(int argc, VALUE *argv, VALUE self, int op) if (rb_obj_is_kind_of(attr, rb_cHash) == Qtrue) { VALUE a, x; - a = rb_funcall(attr, IDkeys, 0, NULL); + a = rb_funcall(attr, IDkeys, 0, 0); while ((x = rb_ary_shift(a)) != Qnil) { VALUE v = rb_hash_aref(attr, x); @@ -2131,7 +2148,7 @@ dbc_connect(int argc, VALUE *argv, VALUE self) #endif rb_raise(Cerror, "%s", msg); } - + { SQLRETURN nRet; @@ -2144,7 +2161,7 @@ dbc_connect(int argc, VALUE *argv, VALUE self) nRet = SQLSetConnectAttr(dbc, SQL_ATTR_LOGIN_TIMEOUT, (SQLPOINTER)nTimeout, SQL_IS_INTEGER); if (!SQL_SUCCEEDED(nRet)) fprintf(stderr, "Failed to set timeout to %d: error %d\n", nTimeout, nRet); } - + if (!succeeded(SQL_NULL_HENV, dbc, SQL_NULL_HSTMT, SQLConnect(dbc, (SQLTCHAR *) sdsn, SQL_NTS, (SQLTCHAR *) suser, @@ -2188,7 +2205,7 @@ dbc_drvconnect(VALUE self, VALUE drv) VALUE d, a, x; d = rb_str_new2(""); - a = rb_funcall(rb_iv_get(drv, "@attrs"), IDkeys, 0, NULL); + a = rb_funcall(rb_iv_get(drv, "@attrs"), IDkeys, 0, 0); while ((x = rb_ary_shift(a)) != Qnil) { VALUE v = rb_hash_aref(rb_iv_get(drv, "@attrs"), x); @@ -4798,16 +4815,16 @@ date_init(int argc, VALUE *argv, VALUE self) if (argc > 1) { rb_raise(rb_eArgError, "wrong # arguments"); } - d = rb_funcall(y, IDday, 0, NULL); - m = rb_funcall(y, IDmonth, 0, NULL); - y = rb_funcall(y, IDyear, 0, NULL); + d = rb_funcall(y, IDday, 0, 0); + m = rb_funcall(y, IDmonth, 0, 0); + y = rb_funcall(y, IDyear, 0, 0); } else if (rb_obj_is_kind_of(y, rb_cDate) == Qtrue) { if (argc > 1) { rb_raise(rb_eArgError, "wrong # arguments"); } - d = rb_funcall(y, IDmday, 0, NULL); - m = rb_funcall(y, IDmonth, 0, NULL); - y = rb_funcall(y, IDyear, 0, NULL); + d = rb_funcall(y, IDmday, 0, 0); + m = rb_funcall(y, IDmonth, 0, 0); + y = rb_funcall(y, IDyear, 0, 0); } else if ((argc == 1) && (rb_obj_is_kind_of(y, rb_cString) == Qtrue)) { if (date_load1(self, y, 0) != Qnil) { return self; @@ -5033,9 +5050,9 @@ time_init(int argc, VALUE *argv, VALUE self) if (argc > 1) { rb_raise(rb_eArgError, "wrong # arguments"); } - s = rb_funcall(h, IDsec, 0, NULL); - m = rb_funcall(h, IDmin, 0, NULL); - h = rb_funcall(h, IDhour, 0, NULL); + s = rb_funcall(h, IDsec, 0, 0); + m = rb_funcall(h, IDmin, 0, 0); + h = rb_funcall(h, IDhour, 0, 0); } else if ((argc == 1) && (rb_obj_is_kind_of(h, rb_cString) == Qtrue)) { if (time_load1(self, h, 0) != Qnil) { return self; @@ -5273,13 +5290,13 @@ timestamp_init(int argc, VALUE *argv, VALUE self) if (argc > 1) { rb_raise(rb_eArgError, "wrong # arguments"); } - f = rb_funcall(y, IDusec, 0, NULL); - ss = rb_funcall(y, IDsec, 0, NULL); - mm = rb_funcall(y, IDmin, 0, NULL); - hh = rb_funcall(y, IDhour, 0, NULL); - d = rb_funcall(y, IDday, 0, NULL); - m = rb_funcall(y, IDmonth, 0, NULL); - y = rb_funcall(y, IDyear, 0, NULL); + f = rb_funcall(y, IDusec, 0, 0); + ss = rb_funcall(y, IDsec, 0, 0); + mm = rb_funcall(y, IDmin, 0, 0); + hh = rb_funcall(y, IDhour, 0, 0); + d = rb_funcall(y, IDday, 0, 0); + m = rb_funcall(y, IDmonth, 0, 0); + y = rb_funcall(y, IDyear, 0, 0); f = INT2NUM(NUM2INT(f) * 1000); } else if (rb_obj_is_kind_of(y, rb_cDate) == Qtrue) { if (argc > 1) { @@ -5289,9 +5306,9 @@ timestamp_init(int argc, VALUE *argv, VALUE self) ss = INT2FIX(0); mm = INT2FIX(0); hh = INT2FIX(0); - d = rb_funcall(y, IDmday, 0, NULL); - m = rb_funcall(y, IDmonth, 0, NULL); - y = rb_funcall(y, IDyear, 0, NULL); + d = rb_funcall(y, IDmday, 0, 0); + m = rb_funcall(y, IDmonth, 0, 0); + y = rb_funcall(y, IDyear, 0, 0); } else if ((argc == 1) && (rb_obj_is_kind_of(y, rb_cString) == Qtrue)) { if (timestamp_load1(self, y, 0) != Qnil) { return self; @@ -5766,13 +5783,13 @@ stmt_param_output_value(int argc, VALUE *argv, VALUE self) time = (TIME_STRUCT *) q->paraminfo[vnum].outbuf; frac = rb_float_new(0.0); - now = rb_funcall(rb_cTime, IDnow, 0, NULL); + now = rb_funcall(rb_cTime, IDnow, 0, 0); v = rb_funcall(rb_cTime, (q->dbcp->gmtime == Qtrue) ? IDutc : IDlocal, 7, - rb_funcall(now, IDyear, 0, NULL), - rb_funcall(now, IDmonth, 0, NULL), - rb_funcall(now, IDday, 0, NULL), + rb_funcall(now, IDyear, 0, 0), + rb_funcall(now, IDmonth, 0, 0), + rb_funcall(now, IDday, 0, 0), INT2NUM(time->hour), INT2NUM(time->minute), INT2NUM(time->second), @@ -6423,14 +6440,14 @@ do_fetch(STMT *q, int mode) time = (TIME_STRUCT *) valp; frac = rb_float_new(0.0); - now = rb_funcall(rb_cTime, IDnow, 0, NULL); + now = rb_funcall(rb_cTime, IDnow, 0, 0); v = rb_funcall(rb_cTime, (q->dbcp->gmtime == Qtrue) ? IDutc : IDlocal, 7, - rb_funcall(now, IDyear, 0, NULL), - rb_funcall(now, IDmonth, 0, NULL), - rb_funcall(now, IDday, 0, NULL), + rb_funcall(now, IDyear, 0, 0), + rb_funcall(now, IDmonth, 0, 0), + rb_funcall(now, IDday, 0, 0), INT2NUM(time->hour), INT2NUM(time->minute), INT2NUM(time->second), @@ -7097,7 +7114,7 @@ stmt_prep_int(int argc, VALUE *argv, VALUE self, int mode) args.StatementHandle = hstmt; args.StatementText = ssql; args.TextLength = SQL_NTS; - ret = (SQLRETURN)rb_thread_call_without_gvl(SQLExecDirect_wrapper, &args, SQLExecDirect_unblock, &args); + ret = (SQLRETURN)rb_thread_call_without_gvl(SQLExecDirect_wrapper_with_gvl, &args, SQLExecDirect_unblock, &args); if (!succeeded_nodata(SQL_NULL_HENV, SQL_NULL_HDBC, hstmt, ret, &msg, "SQLExecDirect('%s')", csql)) { goto sqlerr; @@ -7289,9 +7306,9 @@ bind_one_param(int pnum, VALUE arg, STMT *q, char **msgp, int *outpp) ctype = SQL_C_TIME; time = (TIME_STRUCT *) valp; memset(time, 0, sizeof (TIME_STRUCT)); - time->hour = rb_funcall(arg, IDhour, 0, NULL); - time->minute = rb_funcall(arg, IDmin, 0, NULL); - time->second = rb_funcall(arg, IDsec, 0, NULL); + time->hour = rb_funcall(arg, IDhour, 0, 0); + time->minute = rb_funcall(arg, IDmin, 0, 0); + time->second = rb_funcall(arg, IDsec, 0, 0); rlen = 1; vlen = sizeof (TIME_STRUCT); } else if (q->paraminfo[pnum].type == SQL_DATE) { @@ -7300,9 +7317,9 @@ bind_one_param(int pnum, VALUE arg, STMT *q, char **msgp, int *outpp) ctype = SQL_C_DATE; date = (DATE_STRUCT *) valp; memset(date, 0, sizeof (DATE_STRUCT)); - date->year = rb_funcall(arg, IDyear, 0, NULL); - date->month = rb_funcall(arg, IDmonth, 0, NULL); - date->day = rb_funcall(arg, IDday, 0, NULL); + date->year = rb_funcall(arg, IDyear, 0, 0); + date->month = rb_funcall(arg, IDmonth, 0, 0); + date->day = rb_funcall(arg, IDday, 0, 0); rlen = 1; vlen = sizeof (TIMESTAMP_STRUCT); } else { @@ -7311,16 +7328,16 @@ bind_one_param(int pnum, VALUE arg, STMT *q, char **msgp, int *outpp) ctype = SQL_C_TIMESTAMP; ts = (TIMESTAMP_STRUCT *) valp; memset(ts, 0, sizeof (TIMESTAMP_STRUCT)); - ts->year = rb_funcall(arg, IDyear, 0, NULL); - ts->month = rb_funcall(arg, IDmonth, 0, NULL); - ts->day = rb_funcall(arg, IDday, 0, NULL); - ts->hour = rb_funcall(arg, IDhour, 0, NULL); - ts->minute = rb_funcall(arg, IDmin, 0, NULL); - ts->second = rb_funcall(arg, IDsec, 0, NULL); + ts->year = rb_funcall(arg, IDyear, 0, 0); + ts->month = rb_funcall(arg, IDmonth, 0, 0); + ts->day = rb_funcall(arg, IDday, 0, 0); + ts->hour = rb_funcall(arg, IDhour, 0, 0); + ts->minute = rb_funcall(arg, IDmin, 0, 0); + ts->second = rb_funcall(arg, IDsec, 0, 0); #ifdef TIME_USE_USEC - ts->fraction = rb_funcall(arg, IDusec, 0, NULL) * 1000; + ts->fraction = rb_funcall(arg, IDusec, 0, 0) * 1000; #else - ts->fraction = rb_funcall(arg, IDnsec, 0, NULL); + ts->fraction = rb_funcall(arg, IDnsec, 0, 0); #endif rlen = 1; vlen = sizeof (TIMESTAMP_STRUCT); @@ -7333,9 +7350,9 @@ bind_one_param(int pnum, VALUE arg, STMT *q, char **msgp, int *outpp) ctype = SQL_C_DATE; date = (DATE_STRUCT *) valp; memset(date, 0, sizeof (DATE_STRUCT)); - date->year = rb_funcall(arg, IDyear, 0, NULL); - date->month = rb_funcall(arg, IDmonth, 0, NULL); - date->day = rb_funcall(arg, IDmday, 0, NULL); + date->year = rb_funcall(arg, IDyear, 0, 0); + date->month = rb_funcall(arg, IDmonth, 0, 0); + date->day = rb_funcall(arg, IDmday, 0, 0); rlen = 1; vlen = sizeof (DATE_STRUCT); break; @@ -7546,7 +7563,7 @@ stmt_exec_int(int argc, VALUE *argv, VALUE self, int mode) } args.StatementHandle = q->hstmt; - ret = (SQLRETURN)rb_thread_call_without_gvl(SQLExecute_wrapper, &args, SQLExecute_unblock, &args); + ret = (SQLRETURN)rb_thread_call_without_gvl(SQLExecute_wrapper_with_gvl, &args, SQLExecute_unblock, &args); if (!succeeded_nodata(SQL_NULL_HENV, SQL_NULL_HDBC, q->hstmt, ret, &msg, "SQLExecute")) { error: @@ -7862,7 +7879,7 @@ mod_2time(int argc, VALUE *argv, VALUE self) rb_raise(rb_eTypeError, "expecting ODBC::Date"); } } else { - VALUE now = rb_funcall(rb_cTime, IDnow, 0, NULL); + VALUE now = rb_funcall(rb_cTime, IDnow, 0, 0); y = rb_funcall(rb_cTime, IDyear, 1, now); m = rb_funcall(rb_cTime, IDmonth, 1, now); diff --git a/ruby-odbc.gemspec b/ruby-odbc.gemspec index b2cfa58..407a982 100644 --- a/ruby-odbc.gemspec +++ b/ruby-odbc.gemspec @@ -1,7 +1,7 @@ require 'date' spec = Gem::Specification.new do |s| s.name = "ruby-odbc" - s.version = "0.102.cv" + s.version = "0.103.cv" s.date = Date.today.to_s s.author = "Christian Werner" s.email = "chw @nospam@ ch-werner.de"