Como exatamente o tipo “char” de um byte funciona no PostgreSQL?

9

Costumo ver pessoas falando "char". Eu nunca usei isso. É definido nos documentos como,

O tipo "char" (observe as aspas) é diferente de char (1), pois ele usa apenas um byte de armazenamento. É usado internamente nos catálogos do sistema como um tipo de enumeração simplista.

E além disso,

"char"  1 byte  single-byte internal type

Então, se é um byte, qual é o domínio e como você o utilizaria? É assinado ou não assinado? Neste post de @Erwin Brandstetter, ele explica tudo , mas ainda estou confuso. Ele está usando ascii()e chr(), e fornece isso

SELECT i
     , chr(i)::"char"        AS i_encoded
     , ascii(chr(i)::"char") AS i_decoded
FROM   generate_series(1,256) i;

Isso está fazendo algo realmente estranho entre 10 e 11.

  i  | i_encoded | i_decoded 
-----+-----------+-----------
...
   8 | \x08      |         8
   9 |           |         9
  10 |          +|        10
     |           |           -- WTF is going on here.
  11 | \x0B      |        11
  12 | \x0C      |        12
...

Também fica muito estranho aqui:

 126 | ~         |       126
 127 | \x7F      |       127
 128 |           |       128
 129 |           |       128
 130 |           |       128
 131 |           |       128

Por que tudo ao norte de 128 está sendo decodificado como 128? Mas, para aguentar um pouco, depois de 192, há um interruptor e eles são decodificados como 192 ..

 190 |           |       128
 191 |           |       128
 192 |           |       192
 193 |           |       192
 194 |           |       192
 195 |           |       192
 196 |           |       192
 197 |           |       192

Erwin diz

Existem vários caracteres não destinados à exibição. Portanto, codifique antes de armazenar e decodifique antes de exibir ...

Não sei ao certo por que devemos codificar, se estamos fazendo exatamente o que essas perguntas fazem.

CREATE TABLE foo AS
SELECT i::"char"
FROM   generate_series(-128,127) i;

Isso funciona bem. Podemos recuperar as entradas usando

SELECT i::int FROM foo;

Então, resumindo,

  1. O que o código de Erwin está fazendo entre 10 e 11, onde o i é nulo?
  2. Por que 128 é repetido tantas vezes?
  3. Por que 192 é repetido tantas vezes?
  4. Como acionar a incapacidade de armazenar 0, quando Erwin diz que você não pode codificar 0 dessa maneira (caractere nulo não permitido)

    CREATE TABLE foo AS SELECT 0::int::"char" AS x;
    SELECT x::int FROM foo;
     x 
    ---
    0
    
Evan Carroll
fonte

Respostas:

11

1 chr(10)

... produz o caractere LINEFEED (também conhecido como sequência de escape \n) e o psql exibe o caractere com uma nova linha (indicada por +). Tudo correto lá.

2. & 3. ascii()produz 128 ou 192?

Começa com um erro que cometi. Eu assumi "char"que descuidadamente cobriria o intervalo de um número inteiro de 1 byte não assinado (0 a 255) na resposta referenciada (agora corrigida), mas na verdade é o intervalo de um número inteiro de 1 byte (-128 a 127) assinado internamente.

ascii()usa um textparâmetro, a conversão implícita de "char"para textproduz um caractere codificado com vários bytes no unicode e a função retorna (de acordo com a documentação emascii() ):

Código ASCII do primeiro caractere do argumento. Para UTF8, retorna o ponto de código Unicode do caractere. Para outras codificações multibyte, o argumento deve ser um caractere ASCII.

Portanto, obtemos muitos valores truncados. 128 e 192 são valores de bytes para o byte inicial de caracteres multibyte.

4. O byte nulo

A incapacidade de armazenamento de bytes nulos só afeta os tipos de caracteres regulares ( text, char, varchar), não "char". Isso se aplica ao meu exemplo de buggy, porque lancei textcomo trampolim. Ao transmitir entre "char"e integerdiretamente, a limitação não se aplica. O manual sobre chr():

O caractere NULL (0) não é permitido porque os tipos de dados de texto não podem armazenar esses bytes.

Não é o caso para "char", onde 0é mapeado para a string vazia '':

SELECT ''::"char"::int  -- 0
     , 0::"char" = '';  -- t

Lembre-se: "char"ainda é um tipo "interno" destinado à enumeração simples e barata. Não foi oficialmente projetado para o que estamos fazendo aqui e não é portátil para outros RDBMS. Não há garantias pelo projeto Postgres para isso.

Erwin Brandstetter
fonte
Eu ainda acho que o resultado da exibição psqlé um bug ou algo estranho. Ele termina a linha e depois pula uma linha?
Evan Carroll
4
@ Evan não, não 'pula uma linha', a linha em branco é a continuação da linha anterior (que é multilinha). Se você conseguisse que o psql desenhasse linhas horizontais entre as linhas de saída, isso seria mais óbvio, mas como você não pode, a pista visual é o '+'.
Jack diz que tente topanswers.xyz 5/17 /
0

Para mudar para o intervalo assinado, você pode criar algumas funções para ajudar na assistência. Esta lista criará funções que não são convertidas para ajudar no processo de passar de um intervalo int não assinado de um byte[0-255] para um intervalo assinado de um byte que o caractere exige[-128,127] .

Exemplo

Um extrato do README

Agora você pode, por exemplo, armazenar os valores no intervalo da [0-255]tabela.

CREATE TABLE t(x) AS VALUES
  (to_uchar(255)),
  (to_uchar(0));

Converta-os em bit(8)

SELECT to_bit8(x) FROM t;
 to_bit8  
----------
 11111111
 00000000
(2 rows)

Talvez você queira limpar os dois bits de ordem inferior, você pode fazer isso com BITWISE-AND,

UPDATE t
  SET x = to_uchar( to_bit8(x) & (x'fc')::bit(8) );

SELECT to_bit8(x) FROM t;
 to_bit8  
----------
 11111100
 00000000
(2 rows)
Evan Carroll
fonte