From nobody at rubyforge.org Tue Feb 2 08:14:32 2010 From: nobody at rubyforge.org (nobody at rubyforge.org) Date: Tue, 2 Feb 2010 08:14:32 -0500 (EST) Subject: [ruby-oci8-commit] [375] trunk/ruby-oci8: * ext/oci8/ocinumber.c: fix to support NNUMBERS with scale larger Message-ID: <20100202131432.65C9318582E0@rubyforge.org> Revision: 375 Author: kubo Date: 2010-02-02 08:14:30 -0500 (Tue, 02 Feb 2010) Log Message: ----------- * ext/oci8/ocinumber.c: fix to support NNUMBERS with scale larger than 38 by using scientific number notation to convert OraNumber to BigDecimal. OraNumber#to_i is also fixed. (reported by Raimonds Simanovskis) Modified Paths: -------------- trunk/ruby-oci8/ChangeLog trunk/ruby-oci8/ext/oci8/ocinumber.c Modified: trunk/ruby-oci8/ChangeLog =================================================================== --- trunk/ruby-oci8/ChangeLog 2010-01-24 13:43:11 UTC (rev 374) +++ trunk/ruby-oci8/ChangeLog 2010-02-02 13:14:30 UTC (rev 375) @@ -1,3 +1,9 @@ +2010-02-02 KUBO Takehiro + * ext/oci8/ocinumber.c: fix to support NNUMBERS with scale larger + than 38 by using scientific number notation to convert OraNumber + to BigDecimal. OraNumber#to_i is also fixed. + (reported by Raimonds Simanovskis) + 2010-01-24 KUBO Takehiro * ext/oci8/error.c: Use OCIErrorGet() to retrieve the error message when the OCI return code is OCI_NO_DATA. Modified: trunk/ruby-oci8/ext/oci8/ocinumber.c =================================================================== --- trunk/ruby-oci8/ext/oci8/ocinumber.c 2010-01-24 13:43:11 UTC (rev 374) +++ trunk/ruby-oci8/ext/oci8/ocinumber.c 2010-02-02 13:14:30 UTC (rev 375) @@ -109,6 +109,7 @@ static VALUE onum_to_f(VALUE self); static VALUE onum_to_r(VALUE self); static VALUE onum_to_d(VALUE self); +static VALUE onum_to_d_real(OCINumber *num, OCIError *errhp); static VALUE onum_s_alloc(VALUE klass) { @@ -136,13 +137,23 @@ signed long sl; char buf[512]; ub4 buf_size = sizeof(buf); + sword rv; if (OCINumberToInt(errhp, s, sizeof(sl), OCI_NUMBER_SIGNED, &sl) == OCI_SUCCESS) { return LONG2NUM(sl); } - oci_lc(OCINumberToText(errhp, s, NUMBER_FORMAT2, NUMBER_FORMAT2_LEN, - NULL, 0, &buf_size, TO_ORATEXT(buf))); - return rb_cstr2inum(buf, 10); + /* convert to Integer via String */ + rv = OCINumberToText(errhp, s, NUMBER_FORMAT2, NUMBER_FORMAT2_LEN, + NULL, 0, &buf_size, TO_ORATEXT(buf)); + if (rv == OCI_SUCCESS) { + return rb_cstr2inum(buf, 10); + } + if (rv == OCI_ERROR && oci8_get_error_code(errhp) == 22065) { + /* convert to Integer via BigDecimal as a last resort */ + VALUE d = onum_to_d_real(s, errhp); + return rb_funcall(d, rb_intern("to_i"), 0); + } + oci8_raise(errhp, rv, NULL); } VALUE oci8_make_float(OCINumber *s, OCIError *errhp) @@ -1195,11 +1206,23 @@ */ static VALUE onum_to_d(VALUE self) { + return onum_to_d_real(_NUMBER(self), oci8_errhp); +} + +/* Converts to BigDecimal via number in scientific notation */ +static VALUE onum_to_d_real(OCINumber *num, OCIError *errhp) +{ + char buf[64]; + ub4 buf_size = sizeof(buf); + const const char *fmt = "FM9.99999999999999999999999999999999999999EEEE"; + if (!cBigDecimal) { rb_require("bigdecimal"); cBigDecimal = rb_const_get(rb_cObject, id_BigDecimal); } - return rb_funcall(rb_cObject, id_BigDecimal, 1, onum_to_s(self)); + oci_lc(OCINumberToText(errhp, num, (const oratext *)fmt, strlen(fmt), + NULL, 0, &buf_size, TO_ORATEXT(buf))); + return rb_funcall(rb_cObject, id_BigDecimal, 1, rb_usascii_str_new(buf, buf_size)); } /* From nobody at rubyforge.org Sun Feb 7 05:02:22 2010 From: nobody at rubyforge.org (nobody at rubyforge.org) Date: Sun, 7 Feb 2010 05:02:22 -0500 (EST) Subject: [ruby-oci8-commit] [376] trunk/ruby-oci8: * ext/oci8/error.c, ext/oci8/oci8.h: add oci8_raise_by_msgno() Message-ID: <20100207100222.D23591588062@rubyforge.org> Revision: 376 Author: kubo Date: 2010-02-07 05:02:22 -0500 (Sun, 07 Feb 2010) Log Message: ----------- * ext/oci8/error.c, ext/oci8/oci8.h: add oci8_raise_by_msgno() to retrieve a Oracle error message which depends on NLS_LANGUAGE. * ext/oci8/oranumber_util.c, ext/oci8/oranumber_util.h, ext/oci8/extconf.rb: add handwritten conversion functions from OCINumber internal representation to string and vice versa. * ext/oci8/ocinumber.c: 1. use handwritten conversion functions instead of OCI functions to convert OraNumber to string and vice varse. 2. add OraNumber#dump. * test/test_oranumber.rb: add test cases to check conversion from OraNumber to string and vice varse. Modified Paths: -------------- trunk/ruby-oci8/ChangeLog trunk/ruby-oci8/ext/oci8/error.c trunk/ruby-oci8/ext/oci8/extconf.rb trunk/ruby-oci8/ext/oci8/oci8.h trunk/ruby-oci8/ext/oci8/ocinumber.c trunk/ruby-oci8/test/test_oranumber.rb Added Paths: ----------- trunk/ruby-oci8/ext/oci8/oranumber_util.c trunk/ruby-oci8/ext/oci8/oranumber_util.h Modified: trunk/ruby-oci8/ChangeLog =================================================================== --- trunk/ruby-oci8/ChangeLog 2010-02-02 13:14:30 UTC (rev 375) +++ trunk/ruby-oci8/ChangeLog 2010-02-07 10:02:22 UTC (rev 376) @@ -1,3 +1,15 @@ +2010-02-07 KUBO Takehiro + * ext/oci8/error.c, ext/oci8/oci8.h: add oci8_raise_by_msgno() + to retrieve a Oracle error message which depends on NLS_LANGUAGE. + * ext/oci8/oranumber_util.c, ext/oci8/oranumber_util.h, + ext/oci8/extconf.rb: add handwritten conversion functions from + OCINumber internal representation to string and vice versa. + * ext/oci8/ocinumber.c: 1. use handwritten conversion functions + instead of OCI functions to convert OraNumber to string + and vice varse. 2. add OraNumber#dump. + * test/test_oranumber.rb: add test cases to check conversion from + OraNumber to string and vice varse. + 2010-02-02 KUBO Takehiro * ext/oci8/ocinumber.c: fix to support NNUMBERS with scale larger than 38 by using scientific number notation to convert OraNumber Modified: trunk/ruby-oci8/ext/oci8/error.c =================================================================== --- trunk/ruby-oci8/ext/oci8/error.c 2010-02-02 13:14:30 UTC (rev 375) +++ trunk/ruby-oci8/ext/oci8/error.c 2010-02-07 10:02:22 UTC (rev 376) @@ -32,6 +32,8 @@ static char *errbuf; static ub4 errbufsiz; +static OCIMsg *msghp; + NORETURN(static void oci8_raise2(dvoid *errhp, sword status, ub4 type, OCIStmt *stmthp, const char *file, int line)); NORETURN(static void set_backtrace_and_raise(VALUE exc, const char *file, int line)); @@ -347,3 +349,30 @@ rb_ivar_set(exc, oci8_id_message, rb_ary_new3(1, msg)); set_backtrace_and_raise(exc, file, line); } + +static VALUE get_error_msg_by_msgno(ub4 msgno) +{ + char buf[256]; + int sz; + + if (msghp == NULL) { + OCIMessageOpen(oci8_envhp, oci8_errhp, &msghp, TO_ORATEXT("rdbms"), TO_ORATEXT("ora"), OCI_DURATION_PROCESS); + } + + sz = snprintf(buf, sizeof(buf), "ORA-%05u: ", msgno); + if (msghp == NULL || OCIMessageGet(msghp, msgno, TO_ORATEXT(buf + sz), sizeof(buf) - sz) == NULL) { + strcpy(buf + sz, "could not get the error message"); + } + return rb_external_str_new_with_enc(buf, strlen(buf), oci8_encoding); + +} + +void oci8_do_raise_by_msgno(ub4 msgno, const char *file, int line) +{ + VALUE msg = get_error_msg_by_msgno(msgno); + VALUE exc = rb_funcall(eOCIError, oci8_id_new, 1, msg); + + rb_ivar_set(exc, oci8_id_code, rb_ary_new3(1, INT2FIX(-1))); + rb_ivar_set(exc, oci8_id_message, rb_ary_new3(1, msg)); + set_backtrace_and_raise(exc, file, line); +} Modified: trunk/ruby-oci8/ext/oci8/extconf.rb =================================================================== --- trunk/ruby-oci8/ext/oci8/extconf.rb 2010-02-02 13:14:30 UTC (rev 375) +++ trunk/ruby-oci8/ext/oci8/extconf.rb 2010-02-07 10:02:22 UTC (rev 376) @@ -86,7 +86,7 @@ "stmt.o", "bind.o", "metadata.o", "attr.o", "lob.o", "oradate.o", "ocinumber.o", "ocidatetime.o", "object.o", "apiwrap.o", - "encoding.o", "xmldb.o"] + "encoding.o", "xmldb.o", "oranumber_util.o"] if RUBY_PLATFORM =~ /mswin32|cygwin|mingw32|bccwin32/ $defs << "-DUSE_WIN32_C" Modified: trunk/ruby-oci8/ext/oci8/oci8.h =================================================================== --- trunk/ruby-oci8/ext/oci8/oci8.h 2010-02-02 13:14:30 UTC (rev 375) +++ trunk/ruby-oci8/ext/oci8/oci8.h 2010-02-07 10:02:22 UTC (rev 376) @@ -336,6 +336,7 @@ #define oci8_raise(err, status, stmt) oci8_do_raise(err, status, stmt, __FILE__, __LINE__) #define oci8_env_raise(err, status) oci8_do_env_raise(err, status, __FILE__, __LINE__) #define oci8_raise_init_error() oci8_do_raise_init_error(__FILE__, __LINE__) +#define oci8_raise_by_msgno(msgno) oci8_do_raise_by_msgno(msgno, __FILE__, __LINE__) /* raise on error */ #define oci_lc(rv) do { \ @@ -412,6 +413,7 @@ NORETURN(void oci8_do_env_raise(OCIEnv *, sword status, const char *file, int line)); NORETURN(void oci8_do_raise_init_error(const char *file, int line)); sb4 oci8_get_error_code(OCIError *errhp); +NORETURN(void oci8_do_raise_by_msgno(ub4 msgno, const char *file, int line)); /* ocihandle.c */ void Init_oci8_handle(void); Modified: trunk/ruby-oci8/ext/oci8/ocinumber.c =================================================================== --- trunk/ruby-oci8/ext/oci8/ocinumber.c 2010-02-02 13:14:30 UTC (rev 375) +++ trunk/ruby-oci8/ext/oci8/ocinumber.c 2010-02-07 10:02:22 UTC (rev 376) @@ -8,6 +8,7 @@ #include "oci8.h" #include #include +#include "oranumber_util.h" #ifndef RUBY_VM /* ruby 1.8 */ @@ -36,26 +37,6 @@ static OCINumber const_PI2; /* +PI/2 */ static OCINumber const_mPI2; /* -PI/2 */ -#define SHRESHOLD_FMT_STR "00000000000000000000000000" -#define SHRESHOLD_FMT (OraText*)SHRESHOLD_FMT_STR -#define SHRESHOLD_FMT_LEN (sizeof(SHRESHOLD_FMT_STR) - 1) -#define SHRESHOLD_VAL_STR "10000000000000000000000000" -#define SHRESHOLD_VAL (OraText*)SHRESHOLD_VAL_STR -#define SHRESHOLD_VAL_LEN (sizeof(SHRESHOLD_VAL_STR) - 1) -static OCINumber const_shreshold; - -/* TODO: scale: -84 - 127 */ -#define NUMBER_FORMAT1_STR "FM9999999999999999999999990.9999999999999999999999999999999999999" -#define NUMBER_FORMAT1 (OraText*)NUMBER_FORMAT1_STR -#define NUMBER_FORMAT1_LEN (sizeof(NUMBER_FORMAT1_STR) - 1) -#define NUMBER_FORMAT2_STR "FM99999999999999999999999999999999999990.999999999999999999999999" -#define NUMBER_FORMAT2 (OraText*)NUMBER_FORMAT2_STR -#define NUMBER_FORMAT2_LEN (sizeof(NUMBER_FORMAT2_STR) - 1) -#define NUMBER_FORMAT2_DECIMAL (sizeof("999999999999999999999999") - 1) -#define NUMBER_FORMAT_INT_STR "FM99999999999999999999999999999999999990" -#define NUMBER_FORMAT_INT (OraText*)NUMBER_FORMAT_INT_STR -#define NUMBER_FORMAT_INT_LEN (sizeof(NUMBER_FORMAT_INT_STR) - 1) - #define _NUMBER(val) ((OCINumber *)DATA_PTR(val)) /* dangerous macro */ #define RBOCI8_T_ORANUMBER (T_MASK + 1) @@ -136,24 +117,18 @@ { signed long sl; char buf[512]; - ub4 buf_size = sizeof(buf); sword rv; if (OCINumberToInt(errhp, s, sizeof(sl), OCI_NUMBER_SIGNED, &sl) == OCI_SUCCESS) { return LONG2NUM(sl); } /* convert to Integer via String */ - rv = OCINumberToText(errhp, s, NUMBER_FORMAT2, NUMBER_FORMAT2_LEN, - NULL, 0, &buf_size, TO_ORATEXT(buf)); - if (rv == OCI_SUCCESS) { + rv = oranumber_to_str(s, buf); + if (rv > 0) { return rb_cstr2inum(buf, 10); } - if (rv == OCI_ERROR && oci8_get_error_code(errhp) == 22065) { - /* convert to Integer via BigDecimal as a last resort */ - VALUE d = onum_to_d_real(s, errhp); - return rb_funcall(d, rb_intern("to_i"), 0); - } - oci8_raise(errhp, rv, NULL); + oranumber_dump(s, buf); + rb_raise(eOCIException, "Could not convert OraNumber(%s) to string", buf); } VALUE oci8_make_float(OCINumber *s, OCIError *errhp) @@ -175,29 +150,16 @@ StringValue(str); /* set from string. */ if (NIL_P(fmt)) { - int i, cnt = 0; - for (i = RSTRING_LEN(str) - 1; i >= 0; i--) { - if (RSTRING_PTR(str)[i] != ' ') - cnt++; - if (RSTRING_PTR(str)[i] == '.') { - i = RSTRING_LEN(str) - i; - break; - } - } - if (i == -1) - cnt = 0; - if (cnt <= NUMBER_FORMAT2_DECIMAL) { - fmt_ptr = NUMBER_FORMAT2; - fmt_len = NUMBER_FORMAT2_LEN; + int rv = oranumber_from_str(result, RSTRING_PTR(str), RSTRING_LEN(str)); + if (rv == ORANUMBER_SUCCESS) { + return; /* success */ } else { - fmt_ptr = NUMBER_FORMAT1; - fmt_len = NUMBER_FORMAT1_LEN; + oci8_raise_by_msgno(rv); } - } else { - StringValue(fmt); - fmt_ptr = RSTRING_ORATEXT(fmt); - fmt_len = RSTRING_LEN(fmt); } + StringValue(fmt); + fmt_ptr = RSTRING_ORATEXT(fmt); + fmt_len = RSTRING_LEN(fmt); if (NIL_P(nls_params)) { nls_params_ptr = NULL; nls_params_len = 0; @@ -1064,30 +1026,14 @@ rb_scan_args(argc, argv, "02", &fmt /* nil */, &nls_params /* nil */); if (NIL_P(fmt)) { - OCINumber absval; - sword sign; - boolean is_int; - - oci_lc(OCINumberIsInt(errhp, _NUMBER(self), &is_int)); - if (is_int) { - fmt_ptr = NUMBER_FORMAT_INT; - fmt_len = NUMBER_FORMAT_INT_LEN; - } else { - oci_lc(OCINumberAbs(errhp, _NUMBER(self), &absval)); - oci_lc(OCINumberCmp(errhp, &absval, &const_shreshold, &sign)); - if (sign >= 0) { - fmt_ptr = NUMBER_FORMAT2; - fmt_len = NUMBER_FORMAT2_LEN; - } else { - fmt_ptr = NUMBER_FORMAT1; - fmt_len = NUMBER_FORMAT1_LEN; - } + rv = oranumber_to_str(_NUMBER(self), buf); + if (rv > 0) { + return rb_usascii_str_new(buf, rv); } - } else { - StringValue(fmt); - fmt_ptr = RSTRING_ORATEXT(fmt); - fmt_len = RSTRING_LEN(fmt); } + StringValue(fmt); + fmt_ptr = RSTRING_ORATEXT(fmt); + fmt_len = RSTRING_LEN(fmt); if (NIL_P(nls_params)) { nls_params_ptr = NULL; nls_params_len = 0; @@ -1285,6 +1231,24 @@ return oci8_make_ocinumber(&result, errhp); } +/* + * call-seq: + * onum.dump -> string + * + * Returns internal representation whose format is same with + * the return value of Oracle SQL function DUMP(). + * + * OraNumber.new(100).dump #=> "Typ=2 Len=2: 194,2" + * OraNumber.new(123).dump #=> "Typ=2 Len=3: 194,2,24" + * OraNumber.new(0.1).dump #=> "Typ=2 Len=2: 192,11" + */ +static VALUE onum_dump(VALUE self) +{ + char buf[ORANUMBER_DUMP_BUF_SIZ]; + int rv = oranumber_dump(_NUMBER(self), buf); + return rb_usascii_str_new(buf, rv); +} + static VALUE onum_hash(VALUE self) { char *c = DATA_PTR(self); @@ -1320,7 +1284,7 @@ * * Dump onum for marshaling. */ -static VALUE onum_dump(int argc, VALUE *argv, VALUE self) +static VALUE onum__dump(int argc, VALUE *argv, VALUE self) { char *c = DATA_PTR(self); int size = c[0] + 1; @@ -1466,9 +1430,6 @@ OCINumberDiv(errhp, &num1 /* PI */, &num2 /* 2 */, &const_PI2); /* set const_mPI2 */ OCINumberNeg(errhp, &const_PI2 /* PI/2 */, &const_mPI2); - /* set const_shreshold */ - OCINumberFromText(errhp, SHRESHOLD_VAL, SHRESHOLD_VAL_LEN, SHRESHOLD_FMT, SHRESHOLD_FMT_LEN, - NULL, 0, &const_shreshold); /* PI */ OCINumberSetPi(errhp, &num1); @@ -1539,12 +1500,13 @@ if (have_OCINumberShift) { rb_define_method(cOCINumber, "shift", onum_shift, 1); } + rb_define_method(cOCINumber, "dump", onum_dump, 0); rb_define_method_nodoc(cOCINumber, "hash", onum_hash, 0); rb_define_method_nodoc(cOCINumber, "inspect", onum_inspect, 0); /* methods for marshaling */ - rb_define_method(cOCINumber, "_dump", onum_dump, -1); + rb_define_method(cOCINumber, "_dump", onum__dump, -1); rb_define_singleton_method(cOCINumber, "_load", onum_s_load, 1); oci8_define_bind_class("OraNumber", &bind_ocinumber_class); Added: trunk/ruby-oci8/ext/oci8/oranumber_util.c =================================================================== --- trunk/ruby-oci8/ext/oci8/oranumber_util.c (rev 0) +++ trunk/ruby-oci8/ext/oci8/oranumber_util.c 2010-02-07 10:02:22 UTC (rev 376) @@ -0,0 +1,301 @@ +/* -*- c-file-style: "ruby"; indent-tabs-mode: nil -*- */ +#include +#include +#include "oranumber_util.h" + +int oranumber_to_str(const OCINumber *on, char *buf) +{ + signed char exponent; + signed char mantissa[21]; /* terminated by a negative number */ + int datalen = on->OCINumberPart[0]; + int len = 0; + int idx; + int n; + + if (datalen == 0) { + /* too short */ + return -1; + } + if (datalen == 1) { + if (on->OCINumberPart[1] == 0x80) { + /* zero */ + buf[0] = '0'; + buf[1] = '\0'; + return 1; + } + /* unexpected format */ + return -1; + } + if (datalen > 21) { + /* too long */ + return -1; + } + /* normalize exponent and mantissa */ + if (on->OCINumberPart[1] >= 128) { + /* positive number */ + exponent = on->OCINumberPart[1] - 193; + for (idx = 0; idx < on->OCINumberPart[0] - 1; idx++) { + mantissa[idx] = on->OCINumberPart[idx + 2] - 1; + } + mantissa[idx] = -1; + } else { + /* negative number */ + exponent = 62 - on->OCINumberPart[1]; + for (idx = 0; idx < on->OCINumberPart[0] - 1; idx++) { + mantissa[idx] = 101 - on->OCINumberPart[idx + 2]; + } + mantissa[idx] = -1; + buf[len++] = '-'; + } + /* convert exponent and mantissa to human readable number */ + idx = 0; + if (exponent-- >= 0) { + /* integer part */ + n = mantissa[idx++]; + if (n / 10 != 0) { + buf[len++] = n / 10 + '0'; + } + buf[len++] = n % 10 + '0'; + while (exponent-- >= 0) { + n = mantissa[idx++]; + if (n < 0) { + do { + buf[len++] = '0'; + buf[len++] = '0'; + } while (exponent-- >= 0); + buf[len] = '\0'; + return len; + } + buf[len++] = n / 10 + '0'; + buf[len++] = n % 10 + '0'; + } + if (mantissa[idx] < 0) { + buf[len] = '\0'; + return len; + } + } else { + buf[len++] = '0'; + } + buf[len++] = '.'; + /* fractional number part */ + while (++exponent < -1) { + buf[len++] = '0'; + buf[len++] = '0'; + } + while ((n = mantissa[idx++]) >= 0) { + buf[len++] = n / 10 + '0'; + buf[len++] = n % 10 + '0'; + } + if (buf[len - 1] == '0') { + len--; + } + buf[len] = '\0'; + return len; +} + +int oranumber_from_str(OCINumber *on, const char *buf, int buflen) +{ + const char *end; + int is_positive = 1; + char mantissa[41]; + int dec_point; + long exponent = 0; + int idx = 0; + int i; + + if (buflen < 0) { + end = buf + strlen(buf); + } else { + end = buf + buflen; + } + + /* skip leading spaces */ + while (buf < end && *buf == ' ') { + buf++; + } + if (buf == end) { + return ORANUMBER_INVALID_NUMBER; + } + /* read a sign mark */ + if (*buf == '+') { + buf++; + } else if (*buf == '-') { + buf++; + is_positive = 0; + } + /* next should be number or a dot */ + if ((*buf < '0' || '9' < *buf) && *buf != '.') { + return ORANUMBER_INVALID_NUMBER; + } + /* skip leading zeros */ + while (buf < end) { + if (*buf == '0') { + buf++; + } else { + break; + } + } + /* read integer part */ + while (buf < end) { + if ('0' <= *buf && *buf <= '9') { + if (idx < 41) { + mantissa[idx] = *buf - '0'; + } + idx++; + } else if (*buf == '.' || *buf == 'E' || *buf == 'e' || *buf == ' ') { + break; + } else { + return ORANUMBER_INVALID_NUMBER; + } + buf++; + } + dec_point = idx; + /* read fractional part */ + if (buf < end && *buf == '.') { + buf++; + if (idx == 0) { + /* skip leading zeros */ + while (buf < end) { + if (*buf == '0') { + dec_point--; + buf++; + } else { + break; + } + } + } + while (buf < end) { + if ('0' <= *buf && *buf <= '9') { + if (idx < 41) { + mantissa[idx++] = *buf - '0'; + } + } else if (*buf == 'E' || *buf == 'e' || *buf == ' ') { + break; + } else { + return ORANUMBER_INVALID_NUMBER; + } + buf++; + } + } + /* read exponent part */ + if (buf < end && (*buf == 'E' || *buf == 'e')) { + int negate = 0; + buf++; + if (buf < end) { + if (*buf == '+') { + buf++; + } else if (*buf == '-') { + buf++; + negate = 1; + } + } + while (buf < end) { + if ('0' <= *buf && *buf <= '9') { + exponent *= 10; + exponent += *buf - '0'; + } else if (*buf == ' ') { + break; + } else { + return ORANUMBER_INVALID_NUMBER; + } + buf++; + } + if (negate) { + exponent = -exponent; + } + } + /* skip trailing spaces */ + while (buf < end && *buf == ' ') { + buf++; + } + /* final format check */ + if (buf != end) { + return ORANUMBER_INVALID_NUMBER; + } + /* determine exponent */ + exponent += dec_point - 1; + if (exponent % 2 == 0) { + memmove(mantissa + 1, mantissa, 40); + mantissa[0] = 0; + idx++; + } + /* round if needed */ + if (idx > 40) { + idx = 40; + if (mantissa[40] >= 5) { + /* round up */ + for (i = 39; i >= 0; i--) { + mantissa[i]++; + if (mantissa[i] == 10) { + mantissa[i] = 0; + } else { + break; + } + } + if (i == -1) { + /* all figures are rounded up. */ + mantissa[0] = 0; + mantissa[1] = 1; + idx = 2; + exponent++; + } + } + } + /* shrink mantissa scale */ + while (idx > 0 && mantissa[idx - 1] == 0) { + idx--; + } + /* check zero or underflow */ + if (idx == 0 || exponent < -130) { + on->OCINumberPart[0] = 1; + on->OCINumberPart[1] = 0x80; + return ORANUMBER_SUCCESS; + } + /* check overflow */ + if (exponent > 125) { + return ORANUMBER_NUMERIC_OVERFLOW; + } + /* change the base number from 10 to 100 */ + if (idx % 2 == 1) { + mantissa[idx++] = 0; + } + idx /= 2; + for (i = 0; i < idx; i++) { + mantissa[i] = mantissa[i * 2] * 10 + mantissa[i * 2 + 1]; + } + /* add negative value's terminator */ + if (!is_positive && idx < 20) { + mantissa[idx++] = -1; + } + /* construct OCINumber */ + on->OCINumberPart[0] = 1 + idx; + if (is_positive) { + on->OCINumberPart[1] = (exponent >> 1) + 193; + for (i = 0; i < idx; i++) { + on->OCINumberPart[i + 2] = mantissa[i] + 1; + } + } else { + on->OCINumberPart[1] = 62 - (exponent >> 1); + for (i = 0; i < idx; i++) { + on->OCINumberPart[i + 2] = 101 - mantissa[i]; + } + } + return ORANUMBER_SUCCESS; +} + +int oranumber_dump(const OCINumber *on, char *buf) +{ + int idx; + int len = on->OCINumberPart[0]; + int offset; + + offset = sprintf(buf, "Typ=2 Len=%u: ", len); + if (len > 21) { + len = 21; + } + for (idx = 1; idx <= len; idx++) { + offset += sprintf(buf + offset, "%hhu,", on->OCINumberPart[idx]); + } + buf[--offset] = '\0'; + return offset; +} Added: trunk/ruby-oci8/ext/oci8/oranumber_util.h =================================================================== --- trunk/ruby-oci8/ext/oci8/oranumber_util.h (rev 0) +++ trunk/ruby-oci8/ext/oci8/oranumber_util.h 2010-02-07 10:02:22 UTC (rev 376) @@ -0,0 +1,21 @@ +/* -*- c-file-style: "ruby"; indent-tabs-mode: nil -*- */ +/* + * oranumber_util.h - part of ruby-oci8 + * + * Copyright (C) 2010 KUBO Takehiro + */ +#ifndef ORANUMBER_UTIL_H +#define ORANUMBER_UTIL_H 1 +#include + +#define ORANUMBER_SUCCESS 0 +#define ORANUMBER_INVALID_NUMBER 1722 +#define ORANUMBER_NUMERIC_OVERFLOW 1426 + +int oranumber_to_str(const OCINumber *on, char *buf); +int oranumber_from_str(OCINumber *on, const char *buf, int buflen); + +#define ORANUMBER_DUMP_BUF_SIZ 99 +int oranumber_dump(const OCINumber *on, char *buf); + +#endif Modified: trunk/ruby-oci8/test/test_oranumber.rb =================================================================== --- trunk/ruby-oci8/test/test_oranumber.rb 2010-02-02 13:14:30 UTC (rev 375) +++ trunk/ruby-oci8/test/test_oranumber.rb 2010-02-07 10:02:22 UTC (rev 376) @@ -473,6 +473,45 @@ actual_val = OraNumber.new(x).to_s assert_equal(expected_val, actual_val, x) end + + conn = get_oci8_connection() + begin + cursor = conn.parse('select to_number(:1) from dual') + cursor.define(1, OraNumber) + cursor.bind_param(1, nil, String, 200) + [ + "100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", # 1E125 + "10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", # 1E124 + "234567890234567890234567890234567890000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "23456789023456789023456789023456789000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "2345678902345678902345678902345678900", + "234567890234567890234567890234567890", + "23456789023456789023456789023456789", + "2345678902345678902345678902345678.9", + "234567890234567890234567890234567.89", + "23.456789023456789023456789023456789", + "2.3456789023456789023456789023456789", + "0.23456789023456789023456789023456789", # 2.34..snip..E-1 + "0.023456789023456789023456789023456789", # 2.34..snip..E-2 + "0.0023456789023456789023456789023456789", # 2.34..snip..E-3 + "0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023456789023456789023456789023456789", # 2.34..snip..E-130 + "0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", # 1E-129 + "0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", # 1E-130 + ].each do |str| + cursor[1] = str + cursor.exec + onum = cursor.fetch[0] + assert_equal(str, onum.to_s, "test data: " + str) + + str = '-' + str + cursor[1] = str + cursor.exec + onum = cursor.fetch[0] + assert_equal(str, onum.to_s, "test data: " + str) + end + ensure + conn.logoff + end end # onum.truncate -> integer @@ -488,6 +527,109 @@ end end + def test_new_from_string + conn = get_oci8_connection() + begin + cursor = conn.parse('select to_number(:1) from dual') + cursor.define(1, OraNumber) + cursor.bind_param(1, nil, String, 200) + [ + "100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", # 1E125 + "10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", # 1E124 + "234567890234567890234567890234567890000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "23456789023456789023456789023456789000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "2345678902345678902345678902345678900", + "234567890234567890234567890234567890", + "23456789023456789023456789023456789", + "2345678902345678902345678902345678.9", + "234567890234567890234567890234567.89", + "23.456789023456789023456789023456789", + "2.3456789023456789023456789023456789", + "0.23456789023456789023456789023456789", # 2.34..snip..E-1 + "0.023456789023456789023456789023456789", # 2.34..snip..E-2 + "0.0023456789023456789023456789023456789", # 2.34..snip..E-3 + "0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000023456789023456789023456789023456789", # 2.34..snip..E-130 + "0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", # 1E-129 + "0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001", # 1E-130 + + # leading spaces + " 123", + # trailing spaces + "123 ", + "123.456 ", + "123E10 ", + "123.456E10 ", + # scientific notation + "1234567890000e-9", + "123456789000e-8", + "123456789e-5", + "12345.6789e-1", + "12.3456789e+1", + "0.0000123456789E7", + # round to zero + "1E-131", + # overflow + "1E126", + "10E125", + "100000000000E115", + "1E+126", + # invalid number + "", + " ", + "abc", + "1E10a", + "11E10a", + "1.1.1", + # round down + "444444444444444444444444444444444444444444444444440000", + "44444444444444444444444444444444444444444444444444000", + "44444444444444444444444444.444444444444444444444444", + "4444444444444444444444444.4444444444444444444444444", + "0.000000044444444444444444444444444444444444444444444444444", + "0.00000044444444444444444444444444444444444444444444444444", + # round up + "555555555555555555555555555555555555555555555555550000", + "55555555555555555555555555555555555555555555555555000", + "55555555555555555555555555.555555555555555555555555", + "5555555555555555555555555.5555555555555555555555555", + "0.000000055555555555555555555555555555555555555555555555555", + "0.00000055555555555555555555555555555555555555555555555555", + "999999999999999999999999999999999999999999999999990000", + "99999999999999999999999999999999999999999999999999000", + "99999999999999999999999999.999999999999999999999999", + "9999999999999999999999999.9999999999999999999999999", + "0.000000099999999999999999999999999999999999999999999999999", + "0.00000099999999999999999999999999999999999999999999999999", + # overflow by round up + "999999999999999999999999999999999999999999999999999999999900000000000000000000000000000000000000000000000000000000000000000000", + ].each do |str| + run_test_new_from_string(cursor, str) + run_test_new_from_string(cursor, '-' + str) + end + ensure + conn.logoff + end + end + + def run_test_new_from_string(cursor, str) + cursor[1] = str + onum = nil + begin + cursor.exec + onum = cursor.fetch[0] + rescue OCIError => oraerr + begin + OraNumber.new(str) + flunk("exception expected but none was thrown. test data: " + str) + rescue + assert_equal(oraerr.to_s, $!.to_s, "test data: " + str) + end + end + if onum + assert_equal(onum.dump, OraNumber.new(str).dump, "test data: " + str) + end + end + def test_new_from_bigdecimal ["+Infinity", "-Infinity", "NaN"].each do |n| assert_raise TypeError do @@ -504,7 +646,8 @@ [ [Rational(1, 2), "0.5"], [Rational(3, 5), "0.6"], - [Rational(10, 3), "3.3333333333333333333333333333333333333"], + [Rational(10, 3), "3.33333333333333333333333333333333333333"], + [Rational(20, 3), "6.66666666666666666666666666666666666667"], ].each do |ary| assert_equal(ary[1], OraNumber.new(ary[0]).to_s) end From nobody at rubyforge.org Sun Feb 7 07:52:15 2010 From: nobody at rubyforge.org (nobody at rubyforge.org) Date: Sun, 7 Feb 2010 07:52:15 -0500 (EST) Subject: [ruby-oci8-commit] [377] trunk/ruby-oci8: * ext/oci8/ocinumber.c, ext/oci8/oranumber_util.c, Message-ID: <20100207125215.ECCD1158806F@rubyforge.org> Revision: 377 Author: kubo Date: 2010-02-07 07:52:15 -0500 (Sun, 07 Feb 2010) Log Message: ----------- * ext/oci8/ocinumber.c, ext/oci8/oranumber_util.c, ext/oci8/oranumber_util.h: change the declaration of oranumber_to_str() to prevent buffer overflow by unexpected invalid Oracle number internal data. Modified Paths: -------------- trunk/ruby-oci8/ChangeLog trunk/ruby-oci8/ext/oci8/ocinumber.c trunk/ruby-oci8/ext/oci8/oranumber_util.c trunk/ruby-oci8/ext/oci8/oranumber_util.h Modified: trunk/ruby-oci8/ChangeLog =================================================================== --- trunk/ruby-oci8/ChangeLog 2010-02-07 10:02:22 UTC (rev 376) +++ trunk/ruby-oci8/ChangeLog 2010-02-07 12:52:15 UTC (rev 377) @@ -1,4 +1,10 @@ 2010-02-07 KUBO Takehiro + * ext/oci8/ocinumber.c, ext/oci8/oranumber_util.c, + ext/oci8/oranumber_util.h: change the declaration of + oranumber_to_str() to prevent buffer overflow by + unexpected invalid Oracle number internal data. + +2010-02-07 KUBO Takehiro * ext/oci8/error.c, ext/oci8/oci8.h: add oci8_raise_by_msgno() to retrieve a Oracle error message which depends on NLS_LANGUAGE. * ext/oci8/oranumber_util.c, ext/oci8/oranumber_util.h, Modified: trunk/ruby-oci8/ext/oci8/ocinumber.c =================================================================== --- trunk/ruby-oci8/ext/oci8/ocinumber.c 2010-02-07 10:02:22 UTC (rev 376) +++ trunk/ruby-oci8/ext/oci8/ocinumber.c 2010-02-07 12:52:15 UTC (rev 377) @@ -123,12 +123,12 @@ return LONG2NUM(sl); } /* convert to Integer via String */ - rv = oranumber_to_str(s, buf); + rv = oranumber_to_str(s, buf, sizeof(buf)); if (rv > 0) { return rb_cstr2inum(buf, 10); } oranumber_dump(s, buf); - rb_raise(eOCIException, "Could not convert OraNumber(%s) to string", buf); + rb_raise(eOCIException, "Invalid internal number format: %s", buf); } VALUE oci8_make_float(OCINumber *s, OCIError *errhp) @@ -1026,10 +1026,12 @@ rb_scan_args(argc, argv, "02", &fmt /* nil */, &nls_params /* nil */); if (NIL_P(fmt)) { - rv = oranumber_to_str(_NUMBER(self), buf); + rv = oranumber_to_str(_NUMBER(self), buf, sizeof(buf)); if (rv > 0) { return rb_usascii_str_new(buf, rv); } + oranumber_dump(_NUMBER(self), buf); + rb_raise(eOCIException, "Invalid internal number format: %s", buf); } StringValue(fmt); fmt_ptr = RSTRING_ORATEXT(fmt); Modified: trunk/ruby-oci8/ext/oci8/oranumber_util.c =================================================================== --- trunk/ruby-oci8/ext/oci8/oranumber_util.c 2010-02-07 10:02:22 UTC (rev 376) +++ trunk/ruby-oci8/ext/oci8/oranumber_util.c 2010-02-07 12:52:15 UTC (rev 377) @@ -3,7 +3,7 @@ #include #include "oranumber_util.h" -int oranumber_to_str(const OCINumber *on, char *buf) +int oranumber_to_str(const OCINumber *on, char *buf, int buflen) { signed char exponent; signed char mantissa[21]; /* terminated by a negative number */ @@ -11,6 +11,20 @@ int len = 0; int idx; int n; +#define PUTC(chr) do { \ + if (len < buflen) { \ + buf[len++] = (chr); \ + } else { \ + return ORANUMBER_TOO_SHORT_BUFFER; \ + } \ +} while(0) +#define PUTEND() do { \ + if (len < buflen) { \ + buf[len] = '\0'; \ + } else { \ + return ORANUMBER_TOO_SHORT_BUFFER; \ + } \ +} while(0) if (datalen == 0) { /* too short */ @@ -19,8 +33,8 @@ if (datalen == 1) { if (on->OCINumberPart[1] == 0x80) { /* zero */ - buf[0] = '0'; - buf[1] = '\0'; + PUTC('0'); + PUTEND(); return 1; } /* unexpected format */ @@ -45,7 +59,7 @@ mantissa[idx] = 101 - on->OCINumberPart[idx + 2]; } mantissa[idx] = -1; - buf[len++] = '-'; + PUTC('-'); } /* convert exponent and mantissa to human readable number */ idx = 0; @@ -53,43 +67,43 @@ /* integer part */ n = mantissa[idx++]; if (n / 10 != 0) { - buf[len++] = n / 10 + '0'; + PUTC(n / 10 + '0'); } - buf[len++] = n % 10 + '0'; + PUTC(n % 10 + '0'); while (exponent-- >= 0) { n = mantissa[idx++]; if (n < 0) { do { - buf[len++] = '0'; - buf[len++] = '0'; + PUTC('0'); + PUTC('0'); } while (exponent-- >= 0); - buf[len] = '\0'; + PUTEND(); return len; } - buf[len++] = n / 10 + '0'; - buf[len++] = n % 10 + '0'; + PUTC(n / 10 + '0'); + PUTC(n % 10 + '0'); } if (mantissa[idx] < 0) { - buf[len] = '\0'; + PUTEND(); return len; } } else { - buf[len++] = '0'; + PUTC('0'); } - buf[len++] = '.'; + PUTC('.'); /* fractional number part */ while (++exponent < -1) { - buf[len++] = '0'; - buf[len++] = '0'; + PUTC('0'); + PUTC('0'); } while ((n = mantissa[idx++]) >= 0) { - buf[len++] = n / 10 + '0'; - buf[len++] = n % 10 + '0'; + PUTC(n / 10 + '0'); + PUTC(n % 10 + '0'); } if (buf[len - 1] == '0') { len--; } - buf[len] = '\0'; + PUTEND(); return len; } Modified: trunk/ruby-oci8/ext/oci8/oranumber_util.h =================================================================== --- trunk/ruby-oci8/ext/oci8/oranumber_util.h 2010-02-07 10:02:22 UTC (rev 376) +++ trunk/ruby-oci8/ext/oci8/oranumber_util.h 2010-02-07 12:52:15 UTC (rev 377) @@ -8,11 +8,14 @@ #define ORANUMBER_UTIL_H 1 #include +#define ORANUMBER_INVALID_INTERNAL_FORMAT -1 +#define ORANUMBER_TOO_SHORT_BUFFER -2 + #define ORANUMBER_SUCCESS 0 #define ORANUMBER_INVALID_NUMBER 1722 #define ORANUMBER_NUMERIC_OVERFLOW 1426 -int oranumber_to_str(const OCINumber *on, char *buf); +int oranumber_to_str(const OCINumber *on, char *buf, int buflen); int oranumber_from_str(OCINumber *on, const char *buf, int buflen); #define ORANUMBER_DUMP_BUF_SIZ 99 From nobody at rubyforge.org Tue Feb 9 05:42:39 2010 From: nobody at rubyforge.org (nobody at rubyforge.org) Date: Tue, 9 Feb 2010 05:42:39 -0500 (EST) Subject: [ruby-oci8-commit] [378] trunk/ruby-oci8/dist-files: * dist-files: add ext/oci8/oranumber_util .c and Message-ID: <20100209104240.2B28915B802B@rubyforge.org> Revision: 378 Author: kubo Date: 2010-02-09 05:42:38 -0500 (Tue, 09 Feb 2010) Log Message: ----------- * dist-files: add ext/oci8/oranumber_util.c and ext/oci8/oranumber_util.h. (reported by Raimonds Simanovskis) Modified Paths: -------------- trunk/ruby-oci8/dist-files Modified: trunk/ruby-oci8/dist-files =================================================================== --- trunk/ruby-oci8/dist-files 2010-02-07 12:52:15 UTC (rev 377) +++ trunk/ruby-oci8/dist-files 2010-02-09 10:42:38 UTC (rev 378) @@ -35,6 +35,8 @@ ext/oci8/ocinumber.c ext/oci8/oraconf.rb ext/oci8/oradate.c +ext/oci8/oranumber_util.c +ext/oci8/oranumber_util.h ext/oci8/post-config.rb ext/oci8/stmt.c ext/oci8/object.c From nobody at rubyforge.org Tue Feb 9 05:45:57 2010 From: nobody at rubyforge.org (nobody at rubyforge.org) Date: Tue, 9 Feb 2010 05:45:57 -0500 (EST) Subject: [ruby-oci8-commit] [379] trunk/ruby-oci8/ChangeLog: * dist-files: add ext/oci8/oranumber_util. c and Message-ID: <20100209104557.519263C8048@rubyforge.org> Revision: 379 Author: kubo Date: 2010-02-09 05:45:56 -0500 (Tue, 09 Feb 2010) Log Message: ----------- * dist-files: add ext/oci8/oranumber_util.c and ext/oci8/oranumber_util.h. (reported by Raimonds Simanovskis) Modified Paths: -------------- trunk/ruby-oci8/ChangeLog Modified: trunk/ruby-oci8/ChangeLog =================================================================== --- trunk/ruby-oci8/ChangeLog 2010-02-09 10:42:38 UTC (rev 378) +++ trunk/ruby-oci8/ChangeLog 2010-02-09 10:45:56 UTC (rev 379) @@ -1,3 +1,8 @@ +2010-02-09 KUBO Takehiro + * dist-files: add ext/oci8/oranumber_util.c and + ext/oci8/oranumber_util.h. + (reported by Raimonds Simanovskis) + 2010-02-07 KUBO Takehiro * ext/oci8/ocinumber.c, ext/oci8/oranumber_util.c, ext/oci8/oranumber_util.h: change the declaration of From nobody at rubyforge.org Sat Feb 27 05:43:28 2010 From: nobody at rubyforge.org (nobody at rubyforge.org) Date: Sat, 27 Feb 2010 05:43:28 -0500 (EST) Subject: [ruby-oci8-commit] [380] trunk/ruby-oci8: * ext/oci8/apiwrap.yml, ext/oci8/error.c, ext/oci8/ oci8.h, Message-ID: <20100227104329.163AE18582F4@rubyforge.org> Revision: 380 Author: kubo Date: 2010-02-27 05:43:28 -0500 (Sat, 27 Feb 2010) Log Message: ----------- * ext/oci8/apiwrap.yml, ext/oci8/error.c, ext/oci8/oci8.h, ext/oci8/ocinumber.c: fix for Oracle 8.0 client, which doesn't have OCIMessageOpen() and OCIMessageGet(). * ext/oci8/oci8.c: add a new method OCI8.error_message to get a error message which depends on NLS_LANGUAGE. Modified Paths: -------------- trunk/ruby-oci8/ChangeLog trunk/ruby-oci8/ext/oci8/apiwrap.yml trunk/ruby-oci8/ext/oci8/error.c trunk/ruby-oci8/ext/oci8/oci8.c trunk/ruby-oci8/ext/oci8/oci8.h trunk/ruby-oci8/ext/oci8/ocinumber.c Modified: trunk/ruby-oci8/ChangeLog =================================================================== --- trunk/ruby-oci8/ChangeLog 2010-02-09 10:45:56 UTC (rev 379) +++ trunk/ruby-oci8/ChangeLog 2010-02-27 10:43:28 UTC (rev 380) @@ -1,3 +1,10 @@ +2010-02-27 KUBO Takehiro + * ext/oci8/apiwrap.yml, ext/oci8/error.c, ext/oci8/oci8.h, + ext/oci8/ocinumber.c: fix for Oracle 8.0 client, which doesn't + have OCIMessageOpen() and OCIMessageGet(). + * ext/oci8/oci8.c: add a new method OCI8.error_message to get + a error message which depends on NLS_LANGUAGE. + 2010-02-09 KUBO Takehiro * dist-files: add ext/oci8/oranumber_util.c and ext/oci8/oranumber_util.h. Modified: trunk/ruby-oci8/ext/oci8/apiwrap.yml =================================================================== --- trunk/ruby-oci8/ext/oci8/apiwrap.yml 2010-02-09 10:45:56 UTC (rev 379) +++ trunk/ruby-oci8/ext/oci8/apiwrap.yml 2010-02-27 10:43:28 UTC (rev 380) @@ -947,6 +947,23 @@ - OCILobLocator *locp - ub1 mode +OCIMessageOpen: + :version: 810 + :args: - dvoid *envhp + - OCIError *errhp + - OCIMsg **msghp + - CONST OraText *product + - CONST OraText *facility + - OCIDuration dur + +OCIMessageGet: + :version: 810 + :ret: OraText * + :args: - OCIMsg *msgh + - ub4 msgno + - OraText *msgbuf + - size_t buflen + # round trip: 0 OCINumberIsInt: :version: 810 Modified: trunk/ruby-oci8/ext/oci8/error.c =================================================================== --- trunk/ruby-oci8/ext/oci8/error.c 2010-02-09 10:45:56 UTC (rev 379) +++ trunk/ruby-oci8/ext/oci8/error.c 2010-02-27 10:43:28 UTC (rev 380) @@ -350,26 +350,36 @@ set_backtrace_and_raise(exc, file, line); } -static VALUE get_error_msg_by_msgno(ub4 msgno) +VALUE oci8_get_error_message(ub4 msgno, const char *default_msg) { - char buf[256]; - int sz; + char head[32]; + size_t headsz; + const char *errmsg = NULL; + char msgbuf[64]; - if (msghp == NULL) { - OCIMessageOpen(oci8_envhp, oci8_errhp, &msghp, TO_ORATEXT("rdbms"), TO_ORATEXT("ora"), OCI_DURATION_PROCESS); + if (have_OCIMessageGet) { + if (msghp == NULL) { + oci_lc(OCIMessageOpen(oci8_envhp, oci8_errhp, &msghp, TO_ORATEXT("rdbms"), TO_ORATEXT("ora"), OCI_DURATION_PROCESS)); + } + errmsg = TO_CHARPTR(OCIMessageGet(msghp, msgno, NULL, 0)); } - - sz = snprintf(buf, sizeof(buf), "ORA-%05u: ", msgno); - if (msghp == NULL || OCIMessageGet(msghp, msgno, TO_ORATEXT(buf + sz), sizeof(buf) - sz) == NULL) { - strcpy(buf + sz, "could not get the error message"); + if (errmsg == NULL) { + if (default_msg != NULL) { + errmsg = default_msg; + } else { + /* last resort */ + snprintf(msgbuf, sizeof(msgbuf), "Message %u not found; product=rdbms; facility=ora", msgno); + errmsg = msgbuf; + } } - return rb_external_str_new_with_enc(buf, strlen(buf), oci8_encoding); - + headsz = snprintf(head, sizeof(head), "ORA-%05u: ", msgno); + return rb_str_append(rb_usascii_str_new(head, headsz), + rb_external_str_new_with_enc(errmsg, strlen(errmsg), oci8_encoding)); } -void oci8_do_raise_by_msgno(ub4 msgno, const char *file, int line) +void oci8_do_raise_by_msgno(ub4 msgno, const char *default_msg, const char *file, int line) { - VALUE msg = get_error_msg_by_msgno(msgno); + VALUE msg = oci8_get_error_message(msgno, default_msg); VALUE exc = rb_funcall(eOCIError, oci8_id_new, 1, msg); rb_ivar_set(exc, oci8_id_code, rb_ary_new3(1, INT2FIX(-1))); Modified: trunk/ruby-oci8/ext/oci8/oci8.c =================================================================== --- trunk/ruby-oci8/ext/oci8/oci8.c 2010-02-09 10:45:56 UTC (rev 379) +++ trunk/ruby-oci8/ext/oci8/oci8.c 2010-02-27 10:43:28 UTC (rev 380) @@ -64,6 +64,28 @@ return oracle_client_vernum; } +/* + * call-seq: + * OCI8.error_message(message_no) -> string + * + * Get the Oracle error message specified by message_no. + * Its language depends on NLS_LANGUAGE. + * + * Note: This method is unavailable if the Oracle client version is 8.0. + * + * example: + * # When NLS_LANG is american_america.AL32UTF8 + * OCI8.error_message(1) # => "ORA-00001: unique constraint (%s.%s) violated" + * + * # When NLS_LANG is french_japan.AL32UTF8 + * OCI8.error_message(1) # => "ORA-00001: violation de contrainte unique (%s.%s)" + * + */ +static VALUE oci8_s_error_message(VALUE klass, VALUE msgid) +{ + return oci8_get_error_message(NUM2UINT(msgid), NULL); +} + #define CONN_STR_REGEX "/^([^(\\s|\\@)]*)\\/([^(\\s|\\@)]*)(?:\\@(\\S+))?(?:\\s+as\\s+(\\S*)\\s*)?$/i" static void oci8_do_parse_connect_string(VALUE conn_str, VALUE *user, VALUE *pass, VALUE *dbname, VALUE *mode) { @@ -942,6 +964,9 @@ id_set_prefetch_rows = rb_intern("prefetch_rows="); rb_define_singleton_method_nodoc(cOCI8, "oracle_client_vernum", oci8_s_oracle_client_vernum, 0); + if (have_OCIMessageOpen && have_OCIMessageGet) { + rb_define_singleton_method(cOCI8, "error_message", oci8_s_error_message, 1); + } rb_define_private_method(cOCI8, "parse_connect_string", oci8_parse_connect_string, 1); rb_define_method(cOCI8, "initialize", oci8_svcctx_initialize, -1); rb_define_method(cOCI8, "logoff", oci8_svcctx_logoff, 0); Modified: trunk/ruby-oci8/ext/oci8/oci8.h =================================================================== --- trunk/ruby-oci8/ext/oci8/oci8.h 2010-02-09 10:45:56 UTC (rev 379) +++ trunk/ruby-oci8/ext/oci8/oci8.h 2010-02-27 10:43:28 UTC (rev 380) @@ -336,7 +336,7 @@ #define oci8_raise(err, status, stmt) oci8_do_raise(err, status, stmt, __FILE__, __LINE__) #define oci8_env_raise(err, status) oci8_do_env_raise(err, status, __FILE__, __LINE__) #define oci8_raise_init_error() oci8_do_raise_init_error(__FILE__, __LINE__) -#define oci8_raise_by_msgno(msgno) oci8_do_raise_by_msgno(msgno, __FILE__, __LINE__) +#define oci8_raise_by_msgno(msgno, default_msg) oci8_do_raise_by_msgno(msgno, default_msg, __FILE__, __LINE__) /* raise on error */ #define oci_lc(rv) do { \ @@ -413,7 +413,8 @@ NORETURN(void oci8_do_env_raise(OCIEnv *, sword status, const char *file, int line)); NORETURN(void oci8_do_raise_init_error(const char *file, int line)); sb4 oci8_get_error_code(OCIError *errhp); -NORETURN(void oci8_do_raise_by_msgno(ub4 msgno, const char *file, int line)); +VALUE oci8_get_error_message(ub4 msgno, const char *default_msg); +NORETURN(void oci8_do_raise_by_msgno(ub4 msgno, const char *default_msg, const char *file, int line)); /* ocihandle.c */ void Init_oci8_handle(void); Modified: trunk/ruby-oci8/ext/oci8/ocinumber.c =================================================================== --- trunk/ruby-oci8/ext/oci8/ocinumber.c 2010-02-09 10:45:56 UTC (rev 379) +++ trunk/ruby-oci8/ext/oci8/ocinumber.c 2010-02-27 10:43:28 UTC (rev 380) @@ -154,7 +154,16 @@ if (rv == ORANUMBER_SUCCESS) { return; /* success */ } else { - oci8_raise_by_msgno(rv); + const char *default_msg = NULL; + switch (rv) { + case ORANUMBER_INVALID_NUMBER: + default_msg = "invalid number"; + break; + case ORANUMBER_NUMERIC_OVERFLOW: + default_msg = "numeric overflow"; + break; + } + oci8_raise_by_msgno(rv, default_msg); } } StringValue(fmt); From nobody at rubyforge.org Sat Feb 27 07:32:53 2010 From: nobody at rubyforge.org (nobody at rubyforge.org) Date: Sat, 27 Feb 2010 07:32:53 -0500 (EST) Subject: [ruby-oci8-commit] [381] trunk/ruby-oci8: * ext/oci8/error.c, ext/oci8/extconf.rb, ext/oci8/ oci8.h: fix for Message-ID: <20100227123253.DFE2A18582E1@rubyforge.org> Revision: 381 Author: kubo Date: 2010-02-27 07:32:53 -0500 (Sat, 27 Feb 2010) Log Message: ----------- * ext/oci8/error.c, ext/oci8/extconf.rb, ext/oci8/oci8.h: fix for old Oracle versions, which lack declarations of OCIMsg, oraub8, orasb8 and OCI_DURATION_PROCESS. * ext/oci8/ocihandle.c: fix for mingw compiler. Modified Paths: -------------- trunk/ruby-oci8/ChangeLog trunk/ruby-oci8/ext/oci8/error.c trunk/ruby-oci8/ext/oci8/extconf.rb trunk/ruby-oci8/ext/oci8/oci8.h trunk/ruby-oci8/ext/oci8/ocihandle.c Modified: trunk/ruby-oci8/ChangeLog =================================================================== --- trunk/ruby-oci8/ChangeLog 2010-02-27 10:43:28 UTC (rev 380) +++ trunk/ruby-oci8/ChangeLog 2010-02-27 12:32:53 UTC (rev 381) @@ -1,4 +1,10 @@ 2010-02-27 KUBO Takehiro + * ext/oci8/error.c, ext/oci8/extconf.rb, ext/oci8/oci8.h: fix for + old Oracle versions, which lack declarations of OCIMsg, oraub8, + orasb8 and OCI_DURATION_PROCESS. + * ext/oci8/ocihandle.c: fix for mingw compiler. + +2010-02-27 KUBO Takehiro * ext/oci8/apiwrap.yml, ext/oci8/error.c, ext/oci8/oci8.h, ext/oci8/ocinumber.c: fix for Oracle 8.0 client, which doesn't have OCIMessageOpen() and OCIMessageGet(). Modified: trunk/ruby-oci8/ext/oci8/error.c =================================================================== --- trunk/ruby-oci8/ext/oci8/error.c 2010-02-27 10:43:28 UTC (rev 380) +++ trunk/ruby-oci8/ext/oci8/error.c 2010-02-27 12:32:53 UTC (rev 381) @@ -34,6 +34,10 @@ static OCIMsg *msghp; +#ifndef OCI_DURATION_PROCESS +#define OCI_DURATION_PROCESS ((OCIDuration)5) +#endif + NORETURN(static void oci8_raise2(dvoid *errhp, sword status, ub4 type, OCIStmt *stmthp, const char *file, int line)); NORETURN(static void set_backtrace_and_raise(VALUE exc, const char *file, int line)); Modified: trunk/ruby-oci8/ext/oci8/extconf.rb =================================================================== --- trunk/ruby-oci8/ext/oci8/extconf.rb 2010-02-27 10:43:28 UTC (rev 380) +++ trunk/ruby-oci8/ext/oci8/extconf.rb 2010-02-27 12:32:53 UTC (rev 381) @@ -69,6 +69,7 @@ have_type('OCICallbackLobRead2', 'ociap.h') have_type('OCICallbackLobWrite2', 'ociap.h') have_type('OCIAdmin*', 'ociap.h') +have_type('OCIMsg*', 'ociap.h') if with_config('oracle-version') oci_client_version = OCI8::OracleVersion.new(with_config('oracle-version')).to_i Modified: trunk/ruby-oci8/ext/oci8/oci8.h =================================================================== --- trunk/ruby-oci8/ext/oci8/oci8.h 2010-02-27 10:43:28 UTC (rev 380) +++ trunk/ruby-oci8/ext/oci8/oci8.h 2010-02-27 12:32:53 UTC (rev 381) @@ -67,6 +67,8 @@ typedef unsigned __int64 oraub8; typedef signed __int64 orasb8; #endif +typedef oraub8 ub8; +typedef orasb8 sb8; #endif /* ORAXB8_DEFINED */ #ifndef HAVE_TYPE_ORATEXT @@ -91,6 +93,9 @@ #ifndef HAVE_TYPE_OCIADMIN_ typedef struct OCIAdmin OCIAdmin; #endif +#ifndef HAVE_TYPE_OCIMSG_ +typedef struct OCIMsg OCIMsg; +#endif /* new macros in ruby 1.8.6. * define compatible macros for ruby 1.8.5 or lower. Modified: trunk/ruby-oci8/ext/oci8/ocihandle.c =================================================================== --- trunk/ruby-oci8/ext/oci8/ocihandle.c 2010-02-27 10:43:28 UTC (rev 380) +++ trunk/ruby-oci8/ext/oci8/ocihandle.c 2010-02-27 12:32:53 UTC (rev 381) @@ -9,7 +9,7 @@ */ #include "oci8.h" -#ifdef WIN32 +#ifdef _MSC_VER #define MAGIC_NUMBER 0xDEAFBEAFDEAFBEAFui64; #else #define MAGIC_NUMBER 0xDEAFBEAFDEAFBEAFull; From nobody at rubyforge.org Sat Feb 27 09:18:56 2010 From: nobody at rubyforge.org (nobody at rubyforge.org) Date: Sat, 27 Feb 2010 09:18:56 -0500 (EST) Subject: [ruby-oci8-commit] [382] trunk/ruby-oci8: * lib/oci8/datetime.rb: fix a problem that fractional seconds are lost Message-ID: <20100227141856.BAD3618582E5@rubyforge.org> Revision: 382 Author: kubo Date: 2010-02-27 09:18:56 -0500 (Sat, 27 Feb 2010) Log Message: ----------- * lib/oci8/datetime.rb: fix a problem that fractional seconds are lost when Time value is bound to TIMESTAMP. (reported by Raimonds Simanovskis) Modified Paths: -------------- trunk/ruby-oci8/ChangeLog trunk/ruby-oci8/lib/oci8/datetime.rb Modified: trunk/ruby-oci8/ChangeLog =================================================================== --- trunk/ruby-oci8/ChangeLog 2010-02-27 12:32:53 UTC (rev 381) +++ trunk/ruby-oci8/ChangeLog 2010-02-27 14:18:56 UTC (rev 382) @@ -1,4 +1,9 @@ 2010-02-27 KUBO Takehiro + * lib/oci8/datetime.rb: fix a problem that fractional seconds are lost + when Time value is bound to TIMESTAMP. + (reported by Raimonds Simanovskis) + +2010-02-27 KUBO Takehiro * ext/oci8/error.c, ext/oci8/extconf.rb, ext/oci8/oci8.h: fix for old Oracle versions, which lack declarations of OCIMsg, oraub8, orasb8 and OCI_DURATION_PROCESS. Modified: trunk/ruby-oci8/lib/oci8/datetime.rb =================================================================== --- trunk/ruby-oci8/lib/oci8/datetime.rb 2010-02-27 12:32:53 UTC (rev 381) +++ trunk/ruby-oci8/lib/oci8/datetime.rb 2010-02-27 14:18:56 UTC (rev 382) @@ -92,9 +92,13 @@ end return [year, month, day, hour, minute, sec] unless full - # sec_fraction + # fractional second if val.respond_to? :sec_fraction fsec = (val.sec_fraction * @@datetime_fsec_base).to_i + elsif val.respond_to? :nsec + fsec = val.nsec + elsif val.respond_to? :usec + fsec = val.usec * 1000 else fsec = 0 end From nobody at rubyforge.org Sun Feb 28 06:55:41 2010 From: nobody at rubyforge.org (nobody at rubyforge.org) Date: Sun, 28 Feb 2010 06:55:41 -0500 (EST) Subject: [ruby-oci8-commit] [383] trunk/ruby-oci8: * NEWS: add changes between 2.0.3 and 2.0.4. Message-ID: <20100228115541.BFFE718582CE@rubyforge.org> Revision: 383 Author: kubo Date: 2010-02-28 06:55:41 -0500 (Sun, 28 Feb 2010) Log Message: ----------- * NEWS: add changes between 2.0.3 and 2.0.4. * VERSION: change the version to 2.0.3. * ext/oci8/stmt.c: fix segmentation fault when OCI8::Cursor#fetch is called prior to OCI8::Cursor#exec. * ext/oci8/oci8.c: minor fix in rdoc comment. Modified Paths: -------------- trunk/ruby-oci8/ChangeLog trunk/ruby-oci8/NEWS trunk/ruby-oci8/VERSION trunk/ruby-oci8/ext/oci8/oci8.c trunk/ruby-oci8/ext/oci8/stmt.c Modified: trunk/ruby-oci8/ChangeLog =================================================================== --- trunk/ruby-oci8/ChangeLog 2010-02-27 14:18:56 UTC (rev 382) +++ trunk/ruby-oci8/ChangeLog 2010-02-28 11:55:41 UTC (rev 383) @@ -1,3 +1,10 @@ +2010-02-28 KUBO Takehiro + * NEWS: add changes between 2.0.3 and 2.0.4. + * VERSION: change the version to 2.0.3. + * ext/oci8/stmt.c: fix segmentation fault when OCI8::Cursor#fetch + is called prior to OCI8::Cursor#exec. + * ext/oci8/oci8.c: minor fix in rdoc comment. + 2010-02-27 KUBO Takehiro * lib/oci8/datetime.rb: fix a problem that fractional seconds are lost when Time value is bound to TIMESTAMP. @@ -40,7 +47,7 @@ OraNumber to string and vice varse. 2010-02-02 KUBO Takehiro - * ext/oci8/ocinumber.c: fix to support NNUMBERS with scale larger + * ext/oci8/ocinumber.c: fix to support NUMBERS with scale larger than 38 by using scientific number notation to convert OraNumber to BigDecimal. OraNumber#to_i is also fixed. (reported by Raimonds Simanovskis) Modified: trunk/ruby-oci8/NEWS =================================================================== --- trunk/ruby-oci8/NEWS 2010-02-27 14:18:56 UTC (rev 382) +++ trunk/ruby-oci8/NEWS 2010-02-28 11:55:41 UTC (rev 383) @@ -1,3 +1,58 @@ +2.0.4: + +* New Features + + - OCI8.error_message(message_no) -> string + + Gets the Oracle error message specified by message id. + Its language depends on NLS_LANGUAGE. + + Note: This method is unavailable if the Oracle client + version is 8.0. + + # When NLS_LANG is AMERICAN_AMERICA.AL32UTF8 + OCI8.error_message(1) + # => "ORA-00001: unique constraint (%s.%s) violated" + + # When NLS_LANG is FRENCH_FRANCE.AL32UTF8 + OCI8.error_message(1) + # => "ORA-00001: violation de contrainte unique (%s.%s)" + + - OraNumber#dump -> string + + Returns OraNumber's internal representation whose format + is same with the return value of Oracle SQL function DUMP(). + + OraNumber.new(100).dump #=> "Typ=2 Len=2: 194,2" + OraNumber.new(123).dump #=> "Typ=2 Len=3: 194,2,24" + OraNumber.new(0.1).dump #=> "Typ=2 Len=2: 192,11" + +* Fixed issues + + - Fractional second part is lost when ruby's Time instance is bound + to Oracle datatype TIMESTAMP. + (reported by Raimonds Simanovskis) + + - OraNumber#to_i and OraNumber#to_s fail when its scale is larger + than 38. + (reported by Raimonds Simanovskis) + + - Memory leak about 30 bytes per one place holder for object type. + + - Segmentation fault when a collection of string is bound. + (reported by Raimonds Simanovskis) + + - Segmentation fault when GC starts while initializing a bind + object for object type. + (reported by Remi Gagnon) + + - Segmentation fault when OCI8::Cursor#fetch is called prior to + OCI8::Cursor#exec. + + - Detailed error message is not reported when PL/SQL NO_DATA_FOUND + exception is raised. + (reported by Raimonds Simanovskis) + 2.0.3: * Incompatible Changes Modified: trunk/ruby-oci8/VERSION =================================================================== --- trunk/ruby-oci8/VERSION 2010-02-27 14:18:56 UTC (rev 382) +++ trunk/ruby-oci8/VERSION 2010-02-28 11:55:41 UTC (rev 383) @@ -1 +1 @@ -2.0.3 +2.0.4 Modified: trunk/ruby-oci8/ext/oci8/oci8.c =================================================================== --- trunk/ruby-oci8/ext/oci8/oci8.c 2010-02-27 14:18:56 UTC (rev 382) +++ trunk/ruby-oci8/ext/oci8/oci8.c 2010-02-28 11:55:41 UTC (rev 383) @@ -74,10 +74,10 @@ * Note: This method is unavailable if the Oracle client version is 8.0. * * example: - * # When NLS_LANG is american_america.AL32UTF8 + * # When NLS_LANG is AMERICAN_AMERICA.AL32UTF8 * OCI8.error_message(1) # => "ORA-00001: unique constraint (%s.%s) violated" * - * # When NLS_LANG is french_japan.AL32UTF8 + * # When NLS_LANG is FRENCH_FRANCE.AL32UTF8 * OCI8.error_message(1) # => "ORA-00001: violation de contrainte unique (%s.%s)" * */ Modified: trunk/ruby-oci8/ext/oci8/stmt.c =================================================================== --- trunk/ruby-oci8/ext/oci8/stmt.c 2010-02-27 14:18:56 UTC (rev 382) +++ trunk/ruby-oci8/ext/oci8/stmt.c 2010-02-28 11:55:41 UTC (rev 383) @@ -342,16 +342,18 @@ oci8_bind_t *obind; const oci8_bind_class_t *bind_class; - obind = (oci8_bind_t *)stmt->base.children; - do { - if (obind->base.type == OCI_HTYPE_DEFINE) { - bind_class = (const oci8_bind_class_t *)obind->base.klass; - if (bind_class->pre_fetch_hook != NULL) { - bind_class->pre_fetch_hook(obind, stmt->svc); + if (stmt->base.children != NULL) { + obind = (oci8_bind_t *)stmt->base.children; + do { + if (obind->base.type == OCI_HTYPE_DEFINE) { + bind_class = (const oci8_bind_class_t *)obind->base.klass; + if (bind_class->pre_fetch_hook != NULL) { + bind_class->pre_fetch_hook(obind, stmt->svc); + } } - } - obind = (oci8_bind_t *)obind->base.next; - } while (obind != (oci8_bind_t*)stmt->base.children); + obind = (oci8_bind_t *)obind->base.next; + } while (obind != (oci8_bind_t*)stmt->base.children); + } rv = OCIStmtFetch_nb(svcctx, stmt->base.hp.stmt, oci8_errhp, 1, OCI_FETCH_NEXT, OCI_DEFAULT); #ifdef USE_DYNAMIC_FETCH while (rv == OCI_NEED_DATA) {