Невозможно вставить символы Юникода через freetds unixODBC в MS SQL 2008

У меня есть приложение С++, построенное на RHEL 5, которое подключается к MS SQL 2008 через freeTDS и unixODBC, расположенное на компьютере с Windows.

Это запрос, который приложение отправляет в базу данных.

INSERT INTO mytable (SAMPLE) VALUES(N'乕乭????????乺丕')

Um@D@Iz фактически вставляется в базу данных при вызове вышеуказанного запроса.

Ниже приведены конфигурации, которые я использую:

== freetds.conf ==
[myserver.mydomain.com]
client charset = UTF-16
debug flags = 0xffff
dump file = /tmp/dump.log
dump file append = yes
host = 127.0.01
port = 1433
tds version = 7.3

== odbcinst.ini ==
[FreeTDS Driver]
Description     = FreeTDS
Driver          = /usr/lib64/libtdsodbc.so.0

== odbc.ini ==
[mydsn]
Description     = MS SQL connection to 'mydb' database
Driver          = FreeTDS Driver
Servername      = myserver.mydomain.com
Port            = 1433
TDS_Version     = 7.3
Database        = mydb
UserName        = sa
Password        = mypassword
Trace           = Yes
TraceFile       = /tmp/odbc.log
ForceTrace      = Yes

Я могу напрямую вставить данные в базу данных через

INSERT INTO mytable (SAMPLE) VALUES(N'乕乭????????乺丕') но не через freeTDS и unixODBC

Пожалуйста, найдите код, который я использую ниже:

#include <iostream>

#ifdef WIN32
  #include <windows.h>
#endif

#include <sql.h>
#include <sqlext.h>
#include <sqltypes.h>

#include "unicode/ustdio.h"

using namespace std;

int main (int argc, char* argv[])
{
  SQLHSTMT hSQLStatement = 0;
  SQLHENV hSQLEnvironment = 0;
  SQLHDBC hSQLODBC = 0;

  SQLRETURN sqlRet = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &hSQLEnvironment);

  if(SQL_SUCCEEDED(sqlRet))
  {
    sqlRet = SQLSetEnvAttr(hSQLEnvironment, 
                           SQL_ATTR_ODBC_VERSION,
                           (void*)SQL_OV_ODBC3, 
                           0);

    if(SQL_SUCCEEDED(sqlRet))
    {
        sqlRet = SQLAllocHandle(SQL_HANDLE_DBC, 
                                hSQLEnvironment, 
                                &hSQLODBC);
    }
    else
    {
      cout << "Error in SQLAllocHandle for SQL_HANDLE_DBC" << endl;
    }
  }
  else
  {
    cout << "Error in SQLAllocHandle for SQL_HANDLE_ENV" << endl;
  }

  UnicodeString DSNName = "mydsn";
  UnicodeString UserName = "sa";
  UnicodeString Password = "mypassword";

  UnicodeString Value = "";

  UChar32 character = 20053;
  Value.append(character);

  character = 20077;
  Value.append(character);

  character = 131140;
  Value.append(character);

  character = 131145;
  Value.append(character);

  character = 20090;
  Value.append(character);

  character = 19989;
  Value.append(character);

  UnicodeString SQLStatement = "INSERT INTO mytable (sample) VALUES(N";
  SQLStatement.append("'");
  SQLStatement.append(Value);
  SQLStatement.append("'");
  SQLStatement.append(")");

  if(0 != hSQLODBC)
  {
    SQLRETURN sqlRet = SQLConnectW(hSQLODBC, 
                                   (SQLWCHAR*)DSNName.getTerminatedBuffer(),
                                   SQL_NTS, 
                                   (SQLWCHAR*)UserName.getTerminatedBuffer(),
                                   SQL_NTS,
                                   (SQLWCHAR*)Password.getTerminatedBuffer(),
                                   SQL_NTS);

    if(SQL_SUCCEEDED(sqlRet))
    {
      cout << "Connection to database successful" << endl;

      SQLRETURN sqlRet = SQLAllocHandle(SQL_HANDLE_STMT, 
                                        hSQLODBC, 
                                        &hSQLStatement);

      if(SQL_SUCCEEDED(sqlRet))
      {
        sqlRet = SQLExecDirectW(hSQLStatement, 
                                (SQLWCHAR*)SQLStatement.getTerminatedBuffer(), 
                                SQL_NTS);

        if(SQL_SUCCEEDED(sqlRet))
        {
          cout << "Query Execution successful" << endl;
        }
        else
          cout << "Query Execution failed" << endl;
      }
    }
    else
    {
      cout << "Connection to database failed" << endl;
    }
  }

  return 0;
}

Есть идеи, что здесь может быть не так?

РЕДАКТИРОВАТЬ 1: добавлен пример кода

РЕДАКТИРОВАТЬ 2: обновлено в соответствии с предложением Оливера


person D3XT3R    schedule 23.02.2016    source источник
comment
тип данных столбца SAMPLE должен быть NVARCHAR   -  person Jaydip Jadhav    schedule 23.02.2016
comment
да тип данных NVARCHAR   -  person D3XT3R    schedule 23.02.2016
comment
Какую версию FreeTDS вы используете? 0,95, 0,91? TDS версии 7.3 поддерживается только в FreeTDS 0.95. Я считаю, что 0.91 поставляется с RHEL 6, но я не уверен с RHEL 5.   -  person FlipperPA    schedule 28.02.2016


Ответы (2)


Документация ICU UnicodeString здесь (https://ssl.icu-project.org /apiref/icu4c/classicuSQLStatement.getTerminatedBuffer()1UnicodeString.html#details) говорит: «В ICU строка Unicode состоит из 16-битных единиц кода Unicode». Поскольку вы используете SQLStatement.getTerminatedBuffer() для получения этих данных, я прочитал это как данные в UTF-16, когда вы захватываете их и вставляете в SQLExecDirectW().

С другой стороны, вы указываете client charset = UTF-8 в конфигурации FreeTDS. Поскольку он работает с собственным клиентом, я бы попытался изменить кодировку для клиента FreeTDS на UTF-16.

person Oliver    schedule 25.02.2016
comment
обновил кодировку до UTF-16, та же проблема осталась. - person D3XT3R; 25.02.2016

SQL Server использует UTF-16 для NVARCHAR. Из информации в вашем вопросе похоже, что у вас есть строки в UTF-8. Сначала преобразуйте оператор вставки в UTF-16, прежде чем отправлять его на SQL Server.


Обновление: я вижу, вы добавили пример кода. Я вижу, вы добавляете значения UChar32 к экземпляру UnicodeString. Они имеют ширину 4 байта. AFAICT вам нужно append UChar (шириной 2 байта).

person TT.    schedule 23.02.2016
comment
Тот же запрос при вызове через приложение C++, созданное для Windows с использованием драйвера SQL Server Native Client 10.0 odbc, успешно вставляется в базу данных. Я не уверен, что FreeTDS может делать здесь. - person D3XT3R; 25.02.2016
comment
@ D3XT3R D3XT3R Глядя на то, что на самом деле вставлено, это пахнет сужением до одиночных байтов, а не до двойных байтов в строке UTF-16. Просто в качестве теста преобразуйте строку в массив байтов и вставьте строку как INSERT INTO mytable(sample)VALUES(CAST(0x<byte pattern> AS NVARCHAR(4000)), где шаблон байта — это последовательность байтов в шестнадцатеричной форме для строки в UTF-16. - person TT.; 25.02.2016
comment
@ D3XT3R Вам действительно следует подумать о добавлении к вашему вопросу MVCE, то есть кода для воспроизведения вашей проблемы. Возможно, проблема в вашем коде, а не в драйвере FreeDTS. Вы используете строковый тип в UTF-16? - person TT.; 25.02.2016
comment
@ D3XT3R Кстати, это будет UTF-16 LE (Little Endian). - person TT.; 25.02.2016
comment
@ D3XT3R Я исхожу из опыта работы с JAVA / SQL-Server ... Я просто исхожу из того, что знаю о SQL-Server и симптомах, которые вы описываете в своем вопросе. MVCE действительно необходим, если вы хотите, чтобы специалисты по C++/FreeDTS могли что-то сказать о том, что вы можете делать неправильно на этом уровне. - person TT.; 25.02.2016
comment
@ D3XT3R Нет, спасибо. Вы должны иметь код внутри своего вопроса, предоставив минимальный/полный/проверяемый/пример. Вот как работает SO, вопросы должны содержать информацию в вопросе, а не на сторонних ресурсах. - person TT.; 25.02.2016
comment
@ D3XT3R Обновленный ответ - person TT.; 26.02.2016