Re: Status of UTF-8 support in 0.83 dev

Sebastien FLAESCH <[email protected]>
Newsgroups gmane.comp.db.tds.freetds
Organization Four J's Development Tools
Message-ID <[email protected]>
Frediano,

While testing ParameterType = SQL_WVARCHAR, I faced a problem with ColumnSize...

According to MSDN, ColumnSize defines the precision of the type in *characters* (not bytes):

       http://msdn.microsoft.com/en-us/library/ms711786(VS.85).aspx

With attached example, when using a CHAR(2) column, I just bind with ParameterType=SQL_WVARCHAR and ColumnSize=2 ...

But I get an error:

SQL State: 42000
SQL code : 8016
Message  : [FreeTDS][SQL Server]The incoming tabular data stream (TDS) remote
            procedure call (RPC) protocol stream is incorrect.
            Parameter 3 (""): Data type 0xE7 has an invalid data length or metadata length.

Please could you have a look at this?

Note in my SQL Native Client driver, I do also use SQL_WVARCHAR and ColumnSize has to be passed as a number of characters.


Thanks!
Seb


Frediano Ziglio wrote:
> Il giorno gio, 14/08/2008 alle 11.53 -0400, James K. Lowden ha scritto:
>> Hi Sebastien, 
>>
>>> James K. Lowden wrote:
>>>> "Wide" characters have a fixed width, usually 16 bits.  UTF-8 has a
>>>> variable width and for the most part can be treated like ASCII
>>>> (strlen(3) et al.).  Declaring UTF-8 as SQL_C_WCHAR incorrectly tells
>>>> the driver there's a character in every two bytes.
>>> Oh take care, actually there are 2 type specifications in
>>> SQLBindParameter:
>>>
>>> SQLRETURN SQLBindParameter(
>>>       SQLHSTMT     StatementHandle,
>>>       SQLUSMALLINT     ParameterNumber,
>>>       SQLSMALLINT     InputOutputType,
>>>       SQLSMALLINT     ValueType,          -- This is the C type
>>>       SQLSMALLINT     ParameterType,      -- This is the SQL equivalent
>>>       SQLULEN     ColumnSize,
>>>       SQLSMALLINT     DecimalDigits,
>>>       SQLPOINTER     ParameterValuePtr,
>>>       SQLINTEGER     BufferLength,
>>>       SQLLEN *     StrLen_or_IndPtr);
>>>
>>> For sure, when using (char *) / UTF-8, ValueType *must* be SQL_C_CHAR,
>>> not SQL_C_WCHAR... (that one is for wchar_t).
>>>
>>> I was talking about ParameterType...
>>>
>>> Should it be  SQL_W[VAR]CHAR  or  SQL_[VAR]CHAR  when binding UTF-8?
>> Oh, I see.  Your question surprises me, then, because IIUC the client-side
>> encoding doesn't matter for ParameterType.  ParameterType describes the
>> data type of the parameter as used in the query, not as represented in the
>> client buffer.  (I"m just an ODBC student.  Correct me if I'm wrong.) 
>>
> 
> ParameterType specify the type to use with the server so if SQL_VARCHAR
> is used a VARCHAR is sent to the server while using SQL_WVARCHAR an
> NVARCHAR is used. This cause the problem with japanese encoding,
> SQL_VARCHAR is not able to "contain" japanese so the error. Some
> updates:
> - I split my working patch for wide support committing support for
> server N(VAR)CHAR, very short and seems to work correctly with UTF-8
> (the other part is to support *W functions and SQL_C_WCHAR... and it's a
> very very long work)
> - I ported Sebastian tests in a new src/odbc/unittests/utf8.c test, it
> works correctly and it tests data inserted too. I used a trick to force
> client encoding (using SQLDriverConnect instead of SQLConnect)
> - I wrote a workaround for invalid TDS protocol if a conversion error
> happen. Now libTDS send an empty string... not correct but at least you
> get an error from library and you can continue to issue queries. The
> patch was backported to 0.82 too. I'll write a definitive fix for this
> (it require to handle correctly errors and cancel sending query).
> 
>> For instance, any of these pairs works:
>>
>> 	ValueType	ParameterType 
>> 	-------------	-------------
>> 	SQL_C_CHAR	SQL_DECIMAL
>> 	SQL_C_CHAR	SQL_VARCHAR
>> 	SQL_C_DOUBLE	SQL_FLOAT	
>>
>> For character data parameters, the choice of SQL_W[VAR]CHAR or
>> SQL_[VAR]CHAR is governed by the data type of the parameter as understood
>> by the server.  For stored procedures, the parameter type is obvious: it's
>> declared within the procedure text.  For placeholders in open SQL text, I
>> would say the type is always SQL_[VAR]CHAR.  
>>
> 
> No, the client encodes the parameter type as specified by the
> application. The server converts the parameter as needed.
> 
>> That is, if your query is, 
>>
>> 	CREATE TABLE t (name nvarchar(30))
>> 	INSERT t values( ? )	
>>
>> You would use SQL_[VAR]CHAR.  
>>
>> Now, what to do when what you really want is equivalent to "INSERT t
>> values( N'string' )"?  I assume it's still SQL_[VAR]CHAR, but I won't know
>> for sure until either Frediano explains or I figure out how sp_execute
>> works.  
>>
> 
> sp_execute is a special store procedure witch is embedded in sql server
> and support variable parameters... let's say it's like a printf instead
> of a strcpy. You specify parameters you pass followed by parameters and
> server execute the query.
> 
>> One thing you could try is SQLDescribeParam with various combinations
>> using Microsoft's driver.  We would follow whatever it says/expects.  
>>
> 
> SQLDescribeParam is a workaround on mssql... it parse the query to get
> parameters type. Obviously on complicated query it isn't able to tell
> the truth... Assuming query is "SELECT * FROM table WHERE x = ?" it
> build a "SET FMTONLY ON SELECT x FROM table" and get x type... this is
> the reason is still unsupported by our driver... nobody wants to write
> the sql parser :)
> 
> bye
>   Frediano
> 
> 
> _______________________________________________
> FreeTDS mailing list
> [email protected]
> http://lists.ibiblio.org/mailman/listinfo/freetds
>

_______________________________________________
FreeTDS mailing list
[email protected]
http://lists.ibiblio.org/mailman/listinfo/freetds
odbctest112.c (text/plain, 5.4 KB)
/* Testing SQL_WVARCHAR and ColumnSize parameter of SQLBindParameter().
 * Author: Sebastien FLAESCH
 *
 * This is a simple ASCII sample, using ColumnSize = 2 (characters).
 *
 * With freetds-0.83.dev.20080901, we get this error:
 *
 *  SQL State: 42000
 *  SQL code : 8016
 *  Message  : [FreeTDS][SQL Server]The incoming tabular data stream (TDS)
 *             remote procedure call (RPC) protocol stream is incorrect.
 *             Parameter 3 (""):
 *             Data type 0xE7 has an invalid data length or metadata length.
 *
 */

static const char * g_dbname = "freetds_msvtest1_cobra";
static const char * g_usernm = "msvuser";
static const char * g_passwd = "fourjs";

#ifdef _WIN32
#include <WINDOWS.H>
#endif
#include <stdio.h>
#include <sql.h>
#include <sqlext.h>

#define CHECK_RCODE(t,h,m) \
   if ( rcode != SQL_NO_DATA \
     && rcode != SQL_SUCCESS \
     && rcode != SQL_SUCCESS_WITH_INFO  \
     && rcode != SQL_NEED_DATA ) { \
      fprintf(stderr,"Error %d at: %s\n",rcode,m); \
      getErrorInfo(t,h); \
      exit(1); \
   }

static int v_key;
static SQLINTEGER v_ind_key;

static char v_varchar[50];
static SQLINTEGER v_ind_varchar;

static void getErrorInfo(SQLSMALLINT sqlhdltype, SQLHANDLE sqlhandle)
{
    SQLRETURN rcode = 0;
    SQLCHAR sqlstate[SQL_SQLSTATE_SIZE + 1];
    SQLINTEGER naterror = 0;
    SQLCHAR msgtext[SQL_MAX_MESSAGE_LENGTH + 1];
    SQLSMALLINT msgtextl = 0;
    int ifxerror = 0;
    rcode = SQLGetDiagRec((SQLSMALLINT) sqlhdltype,
                          (SQLHANDLE) sqlhandle,
                          (SQLSMALLINT) 1,
                          (SQLCHAR *) sqlstate,
                          (SQLINTEGER *) & naterror,
                          (SQLCHAR *) msgtext,
                          (SQLSMALLINT) sizeof(msgtext),
                          (SQLSMALLINT *) & msgtextl);
    fprintf(stderr, "Diagnostic info:\n");
    fprintf(stderr, "  SQL State: %s\n", (char *) sqlstate);
    fprintf(stderr, "  SQL code : %d\n", (int) naterror);
    fprintf(stderr, "  Message  : %s\n", (char *) msgtext);
}

main(int argc,char **argv)
{
    SQLRETURN rcode;
    SQLHENV m_henv;
    SQLHDBC m_hdbc;
    SQLHSTMT m_hstmt1;
    int i,j;
    const char * dbname;
    const char * usernm;
    const char * passwd;

    if (argc == 4) {
       dbname = argv[1];
       usernm = argv[2];
       passwd = argv[3];
    } else {
       dbname = g_dbname;
       usernm = g_usernm;
       passwd = g_passwd;
    }

    m_henv = NULL;
    rcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &m_henv);
    CHECK_RCODE(SQL_HANDLE_ENV,NULL,"SQLAllocHandle EnvH");

    rcode = SQLSetEnvAttr(m_henv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER) SQL_OV_ODBC3, SQL_IS_UINTEGER);
    CHECK_RCODE(SQL_HANDLE_ENV,m_henv,"ODBC V3");

    m_hdbc = NULL;
    rcode = SQLAllocHandle(SQL_HANDLE_DBC, (SQLHANDLE) m_henv, (SQLHANDLE *) &m_hdbc);
    CHECK_RCODE(SQL_HANDLE_ENV,m_henv,"SQLAllocHandle DbcH");

    rcode = SQLConnect((SQLHANDLE) m_hdbc,
                       (SQLCHAR *) dbname,
                       (SQLINTEGER) SQL_NTS,
                       (SQLCHAR *) usernm,
                       (SQLINTEGER) SQL_NTS,
                       (SQLCHAR *) passwd,
                       (SQLINTEGER) SQL_NTS);
    CHECK_RCODE(SQL_HANDLE_DBC,m_hdbc,"SQLConnect");

    rcode = SQLSetConnectAttr(m_hdbc,
               SQL_ATTR_AUTOCOMMIT, (SQLPOINTER) SQL_AUTOCOMMIT_ON, SQL_IS_UINTEGER);
    CHECK_RCODE(SQL_HANDLE_ENV,m_henv,"SQLSetConnectAttr(autocommit)");

    m_hstmt1 = NULL;
    rcode = SQLAllocHandle(SQL_HANDLE_STMT, m_hdbc, &m_hstmt1);
    CHECK_RCODE(SQL_HANDLE_DBC,m_hdbc,"SQLAllocHandle StmtH 1");

    rcode = SQLExecDirect(m_hstmt1, (SQLCHAR *) "drop table tab1", SQL_NTS);
    rcode = SQLExecDirect(m_hstmt1, (SQLCHAR *) "create table tab1 (k int, c varchar(2))", SQL_NTS);
    CHECK_RCODE(SQL_HANDLE_STMT,m_hstmt1,"SQLExecDirect 1.1");

    rcode = SQLPrepare(m_hstmt1, (SQLCHAR *) "insert into tab1 values (?,?)", SQL_NTS);
    CHECK_RCODE(SQL_HANDLE_STMT,m_hstmt1,"SQLPrepare insert");

    SQLBindParameter(m_hstmt1, 1, SQL_PARAM_INPUT,
                     SQL_C_LONG, SQL_INTEGER, 0, 0, &v_key, 0, &v_ind_key);
    CHECK_RCODE(SQL_HANDLE_STMT,m_hstmt1,"SQLBindParameter 1");

    SQLBindParameter(m_hstmt1,
                     /* ParameterNumber */         2,
                     /* InputOutputType */         SQL_PARAM_INPUT,
                     /* ValueType */               SQL_C_CHAR,
                     /* ParameterType */           SQL_WVARCHAR,
                     /* ColumnSize */              2, /* Characters! */
                     /* DecimalDigits */           0,
                     /* ParameterValuePtr */       v_varchar,
                     /* BufferLength */            sizeof(v_varchar),
                     /* StrLen_or_IndPtr */        &v_ind_varchar);
    CHECK_RCODE(SQL_HANDLE_STMT,m_hstmt1,"SQLBindParameter 2");

    v_key = 1;
    strcpy(v_varchar, "ab");
    v_ind_varchar = strlen(v_varchar);
    rcode = SQLExecute(m_hstmt1);
    CHECK_RCODE(SQL_HANDLE_STMT,m_hstmt1,"SQLExecute 1.1 StmtH");

    rcode = SQLFreeHandle(SQL_HANDLE_STMT, (SQLHANDLE) m_hstmt1);
    CHECK_RCODE(SQL_HANDLE_STMT,m_hstmt1,"SQLFreeHandle StmtH 1");

    rcode = SQLDisconnect(m_hdbc);
    CHECK_RCODE(SQL_HANDLE_DBC,m_hdbc,"SQLDisconnect");

    rcode = SQLFreeHandle(SQL_HANDLE_DBC, (SQLHANDLE) m_hdbc);
    CHECK_RCODE(SQL_HANDLE_DBC,m_hdbc,"SQLFreeHandle DbcH");

    rcode = SQLFreeHandle(SQL_HANDLE_ENV, (SQLHANDLE) m_henv);
    CHECK_RCODE(SQL_HANDLE_ENV,m_henv,"SQLFreeHandle EnvH");

}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.