Por que o Oracle usa um comprimento de bytes diferente do java para o esquilo de caracteres unicode suplementares?

8

Eu tenho código java aparando uma string UTF-8 para o tamanho da minha coluna Oracle (11.2.0.4.0) que acaba gerando um erro porque java e Oracle veem a string como comprimentos de bytes diferentes. Eu verifiquei que meu NLS_CHARACTERSETparâmetro no Oracle é 'UTF8'.

Escrevi um teste que ilustra meu problema abaixo usando o emoji de esquilo unicode (🐿️)

public void test() throws UnsupportedEncodingException, SQLException {
    String squirrel = "\uD83D\uDC3F\uFE0F";
    int squirrelByteLength = squirrel.getBytes("UTF-8").length; //this is 7
    Connection connection = dataSource.getConnection();

    connection.prepareStatement("drop table temp").execute();

    connection.prepareStatement("create table temp (foo varchar2(" + String.valueOf(squirrelByteLength) + "))").execute();

    PreparedStatement statement = connection.prepareStatement("insert into temp (foo) values (?)");
    statement.setString(1, squirrel);
    statement.executeUpdate();
}

Isso falha na última linha do teste com a seguinte mensagem:

ORA-12899: valor muito grande para a coluna
"MYSCHEMA". "TEMP". "FOO" (real: 9, máximo: 7)

A configuração de NLS_LENGTH_SEMANTICSé BYTE. Infelizmente, não posso mudar isso, pois é um sistema legado. Não estou interessado em aumentar o tamanho da coluna, apenas em poder prever com precisão o tamanho do Oracle de uma string.

agradl
fonte
Infelizmente, estou vendo relatórios conflitantes na internet sobre quantos bytes devem ser. Alguns dizem 7, outros 8, outros 12 (???). O que acontece se você declarar o campo Oracle como 8 em vez de 7. Funciona então? Sei que isso não responde explicitamente à sua pergunta do porquê, mas pode lhe dar uma resposta.
Jcolebrand

Respostas:

3

O que se segue é minha especulação.

Java Strings são representados internamente usando a codificação UTF-16 . Quando você getBytes("UTF-8")converte Java entre as duas codificações, e provavelmente usa uma plataforma Java atualizada.

Quando você tenta armazenar um Java Stringno banco de dados, o Oracle também realiza a conversão entre o UTF-16 nativo do Java e o conjunto de caracteres do banco de dados, conforme determinado por NLS_CHARACTERSET.

O caractere de esquilo foi aprovado como parte do padrão Unicode em 2014 (de acordo com a página que você vinculou), enquanto a versão mais recente do Oracle 11g rel.2 foi publicada em 2013 .

Pode-se supor que o Oracle use um algoritmo de conversão de caracteres diferente ou desatualizado, de modo que a representação de bytes de 🐿️) no servidor (9 bytes de comprimento) seja diferente do que getBytes()retorna no cliente (7 bytes).

Acho que para resolver esse problema, você pode atualizar o servidor Oracle ou usar UTF-16 como o conjunto de caracteres do banco de dados.

mustaccio
fonte
Isso resolveu o problema. Meu oracle 11g estava usando o jdk 1.6.0_141 enquanto a instância 12 está usando o jdk 1.8.0_121
agradl
3
Por favor, marque a pergunta como respondida para que a próxima pessoa sabe isso funcionou :)
jcolebrand
Falei cedo demais, estou investigando mais para confirmar minha suspeita - não estava relacionada à versão do oracle ... fique ligado
agradl
1

O problema está no manuseio de caracteres unicode suplementares pela Oracle, quando NLS_LENGTH_SEMANTICShouver UTF8.

A partir da documentação (ênfase adicionada).

O conjunto de caracteres UTF8 codifica caracteres em um, dois ou três bytes. É para plataformas baseadas em ASCII.

Caracteres suplementares inseridos em um banco de dados UTF8 não corrompem os dados no banco de dados. Um caractere suplementar é tratado como dois caracteres separados definidos pelo usuário que ocupam 6 bytes. A Oracle recomenda que você alterne para AL32UTF8 para obter suporte completo de caracteres suplementares no conjunto de caracteres do banco de dados.

Além disso, o último ponto de código na cadeia de esquilo é um seletor de variação e opcional. Vi isso usando um inspetor de caracteres unicode

Depois de alterar o NLS_CHARACTERSETparâmetro do banco de dados para AL32UTF8o teste aprovado.

agradl
fonte