ОБНОВЛЕНИЕ с символом NULL в середине массива std::string или char

Мы используем базу данных Oracle 12c и CentOS7 с OCCI для подключения. Мы пытаемся вставить массив символов в базу данных, но этот массив символов имеет символ NUL посередине. Когда мы используем функцию statement->setString, обновление выполняется успешно, однако, как только оно увидит символ NUL, оно помещает туда только символы NUL. См. этот пример кода и его вывод.

Пример кода с использованием setString:

static void Run(const std::string &connectionString, const std::string &user, const std::string &pwd)
{
    Environment *env = Environment::createEnvironment();

    Connection *conn = env->createConnection(user, pwd, connectionString);

    Statement *stmt = conn->createStatement("UPDATE my_customers SET first_name = :1 WHERE customer_id = :2");

    stmt->setString(1, std::string("GEO\0RGE              ", 20));
    stmt->setInt(2, 10);

    stmt->setString(1, std::string(adrs_first_name, sizeof(adrs_first_name)));

    oracle::occi::Statement::Status status = stmt->execute();

    conn->terminateStatement(stmt);
    conn->commit();
}

Доступ к базе данных после обновления:

SELECT first_name FROM my_customers WHERE customer_id = 10;

GEO

SELECT rawtohex(first_name) FROM my_customers WHERE customer_id = 10;

47454F0000000000000000000000000000000000

Однако я ожидал, что это будет

47454F0047452032322020202020202020202020

Итак, я попытался использовать oracle::occi::Bytes - эта ошибка с

ORA – 12899: слишком большое значение для столбца "MAIN_USER"."MY_CUSTOMERS"."FIRST_NAME" (фактическое: 40, максимальное: 20)

Пример кода с использованием setBytes:

static void Run(const std::string &connectionString, const std::string &user, const std::string &pwd)
{
    Environment *env = Environment::createEnvironment();
    Connection *conn = env->createConnection(user, pwd, connectionString);
    Statement *stmt = conn->createStatement("UPDATE my_customers SET first_name = :1 WHERE customer_id = :2");
    std::string s("GEO\0RGE              ", 20);
    oracle::occi::Bytes bytes((unsigned char *)s.c_str(), 20, 0, env);
    stmt->setBytes(1, bytes);
    stmt->setInt(2, 10);
    try
    {

        oracle::occi::Statement::Status status = stmt->execute();
    }
    catch (oracle::occi::SQLException &e)
    {
        std::cout << "Error " << e.getErrorCode() << ": " << e.what() << std::endl;
    }
    conn->terminateStatement(stmt);
    conn->commit();
}

Вывод:

Error 12899 : ORA - 12899 : value too large for column "MAIN_USER"."MY_CUSTOMERS"."FIRST_NAME" (actual : 40, maximum : 20)

Итак, я попытался отправить половину байтов, изменив второй параметр конструктора oracle::occi::Bytes на 10, и это удалось, однако после чтения значения из базы данных я понял, что это строковое представление шестнадцатеричного значения символы. Итак, мой вопрос на данный момент: почему Oracle12c помещает шестнадцатеричное значение в виде строки, когда я передаю oracle::occi:Bytes.

Пример кода с использованием половины фактической длины и setBytes:

static void Run(const std::string &connectionString, const std::string &user, const std::string &pwd)
{
    Environment *env = Environment::createEnvironment();
    Connection *conn = env->createConnection(user, pwd, connectionString);
    Statement *stmt = conn->createStatement("UPDATE my_customers SET first_name = :1 WHERE customer_id = :2");

    std::string s("GEO\0RGE              ", 20);
    oracle::occi::Bytes bytes((unsigned char *)s.c_str(), 10, 0, env);

    stmt->setBytes(1, bytes);
    stmt->setInt(2, 10);
    try
    {
        oracle::occi::Statement::Status status = stmt->execute();
    }
    catch (oracle::occi::SQLException &e)
    {
        std::cout << "Error " << e.getErrorCode() << ": " << e.what() << std::endl;
    }

    conn->terminateStatement(stmt);
    conn->commit();
}

Доступ к базе данных после обновления: SELECT first_name FROM my_customers WHERE customer_id = 10;

47454F00524745202020

ПРИМЕЧАНИЕ. Этот запрос не был преобразован в rawtohex — это фактическое значение массива символов в базе данных.

Вот определение таблицы:

DESCRIBE MAIN_USER.MY_CUSTOMERS

Name                           Null Type
------------------------------ ---- --------------
CUSTOMER_ID                         NUMBER(10)
FIRST_NAME                          CHAR(20 CHAR)

Вот информация о нашем экземпляре Oracle: Oracle Database 12c Enterprise Edition Release 12.1.0.2.0 — 64-битная рабочая версия с опциями секционирования, реальных кластеров приложений, автоматического управления хранилищем, OLAP, расширенной аналитики и тестирования реальных приложений.

Мы используем клиент Oracle occi 12.1 64bit.


person Mechanic    schedule 08.06.2016    source источник
comment
но этот массив символов имеет символ NUL в середине -- это означает, что это данные с непечатаемыми управляющими символами ASCII, поэтому все ставки снимаются, когда система встречает один из этих символов. Почему бы не использовать Base64 для кодирования строки и записи ее как символьных данных? Или сохранить его как двоичные данные, подобно тому, как вы сохраняете данные изображения в базу данных?   -  person PaulMcKenzie    schedule 08.06.2016
comment
std::string поддерживает все символы, более или менее это просто список символов. Мы ожидаем, что при отправке std::string с длиной в setString будут помещены все символы, указанные длиной std::string , а не только до управляющего символа.   -  person Mechanic    schedule 08.06.2016
comment
Я знаю, что std::string поддерживает символы NULL. Проблема в том, что вы делаете предположение о том, как работает setString, когда дана строка с нулевым символом, а длина выходит за пределы нулевого символа. Если в документах для этой функции явно не указано, что она делает, или у вас нет исходного кода этой функции, вы не можете предположить, как она обрабатывает строку. Даже если setString действительно делает то, что вы говорите, как база данных обрабатывает управляющие символы внутри типов CHAR? Чтобы облегчить это, вы можете сохранить строку в кодировке Base64. Вот для чего создан Base64.   -  person PaulMcKenzie    schedule 08.06.2016
comment
Я согласен с этим утверждением, однако, если вы прочитаете мой вопрос, вы заметите, что я пытался использовать setBytes из-за вашего точного утверждения. Это, в свою очередь, вызвало другую проблему. Я хотел бы отметить, что проблема заключается не в том, может ли std::string содержать NULL, проблема/вопрос, который я задаю, заключается в том, КАК ПОЛУЧИТЬ НУЛЕВОЙ СИМВОЛ В БАЗУ ДАННЫХ, ИСПОЛЬЗУЯ ЗАЯВЛЕНИЕ ОБНОВЛЕНИЯ ЧЕРЕЗ OCCI?   -  person Mechanic    schedule 08.06.2016
comment
У вас есть другая служебная программа (может быть, SQLDeveloper?), которая может установить этот столбец CHAR (20) в строку, содержащую встроенные управляющие символы? Вы не хотите тратить свое время, пытаясь сделать что-то программно с OCCI, если результаты, которые вы ищете, на самом деле не могут быть достигнуты. Возможно, это действительно проблема Oracle DB CHAR, а не проблема occi или selectString.   -  person PaulMcKenzie    schedule 08.06.2016
comment
Да, у нас есть другие процессы, вставляющие эти данные, но не через OCCI, поэтому мы знаем, что это достижимо. Мы можем считывать данные из базы данных, но не можем вставлять/обновлять их.   -  person Mechanic    schedule 08.06.2016


Ответы (1)


Для тех, кто может наткнуться на это, я закончил разговор со службой поддержки Oracle, и они сказали мне, что нет способа сделать то, что я хотел (как и ожидалось), однако обходной путь Byte приблизил нас, и мы смогли взять шестнадцатеричную строку передается setBytes и превращает его в необработанные данные, а затем преобразует эти необработанные данные в varchar2, который в конечном итоге «работает» - однако я не знаю, с какими последствиями мы столкнемся позже, но пока это работает .

Код:

static void Run(const std::string &connectionString, const std::string &user, const std::string &pwd)
{
    Environment *env = Environment::createEnvironment();
    Connection *conn = env->createConnection(user, pwd, connectionString);
    Statement *stmt = conn->createStatement("UPDATE my_customers SET first_name = utl_raw.cast_to_varchar2(hextoraw(:1)) WHERE customer_id = :2");

    std::string s("GEO\0RGE              ", 20);
    oracle::occi::Bytes bytes((unsigned char *)s.c_str(), 20, 0, env);

    stmt->setBytes(1, bytes);
    stmt->setInt(2, 10);
    try
    {
        oracle::occi::Statement::Status status = stmt->execute();
    }
    catch (oracle::occi::SQLException &e)
    {
        std::cout << "Error " << e.getErrorCode() << ": " << e.what() << std::endl;
    }

    conn->terminateStatement(stmt);
    conn->commit();
}

Затем проверка базы данных:

SELECT rawtohex(first_name) FROM my_customers WHERE customer_id = 10;

47454F0052474520202020202020202020202020

Так вроде работает

person Mechanic    schedule 09.06.2016