IBM Support

Error -21005 when using ODBC to select data from a database.

Troubleshooting


Problem

Error -21005 selecting data from an IDS engine using ODBC.

Symptom

Problem when connecting to IDS Engine using ODBC.

Cause

The most common reason for this error is having character outside the codeset specified in the database.

The environment variable DB_LOCALE is used to specify the database locale. When a database is created, the codeset used is defined by this variable, which means that only characters included in that particular codeset would be allowed.

The default value for DB_LOCALE is en_US.8859-1. This locale value corresponds to "English" language, "US" territory and "8859-1" codeset.

The 8859-1 codeset has a limited number of characters, some symbols like the Euro or single quotation mark are not allowed (other codeset like CP1252 or UNICODE do allow these symbols)

If the client machine uses a different codeset than the one used in the database, a codeset conversion must be performed by the client communication libraries (GLS library) to correctly transform between the two codesets.

An English Windows machine by default uses the 1252 codeset, which means a conversion would be needed. If at some point during this conversion the GLS libraries find a character that is not allowed in any of the two codesets it would fail with a GLS error.

Since CSDK version 2.80, the ODBC driver is Unicode enabled, this means all the data the driver handles has to be in Unicode format. This means that a extra conversion has to be done:

When retrieving data from the database the conversion is as follows:

  Codeset in DB_LOCALE -> Unicode -> Codeset in CLIENT_LOCALE

When inserting data:

  Codeset in CLIENT_LOCALE -> Unicode -> Codeset in DB_LOCALE

Error -21005 would appear when doing a select if there was an error in conversion.
If for some reason the database contains characters outside the allowed range (e.g.: a 'Euro' symbol in a 8859-1 database) the GLS functions can't convert the character because it is not supposed to be there in the first place.

Codeset conversion.

The mapping between different codesets is defined in the CV files in the $INFORMIXDIR directory.

Assume a database is created with the default 8859-1 codeset, and a connection is to be made from an English Windows (CP1252)

04e4 correspond to CP1252
0333 correspond to 8859-1

(The 'Registry' file inside the $INFORMIXDIR\gls\cv3 directory contains the different representations of the codeset names, from decimal,text and hexadecimal)

The conversion file between 1252 and 8859-1 looks like :

     ---- 04e40333.cv ------
     <source_version>  3
     <modified_date>  "05-04-2004"
     <source_codeset>    "1252"
     <target_codeset>    "8859-1"

     \x00...\xff \x00... # Default everything onto itself
     \x80    \x1a error      # No Euro-sign in 8859-1
     ...
     \x90 \x1a error # undefined-char-00006
     \x91 \x1a error # left single quotation mark
     \x92 \x1a error # right single quotation mark
     ...
     ---- 04e40333.cv ------

The lines inside specify which and how the different codes are mapped between the two codesets

The first line indicates that all the codes from 0x00 to 0xFF should be converted to the same value for a 8859-1 codeset

    \x00...\xff \x00... # Default everything onto itself

The rest of the lines indicate what values are not allowed, and that it should raise an error if one is found in the conversion.

the Euro symbol is allowed in the 1252 codeset and has the code 0x80, but is not allowed in 8859-1, so it has to raise an error.

    \x80    \x1a error      # No Euro-sign in 8859-1



The conversion file between CP1252 and 8859-15 (which does support the Euro) :

     ---- 04e4e02f.cv ------
     <source_version> 2
     <modified_date> "05-04-2004"
     <source_codeset>    "1252"
     <target_codeset>    "8859-15"

     \x00...\xff \x00... # Default everything onto itself
     \x82 \x1a error # single low-9 quotation mark
     ...
     \x91 \x1a error # left single quotation mark
     \x92 \x1a error # right single quotation mark
     ...
     \x80    \xa4            # euro symbol
     \x8a    \xa6            # latin capital letter s with caron
     ...
     ---- 04e4e02f.cv ------


In this case there is a conversion between the Euro in CP1252 to the 8859-15

    \x80    \xa4            # euro symbol

In 1252 the code for the Euro is 0x80 when in 8859-15 it is 0xA4

This GLS conversion is what makes it possible for clients with different codesets to share the same data.
A CP1252 client would insert a Euro (0x80), the GLS functions would convert the (0x80) into a (0xA4) which is the value for a Euro symbol in the 8859-15 codeset.


If a second client, this time with a codeset of 858 inserts a euro (0xD5) it would also be converted to the correct (0xA4) for the 8859-15 codeset. The conversion works in both ways, when a client with a 858 codeset select the Euro symbol from the database, the (0xA4) gets converted to (0xD5)

The conversion file between 858 and 8859-15 (which does support the Euro) :

     ---- 035ae02f.cv -----
     <source_version> 1
     <modified_date> "05-07-2004"
     <source_codeset>    "858"
     <target_codeset>    "8859-15"

     \x00...\xff \x00... # Default everything onto itself
     ...
     \xd4 \xc8 # latin capital letter e with grave
     \xd5 \xa4       # euro
     \xd6 \xcd # latin capital letter i with acute
     ---- 035ae02f.cv -----


Environment settings

The two variables that control the GLS conversion are CLIENT_LOCALE and DB_LOCALE.
CLIENT_LOCALE must contain the codeset of the Client Operating system and DB_LOCALE the codeset of the database.

The gls functions use these two values to know how the conversion has to be done.
If both codesets are the same, the functions assume there is no need to do any conversion, so the data would be directly passed to the server. This could cause corruption if the data is not in the correct format.

Let's say there is an en_US.88591 database, so the DB_LOCALE is set to en_US.8859-1.
In a Windows machine with CP1252 as the codeset, assume the CLIENT_LOCALE is set to to en_US.8859-1 by mistake.

The characters to be inserted are from a 1252 codeset, but the GLS library does not know this ( The CLIENT_LOCALE would need setting correctly ). If a Euro (0x80) is inserted , because there is no conversion that takes place, the 0x80 would be directly inserted into the database - causing corruption. (A code outside the allowed range)

If then, a select of this data is attempted with a client like ODBC (which would always do a GLS conversion to Unicode) the -21005 error would appear.

Diagnosing The Problem

This is an example of how to generate the problem:

     ------
     D:\Infx\ids1150>type euro.txt
     €
     D:\Infx\ids1150>od -x euro.txt
     0000000000      0080
     0000000001
     D:\Infx\ids1150>
     D:\Infx\ids1150>set CLIENT_LOCALE=en_US.8859-1
     D:\Infx\ids1150>set DB_LOCALE=en_US.8859-1
     D:\Infx\ids1150>dbaccess stores7 -

     Database selected.
     > create table test1(c1 char(10));
     Table created.
     > load from euro.txt insert into test1;
     1 row(s) loaded.
     > select * from test1;
     c1

     €
     1 row(s) retrieved.
     >
     -----

So the database has been corrupted by inserting an invalid character. If the correct value for CLIENT_LOCALE had been set (remember the data came from a Windows 1252 codeset) the insert would have failed like this :

     -------
     D:\Infx\ids1150>set CLIENT_LOCALE=en_US.cp1252
     
     D:\Infx\ids1150>dbaccess stores7 -
     Database selected.

     > load from euro.txt insert into test1;
     23103: Code-set conversion function failed due to illegal sequence or invalid value.
       847: Error in load file line 1.
     Error in line 1
     Near character position 37
     >
     --------

If an attempt is made to select the euro character from the "test1" table from ODBC , an -21005 error results

     ------
     D:\Infx>cat test.vbs
     set cx=createobject("ADODB.Connection")
     cx.provider="msdasql"
     cx.connectionstring="dsn=%DNS_NAME%"
     cx.open
     set cr=createobject("ADODB.Recordset")
     set cr.activeconnection=cx
     cr.open  "select c1 from test1", cx, 2, 3
     WScript.Stdout.Write cr.fields("c1")
     cr.close
     D:\Infx>

     D:\Infx>C:\windows\SysWOW64\cscript.exe test.vbs
     Microsoft (R) Windows Script Host Version 5.6
     Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

     D:\Infx\test.vbs(8, 1) Microsoft VBScript runtime error: Type mismatch: 'WScript.Stdout.Write'

     D:\Infx>
     --------

In the odbc trace see the -21005 error:

----- odbc.trace -----
     ...
     cscript.exe x   708-ce0 EXIT  SQLExtendedFetch  with return code -1 (SQL_ERROR)
HSTMT               03171AF8
UWORD                        1 <SQL_FETCH_NEXT>
SQLLEN                     0
SQLULEN *           0x002CE948
UWORD *             0x02BE9CC0

DIAG [S1000] [Informix][Informix ODBC Driver]Unspecified System Error =  -21005. (21005)
DIAG [01S01] [Informix][Informix ODBC Driver]Error in row. (-11006)
     ...
     ----- odbc.trace -----


If this kind of corruption exists in the database, the only way to solve it is to recreate the database with a codeset that allows the kind of data to be inserted correctly

In our test, the data was inserted from a CP1252 Windows without any conversion, which means that the data held internally by the Informix Dynamic Server is in CP1252 codeset (even though the codeset of the database was set to 8859-1)

Resolving The Problem

To fix this problem the locale of the database has to change
A DBEXPORT / DBIMPORT must be performed to recreate the database with a CP1252 codeset which will allow the euro symbol to be inserted.

1) Check the Locale Setting of the database
     ------
     D:\Infx\ids1150>dbaccess sysmaster -
     Database selected.

     > select * from sysdbslocale where dbs_dbsname='stores7';

     dbs_dbsname  stores7
     dbs_collate  en_US.819

     1 row(s) retrieved.
     >
     ------

2) Export the data

Specify CLIENT_LOCALE to be the same as DB_LOCALE so there is no conversion while creating the unload files:

     ------
     D:\Infx\ids1150>set CLIENT_LOCALE=en_US.8859-1
     D:\Infx\ids1150>set DB_LOCALE=en_US.8859-1
     D:\Infx\ids1150>dbexport stores7 > out.txt
     D:\Infx\ids1150>
     ------

3) Drop the database (or rename it)

     ------
     D:\Infx\ids1150>dbaccess sysmaster -
     Database selected.
     > drop database stores7;
     Database dropped.
     >
     Database closed.
     D:\Infx\ids1150>
     ------

4) Import the data

Do the DBIMPORT to recreate the database. The value of CLIENT_LOCALE has to be set to en_US.1252 because the data in the unload files is in 1252 format (codeset) .
DB_LOCALE is set to en_US.1252 so the database would be created with 1252 codeset which would allow the Euro symbol to be inserted.

     ------
     D:\Infx\ids1150>set CLIENT_LOCALE=en_US.1252
     D:\Infx\ids1150>set DB_LOCALE=en_US.1252
     D:\Infx\ids1150>dbimport stores7 > out.txt
     D:\Infx\ids1150>dbaccess sysmaster -
     Database selected.
     > select * from sysdbslocale where dbs_dbsname='stores7';

     dbs_dbsname  stores7
     dbs_collate  en_US.1252
     1 row(s) retrieved.
     >
     ------

The 'Euro' (0x80) is still there but because the codeset has changed, now there would be no problems accessing that data

     ------
     D:\Infx\ids1150>dbaccess stores7 -
     Database selected.
     > select * from test1;

     c1
     €
     1 row(s) retrieved.
     >
     ------

5) Change the DB_LOCALE specified in the client program

Running the ODBC test client without any change to the ODBC would fail because the database does not allow the connection if the value for DB_LOCALE specifed in the DSN is wrong (it was created with en_US.8859-1 but now the database is en_US.1252)

     ------
     D:\Infx>C:\windows\SysWOW64\cscript.exe x.vbs
     Microsoft (R) Windows Script Host Version 5.6
     Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

     D:\Infx\x.vbs(4, 1) Microsoft OLE DB Provider for ODBC Drivers: [Informix][Informix ODBC Driver][Informix]Database locale information mismatch.

     D:\Infx>
     ------

After changing DB_LOCALE setting specified in the ODBC DSN everything works ok.

     -----
     D:\Infx>C:\windows\SysWOW64\cscript.exe x.vbs
     Microsoft (R) Windows Script Host Version 5.6
     Copyright (C) Microsoft Corporation 1996-2001. All rights reserved.

     €
     D:\Infx>
     -----

[{"Product":{"code":"SSVT2J","label":"Informix Tools"},"Business Unit":{"code":"BU059","label":"IBM Software w\/o TPS"},"Component":"Informix Client Software Development Kit (CSDK)","Platform":[{"code":"PF002","label":"AIX"},{"code":"PF025","label":"Platform Independent"},{"code":"PF008","label":"DYNIX\/ptx"},{"code":"PF010","label":"HP-UX"},{"code":"PF015","label":"IRIX"},{"code":"PF016","label":"Linux"},{"code":"PF022","label":"OS X"},{"code":"PF026","label":"Reliant UNIX"},{"code":"PF027","label":"Solaris"},{"code":"PF033","label":"Windows"}],"Version":"2.7;2.8;2.9;3.0;3.5","Edition":"","Line of Business":{"code":"LOB10","label":"Data and AI"}}]

Document Information

Modified date:
20 January 2022

UID

swg21402358