DBD::Informix test failure for lvarchar - bug found!



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 <jonathan.leffler@xxxxxxxxx> #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."


Relevant Pages

  • ANNOUNCE: Rose::DB::Object 0.601 released
    ... There have been many changes and bug fixes since the last announcement to ... * Added pre_init_hookmethod to metadata objects and the loader. ... * Added support for bigserial columns. ... * Improved auto-detection of primary key sequence names. ...
    (perl.dbi.users)
  • Re: for Lynn Trapp RE: two records for one relationship
    ... BTW: your previous concern about a finished good being associated with another finished good has been addresssed. ... "Lynn Trapp" wrote: ... > primary key and the foreign key MUST be the same. ... The code shows a bug when selecting a txtProfileID ...
    (microsoft.public.access.tablesdbdesign)
  • Re: ERROR 515: while creating a merge replication publication
    ... I did as you suggested and manually changed my IDENTITY PRIMARY KEY columns to PRIMARY KEY IDENTITY NOT FOR REPLICATION. ... Every table in the databas has one and only one PRIMARY KEY column now, and they are all created as IDENTITY NOT FOR REPLICATION. ... There is a bug if you have a composite primary key which may generate the error message you are seeing. ...
    (microsoft.public.sqlserver.replication)
  • Re: Any way to make PG driver obey PrintWarn?
    ... Hash: SHA1 ... CREATE TABLE / PRIMARY KEY will create implicit index ... This is a bug in DBD::Pg. ...
    (perl.dbi.users)