Как преобразовать значения Oracle VARCHAR2 в UTF-8 из списка возможных кодировок?

по устаревшим причинам у нас есть столбец VARCHAR2 в нашей базе данных Oracle 10-где кодировка символов установлена в AL32UTF8-которые содержат какие-либо не-UTF-8 значений. Значения всегда находятся в одном из следующих наборов символов:

  • US-ASCII
  • UTF-8
  • CP1252
  • латинский-1

Я написал функцию Perl для исправления сломанных значений вне базы данных. Для значения из этого столбца базы данных он проходит через этот список кодировки и пытается преобразовать значение в UTF-8. Если преобразование не удается, он пытается следующую кодировку. Первое, что нужно преобразовать без ошибок, - это значение, которое мы сохраняем. Теперь я хотел бы реплицировать эту функциональность внутри базы данных, чтобы любой мог ее использовать.

однако, все, что я могу найти на это CONVERT функции, который никогда не терпит неудачу, но вставляет символ замены для символов, которые он не распознает. Так что, насколько я могу судить, узнать это невозможно. когда преобразование не удалось.

поэтому у меня есть два вопроса:

  1. есть ли какой-то существующий интерфейс, который пытается преобразовать строку в один из списков кодировок, возвращая первый, который преуспевает?
  2. а если нет, есть ли другой интерфейс, который указывает на сбой, если он не может преобразовать строку в кодировку? Если да, то я мог бы написать предыдущий функция.

обновление:

Для справки, я написал эту функцию PostgreSQL в PL / pgSQL, которая делает именно то, что мне нужно:

CREATE OR REPLACE FUNCTION encoding_utf8(
    bytea
) RETURNS TEXT LANGUAGE PLPGSQL STRICT IMMUTABLE AS $$
DECLARE
    encoding TEXT;
BEGIN
    FOREACH encoding IN ARRAY ARRAY[
        'UTF8',
        'WIN1252',
        'LATIN1'
    ] LOOP
        BEGIN
            RETURN convert_from(, encoding);
        EXCEPTION WHEN character_not_in_repertoire OR untranslatable_character THEN
            CONTINUE;
        END;
    END LOOP;
END;
$$;

Я бы очень хотел знать, как сделать эквивалент в Oracle.

2 ответов


благодаря ключевой информации о незаконных символах в UTF-8 от @collapsar, а также некоторым раскопкам коллегой, я придумал это:

CREATE OR REPLACE FUNCTION reencode(string IN VARCHAR2) RETURN VARCHAR2
AS
    encoded VARCHAR2(32767);
    type  array_t IS varray(3) OF VARCHAR2(15);
    array array_t := array_t('AL32UTF8', 'WE8MSWIN1252', 'WE8ISO8859P1');
BEGIN
    FOR I IN 1..array.count LOOP
        encoded := CASE array(i)
            WHEN 'AL32UTF8' THEN string
            ELSE CONVERT(string, 'AL32UTF8', array(i))
        END;
        IF instr(
            rawtohex(
                utl_raw.cast_to_raw(
                    utl_i18n.raw_to_char(utl_raw.cast_to_raw(encoded), 'utf8')
                )
            ),
            'EFBFBD'
        ) = 0 THEN
            RETURN encoded;
        END IF;
    END LOOP;
    RAISE VALUE_ERROR;
END;

Любопытно, что он никогда не попадает в WE8ISO8859P1: WE8MSWIN1252 преобразует каждый из списка 800 или так плохих значений, которые у меня есть без жалоб. То же самое не верно для моих реализаций Perl или PostgreSQL, где CP1252 терпит неудачу для некоторых значений, но ISO-8859-1 преуспевает. Тем не менее, значения от Oracle кажутся адекватными, и, похоже, действителен Unicode (протестирован путем загрузки их в PostgreSQL), поэтому я не могу жаловаться. Думаю, этого будет достаточно, чтобы санировать мои данные.


чтобы проверить, содержит ли столбец базы данных недопустимый utf-8, используйте следующий запрос:

 select CASE
            INSTR (
                  RAWTOHEX (
                      utl_raw.cast_to_raw (
                          utl_i18n.raw_to_char (
                                utl_raw.cast_to_raw ( <your_column> )
                              , 'utf8'
                          )
                      )
                  )
                , 'EFBFBD'
            )
        WHEN 0 THEN 'OK'
        ELSE 'FAIL' 
        END
   from <your_table>
      ;

учитывая, что ваша кодировка БД-al32utf8.

отметим, что EF BF BD представляет собой незаконное кодирование в utf-8.

поскольку все другие кодировки, которые вы указываете, ориентированы на байт, преобразование в unicode никогда не завершится неудачей, но, возможно, приведет к различным кодовым точкам. без контекстной информации автоматизированное определение фактического источника кодировка невозможна.

С наилучшими пожеланиями, Карстен

ps: имена oracle для кодировок: CP1252 ->WE8MSWIN1252 LATIN-1 ->WE8ISO8859P1