Finally, after about 30 hours of hair tearing and hard work, I've isolated a pure ESQL/C version of this bug and demonstrated that it primarily afflicts 32-bit ports of Informix ESQL/C that come with CSDK 2.90 and 2.81 . It does not afflict most 64-bit ports (though 2.81.FC2 crashed with a core dump, as did 2.80.UC2), and it does not affect older ports (2.80.UC1 was OK). It did not reproduce on RHEL 4 running on Linux PPC-64 with CSDK 3.00.FC1(actually, a nightly build prior to that release). Finally, it only affects LVARCHAR NOT NULL, and only if the table is not a temporary table. Since I kept the server constant (IDS 10.00.UC5 running on Solaris 10), the problem is most likely in ESQL/C rather than IDS itself; if it is perchance in IDS, it is in the code that recognizes different versions of ESQL/C and somehow reacts differently.
This bug is now idsdb00139040 in the IBM/Informix CQ database. Sadly, as yet, I do not have a workaround or fix for this -- that comes next. The reproduction code follows: /* ** @(#)$Id: bug-lvcnn.ec,v 1.3 2007/06/13 05:35:27 jleffler Exp $ ** ** Demonstration of bug originally reported in DBD::Informix ** as RT#13708 at http://rt.cpan.org/ and ignored for a couple of years. ** Primarily seems to afflict 32-bit ports of CSDK (ESQL/C). ** And primarily the more recent versions - 2.90 and maybe 2.81. ** ** Bug: SQL DESCRIPTOR does not handle LVARCHAR NOT NULL properly. ** ** Demonstrated on Solaris 10 with CSDK 2.90.UC4. ** No bug with 64-bit on Solaris 10 with CSDK 2.90.FC4. ** No bug with 64-bit on Linux PPC 64 with CSDK 3.00.FN125 (nightly build). ** No bug with 32-bit on Solaris 10 with CSDK 2.80.UC1 ** Core dump on 64-bit on Solaris 10 with CSDK 2.81.FC2 ** Core dump on 32-bit on Solaris 10 with CSDK 2.81.UC2 ** In each of the above cases, the test DBMS is IDS 10.00.UC5 running on Solaris 10. ** Also seen by customers on various platforms - primarily 32-bit. */ #include <stdlib.h> #include <string.h> #include <stdio.h> $ static char lvc1[] = "This is row 1"; $ static char lvc2[] = "And this is in row 1 too"; static int num_bugs = 0; static void print_descriptor(const char *p_name, int p_index) { $ int index = p_index; $ const char *name = p_name; $ long coltype; $ long collength; $ long colind; $ char colname[129]; $ int nullable; $ whenever error stop; $ get descriptor :name VALUE :index :coltype = TYPE, :collength = LENGTH, :nullable = NULLABLE, :colind = INDICATOR, :colname = NAME; colname[byleng(colname, strlen(colname))] = '\0'; printf("%s:%02d: type = %2d, length = %4d, nulls = %2d, indicator = %2d, name = %s\n", name, index, coltype, collength, nullable, colind, colname); } static void check_data(void) { $ lvarchar *lv1 = 0; $ lvarchar *lv2 = 0; $ int row; $ short ind; $ prepare p from "select row_number, lvc_with_null, lvc_wout_null from lvarchar_test order by ro w_number"; $ declare c cursor for p; $ allocate descriptor "d" with max 3; $ describe p using sql descriptor "d"; /* Print allocator description */ print_descriptor("d", 1); print_descriptor("d", 2); print_descriptor("d", 3); ifx_var_flag(&lv1, 1); ifx_var_flag(&lv2, 1); $ open c; while (sqlca.sqlcode == 0) { $ fetch c using sql descriptor "d"; if (sqlca.sqlcode != 0) break; $ get descriptor "d" VALUE 1 :row = DATA, :ind = INDICATOR; if (ind == 0) printf("row_number = %d:\n", row); else printf("row_number IS NULL (ind = %d)\n", ind); $ get descriptor "d" VALUE 2 :lv1 = DATA, :ind = INDICATOR; if (ind != 0) printf(" lvc_with_null IS NULL (ind = %d)\n", ind); else { char *result = (char *)ifx_var_getdata(&lv1); int length = ifx_var_getlen(&lv1); if (length < 0) { printf("Length of lvarchar < 0\n"); length = 0; } if (result == 0) { printf("Result of lvarchar == 0x00000000\n"); } printf(" lvc_with_null = <<%.*s>>\n", length, result); if (strcmp(result, lvc1) != 0) { printf("**BUG** wanted = <<%s>>\n", lvc1); num_bugs++; } } $ get descriptor "d" VALUE 3 :lv2 = DATA, :ind = INDICATOR; if (ind != 0) printf(" lvc_wout_null IS NULL (ind = %d)\n", ind); else { char *result = (char *)ifx_var_getdata(&lv2); int length = ifx_var_getlen(&lv2); if (length < 0) { printf("Length of lvarchar < 0\n"); length = 0; } if (result == 0) { printf("Result of lvarchar == 0x00000000\n"); } printf(" lvc_wout_null = <<%.*s>>\n", length, result); if (strcmp(result, lvc2) != 0) { printf("**BUG** wanted = <<%s>>\n", lvc2); num_bugs++; } } } ifx_var_freevar(&lv1); ifx_var_freevar(&lv2); $ close c; $ free c; $ free p; $ deallocate descriptor "d"; } int main(int argc, char **argv) { $ char *dbname = "stores"; if (argc > 1) dbname = argv[1]; $ database :dbname; $ whenever error continue; $ drop table lvarchar_test; $ whenever error stop; printf("\nTest 1: LVARCHAR(128) - with NOT NULL\n"); $ create table lvarchar_test ( row_number serial not null primary key, lvc_with_null lvarchar(128), lvc_wout_null lvarchar(128) not null ); $ insert into lvarchar_test values(1, :lvc1, :lvc2); check_data(); printf("\nTest 2: LVARCHAR - with NOT NULL\n"); $ drop table lvarchar_test; $ create table lvarchar_test ( row_number serial not null primary key, lvc_with_null lvarchar, lvc_wout_null lvarchar not null ); $ insert into lvarchar_test values(1, :lvc1, :lvc2); check_data(); printf("\nTest 3: LVARCHAR(128) - without NOT NULL\n"); $ drop table lvarchar_test; $ create table lvarchar_test ( row_number serial not null primary key, lvc_with_null lvarchar(128), lvc_wout_null lvarchar(128) ); $ insert into lvarchar_test values(1, :lvc1, :lvc2); check_data(); printf("\nTest 4: LVARCHAR - without NOT NULL\n"); $ drop table lvarchar_test; $ create table lvarchar_test ( row_number serial not null primary key, lvc_with_null lvarchar, lvc_wout_null lvarchar ); $ insert into lvarchar_test values(1, :lvc1, :lvc2); check_data(); printf("\nTest 5: LVARCHAR(128) - with NOT NULL in TEMP TABLE\n"); $ drop table lvarchar_test; $ create temp table lvarchar_test ( row_number serial not null primary key, lvc_with_null lvarchar(128), lvc_wout_null lvarchar(128) not null ); $ insert into lvarchar_test values(1, :lvc1, :lvc2); check_data(); $ close database; if (num_bugs == 0) printf("== PASSED ==\n"); else printf("** FAILED ** %d bugs detected\n", num_bugs); return(num_bugs > 0); /* 0 on no bugs; 1 otherwise */ } To say that the circumstances under which it fails are obscure is to be excessively polite. I tried to release an update to DBD::Informix, but the release process goes through the test suite, and since the test t/t93lvarchar.t was failing, it was not possible to make the release automatically, so I haven't made the update. I may decide to cheat and make the release using a 64-bit Perl and 64-bit ESQL/C, like I did with the DBD::Informix 2007.0226 (though that was released completely unaware of the issue - this one would be released despite knowing the test fails). The temptation to modify the test (eg to use a temp table instead of a permanent one) is also quite considerable. -- Jonathan Leffler <[EMAIL PROTECTED]> #include <disclaimer.h> Guardian of DBD::Informix - v2007.0226 - http://dbi.perl.org "Blessed are we who can laugh at ourselves, for we shall never cease to be amused."
